动手学深度学习v2
Day 2
0x00 线性回归
简单且流行的基本回归模型
对n维输入
x
n
x_n
xn的加权
w
n
w_n
wn再加偏差
b
b
b
- 模型举例
y y y = w 1 x 1 w_1x_1 w1x1 + w 2 x 2 w_2x_2 w2x2 + w 3 x 3 w_3x_3 w3x3 + b b b
上述线性模型(可拓展到全部线性模型),可以看做单层的神经网络
输入的维度是d 输出维度为1
上图看出一个输入层和一个输出层,带权重的层只有输入层一层,因此叫单层网络
-
平方损失函数
l ( y , y ^ ) = 1 2 ( y − y ^ ) 2 l(y, \hat y) = \frac 1 2(y - \hat y)^2 l(y,y^)=21(y−y^)2
以上求解结果越小区别越小,结果越大区别越大 -
训练
利用现有数据制作训练集,通常情况下越多越好
线性模型有显示解析解,但实际过程基本不会有线性问题出现,不追求显示解 -
优化方法
- 梯度下降
一个模型没有显示解的时候,随机挑选一个初始值(任意初始值),接下来不断沿着梯度下降的方向更新参数,最终获取到最优解。
w t = w t − 1 − η ∂ l ∂ w t − 1 w_t = w_{t-1} - \eta \frac {\partial l} {\partial{w_{t-1}}} wt=wt−1−η∂wt−1∂l
η \eta η : 学习率(超参数:需要人工设置的值)
∂ l ∂ w t − 1 \frac {\partial l} {\partial{w_{t-1}}} ∂wt−1∂l : 梯度
太小计算次数太多复杂度高,太大产生震荡现象而拿不到最优解
-
小批量随机梯度下降(默认算法)
直接使用梯度下降太过于复杂,需要对整个训练集数据操作。
采样b个样本来近似全部数据,b是另一个重要的超参数-采样批量大小(batch) -
总结
不断地沿着反梯度方向(梯度方向是增长最快的方向)更新参数
b b b和 η \eta η是很重要超参数
0x01 手动实现
1.训练数据准备
这里的函数synthetic_data实现了一个带有噪声的线性模型如下:
y
=
w
x
+
b
+
c
y = wx + b + c
y=wx+b+c
取
w
w
w =
[
2
,
−
3.4
]
T
[2, -3.4]^T
[2,−3.4]T
b
=
4.2
b = 4.2
b=4.2
import torch
import random
# 此函数会根据输入的w,b的值成对输出y x 我们用作训练集数据
def synthetic_data(w, b, num_examples):
x = torch.normal(0, 1, (num_examples, len(w)))
y = torch.matmul(x, w) + b # pytorch的乘法
y += torch.normal(0, 0.01, y.shape) # 加上噪声
return x, y.reshape((-1, 1))
输入w,b的真值测试一下,生成1000个
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
# 输出看一下
for i in range(1000):
print("数据 ", features[i], " 标签 ", labels[i])
部分输出如下:
数据 tensor([1.9494, 1.3384]) 标签 tensor([3.5615])
数据 tensor([ 1.0765, -0.0897]) 标签 tensor([6.6455])
数据 tensor([-0.3591, -0.9325]) 标签 tensor([6.6370])
数据 tensor([-1.2803, -0.2001]) 标签 tensor([2.3254])
数据 tensor([-1.3962, 0.8183]) 标签 tensor([-1.3768])
数据 tensor([ 0.5650, -0.4530]) 标签 tensor([6.8746])
数据 tensor([-0.5573, -0.3996]) 标签 tensor([4.4393])
...
2.抽取batch
# 这里实现的是,每次从训练集中抽取一个batch的数据进行训练
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_size个数据取一个,可以做到不重复抽取且每个数据都会用到
batch_indices = torch.tensor(indices[i: min(i + batch_size, num_examples)]) # 防止最后一组没有一个batch_size的数据
'''
yield 生成器 学识浅薄惭愧 从没用过的python关键字
查了下暂时理解成return 每次需要时候迭代一次 比list省内存
每次需要用到下一个的时候才调用
'''
yield features[batch_indices], labels[batch_indices]
以下代码测试输出:
batch_size = 10
for x, y in data_iter(batch_size, features, labels):
print(x)
print(y)
部分输出如下:
tensor([[-0.4891, -0.2155],
[-0.4724, 2.2419],
[ 0.8081, -0.0244],
[ 0.1264, 0.8761],
[ 1.5874, 2.0016],
[ 1.8350, 0.3115],
[ 0.2307, -1.1173],
[-1.0151, -0.4539],
[ 0.0415, -0.4983],
[ 0.7697, 0.4390]])
tensor([[ 3.9540],
[-4.3682],
[ 5.8896],
[ 1.4842],
[ 0.5696],
[ 6.8205],
[ 8.4443],
[ 3.7195],
[ 5.9762],
[ 4.2288]])
...
3.模型设置
很容易理解,实现一个线性模型:
y
=
w
x
+
b
y = wx + b
y=wx+b
def linreg(x, w, b):
return torch.matmul(x, w) + b
4.损失函数
定义上述均方根损失函数
def squard_loss(y_hat, y):
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2 # 这里的reshape目的是为了统一计算,过程中可能一个行向量一个列向量,也可以在之前的步骤中规定好
5.优化算法
小批量随机梯度下降算法的手动实现
# lr 学习率
# batch_size 批量大小
def sgd(params, lr, batch_size):
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size # 之前没有求均值,在这里求
param.grad.zero_() # 防止梯度累加
6.训练验证
# 定义超参数
lr = 0.03 # 学习率
num_epochs = 3 # 数据扫三遍
# 以下是为了多个网络方便编程,感觉像是函数别名
net = linreg
loss = squared_loss
# w b 都需要记录梯度
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
for epoch in range(num_epochs):
for x, y in data_iter(batch_size, features, labels):
l = loss(net(x, w, b), y)
l.sum().backward() # 这里l形状(batch_size, 1) 不是标量,因此计算梯度利用sum()计算
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-string语法 {}内直接显示为变量 :f是保留6位精度
print(f"w的估计误差:{true_w - w.reshape(true_w.shape)}")
print(f"b的估计误差:{true_b - b}")
结果如下:
epoch 1, loss 0.034460
epoch 2, loss 0.000128
epoch 3, loss 0.000056
w的估计误差:tensor([0.0005, 0.0001], grad_fn=<SubBackward0>)
b的估计误差:tensor([5.6744e-05], grad_fn=<RsubBackward1>)
0x02 pytorch实现
1.准备数据集
import torch
import torch.utils import data
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
2.构造了一个pytorch的数据迭代器
- TensorDataset() 传入两个参数分别是 样本数据,样本标签 并根据第一个张量(样本数据)的维度返回一一对应的样本
- Dataloader 随机取batch_size个数据
def load_array(data_arrays, batch_size, is_train=True):
# 此处*将列表元素分别当做两个参数传入 即features 和 labels
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)
print(next(iter(data_iter)))
输出如下:
# 此处为了清晰我手动调整了格式
[
tensor([[-0.1956, -0.9790],
[-0.8501, 1.4546],
[ 0.0941, 1.3185],
[ 1.6486, -0.5259],
[ 0.9986, 0.7224],
[ 0.0788, -0.0592],
[-0.7319, 0.0070],
[-1.3904, -1.5994],
[ 0.0679, 0.9106],
[ 0.1502, -1.1432]]),
tensor([[ 7.1412],
[-2.4247],
[-0.0891],
[ 9.2879],
[ 3.7425],
[ 4.5828],
[ 2.7132],
[ 6.8642],
[ 1.2361],
[ 8.3878]])
]
3.模型定义
from torch import nn # 这个类有大量定义好的层
# 此处Squential是将这个线性层放到一个list中,深度神经网络中更方便
net = nn.Squential(nn.Linear(2, 1)) # Linear全连接层、线性层
4.损失函数和优化算法
nn.MSELoss() # 均方误差损失函数
torch.optim.SGD(网络参数, 学习率) # 小批量随机梯度下降算法
5.训练验证
# 初始化参数
net[0].weight.data.normal_(0, 0.01) # w
net[0].bias.data.fill_(0) # b
# 均方误差 平方L2范数
loss = nn.MSELoss()
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
# 次数
num_epochs = 3
# 训练
for epoch in range(num_epochs):
for x,y in data_iter: # 拿出一个批次数据
l = loss(net(x), y) # net已经初始化w、b无需传入 net(x) 是预测值 y是标签
trainer.zero_grad() # 梯度清零
l.backward() # 计算梯度 pytorch自动做了sum操作
trainer.step() # 更新模型w b
l = loss(net(features), labels) # 计算一下偏差
print(f"epoch {epoch + 1}, loss {l:f}")
输出如下:
epoch 1, loss 0.000309
epoch 2, loss 0.000104
epoch 3, loss 0.000105