一、线性回归
1.优化问题
(1)若样本量为
n
n
n,解释变量为
d
d
d,则线性回归模型为
y
i
=
∑
j
=
1
d
x
i
j
w
j
+
b
+
ϵ
i
⟺
y
=
X
w
+
b
+
ϵ
y_i=\sum_{j=1}^d x_i^j w_j+b+\epsilon_i\ \Longleftrightarrow \mathbf{y}=\mathbf{Xw}+\mathbf{b}+\mathbf{\epsilon}
yi=j=1∑dxijwj+b+ϵi ⟺y=Xw+b+ϵ
其中,
ϵ
i
∼
N
(
0
,
σ
2
)
\epsilon_i\sim\mathcal{N}(0,\sigma^2)
ϵi∼N(0,σ2).令
X
=
[
1
x
1
1
⋯
x
1
d
1
x
2
1
⋯
x
2
d
⋮
⋮
⋮
1
x
n
1
⋯
x
n
d
]
w
=
[
b
w
1
⋯
w
d
]
T
\mathbf{X}=\begin{bmatrix} 1 & x_1^1 &\cdots & x_1^d\\ 1 & x_2^1 & \cdots& x_2^d\\ \vdots & \vdots & &\vdots\\ 1 & x_n^1 & \cdots & x_n^d \end{bmatrix}\quad \mathbf{w}=\begin{bmatrix} b & w_1 & \cdots & w_d\end{bmatrix}^T
X=
11⋮1x11x21⋮xn1⋯⋯⋯x1dx2d⋮xnd
w=[bw1⋯wd]T
则有
y
=
X
w
+
ϵ
\mathbf{y}=\mathbf{Xw}+\mathbf{\epsilon}
y=Xw+ϵ
(2)优化问题
min
w
,
b
L
(
w
,
b
)
=
1
2
∥
y
−
X
w
−
b
1
n
∥
2
\min_{\mathbf{w},b} L(\mathbf{w},b)=\frac{1}{2}\|\mathbf{y}-\mathbf{Xw}-b\mathbf{1}_n\|^2
w,bminL(w,b)=21∥y−Xw−b1n∥2
(3)随机梯度下降
通过计算目标函数的梯度更新参数,但由于更新参数前需要遍历整个数据集,其运行速度会很慢,故在每次需要计算更新的时候随机抽取一小批样本(小批量随机梯度下降)。
- 给出参数初始值;
- 从数据集中随机抽取小批量样本B;
- 计算小批量损失均值关于模型参数的梯度;
- 将梯度乘以学习率 η \eta η,从当前参数值中减去; ( w , b ) ← ( w , b ) − η ∣ B ∣ ∑ i ∈ B ∂ ( w , b ) l i ( w , b ) (\mathbf{w},b)\leftarrow (\mathbf{w},b)-\frac{\eta}{|B|}\sum_{i\in B}\partial_{(\mathbf{w},b)}l^i(\mathbf{w},b) (w,b)←(w,b)−∣B∣ηi∈B∑∂(w,b)li(w,b)
- 重复第2-4步。
2.代码实现
(1)生成小批量。输入批量大小、特征矩阵、标签向量,生成大小为batch_size的小批量。
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(
indices[i: min(i + batch_size, num_examples)])
yield features[batch_indices], labels[batch_indices]
此种方法在存储与运行速度方面不占优。
(2)随机梯度下降算法,每次迭代梯度需要手动设为0,才能使下一次和上一次不相关。
def sgd(params, lr, batch_size): #@save
"""小批量随机梯度下降"""
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
(3)学习率 η \eta η的选取,太小会使收敛速度较慢,太大可能使得梯度计算出现问题。
3.Pytorch版本的实现。
在PyTorch中,data模块提供了数据处理工具,nn模块定义了大量的神经网络层和常见损失函数。
(1)生成小批量:
def load_array(data_arrays, batch_size, is_train=True): #@save
"""构造一个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)
is_train表示是否希望数据迭代器对象在每个迭代周期内打乱数据。
(2)定义模型:
- Sequential类将多个层串联在一起。 当给定输入数据时,Sequential实例将数据传入到第一层, 然后将第一层的输出作为第二层的输入,以此类推。
- 在PyTorch中,全连接层在Linear类中定义。nn.Linear(input.size, output.size).
- 通过net[0]选择网络中的第一个图层, 然后使用weight.data和bias.data方法访问参数。 还可使用替换方法normal_和fill_来重写参数值。
net[0].weight.data.normal_(0, 0.01) net[0].bias.data.fill_(0)
(3)定义优化算法(指定学习率=0.03)
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
(4)训练
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() #优化器已做sum处理
trainer.step() #模型更新
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')
二、softmax回归(用于分类问题)
1.softmax函数与交叉熵损失
(1)softmax函数定义
y
^
=
s
o
f
t
m
a
x
(
o
)
,
w
i
t
h
y
^
j
=
exp
(
o
j
)
∑
k
exp
(
o
k
)
\hat{\mathbf{y}}=softmax(\mathbf{o}),\quad with\quad \hat{y}_j=\frac{\exp(o_j)}{\sum_{k}\exp(o_k)}
y^=softmax(o),withy^j=∑kexp(ok)exp(oj)
可见,
∑
j
y
j
=
1
\sum_j y_j=1
∑jyj=1.
(2)交叉熵,用于衡量分布之间的差异
H
(
p
,
q
)
=
−
∑
i
p
i
log
(
q
i
)
H(\mathbf{p},\mathbf{q})=-\sum_{i}p_i\log(q_i)
H(p,q)=−i∑pilog(qi)
(3)损失函数及其导数
损失函数:
l
(
y
,
y
^
)
=
−
∑
j
=
1
q
y
j
log
y
^
j
=
−
∑
j
=
1
q
y
j
log
exp
(
o
j
)
+
log
(
∑
k
=
1
q
exp
(
o
k
)
)
∑
j
=
1
q
y
j
=
log
∑
k
=
1
q
exp
(
o
k
)
−
∑
j
=
1
q
y
j
o
j
\begin{align*}l(\mathbf{y},\hat{\mathbf{y}})&=-\sum_{j=1}^q y_j\log{\hat{y}_j}\\ &=-\sum_{j=1}^q y_j\log{\exp(o_j)}+\log\left(\sum_{k=1}^q \exp(o_k)\right)\sum_{j=1}^q y_j\\ &=\log\sum_{k=1}^q \exp(o_k)-\sum_{j=1}^q y_j o_j \end{align*}
l(y,y^)=−j=1∑qyjlogy^j=−j=1∑qyjlogexp(oj)+log(k=1∑qexp(ok))j=1∑qyj=logk=1∑qexp(ok)−j=1∑qyjoj
关于任意为规范化的预测
o
j
o_j
oj的导数
∂
o
j
l
(
y
,
y
^
)
=
s
o
f
t
m
a
x
(
o
)
j
−
y
j
\partial_{o_j} l(\mathbf{y},\hat{\mathbf{y}})=softmax(\mathbf{o})_j-y_j
∂ojl(y,y^)=softmax(o)j−yj
2.代码实现
(1)图像数据
图片转张量代码,且其值均在0~1之间。
trans = transforms.ToTensor()
在调用 d2l.load_data_fashion_mnist(batch_size) 时出现报错 name ‘transforms’ is not defined,在torch.py中对应函数中代码改成
trans = torchvision.transforms.ToTensor()
问题得到解决。
(2)根据公式定义softmax
def softmax(X):
X_exp = torch.exp(X)
partition = X_exp.sum(1, keepdim=True) #保持维度不变
return X_exp / partition # 这里应用了广播机制
(3)定义softmax回归模型
def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
将原始图像展平为向量。
(4)根据公式定义损失函数
def cross_entropy(y_hat, y):
return - torch.log(y_hat[range(len(y_hat)), y])
(5)分类精度
argmax可以获得每行中最大元素的索引。
(6)训练
updater 中可定义优化算法,可供调参的变量时迭代次数(num_epochs)和学习率(lr)。
(7)预测
3.简洁实现
(1)初始化模型参数
需在Sequential中添加一个带有10个输出的全连接层
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))
(2)softmax的实现
前面定义的softmax函数代码,从计算角度来看,其中的指数可能会造成数值稳定性问题(溢出),故变化为
log
(
y
^
j
)
=
log
(
exp
(
o
j
−
max
(
o
k
)
)
∑
k
exp
(
o
j
−
max
(
o
k
)
)
)
=
o
j
−
max
(
o
k
)
−
log
(
∑
k
exp
(
o
j
−
max
(
o
k
)
)
)
\begin{align*} \log(\hat{y}_j)&=\log\left(\frac{\exp(o_j-\max(o_k))}{\sum_k \exp(o_j-\max(o_k))}\right)\\ &=o_j-\max(o_k)-\log\left(\sum_k \exp(o_j-\max(o_k))\right) \end{align*}
log(y^j)=log(∑kexp(oj−max(ok))exp(oj−max(ok)))=oj−max(ok)−log(k∑exp(oj−max(ok)))
loss = nn.CrossEntropyLoss(reduction='none')
(3)优化算法
torch内置的小批量随机梯度下降算法
trainer = torch.optim.SGD(net.parameters(), lr=0.1)