跳转至

动手学深度学习 第三章 线性神经网络

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)

定义模型

返回的算式就是要拟合的函数形式

使用框架
from torch import nn

net = nn.Sequential(nn.Linear(2, 1))

定义损失函数

返回值和数据之间的误差计算

使用框架
loss = nn.MSELoss()

注意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_()  # 梯度下降一次后就清空梯度
使用框架
# net.parameters()为从模型中获得要优化的参数
trainer = torch.optim.SGD(net.parameters(), lr=0.03)

训练

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: