在繼續講解模塊消融前,先補充幾個之前沒提的基礎概念
尤其需要搞懂張量的維度、以及計算后的維度,這對于你未來理解復雜的網絡至關重要
一、 隨機張量的生成
在深度學習中經常需要隨機生成一些張量,比如權重的初始化,或者計算輸入緯度經過模塊后輸出的維度,都可以用一個隨機函數來實現需要的張量格式,而無需像之前一樣必須加載一張真實的圖片。
隨機函數的種類很多,我們了解其中一種即可,畢竟目的主要就是生成,對分布要求不重要。
1.1 torch.randn函數
在 PyTorch 中,torch.randn()是一個常用的隨機張量生成函數,它可以創建一個由標準正態分布(均值為 0,標準差為 1)隨機數填充的張量。這種隨機張量在深度學習中非常實用,常用于初始化模型參數、生成測試數據或模擬輸入特征。
torch.randn(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
- size:必選參數,表示輸出張量的形狀(如(3, 4)表示 3 行 4 列的矩陣)。
- dtype:可選參數,指定張量的數據類型(如torch.float32、torch.int64等)。
- device:可選參數,指定張量存儲的設備(如'cpu'或'cuda')。
- requires_grad:可選參數,是否需要計算梯度(常用于訓練模型時)。
import torch
# 生成標量(0維張量)
scalar = torch.randn(())
print(f"標量: {scalar}, 形狀: {scalar.shape}")
標量: -1.6167410612106323, 形狀: torch.Size([])
# 生成向量(1維張量)
vector = torch.randn(5) # 長度為5的向量
print(f"向量: {vector}, 形狀: {vector.shape}")
向量: tensor([-1.9524, 0.5900, 0.7467, -1.8307, 0.4263]), 形狀: torch.Size([5])
# 生成矩陣(2維張量)
matrix = torch.randn(3, 4) # 3行4列的矩陣
print(f"矩陣:{matrix},矩陣形狀: {matrix.shape}")
矩陣:tensor([[ 0.0283, 0.7692, 0.2744, -1.6120],[ 0.3726, 1.5382, -1.0128, 0.4129],[ 0.4898, 1.4782, 0.2019, 0.0863]]),矩陣形狀: torch.Size([3, 4])
# 生成3維張量(常用于圖像數據的通道、高度、寬度)
tensor_3d = torch.randn(3, 224, 224) # 3通道,高224,寬224
print(f"3維張量形狀: {tensor_3d.shape}") # 輸出: torch.Size([3, 224, 224])
3維張量形狀: torch.Size([3, 224, 224])
# 生成4維張量(常用于批量圖像數據:[batch, channel, height, width])
tensor_4d = torch.randn(2, 3, 224, 224) # 批量大小為2,3通道,高224,寬224
print(f"4維張量形狀: {tensor_4d.shape}") # 輸出: torch.Size([2, 3, 224, 224])
4維張量形狀: torch.Size([2, 3, 224, 224])
1.2 其他隨機函數
除了這些隨機函數還有很多,自行了解,主要是生成數據的分布不同。掌握一個即可,掌握多了參數也記不住。
torch.rand():生成在 [0, 1) 范圍內均勻分布的隨機數。
x = torch.rand(3, 2) # 生成3x2的張量
print(f"均勻分布隨機數: {x}, 形狀: {x.shape}")
均勻分布隨機數: tensor([[0.2089, 0.7786],[0.1043, 0.1573],[0.9637, 0.0397]]), 形狀: torch.Size([3, 2])
torch.randint():生成指定范圍內的隨機整數
x = torch.randint(low=0, high=10, size=(3,)) # 生成3個0到9之間的整數
print(f"隨機整數: {x}, 形狀: {x.shape}")
隨機整數: tensor([3, 5, 7]), 形狀: torch.Size([3])
torch.normal():生成指定均值和標準差的正態分布隨機數。
mean = torch.tensor([0.0, 0.0])
std = torch.tensor([1.0, 2.0])
x = torch.normal(mean, std) # 生成兩個正態分布隨機數
print(f"正態分布隨機數: {x}, 形狀: {x.shape}")
正態分布隨機數: tensor([ 0.1419, -1.5212]), 形狀: torch.Size([2])
# 一維張量與二維張量相加
a = torch.tensor([[1, 2, 3], [4, 5, 6]]) # 形狀: (2, 3)
b = torch.tensor([10, 20, 30]) # 形狀: (3,)# 廣播后:b被擴展為[[10, 20, 30], [10, 20, 30]]
result = a + b
result
tensor([[11, 22, 33],[14, 25, 36]])
1.3 輸出維度測試
import torch
import torch.nn as nn# 生成輸入張量 (批量大小, 通道數, 高度, 寬度)
input_tensor = torch.randn(1, 3, 32, 32) # 例如CIFAR-10圖像
print(f"輸入尺寸: {input_tensor.shape}") # 輸出: [1, 3, 32, 32]
輸入尺寸: torch.Size([1, 3, 32, 32])
二維的卷積和池化計算公式是一致的
# 1. 卷積層操作
conv1 = nn.Conv2d(in_channels=3, # 輸入通道數out_channels=16, # 輸出通道數(卷積核數量)kernel_size=3, # 卷積核大小stride=1, # 步長padding=1 # 填充
)
conv_output = conv1(input_tensor) # 由于 padding=1 且 stride=1,空間尺寸保持不變
print(f"卷積后尺寸: {conv_output.shape}") # 輸出: [1, 16, 32, 32]
卷積后尺寸: torch.Size([1, 16, 32, 32])
# 2. 池化層操作 (減小空間尺寸)
pool = nn.MaxPool2d(kernel_size=2, stride=2) # 創建一個最大池化層
pool_output = pool(conv_output)
print(f"池化后尺寸: {pool_output.shape}") # 輸出: [1, 16, 16, 16]
池化后尺寸: torch.Size([1, 16, 16, 16])
# 3. 將多維張量展平為向量
flattened = pool_output.view(pool_output.size(0), -1)
print(f"展平后尺寸: {flattened.shape}") # 輸出: [1, 4096] (16*16*16=4096)
展平后尺寸: torch.Size([1, 4096])
# 4. 線性層操作
fc1 = nn.Linear(in_features=4096, # 輸入特征數out_features=128 # 輸出特征數
)
fc_output = fc1(flattened)
print(f"線性層后尺寸: {fc_output.shape}") # 輸出: [1, 128]
線性層后尺寸: torch.Size([1, 128])
# 5. 再經過一個線性層(例如分類器)
fc2 = nn.Linear(128, 10) # 假設是10分類問題
final_output = fc2(fc_output)
print(f"最終輸出尺寸: {final_output.shape}") # 輸出: [1, 10]
print(final_output)
最終輸出尺寸: torch.Size([1, 10]) tensor([[-0.3018, -0.4308, 0.3248, 0.2808, 0.5109, -0.0881, -0.0787, -0.0700,-0.1004, -0.0580]], grad_fn=<AddmmBackward>)
多分類問題通常使用Softmax,二分類問題用Sigmoid
# 使用Softmax替代Sigmoid
softmax = nn.Softmax(dim=1) # 在類別維度上進行Softmax
class_probs = softmax(final_output)
print(f"Softmax輸出: {class_probs}") # 總和為1的概率分布
print(f"Softmax輸出總和: {class_probs.sum():.4f}")
Softmax輸出: tensor([[0.0712, 0.0626, 0.1332, 0.1275, 0.1605, 0.0882, 0.0890, 0.0898, 0.0871,0.0909]], grad_fn=<SoftmaxBackward>) Softmax輸出總和: 1.0000
通過這種方法,可以很自然的看到每一層輸出的shape,實際上在pycharm等非交互式環境ipynb時,可以借助斷點+調試控制臺不斷測試維度信息,避免報錯。
二、廣播機制
什么叫做廣播機制
PyTorch 的廣播機制(Broadcasting)是一種強大的張量運算特性,允許在不同形狀的張量之間進行算術運算,而無需顯式地擴展張量維度或復制數據。這種機制使得代碼更簡潔高效,尤其在處理多維數據時非常實用。
當對兩個形狀不同的張量進行運算時,PyTorch 會自動調整它們的形狀,使它們在維度上兼容。具體規則如下:
從右向左比較維度:PyTorch 從張量的最后一個維度開始向前比較,檢查每個維度的大小是否相同或其中一個為 1。 維度擴展規則: 如果兩個張量的某個維度大小相同,則繼續比較下一個維度。 如果其中一個張量的某個維度大小為 1,則該維度會被擴展為另一個張量對應維度的大小。 如果兩個張量的某個維度大小既不相同也不為 1,則會報錯。
2.1 PyTorch 廣播機制
PyTorch 的廣播機制(Broadcasting)是一種高效的張量運算特性,允許在不同形狀的張量之間執行元素級操作(如加法、乘法),而無需顯式擴展或復制數據。這種機制通過自動調整張量維度來實現形狀兼容,使代碼更簡潔、計算更高效。
當對兩個形狀不同的張量進行運算時,PyTorch 會按以下規則自動處理維度兼容性:
- 從右向左比較維度:PyTorch 從張量的最后一個維度(最右側)開始向前逐維比較。
- 維度擴展條件:
- 相等維度:若兩個張量在某一維度上大小相同,則繼續比較下一維度。
- 一維擴展:若其中一個張量在某一維度上大小為?1,則該維度會被擴展為另一個張量對應維度的大小。
- 不兼容錯誤:若某一維度大小既不相同也不為?1,則拋出?
RuntimeError
。-----維度必須滿足廣播規則,否則會報錯。
- 維度補全規則:若一個張量的維度少于另一個,則在其左側補 1?直至維度數匹配。
關注2個信息
- 廣播后的尺寸變化
- 擴展后的值變化
2.1.1 加法的廣播機制
1二維張量與一維向量相加
import torch# 創建原始張量
a = torch.tensor([[10], [20], [30]]) # 形狀: (3, 1)
b = torch.tensor([1, 2, 3]) # 形狀: (3,)result = a + b
# 廣播過程
# 1. b補全維度: (3,) → (1, 3)
# 2. a擴展列: (3, 1) → (3, 3)
# 3. b擴展行: (1, 3) → (3, 3)
# 最終形狀: (3, 3)print("原始張量a:")
print(a)print("\n原始張量b:")
print(b)print("\n廣播后a的值擴展:")
print(torch.tensor([[10, 10, 10],[20, 20, 20],[30, 30, 30]])) # 實際內存中未復制,僅邏輯上擴展print("\n廣播后b的值擴展:")
print(torch.tensor([[1, 2, 3],[1, 2, 3],[1, 2, 3]])) # 實際內存中未復制,僅邏輯上擴展print("\n加法結果:")
print(result)
原始張量a: tensor([[10],[20],[30]])原始張量b: tensor([1, 2, 3])廣播后a的值擴展: tensor([[10, 10, 10],[20, 20, 20],[30, 30, 30]])廣播后b的值擴展: tensor([[1, 2, 3],[1, 2, 3],[1, 2, 3]])加法結果: tensor([[11, 12, 13],[21, 22, 23],[31, 32, 33]])
2三維張量與二維張量相加
# 創建原始張量
a = torch.tensor([[[1], [2]], [[3], [4]]]) # 形狀: (2, 2, 1)
b = torch.tensor([[10, 20]]) # 形狀: (1, 2)# 廣播過程
# 1. b補全維度: (1, 2) → (1, 1, 2)
# 2. a擴展第三維: (2, 2, 1) → (2, 2, 2)
# 3. b擴展第一維: (1, 1, 2) → (2, 1, 2)
# 4. b擴展第二維: (2, 1, 2) → (2, 2, 2)
# 最終形狀: (2, 2, 2)result = a + b
print("原始張量a:")
print(a)print("\n原始張量b:")
print(b)print("\n廣播后a的值擴展:")
print(torch.tensor([[[1, 1],[2, 2]],[[3, 3],[4, 4]]])) # 實際內存中未復制,僅邏輯上擴展print("\n廣播后b的值擴展:")
print(torch.tensor([[[10, 20],[10, 20]],[[10, 20],[10, 20]]])) # 實際內存中未復制,僅邏輯上擴展print("\n加法結果:")
print(result)
原始張量a: tensor([[[1],[2]],[[3],[4]]])原始張量b: tensor([[10, 20]])廣播后a的值擴展: tensor([[[1, 1],[2, 2]],[[3, 3],[4, 4]]])廣播后b的值擴展: tensor([[[10, 20],[10, 20]],[[10, 20],[10, 20]]])加法結果: tensor([[[11, 21],[12, 22]],[[13, 23],[14, 24]]])
3二維張量與標量相加
# 創建原始張量
a = torch.tensor([[1, 2], [3, 4]]) # 形狀: (2, 2)
b = 10 # 標量,形狀視為 ()# 廣播過程
# 1. b補全維度: () → (1, 1)
# 2. b擴展第一維: (1, 1) → (2, 1)
# 3. b擴展第二維: (2, 1) → (2, 2)
# 最終形狀: (2, 2)result = a + b
print("原始張量a:")
print(a)
# 輸出:
# tensor([[1, 2],
# [3, 4]])print("\n標量b:")
print(b)
# 輸出: 10print("\n廣播后b的值擴展:")
print(torch.tensor([[10, 10],[10, 10]])) # 實際內存中未復制,僅邏輯上擴展print("\n加法結果:")
print(result)
# 輸出:
# tensor([[11, 12],
# [13, 14]])
原始張量a: tensor([[1, 2],[3, 4]])標量b: 10廣播后b的值擴展: tensor([[10, 10],[10, 10]])加法結果: tensor([[11, 12],[13, 14]])
4高維張量與低維張量相加
# 創建原始張量
a = torch.tensor([[[1, 2], [3, 4]]]) # 形狀: (1, 2, 2)
b = torch.tensor([[5, 6]]) # 形狀: (1, 2)# 廣播過程
# 1. b補全維度: (1, 2) → (1, 1, 2)
# 2. b擴展第二維: (1, 1, 2) → (1, 2, 2)
# 最終形狀: (1, 2, 2)result = a + b
print("原始張量a:")
print(a)
# 輸出:
# tensor([[[1, 2],
# [3, 4]]])print("\n原始張量b:")
print(b)
# 輸出:
# tensor([[5, 6]])print("\n廣播后b的值擴展:")
print(torch.tensor([[[5, 6],[5, 6]]])) # 實際內存中未復制,僅邏輯上擴展print("\n加法結果:")
print(result)
# 輸出:
# tensor([[[6, 8],
# [8, 10]]])
原始張量a: tensor([[[1, 2],[3, 4]]])原始張量b: tensor([[5, 6]])廣播后b的值擴展: tensor([[[5, 6],[5, 6]]])加法結果: tensor([[[ 6, 8],[ 8, 10]]])
關鍵總結
- 尺寸變化:廣播后的形狀由各維度的最大值決定(示例 2 中最終形狀為 (2, 2, 2))。
- 值擴展:維度為 1 的張量通過復制擴展值(示例 1 中 b 從 [1, 2, 3] 擴展為三行相同的值)。
- 內存效率:擴展是邏輯上的,實際未復制數據,避免了內存浪費。
2.1.2 乘法的廣播機制
矩陣乘法(@)的特殊規則
矩陣乘法除了遵循通用廣播規則外,還需要滿足矩陣乘法的維度約束:
最后兩個維度必須滿足:A.shape[-1] == B.shape[-2](即 A 的列數等于 B 的行數)
其他維度(批量維度):遵循通用廣播規則
1批量矩陣與單個矩陣相乘
import torch# A: 批量大小為2,每個是3×4的矩陣
A = torch.randn(2, 3, 4) # 形狀: (2, 3, 4)# B: 單個4×5的矩陣
B = torch.randn(4, 5) # 形狀: (4, 5)# 廣播過程:
# 1. B補全維度: (4, 5) → (1, 4, 5)
# 2. B擴展第一維: (1, 4, 5) → (2, 4, 5)
# 矩陣乘法: (2, 3, 4) @ (2, 4, 5) → (2, 3, 5)
result = A @ B # 結果形狀: (2, 3, 5)print("A形狀:", A.shape) # 輸出: torch.Size([2, 3, 4])
print("B形狀:", B.shape) # 輸出: torch.Size([4, 5])
print("結果形狀:", result.shape) # 輸出: torch.Size([2, 3, 5])
A形狀: torch.Size([2, 3, 4]) B形狀: torch.Size([4, 5]) 結果形狀: torch.Size([2, 3, 5])
2批量矩陣與批量矩陣相乘(部分廣播)
# A: 批量大小為3,每個是2×4的矩陣
A = torch.randn(3, 2, 4) # 形狀: (3, 2, 4)# B: 批量大小為1,每個是4×5的矩陣
B = torch.randn(1, 4, 5) # 形狀: (1, 4, 5)# 廣播過程:
# B擴展第一維: (1, 4, 5) → (3, 4, 5)
# 矩陣乘法: (3, 2, 4) @ (3, 4, 5) → (3, 2, 5)
result = A @ B # 結果形狀: (3, 2, 5)print("A形狀:", A.shape) # 輸出: torch.Size([3, 2, 4])
print("B形狀:", B.shape) # 輸出: torch.Size([1, 4, 5])
print("結果形狀:", result.shape) # 輸出: torch.Size([3, 2, 5])
A形狀: torch.Size([3, 2, 4]) B形狀: torch.Size([1, 4, 5]) 結果形狀: torch.Size([3, 2, 5])
3三維張量與二維張量相乘(高維廣播)
# A: 批量大小為2,通道數為3,每個是4×5的矩陣
A = torch.randn(2, 3, 4, 5) # 形狀: (2, 3, 4, 5)# B: 單個5×6的矩陣
B = torch.randn(5, 6) # 形狀: (5, 6)# 廣播過程:
# 1. B補全維度: (5, 6) → (1, 1, 5, 6)
# 2. B擴展第一維: (1, 1, 5, 6) → (2, 1, 5, 6)
# 3. B擴展第二維: (2, 1, 5, 6) → (2, 3, 5, 6)
# 矩陣乘法: (2, 3, 4, 5) @ (2, 3, 5, 6) → (2, 3, 4, 6)
result = A @ B # 結果形狀: (2, 3, 4, 6)print("A形狀:", A.shape) # 輸出: torch.Size([2, 3, 4, 5])
print("B形狀:", B.shape) # 輸出: torch.Size([5, 6])
print("結果形狀:", result.shape) # 輸出: torch.Size([2, 3, 4, 6])
A形狀: torch.Size([2, 3, 4, 5]) B形狀: torch.Size([5, 6]) 結果形狀: torch.Size([2, 3, 4, 6])
知識點回顧:
- 隨機張量的生成:torch.randn函數
- 卷積和池化的計算公式(可以不掌握,會自動計算的)
- pytorch的廣播機制:加法和乘法的廣播機制
ps:numpy運算也有類似的廣播機制,基本一致
作業:
自己多借助ai舉幾個例子幫助自己理解即可
一、隨機張量生成:torch.randn 函數
功能
生成服從 標準正態分布(均值為 0,方差為 1) 的隨機張量。
常見用法與示例
python
運行
import torch# 示例1:生成形狀為 (3, 4) 的隨機張量(2維)
tensor1 = torch.randn(3, 4)
print("tensor1 shape:", tensor1.shape)
print("tensor1內容:\n", tensor1)# 示例2:生成形狀為 (2, 3, 5) 的隨機張量(3維,常用于圖像批次)
tensor2 = torch.randn(2, 3, 5) # 假設 batch_size=2,通道數=3,尺寸=5x5
print("\ntensor2 shape:", tensor2.shape)
print("tensor2內容:\n", tensor2)# 示例3:生成指定數據類型的張量(如 float64)
tensor3 = torch.randn(2, 2, dtype=torch.float64)
print("\ntensor3 dtype:", tensor3.dtype)
輸出說明
plaintext
tensor1 shape: torch.Size([3, 4])
tensor1內容:
tensor([[ 0.123, -0.456, 0.789, -1.012],
[-2.345, 3.456, -4.567, 5.678],
[-6.789, 7.890, -8.901, 9.012]])
tensor2 shape: torch.Size([2, 3, 5])
tensor2內容:
tensor([[[-0.111, 0.222, -0.333, 0.444, -0.555],
[ 0.666, -0.777, 0.888, -0.999, 1.010],
[ 1.111, -1.222, 1.333, -1.444, 1.555]],
[[-1.666, 1.777, -1.888, 1.999, -2.010],
[ 2.111, -2.222, 2.333, -2.444, 2.555],
[-2.666, 2.777, -2.888, 2.999, -3.010]]])
tensor3 dtype: torch.float64
二、PyTorch 廣播機制(加法和乘法)
核心規則
當兩個張量形狀不同時,PyTorch 會自動擴展維度較小的張量,使其形狀與另一個張量兼容,前提是:
- 從后往前(右到左)對比維度,每個維度要么相等,要么其中一個為 1。
- 擴展的維度會被 “復制”,以匹配另一個張量的形狀。
示例 1:加法廣播(維度互補)
python
運行
# 張量A:形狀 (3, 1),張量B:形狀 (1, 4)
A = torch.tensor([[1], [2], [3]]) # shape (3, 1)
B = torch.tensor([[1, 2, 3, 4]]) # shape (1, 4)
C = A + B # 廣播后形狀變為 (3, 4)
print("A + B 的結果:\n", C)
廣播過程分析
- A 的形狀:(3, 1) → 從右到左維度為 [1, 3](注意 PyTorch 按 (dim0, dim1) 存儲,這里從后往前是 dim1=1, dim0=3)。
- B 的形狀:(1, 4) → 從右到左維度為 [4, 1]。
- 對比維度:最后一維(列):A 的維度是 1,B 的維度是 4 → 兼容,A 的列會被復制為 4 列。
- 第一維(行):A 的維度是 3,B 的維度是 1 → 兼容,B 的行會被復制為 3 行。
- 結果形狀:(3, 4)。
輸出結果
plaintext
A + B 的結果:
tensor([[2, 3, 4, 5],
[3, 4, 5, 6],
[4, 5, 6, 7]])
示例 2:乘法廣播(維度為 1 的擴展)
python
運行
# 張量X:形狀 (2, 3, 4),張量Y:形狀 (4,)
X = torch.randn(2, 3, 4) # shape (2, 3, 4)
Y = torch.tensor([1, 2, 3, 4]) # shape (4,) → 等價于 (1, 1, 4)
Z = X * Y # 廣播后Y形狀變為 (1, 1, 4),與X的 (2, 3, 4) 兼容
print("X * Y 的形狀:", Z.shape)
廣播過程分析
- Y 的形狀:(4,) → 視為 (1, 1, 4)(補前兩個維度為 1)。
- 對比維度:最后一維(4):相等,無需擴展。
- 前兩維(1 和 1):與 X 的 (2, 3) 兼容,Y 會被復制為 (2, 3, 4)。
- 結果形狀:(2, 3, 4)。
三、Numpy 廣播機制(與 PyTorch 基本一致)
示例:Numpy 加法廣播
python
運行
import numpy as np# 數組A:形狀 (3, 1),數組B:形狀 (1, 4)
A = np.array([[1], [2], [3]])
B = np.array([[1, 2, 3, 4]])
C = A + B # 結果與 PyTorch 示例1完全一致
print("Numpy A + B 的結果:\n", C)
輸出結果
plaintext
Numpy A + B 的結果:
[[2 3 4 5]
[3 4 5 6]
[4 5 6 7]]
四、卷積和池化(自動計算,無需手動公式)
在 PyTorch 中,卷積和池化通過定義層(如 nn.Conv2d, nn.MaxPool2d)實現,框架會自動計算輸出形狀。以下是簡單示例:
示例:2D 卷積層
python
運行
import torch.nn as nn# 輸入:batch_size=1,通道數=1,尺寸=5x5
x = torch.randn(1, 1, 5, 5)
# 卷積層:輸入通道=1,輸出通道=2,卷積核=3x3,步長=1,填充=0
conv_layer = nn.Conv2d(in_channels=1, out_channels=2, kernel_size=3)
output = conv_layer(x) # 輸出形狀:(1, 2, 3, 3)(自動計算:(5-3+1)=3)
print("卷積輸出形狀:", output.shape)
示例:最大池化層
python
運行
# 池化層:核大小=2x2,步長=2
pool_layer = nn.MaxPool2d(kernel_size=2, stride=2)
pooled = pool_layer(output) # 輸出形狀:(1, 2, 1, 1)(3//2=1)
print("池化輸出形狀:", pooled.shape)
@浙大疏錦行