线性回归实现
从零开始
将从零开始实现整个方法, 包括数据流水线、模型、损失函数和小批量随机梯度下降优化器
导包
%matplotlib inline
import random
import torch
from d2l import torch as d2l
- random:导入random包用于随机初始化权重
- d2l:将用过的或者实现过的算法放在d2l的包里面
- matplotlib inline:在plot的时候默认是嵌入到matplotlib中
- 报错 No module named ‘matplotlib’ :在命令行中使用 pip install matplotlib 安装 matplotlib 包即可
- 报错 No module named ‘d2l’ :在命令行中使用 pip install d2l 安装 d2l 包即可,安装完成之后可能需要重新打开程序才能生效
生成数据集
def synthetic_data(w, b, num_examples):
"""生成y=Xw+b+噪声"""
X = torch.normal(0, 1, (num_examples, len(w)))#生成均值为0,方差为1,数量为num_examples,列数为len(w)的随机数
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape) #生成一个均值为0,方差为0.01,和y.shape相同的随机数
return X, y.reshape((-1, 1)) # 把y作为列向量返回
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
- 构造人造数据集的好处是知道真实的 w 和 b
- X = torch.normal(0,1,(num_examples,len(w))):X 是一个均值为 0 ,方差为 1 的随机数,他的行数等于样本数,列数等于 w 的长度
- y += torch.normal(0,0.01,y.shape):给 y 加上了一个均值为 0 ,方差为 0.01 形状和 y 相同的噪声
- return X,y.reshape((-1,1)):最后把 X 和 y 做成一个列向量返回
- true_w:真实的 w
- true_b:真实的 b
- features,labels = synthetic_data(true_w,true_b,1000),根据函数来生成特征和标注
数据可视化
d2l.set_figsize()
d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1)
- detach():在pytorch的一些版本中,需要从计算图中detach出来才能转到numpy中去
- scatter函数最后一个参数应该是图中点的大小
生成大小为batch_size
的小批量
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
random.shuffle(indices)
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]
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
print(X, '\n', y)
break
- batch_size:批量大小
- num_examples:样本数
- random.shuffle(indices):将下标打乱,实现对样本的随机访问
- for i in range(0,num_examples,batch_size):从 0 开始到样本数结束,每次跳批量大小
- yield features[batch_indices],labels[batch_indices]:通过indices,每次产生随机顺序的特征和其对应的随即顺序标号
- yield是python中的一个迭代器
- 在主函数部分,for遍历一遍就break了,导致在data_iter内部的for部分仅迭代了一次,i = 0。
- 如果把break删除,可以发现,data_iter会循环num_examples/batch_size次
定义模型,将模型的输入和参数同模型的输出关联起来
def linreg(X, w, b):
"""线性回归模型"""
return torch.matmul(X, w) + b
定义损失函数
def squared_loss(y_hat, y):
"""均方损失"""
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
- y_hat:预测值
- y:真实值
- 虽然 y_hat 和 y 元素个数是一样的,但是可能他们一个是行向量一个是列向量,因此需要使用reshape进行统一
定义优化算法
def sgd(params, lr, batch_size):
"""小批量随机梯度下降"""
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
- params:给定的所有参数,包含 w 和 b ,他是一个list
- lr:学习率
- param.grad.zero_():手动将梯度设置成 0 ,在下一次计算梯度的时候就不会和上一次相关了
param -= lr * param.grad / batch_size
把w和b参数加上学习率x下降率,得到一个更新更准确的w和b,/batch_size
是因为之前计算损失函数的时候没有平均,现在平均一下
训练过程
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True) #获得一个随机的w:(2,1)
b = torch.zeros(1, requires_grad=True)
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels): #这里会运行1000/10次,加上上一层循环的num_epochs,一共会运行300次
l = loss(net(X, w, b), y) #获取一遍当前w和b对于一定批量X产生的loss,这个时候loss是(10,1)
l.sum().backward() #把loss求和求梯度
sgd([w, b], lr, batch_size) #把we和b传入,此时对于w.grad和b.grad都已经计算好了相应的梯度大小,如果把w.grad打印,应该是一个(2,1)的矩阵,b的是(1,1)的标量
with torch.no_grad():
train_l = loss(net(features, w, b), labels) #这个时候的loss是(1000,1),因为带入的是所有的feature和labels
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
- num_epoch=3:将整个数据扫描三遍
- net:之前定义的模型
- loss:均方损失
- 每一次对数据扫描一遍,扫描的时候拿出一定批量的X和y
![[Notepad_202304091423_51738_edit_88765852083114.png]]
简洁实现
导包+生成数据集
import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)
读取数据
调用框架中现有的API来读取数据
def load_array(data_arrays, batch_size, is_train=True):
"""构造一个PyTorch数据迭代器"""
dataset = data.TensorDataset(*data_arrays) #把一个list传到tensor的dataset里
return data.DataLoader(dataset, batch_size, shuffle=is_train) #通过dataloader随机挑选,shuffle是否要打乱顺序
batch_size = 10
data_iter = load_array((features, labels), batch_size)
next(iter(data_iter)) #iter()将data_iter转换为python的iterator,通过next得到一个X,y
模型定义
使用框架的预定义好的层
from torch import nn #nn定义了大量的层
net = nn.Sequential(nn.Linear(2, 1))
# 定义输入、输出维度,Linear单层神经网络
#Sequetial是一个有序的容器,神经网络模块将按照在传入构造器的顺序依次被添加到计算图中执行
- nn 是神经网络的缩写
- nn.Linear(2,1):输入的维度是2,输出的维度是1
- sequential:可以理解成是层的排列,将一层一层的神经网络进行排列在一起
初始化模型参数
net[0].weight.data.normal_(0, 0.01) #normal使用正态分布替换weight里data的值
net[0].bias.data.fill_(0) #偏差设置为0
- normal_:使用正态分布来替换掉data的值(均值为0,方差为0.01)
- fill_:将偏差直接设置为0
计算均方误差使用的是MSELoss
类,也称为平方
L
2
L_2
L2范数
均方误差损失也是一种比较常见的损失函数,其定义为:
M
S
E
=
1
n
∑
i
n
(
y
i
^
−
y
i
)
2
\mathbf{MSE}=\frac{1}{n}\sum_i^n(\hat{y_i}-y_i)^2
MSE=n1i∑n(yi^−yi)2
loss = nn.MSELoss()
实例化一个SGD
实例(优化函数)
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
- 把参数(包括w和b)和学习率传进去
训练
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X) ,y)
trainer.zero_grad()
l.backward()
trainer.step()
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')