线性回归的从零开始实现

https://zh.d2l.ai/chapter_linear-networks/linear-regression-scratch.html

2 Likes

sgd函数没有返回值,里面的变量都是临时变量

param -= lr * param.grad / batch_size

上面这行代码只是在函数里做更新,更新的也只是临时变量,而且sgd函数也没有返回值,是怎么更新到w和b的呢?

4 Likes

你可以认为这边的 param 是对 [w, b] 的引用,更新 param 就改变了 wb。参见 Why are variables in Python different from other programming languages’?

4 Likes

python函数传递的是一个引用,你可以当作C里的指针,再看看python的基础语法吧 :smiley:

2 Likes

我的观点是这样的:
不应该用传递的参数是不是引用来解释,其实这关系到变量的作用域(scope)问题,即函数内的变量是全局的(global)还是局部的(local)。

当某一变量var在函数外面已经声明时 (如var=v0),函数内部默认var为全局变量且可以访问该变量,除非在函数内部有修改变量var的行为(如重新赋值 var=v1 或者代数运算 var=var+v1 等)。在这种修改变量的情况下,变量var会被定义为局部变量并被重新分配内存,它在函数内部的变化不会影响到外部的全局变量var的值(即var=v0保持不变)。

以上规则无论变量有没有被传入函数都适用,即 f(var…) 或者 f(…)。

特殊之处在于本节sgd中使用的运算符(-=)会执行原地操作(in-place operation),也就是运算结果会赋给同一块内存。由于params本身就是全局变量,修改后的结果仍然赋给它的内存,所以变化的也就是全局变量了。如修改为 param = param - … 结果就不对了,大家可以试一试。

类似的运算符还有 +=, *=, /= … 关于 in-place operation的讲解可以参考之前的 2.1.5节 :“节省内存”。

21 Likes

造成你困惑的最主要原因的核心是“可变对象与不可变对象”。
对于函数中的for循环,param得到的是列表中元素的引用,这没有问题。
但是呢,会不会就地改变(直接作用到变量)这得看具体的实现。
“-=”操作符会调用__isub__函数,而"-"操作符会调用__sub__函数,一般对于可变对象来说“-=”操作符会直接改变self自身。对于pytorch来说,应该会调用sub_函数(找了一下源码,翻到C源码,没找到具体代码在哪)。
举个例子

import torch

x1 = 1
x2 = 2
params = [x1, x2]
for p in params:
    print(id(p), id(x1), id(x2))
    p -= 4
    print(id(p), id(x1), id(x2))
print(params)

x1 = torch.Tensor([1])
x2 = torch.Tensor([2])
params = [x1, x2]
for p in params:
    print(id(p), id(x1), id(x2))
    p -= 4
    print(id(p), id(x1), id(x2))
print(params)

你会得到(你自己运行的话,id得到的地址会不一样)

9784896 9784896 9784928
9784768 9784896 9784928
9784928 9784896 9784928
9784800 9784896 9784928
[1, 2]
139752445458112 139752445458112 139752445458176
139752445458112 139752445458112 139752445458176
139752445458176 139752445458112 139752445458176
139752445458176 139752445458112 139752445458176
[tensor([-3.]), tensor([-2.])]

可以看到对于int类型,地址变换了,而torch类型,地址却没有变化。
p -= 4等价于p.sub_(4)。这个可变对象改变了自身。而若如vin100提到的写成p = p - 4则会调用构造函数,并返回一个新的变量,也就不可能作用到原先的“可变对象”。
int类没有发生就地变化是因为它是一个不可变对象。

24 Likes

param -= lr * param.grad / batch_size
这里的param和param.grad能够相运算前提是两者的shape是一样的,那么无论f(x)是怎样的,x.grad和x是否都是shape相等,这是怎么保证的,因为从矩阵求导的定义无法理解,这与y.sum().backward()是否有关。求教

x.grad与x shape是一样的。标量关于向量的求导,梯度的shape等于x的shape。

2 Likes

想问一下为什么需要d2l这个库,比如说画图,matplotlib不是已经够用了吗,重新去用一个新的库的意义在哪

2 Likes

Why it seems smaller batch_size, with all other parameters no changed, the loss is smaller?
With batch_size=5

epoch 1,loss 0.000701
epoch 2,loss 0.000053
epoch 3,loss 0.000051
w has diff: tensor([-1.3564e-03, -1.0967e-05, 1.7130e-04, -4.7803e-05],
grad_fn=)
b has diff: tensor([0.0018], grad_fn=)

With batch_size=10

epoch 1,loss 0.029642
epoch 2,loss 0.000512
epoch 3,loss 0.000059
w has diff: tensor([-9.8300e-04, -6.9952e-04, -1.5647e-03, -8.0585e-05],
grad_fn=)
b has diff: tensor([0.0052], grad_fn=)

With batch_size=50

epoch 1,loss 0.864528
epoch 2,loss 0.387332
epoch 3,loss 0.174557
w has diff: tensor([-0.0904, -0.1257, -0.1239, -0.1213], grad_fn=)
b has diff: tensor([0.8005], grad_fn=)

With batch_size=100
epoch 1,loss 1.446548
epoch 2,loss 0.911614
epoch 3,loss 0.626752
w has diff: tensor([-0.1589, -0.1924, -0.2357, -0.2119], grad_fn=)
b has diff: tensor([1.5956], grad_fn=)

The total training should be the same, batch_size * data division = total training data.
For batch_size=100, we have 10 data divisions, but still 1000 training samples.

为什么要使用dl2包,而不是直接使用plt包?个人认为对画图工具的接口熟悉也很重要啊。如果dl2的画图接口与plt包类似为什么不直接使用?如果不类似,则更有必要去掌握plt包了。

3 Likes

When you select a larger batch_size the total number the parameter updates shrink sharply. the model is under-fitting. you should try to change epoch to a large number so that the loss arrived at the optimum.

2 Likes

我认为是D2L里面帮我们install并import了一系列库,例如pandas, numpy, matplotlib。就不用写那么多行代码了而已

6 Likes

true_w = [2, -3.4] 请问这是什么意思吖

1 Like

for X, y in data_iter(batch_size, features, labels):
print(X, ‘\n’, y)
break
请问这段代码的break起到什么作用?

就是输出第一组随机的iter,如果不加break,就会把所有的数据都遍历并输出一次

2 Likes

train_l = loss(net(features, w, b), labels)
请问这行代码中的loss和net函数的定义是什么,我在前面没有找到这两个

params: param -= lr * param.grad / batch_size
这里参数的更新为什么梯度要除以批量大小?

3 Likes

w(weights)是权重,这里是设置真实权重

1 Like
net = linreg
loss = squared_loss

3.2.7 有定义哈

3 Likes