目录
  1. 一、线性模型
  2. 1.1 模型的数学表示
  3. 1.2 衡量预估质量
  4. 1.3 训练数据的表示
  5. 1.4 参数学习
  6. 1.5 线性模型求解
  7. 1.6 线性回归的从零实现
  8. 1.7 线性回归的简单实现
  9. 二、基础优化方法——梯度下降
  10. 三、Softmax回归
  11. 1.1 如何将分类问题转化为回归问题

一、线性模型

1.1 模型的数学表示

  • 输入:x=[x1,x2,,xn]T\textbf x=[x_1,x_2,\dots,x_n]^T
  • 权重:w=[w1,w2,,wn]T\textbf w=[w_1, w_2, \dots, w_n]^T,决定每个特征对预测值的英雄
  • 偏差:bb,决定特征都为0时,预测值应为多少。
  • 输出为输入的加权和y=w1x1+w2x2++wnxn+by=w_1x_1+w_2x_2+\dots+w_nx_n+b
  • 向量形式的输出:y=<w,x>+by=<\textbf w,\textbf x>+b

线性模型可以看作单层神经网络。

1.2 衡量预估质量

衡量预估的质量就是比较真实值和预估值,假设yy是真实值,y^\hat y是估计值,可以有多种形式衡量两者的差距,例如平方损失l(y,y^)=12(yy^)2l(y,\hat y)=\frac{1}{2}(y-\hat y)^2

1.3 训练数据的表示

通常我们会收集一些数据点包括训练样本和对应的真实值,记为:

X=[x1,x2,,xn]T\textbf X=[\textbf x_1,\textbf x_2,\cdots,\textbf x_n]^T

y=[y1,y2,,yn]T\textbf y=[y_1,y_2,\cdots,y_n]^T

其中xn\textbf x_n是列向量,代表每一个样本数据。这样X\textbf X的每一行就是一个样本。

这样所有样本的预测值就可以表示为:

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

1.4 参数学习

训练的损失带入1.2中提到的平方损失公式。

l(w,b)=12(yy^)2=12(wx+by^)l(\textbf w,b)=\frac{1}{2}(y-\hat y)^2=\frac{1}{2}\left(\mathbf{w}^\top \mathbf{x} + b - \hat y\right)

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.

通过令L(w,b)L(\textbf w,b)最小,找到对应的参数w\textbf wbb

1.5 线性模型求解

为了便于计算,令X=[X,1]\textbf X=[\textbf X, 1]w=[w,b]T\textbf w=[\textbf w, b]^T,在X\textbf X的最右侧添加全一的列,在w\textbf w的下面添加标量bb。这样就可以直接计算y=Xw\textbf y = \textbf X \textbf w

带入损失函数:

l(X,y,w)=12nyXw2l(\textbf X,\textbf y,\textbf w)=\frac{1}{2n}||\textbf y-\textbf X \textbf w||^2

wl(X,yw)=1n(yXw)TX\frac{\partial}{\partial \textbf w}l(\textbf X,\textbf y,\textbf w)=\frac{1}{n}(\textbf y-\textbf X \textbf w)^T\textbf X

由于损失函数是凸函数,所以最优解满足梯度为0,可以解得

w=(XTX)1Xy\textbf w^*=(\textbf X^T\textbf X)^{-1}\textbf X\textbf y

1.6 线性回归的从零实现

(1)导入包

import random
import torch
from d2l import torch as d2l    # pip install d2l

(2)构造数据集

创建一个带有噪声的人造数据集,使用真实的wwbb生成数据集及标签。

def synthetic_data(w, b, num_examples):  
    """生成 y = Xw + b + 噪声。"""
    X = torch.normal(0, 1, (num_examples, len(w)))  # 均值为0,方差为1的随机数样本
    y = torch.matmul(X, w) + b
    y += torch.normal(0, 0.01, y.shape)  # 添加均值为0,方差为0.01的噪声
    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)

features的每一行都包含一个二维数据样本,labels中的每一行都包含一个标签值。

(3)读取批量

def data_iter(batch_size, features, labels):
    num_examples = len(features)  # 样本数量
    indices = list(range(num_examples))  # 生成每个样本的index
    random.shuffle(indices)  # 将index随机打乱
    for i in range(0, num_examples, batch_size):  # 步长为batch_size
	    # 获取i到i+batch_size的下标
        batch_indices = torch.tensor(indices[i:min(i + batch_size, num_examples)])
        # 每次yield返回一个值,下次调用从上次的yield开始
        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

(4)初始化模型参数

