1. 从0开始实现线性回归 (不借助pytorch的框架)
目录
2.2 读取数据集 使用next函数和iter函数对读取数据进行迭代展示
1.1 数据集生成(带有噪声来拟合真实数据)
import torch
import numpy as np
import matplotlib.pyplot as plt
def synthetic_data(w, b, num_examples):
""" 生成 y = X * w + b + 噪声 """
X = torch.normal(0, 1, (num_examples, len(w)))
y = torch.matmul(X, w) + b
'''这里生成了一个均值为0,标准差为0.01,维度形态为y.shape的噪声加在本来为完全拟合的分布'''
y += torch.normal(0, 0.01, y.shape)
'''这里的-1代表无添加,即自动转化填入数值 '''
return X, y.reshape((-1, 1))
1.1.1 明显X为一个n * len(w)的二维张量,而w是一个一维的张量,他们之间怎么实现矩阵乘法函数,从而与b组合(这里一维和多维的基本运算会以广播形式进行拓展)形成一维的y呢?
由pytorch中的matmul运算规则 可知,一维张量会在允许计算的情况下对应拓展一维单层,然后运算,运算结果如果出现了单层的维度,就会降维,因此y这里直接变成了n个元素的单维度张量
这里直接定义好真实的w和b的值,并利用上面函数生成1000个数量的数据集
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
1.1.2 可以通过散点图的方式,看到两个特征关于y的线性关系,这里为了能使用matplotlib绘图,在pytorch中需要把张量通过detach函数,将tensor从计算图剥离,然后才能使用numpy函数转化为矩阵传入plt ——》pytorch 中 detach函数的作用 ——它作为叶子节点仍指向之前tensor,具体可以看链接
plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1)
plt.scatter(features[:, 0].detach().numpy(), labels.detach().numpy(), 1)
1.2 批量读取数据集——Mini-batch思想
1.2.1 这里其实经过测试发现,无需将抽出指定大小的列表转化为tensor,也可以传入features中,获取对应迭代器位置的元素,但是李沐老师原文进行了转化,保持一个疑问
def data_iter(batch_size, features, labels):
'''len此类函数在一个多维张量或者矩阵里面,返回的是第0维的属性,这里返回的是X数据量'''
num_examples = len(features)
'''然后对正常数据的迭代器生成列表并打乱迭代器'''
indices = list(range(num_examples))
random.shuffle(indices)
'''从0->x总数量n进行迭代,步长为我们想要的batch数'''
for i in range(0, num_examples, batch_size):
'''这里巧妙的min可以在最终组数据不够的时候返回的是最后一个节点的下标'''
batch_indices = torch.tensor(indices[i: min(i + batch_size, num_examples)])
print(batch_indices.type)
'''yield类似return 返回一个值,并且记住这个返回的位置,下次迭代从这个位置开始'''
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
1.3 初始化模型参数 模型 损失函数
w = torch.normal(0, 0.01, (2, 1), requires_grad = True)
b = torch.zeros(1, requires_grad = True)
def linreg(X, w, b):
"""线性回归模型"""
return np.dot(X, w) + b
def squared_loss(y_hat, y): #@save
"""均方损失"""
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
1.4 定义优化算法
小批量随机梯度下降在每一步中,使用从数据集中随机抽取的一个小批量,然后根据参数计算损失的梯度。 接下来,朝着减少损失的方向更新我们的参数。 下面的函数实现小批量随机梯度下降更新。 该函数接受模型参数集合、学习速率和批量大小作为输入。每 一步更新的大小由学习速率lr
决定。 因为我们计算的损失是一个批量样本的总和,所以我们用批量大小(batch_size
) 来规范化步长,这样步长大小就不会取决于我们对批量大小的选择。
def sgd(params, lr, batch_size):
"""小批量随机梯度下降"""
for param in params:
'''这里切片是因为传入参数包括w和b两个参数'''
param[:] = param - lr * param.grad / batch_size
1.5 训练模型
从刘二大人的视频可以学到,整个训练就是分为参数设置,迭代轮次,批量处理(如果使用mini-batch),正向传播(损失计算),反向传播,更新参数,梯度清0,梯度更新
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.requires_grad_(True)
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}')
1.5.1 当不需要在一些地方计算梯度,仅仅是取得数值的时候,我们可以使用with torch.no_grad() 《with torch.no_grad() 详细解析》去提高运行速度,当然链接里还提到使用torch.set_grad_enabled()也可以进行相同操作
1.5.2 当然,我们可以直接根据我们已知的真实参数和我们模型计算出来的参数进行求差值,得到实际的误差,不过我们不应该追求最完美的参数,这可能会导致过拟合,而应该去追求一个泛化能力最强的模型
print('w 的估计误差为 : {}'.format(true_w - w.reshape(true_w.shape)))
print('b 的估计误差为 : {}'.format(true_b - b))
2. 借助深度学习框架简洁搭建上面的线性回归模型
2.1 数据集生成
import numpy as np
import torch
from torch.utils import data
def synthetic_data(w, b, num_examples):
""" 生成 y = X * w + b + 噪声 """
X = torch.normal(0, 1, (num_examples, 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 = synthetic_data(true_w, true_b, 1000)
2.2 读取数据集 使用next函数和iter函数对读取数据进行迭代展示
2.2.1 其次这里涉及到了TensorDataset函数和DataLoader函数
def load_array(data_arrays, batch_size, is_train=True):
"""构造一个PyTorch数据迭代器"""
dataset = data.TensorDataset(*data_arrays)
return data.DataLoader(dataset, batch_size, shuffle=is_train)
batch_size = 10
data_iter = load_array((features, labels), batch_size)
'''使用函数对生成的数据进行迭代展示'''
next(iter(data_iter))
2.3 定义模型
2.3.1 在PyTorch中,全连接层在Linear
类中定义。 值得注意的是,我们将两个参数传递到nn.Linear
中。 第一个指定输入特征形状,即2,第二个指定输出特征形状,输出特征形状为单个标量,因此为1。
我们这里使用到了Sequential函数,它的作用类似于封装一个我们自定义的模型,可以将我们写好的网络一层一层连接实例化 torch.nn.Sequential()讲解
# nn是神经网络的缩写
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
2.4 初始化模型参数 定义损失函数 定义优化算法
2.4.1 normal和fill加下划线都是将原本data的值进行填充替换的作用
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
'''这里MSE也就是所谓的L2范数'''
loss = nn.MSELoss()
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
2.5 训练
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}')
2.5.1 当然我们可以像之前一样对它的误差进行判断
w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)