文章目錄
- 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 = x2
,其導數為dy/dx = 2x
。由于y.sum()
是x?2 + x?2 + x?2
,其對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.])