Python深度學習基礎——深度神經網絡(DNN)(PyTorch)

張量

數組與張量

PyTorch 作為當前首屈一指的深度學習庫,其將 NumPy 數組的語法盡數吸收,作為自己處理張量的基本語法,且運算速度從使用 CPU 的數組進步到使用 GPU 的張量。
NumPy 和 PyTorch 的基礎語法幾乎一致,具體表現為:

  • np 對應 torch;
  • 數組 array 對應張量 tensor;
  • NumPy 的 n 維數組對應著 PyTorch 的 n 階張量。

數組與張量之間可以互相轉換

  • 數組 arr 轉為張量 ts:ts = torch.tensor(arr);
  • 張量 ts 轉為數組 arr:arr = np.array(ts)。

從數組到張量

PyTorch 只是少量修改了 NumPy 的函數或方法,現對其中不同的地方進行羅列。

NumPy 的函數PyTorch 的函數用法區別
數據類型.astype( ).type( )
隨機數組np.random.random( )torch.rand( )
隨機數組np.random.randint( )torch.randint( )不接納一維張量
隨機數組np.random.normal( )torch.normal( )不接納一維張量
隨機數組np.random.randn( )torch.randn( )
數組切片.copy( ).clone( )
數組拼接np.concatenate( )torch.cat( )
數組分裂np.split( )torch.split( )參數含義優化
矩陣乘積np.dot( )torch.matmul( )
矩陣乘積np.dot(v,v)torch.dot( )
矩陣乘積np.dot(m,v)torch.mv( )
矩陣乘積np.dot(m,m)torch.mm( )
數學函數np.exp( )torch.exp( )必須傳入張量
數學函數np.log( )torch.log( )必須傳入張量
聚合函數np.mean( )torch.mean( )必須傳入浮點型張量
聚合函數np.std( )torch.std( )必須傳入浮點型張量

用GPU存儲張量

默認的張量使用 CPU 存儲,可將其搬至 GPU 上,如示例所示。

import torch
# 默認的張量存儲在 CPU 上
ts1 = torch.randn(3,4)
ts1                #OUT:tensor([[ 2.2716, 1.2107, -0.0582, 0.5885 ],#            [-0.5868, -0.6480, -0.2591, 0.1605],#            [-1.3968, 0.7999, 0.5180, 1.2214 ]])# 移動到 GPU 上
ts2 = ts1.to('cuda:0') # 第一塊 GPU 是 cuda:0
ts2                #OUT: tensor([[ 2.2716, 1.2107, -0.0582, 0.5885 ],#             [-0.5868, -0.6480, -0.2591, 0.1605],#             [-1.3968, 0.7999, 0.5180, 1.2214 ]], device='cuda:0')

以上操作可以把數據集搬到 GPU 上,但是神經網絡模型也要搬到 GPU 上才可正常運行,使用下面的代碼即可。

# 搭建神經網絡的類,此處略,詳見第三章
class DNN(torch.nn.Module):
#略
# 根據神經網絡的類創建一個網絡
model = DNN().to('cuda:0') # 把該網絡搬到 GPU 上

想要查看顯卡是否在運作時,在 cmd 中輸入:nvidia-smi,如下圖所示。
在這里插入圖片描述

DNN原理

神經網絡通過學習大量樣本的輸入與輸出特征之間的關系,以擬合出輸入與輸出之間的方程,學習完成后,只給它輸入特征,它便會可以給出輸出特征。神經網絡可以分為這么幾步:劃分數據集、訓練網絡、測試網絡、使用網絡。

劃分數據集

數據集里每個樣本必須包含輸入與輸出,將數據集按一定的比例劃分為訓練集與測試集,分別用于訓練網絡與測試網絡,如下表所示。

樣本輸入特征輸出特征
訓練集1In1 ln2 ln3Out1 Out2 Out3
訓練集2* * ** * *
訓練集3* * ** * *
訓練集4* * ** * *
訓練集5* * ** * *
訓練集* * ** * *
訓練集800* * ** * *
測試集801* * ** * *
測試集802* * ** * *
測試集* * ** * *
測試集1000* * ** * *

考慮到數據集的輸入特征與輸出特征都是 3 列,因此神經網絡的輸入層與輸出層也必須都是 3 個神經元,隱藏層可以自行設計,如下圖所示。
在這里插入圖片描述
考慮到 Python 列表、NumPy 數組以及 PyTorch 張量都是從索引[0]開始,再加之輸入層沒有內部參數(權重 ω 與偏置 b),所以習慣將輸入層稱之為第 0 層。

