python
def sgd(params, lr, batch_size): #@save
"""小批量随机梯度下降"""
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
sgd([w, b], lr, batch_size)
上述代码中,param遍历params的所有元素,并进行sgd梯度下降,但是其仅仅对param进行修改,为何params元素会随之改变?
于是我进行实验,发现无论是最简单的数组还是np.array(ndarray格式),都是只修改了param而不修改params,如下图所示:
于是询问copilot:
然而为何源码中的params会改变?继续询问:
继续:
由 你 发送:
为什么一个是副本,一个是引用,两者有什么区别,如何判断
由 Copilot 发送:
这是一个很好的问题!在 Python 中,变量是如何传递的取决于它们的类型和使用方式。让我们详细解释一下副本和引用的区别,以及如何判断它们:
副本
当你创建一个变量的副本时,你实际上是在内存中创建了一个新的对象,并将原始对象的值复制到这个新对象中。修改副本不会影响原始对象。
例如:
Python
a = 10
b = a # 创建副本
b += 5
print(a) # 输出 10
print(b) # 输出 15
.
在这个例子中,a
和 b
是两个独立的变量,修改 b
不会影响 a
。
引用
当你创建一个变量的引用时,你实际上是在创建一个指向原始对象的指针。修改引用会直接影响原始对象。
例如:
Python
a = [1, 2, 3]
b = a # 创建引用
b.append(4)
print(a) # 输出 [1, 2, 3, 4]
print(b) # 输出 [1, 2, 3, 4]
在这个例子中,a
和 b
指向同一个列表对象,修改 b
会直接影响 a
。
如何判断
判断一个变量是副本还是引用,主要取决于变量的类型和操作方式:
- 不可变对象(如整数、字符串、元组):赋值操作通常会创建副本。
- 可变对象(如列表、字典、集合):赋值操作通常会创建引用。
在你的代码中:
Python
for param in params:
param -= lr * param.grad / batch_size
param
是 params
列表中的元素的引用,因此修改 param
会直接影响 params
列表中的元素。
于是可以肯定,数据类型不同,同样的语句,params会出现修改和不修改两种情况,见下图:
可见tensor类型是可以通过该语句直接修改params的值,而ndarray则不行。