参考教程

线性回归

线性模型

  1. 当输入包含dd个特征时,将预测结果y^\hat{y}表示为:

    y^=w1x1+...+wdxd+b.\hat{y} = w_1 x_1 + ... + w_d x_d + b.

  2. 将所有特征放到向量xRd\mathbf{x} \in \mathbb{R}^d中,并将所有权重放到向量wRd\mathbf{w} \in \mathbb{R}^d中:

    y^=wx+b.\hat{y} = \mathbf{w}^\top \mathbf{x} + b.

  3. 用符号表示的矩阵XRn×d\mathbf{X} \in \mathbb{R}^{n \times d}可以引用整个数据集的nn个样本。其中,X\mathbf{X}的每一行是一个样本,每一列是一种特征:

    y^=Xw+b{\hat{\mathbf{y}}} = \mathbf{X} \mathbf{w} + b

损失函数

  1. 回归问题中最常用的损失函数时平方误差函数。当样本ii的预测值为y^(i)\hat{y}^{(i)},其相应的真实标签为y(i){y}^{(i)}时,平方误差可以定义为:

l(i)(w,b)=12(y^(i)y(i))2.l^{(i)}(\mathbf{w}, b) = \frac{1}{2} \left(\hat{y}^{(i)} - y^{(i)}\right)^2.

  1. 为度量模型在整个数据集上的质量,需计算在训练集nn个样本上的损失均值

L(w,b)=1ni=1nl(i)(w,b)=1ni=1n12(wx(i)+by(i))2.L(\mathbf{w}, b) =\frac{1}{n}\sum_{i=1}^n l^{(i)}(\mathbf{w}, b) =\frac{1}{n} \sum_{i=1}^n \frac{1}{2}\left(\mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)}\right)^2.

  1. 训练模型时,寻找一组参数(w,b\mathbf{w}^*, b^*),这组参数能最小化在所有训练样本上的总损失:

w,b=*argminw,b L(w,b).\mathbf{w}^*, b^* = \operatorname*{argmin}_{\mathbf{w}, b}\ L(\mathbf{w}, b).

正态分布与平方损失

  1. 正态分布概率密度函数:

p(x)=12πσ2exp(12σ2(xμ)2).p(x) = \frac{1}{\sqrt{2 \pi \sigma^2}} \exp\left(-\frac{1}{2 \sigma^2} (x - \mu)^2\right).

  1. 通过给定的x\mathbf{x}观测到特定yy的可能性:

P(yx)=12πσ2exp(12σ2(ywxb)2).P(y \mid \mathbf{x}) = \frac{1}{\sqrt{2 \pi \sigma^2}} \exp\left(-\frac{1}{2 \sigma^2} (y - \mathbf{w}^\top \mathbf{x} - b)^2\right).

  1. 根据最大似然估计法,参数w\mathbf{w}bb的最优值是使整个数据集的可能性最大的值:

P(yX)=i=1np(y(i)x(i)).P(\mathbf y \mid \mathbf X) = \prod_{i=1}^{n} p(y^{(i)}|\mathbf{x}^{(i)}).

  1. 改为最小化负对数似然logP(yX)-\log P(\mathbf y \mid \mathbf X)

logP(yX)=i=1n12log(2πσ2)+12σ2(y(i)wx(i)b)2.-\log P(\mathbf y \mid \mathbf X) = \sum_{i=1}^n \frac{1}{2} \log(2 \pi \sigma^2) + \frac{1}{2 \sigma^2} \left(y^{(i)} - \mathbf{w}^\top \mathbf{x}^{(i)} - b\right)^2.

现在我们只需要假设σ\sigma是某个固定常数就可以忽略第一项,因为第一项不依赖于w\mathbf{w}bb。现在第二项除了常数1σ2\frac{1}{\sigma^2}外,其余部分和前面介绍的平方误差损失是一样的。

线性回归从零实现

import random
import torch

def synthetic_data(w, b, num_examples):  #生成数据集
    """生成 y = Xw + 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)

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]
        
# 初始化模型参数
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)

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

def sgd(params, lr, batch_size):  #定义优化算法
    """小批量随机梯度下降。"""
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size
            param.grad.zero_()
            
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.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}')

print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')
#输出
epoch 1, loss 0.024281
epoch 2, loss 0.000082
epoch 3, loss 0.00004
w的估计误差: tensor([0.0005, 0.0003], grad_fn=<SubBackward0>)
b的估计误差: tensor([0.0009], grad_fn=<RsubBackward1>)

线性回归简洁实现

import numpy as np
import torch
from torch.utils import data
from torch import nn

def synthetic_data(w, b, num_examples):  #生成数据集
        """生成 y = Xw + 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)

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)

# 定义模型
net = nn.Sequential(nn.Linear(2, 1))
# 初始化模型参数
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
#定义损失函数
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)
        trainer.zero_grad()
        l.backward()
        trainer.step()
    l = loss(net(features), labels)
    print(f'epoch {epoch + 1}, loss {l:f}')
    
w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)
#输出
epoch 1, loss 0.000376
epoch 2, loss 0.000101
epoch 3, loss 0.000101
w的估计误差: tensor([ 0.0006, -0.0008])
b的估计误差: tensor([5.4359e-05])

softmax回归

softmax运算

为了将未归一化的预测变换为非负并且总和为1,同时要求模型保持可导。

y^=softmax(o)其中y^j=exp(oj)kexp(ok)\hat{\mathbf{y}} = \mathrm{softmax}(\mathbf{o})\quad \text{其中}\quad \hat{y}_j = \frac{\exp(o_j)}{\sum_k \exp(o_k)}

尽管softmax是一个非线性函数,但softmax回归的输出仍然由输入特征的仿射变换决定。因此,softmax回归是一个线性模型。

交叉熵损失函数及其导数

l(y,y^)=j=1qyjlogexp(oj)k=1qexp(ok)=j=1qyjlogk=1qexp(ok)j=1qyjoj=logk=1qexp(ok)j=1qyjoj.\begin{aligned} l(\mathbf{y}, \hat{\mathbf{y}}) &= - \sum_{j=1}^q y_j \log \frac{\exp(o_j)}{\sum_{k=1}^q \exp(o_k)} \\ &= \sum_{j=1}^q y_j \log \sum_{k=1}^q \exp(o_k) - \sum_{j=1}^q y_j o_j\\ &= \log \sum_{k=1}^q \exp(o_k) - \sum_{j=1}^q y_j o_j. \end{aligned}

考虑相对于任何未归一化的预测ojo_j的导数。我们得到:

ojl(y,y^)=exp(oj)k=1qexp(ok)yj=softmax(o)jyj.\partial_{o_j} l(\mathbf{y}, \hat{\mathbf{y}}) = \frac{\exp(o_j)}{\sum_{k=1}^q \exp(o_k)} - y_j = \mathrm{softmax}(\mathbf{o})_j - y_j.

换句话说,导数是我们模型分配的概率(由softmax得到)与实际发生的情况(由独热标签向量表示)之间的差异。从这个意义上讲,与我们在回归中看到的非常相似,其中梯度是观测值yy和估计值y^\hat{y}之间的差异。