赞
踩
学习了动手学深度学习后,学习了如何创建一个线性回归模型,总结一下。
线性回归需要哪些基本因素:
1、线性模型
2、损失函数
3、解析解
4、随机梯度下降
5、用模型进行预测
为了解释线性回归,我们举一个实际的例子: 我们希望根据房屋的面积(平方英尺)和房龄(年)来估算房屋价格(美元)。 为了开发一个能预测房价的模型,我们需要收集一个真实的数据集。 这个数据集包括了房屋的销售价格、面积和房龄。 在机器学习的术语中,该数据集称为训练数据集(training data set) 或训练集(training set)。 每行数据(比如一次房屋交易相对应的数据)称为样本(sample), 也可以称为数据点(data point)或数据样本(data instance)。 我们把试图预测的目标(比如预测房屋价格)称为标签(label)或目标(target)。 预测所依据的自变量(面积和房龄)称为特征(feature)或协变量(covariate)。
线性模型的话是一个公式,假设以这个例子来说,线性假设是指目标(房屋价格)可以表示为特征(面积和房龄)的加权和,如下面的式子:
Wareahe和Wage称为权重,权重决定了每个特征对我们预测值的影响。b称为偏置(bias)、偏移量(offset)或截距(intercept)。 偏置是指当所有特征都取值为0时,预测值应该为多少。
给定一个数据集,我们的目标是寻找模型的权重w和偏置b, 使得根据模型做出的预测大体符合数据里的真实价格。 输出的预测值由输入特征通过线性模型的仿射变换决定,仿射变换由所选权重和偏置确定。
而在机器学习领域,我们通常使用的是高维数据集,建模时采用线性代数表示法会比较方便。 当我们的输入包含d个特征时,我们将预测结果y(通常使用“尖角”符号表示y的估计值)表示为:
当有多个x,我们可以把w放在一个向量中。
在我们开始考虑如何用模型拟合(fit)数据之前,我们需要确定一个拟合程度的度量。 损失函数(loss function)能够量化目标的实际值与预测值之间的差距。 通常我们会选择非负数作为损失,且数值越小表示损失越小,完美预测时的损失为0。 回归问题中最常用的损失函数是平方误差函数。
当样本i的预测值为,其相应的真实标签为时, 平方误差可以定义为以下公式:
常数1/2不会带来本质的差别,但这样在形式上稍微简单一些 (因为当我们对损失函数求导后常数系数为1)。 由于训练数据集并不受我们控制,所以经验误差只是关于模型参数的函数。
我的理解是损失函数为真实值和预测值的差值的平方,可以看看偏差量的多少?
我有点理解为数学上的方差(不知道对不对),我们需要尽量把这个量控制的小
线性回归刚好是一个很简单的优化问题。 线性回归的解可以用一个公式简单地表达出来, 这类解叫作解析解(analytical solution)。 首先,我们将偏置b合并到参数w中,合并方法是在包含所有参数的矩阵中附加一列。 我们的预测问题是最小化‖y - Xw‖2。 这在损失平面上只有一个临界点,这个临界点对应于整个区域的损失极小点。 将失关于w的导数设为0,得到解析解:
即使在我们无法得到解析解的情况下,我们仍然可以有效地训练模型。 在许多任务上,那些难以优化的模型效果要更好。 因此,弄清楚如何训练这些难以优化的模型是非常重要的。
本书中我们用到一种名为梯度下降(gradient descent)的方法, 这种方法几乎可以优化所有深度学习模型。 它通过不断地在损失函数递减的方向上更新参数来降低误差。
梯度下降最简单的用法是计算损失函数(数据集中所有样本的损失均值) 关于模型参数的导数(在这里也可以称为梯度)。 但实际中的执行可能会非常慢:因为在每一次更新参数之前,我们必须遍历整个数据集。 因此,我们通常会在每次需要计算更新的时候随机抽取一小批样本, 这种变体叫做小批量随机梯度下降(minibatch stochastic gradient descent)。
在每次迭代中,我们首先随机抽样一个小批量, 它是由固定数量的训练样本组成的。 然后,我们计算小批量的平均损失关于模型参数的导数(也可以称为梯度)。 最后,我们将梯度乘以一个预先确定的正数,并从当前参数的值中减掉。
我们用下面的数学公式来表示这一更新过程(表示偏导数):
总结一下,算法的步骤如下: (1)初始化模型参数的值,如随机初始化; (2)从数据集中随机抽取小批量样本且在负梯度的方向上更新参数,并不断迭代这一步骤。 对于平方损失和仿射变换,我们可以明确地写成如下形式:
这一部分我的理解是,随机梯度下降是为了减少运算的速度,不能下降的太多,也不能下降的太少,我们是用已知的确定的正数来减少模型计算的速度从而使得模型速度变快,如果梯度下降的太过分,会导致模型的准确性下降,如果梯度太多,会导致模型运算速度变慢。
计算梯度是一件很贵的事情!不能走太多,当然也不能走太少,沿着箭头方向就是梯度方向。
给定“已学习”的线性回归模型w^⊤x+b^, 现在我们可以通过房屋面积x1和房龄x2来估计一个(未包含在训练数据中的)新房屋价格。 给定特征估计目标的过程通常称为预测(prediction)或推断(inference)。
本次模型实现,我们将只使用张量和自动求导
- import random
- import torch
- from d2l import torch as d2l
为了简单起见,我们将根据带有噪声的线性模型构造一个人造数据集。 我们的任务是使用这个有限样本的数据集来恢复这个模型的参数。 我们将使用低维数据,这样可以很容易地将其可视化。 在下面的代码中,我们生成一个包含1000个样本的数据集, 每个样本包含从标准正态分布中采样的2个特征。
- def synthetic_data(w, b, num_examples):
- """生成y=Xw+b+噪声"""
- X = torch.normal(0, 1, (num_examples, len(w)))
- y = torch.matmul(X, w) + b
- y += torch.normal(0, 0.01, y.shape)
- return X, y.reshape((-1, 1))
-
- true_w = torch.tensor([2, -3.4])
- true_b = 4.2
- features, labels = synthetic_data(true_w, true_b, 1000)
注意,features
中的每一行都包含一个二维数据样本, labels
中的每一行都包含一维标签值(一个标量)。
通过生成第二个特征features[:, 1]
和labels
的散点图, 可以直观观察到两者之间的线性关系。
训练模型时要对数据集进行遍历,每次抽取一小批量样本,并使用它们来更新我们的模型。 由于这个过程是训练机器学习算法的基础,所以有必要定义一个函数, 该函数能打乱数据集中的样本并以小批量方式获取数据。
在下面的代码中,我们定义一个data_iter
函数, 该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为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]
通常,我们利用GPU并行运算的优势,处理合理大小的“小批量”。 每个样本都可以并行地进行模型计算,且每个样本损失函数的梯度也可以被并行计算。 GPU可以在处理几百个样本时,所花费的时间不比处理一个样本时多太多。
我们直观感受一下小批量运算:读取第一个小批量数据样本并打印。 每个批量的特征维度显示批量大小和输入特征数。 同样的,批量的标签形状与batch_size
相等。
- batch_size = 10
-
- for X, y in data_iter(batch_size, features, labels):
- print(X, '\n', y)
- break
在我们开始用小批量随机梯度下降优化我们的模型参数之前, 我们需要先有一些参数。 在下面的代码中,我们通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重, 并将偏置初始化为0。
- w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
- b = torch.zeros(1, requires_grad=True)
在初始化参数之后,我们的任务是更新这些参数,直到这些参数足够拟合我们的数据。 每次更新都需要计算损失函数关于模型参数的梯度。 有了这个梯度,我们就可以向减小损失的方向更新每个参数。 因为手动计算梯度很枯燥而且容易出错,所以没有人会手动计算梯度。 我们使用自动微分来计算梯度。
接下来,我们必须定义模型,将模型的输入和参数同模型的输出关联起来。 回想一下,要计算线性模型的输出, 我们只需计算输入特征X和模型权重w的矩阵-向量乘法后加上偏置b。 注意,上面的Xw是一个向量,而b是一个标量。 当我们用一个向量加一个标量时,标量会被加到向量的每个分量上。
- def linreg(X, w, b):
- """线性回归模型"""
- return torch.matmul(X, w) + b
因为需要计算损失函数的梯度,所以我们应该先定义损失函数。 这里我们使用平方损失函数。 在实现中,我们需要将真实值y
的形状转换为和预测值y_hat
的形状相同。
- def squared_loss(y_hat, y): #@save
- """均方损失"""
- return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
在每一步中,使用从数据集中随机抽取的一个小批量,然后根据参数计算损失的梯度。 接下来,朝着减少损失的方向更新我们的参数。 下面的函数实现小批量随机梯度下降更新。 该函数接受模型参数集合、学习速率和批量大小作为输入。每 一步更新的大小由学习速率lr
决定。 因为我们计算的损失是一个批量样本的总和,所以我们用批量大小(batch_size
) 来规范化步长,这样步长大小就不会取决于我们对批量大小的选择。
- def sgd(params, lr, batch_size):
- """小批量随机梯度下降"""
- with torch.no_grad():
- for param in params:
- param -= lr * param.grad / batch_size
- param.grad.zero_()
现在我们已经准备好了模型训练所有需要的要素,可以实现主要的训练过程部分了。 理解这段代码至关重要,因为从事深度学习后, 相同的训练过程几乎一遍又一遍地出现。 在每次迭代中,我们读取一小批量训练样本,并通过我们的模型来获得一组预测。 计算完损失后,我们开始反向传播,存储每个参数的梯度。 最后,我们调用优化算法sgd
来更新模型参数。
在每个迭代周期(epoch)中,我们使用data_iter
函数遍历整个数据集, 并将训练数据集中所有样本都使用一次(假设样本数能够被批量大小整除)。 这里的迭代周期个数num_epochs
和学习率lr
都是超参数,分别设为3和0.03。 设置超参数很棘手,需要通过反复试验进行调整。
- lr = 0.03
- num_epochs = 3
- net = linreg
- loss = squared_loss
-
- for epoch in range(num_epochs):
- for X, y in data_iter(batch_size, features, labels):
- l = loss(net(X, w, b), y) # X和y的小批量损失
- # 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
- # 并以此计算关于[w,b]的梯度
- l.sum().backward()
- sgd([w, b], lr, batch_size) # 使用参数的梯度更新参数
- with torch.no_grad():
- train_l = loss(net(features, w, b), labels)
- print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
结果
因为我们使用的是自己合成的数据集,所以我们知道真正的参数是什么。 因此,我们可以通过比较真实参数和通过训练学到的参数来评估训练的成功程度。 事实上,真实参数和通过训练学到的参数确实非常接近。
- print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
- print(f'b的估计误差: {true_b - b}')
注意,我们不应该想当然地认为我们能够完美地求解参数。 在机器学习中,我们通常不太关心恢复真正的参数,而更关心如何高度准确预测参数。 幸运的是,即使是在复杂的优化问题上,随机梯度下降通常也能找到非常好的解。 其中一个原因是,在深度网络中存在许多参数组合能够实现高度精确的预测。
本章讲述了基础线性模型怎么实现,要注意的是要理解训练的过程,特别的训练过程中的sgd函数,本人学C++为基础,可能不太能理解python里的参数改变,我理解为调入了函数中的值就像C++中的按照指针引用一样,可以直接改变值,所以说sgd不需要返回值也可以更改参数的值,从而实现更新参数,计算损失然后求梯度,再来更新。线性代数的基础知识很重要,还有就是说为什么其中这一部分的l为什么要求sum呢,因为一般来说我们都是用标量来求梯度,用向量来求梯度很少(目前的我无法解释),还要多熟悉torch的运用,不然会导致看不懂代码的情况。
最后放一下完整的代码:
- import random
- import torch
- from d2l import torch as d2l
- '''根据带有噪声的线性模型构造一个人造数据集,我们使用线性模型参数w = [2,-3,4]T、b = 4.2和噪声项e生成数据集及其标签'''
- '''y = Xw + b + 噪声'''
- def synthetic_data(w,b,num_examples):
- '''生成 y = Xw + b + 噪声。'''
- X = torch.normal(0,1,(num_examples,len(w)))
- Y = torch.matmul(X,w) + b
- Y += torch.normal(0,0.01,Y.shape)
- return X,Y.reshape((-1,1))
- true_w = torch.tensor([2,-3.4])
- true_b = 4.2
- features,labels = synthetic_data(true_w,true_b,1000)
- print('features:',features[0],'\nlabel:',labels[0])
- d2l.set_figsize()
- d2l.plt.scatter(features[:,1].detach().numpy(),labels.detach().numpy(),1)
- '''展示你的画图'''
- # d2l.plt.show()
- '''定义一个data_iter函数,该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为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]
- batch_size = 10
- for X,y in data_iter(batch_size,features,labels):
- print(X,'\n',y)
- break
- '''定义初始化模型'''
- w = torch.normal(0,0.01,size=(2,1),requires_grad=True)#均值为0,方差为1,随机的正态分布
- b = torch.zeros(1,requires_grad=True)#偏差来说给0
- '''定义模型'''
- def linreg(X,w,b):
- '''线性回归模型'''
- return torch.matmul(X,w) + b
- '''定义损失函数'''
- def squared_loss(y_hat,y):
- '''均方损失'''
- return (y_hat - y.reshape(y_hat.shape))**2 / 2
- '''定义优化算法'''
- def sgd(params,lr,batch_size):
- '''小批量随机梯度下降'''
- with torch.no_grad():
- for params in params:
- params -= lr * params.grad / batch_size
- params.grad.zero_()
- '''训练过程'''
- lr = 0.03
- num_epochs = 3
- net = linreg
- loss = squared_loss
-
- for epoch in range(num_epochs):
- for X,y in data_iter(batch_size,features,labels):
- l = loss(net(X,w,b),y)
- l.sum().backward()
- sgd([w,b],lr,batch_size)
- with torch.no_grad():
- train_l = loss(net(features,w,b),labels)
- print(f'epoch {epoch + 1},loss {float(train_l.mean()):f}')
-
- '''比较真实参数和通过训练学到的参数来评估训练的成功程度'''
- print(f'w的估计误差:{true_w - w.reshape(true_w.shape)}')
- print(f'b的估计误差:{true_b - b}')
我用的是pycharm,pycharm要是想展示d2l的图得写d2l.plt.show(),我已经注释在上面了,需要展示图的把注释去掉即可。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。