Skip to content

动手学深度学习

写在前面:

本文采用的教程为00 预告【动手学深度学习v2】_哔哩哔哩_bilibili

参考书籍为《动手学深度学习》李沐著

感谢上述作者做出的贡献

线性神经网络

线性回归

引入模型

在提到线性回归之前,我们先引入一个简化模型

如果现在你要买一套房子

假设1:影响房价的关键因素是卧室的个数,卫生间个数和居住面积,记为x1,x2,x3

假设2:成交价是关键因素的加权和

y=w1x1+w2x2+w3x3+b

我们将上述概念抽象成一个线性模型

  • 给定一个n维输入 x=[x1,x2,...,xn]T

  • 线性模型有一个n维权重和一个标准偏差

w=[w1,w2,...,wn]T,b
  • 输出是输入的加权和
y=w1x1+w2x2+w3x3+wnxn+b

注:我们可以将线性模型看作是一个单层神经网络

抽象出线性模型后,我们进行预估价值的衡量

  • 比较真实值和预估值,例如房屋售价和估价
  • 假设y是真实值,y^是估计值,我们可以比较
(y,y^)=12(yy^)2

我们把上述公式叫做平方损失(最小二乘法)

注:机器学习的目标就是找到损失函数(代价函数)的最小

训练数据

例如我们可以收集一些数据点来决定参数值(权重和偏差),例如过去6个月卖的房子。我们把这些数据称之为训练数据,通常越多越好

参数学习

  • 训练损失
(X,y,w,b)=12ni=1n(yixi,wb)2=12nyXwb2
  • 最小化损失来学习参数
w,b=argminw,b(X,y,w,b)

显示解

由上述内容我们可知

  • 线性回归是对n维输入的加权,外加偏差
  • 使用平方损失来衡量预测值和真实值之间的差异
  • 线性回归有显示解(能明确用数学表达出来的解)
  • 线性回归可以看作是单层神经网络

梯度下降算法

线性回归的从零开始实现

我们将从零开始优化整个方法,包括数据流水线、模型、损失函数和小批量随机梯度下降优化器

首先我们导入一些程序需要的包

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

本文使用带有噪声的线性模型构造一个人造数据集。 任务是使用这个有限样本的数据集来恢复这个模型的参数。,我们将使用低维数据,这样可以很容易地将其可视化。

我们使用线性模型参数w=[2,3,4]Tb=4.2和噪声项ϵ生成数据集及其标签:

y=Xw+b+ϵ
python
def synthetic_data(w, b, num_examples):  #@save
    """生成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)
    return X, y.reshape((-1, 1))
#定义线性模型的真实权重和偏差
true_w = torch.tensor([2, -3.4])
true_b = 4.2
#生成1000个样本的合成数据
features, labels = synthetic_data(true_w, true_b, 1000)

生成好数据集后,我们对数据集进行读取。训练模型时要对数据集进行遍历,每次抽取一小批量样本,并使用它们来更新我们的模型。 由于这个过程是训练机器学习算法的基础,所以有必要定义一个函数, 该函数能打乱数据集中的样本并以小批量方式获取数据。

python
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(
            #切片操作,获取当前批次的索引范围。如果 i + batch_size 超过了总样本数,则取到末尾。
            indices[i: min(i + batch_size, num_examples)])
        yield features[batch_indices], labels[batch_indices]

在开始用小批量随机梯度下降优化我们的模型参数之前, 我们需要先有一些参数。 在下面的代码中,我们通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重, 并将偏置初始化为0。

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

我们必须定义模型,将模型的输入和参数同模型的输出关联起来。

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

因为需要计算损失函数的梯度,所以我们应该在计算梯度之前先定义损失函数。

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

优化算法我们选用小批量随机梯度下降算法。在每一步中,使用从数据集中随机抽取的一个小批量,然后根据参数计算损失的梯度。 接下来,朝着减少损失的方向更新我们的参数。 下面的函数实现小批量随机梯度下降更新。 该函数接受模型参数集合、学习速率和批量大小作为输入。每 一步更新的大小由学习速率lr决定。 因为我们计算的损失是一个批量样本的总和,所以我们用批量大小(batch size)来规范化步长,这样步长大小就不会取决于我们对批量大小的选择

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

在准备好模型训练的所有需要的元素后,便可以开始实现主要的训练部分了。在每次迭代中,我们读取一小批量训练样本,并通过我们的模型来获得一组预测。 计算完损失后,我们开始反向传播,存储每个参数的梯度。 最后,我们调用优化算法sgd来更新模型参数。

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

多层感知机