前言
最近在跟着李沐老师学习深度学习,听着课跟着老师的思路走的时候感觉很简单,自己动手就不断踩坑,磕磕绊绊加看着老师的代码,耗时一下午,终于写出了一版自己的(数据划分那里直接copy的老师的哈哈哈哈,菜鸟不会写)。附上学习链接:http://【08 线性回归 + 基础优化算法【动手学深度学习v2】-哔哩哔哩】 https://b23.tv/ItY64i9
代码书写流程:
第一步:生成数据集
这里线性函数假设为 y = Xw + b + 随机噪声
def create_data(w, b, num_datas):
"""create:y = Xw + b + 噪声。"""
X = torch.normal(0, 1, (num_datas, len(w)))
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape)
return X, y.reshape((-1, 1))
true_w = torch.tensor([[2], [-3.4]])
true_b = torch.tensor([4.2])
features, labels = create_data(true_w, true_b, 1000)
第二步:构建数据集读取函数
def data_split(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
# 这些样本是随机读取的,没有特定的顺序
random.shuffle(indices)
# 划分标准是根据索引来的
# 关于 yield 函数,大家可以参考下面的链接
# https://blog.csdn.net/mieleizhi0522/article/details/82142856
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(
indices[i: min(i + batch_size, num_examples)])
yield features[batch_indices], labels[batch_indices]
第三步:初始化模型参数
这一步记得给这两个张量加上requires_grad=True的属性,这样才可以进行下面的自动求导操作,对谁求导,就要给谁加上requires_grad=True属性。
w = torch.tensor([[0.0], [0.2]], requires_grad=True)
b = torch.tensor([0.5], requires_grad=True)
第四步:定义模型
这一步就不用加噪声了
def linear_regression(x0, w0, b0):
return torch.matmul(x0, w0) + b0
第五步:定义损失函数
这里使用的是均方损失
def mse_loss(y_pred, y):
los = pow(y_pred - y, 2) / 2
return los
第六步:定义优化算法
这里使用的是梯度下降算法
def optimize_w(lr, w, b, batch_size):
with torch.no_grad():
w += - lr * w.grad / batch_size
b += - lr * b.grad / batch_size
w.grad.zero_()
b.grad.zero_()
第七步 训练及画图
num_epochs = 20
batch_size = 32
lr = 0.03
Net = linear_regression
all_loss = np.zeros(num_epochs, float)
for epoch in range(num_epochs):
for X, y in data_split(batch_size, features, labels):
y_pred = Net(X, w, b)
loss = mse_loss(y_pred, y)
# l = loss.sum()
loss.sum().backward()
optimize_w(lr, w, b, batch_size)
"""
with torch.no_grad() 后面的操作就是在计算所有数据进行一遍训练后,模型
训练结果的平均损失
"""
with torch.no_grad():
# torch.no_grad() 意味着这里的计算不进行梯度累计
train_l = mse_loss(Net(features, w, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
all_loss[epoch] = train_l.mean()
x = np.arange(1, len(all_loss) + 1, 1, int)
print(x)
plt.plot(x, all_loss)
plt.xlabel("epochs") # 设置 x 轴标签
plt.ylabel("loss") # 设置 y 轴标签
plt.title("loss_curve") # 设置标题
plt.show()
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')
总结
完全不看答案的情况下自己基本构思不出来完整的步骤,因为目前许多python函数和包也都不太会用,写代码的过程中遇到两个很有意思的错误,在这里和大家分享一下。
错误一:element 0 of tensors does not require grad and does not have a grad_fn
这个错误出现在第七步训练时,loss.bacward()的过程中,调试了一下出错具体在loss函数里面,具体的loss函数设计代码如下,个人初步猜想应该是地址的问题,也就是使用los = torch.tensor(pow(y_pred - y, 2)) 计算loss的时候,相当于将pow(y_pred - y, 2)的值赋给了一个新的los变量,这个变量并不在最后自动求导的计算图里面,只有使用los = pow(y_pred - y, 2) / 2,才会将loss的结果记录在自动求导的计算图里面。
def mse_loss(y_pred, y):
"""
# 使用报错: element 0 of tensors does not require grad and does not have a grad_fn
los = torch.tensor(pow(y_pred - y, 2)) 得到结果值为tensor(number)
"""
# los = pow(y_pred - y, 2) / 2通过,没有报上面的错
# 得到结果值为tensor(number,grad_fn=<SumBackward0>)
# 可以得知这个提醒还是很专业的,有grad_fn属性的参数才能反向传播
# 菜鸟看不懂,为什么第一种方式会没有grad_fn
# 难道是相当于重新写了一个tensor张量,梯度属性就没有传递过来
los = pow(y_pred - y, 2) / 2
return los
错误二: 'NoneType' object has no attribute 'zero_'
这个错误出现在程序第六步的优化算法里面,具体的地方是在梯度清零里面,w.grad.zero_()这一行,因为前面赋值的时候:w = w - lr * w.grad / batch_size,和第一个错误应该是一样的,w为一个新的,和原始w地址不同的值,不在自动求导的计算图里面了,因此改成w += - lr * w.grad / batch_size,程序就可以正常运行了。调试后,两者输出类型分别为tensor([[number],[number]])和tensor([[number],[number]],requires_grad=True),可以看出,一个是要求求梯度,一个是没有要求求梯度的,因此在计算时,前者就会报错(没有要求求梯度,怎么会有梯度清零)
with torch.no_grad():
"""
w = w - lr * w.grad / batch_size
b = b - lr * b.grad / batch_size
在 w.grad.zero_() 行报错: 'NoneType' object has no attribute 'zero_'
得到的 w 结果值为tensor([[number],[number]])
"""
# w += - lr * w.grad / batch_size通过,没有报上面的错
# 得到结果值为tensor([[number],[number]],requires_grad=True)
# 可以看出对于求导参数来说,只有有requires_grad=True,才可以执行一系列求导操作
# 包括梯度清零
w += - lr * w.grad / batch_size
b += - lr * b.grad / batch_size
w.grad.zero_()
b.grad.zero_()
原因:运行一些操作可能会导致为结果分配新内存
下面是李沐老师在课程中讲到的一些有关节省内存方面的东西,感觉是我这两个错误出现的原因,在这里贴出来记录一下 ,以后遇到类似问题就可以从这里找解决方法:
还有其他的执行原地操作的方法,具体参考老师讲义中:pytorch/chapter_preliminaries/ndarray.ipynb 最后节省内存一节
完整代码:
import torch
import matplotlib.pylab as plt
import random
import numpy as np
# 线性回归从0开始实现
# 01生成数据集
def create_data(w, b, num_datas):
"""create:y = Xw + b + 噪声。"""
X = torch.normal(0, 1, (num_datas, len(w)))
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape)
return X, y.reshape((-1, 1))
true_w = torch.tensor([[2], [-3.4]])
true_b = torch.tensor([4.2])
features, labels = create_data(true_w, true_b, 1000)
# 画出数据分布
# plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1)
# plt.show()
# 02读取数据集
"""
机器学习训练时会输入大量数据,通常我们需要将整个数据集进行划分然后丢给网络进行训练
这里可以设计一个data_split函数,可以按照特定批次大小划分数据集,分别进行训练,
该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量]。
每个小批量包含一组特征和标签。
"""
def data_split(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
# 这些样本是随机读取的,没有特定的顺序
random.shuffle(indices)
# 划分标准是根据索引来的
# 关于 yield 函数,大家可以参考下面的链接
# https://blog.csdn.net/mieleizhi0522/article/details/82142856
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(
indices[i: min(i + batch_size, num_examples)])
yield features[batch_indices], labels[batch_indices]
# 03初始化模型参数
w = torch.tensor([[0.0], [0.2]], requires_grad=True)
b = torch.tensor([0.5], requires_grad=True)
# 04定义模型
def linear_regression(x0, w0, b0):
return torch.matmul(x0, w0) + b0
# 05定义损失函数
def mse_loss(y_pred, y):
"""
# 使用报错: element 0 of tensors does not require grad and does not have a grad_fn
los = torch.tensor(pow(y_pred - y, 2)) 得到结果值为tensor(number)
"""
# los = pow(y_pred - y, 2) / 2通过,没有报上面的错
# 得到结果值为tensor(number,grad_fn=<SumBackward0>)
# 可以得知这个提醒还是很专业的,有grad_fn属性的参数才能反向传播
# 菜鸟看不懂,为什么第一种方式会没有grad_fn
# 难道是相当于重新写了一个tensor张量,梯度属性就没有传递过来
los = pow(y_pred - y, 2) / 2
return los
# 06定义优化算法
def optimize_w(lr, w, b, batch_size):
with torch.no_grad():
"""
w = w - lr * w.grad / batch_size
b = b - lr * b.grad / batch_size
在 w.grad.zero_() 行报错: 'NoneType' object has no attribute 'zero_'
得到的 w 结果值为tensor([[number],[number]])
"""
# w += - lr * w.grad / batch_size通过,没有报上面的错
# 得到结果值为tensor([[number],[number]],requires_grad=True)
# 可以看出对于求导参数来说,只有有requires_grad=True,才可以执行一系列求导操作
# 包括梯度清零
w += - lr * w.grad / batch_size
b += - lr * b.grad / batch_size
w.grad.zero_()
b.grad.zero_()
# 07训练
num_epochs = 20
batch_size = 32
lr = 0.03
Net = linear_regression
all_loss = np.zeros(num_epochs, float)
for epoch in range(num_epochs):
for X, y in data_split(batch_size, features, labels):
y_pred = Net(X, w, b)
loss = mse_loss(y_pred, y)
# l = loss.sum()
loss.sum().backward()
optimize_w(lr, w, b, batch_size)
"""
with torch.no_grad() 后面的操作就是在计算所有数据进行一遍训练后,模型
训练结果的平均损失
"""
with torch.no_grad():
# torch.no_grad() 意味着这里的计算不进行梯度累计
train_l = mse_loss(Net(features, w, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
all_loss[epoch] = train_l.mean()
x = np.arange(1, len(all_loss) + 1, 1, int)
print(x)
plt.plot(x, all_loss)
plt.xlabel("epochs") # 设置 x 轴标签
plt.ylabel("loss") # 设置 y 轴标签
plt.title("loss_curve") # 设置标题
plt.show()
print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')