1 前言

1.1 线性回归模型

1、给定n维输入 x=[x1,x2,...,xn]Tx = [x_1,x_2,...,x_n]^Tx=[x1,x2,...,xn]T
2、线性模型有一个n维权重和一个标量偏差 w=[w1,w2,...,wn]T,bw = [w_1,w_2,...,w_n]^T, bw=[w1,w2,...,wn]T,b
3、输出是输入的加权和 y=w1x1+w2x2+...+wnxn+by = w_1x_1 + w_2x_2 + ... + w_nx_n + by=w1x1+w2x2+...+wnxn+b
4、向量版本: y=<w,x>+by = <w,x> + by=<w,x>+b

1.2衡量预估质量:平方损失 L(y,y^)=12(y−y^)2L(y, \hat{y}) = \frac{1}{2} (y - \hat{y})^2L(y,y^)=21(yy^)2

1.3训练数据: X=[x1,x2,...,xn]TX = [x_1,x_2,...,x_n]^TX=[x1,x2,...,xn]T y=[y1,y2,...,yn]Ty = [y_1,y_2,...,y_n]^Ty=[y1,y2,...,yn]T

1.4 参数学习

1、训练损失: L(X,y,w,b)=12n∑i=1n(yi−<xi,w>−b)2=12n∥y−Xw−b∥2L(X,y,w,b) = \frac{1}{2n} \sum_{i=1}^n(y_i - <x_i,w> - b)^2 = \frac{1}{2n} \|\mathbf{y - Xw - b}\|^2L(X,y,w,b)=2n1i=1n(yi<xi,w>b)2=2n1yXwb2
2、最小化损失学习参数: w∗,b∗=arg⁡min⁡w,b L(X,y,w,b)w^*,b^* = \underset{w,b}{\arg\min} \, L(X,y,w,b)w,b=w,bargminL(X,y,w,b)

1.5 梯度下降

1、挑选一个初始值 w0w_0w0
2、重复迭代参数 t = 1,2,3 wt=wt−1−η∂L∂wt−1w_t = w_{t-1} - \eta \frac{\partial L}{\partial w_{t-1}}wt=wt1ηwt1L
3、沿梯度方向将增加损失函数值
4、学习率 η\etaη:步长的超参数

1.6 小批量随机梯度下降

1、在整个训练集上算梯度太贵,一个深度神经网络模型可能需要数分钟甚至数小时
2、我们可以随机采样b个样本 i1,i2,...,ibi_1,i_2,...,i_bi1,i2,...,ib 来近似损失 1b∑i∈IbL(xi,yi,w)\frac{1}{b} \sum\limits_{i \in I_b} L(x_i,y_i,w)b1iIbL(xi,yi,w)
3、b是批量大小,另一个重要的超参数

2 导入所需的函数模块

%matplotlib inline
import random
import torch
from d2l import torch as d2l

注:%matplotlib inline是Jupyter Notebook中的一个魔法指令,可以在Notebook中直接显示Matplotlib绘制的图形。

3 线性回归的从零开始实现

3.1 生成数据集

1、根据带有噪声的线性模型构造一个人造数据集。使用线性模型参数 w=[2,−3.4]Tw = [2,-3.4]^Tw=[2,3.4]Tb=4.2b = 4.2b=4.2和噪声ϵ\epsilonϵ生成数据集及其标签: y=Xw+b+ϵy = Xw + b + \epsilony=Xw+b+ϵ
2、在这个数据集中包含1000个样本,每个样本包含从标准正态分布中抽样的两个特征(feature),并合成一个1000×2的矩阵。
3、我们的任务是:利用人造数据集中有限的样本数据来恢复这个模型的参数。

def synthetic_data(w, b, num_examples):
    X = torch.normal(0, 1, (num_examples, len(w))) #生成均值为0,方差为1的随机数x 有n个样本,列数是w的长度。
    y = torch.matmul(X, w) + b
    y += torch.normal(0, 0.01, y.shape) #加入一个均值为0,方差为1的随机噪音
    return X, y.reshape((-1, 1)) #将X和y作为列向量返回

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)

注:
1、因为数据集中的所有样本和参数都是人为给定的,因此,利用synthetic_data()函数返回的features和labels均可看作是真实值。
2、XXX是一个1000×2的矩阵,wTw^TwT是一个2×1的向量,再加上标量偏差bbb和噪音ϵ\epsilonϵ得到的labels是一个1000×1的向量。features中的每一行都包含一个二维数据样本,labels中的每一行都包含一个一维标签值(一个标量)。

print('features:', features[0], '\nlabel:', labels[0])

输出为第一个特征的值和对应的标签。

features: tensor([2.1646, 0.8574]) 
label: tensor([5.6126])

3、生成第二个features[:, 1]和labels的散点图。可以直观地观察到两者之间的线性关系。

d2l.set_figsize()
d2l.plt.scatter(features[:, 1].detach().numpy(), 
               labels.detach().numpy(), 1);

3.2 读取数据集

1、定义一个data_iter()函数,该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量。
2、训练模型时,需要对数据集进行遍历,每次抽取小批量样本,对模型进行更新。data_iter()可以打乱数据集中的样本并以小批量的方式获取数据。

