深度学习丨线性回归

今天开始学习李沐老师的《动手学深度学习》。李沐老师的课程是免费分享在b站上的(主页:跟李沐学AI),主要教学模式是概念讲解+实验,每节课讲完知识点都会带大家从零实现,对加深理解很有帮助。这系列笔记里的代码大部分都来自李沐等多位老师的《动手学深度学习(pytorch版)》,在看完老师的讲解后再自己手动实现一遍。

一、向量导数

(一)y为标量,x为向量

求梯度grad y。深度学习中,因变量一般为标量。

(二)y为向量,x为标量

分别对x求导,组成一个列向量。

(三)x, y均为向量

分别求梯度,组成矩阵。当x、y维度更高时同理类推。

二、自动求导

(一)计算图

计算图是有向无环图,类似于利用DAG表述表达式

(二)前向传播与反向传播

  • 前向传播是指输入数据通过网络逐层传递并计算预测输出的过程。
  • 反向传播则是用来更新网络参数(权重和偏置)以最小化损失函数的一种优化算法,通常用于梯度下降法中。

三、线性回归基础优化

线性回归方程为:

(一)训练数据

收集数据点并确定回归方程,可采用均方损失函数:

(二)梯度下降

  • 首先挑选初始值w0

  • 该递推式含义为沿损失函数的梯度方向更新参数值,其中η为学习率,由人为选择。通过该递推式迭代出w1、w2、w3。

(三)小批量随机梯度下降

在整个训练集上计算梯度代价过大,可以多次随机选取b个样本来近似损失。

四、线性回归的实现

以下采用小批量随机梯度下降训练一个线性回归模型。本例仅供学习使用,为了方便,数据集直接由人工生成。

(一)生成数据集

1
2
3
4
5
6
7
8
9
def synthetic_data(w, b, num): #生成y=wX+b+噪声(误差)
x = torch.normal(0, 1, (num, len(w))) #num个样本,len(w)个自变量,标准正态
y = torch.matmul(x, w)+b
y += torch.normal(0, 0.01, y.shape) #加入随机误差
return x, y.reshape((-1, 1)) #y变为列向量

true_w = torch.tensor([2,-3.4])
true_b = 4.2 #y=2*x1-3.4*x2+4.2
features, labels = synthetic_data(true_w, true_b, 1000) #得到训练样本

以上代码生成了线性回归模型 的一组样本(样本量n=1000)。通过训练,我们要使估计出的w和b尽可能接近其真实值。

(二)生成多组小批量样本

1
2
3
4
5
6
7
8
9
def data_iter(batch_size, features, labels):#随机抽取大小为batch_size的样本
num = len(features)
indices = list(range(num))#生成下标
random.shuffle(indices)#随机打乱下标
for i in range(0, num, batch_size):#for(int i=0;i<num;i+=batch_size),本例中共循环1000/10=10次
batch_indices = torch.tensor(
indices[i:min(i+batch_size, num)])
yield features[batch_indices], labels[batch_indices]#迭代生成100组样本
batch_size = 10

以上代码按批量大小为10将数据集进行随机划分

(三)定义初始化模型参数

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

(四)定义模型和损失函数

1
2
3
4
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

(五)定义优化算法

1
2
3
4
5
def sgd(params, lr, batch_size):#小批量随机梯度下降
with torch.no_grad():#梯度于训练模块中计算
for param in params:#有w,b两个参数
param -= lr * param.grad / batch_size#参数迭代
param.grad.zero_()

(六)训练模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
lr = 0.03#学习率,可以自己调整,不能太大也不能太小
num_epochs = 3#对训练集进行3次扫描,多次重复可以降低误差
net = linreg
loss = squared_loss
for epoch in range(num_epochs):#对训练集进行3次扫描
for x, y in data_iter(batch_size, features, labels):
l = loss(net(x, w, b), y)#net为回归方程,估计出y_hat,得到损失函数
l.sum().backward()#l为向量,求和并反向传递
sgd([w, b], lr, batch_size)#使用参数的梯度迭代出新的参数
with torch.no_grad():
train_l = loss(net(features, w, b), labels)#用训练出的w,b计算损失
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}')

out:

1
2
3
4
5
epoch 1, loss 0.066226
epoch 2, loss 0.000361
epoch 3, loss 0.000052
w的估计误差:tensor([ 0.0014, -0.0013], grad_fn=<SubBackward0>)
b的估计误差:tensor([0.0016], grad_fn=<RsubBackward1>)

可以看出,每次扫描后参数估计值将更加接近真实值,最终训练得到的参数估计值和实际十分接近。

(七)总结

虽然线性回归模型在实际中很少应用,但也展示了较为完整的深度学习训练过程,作为入门有助于理解深度学习的方法论。出于学习需要,以上流程尽可能手动实现。在实际中,通过API调用可以大大减少代码量。