(d2l-ai/d2l-zh)《动手学深度学习》pytorch 笔记线性神经网络(暂停)

Posted Dontla

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(d2l-ai/d2l-zh)《动手学深度学习》pytorch 笔记线性神经网络(暂停)相关的知识,希望对你有一定的参考价值。

开源项目地址:d2l-ai/d2l-zh

教材官网:https://zh.d2l.ai/

书介绍:https://zh-v2.d2l.ai/

笔记基于2021年7月26日发布的版本,书及代码下载地址在github网页的最下面

交流者论坛

额外:
https://distill.pub/

pytorch中文文档(哪个函数不明白用法查询非常好用)


(d2l-ai/d2l-zh)《动手学深度学习》pytorch 笔记(1)(序言、pytorch的安装、神经网络涉及符号)

(d2l-ai/d2l-zh)《动手学深度学习》pytorch 笔记(2)前言(介绍各种机器学习问题)

文章目录


在介绍深度神经⽹络之前,我们需要了解神经⽹络训练的基础知识。在本章中,我们将介绍神经⽹络的整个训练过程,包括:定义简单的神经⽹络架构、数据处理、指定损失函数和如何训练模型。经典统计学习技术中的线性回归和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)

  1. 假设我们有⼀些数据 x1, . . . , xn ∈ R。我们的⽬标是找到⼀个常数b,使得

    最小化 。
  • 找到最优值 b 的解析解。
  • 这个问题及其解与正态分布有什么关系?
  1. 推导出使⽤平⽅误差的线性回归优化问题的解析解。为了简化问题,可以忽略偏置b(我们可以通过向 X添加所有值为1的⼀列来做到这⼀点)。
  • ⽤矩阵和向量表⽰法写出优化问题(将所有数据视为单个矩阵,将所有⽬标值视为单个向量)。
  • 计算损失对w的梯度。
  • 通过将梯度设为0、求解矩阵⽅程来找到解析解。
  • 什么时候可能⽐使⽤随机梯度下降更好?这种⽅法何时会失效?
  1. 假定控制附加噪声 ϵ 的噪声模型是指数分布。也就是说,
  • 写出模型 − 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 笔记前言(介绍各种机器学习问题)以及数据操作预备知识Ⅱ(线性代数微分自动求导)

今天的第二篇计划

Softmax 回归 + 损失函数 + 图片分类数据集 动手学深度学习v2 pytorch

批量归一化Batch Normalization 动手学深度学习v2