最近在学习深度学习,看李沐老师的b站视频,光是开头的线性回归的代码实现便看了好多遍。
由于本人水平不高,基础不厚实,在阅读py代码时出现了极大问题。幸运的是有评论区,弹幕区,dive into deep learning等网络资源帮助我的理解。
特此整理,线性回归从零开始的代码实现。既帮自己复习回顾,也向别的初学同学分享一下我的思路。
1 根据设定的true_w,true_b生成数据集
2 利用数据集进行训练, 得到w和b
3 观察w和true_w , b和true_b的误差,以验证训练效果
4 极大增强了初学者的自信心(狗头
1 初始化参数,获得随机的w和b
2 计算该w和b下的损失值
3 计算梯度,利用梯度下降法,调整w和b
4 重复2和3,重复次数是epoch*example_nums/batch
(轮数 * 样本数/每次的批量数目)
注意点:
对数据集进行乱序,避免对相同数据集进行重复训练
#后续用到random.shuffle方法实现对列表内元素乱序
import random
#主要是用于张量的数据结构和数学操作(算张量蹭蹭快那种)
import torch
什么是张量(tensor)?
移步百度:张量,向量,矩阵,标量…的概念
#传入的w是true_w,b是true_b,num_example是样本数量
#y=xw+b
#生成数据集
def create_data(w, b, nums_example):
#生成 均值0 标准差1 形状1000*2 的张量X
X = torch.normal(0, 1, (nums_example, len(w)))
#生成y,y的表达式是y=xw+b
y = torch.matmul(X, w) + b
#增加噪声,否则太理想化
y += torch.normal(0, 0.01, y.shape)
#此时y的形状是行向量,需要将其转化为列向量方便后续计算
#返回值是特征值X和预测值y,X和y都是张量
#在本代码中,返回值的X具体形状是1000*2 y形状是1000*1
return X, y.reshape(-1, 1)
根据给定的w和b生成数据集
true_w = torch.tensor([2, -3.4])
true_b = 4.2
#生成数据集
#features特征值 labels标签(预测值)
#这里的特征值不是线性代数里的特征值
features, labels = create_data(true_w, true_b, 1000)
补充:torch.matmul()的用法
torch.matmul是张量的乘法
1 如果输入都是一维时,则是向量点积
2 如果输入都是二维时,则是普通的矩阵乘法,和tensor.mm函数用法相同(大一线性代数学的矩阵乘法)。
3 如果第一个tensor是二维的,第二个tensor是一维的,则将第二个tensor扩充到二维(维数后面插入长度为1的新维度),然后执行矩阵乘法,得到结果后再将此维度去掉,得到的结果与第二个tensor的维度相同。(本文代码属于这种情况)
4 如果第一个tensor是一维的,第二个tensor是二维的,则将第一个tensor扩充到二维(维数后面插入长度为1的新维度),然后执行矩阵乘法,得到结果后再将此维度去掉,得到的结果与第一个tensor的维度相同。
小技巧:
如果tensor1是二维,形状是1000 * 2。tensor2是一维,形状是2。
然后执行matmul(tensor1,tensor2),只需要把一维的tensor2的形状看成是2 * 0就好了。
得到1000 * 0的张量也就是一维的向量。
5 其他情况,暂时用不到
# 读数据集
# 参数:批量数目 特征值 预测值
# 本代码指定批量数目是10
def read_data(batch_size, features, lables):
nums_example = len(features)#计算样本的数量(行数)
indices = list(range(nums_example))#生成0-999的range元组,将其转为列表
random.shuffle(indices) # 将列表中的所有元素乱序
#为什么要乱序?因为使得每一轮epoch中,不出现相同的批次
#对数据集进行扫描(遍历)
for i in range(0, nums_example, batch_size): # range(start, stop, step(步长))
index_tensor = torch.tensor(indices[i: min(i + batch_size, nums_example)])#取min:防止溢出(数组越界)
#将列表切片转成tensor
yield features[index_tensor], lables[index_tensor]
# 张量也可以切片
# 取出index_tensor对应的位置的元素组成新的张量
#tensor[tensor]->new tensor
设置批量数目
#设置批量数目是10
batch_size = 10
补充:yield关键字的用法
点击进入同站链接 【python】【关键字】yield的用法和相关知识
输入特征值 返回计算结果值
# 定义返回值模型
def net(X, w, b):
return torch.matmul(X, w) + b
输入预测值y_hat和实际值y ,返回损失值(损失值是形状与y_hat同的张量)
例:张量 y_hat和y整形后的形状是1000**1 ,根据函数,
相同位置的元素作差再平方再除以2,
得到新张量的对应位置的元素,
这个张量的形状也是是1000*1。
# 定义损失函数
def loss(y_hat, y):
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
# 为什么要加 reshape:?两个张量形状不一样
# y_hat_shape:torch.Size([10, 1])
# y_shape: torch.Size([10])
梯度下降法:s=s-l*▽
l(learningrate):学习率(步长) , ▽:梯度
l过小:收敛缓慢
l过大:锯齿效应
# 定义优化算法
def sgd(params, batch_size, lr):
with torch.no_grad():
# with torch.no_grad() 主要用于停止autograd(自动求导)模块的工作
# 可见https://zh-v2.d2l.ai/chapter_preliminaries/autograd.html
for param in params:
param -= lr * param.grad / batch_size
## 如果用param = param - lr * param.grad / batch_size会导致导数丢失, zero_()函数报错,可能和自动求导的底层原理有关
# 更正:y=y+? 这个新的y的地址不是原先y的地址了
# 但是y+=? y的地址不变 应该是由于地址变化的原因导致错误
param.grad.zero_()
# 每次都把梯度清零 不然会累加
## 导数如果丢失了,会报错‘NoneType’ object has no attribute ‘zero_’
##初始化参数
# 给w和b赋值,requires_grad参数设置为True,反向传播时,该tensor就会自动求导
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
# 训练模型
lr = 0.03#设置学习率为0.03
num_epochs = 3#设置训练轮数为3
for epoch in range(0, num_epochs):
for X, y in read_data(batch_size, features, labels):
f = loss(net(X, w, b), y)
# `f`形状是(`batch_size`, 1),是张量。
# `f`中的所有元素加到一起,并计算关于[`w`, `b`]的梯度
f.sum().backward()#获取反向传播参数
sgd([w, b], batch_size, lr) # 使用参数的梯度更新参数
with torch.no_grad():
#with torch.no_grad的作用:
#在该模块下,所有计算得出的tensor的requires_grad都自动设置为False。
train_l = loss(net(features, w, b), labels)
print("w {0} \nb {1} \nloss {2:f}".format(w, b, float(train_l.mean())))
print("w误差 ", true_w - w, "\nb误差 ", true_b - b)
# 由于loss的返回值是一个张量,每个元素都是差值的平方。
# train.mean求得是均方误差。
import random
import torch
#构造数据集
def create_data(w, b, nums_example):
X = torch.normal(0, 1, (nums_example, 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 = 4.2
features, labels = create_data(true_w, true_b, 1000)
# 读数据集
def read_data(batch_size, features, lables):
nums_example = len(features)
indices = list(range(nums_example))
random.shuffle(indices)
for i in range(0, nums_example, batch_size): # range(start, stop, step)
index_tensor = torch.tensor(indices[i: min(i + batch_size, nums_example)])
yield features[index_tensor], lables[index_tensor]
batch_size = 10
# 定义模型
def net(X, w, b):
return torch.matmul(X, w) + b
# 定义损失函数
def loss(y_hat, y):
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
# 定义优化算法
def sgd(params, batch_size, lr):
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
##初始化参数
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
# 训练模型
lr = 0.03
num_epochs = 3
for epoch in range(0, num_epochs):
for X, y in read_data(batch_size, features, labels):
f = loss(net(X, w, b), y)
f.sum().backward()
sgd([w, b], batch_size, lr) # 使用参数的梯度更新参数
with torch.no_grad():
train_l = loss(net(features, w, b), labels)
print("\nloss{0:d} {1:f}".format(epoch,float(train_l.mean())))
print("w误差 ", true_w - w, "\nb误差 ", true_b - b)