动手学深度学习-学习笔记【二】(基础知识)
文章目录
- 1、概述
- 2、课程学习
- 2.1、深度学习介绍
- 2.2、安装
- 2.3、数据操作
- 2.4、数据预处理
- 2.5、线性代数
- 2.6、微积分
- 2.7、自动微分
- 2.8、概率
- 2.8.1、基本概率论
- 2.8.2、处理多个随机变量
- 2.8.3、期望和方差
- 2.9、查阅文档
1、概述
本篇博客用来记录我学习深度学习的学习笔记,本篇博客主要深度学习所需的一些预备知识,
包括数据操作,线性代数,微积分,概率论等
2、课程学习
2.1、深度学习介绍
深度学习是机器学习的一种
深度学习在广告推荐中的案例
2.2、安装
Conda 是一个开源的 软件包管理系统 和 环境管理系统,专为数据科学、机器学习、生物信息学等领域设计。它允许用户高效地安装、管理和切换多个版本的软件包及其依赖项,同时支持多种编程语言(如 Python、R、Ruby、Java 等)和跨平台(Linux、macOS、Windows)。
Jupyter Notebook 是一个功能强大的交互式计算工具,广泛应用于数据科学、教育、研究和开发领域。作用包括:数据分析与可视化,机器学习与建模,教育与教学,科研与报告,合作与共享
D2L 是一个面向中文读者的深度学习开源项目,由 李沐博士 等人开发,旨在通过 可运行代码、数学公式和实践案例 的结合,帮助用户系统性地学习深度学习。
PyTorch 是一个开源的深度学习框架,由 Facebook 的 AI 研究团队开发。它基于 Python 语言,支持动态计算图(Dynamic Computation Graph),广泛应用于计算机视觉、自然语言处理等领域。
TorchVision 是 PyTorch 生态中专注于 计算机视觉 的核心库
2.3、数据操作
N 维数组样例
N维数组是机器学习和神经网络的主要数据结构。
张量表示一个由数值组成的数组,这个数组可能有多个维度。
具有一个轴的张量对应数学上的向量(vector);
具有两个轴的张量对应数学上的矩阵(matrix);
具有两个轴以上的张量没有特殊的数学名称。
N 维数组样例
N 维数组是机器学习和神经网络的主要数据结构
创建数组的前提条件
- 形状:例如 3 * 4 矩阵
- 每个元素的数据类型:例如 32 位浮点数
- 每个元素的值:例如全是0,或者随机数
访问元素
首先,我们可以使用 arange
创建一个行向量 x
。
这个行向量包含以0开始的前12个整数,它们默认创建为整数。
也可指定创建类型为浮点数。张量中的每个值都称为张量的 元素(element)。
import torchA = torch.arange(5, dtype=torch.float32)
print(A) # tensor([0., 1., 2., 3., 4.])
我们导入
torch
。请注意,虽然它被称为PyTorch
,但是代码中使用torch
而不是pytorch
。
可以通过张量的shape
属性来访问张量(沿每个轴的长度)的形状 。
import torchA = torch.arange(5, dtype=torch.float32)
print(A) # tensor([0., 1., 2., 3., 4.])
print(A.shape) # torch.Size([5])
如果只想知道张量中元素的总数,即形状的所有元素乘积,可以检查它的大小(size)。
import torchA = torch.arange(5, dtype=torch.float32)
print(A) # tensor([0., 1., 2., 3., 4.])
print(A.numel()) # 5
要改变一个张量的形状而不改变元素数量和元素值,我们可以调用 reshape
函数
import torchA = torch.arange(20, dtype=torch.float32).reshape(5, 4)
print(A)
# tensor([[ 0., 1., 2., 3.],
# [ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.],
# [12., 13., 14., 15.],
# [16., 17., 18., 19.]])
使用全0,全1,其他常量或者从特定分布中随机采样的数字
通过提供包含数值的 Python 列表(或嵌套列表)来为所需张量中的每个元素赋予确定值
操作符运算
常见的标准算术运算符(+,-,*,/,**)都可以按升级为按照元素运算
我们也可以把多个张量连结在一起
dim=0
表示将 X 和 Y 按照行进行合并
dim=1
表示将 X 和 Y 按照列进行合并
我们也可以通过逻辑运算符构建二元张量
对张量中的所有元素进行求和会产生一个只有一个元素的张量。
广播机制
即使形状不同,依然可以进行张量的加减法
广播(Broadcasting)是 PyTorch 中一种允许不同形状张量进行逐元素运算的机制。
核心思想是:自动扩展较小的张量,使其形状与较大的张量兼容,从而避免手动复制数据,节省内存并简化代码。
即使形状不同,我们任然可以通过调用广播机制来执行按元素操作
特殊元素获取
可以使用
X[-1]
取出最后一行
使用X[1:3]
取出第二行和第三行
注意:
行和列都是从 0 开始
除读取外,我们还可以通过指定索引来将元素写入矩阵。
按照区域赋值
为多个元素赋值相同的值,我们只需要索引所有元素,然后为他们赋值
python 使用
id()
来获取变量的地址(类似 C 中的指针)
在 PyTorch 中,id()
是 Python 内置函数,用于返回对象的 唯一标识符(即内存地址)。
运行一些操作可能会导致为新结果分配内存
执行原地操作
其中上图第二个样例中,Z 的内存没有发生变化,前后是一致的
如果在后续计算中,没有重复使用 X
,我们也可以使用 X[:] = X + Y
或 X += Y
来减少操作的内存开销
numPy
和torch
使用不一样的数据类型
numPy
和torch
可以互相转换
torch
转化为 NumPy
张量
将大小为 1 的张量转换为 Python 标量
2.4、数据预处理
为了能用深度学习来解决现实世界的问题,我们经常从预处理原始数据开始, 而不是从那些准备好的张量格式数据开始。
读取数据集
我们首先创建一个人工数据集,并存储在CSV(逗号分隔值)文件
../data/house_tiny.csv
中
要从创建的CSV文件
中加载原始数据集,我们导入pandas包并调用read_csv
函数。
import os
import pandas as pdos.makedirs(os.path.join('..', 'data'), exist_ok=True)
data_file = os.path.join('..', 'data', 'house_tiny.csv')
with open(data_file, 'w') as f:f.write('NumRooms,Alley,Price\n') # 列名f.write('NA,Pave,127500\n') # 每行表示一个数据样本f.write('2,NA,106000\n')f.write('4,NA,178100\n')f.write('NA,NA,140000\n')data = pd.read_csv(data_file)
print(data)
# NumRooms Alley Price
# 0 NaN Pave 127500
# 1 2.0 NaN 106000
# 2 4.0 NaN 178100
# 3 NaN NaN 140000
“NaN”项代表缺失值。
处理缺失值
对于缺失值的处理,有两种处理办法,插值法和删除法
- 插值法用一个替代值弥补缺失值
- 删除法则直接忽略缺失值。
通过位置索引iloc,我们将data
分成inputs
和outputs
, 其中前者为data的前两列,而后者为data的最后一列。
对于inputs
中缺少的数值,我们用同一列的均值替换“NaN”
项。
对于inputs中的类别值或离散值,我们将“NaN”视为一个类别。 由于“巷子类型”(“Alley”)列只接受两种类型的类别值“Pave”和“NaN”, pandas可以自动将此列转换为两列“Alley_Pave”和“Alley_nan”。 巷子类型为“Pave”的行会将“Alley_Pave”的值设置为1,“Alley_nan”的值设置为0。 缺少巷子类型的行会将“Alley_Pave”和“Alley_nan”分别设置为0和1。
转换为张量格式
现在inputs
和outputs
中的所有条目都是数值类型,它们可以转换为张量格式。
2.5、线性代数
标量
仅包含一个数值被称为标量(scalar)
- 确定的数值被称为标量值
- 不确定的符号被称为变量
标量由只有一个元素的张量表示。
向量
向量可以被视为标量值组成的列表。
这些标量值被称为向量的元素(element)或分量(component)。
通过一维张量表示向量。
在数学中,我们可以使用下标来引用向量的任一元素
在代码中,我们通过张量的索引来访问任一元素。
长度,维度和形状
向量的长度通常称为向量的维度(dimension)
当用张量表示一个向量(只有一个轴)时,我们也可以通过.shape
属性访问向量的长度。
形状(shape)是一个元素组,列出了张量沿每个轴的长度(维数)。 对于只有一个轴的张量,形状只有一个元素。
矩阵
正如向量将标量从零阶推广到一阶,矩阵将向量从一阶推广到二阶。
我们可以将任意矩阵 A ∈ R m × n A \in \mathbb{R}^{m \times n} A∈Rm×n
视为一个表格,其中每个元素 aij
属于第 i
行,第 j
列
当矩阵具有相同数量的行和列时,其形状将变为正方形; 因此,它被称为方阵(square matrix)。
当调用函数来实例化张量时, 我们可以通过指定两个分量m
和n
来创建一个形状为m * n
的矩阵。
当我们交换矩阵的行和列时,结果称为矩阵的转置(transpose)。
作为方阵的一种特殊类型,对称矩阵(symmetric matrix)等于其转置
张量
就像向量是标量的推广,矩阵是向量的推广一样,我们可以构建具有更多轴的数据结构。 张量(本小节中的“张量”指代数对象)是描述具有任意数量轴的n
维数组的通用方法。
张量算法的基本形式
同样,给定具有相同形状的任意两个张量,任何按元素二元运算的结果都将是相同形状的张量。
例如,将两个相同形状的矩阵相加,会在这两个矩阵上执行元素加法。
具体而言,两个矩阵的按元素乘法称为Hadamard积(Hadamard product)
将张量乘以或加上一个标量不会改变张量的形状,其中张量的每个元素都将与标量相加或相乘。
降维
我们可以对任意张量进行的一个有用的操作是计算其元素的和。
在代码中,求和的函数
import torchx = torch.arange(4, dtype=torch.float32)
print(x) # tensor([0., 1., 2., 3.])
print(x.sum()) # tensor(6.)
我们可以表示任意形状张量的元素和。
import torchA = torch.arange(20).reshape(5, 4)
print(A)
# tensor([[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11],
# [12, 13, 14, 15],
# [16, 17, 18, 19]])print(A.shape) # torch.Size([5, 4])
print(A.sum()) # tensor(190)
默认情况下,调用求和函数会沿所有的轴降低张量的维度,使它变为一个标量。
我们还可以指定张量沿哪一个轴来通过求和降低维度。
以矩阵为例,为了通过求和所有行的元素来降维(轴0)
- 可以在调用函数时指定
axis=0
。 由于输入矩阵沿0轴降维以生成输出向量,因此输入轴0的维数在输出形状中消失。(只保留一行) - 指定
axis=1
将通过汇总所有列的元素降维(轴1)。因此,输入轴1的维数在输出形状中消失。(只保留一列)
import torchA = torch.arange(20).reshape(5, 4)
print(A)
# tensor([[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11],
# [12, 13, 14, 15],
# [16, 17, 18, 19]])# 对所有行进行降维,只保留一行
A_sum_axis0 = A.sum(axis=0)
print(A_sum_axis0) # tensor([40, 45, 50, 55])
print(A_sum_axis0.shape) # torch.Size([4])# 对所有列进行降维,只保留一列
A_sum_axis1 = A.sum(axis=1)
print(A_sum_axis1) # tensor([ 6, 22, 38, 54, 70])
print(A_sum_axis1.shape) # torch.Size([5])
torch.Size([4])
表示一个 一维张量(向量),其形状(shape)为 (4,),即该张量只有一个维度,且该维度的长度为 4。
维度(Dimensions):张量的维度数由torch.Size 中的元素个数
决定。例如:
torch.Size([])
:0 维张量(标量)。torch.Size([4])
:1 维张量(向量)。torch.Size([2, 3])
:2 维张量(矩阵)。torch.Size([2, 3, 4])
:3 维张量。
沿着行和列对矩阵求和(A.sum(axis=[0, 1])
),等价于对矩阵的所有元素进行求和(A.sum()
)。
import torchA = torch.arange(20).reshape(5, 4)
print(A)
# tensor([[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11],
# [12, 13, 14, 15],
# [16, 17, 18, 19]])print(A.sum(axis=[0, 1])) # tensor(190)
print(A.sum()) # tensor(190)
一个与求和相关的量是平均值(mean或average)。 我们通过将总和除以元素总数来计算平均值。
import torchA = torch.arange(20, dtype=torch.float32).reshape(5, 4)
print(A)
# tensor([[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11],
# [12, 13, 14, 15],
# [16, 17, 18, 19]])print(A.mean()) # tensor(9.5000)
print(A.sum() / A.numel()) # tensor(9.5000)
numel()
是PyTorch
中的一个方法,用于返回张量(Tensor)中 所有元素的总数(即 number of elements 的缩写)。
它通过将张量的所有维度大小相乘,计算出总元素数量。
同样,计算平均值的函数也可以沿指定轴降低张量的维度。
import torchA = torch.arange(20, dtype=torch.float32).reshape(5, 4)
print(A)
# tensor([[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11],
# [12, 13, 14, 15],
# [16, 17, 18, 19]])print(A.mean()) # tensor(9.5000)
print(A.mean(axis=0)) # tensor([ 8., 9., 10., 11.])
print(A.mean(axis=1)) # tensor([ 1.5000, 5.5000, 9.5000, 13.5000, 17.5000])
非降维求和
有时在调用函数来计算总和或均值时保持轴数不变会很有用。
import torchA = torch.arange(20, dtype=torch.float32).reshape(5, 4)
print(A)
# tensor([[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11],
# [12, 13, 14, 15],
# [16, 17, 18, 19]])# 降维求和
sum_A_old = A.sum(axis=1)
print(sum_A_old) # tensor([ 6., 22., 38., 54., 70.])
print(sum_A_old.shape) # torch.Size([5])# 非降维求和
sum_A = A.sum(axis=1, keepdims=True)
print(sum_A)
# tensor([[ 6.],
# [22.],
# [38.],
# [54.],
# [70.]])
print(sum_A.shape) # torch.Size([5, 1])
如果我们想沿某个轴计算A元素的累积总和, 比如axis=0
(按行计算),可以调用cumsum函数
。 此函数不会沿任何轴降低输入张量的维度。
import torchA = torch.arange(20, dtype=torch.float32).reshape(5, 4)
print(A)
# tensor([[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11],
# [12, 13, 14, 15],
# [16, 17, 18, 19]])print(A.cumsum(axis=0))
# tensor([[ 0., 1., 2., 3.],
# [ 4., 6., 8., 10.],
# [12., 15., 18., 21.],
# [24., 28., 32., 36.],
# [40., 45., 50., 55.]])
点积
给定两个向量,他们的点积是相同位置的元素乘积的和
import torchx = torch.arange(4, dtype=torch.float32)
y = torch.ones(4, dtype = torch.float32)print(x) # tensor([0., 1., 2., 3.])
print(y) # tensor([1., 1., 1., 1.])
print(torch.dot(x,y)) # tensor(6.)
矩阵-向量积
矩阵向量积 Ax
是一个长度为 m
的列向量, 其第 i
个元素是点积 aiTx
在代码中使用张量表示矩阵-向量积,我们使用mv
函数。
当我们为矩阵A
和向量x
调用torch.mv(A, x)
时,会执行矩阵-向量积。
import torchA = torch.arange(20, dtype=torch.float32).reshape(5, 4)
x = torch.arange(4, dtype=torch.float32)print(A)
# tensor([[ 0., 1., 2., 3.],
# [ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.],
# [12., 13., 14., 15.],
# [16., 17., 18., 19.]])
print(x) # tensor([0., 1., 2., 3.])print(torch.mv(A, x)) # tensor([ 14., 38., 62., 86., 110.])
注意,A的列维数(沿轴1的长度)必须与x的维数(其长度)相同。
矩阵-矩阵乘法
我们可以将矩阵-矩阵乘法 AB
看作简单地执行 m
次矩阵-向量积,并将结果拼接在一起,形成一个 n * m
矩阵。
import torchA = torch.arange(20, dtype=torch.float32).reshape(5, 4)
B = torch.ones(4, 3)print(A)
# tensor([[ 0., 1., 2., 3.],
# [ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.],
# [12., 13., 14., 15.],
# [16., 17., 18., 19.]])
print(B)
# tensor([[1., 1., 1.],
# [1., 1., 1.],
# [1., 1., 1.],
# [1., 1., 1.]])
print(torch.mm(A, B))
# tensor([[ 6., 6., 6.],
# [22., 22., 22.],
# [38., 38., 38.],
# [54., 54., 54.],
# [70., 70., 70.]])
范数
向量的范数是表示一个向量有多大。 这里考虑的大小(size)概念不涉及维度,而是分量的大小。
范数的常见性质
-
按照常量因子缩放
-
三角不等式
-
范数的非负性
L2范数
向量元素平方和的平方根
import torchu = torch.tensor([3.0, -4.0])
print(torch.norm(u)) # tensor(5.)
L1范数
向量元素的绝对值之和
import torchu = torch.tensor([3.0, -4.0])
print(torch.abs(u).sum()) # tensor(7.)
Lp 范数
Frobenius范数
Frobenius范数(Frobenius norm)是矩阵元素平方和的平方根
import torchA = torch.arange(20, dtype=torch.float32).reshape(5, 4)
print(A)
# tensor([[ 0., 1., 2., 3.],
# [ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.],
# [12., 13., 14., 15.],
# [16., 17., 18., 19.]])
print(torch.norm(A)) # tensor(49.6991)
2.6、微积分
在2500年前,古希腊人把一个多边形分成三角形,并把它们的面积相加,才找到计算多边形面积的方法。 为了求出曲线形状(比如圆)的面积,古希腊人在这样的形状上刻内接多边形。
如下图所示,内接多边形的等长边越多,就越接近圆。 这个过程也被称为逼近法(method of exhaustion)。
逼近法就是积分(integral calculus)的起源。
在深度学习中,我们“训练”模型,不断更新它们,使它们在看到越来越多的数据时变得越来越好。 通常情况下,变得更好意味着最小化一个损失函数(loss function), 即一个衡量“模型有多糟糕”这个问题的分数。 最终,我们真正关心的是生成一个模型,它能够在从未见过的数据上表现良好。 但“训练”模型只能将模型与我们实际能看到的数据相拟合。
因此,我们可以将拟合模型的任务分解为两个关键问题:
- 优化(optimization):用模型拟合观测数据的过程;
- 泛化(generalization):数学原理和实践者的智慧,能够指导我们生成出有效性超出用于训练的数据集本身的模型。
导数和微分
在深度学习中,我们通常选择对于模型参数可微的损失函数。
简而言之,对于每个参数, 如果我们把这个参数增加或减少一个无穷小的量,可以知道损失会以多快的速度增加或减少,
导数的定义:
如果 f ′ ( α ) f'(\alpha) f′(α) 存在,则 f f f 在 α \alpha α 处是可微的。
如果 f f f在一个区间内的每个数上都是可微的,则此函数在此区间中是可微的。
微分常见法则
绘制图及其切线
import numpy as np
from matplotlib_inline import backend_inline
from d2l import torch as d2ldef use_svg_display(): #@save"""使用svg格式在Jupyter中显示绘图"""backend_inline.set_matplotlib_formats('svg')def set_figsize(figsize=(3.5, 2.5)): #@save"""设置matplotlib的图表大小"""use_svg_display()d2l.plt.rcParams['figure.figsize'] = figsize#@save
def set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend):"""设置matplotlib的轴"""axes.set_xlabel(xlabel)axes.set_ylabel(ylabel)axes.set_xscale(xscale)axes.set_yscale(yscale)axes.set_xlim(xlim)axes.set_ylim(ylim)if legend:axes.legend(legend)axes.grid()#@save
def plot(X, Y=None, xlabel=None, ylabel=None, legend=None, xlim=None,ylim=None, xscale='linear', yscale='linear',fmts=('-', 'm--', 'g-.', 'r:'), figsize=(3.5, 2.5), axes=None):"""绘制数据点"""if legend is None:legend = []set_figsize(figsize)axes = axes if axes else d2l.plt.gca()# 如果X有一个轴,输出Truedef has_one_axis(X):return (hasattr(X, "ndim") and X.ndim == 1 or isinstance(X, list)and not hasattr(X[0], "__len__"))if has_one_axis(X):X = [X]if Y is None:X, Y = [[]] * len(X), Xelif has_one_axis(Y):Y = [Y]if len(X) != len(Y):X = X * len(Y)axes.cla()for x, y, fmt in zip(X, Y, fmts):if len(x):axes.plot(x, y, fmt)else:axes.plot(y, fmt)set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend)def f(t):return 3 * t ** 2 - 4 * tx = np.arange(0, 3, 0.1)
plot(x, [f(x), 2 * x - 3], 'x', 'f(x)', legend=['f(x)', 'Tangent line (x=1)'])
d2l.plt.show()
偏导数
到目前为止,我们只讨论了仅含一个变量的函数的微分。
在深度学习中,函数通常依赖于许多变量。
因此,我们需要将微分的思想推广到多元函数(multivariate function上。
- 导数:在一元函数 y = f ( x ) y=f(x) y=f(x)中,导数 f ′ ( x ) f'(x) f′(x)表示曲线在点 ( x , f ( x ) ) (x, f(x)) (x,f(x))处的切线斜率
- 偏导数:在多元函数 z = f ( x , y ) z=f(x, y) z=f(x,y)中,偏导数 ∂ f ∂ x \frac{\partial f}{\partial x} ∂x∂f表示曲面在点 ( x , y , f ( x , y ) ) (x,y,f(x,y)) (x,y,f(x,y))处沿着x轴方向的切线斜率(固定y不变)
梯度
我们可以连结一个多元函数对其所有变量的偏导数,以得到该函数的梯度(gradient)向量。
链式法则
然而,上面方法可能很难找到梯度。
这是因为在深度学习中,多元函数通常是复合(composite)的, 所以难以应用上述任何规则来微分这些函数。
幸运的是,链式法则可以被用来微分复合函数。
链式法则是微积分中用于求解复合函数导数的核心工具
其核心思想是:复合函数的导数等于外层函数的导数乘以内层函数的导数,层层传递,如同链条一样。
2.7、自动微分
深度学习框架通过自动计算导数,即自动微分(automatic differentiation)来加快求导。
实际中,根据设计好的模型,系统会构建一个计算图(computational graph), 来跟踪计算是哪些数据通过哪些操作组合起来产生输出。
自动微分使系统能够随后反向传播梯度。
这里,反向传播(backpropagate)意味着跟踪整个计算图,填充关于每个参数的偏导数。
反向传播:反向传播(Back Propagation, BP) 是神经网络训练的核心算法之一,主要用于计算损失函数对网络参数的梯度,并通过梯度下降等优化方法更新参数,从而最小化损失函数。
计算图
PyTorch 中的 计算图(Computational Graph)是深度学习框架中用于描述数学运算的 有向无环图(DAG)。
它通过节点和边记录张量(Tensor)之间的运算关系,是 PyTorch 实现 自动求导(Autograd)的核心机制。
- 节点(Node):
叶子节点(Leaf Node):用户直接创建的张量(如x = torch.tensor(..., requires_grad=True)
),grad_fn
为None
。
中间节点:由运算生成的张量(如y = x + 2
),grad_fn
记录生成该张量的操作(如<AddBackward0>
)。 - 边(Edge):
表示张量之间的依赖关系,即数据流。例如,y = x * x 中,边连接 x 和 y,表示 y 的计算依赖于 x。
一个简单的例子
一个简单的例子,假设我们想要对函数 y = 2 ∗ x T ∗ x y=2*x^T*x y=2∗xT∗x关于列向量 x x x求导。首先,我们创建变量 x x x并为其分配一个初始值。
在我们计算 y y y关于 x x x的梯度之前,需要一个地方来存储梯度。(避免每次都用新的内存来存储梯度,造成内存耗尽)
在 PyTorch
中,x.requires_grad = True
或 x.requires_grad_(True)
的作用是启用对张量 x 的梯度追踪,这是自动求导(Autograd)的核心机制之一。
PyTorch
默认不会追踪张量的操作,以节省内存
启用后的作用:
PyTorch
会记录所有对x
的操作(如加法、乘法、矩阵运算等),构建一个动态计算图。- 在调用
backward()
进行反向传播时,可以自动计算x
的梯度(x.grad
)。
import torchx = torch.arange(4.0)
print(x) # tensor([0., 1., 2., 3.])# 启动对张量 x 的梯度追踪
x.requires_grad_(True)
print(x.grad) # 默认值是 None# 计算 y = 2x^2
y = 2 * torch.dot(x, x)
print(y) # tensor(28., grad_fn=<MulBackward0>)# 调用反向传播函数来自动计算 y 关于 x 每个分量的梯度,并打印
y.backward()
print(x.grad) # tensor([ 0., 4., 8., 12.])# 验证梯度是否正确
print(x.grad == 4 * x) # tensor([True, True, True, True])
grad_fn=<MulBackward0>
- 含义:
grad_fn
是张量的一个属性,表示生成该张量的操作对应的梯度函数(Gradient Function)。 <MulBackward0>
表示该张量是通过 乘法操作(*)
生成的,并且在反向传播时会使用乘法的反向传播规则(即乘法的导数)。- 动态计算图:
PyTorch
使用动态计算图记录操作历史。grad_fn
是计算图中的一部分,用于追踪该张量是如何从其他张量(叶子节点或中间节点)生成的。
非标量变量的反向传播
当y
不是标量时,向量y
关于向量x
的导数的最自然解释是一个矩阵。
对于高阶和高维的y和x,求导的结果可以是一个高阶张量。
import torchx = torch.arange(4.0)
print(x) # tensor([0., 1., 2., 3.])# 启动对张量 x 的梯度追踪
x.requires_grad_(True)
print(x.grad) # 默认值是 None# 对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self的梯度。
# 本例只想求偏导数的和,所以传递一个1的梯度是合适的
y = x * x
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward()
print(x.grad) # tensor([0., 2., 4., 6.])
为什么需要
.sum()
?
backward()
默认处理标量。若y
是向量,需通过.sum()
或提供梯度权重(gradient 参数)
将其转换为标量。
对于y = x²
,其导数为dy/dx = 2x
。由于y.sum()
是x₁² + x₂² + x₃²
,其对x
的梯度是[2x₁, 2x₂, 2x₃]
。
分离计算
有时,我们希望将某些计算移动到记录的计算图之外。
例如,假设 y y y是作为 x x x的函数计算的,而 z z z则是作为 y y y和 x x x的函数计算的。
想象一下,我们想计算 z z z关于 x x x的梯度,但由于某种原因,希望将 y y y视为一个常数, 并且只考虑到 x x x在 y y y被计算后发挥的作用。
这里可以分离 y y y来返回一个新变量 u u u,该变量与 y y y具有相同的值, 但丢弃计算图中如何计算 y y y的任何信息。
import torchx = torch.arange(4.0)
print(x) # tensor([0., 1., 2., 3.])# 启动对张量 x 的梯度追踪
x.requires_grad_(True)
print(x.grad) # 默认值是 Noney = x * x
# 创建新张量,与原张量数据相同,但不记录梯度,防止梯度传播
u = y.detach()
z = u * xz.sum().backward()
print(x.grad == u) # tensor([True, True, True, True])
将 u u u 作为常数处理
随后可以单独计算 y y y 关于 x x x 的导数
Python 控制梯度流的计算
使用自动微分的一个好处是: 即使构建函数的计算图需要通过 Python
控制流(例如,条件、循环或任意函数调用),我们仍然可以计算得到的变量的梯度。
在下面的代码中,while
循环的迭代次数和 if
语句的结果都取决于输入 a
的值。
import torchdef f(a):b = a * 2while b.norm() < 1000:b = b * 2if b.sum() > 0:c = belse:c = 100 * breturn c# 创建一个标量张量(scalar tensor),
# 其数值从标准正态分布(均值为 0,标准差为 1)中随机生成,
# 并且启用了梯度追踪功能。
a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()print(a.grad == d / a) # tensor(True)
我们现在可以分析上面定义的
f
函数。 请注意,它在其输入a
中是分段线性的。 换言之,对于任何a
,存在某个常量标量k
,使得f(a)=k*a
,其中k
的值取决于输入a
,因此可以用d/a
验证梯度是否正确。
2.8、概率
简单地说,机器学习就是做出预测。
2.8.1、基本概率论
大数定律(law of large numbers): 将出现的次数除以投掷的总次数, 即此事件(event)概率的估计值。随着投掷次数的增加,估计值会越来越接近真实的潜在概率。
在统计学中,我们把从概率分布中抽取样本的过程称为抽样(sampling)。
import torch
from torch.distributions import multinomial
from d2l import torch as d2l# torch.ones([6]):生成一个形状为 (6,) 的张量,所有元素初始化为 1,即 [1, 1, 1, 1, 1, 1]。
fair_probs = torch.ones([6]) / 6 # tensor([0.1667, 0.1667, 0.1667, 0.1667, 0.1667, 0.1667])# total_count 实验的总次数, fair_probs 指定每个类别的概率分布
# sample 从分布中采样一次,返回一个形状为 (6, )的张量(one-hot 向量)
# 返回一个 one-hot 向量,其中只有一个元素为 1(表示该类别被选中),其余为 0。
res = multinomial.Multinomial(10000, fair_probs).sample()print(res / 10000) # tensor([0.1629, 0.1630, 0.1709, 0.1685, 0.1623, 0.1724])# 绘图 看到这些概率如何随着时间的推移收敛到真实概率。
counts = multinomial.Multinomial(10, fair_probs).sample((500,))
cum_counts = counts.cumsum(dim=0)
estimates = cum_counts / cum_counts.sum(dim=1, keepdims=True)d2l.set_figsize((6, 4.5))
for i in range(6):d2l.plt.plot(estimates[:, i].numpy(),label=("P(die=" + str(i + 1) + ")"))
d2l.plt.axhline(y=0.167, color='black', linestyle='dashed')
d2l.plt.gca().set_xlabel('Groups of experiments')
d2l.plt.gca().set_ylabel('Estimated probability')
d2l.plt.legend()
d2l.plt.show()
概率论公理
我们将集合 S = { 1 , 2 , 3 , 4 , 5 , 6 } S = \{1, 2, 3,4,5,6\} S={1,2,3,4,5,6}称为样本空间(sample space)或结果空间(outcome space)
事件(event)是一组给定样本空间的随机结果。
随机变量
随机变量几乎可以是任何数量,并且它可以在随机实验的一组可能性中取一个值。
我们可以将 P ( X ) P(X) P(X)表示为随机变量 X X X上的分布(distribution): 分布告诉我们 X X X获得某一值的概率。
另一方面,我们可以简单用 P ( a ) P(a) P(a)表示随机变量取值 a a a的概率。
离散(discrete)随机变量(如骰子的每一面) 和连续(continuous)随机变量(如人的体重和身高)之间存在微妙的区别。
如果我们进行足够精确的测量,最终会发现这个星球上没有两个人具有完全相同的身高。 在这种情况下,询问某人的身高是否落入给定的区间,比如是否在1.79米和1.81米之间更有意义。 在这些情况下,我们将这个看到某个数值的可能性量化为密度(density)。
2.8.2、处理多个随机变量
联合概率
P ( A = a , B = b ) P(A=a, B=b) P(A=a,B=b)表示: A = a A=a A=a和 B = b B=b B=b同时满足的概率
条件概率
P ( B = b ∣ A = a ) P(B=b | A=a) P(B=b∣A=a)表示:在 a a a已经发生的条件下, A = a A=a A=a和 B = b B=b B=b同时满足的概率
P ( B = b ∣ A = a ) = P ( A = a , B = b ) P ( A = a ) P(B=b | A=a) = \frac{P(A=a, B=b)}{P(A = a)} P(B=b∣A=a)=P(A=a)P(A=a,B=b)
贝叶斯定理
P ( B = b ∣ A = a ) = P ( A = a ∣ B = b ) ∗ P ( B = b ) P ( A = a ) P(B=b | A=a) = \frac{P(A=a | B=b) * P(B=b)}{P(A = a)} P(B=b∣A=a)=P(A=a)P(A=a∣B=b)∗P(B=b)
边际化
即 B B B的概率相当于计算 A A A的所有可能选择,并将所有选择的联合概率聚合在一起
P ( B ) = ∑ A P ( A , B ) P(B) = \sum_{A} P(A,B) P(B)=A∑P(A,B)
独立性
如果两个随机变量 A A A和 B B B是独立的,意味着事件 A A A的发生跟事件 B B B的发生无关。
P ( B = b ∣ A = a ) = P ( B = b ) P(B=b | A=a) =P(B=b) P(B=b∣A=a)=P(B=b)
P ( B = b , A = a ) = P ( B = b ) ∗ P ( A = a ) P(B=b, A=a) =P(B=b) * P(A=a) P(B=b,A=a)=P(B=b)∗P(A=a)
2.8.3、期望和方差
一个随机变量 X X X的期望(expectation,或平均值(average))表示为
E [ X ] = ∑ x x ∗ P ( X = x ) E[X] = \sum_{x} x * P(X = x) E[X]=x∑x∗P(X=x)
衡量随机变量 X X X与其期望值的偏置。这可以通过方差来量化
V a r [ X ] = E [ ( X − E [ X ] ) 2 ] Var[X]=E[(X-E[X])^2] Var[X]=E[(X−E[X])2]
方差的平方根就是标准差
2.9、查阅文档
查找指定函数的方法的作用
help(torch.ones)
返回结果
Help on built-in function ones in module torch:ones(...)ones(*size, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) -> TensorReturns a tensor filled with the scalar value `1`, with the shape definedby the variable argument :attr:`size`.Args:size (int...): a sequence of integers defining the shape of the output tensor.Can be a variable number of arguments or a collection like a list or tuple.Keyword arguments:out (Tensor, optional): the output tensor.dtype (:class:`torch.dtype`, optional): the desired data type of returned tensor.Default: if ``None``, uses a global default (see :func:`torch.set_default_dtype`).layout (:class:`torch.layout`, optional): the desired layout of returned Tensor.Default: ``torch.strided``.device (:class:`torch.device`, optional): the desired device of returned tensor.Default: if ``None``, uses the current device for the default tensor type(see :func:`torch.set_default_device`). :attr:`device` will be the CPUfor CPU tensor types and the current CUDA device for CUDA tensor types.requires_grad (bool, optional): If autograd should record operations on thereturned tensor. Default: ``False``.Example::>>> torch.ones(2, 3)tensor([[ 1., 1., 1.],[ 1., 1., 1.]])>>> torch.ones(5)tensor([ 1., 1., 1., 1., 1.])