w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)

(5)定义模型

def linreg(X, w, b):  
    """线性回归模型。"""
    return torch.matmul(X, w) + b

(6)定义损失函数

def squared_loss(y_hat, y):  
    """均方损失。"""
    return (y_hat - y.reshape(y_hat.shape))**2 / 2

(7)定义优化算法

def sgd(params, lr, batch_size):  
    """小批量随机梯度下降。"""
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size
            param.grad.zero_()

(8)训练过程

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)
        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.7 线性回归的简单实现

(1)导入包

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

true_w = torch.tensor([2, -3.4])
true_b = 4.2
# 利用人工数据合成函数,生成样本和标签
features, labels = d2l.synthetic_data(true_w, true_b, 1000)

(2)调用框架中现有的API读取数据

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)

# 下面代码用于在屏幕显示读取到的数据,打包成iter迭代器,然后依次读取
next(iter(data_iter))

(3)定义模型

from torch import nn

# 将线性层放到Sequential容器中
net = nn.Sequential(nn.Linear(2, 1))  # 指定输入维度2,输出维度1
net[0].weight.data.normal_(0, 0.01)  # 第0层线性层 -> 权重w -> 数据 -> 使用正态分布替换data
net[0].bias.data.fill_(0)  # 第0层线性层 -> 偏差b -> 数据 -> 填充0

(4)均方误差

loss = nn.MSELoss()

(5)随机梯度下降

trainer = torch.optim.SGD(net.parameters(), lr=0.03)

(6)训练过程

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}')

二、基础优化方法——梯度下降

当一个模型没有显式解时,一般通过这种方法求解。

  1. 挑选一个初始值w0w_0
  2. 在训练中重复迭代参数:wt=wt1ηlwt1w_t=w_{t-1}-\eta\frac{\partial l}{\partial w_{t-1}}η\eta:学习率)

也就是说让ww沿梯度方向(数值下降最快的方向)进行参数更新来找到最优解。类似于下山时一直找坡度最陡的方向走,最终到达山脚(最优解)。

(1)超参数1:学习率

其中的学习率代表每一次参数更新的大小,学习率太小参数更新很慢,学习率太大模型会震荡找不到最优解。

(2)超参数2:批量大小

由于在整个训练集上计算梯度时间消耗太大,可能需要数分钟乃至数个小时。

因此通常随机采样bb个样本来近似训练集的损失。

同样批量大小不能太大,也不能太小。如果批量太小,不适合并行计算利用GPU资源,如果批量太大,内存消耗增加,浪费计算。

三、Softmax回归

上面提到的回归,目的是估计一个连续值。而分类问题是用来预测一个离散类别。Softmax回归虽然名字叫回归,但其实是一个分类问题。

常见的分类问题包括:手写数字识别、自然物体分类、蛋白质显微镜图片分类、恶意软件分类、恶意评论分类。

对比回归与分类

回归分类
单连续值数值输出多个输出
跟真实值的区别作为损失输出i是预测i的置信度

1.1 如何将分类问题转化为回归问题

(1)类别编码

对于n个类别,可以用一维有效编码来编码。那么标号就是长为n的向量,每个类别的标号向量分别是y1,y2yn\textbf y_1,\textbf y_2\dots \textbf y_n

其中第i个类别的标号是yi=[0,0,1,00]\textbf y_i=[0,0,1,0\dots 0],其中只有第i个元素是1,其余元素都是0。

(2)确定损失函数

在Softmax中可以选择使用均方损失函数训练。

(3)进行预测

y^=argmax oi\hat y=argmax\ o_i

其中oio_i是每个类别的置信度,argmax oiargmax\ o_i是置信度最大的类别的索引。这样就能找到置信度最大的那个类别。

进行检测的关键是使对于正确类的置信度远大于其它类别。通常需要保证oyoiΔ(y,i)o_y-o_i\ge\Delta(y,i)

(4)置信度转换为概率

我们获得了所有类别的置信度o=[o1,o2,,on]\textbf o=[o_1,o_2,\dots,o_n]

但是置信度只是预测的物体与每个类别的匹配程度,而非识别为该物体的概率,具体转换方法如下:

y^=softmax(o)\hat{\textbf y}=softmax(\textbf o)

y^i=exp(oi)kexp(ok)\hat y_i=\frac{exp(o_i)}{\sum_k exp(o_k)}

这样就实现了置信度o\textbf o的所有元素非负,且和为1。

然后将真实的概率y\textbf yy^\hat {\textbf y}的区别作为损失。