当所求变量与其影响因素之间存在线性关系时,可以使用线性回归来建模处理此类问题。在实际生活中常见的有预测房屋价格、气温、销售额等连续值的问题。
以房屋价格预测为例,房价取决于多个因素,这里只考虑面积和房龄两个因素,并假设房价与两个因素之间为线性关系。
设房屋价格为,房屋面积为
,房龄为
,模型预测得到的价格为
。则三个变量之间的关系可以表示为:
和
是权重,
为偏差,三个参数均为标量。预测得到的
和真实价格
之间允许存在一定误差。我们需要找到合适的参数值,使得误差尽可能小。
损失函数
两个变量之间的误差值可以用误差平方和来衡量,位于之间,数值越小效果越好。第
个样本误差可以表示为:
常数使得对平方项求导后常数系数为1,在形式上可以简单一些。给定训练集,误差只与模型参数有关。
在机器学习中,将衡量误差的函数称为损失函数(loss function),用训练集中所有样本误差的平均来衡量模型预测质量,在这里可以表示为:
训练模型时,我们希望找到一组模型参数使得训练样本平均损失最小:
根据平方损失定义线性回归的损失函数,代码如下:
def squared_loss(y_hat, y):
# pytorch 中的 MSELoss没有除以2
# 返回值为向量
return (y_hat - y.view(y_hat.size())) ** 2 / 2
Pytorch在nn模块提供了各种损失函数,并将这些损失函数定义为 nn.Module 的子类。
class L1Loss(_Loss):……
class NLLLoss(_WeightedLoss):……
class GaussianNLLLoss(_Loss):……
class MSELoss(_Loss):……
这里可以直接使用MSELoss均方误差损失作为模型的损失函数。
loss = nn.MSELoss()
优化算法
当确定模型训练的参数后,就可以得到预测结果与真实值
之间的误差,但我们不能保证随意选择的参数可以使得模型性能很好甚至最小,所以模型中的参数值需要不断的调整,但如果是人为改变参数值,这无疑是一个费时费力不讨好的工作。在深度学习中,通常使用小批量随机梯度下降(mini-batch stochastic gradient desent)方法来改变参数值:如可以先随机选取一组参数的初始值,然后对参数多次迭代,使每次迭代都可能降低损失函数的值。每次迭代中,先随机均匀采样一定数目训练数据样本
(mini-batch),然后求样本
的平均损失有关模型参数的导数(梯度),参数在一个迭代的减小量可以表示为:
表示样本个数,如模型训练时设置
,则
,
称为学习率。两个参数都为超参,是人为设定而非模型训练学出来的。少数情况下,超参也可通过模型训练得出。
优化算法可以定义为:
def sgd(params, lr, batch_size):
for param in params:
# 更改 param 时用的是param.data
# 这里自动求梯度模块计算得到的梯度是一个批量样本的梯度和,将它除以批量大小得到平均值
param.data -= lr * param.grad / batch_size
Pytorch的optim模块提供了很多常用的优化算法如SGD、Adam和RMSProp等,不需要我们自己实现梯度下降算法。学习率为0.03的小批量随机梯度下降算法可以表示为:
import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr=0.03)
读取数据
训练模型时,需要每次取出样本量为的数据,可以表示为:
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):
j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)])
yield features.index_select(0, j), labels.index_select(0, j)
pytorch提供了data包来读取数据,上述代码使用data包处理代码量就少很多:
import torch.utils.data as Data
dataset = Data.TensorDataset(features, labels)
data_iter = Data.DataLoader(dataset, batch_size, shuffle=True)
模型定义
线性回归矢量计算表达式实现:
def linreg(X, w, b):
# 使用 mm 函数做矩阵乘法
return torch.mm(X, w) + b
实际使用时,最常见的做法是继承nn.Module,撰写自己的网络/层;一个nn.Module实例应该包含一些层以及返回输出的前向传播forward方法。
使用nn.Module实现一个线性模型:
class LinearNet(nn.Module):
def __init__(self, n_feature):
super(LinearNet, self).__init__()
self.linear = nn.Linear(n_feature, 1)
def forward(self, x):
y = self.linear(x)
return y
训练模型
在训练时,会有多次迭代改变模型中的参数,也就是会有个迭代周期。每个迭代周期中,会使用训练集中的所有样本,但每次只会取
大小的样本,样本包括影响因素(即特征)和变量真实值(即标签)。根据参数
以及输入样本计算得出预测值
,计算损失函数
,得出
函数分别对
的梯度,使用优化算法更新参数。多次训练后,比较学习到的参数和真实参数,应该很接近。
epoch 1, loss 0.000203
epoch 2, loss 0.000073
epoch 3, loss 0.000066
[2, -3.4] Parameter containing:
tensor([[ 2.0001, -3.3991]], requires_grad=True)
4.2 Parameter containing:
tensor([4.2008], requires_grad=True)
训练采用的数据集通过线性回归模型真实权重和偏差
,以及一个随机噪声项
来生成。
lr = 0.03 #学习率
num_epochs = 3 #epoch 迭代周期
net = linreg # 采用线性回归模型
loss = squared_loss # squared_loss为平方和误差函数
for epoch in range(num_epochs): # 训练模型一共需要num_epochs个迭代周期
# 在每一个迭代周期中,会使用训练数据集中所有样本一次(假设样本数能够被批量大小整除)。X
# 和y分别是小批量样本的特征和标签
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y).sum() # l是有关小批量X和y的损失
l.backward() # 小批量的损失对模型参数求梯度
sgd([w, b], lr, batch_size) # 使用小批量随机梯度下降迭代模型参数
# 不要忘了梯度清零
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,可以省去对损失函数、优化函数和数据读取的自定义实现。
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))
# 梯度清零,等价于net.zero_grad()
optimizer.zero_grad()
l.backward()
optimizer.step()
print('epoch %d, loss %f' % (epoch, l.item()))