訓練網絡

  • 神經網絡的訓練過程,就是經過很多次前向傳播與反向傳播的輪回,最終不斷調整其內部參數(權重 ω 與偏置 b),以擬合任意復雜函數的過程。內部參數一開始是隨機的(如 Xavier 初始值、He 初始值),最終會不斷優化到最佳。
  • 還有一些訓練網絡前就要設好的外部參數:網絡的層數、每個隱藏層的節點數、每個節點的激活函數類型、學習率、輪回次數、每次輪回的樣本數等等。
  • 業界習慣把內部參數稱為參數,外部參數稱為超參數
  1. 前向傳播
    將單個樣本的 3 個輸入特征送入神經網絡的輸入層后,神經網絡會逐層計算到輸出層,最終得到神經網絡預測的 3 個輸出特征。計算過程中所使用的參數就是內部參數,所有的隱藏層與輸出層的神經元都有內部參數,以第 1 層的第 1 個神經元,如下圖所示。
    在這里插入圖片描述
    該神經元節點的計算過程為y = ω1x1 + ω2x2 + ω3x3 + b你可以理解為,每一根線就是一個權重 ω,每一個神經元節點也都有它自己的偏置 b。當然,每個神經元節點在計算完后,由于這個方程是線性的,因此必須在外面套一個非線性的函數:y = σ(ω1x1 + ω2x2 + ω3x3 + b),σ被稱為激活函數。如果你不套非線性函數,那么即使 10 層的網絡,也可以用 1 層就擬合出同樣的方程。

  2. 反向傳播

  • 經過前向傳播,網絡會根據當前的內部參數計算出輸出特征的預測值。但是這個預測值與真實值直接肯定有差距,因此需要一個損失函數來計算這個差距。例如,求預測值與真實值之間差的絕對值,就是一個典型的損失函數。
  • 損失函數計算好后,逐層退回求梯度,這個過程很復雜,原理不必掌握,大致意思就是,看每一個內部參數是變大還是變小,才會使得損失函數變小。這樣就達到了優化內部參數的目的
  • 在這個過程中,有一個外部參數叫學習率。學習率越大,內部參數的優化越快,但過大的學習率可能會使損失函數越過最低點,并在谷底反復橫跳。因此,在網絡的訓練開始之前,選擇一個合適的學習率很重要。
  1. batch_size
    前向傳播與反向傳播一次時,有三種情況:
  • 批量梯度下降(Batch Gradient Descent,BGD),把所有樣本一次性輸入進網絡,這種方式計算量開銷很大,速度也很慢。
  • 隨機梯度下降(Stochastic Gradient Descent,SGD),每次只把一個樣本輸入進網絡,每計算一個樣本就更新參數。這種方式雖然速度比較快,但是收斂性能差,可能會在最優點附近震蕩,兩次參數的更新也有可能抵消。
  • 小批量梯度下降(Mini-Batch Gradient Decent,MBGD)是為了中和上面二者而生,這種辦法把樣本劃分為若干個批,按批來更新參數。

所以,batch_size 即一批中的樣本數,也是一次喂進網絡的樣本數。此外,由于 Batch Normalization 層(用于將每次產生的小批量樣本進行標準化)的存在,batch_size 一般設置為 2 的冪次方,并且不能為 1。
PS:PyTorch 實現時只支持批量與小批量,不支持單個樣本的輸入方式。PyTorch 里的 torch.optim.SGD 只表示梯度下降,批量與小批量見第四、五章

  1. epochs
    1 個 epoch 就是指全部樣本進行 1 次前向傳播與反向傳播。
    假設有 10240 個訓練樣本,batch_size 是 1024,epochs 是 5。那么:
  • 全部樣本將進行 5 次前向傳播與反向傳播;
  • 1 個 epoch,將發生 10 次(10240/1024)前向傳播與反向傳播;
  • 一共發生 50 次(10*5)前向傳播和反向傳播。

測試網絡

為了防止訓練的網絡過擬合,因此需要拿出少量的樣本進行測試。過擬合的意思是:網絡優化好的內部參數只能對訓練樣本有效,換成其它就寄。以線性回歸為例,過擬合下圖b所示
在這里插入圖片描述
當網絡訓練好后,拿出測試集的輸入,進行 1 次前向傳播后,將預測的輸出與測試集的真實輸出進行對比,查看準確率。(測試集就不需要反向傳播了,反向傳播只是為了優化參數)

使用網絡

真正使用網絡進行預測時,樣本只知輸入,不知輸出。直接將樣本的輸入進行 1 次前向傳播,即可得到預測的輸出。

DNN實現

torch.nn 提供了搭建網絡所需的所有組件,nn 即 Neural Network 神經網絡。因此,可以單獨給 torch.nn 一個別名,即 import torch.nn as nn。

import torch
import torch.nn as nn
import matplotlib.pyplot as plt
%matplotlib inline
# 展示高清圖
from matplotlib_inline import backend_inline
backend_inline.set_matplotlib_formats('svg')

制作數據集

在訓練之前,要準備好訓練集的樣本。這里生成 10000 個樣本,設定 3 個輸入特征與 3 個輸出特征,其中:

  • 每個輸入特征相互獨立,均服從均勻分布;
  • 當(X1+X2+X3)< 1 時,Y1 為 1,否則 Y1 為 0;
  • 當 1<(X1+X2+X3)<2 時,Y2 為 1,否則 Y2 為 0;
  • 當(X1+X2+X3)>2 時,Y3 為 1,否則 Y3 為 0;
  • .float()將布爾型張量轉化為浮點型張量。
# 生成數據集
X1 = torch.rand(10000,1) # 輸入特征 1
X2 = torch.rand(10000,1) # 輸入特征 2
X3 = torch.rand(10000,1) # 輸入特征 3
Y1 = ( (X1+X2+X3)<1 ).float() # 輸出特征 1
Y2 = ( (1<(X1+X2+X3)) & ((X1+X2+X3)<2) ).float() # 輸出特征 2
Y3 = ( (X1+X2+X3)>2 ).float() # 輸出特征 3
Data = torch.cat([X1,X2,X3,Y1,Y2,Y3],axis=1) # 整合數據集
Data = Data.to('cuda:0') # 把數據集搬到 GPU 上
Data.shape       #OUT:torch.Size([10000, 6])

事實上,數據的 3 個輸出特征組合起來是一個 One-Hot 編碼(獨熱編碼)。