def data_iter(batch_size, features, labels):
    num_examples = len(features) #生成的样本数和features的数量相同
    indices = list(range(num_examples)) #给每一个样本一个下标,并保存在list里面
    random.shuffle(indices) #将样本下表打乱,表明样本是随机读取的,没有特定的顺序
    for i in range(0, num_examples, batch_size):
        batch_indices = torch.tensor(
            indices[i:min(i + batch_size, num_examples)]) #把batch_indices找出来,从i开始,每次间隔batch_size个长度,如果超出样本数量则取一个最小值
        yield features[batch_indices], labels[batch_indices] #产生随机顺序的features和随机顺序的labels

注:
1、indices可以看作是每一个样本对应的下标构成的列表,将其用random.shuffle()打乱后,再利用切片读取的下表是随机的,表明样本是随机读取的。
2、在for循环中,每一次可以获得一个大小为batch_size的小批量数据,然后把这些小批量的数据对应的下标找出来,即可获得随机顺序的features和labels。
例如:当我们把batch_size设置为10,循环运行一次就可以获得10个数据。

batch_size = 10

for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break
tensor([[ 0.3391,  1.1902],
        [-1.7334,  0.1414],
        [ 0.2543, -0.6221],
        [ 1.2382, -1.7555],
        [ 0.9319,  0.6179],
        [-1.0558,  1.7874],
        [ 1.2791, -0.2341],
        [ 0.8668, -0.3878],
        [ 1.3831, -0.1020],
        [-1.2343,  1.8012]]) 
 tensor([[ 0.8323],
        [ 0.2453],
        [ 6.8352],
        [12.6649],
        [ 3.9699],
        [-3.9846],
        [ 7.5542],
        [ 7.2678],
        [ 7.3195],
        [-4.3847]])

3.3 初始化模型参数

通过从均值为0、标准差为0.01的正态分布中抽取随机随机数来初始化 www,并将 bbb初始化设置为0。requires_grad=True 保存每次运算的梯度。

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

3.4 定义模型

线性回归模型: y=Xw+by = Xw + by=Xw+b

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

3.5定义损失函数

线性回归模型使用的损失函数为平方损失函数: L(y,y^)=12(y−y^)2L(y, \hat{y}) = \frac{1}{2} (y - \hat{y})^2L(y,y^)=21(yy^)2

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

注:为了避免真实值y和y_hat形状不同,需要将y的形状做一下转换。

3.6定义优化算法——小批量随机梯度下降

1、在每一步中,从数据集中随机抽取一个小批量,根据参数计算损失的梯度,然后不断朝着损失减少的方向更新参数。
2、由于我们计算的损失是一个批量中所有样本损失的总和,因此需要对损失做均值。由于我们人为选择batch_size来规范步长,这就是为什么在损失函数中没有求均值,而是将均值挪到了算法中。

def sgd(params, lr, batch_size): #给定参数、学习率和batch_size
    with torch.no_grad(): #更新的时候不需要参数计算
        for param in params:
            param -= lr * param.grad / batch_size #这里求了均值
            param.grad.zero_()

注:对上述代码进行概括,我们将执行以下迭代
1、计算梯度 g=∂(w,b)1B∑i∈BL(x(i),y(i),w,b)g = \partial_{(w,b)} \frac{1}{B} \sum\limits_{i \in B}L(x^{(i)},y^{(i)},w,b)g=(w,b)B1iBL(x(i),y(i),w,b)
2、更新参数 (w,b)=(w,b)−ηg(w, b) = (w, b) - \eta g(w,b)=(w,b)ηg

3.7 训练过程

1、在每轮(epoch)中,我们使用data_iter()函数遍历整个数据集,并将训练数据集中的所有样本都使用一次,将整个数据集遍历三次。
2、将 X,w,bX, w, bX,w,b放在模型中做预测得到y_hat,并与真实值y做损失,将损失求和并对损失函数关于参数wwwbbb求梯度。
3、利用sgd算法对 w,bw, bw,b进行更新,每更新一次用一个小批量的数据。
4、最后利用最终更新的参数,计算整个数据集上的损失。

#设置超参数
lr = 0.03
num_epochs = 3 #把整个数据扫3遍
net = linreg
loss = squared_loss

#训练实现
for epoch in range(num_epochs): #每一次对数据扫一遍
    for X, y in data_iter(batch_size, features, labels): #每一次拿出一个批量大小的X和y
        l = loss(net(X, w, b), y) #把每一次拿出的X,w,b放进模型里做预测,把预测的y和真实的y做损失。l是一个长为批量大小的向量。
        l.sum().backward() #求和算梯度
        sgd([w, b], lr, batch_size) #用sgd对w和b进行更新
    with torch.no_grad():
        train_l = loss(net(features, w, b), labels)
        print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
epoch 1, loss 0.022432
epoch 2, loss 0.000083
epoch 3, loss 0.000051

注:经过三轮的计算,可见损失越来越低,说明模型是可靠的。还可以与人为设置的真实参数作比较,评估训练的成功程度。由于我们加入了噪音,所以有误差也属于正常现象。

print(f'w的估计误差:{true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差:{true_b - b}')
w的估计误差:tensor([-1.6928e-05, -1.3685e-04], grad_fn=<SubBackward0>)
b的估计误差:tensor([0.0012], grad_fn=<RsubBackward1>)

4 小结

本节内容对线性回归进行了实现,包括初始化参数设定,模型、损失函数的定义,以及小批量随机梯度下降的优化算法。通过对人工数据集中的样本以小批量的方式读取,利用sgd算法对参数不断进行更新,最终实现对模型参数的预测,与真实参数十分接近。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