《动手学深度学习》之线性神经网络

零、数据对象的操作

在pytorch中,数据对象基本都可以用张量(实际上就是一个n维矩阵)来表达。

常见函数/方法:

X.shape:返回一个列表,表示张量每个维度的长度

X.numel:返回张量中元素个数

X.reshape:重新设置张量各个维度大小,可以通过指定某个维度大小为-1来自动计算

torch.zeros or torch.ones:创建一个全零张量or创建一个全1张量

torch.zeros_like or torch.ones_like:参数是一个张量,创建一个与参数大小同的全0张量或全1张量

torch.rand or torch.randn:前者创建一个[0,1]均匀分布的张量,后者创建一个服从标准正态分布的张量

元素逐个操作

Python原生的加减乘除和求幂、判断语句运算都是对张量对应元素进行逐个运算。

torch.exp:是指数运算的逐个运算

X.sum:求和,可以带上参数,对某个维度求和,keepdim=True可以指定保持某个维度不被压缩

1
2
X.sum(axis=[0,1])
X.sum(axis=0)

索引和切片

广播机制

当运算的矩阵维数不足时,pytorch会将矩阵的此维度复制,以满足运算要求。

索引和切片机制大致与python的列表相当,但是pytorch还支持用列表来当索引:

1
2
3
4
X = torch.arange(12).reshape(3,4)
X[[0,2],:]
#tensor([[ 0, 1, 2, 3],
# [ 8, 9, 10, 11]])

矩阵操作

torch.dot:点乘

torch.mv(A, x):矩阵-向量积

torch.mm(A, B):矩阵-矩阵积

torch.norm(u):求范数

一、微积分

梯度

梯度是对一个多元函数的各个变量求偏导数之后获得的n维向量。

xf(x)=[f(x)x1,,f(x)xn]\nabla_{\textbf{x}}f(x) = \left[\frac{\partial{f(\textbf{x})}}{\partial{x_1}}, \dots,\frac{\partial{f(\textbf{x})}}{\partial{x_n}}\right]

求梯度时常使用以下规则

  • xAx=AT\nabla_{\textbf{x}}\textbf{A}\textbf{x} = \textbf{A}^T

  • xxTA=A\nabla_{\textbf{x}}\textbf{x}^T\textbf{A} = \textbf{A}

  • xxTAx=(A+AT)x\nabla_{\textbf{x}}\textbf{x}^T\textbf{A}\textbf{x} = (\textbf{A}+\textbf{A}^T)\textbf{x}

  • xx=xxTx=2x\nabla_{\textbf{x}}\Vert \textbf{x}\Vert = \nabla_\textbf{x}\textbf{x}^T\textbf{x}=2\textbf{x}

自动微分

在pytorch中,可以通过简单的函数调用完成复杂的梯度计算

1
2
3
4
5
X = torch.arange(12,require_grad=True)
# 或在之后显式地写X.require_grad(True)
Y = f(X) # 对X进行一系列计算
Y.backward()
x.grad

前提是y是一个标量,否则需要通过y.sum().backward()来调用

梯度会自动累积,所以必要时需要调用X.grad.zero_()来清空梯度

不需要梯度计算时,可以通过

1
2
3
4
5
6
7
x.grad.zero_()
y = x * x
u = y.detach()
z = u * x

z.sum().backward()
x.grad == u

detach函数来将u当做一个常数,不参与梯度计算.

还可以通过with语句让某部分代码不计算梯度

with torch.no_grad():

二、线性回归

线性回归算是高中就接触的问题了,只不过现在将各个参数和自变量(特征)、因变量(标签)用向量来描述

y=Xw+b\textbf{y}=\textbf{Xw}+\textbf{b}

我们假设一些标签和特征是存在线性关系的,我们希望拟合出的y^\hat{\textbf{y}}使得损失LL最小

L(w,b)L(\textbf{w,b})是关于两个参数的函数,在训练集上,通过调整w,b\textbf{w,b}可以使损失达到满意的值,具体方法是对损失函数求梯度,通过梯度下降的方法来求得极小值。

然而线性回归作为回归问题的最简单的模型,是存在解析解的。

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

实际上,代码中还是用求数值解的方法来一点一点逼近的

线性回归的机器学习代码可以分为大如下步骤:

  • 设定一个待求参数的初始值,可以通过正态分布等方法获得,并定义超参数(学习率η\eta,正则化常数λ\lambda等)。
  • 获取训练集,使用当前模型计算估计值,与真实标签求损失(均方差、范数)。
  • 求损失函数关于参数的梯度,利用梯度求出当前参数应当变化的方向,结合学习率对参数进行修正。
  • 训练完毕后,利用测试集(验证集进行评估)

三、softmax回归

softmax是线性回归问题的一种,softmax回归将输出的标签规定为one-hot标签,即标签向量中只有一个维度是1,其他维度是0;同时对预测值(有正有负)计算softmax值,从而保证归一、非负的性质。

yj=eojieoiy_j=\frac{e^{o_j}}{\sum_i{e^{o_i}}}

而损失函数也通过交叉熵损失来定义

l(y,y^)=yjlogyj^l(\textbf{y},\hat{\textbf{y}})=-\sum{y_j\log\hat{y_j}}

由于y\textbf{y}是独热标签,所以损失函数的计算也较为简单。

代入后

l(y,y^)=logk=1qeokj=1qyjojl(\textbf{y},\hat{\textbf{y}})=\log\sum_{k=1}^qe^{o_k}-\sum_{j=1}^qy_jo_j

求梯度:

ojl(y,y^)=eojk=1qeokyj\partial_{o_j}l(\textbf{y},\hat{\textbf{y}})=\frac{e^{o_j}}{\sum_{k=1}^qe^{o_k}}-y_j

四、利用pytorch框架

定义神经网络

nn.Sequential

初始化数据

此章节提及两种初始化方式

1
2
3
4
5
6
7
8
9
10
#方式1
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)

#方式2
def init_weight(m):
if type(m) == nn.Linear:
nn.init.normal(m.weight,std=0.01)

net.apply(init_weight)

定义损失函数

1
2
3
loss = nn.MSELoss() #均方差损失

loss = nn.CrossEntropyLoss(reduction='none') # 交叉熵损失

优化器(训练器)

1
trainer = torch.optim.SGD(net.parameters(),0.1) # 随机梯度下降

开始训练

1
2
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

《动手学深度学习》之线性神经网络
http://zhouhf.top/2022/09/12/《动手学深度学习》之线性神经网络/
作者
周洪锋
发布于
2022年9月12日
许可协议