线性回归
基本概念
线性模型的形式较为简单,其试图学得一个通过属性的线性组合来进行预测的函数,基本表示如下:
在训练一个模型时,我们需要衡量模型预测值与真实值之间的误差,通常我们会选取一个非负数作为误差,且数值越小表示误差越小。一个常用的选择是平方函数。 它在评估索引为i的样本误差的表达式为:
在获得了预测值与真实值之间的误差之后,我们需要使用这一误差对模型进行优化。当模型和损失函数形式较为简单时,上面的误差最小化问题的解可以直接用公式表达出来。这类解叫作解析解(analytical solution)。本节使用的线性回归和平方误差刚好属于这个范畴。然而,大多数深度学习模型并没有解析解,只能通过优化算法有限次迭代模型参数来尽可能降低损失函数的值。这类解叫作数值解(numerical solution)。
在求数值解的优化算法中,小批量随机梯度下降(mini-batch stochastic gradient descent)在深度学习中被广泛使用。它的算法很简单:先选取一组模型参数的初始值,如随机选取;接下来对参数进行多次迭代,使每次迭代都可能降低损失函数的值。在每次迭代中,先随机均匀采样一个由固定数目训练数据样本所组成的小批量(mini-batch,然后求小批量中数据样本的平均损失有关模型参数的导数(梯度),最后用此结果与预先设定的一个正数的乘积作为模型参数在本次迭代的减小量。公示表示为:
其中学习率是一个较为关键的参数,表示每一次更新的步长大小。
具体实现
在使用代码实现是需要注意的一点是使用矢量计算,将一组数据看做一个pytorch矢量进行计算要比使用for循环去以此计算快得多。
从零实现线性模型
从零实现时需要自己实现模型计算,误差计算以及优化函数,具体代码如下:
# 模型计算函数
def linreg(X, w, b):
return torch.mm(X, w) + b
# 损失计算函数
def squared_loss(y_hat, y):
return (y_hat - y.view(y_hat.size())) ** 2 / 2
# 优化函数
def sgd(params, lr, batch_size):
for param in params:
param.data -= lr * param.grad / batch_size # ues .data to operate param without gradient track
训练时,一个epoch代表一个训练周期,每个epoch当中所有的数据都会被训练一遍,代码如下:
lr = 0.03
num_epochs = 5
net = linreg
loss = squared_loss
# training
for epoch in range(num_epochs): # training repeats num_epochs times
# in each epoch, all the samples in dataset will be used once
# X is the feature and y is the label of a batch sample
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y).sum()
# calculate the gradient of batch sample loss
l.backward()
# using small batch random gradient descent to iter model parameters
sgd([w, b], lr, batch_size)
# reset parameter gradient
w.grad.data.zero_()
b.grad.data.zero_()
train_l = loss(net(features, w, b), labels)
print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))
简洁实现线性模型
在pytorch中已经封装了许多常用的模型模块,我们可以直接使用,如下:
class LinearNet(nn.Module):
def __init__(self, n_feature):
super(LinearNet, self).__init__() # call father function to init
self.linear = nn.Linear(n_feature, 1) # function prototype: `torch.nn.Linear(in_features, out_features, bias=True)`
def forward(self, x):
y = self.linear(x)
return y
net = LinearNet(num_inputs)
同样可以使用pytorch中的方法对模型中的参数进行初始化,并定义损失函数,优化函数训练过程等:
from torch.nn import init
# 初始化参数
init.normal_(net[0].weight, mean=0.0, std=0.01)
init.constant_(net[0].bias, val=0.0)
# 定义损失函数
loss = nn.MSELoss()
import torch.optim as optim
# 定义优化函数
optimizer = optim.SGD(net.parameters(), lr=0.03)
# 训练过程
num_epochs = 3
for epoch in range(1, num_epochs + 1):
for X, y in data_iter:
output = net(X)
l = loss(output, y.view(-1, 1))
optimizer.zero_grad() # reset gradient, equal to net.zero_grad()
l.backward()
optimizer.step()
print('epoch %d, loss: %f' % (epoch, l.item()))
softmax
基本概念
线性模型大部分时候面向的是回归问题,直接计算出预测的数值即可,但在分类问题当中,直接使用输出的结果效果却不是太好,主要有两个问题:
一方面,由于输出层的输出值的范围不确定,我们难以直观上判断这些值的意义。
另一方面,由于真实标签是离散值,这些离散值与不确定范围的输出值之间的误差难以衡量。
因此引入softmax计算可以解决这一问题,公式为:
可以看出,y1+y2+y3=1,因此将每个结果值转换为了[0,1]中的一个数值,可以进行直观地比较。
交叉熵损失
在计算分类问题的损失时,如果仍使用平方差损失估计会过于严格,因为我们其实并不需要预测概率完全等于标签概率。改善上述问题的一个方法是使用更适合衡量两个概率分布差异的测量函数。其中,交叉熵(cross entropy)是一个常用的衡量方法:
更进一步的:
有了这两个工具后,我们可以开始进行分类问题的训练。
具体实现
softmax从零实现
使用pytorch中的广播机制我们可以这样实现:
ef softmax(X):
X_exp = X.exp()
partition = X_exp.sum(dim=1, keepdim=True)
return X_exp / partition # 这里应用了广播机制
运用了softmax的模型为:
def net(X):
return softmax(torch.mm(X.view((-1, num_inputs)), W) + b)
交叉熵损失函数为:
def cross_entropy(y_hat, y):
return - torch.log(y_hat.gather(1, y.view(-1, 1)))
训练过程如下:
num_epochs, lr = 5, 0.1
# 本函数已保存在d2lzh_pytorch包中方便以后使用
def train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size,
params=None, lr=None, optimizer=None):
for epoch in range(num_epochs):
train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
for X, y in train_iter:
y_hat = net(X)
l = loss(y_hat, y).sum()
# 梯度清零
if optimizer is not None:
optimizer.zero_grad()
elif params is not None and params[0].grad is not None:
for param in params:
param.grad.data.zero_()
l.backward()
if optimizer is None:
d2l.sgd(params, lr, batch_size)
else:
optimizer.step()
train_l_sum += l.item()
train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item()
n += y.shape[0]
test_acc = evaluate_accuracy(test_iter, net)
print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f'
% (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc))
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, batch_size, [W, b], lr)
softmax简洁实现
pytorch中封装了现成的softmax函数,可以这样使用:
import torch
from torch.nn import functional as F
y = torch.rand(3, requires_grad=True)
print(y)
# 指定在哪一个维度上进行Softmax操作,比如有两个维度:[batch, feature],
# 第一个维度为batch,第二个维度为feature,feature为一个三个值的向量[1, 2, 3],
# 则我们指定在第二个维度上进行softmax操作,则[1, 2, 3] => [p1, p2, p3]的概率值
# 因为y只有一个维度,所以下面指定的是在dim=0,第一个维度上进行的操作
p = F.softmax(y, dim=0)
多层感知机
基本概念
多层感知机实际上就是指多层的神经网络,如下图:
其中有输入层、隐藏层。输出层。但如果只是对先行计算的单穿叠加的话,其结果仍然是一个线性模型,推导如下:
因此我们需要进行非线性变换的激活函数。常用的激活函数有:ReLU,sigmoid,tanh等,在选择时,ReLu函数是一个通用的激活函数,目前在大多数情况下使用。但是,ReLU函数只能在隐藏层中使用。用于分类器时,sigmoid函数及其组合通常效果更好。由于梯度消失问题,有时要避免使用sigmoid和tanh函数。在神经网络层数较多的时候,最好使用ReLu函数,ReLu函数比较简单计算量少,而sigmoid和tanh函数计算量大很多。在选择激活函数的时候可以先选用ReLu函数如果效果不理想可以尝试其他激活函数。
具体实现
多层感知机从零实现
实现时我们需要定义输入层到隐藏层以及隐藏层到输出层的参数,如下:
num_inputs, num_outputs, num_hiddens = 784, 10, 256
W1 = torch.tensor(np.random.normal(0, 0.01, (num_inputs, num_hiddens)), dtype=torch.float)
b1 = torch.zeros(num_hiddens, dtype=torch.float)
W2 = torch.tensor(np.random.normal(0, 0.01, (num_hiddens, num_outputs)), dtype=torch.float)
b2 = torch.zeros(num_outputs, dtype=torch.float)
params = [W1, b1, W2, b2]
for param in params:
param.requires_grad_(requires_grad=True)
还需要定义激活函数,这里我们使用ReLU函数:
def relu(X):
return torch.max(input=X, other=torch.tensor(0.0))
整个网络模型如下:
def net(X):
X = X.view((-1, num_inputs))
H = relu(torch.matmul(X, W1) + b1)
return torch.matmul(H, W2) + b2
# 损失函数
loss = torch.nn.CrossEntropyLoss()
训练过程如下:
num_epochs, lr = 5, 100.0
# def train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size,
# params=None, lr=None, optimizer=None):
# for epoch in range(num_epochs):
# train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
# for X, y in train_iter:
# y_hat = net(X)
# l = loss(y_hat, y).sum()
#
# # 梯度清零
# if optimizer is not None:
# optimizer.zero_grad()
# elif params is not None and params[0].grad is not None:
# for param in params:
# param.grad.data.zero_()
#
# l.backward()
# if optimizer is None:
# d2l.sgd(params, lr, batch_size)
# else:
# optimizer.step() # “softmax回归的简洁实现”一节将用到
#
#
# train_l_sum += l.item()
# train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item()
# n += y.shape[0]
# test_acc = evaluate_accuracy(test_iter, net)
# print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f'
# % (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc))
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, params, lr)
多层感知机简洁实现
使用pytorch我们可以更专注于网络结构的搭建,而其中的具体内容则可以使用其封装好的模块,如下:
num_inputs, num_outputs, num_hiddens = 784, 10, 256
net = nn.Sequential(
d2l.FlattenLayer(),
nn.Linear(num_inputs, num_hiddens),
nn.ReLU(),
nn.Linear(num_hiddens, num_outputs),
)
for params in net.parameters():
init.normal_(params, mean=0, std=0.01)
训练过程如下:
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size,root='/home/kesci/input/FashionMNIST2065')
loss = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=0.5)
num_epochs = 5
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)
这样就完成了一个多层感知机的构建与训练。