# 劃分訓練集與測試集
train_size = int(len(Data) * 0.7) # 訓練集的樣本數量
test_size = len(Data) - train_size # 測試集的樣本數量
Data = Data[torch.randperm( Data.size(0)) , : ] # 打亂樣本的順序  防止有些數據具有先后順序
train_Data = Data[ : train_size , : ] # 訓練集樣本
test_Data = Data[ train_size : , : ] # 測試集樣本
train_Data.shape, test_Data.shape      #OUT:(torch.Size([7000, 6]), torch.Size([3000, 6]))

以上的代碼屬于通用型代碼,便于我們手動分割訓練集與測試集。

搭建神經網絡

  • 搭建神經網絡時,以 nn.Module 作為父類,我們自己的神經網絡可直接繼承父類的方法與屬性,nn.Module 中包含網絡各個層的定義。
  • 在定義的神經網絡子類中,通常包含__init__特殊方法與 forward 方法。__init__特殊方法用于構造自己的神經網絡結構,forward 方法用于將輸入數據進行前向傳播。由于張量可以自動計算梯度,所以不需要出現反向傳播方法。
class DNN(nn.Module):def __init__(self):''' 搭建神經網絡各層 '''super(DNN,self).__init__()self.net = nn.Sequential( # 按順序搭建各層nn.Linear(3, 5), nn.ReLU(), # 第 1 層:全連接層nn.Linear(5, 5), nn.ReLU(), # 第 2 層:全連接層nn.Linear(5, 5), nn.ReLU(), # 第 3 層:全連接層nn.Linear(5, 3) # 第 4 層:全連接層)def forward(self, x):''' 前向傳播 '''y = self.net(x) # x 即輸入數據return y # y 即輸出數據
model = DNN().to('cuda:0') # 創建子類的實例,并搬到 GPU 上
model # 查看該實例的各層     #OUT:DNN(#    (net): Sequential(#      (0): Linear(in_features=3, out_features=5, bias=True)#      (1): ReLU()#      (2): Linear(in_features=5, out_features=5, bias=True)#      (3): ReLU()#      (4): Linear(in_features=5, out_features=5, bias=True)#      (5): ReLU()#      (6): Linear(in_features=5, out_features=3, bias=True)#      )#    )

在上面的 nn.Sequential()函數中,每一個隱藏層后都使用了 RuLU 激活函數,各層的神經元節點個數分別是:3、5、5、5、3。
PS:輸入層有 3 個神經元、輸出層有 3 個神經元,這不是巧合,是有意而為之。輸入層的神經元數量必須與每個樣本的輸入特征數量一致,輸出層的神經元數量必須與每個樣本的輸出特征數量一致。

網絡的內部參數

神經網絡的內部參數是權重與偏置,內部參數在神經網絡訓練之前會被賦予隨機數,隨著訓練的進行,內部參數會逐漸迭代至最佳值,現對參數進行查看。

# 查看內部參數(非必要)
for name, param in model.named_parameters():print(f"參數:{name}\n 形狀:{param.shape}\n 數值:{param}\n")

在這里插入圖片描述
代碼一共給了我們 8 個參數,其中參數與形狀的結果如下表所示,考慮到其數值初始狀態時是隨機的(如 Xavier 初始值、He 初始值),此處不討論。
在這里插入圖片描述
可見,具有權重與偏置的地方只有 net.0、net.2、net.4、net.6,易知這幾個地方其實就是所有的隱藏層與輸出層,這符合理論。

  • 首先,net.0.weight 的權重形狀為[5, 3],5 表示它自己的節點數是 5,3 表示與之連接的前一層的節點數為 3
  • 其次,由于前面里進行了 model =DNN().to(‘cuda:0’)操作,因此所有的內部參數都自帶device=‘cuda:0’。
  • 最后,注意到 requires_grad=True,說明所有需要進行反向傳播的內部參數(即權重與偏置)都打開了張量自帶的梯度計算功能。

網絡的外部參數

外部參數即超參數,這是調參師們關注的重點。搭建網絡時的超參數有:網絡的層數、各隱藏層節點數、各節點激活函數、內部參數的初始值等。訓練網絡的超參數有:如損失函數、學習率、優化算法、batch_size、epochs 等。

  1. 激活函數
    PyTorch 1.12.0 版本進入 https://pytorch.org/docs/1.12/nn.html 搜索 Non-linear Activations,即可查看 torch 內置的所有非線性激活函數(以及各種類型的層)。(網站打開默認為1.12版本,如果你的torch不是1.12,請在網頁左上角自行更改)
  2. 損失函數
    進入 https://pytorch.org/docs/1.12/nn.html 搜索 Loss Functions,即可查看 torch
    內置的所有損失函數。
# 損失函數的選擇
loss_fn = nn.MSELoss()
  1. 學習率與優化算法
    進入 https://pytorch.org/docs/1.12/optim.html,可查看 torch 的所有優化算法(網站打開默認為1.12版本,如果你的torch不是1.12,請在網頁左上角自行更改)
# 優化算法的選擇
learning_rate = 0.01 # 設置學習率
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

注:PyTorch 實現時只支持 BGD 或 MBGD,不支持單個樣本的輸入方式。這里的 torch.optim.SGD 只表示梯度下降,具體的批量與小批量見第四、五章。

訓練網絡

# 訓練網絡
epochs = 1000   # 所有樣本輪回1000次
losses = [] # 記錄損失函數變化的列表
# 給訓練集劃分輸入與輸出
X = train_Data[ : , :3 ] # 前 3 列為輸入特征
Y = train_Data[ : , -3: ] # 后 3 列為輸出特征
for epoch in range(epochs):Pred = model(X) # 一次前向傳播(批量)loss = loss_fn(Pred, Y) # 計算損失函數losses.append(loss.item()) # 記錄損失函數的變化optimizer.zero_grad() # 清理上一輪滯留的梯度loss.backward() # 一次反向傳播optimizer.step() # 優化內部參數Fig = plt.figure()
plt.plot(range(epochs), losses)
plt.ylabel('loss'), plt.xlabel('epoch')
plt.show()

在這里插入圖片描述
PS:losses.append(loss.item())中,.append()是指在列表 losses 后再附加 1 個元素,而.item()方法可將 PyTorch 張量退化為普通元素。

測試網絡

測試時,只需讓測試集進行 1 次前向傳播即可,這個過程不需要計算梯度,因此可以在該局部關閉梯度,該操作使用 with torch.no_grad():命令。
考慮到輸出特征是獨熱編碼,而預測的數據一般都是接近 0 或 1 的小數,為了能讓預測數據與真實數據之間進行比較,因此要對預測數據進行規整。例如,使用 Pred[:,torch.argmax(Pred, axis=1)] = 1 命令將每行最大的數置 1,接著再使用Pred[Pred!=1] = 0 將不是 1 的數字置 0,這就使預測數據與真實數據的格式相同。

# 測試網絡
# 給測試集劃分輸入與輸出
X = test_Data[:, :3] # 前 3 列為輸入特征
Y = test_Data[:, -3:] # 后 3 列為輸出特征
with torch.no_grad(): # 該局部關閉梯度計算功能Pred = model(X) # 一次前向傳播(批量)Pred[:,torch.argmax(Pred, axis=1)] = 1Pred[Pred!=1] = 0correct = torch.sum( (Pred == Y).all(1) ) # 預測正確的樣本total = Y.size(0) # 全部的樣本數量print(f'測試集精準度: {100*correct/total} %')

在計算 correct 時需要動點腦筋。
首先,(Pred == Y)計算預測的輸出與真實的輸出的各個元素是否相等,返回一個 3000 行、3 列的布爾型張量;

其次,(Pred == Y).all(1)檢驗該布爾型張量每一行的 3 個數據是否都是 True,對于全是 True 的樣本行,結果就是 True,否則是 False。all(1)中的 1 表示按“行”掃描,最終返回一個形狀為 3000 的一階張量。

最后,torch.sum( (Pred == Y).all(1) )的意思就是看這 3000 個向量相加,True會被當作 1,False 會被當作 0,這樣相加剛好就是預測正確的樣本數。

保存與導入網絡

現在我們要考慮一件大事,那就是有時候訓練一個大網絡需要幾天,那么必須要把整個網絡連同里面的優化好的內部參數給保存下來。
現以本章前面的代碼為例,當網絡訓練好后,將網絡以文件的形式保存下來,并通過文件導入給另一個新網絡,讓新網絡去跑測試集,看看測試集的準確率是否也是 67%。

  1. 保存網絡
    通過“torch.save(模型名, ‘文件名.pth’)”命令,可將該模型完整的保存至Jupyter 的工作路徑下
 # 保存網絡
torch.save(model, 'model.pth')
  1. 導入網絡
    通過“新網絡 = torch.load('文件名.pth ')”命令,可將該模型完整的導入給新網絡。
# 把模型賦給新網絡
new_model = torch.load('model.pth')

現在,new_model 就與 model 完全一致,可以直接去跑測試集。

  1. 用新模型進行測試
# 測試網絡
# 給測試集劃分輸入與輸出
X = test_Data[:, :3] # 前 3 列為輸入特征
Y = test_Data[:, -3:] # 后 3 列為輸出特征
with torch.no_grad(): # 該局部關閉梯度計算功能Pred = new_model(X) # 用新模型進行一次前向傳播Pred[:,torch.argmax(Pred, axis=1)] = 1Pred[Pred!=1] = 0correct = torch.sum( (Pred == Y).all(1) ) # 預測正確的樣本total = Y.size(0) # 全部的樣本數量print(f'測試集精準度: {100*correct/total} %')        #OUT:測試集精準度: 67.16666412353516 %

批量梯度下降

本小節將完整、快速地再展示一遍批量梯度下降(BGD)的全過程。

import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
%matplotlib inline# 展示高清圖
from matplotlib_inline import backend_inline
backend_inline.set_matplotlib_formats('svg')

制作數據集

這一次的數據集將從 Excel 中導入,需要 Pandas 庫中的 pd.read_csv()函數,這在前一篇文章《Python基礎——Pandas庫》第六章中有詳細的介紹

# 準備數據集
df = pd.read_csv('Data.csv', index_col=0) # 導入數據
arr = df.values # Pandas 對象退化為 NumPy 數組
arr = arr.astype(np.float32) # 轉為 float32 類型數組
ts = torch.tensor(arr) # 數組轉為張量
ts = ts.to('cuda') # 把訓練集搬到 cuda 上
ts.shape        #OUT:torch.Size([759, 9])

PS:將 arr 數組轉為了 np.float32 類型這一步必不可少,沒有的話計算過程會出現一些數據類型不兼容的情況。

# 劃分訓練集與測試集
train_size = int(len(ts) * 0.7) # 訓練集的樣本數量
test_size = len(ts) - train_size # 測試集的樣本數量
ts = ts[ torch.randperm( ts.size(0) ) , : ] # 打亂樣本的順序
train_Data = ts[ : train_size , : ] # 訓練集樣本
test_Data = ts[ train_size : , : ] # 測試集樣本
train_Data.shape, test_Data.shape        #OUT:(torch.Size([531, 9]), torch.Size([228, 9]))

搭建神經網絡

PS:前面的數據集,輸入有 8 個特征,輸出有 1 個特征,那么神經網絡的輸入層必須有 8 個神經元,輸出層必須有 1 個神經元。
隱藏層的層數、各隱藏層的節點數屬于外部參數(超參數),可以自行設置。

class DNN(nn.Module):def __init__(self):''' 搭建神經網絡各層 '''super(DNN,self).__init__()self.net = nn.Sequential( # 按順序搭建各層nn.Linear(8, 32), nn.Sigmoid(), # 第 1 層:全連接層nn.Linear(32, 8), nn.Sigmoid(), # 第 2 層:全連接層nn.Linear(8, 4), nn.Sigmoid(), # 第 3 層:全連接層nn.Linear(4, 1), nn.Sigmoid() # 第 4 層:全連接層)def forward(self, x):''' 前向傳播 '''y = self.net(x) # x 即輸入數據return y # y 即輸出數據
model = DNN().to('cuda:0') # 創建子類的實例,并搬到 GPU 上

訓練網絡

# 損失函數的選擇
loss_fn = nn.BCELoss(reduction='mean')
# 優化算法的選擇
learning_rate = 0.005 # 設置學習率
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
# 訓練網絡
epochs = 5000
losses = [] # 記錄損失函數變化的列表
# 給訓練集劃分輸入與輸出
X = train_Data[ : , : -1 ] # 前 8 列為輸入特征
Y = train_Data[ : , -1 ].reshape((-1,1)) # 后 1 列為輸出特征
# 此處的.reshape((-1,1))將一階張量升級為二階張量
for epoch in range(epochs):Pred = model(X) # 一次前向傳播(批量)loss = loss_fn(Pred, Y) # 計算損失函數losses.append(loss.item()) # 記錄損失函數的變化optimizer.zero_grad() # 清理上一輪滯留的梯度loss.backward() # 一次反向傳播optimizer.step() # 優化內部參數Fig = plt.figure()
plt.plot(range(epochs), losses)
plt.ylabel('loss')
plt.xlabel('epoch')
plt.show()

在這里插入圖片描述

測試網絡

注意,真實的輸出特征都是 0 或 1,因此這里需要對網絡預測的輸出 Pred 進行處理,Pred 大于 0.5 的部分全部置 1,小于 0.5 的部分全部置 0.

# 測試網絡
# 給測試集劃分輸入與輸出
X = test_Data[ : , : -1 ] # 前 8 列為輸入特征
Y = test_Data[ : , -1 ].reshape((-1,1)) # 后 1 列為輸出特征
with torch.no_grad(): # 該局部關閉梯度計算功能Pred = model(X) # 一次前向傳播(批量)Pred[Pred>=0.5] = 1Pred[Pred<0.5] = 0correct = torch.sum( (Pred == Y).all(1) ) # 預測正確的樣本total = Y.size(0) # 全部的樣本數量print(f'測試集精準度: {100*correct/total} %')    #OUT:測試集精準度: 71.0526351928711 %

小批量梯度下降

本章將繼續使用第四章中的 Excel 與神經網絡結構,但使用小批量訓練。在使用小批量梯度下降時,必須使用 3 個 PyTorch 內置的實用工具(utils):

  • DataSet 用于封裝數據集
  • DataLoader 用于加載數據不同的批次
  • random_split 用于劃分訓練集與測試集
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
from torch.utils.data import random_split
import matplotlib.pyplot as plt
%matplotlib inline
# 展示高清圖
from matplotlib_inline import backend_inline
backend_inline.set_matplotlib_formats('svg')

制作數據集

在封裝我們的數據集時,必須繼承實用工具(utils)中的 DataSet 的類,這個過程需要重寫__init__、getitem、__len__三個方法,分別是為了加載數據集、獲取數據索引、獲取數據總量。

# 制作數據集
class MyData(Dataset): # 繼承 Dataset 類def __init__(self, filepath):df = pd.read_csv(filepath, index_col=0) # 導入數據arr = df.values # 對象退化為數組arr = arr.astype(np.float32) # 轉為 float32 類型數組ts = torch.tensor(arr) # 數組轉為張量ts = ts.to('cuda') # 把訓練集搬到 cuda 上self.X = ts[ : , : -1 ] # 前 8 列為輸入特征self.Y = ts[ : , -1 ].reshape((-1,1)) # 后 1 列為輸出特征self.len = ts.shape[0] # 樣本的總數def __getitem__(self, index):return self.X[index], self.Y[index]def __len__(self):return self.len

小批次訓練時,輸入特征與輸出特征的劃分必須寫在上述代碼的子類里面。

# 劃分訓練集與測試集
Data = MyData('Data.csv')
train_size = int(len(Data) * 0.7) # 訓練集的樣本數量
test_size = len(Data) - train_size # 測試集的樣本數量
train_Data, test_Data = random_split(Data, [train_size, test_size])

我們利用實用工具(utils)里的 random_split可輕松實現了訓練集與測試集數據的劃分

# 批次加載器
train_loader = DataLoader(dataset=train_Data, shuffle=True, batch_size=128)
test_loader = DataLoader(dataset=test_Data, shuffle=False, batch_size=64)

實用工具(utils)里的 DataLoader 可以在接下來的訓練中進行小批次的載入數據,shuffle 用于在每一個 epoch 內先洗牌再分批。

搭建神經網絡

class DNN(nn.Module):def __init__(self):''' 搭建神經網絡各層 '''super(DNN,self).__init__()self.net = nn.Sequential( # 按順序搭建各層nn.Linear(8, 32), nn.Sigmoid(), # 第 1 層:全連接層nn.Linear(32, 8), nn.Sigmoid(), # 第 2 層:全連接層nn.Linear(8, 4), nn.Sigmoid(), # 第 3 層:全連接層nn.Linear(4, 1), nn.Sigmoid() # 第 4 層:全連接層)def forward(self, x):''' 前向傳播 '''y = self.net(x) # x 即輸入數據return y # y 即輸出數據
 model = DNN().to('cuda:0') # 創建子類的實例,并搬到 GPU 上

訓練網絡

# 損失函數的選擇
loss_fn = nn.BCELoss(reduction='mean')
# 優化算法的選擇
learning_rate = 0.005 # 設置學習率
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
# 訓練網絡
epochs = 500
losses = []                        # 記錄損失函數變化的列表
for epoch in range(epochs):for (x, y) in train_loader:    # 獲取小批次的 x 與 yPred = model(x)            # 一次前向傳播(小批量)loss = loss_fn(Pred, y)    # 計算損失函數losses.append(loss.item()) # 記錄損失函數的變化optimizer.zero_grad()      # 清理上一輪滯留的梯度loss.backward()            # 一次反向傳播optimizer.step()           # 優化內部參數
Fig = plt.figure()
plt.plot(range(len(losses)), losses)
plt.show()

在這里插入圖片描述

測試網絡

# 測試網絡
correct = 0
total = 0
with torch.no_grad(): # 該局部關閉梯度計算功能for (x, y) in test_loader: # 獲取小批次的 x 與 yPred = model(x) # 一次前向傳播(小批量)Pred[Pred>=0.5] = 1Pred[Pred<0.5] = 0correct += torch.sum( (Pred == y).all(1) )total += y.size(0)
print(f'測試集精準度: {100*correct/total} %')   #OUT:測試集精準度: 73.68421173095703 %

手寫數字識別

手寫數字識別數據集(MNIST)是機器學習領域的標準數據集,它被稱為機器學習領域的“Hello World”,只因任何 AI 算法都可以用此標準數據集進行檢驗。MNIST 內的每一個樣本都是一副二維的灰度圖像,如下圖所示。
在這里插入圖片描述

  • 在 MNIST 中,模型的輸入是一副圖像,模型的輸出就是一個與圖像中對應的數字(0 至 9 之間的一個整數,不是獨熱編碼)。
  • 我們不用手動將輸出轉換為獨熱編碼,PyTorch 會在整個過程中自動將數據集的輸出轉換為獨熱編碼.只有在最后測試網絡時,我們對比測試集的預測輸出與真實輸出時,才需要注意一下。
  • 某一個具體的樣本如下圖所示,每個圖像都是形狀為28*28的二維數組。
    在這里插入圖片描述
    在這種多分類問題中,神經網絡的輸出層需要一個 softmax 激活函數,它可以把輸出層的數據歸一化到 0-1 上,且加起來為 1,這樣就模擬出了概率的意思。

制作數據集

這一章我們需要在 torchvision 庫中分別下載訓練集與測試集,因此需要從torchvision 庫中導入 datasets 以下載數據集,下載前需要借助 torchvision 庫中的 transforms 進行圖像轉換,將數據集變為張量,并調整數據集的統計分布。
由于不需要手動構建數據集,因此不導入 utils 中的 Dataset;又由于訓練集與測試集是分開下載的,因此不導入 utils 中的 random_split。

import torch
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import transforms
from torchvision import datasets
import matplotlib.pyplot as plt
%matplotlib inline
# 展示高清圖
from matplotlib_inline import backend_inline
backend_inline.set_matplotlib_formats('svg')

在下載數據集之前,要設定轉換參數:transform,該參數里解決兩個問題:

  • ToTensor:將圖像數據轉為張量,且調整三個維度的順序為 CWH;C表示通道數,二維灰度圖像的通道數為 1,三維 RGB 彩圖的通道數為 3。
  • Normalize:將神經網絡的輸入數據轉化為標準正態分布,訓練更好;根據統計計算,MNIST 訓練集所有像素的均值是 0.1307、標準差是 0.3081。
# 制作數據集
# 數據集轉換參數
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize(0.1307, 0.3081)
])
# 下載訓練集與測試集
train_Data = datasets.MNIST(root = 'D:/Jupyter/dataset/mnist/', # 下載路徑train = True, # 是 train 集download = True, # 如果該路徑沒有該數據集,就下載transform = transform # 數據集轉換參數
)
test_Data = datasets.MNIST(root = 'D:/Jupyter/dataset/mnist/', # 下載路徑train = False, # 是 test 集download = True, # 如果該路徑沒有該數據集,就下載transform = transform # 數據集轉換參數
)

下載輸出下圖所示:
在這里插入圖片描述

# 批次加載器
train_loader = DataLoader(train_Data, shuffle=True, batch_size=64)
test_loader = DataLoader(test_Data, shuffle=False, batch_size=64)

搭建神經網絡

每個樣本的輸入都是形狀為2828的二維數組,那么對于 DNN 來說,輸入層的神經元節點就要有2828=784個;輸出層使用獨熱編碼,需要 10 個節點。

class DNN(nn.Module):def __init__(self):''' 搭建神經網絡各層 '''super(DNN,self).__init__()self.net = nn.Sequential( # 按順序搭建各層nn.Flatten(), # 把圖像鋪平成一維nn.Linear(784, 512), nn.ReLU(), # 第 1 層:全連接層nn.Linear(512, 256), nn.ReLU(), # 第 2 層:全連接層nn.Linear(256, 128), nn.ReLU(), # 第 3 層:全連接層nn.Linear(128, 64), nn.ReLU(), # 第 4 層:全連接層nn.Linear(64, 10) # 第 5 層:全連接層)def forward(self, x):''' 前向傳播 '''y = self.net(x) # x 即輸入數據return y # y 即輸出數據
model = DNN().to('cuda:0') # 創建子類的實例,并搬到 GPU 上

訓練網絡

# 損失函數的選擇
loss_fn = nn.CrossEntropyLoss() # 自帶 softmax 激活函數
 # 優化算法的選擇
learning_rate = 0.01 # 設置學習率
optimizer = torch.optim.SGD(model.parameters(),lr = learning_rate,momentum = 0.5
)

給優化器了一個新參數 momentum(動量),它使梯度下降算法有了力與慣性,該方法給人的感覺就像是小球在地面上滾動一樣。

# 訓練網絡
epochs = 5
losses = [] # 記錄損失函數變化的列表
for epoch in range(epochs):for (x, y) in train_loader: # 獲取小批次的 x 與 yx, y = x.to('cuda:0'), y.to('cuda:0')Pred = model(x) # 一次前向傳播(小批量)loss = loss_fn(Pred, y) # 計算損失函數losses.append(loss.item()) # 記錄損失函數的變化optimizer.zero_grad() # 清理上一輪滯留的梯度loss.backward() # 一次反向傳播optimizer.step() # 優化內部參數
Fig = plt.figure()
plt.plot(range(len(losses)), losses)
plt.show()

在這里插入圖片描述
PS:由于數據集內部進不去,只能在循環的過程中取出一部分樣本,就立即將之搬到 GPU 上。

測試網絡

# 測試網絡
correct = 0
total = 0
with torch.no_grad(): # 該局部關閉梯度計算功能for (x, y) in test_loader: # 獲取小批次的 x 與 yx, y = x.to('cuda:0'), y.to('cuda:0')Pred = model(x) # 一次前向傳播(小批量)_, predicted = torch.max(Pred.data, dim=1)correct += torch.sum( (predicted == y) )total += y.size(0) 
print(f'測試集精準度: {100*correct/total} %')   #OUT:測試集精準度: 96.65999603271484 %

a, b = torch.max(Pred.data, dim=1)的意思是,找出 Pred 每一行里的最大值,數值賦給 a,所處位置賦給 b。因此上述代碼里的 predicted 就相當于把獨熱編碼轉換回了普通的阿拉伯數字,這樣一來可以直接與 y 進行比較。
由于此處 predicted 與 y 是一階張量,因此 correct 行的結尾不能加.all(1)。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/77345.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/77345.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/77345.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

光伏產品研發項目如何降本增效?8Manage 項目管理軟件在復合材料制造的應用

在復合材料制造領域&#xff0c;特別是光伏PECVD石墨舟和燃料電池石墨雙極板等高精尖產品的研發過程中&#xff0c;高效的項目管理直接決定了產品開發周期、質量和市場競爭力。然而&#xff0c;許多企業在項目立項、進度跟蹤、資源分配和質量控制等環節面臨挑戰。 針對這些痛點…

linux的glib庫使用

glib常用接口使用 1. glib介紹2. glib命令安裝3. 獲取glib的版本信息和兼容信息4. glib使用例子4.1 鏈表例子4.2 哈希表例子4.3 使用面向對象例子 1. glib介紹 廣泛應用于桌面環境、嵌入式系統、GNOME等項目中。它提供了完整的面向對象編程模型&#xff08;GObject&#xff09…

vs2022使用git方法

1、創建git 2、在cmd下執行 git push -f origin master &#xff0c;會把本地代碼全部推送到遠程&#xff0c;同時會覆蓋遠程代碼。 3、需要設置【Git全局設置】&#xff0c;修改的代碼才會顯示可以提交&#xff0c;否則是灰色的不能提交。 4、創建的分支&#xff0c;只要點擊…

SAP ECCS 標準報表 切換為EXCEL電子表格模式

在解決《SAP ECCS標準報表在報表中不存在特征CG細分期間 消息號 GK715報錯分析》問題過程中通過DEBUG方式參照測試環境補錄數據后&#xff0c;不再報GK715錯誤&#xff0c;此時用戶要的很急&#xff0c;要出季報。要求先把數據導出供其分析出季報。 采用導出列表方式&#xff…

基于 Python 和 OpenCV 技術的疲勞駕駛檢測系統(2.0 全新升級,附源碼)

大家好&#xff0c;我是徐師兄&#xff0c;一個有著7年大廠經驗的程序員&#xff0c;也是一名熱衷于分享干貨的技術愛好者。平時我在 CSDN、掘金、華為云、阿里云和 InfoQ 等平臺分享我的心得體會。 &#x1f345;文末獲取源碼聯系&#x1f345; 2025年最全的計算機軟件畢業設計…

MATLAB項目實戰(一)

題目&#xff1a; 某公司有6個建筑工地要開工&#xff0c;每個工地的位置&#xff08;用平面坐標系a&#xff0c;b表示&#xff0c;距離單位&#xff1a;km&#xff09;及水泥日用量d(t)由下表給出&#xff0e;目前有兩個臨時料場位于A(5,1)&#xff0c;B(2,7)&#xff0c;日儲…

[250417] Fedora 42 正式發布,搭載 Linux 6.14 內核和 GNOME 48 桌面環境

目錄 Fedora 42 正式發布全新的 Anaconda Web UI 安裝程序KDE Plasma 榮升官方版本 (Edition)全新 Fedora COSMIC Spin 登場GNOME 48 桌面環境更新其他重要改進獲取與升級 Fedora 42 正式發布 Fedora Linux 42 現已正式發布&#xff01;此版本基于最新的 Linux 內核 6.14 構建…

開源技術如何助力中小企業實現財務管理自主化?

中小企業的數字化困境與開源機遇 國際數據公司&#xff08;IDC&#xff09;研究顯示&#xff0c;全球67%的中小企業因高昂的軟件成本和僵化的功能設計&#xff0c;未能有效推進數字化轉型。傳統商業軟件常面臨三大矛盾&#xff1a; 功能冗余與核心需求缺失&#xff1a;標準化系…

JVM考古現場(二十二):降維打擊·用二向箔優化內存模型

"警報&#xff01;三維堆內存正在經歷二維化坍縮&#xff01;" 我腰間的玄鐵令突然震動&#xff0c;在蜀山劍派的量子劍陣中投射出詭異的曼德博分形——這是三體文明發動降維打擊的鐵證&#xff01; 楔子&#xff1a;二向箔奇點降臨 昆侖鏡監控日志&#xff1a; // …

詳細解釋MCP項目中安裝命令 bunx 和 npx區別

詳細解釋 bunx 和 npx 1. bunx bunx 是 Bun 的一個命令行工具&#xff0c;用于自動安裝和運行來自 npm 的包。它是 Bun 生態系統中類似于 npx 或 yarn dlx 的工具。以下是 bunx 的主要特點和使用方法&#xff1a; 自動安裝和運行&#xff1a; bunx 會自動從 npm 安裝所需的包…

Docker詳細使用

Docker詳細使用 文章目錄 Docker詳細使用使用場景docker安裝常用命令幫助啟動類命令鏡像命令網絡命令容器命令compose&#xff08;服務編排&#xff09; 功能列表存儲&#xff08;掛載本地&#xff09;介紹使用?錄掛載卷映射 網絡介紹使用 DockerfileCompose介紹使用 使用場景…

Ubuntu24.04搭建ESP8266_RTOS_SDK V3.4開發環境

【本文發布于https://blog.csdn.net/Stack_/article/details/147194686&#xff0c;未經允許不得轉載&#xff0c;轉載須注明出處】 需要有Linux使用基礎&#xff0c;自行準備 1、VM17 Pro &#xff08;自行搜索教程安裝&#xff09; 2、ubuntu-24.04-desktop-amd64 &#xff0…

微信小程序實現table樣式,自帶合并行合并列

微信小程序在代碼編寫過程好像不支持原生table的使用&#xff0c;在開發過程中偶爾又得需要拿table來展示。 1.table效果展示 1.wxml <view class"table-container"><view class"table"><view class"table-row"><view cla…

前端面試-React篇

核心概念與虛擬DOM React的虛擬DOM如何工作&#xff1f;Diff算法優化策略是什么&#xff1f;JSX的本質是什么&#xff1f;與模板引擎&#xff08;如Vue&#xff09;有何區別&#xff1f;React組件生命周期&#xff08;類組件&#xff09;的關鍵階段是什么&#xff1f;受控組件…

LFM調制信號分類與檢測識別

LFM調制信號分類與檢測識別 LFM調制信號分類識別AlexNet網絡識別InceptionV3、ResNet-18、ResNet-50網絡識別 LFM調制信號檢測識別 LFM調制信號分類識別 支持識別LFM信號、間歇采樣干擾(ISRJ)、靈巧噪聲干擾(SNJ)、掃頻干擾(SJ)、瞄準干擾(AJ)、阻塞干擾(BJ)、密集假目標干擾(…

Linux 常用命令總結

Linux 常用命令總結 Linux 命令行是系統管理和開發的核心工具&#xff0c;掌握常用命令可以極大提升效率。本文全面總結 Linux 常用命令&#xff0c;涵蓋文件操作、進程管理、網絡管理、系統監控、用戶管理、軟件安裝等多個方面&#xff0c;適合初學者和高級用戶參考。 1. 文件…

RPCRT4!OSF_CCONNECTION::OSF_CCONNECTION函數分析之創建一個RPCRT4!OSF_CCALL--RPC源代碼分析

RPCRT4!OSF_CCONNECTION::OSF_CCONNECTION函數分析之創建一個RPCRT4!OSF_CCALL 第一部分&#xff1a; 1: kd> p RPCRT4!OSF_CCONNECTION::OSF_CCONNECTION0x167: 001b:77bf6957 393dec35c877 cmp dword ptr [RPCRT4!gfRPCVerifierEnabled (77c835ec)],edi 1: kd> …

量化視角:比特幣美債黃金三角博弈的DeepSeek推演

【AI分析】近期全球金融市場呈現罕見的三重分化態勢&#xff1a;比特幣單日振幅超35%、美債收益率創年內最大單日波動、黃金價格突破3271美元/盎司&#xff0c;刷新有記錄以來的最高價。這種極端行情背后&#xff0c;折射出AI模型捕捉到的市場結構性矛盾與資金流動新邏輯。 一…

markdown導出PDF,PDF生成目錄

1、vscode中安裝markdown插件&#xff0c;將編輯的文件導出PDF。 2、安裝PDF Guru Anki軟件 百度網盤&#xff1a;通過網盤分享的文件&#xff1a;PDFGuruAnki 鏈接: https://pan.baidu.com/s/1nU6avM7NUowhEn1FNZQKkA 提取碼: aues PDF中不同的標題需要通過矩形框標注差異&a…

FastAPI與SQLAlchemy數據庫集成

title: FastAPI與SQLAlchemy數據庫集成 date: 2025/04/17 15:33:34 updated: 2025/04/17 15:33:34 author: cmdragon excerpt: FastAPI與SQLAlchemy的集成通過創建虛擬環境、安裝依賴、配置數據庫連接、定義數據模型和實現路由來完成。核心模塊包括數據庫引擎、會話工廠和聲…