(d2l-ai/d2l-zh)《动手学深度学习》pytorch 笔记线性神经网络(暂停)
Posted Dontla
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(d2l-ai/d2l-zh)《动手学深度学习》pytorch 笔记线性神经网络(暂停)相关的知识,希望对你有一定的参考价值。
笔记基于2021年7月26日发布的版本,书及代码下载地址在github网页的最下面
(d2l-ai/d2l-zh)《动手学深度学习》pytorch 笔记(1)(序言、pytorch的安装、神经网络涉及符号)
(d2l-ai/d2l-zh)《动手学深度学习》pytorch 笔记(2)前言(介绍各种机器学习问题)
文章目录
- 3.1 线性回归
- 3.2 线性回归的从零开始实现(110)
- 3.3 线性回归的简洁实现(116)(框架功能:张量存储、线代运算、自动微分求梯度、【数据迭代器、损失函数、优化器】)
- 3.4 softmax回归(分类问题)(121)
- 3.5 图像分类数据集(127)
在介绍深度神经⽹络之前,我们需要了解神经⽹络训练的基础知识。在本章中,我们将介绍神经⽹络的整个训练过程,包括:定义简单的神经⽹络架构、数据处理、指定损失函数和如何训练模型。经典统计学习技术中的线性回归和softmax回归可以视为 线性神经⽹络。为了更容易学习,我们将从这些经典算法开始,向你介绍神经⽹络的基础知识。这些知识将为本书其他部分中更复杂的技术奠定基础。
3.1 线性回归
回归(regression)是指⼀类为⼀个或多个⾃变量与因变量之间关系建模的⽅法。
3.1.1 线性回归的基本元素(102)(linear regression)
线性回归(linear regression)在回归的各种标准⼯具中最简单而且最流⾏。它可以追溯到19世纪初。线性回归基于⼏个简单的假设:⾸先,假设⾃变量 x 和因变量 y 之间的关系是线性的,即y可以表⽰为 x 中元素的加权和,这⾥通常允许包含观测值的⼀些噪声;其次,我们假设任何噪声都⽐较正常,如噪声遵循正态分布。为了解释线性回归,我们举⼀个实际的例⼦:我们希望根据房屋的⾯积(平⽅英尺)和房龄(年)来估算房屋价格(美元)。为了开发⼀个能预测房价的模型,我们需要收集⼀个真实的数据集。这个数据集包括了房屋的销售价格、⾯积和房龄。在机器学习的术语中,该数据集称为 训练数据集(training data set)或训练集(training set),每⾏数据(在这个例⼦中是与⼀次房屋交易相对应的数据)称为样本(sample),也可以称为数据点(data point)或数据样本(data instance)。我们要试图预测的⽬标(在这个例⼦中是房屋价格)称为 标签(label)或 ⽬标(target)。预测所依据的⾃变量(⾯积和房龄)称为 特征(features)或 协变量(covariates)。
线性模型(102)(仿射变换)
线性假设是指⽬标(房屋价格)可以表⽰为特征(⾯积和房龄)的加权和,如下⾯的式⼦:
加入噪声项,啥意思?
损失函数(103)
解析解(104)
像线性回归这样的简单问题存在解析解,但并不是所有的问题都存在解析解。解析解可以进⾏很好的数学分析,但解析解的限制很严格,导致它⽆法应⽤在深度学习⾥。
小批量随机梯度下降(104)(批量大小、学习率、超参数(hyperparameter)、调参、验证数据集、泛化的概念)
即使在我们⽆法得到解析解的情况下,我们仍然可以有效地训练模型。
梯度下降(gradient descent)法,这种⽅法⼏乎可以优化所有深度学习模型。它通过不断地在损失函数递减的⽅向上更新参数来降低误差。
梯度下降最简单的⽤法是计算损失函数(数据集中所有样本的损失均值)关于模型参数的导数(在这⾥也可以称为梯度)。但实际中的执⾏可能会⾮常慢:因为在每⼀次更新参数之前,我们必须遍历整个数据集。因此,我们通常会在每次需要计算更新的时候随机抽取⼀小批样本,这种变体叫做小批量随机梯度下降(minibatchstochastic gradient descent)
。
在每次迭代中,我们⾸先随机抽样⼀个小批量B,它是由固定数量的训练样本组成的。然后,我们计算小批量的平均损失关于模型参数的导数(也可以称为梯度)。最后,我们将梯度乘以⼀个预先确定的正数η(步长),并从当前参数的值中减掉。
在训练了预先确定的若⼲迭代次数后(或者直到满⾜某些其他停⽌条件后),我们记录下模型参数的估计值,表⽰为wˆ ,ˆb。但是,即使我们的函数确实是线性的且⽆噪声,这些估计值也不会使损失函数真正地达到最小值。因为算法会使得损失向最小值缓慢收敛,但却不能在有限的步数内⾮常精确地达到最小值。
线性回归恰好是⼀个在整个域中只有⼀个最小值的学习问题。但是对于像深度神经⽹络这样复杂的模型来说,损失平⾯上通常包含多个最小值。幸运的是,出于某种原因,深度学习实践者很少会去花费⼤⼒⽓寻找这样⼀组参数,使得在 训练集上的损失达到最小。事实上,更难做到的是找到⼀组参数,这组参数能够在我们从未⻅过的数据上实现较低的损失,这⼀挑战被称为 泛化(generalization)。
用学习到的模型进行预测【预测(prediction)与推断(inference)】(105)
我们将尝试坚持使⽤预测这个词。虽然推断这个词已经成为深度学习的标准术语,但其实推断这个词有些⽤词不当。在统计学中,推断更多地表⽰基于数据集估计参数。当深度学习从业者与统计学家交谈时,术语的误⽤经常导致⼀些误解。
3.1.2 矢量化加速(就是不用for循环一个一个计算)(105)
在训练我们的模型时,我们经常希望能够同时处理整个小批量的样本。为了实现这⼀点,需要我们对计算进⾏⽮量化,从而利⽤线性代数库,而不是在Python中编写开销⾼昂的for循环。
矢量化???这个名词的来源是什么?
为了说明⽮量化为什么如此重要,我们考虑对向量相加的两种⽅法。我们实例化两个全1的10000维向量。在⼀种⽅法中,我们将使⽤Python的for循环遍历向量。在另⼀种⽅法中,我们将依赖对 + 的调⽤。
import math
import time
import numpy as np
import torch
from d2l import torch as d2l
# 由于在本书中我们将频繁地进⾏运⾏时间的基准测试,所以让我们定义⼀个计时器。
class Timer: # @save
"""记录多次运⾏时间。"""
def __init__(self):
self.times = []
self.start()
def start(self):
"""启动计时器。"""
self.tik = time.time()
def stop(self):
"""停⽌计时器并将时间记录在列表中。"""
self.times.append(time.time() - self.tik)
return self.times[-1]
def avg(self):
"""返回平均时间。"""
return sum(self.times) / len(self.times)
def sum(self):
"""返回时间总和。"""
return sum(self.times)
def cumsum(self):
"""返回累计时间。"""
return np.array(self.times).cumsum().tolist()
def main():
n = 10000
a = torch.ones(n)
b = torch.ones(n)
# 现在我们可以对⼯作负载进⾏基准测试。
# ⾸先,我们使⽤for循环,每次执⾏⼀位的加法。
c = torch.zeros(n)
timer = Timer()
for i in range(n):
c[i] = a[i] + b[i]
print(f'{timer.stop():.5f} sec') # 0.07998 sec
# print('{:.5f} sec'.format(timer.stop())) # 0.07998 sec
# 或者,我们使⽤重载的 + 运算符来计算按元素的和。
timer.start()
d = a + b
print(f'{timer.stop():.5f} sec') # 0.00100 sec
if __name__ == '__main__':
main()
结果很明显,第⼆种⽅法⽐第⼀种⽅法快得多。⽮量化代码通常会带来数量级的加速。另外,我们将更多的数学运算放到库中,而⽆需⾃⼰编写那么多的计算,从而减少了出错的可能性。
3.1.3 正态分布与平方损失(107)(最大似然估计:最大可能性估计)
接下来,我们通过对噪声分布的假设来解读平⽅损失⽬标函数。
下⾯我们定义⼀个Python函数来计算正态分布。
import math
import numpy as np
import torch
from d2l import torch as d2l
def normal(x, mu, sigma):
p = 1 / math.sqrt(2 * math.pi * sigma ** 2)
return p * np.exp(-0.5 / sigma ** 2 * (x - mu) ** 2)
def main():
# 我们现在可视化正态分布。
# 再次使⽤numpy进⾏可视化
x = np.arange(-7, 7, 0.01)
# 均值和标准差对
params = [(0, 1), (0, 2), (3, 1)]
d2l.plot(x, [normal(x, mu, sigma) for mu, sigma in params], xlabel='x',
ylabel='p(x)', figsize=(4.5, 2.5),
legend=[f'mean {mu}, std {sigma}' for mu, sigma in params])
d2l.plt.show()
if __name__ == '__main__':
main()
实在没搞清楚它后面讲的是啥???
因此,在⾼斯噪声的假设下,最小化均⽅误差等价于对线性模型的最⼤似然估计。??
3.1.4 从线性回归到深度⽹络(108)
到⽬前为⽌,我们只谈论了线性模型。尽管神经⽹络涵盖了更多更为丰富的模型,我们依然可以⽤描述神经⽹络的⽅式来描述线性模型,从而把线性模型看作⼀个神经⽹络。⾸先,让我们⽤“层”符号来重写这个模型。
神经网络图(108)(线性回归模型可以看作单层神经网络)(特征维度、全连接层、稠密层的概念)
深度学习从业者喜欢绘制图表来可视化模型中正在发⽣的事情。在 图3.1.2 中,我们将线性回归模型描述为⼀个神经⽹络。需要注意的是,该图只显⽰连接模式,即只显⽰每个输⼊如何连接到输出,隐去了权重和偏置的值。
在 图3.1.2 所⽰的神经⽹络中,输⼊为 x1, . . . , xd,因此输⼊层中的 输⼊数(或称为 特征维度 feature dimensionality)为 d。⽹络的输出为o1,因此输出层中的 输出数是 1。需要注意的是,输⼊值都是已经给定的,并且只有⼀个计算神经元。由于模型重点在发⽣计算的地⽅,所以通常我们在计算层数时不考虑输⼊层。也就是说,图3.1.2 中神经⽹络的 层数为1。我们可以将线性回归模型视为仅由单个⼈⼯神经元组成的神经⽹络,或称为单层神经⽹络。
对于线性回归,每个输⼊都与每个输出(在本例中只有⼀个输出)相连,我们将这种变换(图3.1.2 中的输出层)称为 全连接层(fully-connected layer)(或称为 稠密层 dense layer)。下⼀章将详细讨论由这些层组成的⽹络。
生物学(109)(了解了解就好)
3.1.5 ⼩结(110)
• 机器学习模型中的关键要素是训练数据,损失函数,优化算法,还有模型本⾝。
• ⽮量化使数学表达上更简洁,同时运⾏的更快。
• 最小化⽬标函数和执⾏最⼤似然估计等价。
• 线性回归模型也是神经⽹络。
3.1.6 练习(110)
- 假设我们有⼀些数据 x1, . . . , xn ∈ R。我们的⽬标是找到⼀个常数b,使得
最小化 。
- 找到最优值 b 的解析解。
- 这个问题及其解与正态分布有什么关系?
- 推导出使⽤平⽅误差的线性回归优化问题的解析解。为了简化问题,可以忽略偏置b(我们可以通过向 X添加所有值为1的⼀列来做到这⼀点)。
- ⽤矩阵和向量表⽰法写出优化问题(将所有数据视为单个矩阵,将所有⽬标值视为单个向量)。
- 计算损失对w的梯度。
- 通过将梯度设为0、求解矩阵⽅程来找到解析解。
- 什么时候可能⽐使⽤随机梯度下降更好?这种⽅法何时会失效?
- 假定控制附加噪声 ϵ 的噪声模型是指数分布。也就是说,
- 写出模型 − log P(y | X) 下数据的负对数似然。
- 你能写出解析解吗?
- 提出⼀种随机梯度下降算法来解决这个问题。哪⾥可能出错?(提⽰:当我们不断更新参数时,在驻点附近会发⽣什么情况)你能解决这个问题吗?
https://discuss.d2l.ai/t/topic/1775
3.2 线性回归的从零开始实现(110)
在了解线性回归的关键思想之后,我们可以开始通过代码来动⼿实现线性回归了。在这⼀节中,我们将从零开始实现整个⽅法,包括数据流⽔线、模型、损失函数和小批量随机梯度下降优化器。虽然现代的深度学习框架⼏乎可以⾃动化地进⾏所有这些⼯作,但从零开始实现可以确保你真正知道⾃⼰在做什么。同时,了解更细致的⼯作原理将⽅便我们⾃定义模型、⾃定义层或⾃定义损失函数。在这⼀节中,我们将只使⽤张量和⾃动求导。在之后的章节中,我们会充分利⽤深度学习框架的优势,介绍更简洁的实现⽅式。
3.2.1 ⽣成数据集(111)
你可以将 ϵ 视为捕获特征和标签时的潜在观测误差。在这⾥我们认为标准假设成⽴,即ϵ服从均值为0的正态分布。为了简化问题,我们将标准差设为0.01。下⾯的代码⽣成合成数据集。
import random
import torch
from d2l import torch as d2l
# synthetic 人造的、合成的
def synthetic_data(w, b, num_examples): # @save
"""⽣成 y = Xw + b + 噪声。"""
X = torch.normal(0, 1, (num_examples, len(w))) # 正态分布 行:num_examples,列:len(w)
# print(X.shape) # torch.Size([1000, 2])
y = torch.matmul(X, w) + b # matmul点积
y += torch.normal(0, 0.01, y.shape) # 加上正态分布噪声
# print(X.shape) # torch.Size([1000, 2])
# print(y.shape) # torch.Size([1000])
# print(y.reshape((-1, 1)).shape) # torch.Size([1000, 1])
return X, y.reshape((-1, 1))
def main():
# true_w = torch.tensor([2, -3.4])
true_w = torch.tensor([2, -3.4]) # true_w的第二个参数决定点形状的分布趋势,因为后面画图的时候X取得是第二列
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000) # 生成人造数据
# 注意,features 中的每⼀⾏都包含⼀个⼆维数据样本,labels 中的每⼀⾏都包含⼀维标签值(⼀个标量)。
print('features:', features[0], '\\nlabel:', labels[0])
# features: tensor([-2.7495, -0.2577])
# label: tensor([-0.4297])
# 通过⽣成第⼆个特征 features[:, 1] 和 labels 的散点图,可以直观地观察到两者之间的线性关系。
d2l.set_figsize()
d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1) # 1是点的大小
d2l.plt.show()
# 为什么X小y大,X大y小?(见上)
if __name__ == '__main__':
main()
3.2.2 读取数据集(112)
回想⼀下,训练模型时要对数据集进⾏遍历,每次抽取⼀小批量样本,并使⽤它们来更新我们的模型。由于这个过程是训练机器学习算法的基础,所以有必要定义⼀个函数,该函数能打乱数据集中的样本并以小批量⽅式获取数据。
在下⾯的代码中,我们定义⼀个data_iter 函数,该函数接收批量⼤小、特征矩阵和标签向量作为输⼊,⽣成⼤小为batch_size的小批量。每个小批量包含⼀组特征和标签。
通常,我们使⽤合理⼤小的小批量来利⽤GPU硬件的优势,因为GPU在并⾏处理⽅⾯表现出⾊。每个样本都可以并⾏地进⾏模型计算,且每个样本损失函数的梯度也可以被并⾏地计算,GPU可以在处理⼏百个样本时,所花费的时间不⽐处理⼀个样本时多太多。
让我们直观感受⼀下。读取第⼀个小批量数据样本并打印。每个批量的特征维度说明了批量⼤小和输⼊特征数。同样的,批量的标签形状与 batch_size 相等。
import random
import torch
from d2l import torch as d2l
# synthetic 人造的、合成的
def synthetic_data(w, b, num_examples): # @save
"""⽣成 y = Xw + b + 噪声。"""
X = torch.normal(0, 1, (num_examples, len(w))) # 正态分布 行:num_examples,列:len(w)
# print(X.shape) # torch.Size([1000, 2])
y = torch.matmul(X, w) + b # matmul点积
y += torch.normal(0, 0.01, y.shape) # 加上正态分布噪声
# print(X.shape) # torch.Size([1000, 2])
# print(y.shape) # torch.Size([1000])
# print(y.reshape((-1, 1)).shape) # torch.Size([1000, 1])
return X, y.reshape((-1, 1))
def data_iter(batch_size, features, labels):
num_examples = len(features) # 1000
indices = list(range(num_examples)) # 生成列表[0, 1, 2, ..., 998, 999]
# 这些样本是随机读取的,没有特定的顺序
random.shuffle(indices) # 打乱indices
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(indices[i:min(i + batch_size, num_examples)]) # 为什么要加min?担心i+batch_size
# 超过num_examples?
yield features[batch_indices], labels[batch_indices]
def main():
# true_w = torch.tensor([2, -3.4])
true_w = torch.tensor([2, -3.4]) # true_w的第二个参数决定点形状的分布趋势,因为后面画图的时候X取得是第二列
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000) # 生成人造数据
# 注意,features 中的每⼀⾏都包含⼀个⼆维数据样本,labels 中的每⼀⾏都包含⼀维标签值(⼀个标量)。
print('features:', features[0], '\\nlabel:', labels[0])
# features: tensor([-2.7495, -0.2577])
# label: tensor([-0.4297])
# 通过⽣成第⼆个特征 features[:, 1] 和 labels 的散点图,可以直观地观察到两者之间的线性关系。
d2l.set_figsize()
d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1) # 1是点的大小
d2l.plt.show()
# 为什么X小y大,X大y小?(见上)
# 读取数据
batch_size = 10
for X, y in data_iter(batch_size, features, labels):
print(X, '\\n', y)
break
# tensor([[-0.0187, -0.4961],
# [ 0.2226, 0.9894],
# [ 0.9929, 0.0527],
# [-1.6255, 1.0358],
# [-1.2527, 0.8796],
# [-0.4309, 2.4232],
# [ 0.2163, 1.3185],
# [ 1.1462, -0.5068],
# [-0.9749, 0.4630],
# [ 0.4969, 1.8860]])
# tensor([[ 5.8492],
# [ 1.2785],
# [ 6.0115],
# [-2.5758],
# [-1.2782],
# [-4.9061],
# [ 0.1676],
# [ 8.2293],
# [ 0.6850],
# [-1.2083]])
if __name__ == '__main__':
main()
当我们运⾏迭代时,我们会连续地获得不同的小批量,直⾄遍历完整个数据集。上⾯实现的迭代对于教学来说很好,但它的执⾏效率很低,可能会在实际问题上陷⼊⿇烦。例如,它要求我们将所有数据加载到内存中,并执⾏⼤量的随机内存访问。在深度学习框架中实现的内置迭代器效率要⾼得多,它可以处理存储在⽂件中的数据和通过数据流提供的数据。(内置迭代器在哪??长什么样子??DataLoader,see you laterrrrr)
3.2.3 初始化模型参数(113)
在我们开始⽤小批量随机梯度下降优化我们的模型参数之前,我们需要先有⼀些参数。在下⾯的代码中,我们通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重,并将偏置初始化为0。
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
在初始化参数之后,我们的任务是更新这些参数,直到这些参数⾜够拟合我们的数据。每次更新都需要计算损失函数关于模型参数的梯度。有了这个梯度,我们就可以向减小损失的⽅向更新每个参数。因为⼿动计算梯度很枯燥而且容易出错,所以没有⼈会⼿动计算梯度。我们使⽤ 2.5节 中引⼊的⾃动微分来计算梯度。
3.2.4 定义模型(114)
接下来,我们必须定义模型,将模型的输⼊和参数同模型的输出关联起来。回想⼀下,要计算线性模型的输出,我们只需计算输⼊特征 X 和模型权重w的矩阵-向量乘法后加上偏置b。注意,上⾯的Xw 是⼀个向量,而b是⼀个标量。回想⼀下 2.1.3节 中描述的⼴播机制。当我们⽤⼀个向量加⼀个标量时,标量会被加到向量的每个分量上。
def linreg(X, w, b): #@save
"""线性回归模型。"""
return torch.matmul(X, w) + b
3.2.5 定义损失函数(114)
因为要更新模型。需要计算损失函数的梯度,所以我们应该先定义损失函数。这⾥我们使⽤ 3.1节 中描述的平⽅损失函数。在实现中,我们需要将真实值y的形状转换为和预测值y_hat的形状相同。
def squared_loss(y_hat, y): #@save
"""均⽅损失。"""
return (y_hat - y.reshape(y_hat.shape))**2 / 2
3.2.6 定义优化算法(114)
正如我们在 3.1节 中讨论的,线性回归有解析解。然而,这是⼀本关于深度学习的书,而不是⼀本关于线性回归的书。由于这本书介绍的其他模型都没有解析解,下⾯我们将在这⾥介绍小批量随机梯度下降的⼯作⽰例。
在每⼀步中,使⽤从数据集中随机抽取的⼀个小批量
以上是关于(d2l-ai/d2l-zh)《动手学深度学习》pytorch 笔记线性神经网络(暂停)的主要内容,如果未能解决你的问题,请参考以下文章
(d2l-ai/d2l-zh)《动手学深度学习》pytorch 笔记(序言pytorch的安装神经网络涉及符号)
(d2l-ai/d2l-zh)《动手学深度学习》pytorch 笔记前言(介绍各种机器学习问题)以及数据操作预备知识Ⅲ(概率)
(d2l-ai/d2l-zh)《动手学深度学习》pytorch 笔记前言(介绍各种机器学习问题)以及数据操作预备知识Ⅱ(线性代数微分自动求导)