动手学深度学习 第三章 线性神经网络
3. 线性神经网络
3.2 线性回归的从零开始实现
- 生成数据(还写了个数据生成器,分批次返回数据)
- 定义模型
- 定义损失函数
- 定义优化算法
- 训练
读取数据集
def data_iter(batch_size, features, labels):
num_examples = len(features) # features 长度
# range(n) 生成[0, ..., n - 1] 的序列(不是列表)
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 生成器,可以循环 for X, y in data_iter(...):
yield features[batch_indices], labels[batch_indices]
目标:把整个数据集打乱,按batch_size逐批返回数据,每次返回一组feature+label
参数:batch_size, features, labels
使用框架实现
# is_train: 是否希望数据迭代器对象在每个迭代周期内打乱数据
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)
定义模型
返回的算式就是要拟合的函数形式
使用框架
定义损失函数
返回值和数据之间的误差计算
使用框架
注意loss是一个向量,同时算出了多个数据的loss
定义优化算法
def sgd(params, lr, batch_size):
"""小批量随机梯度下降"""
with torch.no_grad(): # 优化的时候不用计算梯度
for param in params:
param -= lr * param.grad / batch_size # 梯度取个平均
param.grad.zero_() # 梯度下降一次后就清空梯度
使用框架
训练
for epoch in range(num_epochs):
for X, y in data_iter(bstch_size, features, labels):
l = loss(network(X, w, b), y)
# l 的形状是(batch_size, 1)
# 所以把l都粗暴的加起来然后求梯度
l.sum().backward()
sgd([w, b], lr, batch_size())
with torch.no_grad():
train_l = loss(net(features, w, b), labels)
print(...) # 打印每个batch训练完后的loss
使用框架
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}')
softmax 分类任务
损失函数
交叉熵:\(l(y, \hat{y}) = - \sum_i y_i \log \hat{y_i} = -\log hat{y_y}\)
softmax: \(\hat{y_j} = \frac{exp(o_j)}{\sum_{k = 1}^q exp(o_k)}, 其中o_j就是未归一化的输出\)
合起来
\[l(y, \hat{y}) = - \sum_{j = 1}^q y_j \log \frac{exp(o_j)}{\sum_{k = 1}^q exp(o_k)} = \sum_j y_j \log \sum_k exp(o_k) - \sum_j y_j o_j = \log \sum_k exp(o_k) - \sum_j y_j o_j\]
举例:
\(y = [0, 1, 0], \hat{y} = [\frac{o_j}{\sum_i o_i}]= [0.1, 0.7, 0.2]\)
\(l = -(0 \cdot \log 0.1 + 1 \cdot \log 0.7 + 0 \cdot \log 0.2) = - \log 0.7\)
\(\log\)用来把预测的[0,1]范围内的数放大
损失函数求导
对\(o_j\)求导
\[\partial_{o_j} l(y, \hat{y}) = \frac{exp(o_j)}{\sum_k exp(o_k)} - y_j = softmax(o)_j - y_j\]
softmax实现
num_inputs = 784 # 把图片拉成向量
num_outputs = 10 # 输出为10类
# W的形状是(in, out)
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)
# 这里的输入是XW + b后的值,
# X.shape = batchsize*748
# W.shape = 748*10
# 输入.shape = batchsize*10
def softmax(X):
x_exp = torch.exp(X)
partition = X.exp.sum(1, keepdim=True)
return X_exp / partition
# 交叉熵损失函数
# y_hat.shape: batch_size*num_class
# range(len(y_hat)) 生成行索引(遍历所有行)
# 第i行取y[i]的索引
def cross_entropy(y_hat, y):
return -torch.log(y_hat[range(len(y_hat)), y])
cross_entropy(y_hat, y)
def accuracy(y_hat, y):
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1: