线性回归的从零开始实现

lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss

for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y) # X和y的小批量损失
# 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
# 并以此计算关于[w,b]的梯度
l.sum().backward()
sgd([w, b], lr, batch_size) # 使用参数的梯度更新参数
with torch.no_grad():
train_l = loss(net(features, w, b), labels)
print(f’epoch {epoch + 1}, loss {float(train_l.mean()):f}’)
为何再次运行后loss就固定不变了
epoch 1, loss 0.000050
epoch 2, loss 0.000050
epoch 3, loss 0.000050

d2l的包出问题了吗?实验环境就是Google Colab。
前天跑的还好好的,昨天开始图片生成就有问题了。运行到:
d2l.plt.scatter(features[:, (1)].detach().numpy(), labels.detach().numpy(), 1);
这这一步会出现以下问题:

ImportError Traceback (most recent call last)
/usr/local/lib/python3.7/dist-packages/IPython/core/formatters.py in call(self, obj)
332 pass
333 else:
→ 334 return printer(obj)
335 # Finally look for special method names
336 method = get_real_method(obj, self.print_method)

12 frames
/usr/local/lib/python3.7/dist-packages/matplotlib/backends/backend_svg.py in ()
16 import matplotlib as mpl
17 from matplotlib import _api, cbook, font_manager as fm
—> 18 from matplotlib.backend_bases import (
19 _Backend, _check_savefig_extra_args, FigureCanvasBase, FigureManagerBase,
20 RendererBase)

ImportError: cannot import name ‘_check_savefig_extra_args’ from ‘matplotlib.backend_bases’ (/usr/local/lib/python3.7/dist-packages/matplotlib/backend_bases.py)

1 Like

因为他已经训练到一个符合要求的水准了,除非你重置一下w和b
#初始化参数权重w,偏置b=0
w=torch.normal(0,0.01,size=(2,1),requires_grad=True)
b=torch.zeros(1,requires_grad=True)

你好,我也遇到了这个问题,请问是怎么解决的呀?

我觉得就是重命名一个接口把,net指代一切算法,这样你训练的代码就不用调整了,只要把net指向对应的算法即可

因为python的机制不用于C的函数传参,python传进去的是参数本身也可以说是C++中参数的引用,因此在函数中对参数进行操作会影响函数域外的变量
运行如下代码来检验

class Tes(object):
    def __init__(self):
        self.num = 10
        pass

    pass


def func(inc: Tes) -> Tes:
    inc.num += 10
    print(id(inc))
    return inc


def func2(inc):
    inc += 10
    print(id(inc))
    return inc


a1 = Tes()
print(id(a1))
a2 = func(a1)

print(id(a2) == id(a1)) #此输出为True 对于非python基础类型的成立

b1 = 9999
print(id(b1))
b2 = func2(b1)
print(id(b2) == id(b1))  #此输出为False  

这里是为了在计算的时候确保是进行按元素减法

batch_size为标量 所以除法是没问题的
这里可能存在疑问的是当param是w时,其是一个(2,1)的张量 ,其导数也是一个同shape的张量,可以看一下之前的自动求导部分,标量对于矩阵张量求导结果是一个转置的矩阵,即(1,2)的矩阵,这里并不会影响后续计算,据猜测是发生了转置操作以适配原有的w的shape,但是我在进行代码测试时发现此时竟然产生了广播操作

a = torch.ones((2, 1))
b = torch.ones((1, 2)) * 0.6
c=a-b
print(f'被减数shape为{a.shape}\n'
      f'减数的shape为{b.shape}\n'
      f'结果的shape为{c.shape}\n'

此处发生了广播操作

最终解

参数的梯度值和参数本身,其shape是一致的,那么也就不存在之前的广播,但是这又和沐神3之前PPT上的分子布局不同。先埋个坑 等我回来填

test1=torch.randn((1,2),requires_grad=True)
test2=torch.mm(test1,test1.T)
test2.shape
test2.backward()

test1.grad.shape == test1.shape

data_iter的最后一次取的样本个数可能小于batch size,那这样的话在sgd的时候还是除以batch size是不是不合适?感觉还是在计算loss的时候直接给出均值好一点

1 Like

我也想知道,如果不导入dl2,会影响学习吗?或者代码实现吗?

因为优化算法中用了‘-=’,pytorch规定tensor带require_grad 不能进行“+=”或者“-=”(in-place操作),如果不进行in-place操作而是写成param =param-lr * param.grad / batch_size会导致param地址变动,后面对导数清零的时候就会报错,因为此时w.grad已经是’Nonetype’了
with torch.no_grad():封装可以暂时关闭计算图,也就是在语句内进行的张量运算不改变已有的计算图,这样就可以进行in-place操作了
然而这段代码有除此之外的写法吗?求高人指点

1 Like


有大佬能解释一下这里来wx+b的元素中第一个数-0.3525x2+3.4x0.6474+4.2应该是5.69616,但为什么结果是5.6990,同理第二个数应该是0.24564,但结果是0.2538,同理后面的结果都不对,为什么精度这么差,会对后面模型造成影响吗

我当时情况是x1>x2就交换两元素值。也可以理解,如果两个元素交换值之后,梯度确实依然可以计算,但这个梯度相对的就不再是原来的值了,所以会报错

param 有维度的情况下
param -= lr* param.grad
会原地修改param的值

比如:
a = lst[0] # 传的函数数值
a += 1
print(a,lst)

3 [2, 1]

a = lst # 传递引用
a = a*2
print(a,lst)

[2, 1, 2, 1] [2, 1]

我回来填坑了

经过我的测试
梯度的shape与其本身一直是保持一致的 我还测试了向量对矩阵进行求导,最后变量的梯度仍保持原来的shape,那么我们就可以认识 变量的梯度是与其同shape的一个成员。
沐神之前讲的应该是没有进行求和之前的梯度的计算,但是在pytorch中求导都是对输入进行i求和后再进行求导

在绘制features和labels的散点图时,为什么要使用labels.detach().numpy()呢?将detach().numpy()去掉也不会报错呀?比如这样:d2l.plt.scatter(features[:, (1)], labels, 1);

避免重复造轮子,每次用都去导包定义,更方便统一些吧

这个前面课程好像也提到过,改成 x[:] = x - k 应该就可以了

关于你说的shape是否一样的问题,这里在机器学习中尤其是编程实现和数学上矩阵论的定义是不相同的,从矩阵的定义上来说,shape一定是不一样的
LOSS对W求导的时候,我们能要计算的仅仅是loss[i]对w[i]的导数,不是loss[j]对w[i]的导数,前者是我们所计算的梯度也是数学中雅可比矩阵的对角线,但是后者是其他label[j]对w[i]的求导,也就是数学中雅可比矩阵的非对角向量,所以在自动求导的那一节中,我们总是通过SUM函数来将高维度的向量,转换成标量再对向量求导,这里仅仅是因为我们只计算雅可比行了式中对角线的导数

我也有相同的疑问,我就是用的批量和的梯度,但是最后的训练效果比沐神差不少,具体原因未知,