根据房屋的面积(平方米)和房龄(年)来预测该房屋的售出价格(元)。假设房屋价格只与上述两个因素决定。
设房屋的面积为 x1,房龄为 x2 ,售出价格为 y。假设输出与各个输入之间是线性关系:
y^=x1w1+x2w2+b
其中 w1和 w2是权重(weight),b 是偏差(bias),且均为标量。它们是线性回归模型的参数(parameter)。模型输出 yˆ 是线性回归对真实价格 y的预测或估计。我们通常允许它们之间有一定误差。
训练数据集(training data set):收集一系列的真实数据,并用来寻找模型参数,使得模型预测值与真实值误差最小。在这里指一系列房屋的真实售出价格和它们对应的面积和房龄。
样本(sample):一个研究的对象称为一个样本。在这里指一栋房屋。
标签(label):与预测值相对应的真实值。在这里指房屋的真实售出价格。
特征(feature):研究预测值时需要考虑的因素。在这里指房屋的面积和房龄。特征用来表征样本的特征。
损失函数(loss function):衡量误差的函数。
平方误差函数(square loss):损失函数的一种,公式表达:
其中,指预测值,指标签值。通常,我们用训练数据集中所有样本误差的平均来衡量模型预测的质量,即
解析解和数值解:当模型和损失函数形式较为简单时,上面的误差最小化问题的解可以直接用公式表达出来。这类解叫作解析解(analytical solution)。本节使用的线性回归和平方误差刚好属于这个范畴。然而,大多数深度学习模型并没有解析解,只能通过优化算法有限次迭代模型参数来尽可能降低损失函数的值。这类解叫作数值解(numerical solution)。
小批量随机梯度下降(mini-batch stochastic gradient descent):先选取一组模型参数的初始值,如随机选取;接下来对参数进行多次迭代,使每次迭代都可能降低损失函数的值。在每次迭代中,先随机均匀采样一个由固定数目训练数据样本所组成的小批量(mini-batch)B,然后求小批量中数据样本的平均损失有关模型参数的导数(梯度),最后用此结果与预先设定的一个正数的乘积作为模型参数在本次迭代的减小量。在训练上述讨论的问题中,模型的每个参数将做如下迭代:
在上式中,∣B∣代表每个小批量中的样本个数(批量大小,batch size),η称作学习率(learning rate)并取正数。需要强调的是,这里的批量大小和学习率的值是人为设定的,并不是通过模型训练学出的,因此叫作超参数(hyperparameter)。我们通常所说的“调参”指的正是调节超参数,例如通过反复试错来找到超参数合适的值。在少数情况下,超参数也可以通过模型训练学出,在这本书中的第七章讲优化算法的时候会有提及。
%matplotlib inline #*设置作图嵌入显示*
import torch
from IPython import display
from matplotlib import pyplot as plt #*用于作图*
import numpy as np
import random #*导入随机模块*
生成数据集
num_inputs = 2 #*输入特征数为2*
num_examples = 1000 #*设训练数据集样本数为1000*
true_w = [2, -3.4] #*设置权重*
true_b = 4.2 #*设置偏差*
features = torch.randn(num_examples, num_inputs,
dtype=torch.float32) #*随机生成批量样本特征,形状为1000*2的矩阵,随机数服从正态分布*
labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b #*计算label值,形状为1000*1的矩阵*
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), #*计算出label值后,添加噪声项,表示数据集中无意义的干扰。其中噪声项 ϵϵ 服从均值为0、标准差为0.01的正态分布*
dtype=torch.float32)
PS:实际应用中因为图像采集设备、自然环境因素等诸多原因,导致所处理的图像和“本真”图像有差异,这一部分差异就是噪声。这里添加噪声项是为了模拟现实,其实并没有什么实际用处。
读取数据
# *本函数已保存在d2lzh包中方便以后使用,这个包可以在官网中下载*
def data_iter(batch_size, features, labels):#*传入批量大小、特征、标签*
num_examples = len(features)#*计算长度即数据大小*
indices = list(range(num_examples))#*Python3 list() 函数是对象迭代器,可以把range()返回的可迭代对象转为一个列表,返回的变量类型为列表,即把索引拿出来放到indices里*
random.shuffle(indices) # *样本的读取顺序是随机的,将其打乱*
for i in range(0, num_examples, batch_size):#*从索引0开始取,步长为batch_size大小*
j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)]) #*把数据取出来。最后一次可能不足一个batch。这里不清楚为什么用LongTensor,我在运行Tensor的时候也可以正常运行。*
yield features.index_select(0, j), labels.index_select(0, j)#*按行索引,每次返回batch_size(批量大小)个随机样本的特征和标签*
初始化模型参数
w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)), dtype=torch.float32)#*将权重初始化成均值为0、标准差为0.01的正态随机数*
b = torch.zeros(1, dtype=torch.float32)#*偏差则初始化成0*
#*模型训练中,需要对这些参数求梯度来迭代参数的值,因此我们要让它们的requires_grad=True()*
#*对于需要求导的tensor,其requires_grad属性必须为True*
w.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
定义模型
def linreg(X, w, b):
return torch.mm(X, w) + b #*矩阵相乘加偏差项,X:1000*2,w:2*1,相乘得1000*1的矩阵*
定义损失函数
def squared_loss(y_hat, y): #*平方误差函数*
return (y_hat - y.view(y_hat.size())) ** 2 / 2 #*把真实值y变形成预测值y_hat的形状,并返回和y_hat形状相同的向量*
定义优化算法
def sgd(params, lr, batch_size): #*小批量随机梯度下降算法*
for param in params: #*这里的params传入的即w,b*
param.data -= lr * param.grad / batch_size # *注意这里更改param时用的param.data,这里自动求梯度模块计算得来的梯度是一个批量样本的梯度和。我们将它除以批量大小来得到平均值。*
训练模型
lr = 0.03#学习率
num_epochs = 3#*迭代周期*
net = linreg#*线性回归函数*
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是1000*1向量,需要用sum函数转化成标量*
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()))
print(true_w, '\n', w)
print(true_b, '\n', b)
至于梯度为什么要手动清零,可以参考:添加链接描述
输出:
epoch 1, loss 0.028127
epoch 2, loss 0.000095
epoch 3, loss 0.000050
[2, -3.4]
tensor([[ 1.9998],
[-3.3998]], requires_grad=True)
4.2
tensor([4.2001], requires_grad=True)
可以看出,训练之后w和b都已经非常接近true_w,true_b了。
这次的博客就写到这里,可能有不严谨或错误之处,还请各位评论指正!