Lesson 12.5 softmax回归建模实验
Lesson 12.5 softmax回歸建模實驗
接下來,繼續上一節內容,我們進行softmax回歸建模實驗。
- 導入相關的包
查看自定義模塊是否導入成功
tensorGenCla? #Signature: #tensorGenCla( # num_examples=500, # num_inputs=2, # num_class=3, # deg_dispersion=[4, 2], # bias=False, #) #Docstring: #分類數據集創建函數。 # #:param num_examples: 每個類別的數據數量 #:param num_inputs: 數據集特征數量 #:param num_class:數據集標簽類別總數 #:param deg_dispersion:數據分布離散程度參數,需要輸入一個列表,其中第一個參數表示每個類別數組均值的參考、第二個參數表示隨機數組標準差。 #:param bias:建立模型邏輯回歸模型時是否帶入截距 #:return: 生成的特征張量和標簽張量,其中特征張量是浮點型二維數組,標簽張量是長正型二維數組。 #File: f:\code file\pytorch實戰\torchlearning.py #Type: function一、softmax回歸手動實現
??根據此前的介紹,面對分類問題,更為通用的處理辦法將其轉化為啞變量的形式,然后使用softmax回歸進行處理,這種處理方式同樣適用于二分類和多分類問題。此處以多分類問題為例,介紹softmax的手動實現形式。
【補充】softmax的另一種理解角度
??我們都知道,softmax是用于挑選最大值的一種方法,通過以下公式對不同類的計算結果進行數值上的轉化
δk=ezk∑Kek\delta_k = \frac{e^{z_k}}{\sum^Ke^k}δk?=∑Kekezk??
這種轉化可以將結果放縮到0-1之間,并且使用softmax進行最大值的比較,相比max(softmax是max的柔化版本),能有效避免損失函數求解時在0點不可導的問題,損失函數的函數特性,將是后續我們選擇優化算法的關鍵。具體我們可以通過下述圖像進行比較。
我們以三分類數據集為例,手動構建softmax回歸。
1.生成數據集
??利用此前的數據集生成函數,創建一個三分類、且內部離散程度不是很高的分類數據集
# 設置隨機數種子 torch.manual_seed(420) features, labels = tensorGenCla(bias=True, deg_dispersion=[6, 2]) plt.scatter(features[:, 0], features[:, 1], c = labels) features #tensor([[-6.0141, -4.9911, 1.0000], # [-4.6593, -6.7657, 1.0000], # [-5.9395, -5.2347, 1.0000], # ..., # [ 6.4622, 4.1406, 1.0000], # [ 5.7278, 9.2208, 1.0000], # [ 4.9705, 3.1236, 1.0000]])2.建模流程
- Stage 1.模型選擇
圍繞建模目標,我們可以構建一個只包含一層的神經網絡進行建模。
根據此前課程的介紹,輸出層的每個神經元輸出結果都代表某條樣本在三個類別中softmax后的取值,此時神經網絡擁有兩層,且是全連接。此時從特征到輸出結果,就不再是簡單的線性方程變換,而是矩陣相乘之后進行softmax轉化。
此處X是特征張量,w是由兩層之間的連接權重所組成的矩陣,且w的行數就是輸入數據特征的數量,w的列數就是輸出層的神經元個數,或者說就是分類問題的類別總數。計算過程我們可以通過下述例子進行說明:
f = features[: 10] l = labels[: 10] f l #tensor([[-6.0141, -4.9911, 1.0000], # [-4.6593, -6.7657, 1.0000], # [-5.9395, -5.2347, 1.0000], # [-7.0262, -4.5792, 1.0000], # [-2.3817, -5.1295, 1.0000], # [-0.7093, -5.4693, 1.0000], # [-4.1530, -6.8751, 1.0000], # [-1.9636, -3.3003, 1.0000], # [-6.5046, -6.0710, 1.0000], # [-6.1291, -7.1835, 1.0000]]) #tensor([[0], # [0], # [0], # [0], # [0], # [0], # [0], # [0], # [0], # [0]]) w = torch.arange(9).reshape(3, 3).float() w #tensor([[0., 1., 2.], # [3., 4., 5.], # [6., 7., 8.]]) m1 = torch.mm(f, w) m1 #tensor([[ -8.9733, -18.9785, -28.9837], # [-14.2971, -24.7221, -35.1471], # [ -9.7042, -19.8785, -30.0527], # [ -7.7375, -18.3429, -28.9483], # [ -9.3886, -15.8998, -22.4111], # [-10.4079, -15.5865, -20.7651], # [-14.6253, -24.6535, -34.6816], # [ -3.9010, -8.1649, -12.4289], # [-12.2130, -23.7886, -35.3642], # [-15.5506, -27.8632, -40.1758]])此時,上述矩陣的每一行都代表每一條數據在三個類別上的線性方程計算結果,然后需要進行softmax轉化
torch.sum(w, 1) #tensor([ 3., 12., 21.]) torch.exp(m1) #tensor([[1.2675e-04, 5.7245e-09, 2.5854e-13], # [6.1777e-07, 1.8336e-11, 5.4426e-16], # [6.1026e-05, 2.3275e-09, 8.8770e-14], # [4.3617e-04, 1.0809e-08, 2.6787e-13], # [8.3669e-05, 1.2439e-07, 1.8493e-10], # [3.0193e-05, 1.7016e-07, 9.5900e-10], # [4.4494e-07, 1.9640e-11, 8.6693e-16], # [2.0222e-02, 2.8446e-04, 4.0014e-06], # [4.9654e-06, 4.6637e-11, 4.3803e-16], # [1.7639e-07, 7.9282e-13, 3.5635e-18]]) torch.sum(torch.exp(m1), 1) # 計算每一行的exp之后求和 #tensor([1.2675e-04, 6.1779e-07, 6.1028e-05, 4.3619e-04, 8.3794e-05, 3.0364e-05, 4.4495e-07, 2.0511e-02, 4.9655e-06, 1.7639e-07]) torch.exp(m1) / torch.sum(torch.exp(m1), 1).reshape(-1, 1) #tensor([[9.9995e-01, 4.5163e-05, 2.0398e-09], # [9.9997e-01, 2.9681e-05, 8.8098e-10], # [9.9996e-01, 3.8138e-05, 1.4546e-09], # [9.9998e-01, 2.4781e-05, 6.1412e-10], # [9.9851e-01, 1.4845e-03, 2.2070e-06], # [9.9436e-01, 5.6040e-03, 3.1583e-05], # [9.9996e-01, 4.4139e-05, 1.9484e-09], # [9.8594e-01, 1.3869e-02, 1.9509e-04], # [9.9999e-01, 9.3923e-06, 8.8216e-11], # [1.0000e+00, 4.4946e-06, 2.0202e-11]])上述結果的每一行就是經過sofrmax轉化之后每一條數據在三個不同類別上的取值。該函數和nn.functional中softmax函數功能一致。只不過需要注意的是,我們定義的softmax函數需要輸入原始數據和系數矩陣,而F.softmax需要輸入輸出節點中經過線性運算的結果以及softmax的方向(按行還是按列)。
softmax(f, w) #tensor([[9.9995e-01, 4.5163e-05, 2.0398e-09], # [9.9997e-01, 2.9681e-05, 8.8098e-10], # [9.9996e-01, 3.8138e-05, 1.4546e-09], # [9.9998e-01, 2.4781e-05, 6.1412e-10], # [9.9851e-01, 1.4845e-03, 2.2070e-06], # [9.9436e-01, 5.6040e-03, 3.1583e-05], # [9.9996e-01, 4.4139e-05, 1.9484e-09], # [9.8594e-01, 1.3869e-02, 1.9509e-04], # [9.9999e-01, 9.3923e-06, 8.8216e-11], # [1.0000e+00, 4.4946e-06, 2.0202e-11]]) F.softmax(m1, 1) #tensor([[9.9995e-01, 4.5163e-05, 2.0398e-09], # [9.9997e-01, 2.9681e-05, 8.8098e-10], # [9.9996e-01, 3.8138e-05, 1.4546e-09], # [9.9998e-01, 2.4781e-05, 6.1412e-10], # [9.9851e-01, 1.4845e-03, 2.2069e-06], # [9.9436e-01, 5.6040e-03, 3.1583e-05], # [9.9996e-01, 4.4139e-05, 1.9484e-09], # [9.8594e-01, 1.3869e-02, 1.9509e-04], # [9.9999e-01, 9.3923e-06, 8.8216e-11], # [1.0000e+00, 4.4946e-06, 2.0202e-11]])- Stage 2.確定目標函數
??此時目標函數就是交叉熵損失函數。由于標簽已經經過了啞變量轉化,因此交叉熵的主體就是每條數據的真實類別對應概率的累乘結果。作為多分類問題的最通用的損失函數,我們有必要簡單回顧交叉熵計算過程:
f = torch.tensor([[0.6, 0.2, 0.2], [0.3, 0.4, 0.3]]) l = torch.tensor([0, 1])f l #tensor([[0.6000, 0.2000, 0.2000], # [0.3000, 0.4000, 0.3000]]) #tensor([0, 1])其中f代表兩條數據在三個類別上通過softmax輸出的比例結果,l代表這兩條數據的真實標簽,我們可以將這兩條數據在不同類別上的概率取值看成是隨機變量,而這兩個隨機變量在真實類別上的聯合概率分布的具體取值則是0.6*0.4,進一步,交叉熵損失函數 = -log(所有數據的在真實類別上的聯合概率分布) / 數據總量。據此我們可定義交叉熵損失函數如下:
def m_cross_entropy(soft_z, y):y = y.long()prob_real = torch.gather(soft_z, 1, y)return (-(1/y.numel()) * torch.log(prob_real).sum())注意,根據對數運算性質,有log(x1x2)=log(x1)+log(x2)log(x_1x_2)=log(x_1)+log(x_2)log(x1?x2?)=log(x1?)+log(x2?),因此我們可以將交叉熵損失函數中聯合概率分布的累乘轉化為累加,如果是累乘可以使用以下函數進行計算。但此處更推薦使用累加而不是累乘進行計算,大家想想是什么原因?
#def m_cross_entropy(soft_z, y): # y = y.long() # prob_real = torch.gather(soft_z, 1, y) # return (-(1/y.numel()) * torch.log(torch.prod(prob_real)))gather函數基本使用方法
l f #tensor([0, 1]) #tensor([[0.6000, 0.2000, 0.2000], # [0.3000, 0.4000, 0.3000]]) torch.gather(f, 1, l.reshape(-1, 1).long()) # 相當于批量索引 #tensor([[0.6000], # [0.4000]])再在外側乘以-1/N即可構成啞變量情況下分類問題的交叉熵損失函數的計算結果。
-1 / 2 * (torch.log(torch.tensor(0.6) * torch.tensor(0.4))) #tensor(0.7136) -1 / 2 * (torch.log(torch.tensor(0.6))+torch.log(torch.tensor(0.4))) #tensor(0.7136)當然也可以直接使用上述定義的m_cross_entropy函數進行計算
m_cross_entropy(f, l.reshape(-1, 1).long()) #tensor(0.7136)當然,我們也可以使用nn.CrossEntropyLoss()完成交叉熵損失函數的計算,需要注意的是,nn.CrossEntropyLoss()會自動完成softmax過程,調用該函數時,我們只需要輸入線性方程計算結果即可。
f = features[: 10] l = labels[: 10] w = torch.arange(9).reshape(3, 3).float() f l w #tensor([[-6.0141, -4.9911, 1.0000], # [-4.6593, -6.7657, 1.0000], # [-5.9395, -5.2347, 1.0000], # [-7.0262, -4.5792, 1.0000], # [-2.3817, -5.1295, 1.0000], # [-0.7093, -5.4693, 1.0000], # [-4.1530, -6.8751, 1.0000], # [-1.9636, -3.3003, 1.0000], # [-6.5046, -6.0710, 1.0000], # [-6.1291, -7.1835, 1.0000]]) #tensor([[0], # [0], # [0], # [0], # [0], # [0], # [0], # [0], # [0], # [0]]) #tensor([[0., 1., 2.], # [3., 4., 5.], # [6., 7., 8.]]) criterion = nn.CrossEntropyLoss() criterion(torch.mm(f, w), l.flatten()) #tensor(0.0021) m_cross_entropy(softmax(f, w), l) #tensor(0.0021)需要注意的是,交叉熵損失函數本質上還是關于w參數的函數方程。我們在進行反向傳播時也是將w視為葉節點,通過梯度計算逐步更新w的取值。
- Stage 3.定義優化算法
首先需要定義在softmax回歸下的準確率計算函數
def m_accuracy(soft_z, y):acc_bool = torch.argmax(soft_z, 1).flatten() == y.flatten()acc = torch.mean(acc_bool.float())return(acc)上述函數的soft_z是經過softmax轉化之后模型整體輸出結果。其中argmax返回最大值的索引值
torch.argmax(torch.tensor([1, 2])) #tensor(1)而對于從0開始進行計數的類別來說,以及softmax函數的輸出結果——每一行代表每一條數據在各類別上的softmax取值,我們對softmax的輸出結果進行逐行的最大值索引值的計算,即可直接得出每一條數據在當前模型計算結果下所屬類別的判別結果。
softmax(f, w) torch.argmax(softmax(f, w), 1) #tensor([[9.9995e-01, 4.5163e-05, 2.0398e-09], # [9.9997e-01, 2.9681e-05, 8.8098e-10], # [9.9996e-01, 3.8138e-05, 1.4546e-09], # [9.9998e-01, 2.4781e-05, 6.1412e-10], # [9.9851e-01, 1.4845e-03, 2.2070e-06], # [9.9436e-01, 5.6040e-03, 3.1583e-05], # [9.9996e-01, 4.4139e-05, 1.9484e-09], # [9.8594e-01, 1.3869e-02, 1.9509e-04], # [9.9999e-01, 9.3923e-06, 8.8216e-11], # [1.0000e+00, 4.4946e-06, 2.0202e-11]]) #tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])上述準確率函數可直接輸入線性方程組計算結果,也可輸入softmax之后的結果,softmax本身不影響大小排序。
梯度調整的函數繼續沿用sgd函數。
def sgd(params, lr):params.data -= lr * params.grad params.grad.zero_()- Stage.4 訓練模型
- 模型調試
首先,先嘗試多迭代幾輪,觀察模型收斂速度
# 設置隨機數種子 torch.manual_seed(420) # 迭代輪數 num_epochs = 20# 設置初始權重 w = torch.randn(3, 3, requires_grad = True) # 設置列表容器 train_acc = []# 執行迭代 for i in range(num_epochs):for epoch in range(i):for X, y in data_iter(batch_size, features, labels):l = loss(net(X, w), y)l.backward()sgd(w, lr)train_acc.append(m_accuracy(net(features, w), labels))# 繪制圖像查看準確率變化情況 plt.plot(list(range(num_epochs)), train_acc) train_acc #[tensor(0.4473), # tensor(0.8220), # tensor(0.9547), # tensor(0.9653), # tensor(0.9693), # tensor(0.9693), # tensor(0.9693), # tensor(0.9707), # tensor(0.9673), # tensor(0.9707), # tensor(0.9667), # tensor(0.9713), # tensor(0.9707), # tensor(0.9713), # tensor(0.9707), # tensor(0.9707), # tensor(0.9700), # tensor(0.9700), # tensor(0.9707), # tensor(0.9693)]和此前的邏輯回歸實驗結果類似,在數據內部離散程度較低的情況下,模型收斂速度較快。當然,這里我們可以進行簡單拓展,那就是當每一輪epoch時w都進行不同的隨機取值,會不會影響模型的收斂速度。
# 取10組不同的w,在迭代10輪的情況下觀察其收斂速度 for i in range(10):# torch.manual_seed(420) w = torch.randn(3, 3, requires_grad = True)train_acc = []for epoch in range(10):for X, y in data_iter(batch_size, features, labels):l = loss(net(X, w), y)l.backward()sgd(w, lr)train_acc.append(m_accuracy(net(features, w), labels))plt.plot(list(range(10)), train_acc)
能夠發現,盡管初始w的隨機取值會影響前期模型的準確率,但在整體收斂速度較快的情況下,基本在5輪左右模型都能達到較高的準確率。也就是說,損失函數的初始值點各不相同,但通過一輪輪梯度下降算法的迭代,都能夠找到(逼近)最小值點。此處即驗證了梯度下降算法本身的有效性,同時也說明對于該數據集來說,找到(逼近)損失函數的最小值點并不困難。
二、softmax回歸的快速實現
??接下來,嘗試通過調庫快速sofrmax回歸。經過一輪手動實現,我們已經對softmax回歸的各種建模細節以及數學運算過程已經非常熟悉,調庫實現也就更加容易。
- 定義核心參數
- 數據準備
- Stage 1.定義模型
由于我們所采用的CrossEntropyLoss類進行的損失函數求解,該類會自動對輸入對象進行softmax轉化,因此上述過程仍然只是構建了模型基本架構。
- Stage 2.定義損失函數
- Stage 3.定義優化方法
- Stage 4.模型訓練
接下來,即可執行模型訓練
fit(net = softmax_model, criterion = criterion, optimizer = optimizer, batchdata = batchData, epochs = num_epochs)查看模型訓練結果
softmax_model #softmaxR( # (linear): Linear(in_features=2, out_features=3, bias=True) ) # 查看模型參數 print(list(softmax_model.parameters())) #[Parameter containing: #tensor([[-0.3947, -0.7395], # [ 0.1667, -0.2784], # [ 0.6445, 0.2392]], requires_grad=True), Parameter containing: #tensor([-0.9082, 1.5810, -0.6922], requires_grad=True)] # 計算交叉熵損失 criterion(softmax_model(features), labels.flatten().long()) #tensor(0.1668, grad_fn=<NllLossBackward>) # 借助F.softmax函數,計算準確率 m_accuracy(F.softmax(softmax_model(features), 1), labels) #tensor(0.9620) F.softmax(softmax_model(features), 1) #tensor([[9.5957e-01, 4.0428e-02, 6.4515e-06], # [9.5540e-01, 4.4593e-02, 5.5352e-06], # [9.6062e-01, 3.9378e-02, 5.7618e-06], # ..., # [4.5679e-03, 1.5779e-01, 8.3765e-01], # [2.4782e-04, 2.0569e-02, 9.7918e-01], # [2.3951e-02, 3.7729e-01, 5.9876e-01]], grad_fn=<SoftmaxBackward>)2.模型調試
首先,上述結果能否在多迭代幾輪的情況下逐步提升
# 設置隨機數種子 torch.manual_seed(420) # 創建數據集 features, labels = tensorGenCla(deg_dispersion = [6, 2]) labels = labels.float() # 損失函數要求標簽也必須是浮點型 data = TensorDataset(features, labels) batchData = DataLoader(data, batch_size = batch_size, shuffle = True) #<torch._C.Generator at 0x1f9803ffd30> # 設置隨機數種子 torch.manual_seed(420) # 初始化核心參數 num_epochs = 20 SF1 = softmaxR() cr1 = nn.CrossEntropyLoss() op1 = optim.SGD(SF1.parameters(), lr = lr)# 創建列表容器 train_acc = []# 執行建模 for epochs in range(num_epochs):fit(net = SF1, criterion = cr1, optimizer = op1, batchdata = batchData, epochs = epochs)epoch_acc = m_accuracy(F.softmax(SF1(features), 1), labels)train_acc.append(epoch_acc)# 繪制圖像查看準確率變化情況 plt.plot(list(range(num_epochs)), train_acc)
和手動實現相同,此處模型也展示了非常快的收斂速度。當然需要再次強調,當num_epochs=20時,SF1參數已經訓練了(19+18+…+1)次了。
然后考慮增加數據集分類難度
# 設置隨機數種子 torch.manual_seed(420) # 創建數據集 features, labels = tensorGenCla(deg_dispersion = [6, 4]) labels = labels.float() # 損失函數要求標簽也必須是浮點型 data = TensorDataset(features, labels) batchData = DataLoader(data, batch_size = batch_size, shuffle = True) #<torch._C.Generator at 0x1f9803ffd30> plt.scatter(features[:, 0], features[:, 1], c = labels) # 設置隨機數種子 torch.manual_seed(420) # 初始化核心參數 num_epochs = 20 SF1 = softmaxR() cr1 = nn.CrossEntropyLoss() op1 = optim.SGD(SF1.parameters(), lr = lr)# 創建列表容器 train_acc = []# 執行建模 for epochs in range(num_epochs):fit(net = SF1, criterion = cr1, optimizer = op1, batchdata = batchData, epochs = epochs)epoch_acc = m_accuracy(F.softmax(SF1(features), 1), labels)train_acc.append(epoch_acc)# 繪制圖像查看準確率變化情況 plt.plot(list(range(num_epochs)), train_acc) train_acc #[tensor(0.1420), # tensor(0.7607), # tensor(0.7987), # tensor(0.7987), # tensor(0.7980), # tensor(0.7967), # tensor(0.7847), # tensor(0.8053), # tensor(0.7973), # tensor(0.7913), # tensor(0.7967), # tensor(0.8000), # tensor(0.8000), # tensor(0.7980), # tensor(0.8000), # tensor(0.7827), # tensor(0.8007), # tensor(0.7993), # tensor(0.7980), # tensor(0.7953)]我們發現,收斂速度仍然很快,模型很快就到達了比較穩定的狀態。但和此前的邏輯回歸實驗相同,模型結果雖然比較穩定,但受到數據集分類難度提升影響,模型準確率卻不高,基本維持在80%左右。一般來說,此時就代表模型抵達了判別效力上界,此時模型已經無法有效捕捉數據集中規律。
但到底什么叫做模型判別效力上界呢?從根本上來說就是模型已經到達(逼近)損失函數的最小值點,但模型的評估指標卻無法繼續提升。首先,我們可以初始選擇多個w來觀察損失函數是否已經逼近最小值點而不是落在了局部最小值點附近。
# 初始化核心參數 cr1 = nn.CrossEntropyLoss()# 創建列表容器 train_acc = []# 執行建模 for i in range(10):SF1 = softmaxR()op1 = optim.SGD(SF1.parameters(), lr = lr)fit(net = SF1, criterion = cr1, optimizer = op1, batchdata = batchData, epochs = 10)epoch_acc = m_accuracy(F.softmax(SF1(features), 1), labels)train_acc.append(epoch_acc)train_acc #[tensor(0.7940), # tensor(0.7900), # tensor(0.7960), # tensor(0.7880), # tensor(0.7887), # tensor(0.7980), # tensor(0.7980), # tensor(0.7873), # tensor(0.7960), # tensor(0.8000)]初始化不同的w發現最終模型準確率仍然是80%左右,也從側面印證迭代過程沒有問題,模型已經到達(逼近)最小值點。也就是說問題并不是出在損失函數的求解上,而是出在損失函數的構造上。此時的損失函數哪怕取得最小值點,也無法進一步提升模型效果。而損失函數的構造和模型的構造直接相關,此時若要進一步提升模型效果,就需要調整模型結構了。這也將是下一階段模型調優核心討論的內容。
補充閱讀內容
【損失損失函數取值和模型評估指標之間關系】
??很多時候,損失函數求得最小值也不一定能夠使得模型獲得較好的擬合效果。
def plot_polynomial_fit(x, y, deg):p = np.poly1d(np.polyfit(x, y, deg))t = np.linspace(0, 1, 200)plt.plot(x, y, 'ro', t, p(t), '-')n_dots = 20 x = np.linspace(0, 1, n_dots) # 從0到1,等寬排布的20個數 y = np.sqrt(x) + 0.2*np.random.rand(n_dots) - 0.1 plot_polynomial_fit(x, y, 1)
【關于PyTorch GPU運算的相關介紹】
??在課程剛開始的時候,我們就介紹了關于pytorch GPU版本的安裝,如果此前安裝過GPU版本PyTorch,此處就可以使用GPU進行運算了。在PyTorch 1.0版本之后,CPU計算的代碼和GPU計算的代碼基本可以通用,甚至可以通過全局變量直接控制一份代碼在CPU和GPU上快速切換。
??當然,GPU運算也分為分布式GPU計算和單GPU運算,二者在代碼規則上并無區別,單在計算流程上略有不同,此處先介紹單GPU計算方法,分布式GPU運算將在后續進行講解。
??但通過實踐我們能夠看出,GPU計算在小規模運算時并無優勢,另外,由CPU運算切換至GPU運算也非常便捷,因此如果暫時沒有GPU環境的同學也不用太擔心,可用先了解GPU運算背后原理,待有條件時再進行實踐。
- 測試是否可進行GPU計算
根據此前介紹,我們可用通過torch.cuda.is_available()判斷是否可用GPU進行計算
torch.cuda.is_available() #True- CPU存儲與GPU存儲
??CPU運算和GPU運算的核心區別就在于張量存儲位置的區別,如果張量是存儲在GPU上,則張量運算時就會自動調用CUDA進行GPU運算。默認情況下創建的張量是存儲在CPU內存上,也就是默認情況張量都是CPU運算。
tc = torch.randn(4) tc #tensor([ 1.1650, 2.0070, 0.6959, -0.4931])通過.cuda或者.cpu即可生成一個存儲在gpu或者cpu上的相同數據的對象
tg = tc.cuda() tg #tensor([ 1.1650, 2.0070, 0.6959, -0.4931], device='cuda:0') tg.cpu() #tensor([ 1.1650, 2.0070, 0.6959, -0.4931])當然,我們也可用通過.to()方法來進行轉化
tg.to('cpu') #tensor([ 1.1650, 2.0070, 0.6959, -0.4931]) tc.to('cuda') #tensor([ 1.1650, 2.0070, 0.6959, -0.4931], device='cuda:0')- device屬性
通過張量的device屬性,我們能夠查看張量存儲信息,并且能在創建張量時就直接創建存儲在同一個GPU上的張量
tg.device #device(type='cuda', index=0) torch.randn(4, device=tg.device) #tensor([ 0.2777, 0.2940, 0.9860, -0.4056], device='cuda:0')- CPU張量和GPU張量彼此不能相互運算
當然,如果是分布式GPU運算,存儲在不同GPU上的張量彼此也無法運算
- 模型參數存儲位置
??通過前例我們也發現了,在實例化模型的過程中,如果需要在GPU上運行,則需要在實例化過程對模型進行.cuda操作,核心作用就是將模型的參數保存在GPU上,從而可以和同樣在GPU上的數據進行計算。當然,我們可以通過以下方式讓模型和數據自動在cpu和gpu上切換。
CUDA = torch.cuda.is_available()if CUDA:features = features.cuda()labels = labels.cuda()model = model_class().cuda() else:model = model_class()- 模型運算
接下來,我們可以嘗試將上述模型的運行過程放在GPU上執行。
#創建數據 torch.manual_seed(420) features, labels = tensorGenCla(deg_dispersion = [6, 4]) labels = labels.float() features = features.cuda() labels = labels.cuda()data = TensorDataset(features, labels) batchData = DataLoader(data, batch_size = batch_size, shuffle = True) #<torch._C.Generator at 0x1f9803ffd30> features #tensor([[-6.0282, -3.9822], # [-3.3185, -7.5314], # [-5.8790, -4.4695], # ..., # [ 6.9244, 2.2811], # [ 5.4556, 12.4416], # [ 3.9411, 0.2473]], device='cuda:0')此時features已保存在GPU上,根據標記能看出目前是保存在第一塊GPU上。如果要將其轉移至CPU上,可通過.cpu方法在cpu上新生成一個數據。
而利用GPU進行計算時,則需要在實例化模型時加上.cuda,使得模型初始化參數也保存在GPU上。
# 初始化核心參數 num_epochs = 20 SF4 = softmaxR().cuda() cr4 = nn.CrossEntropyLoss() op4 = optim.SGD(SF4.parameters(), lr = lr)# 創建列表容器 train_acc = []import time start = time.perf_counter()# 執行建模 for epochs in range(num_epochs):fit(net = SF4, criterion = cr4, optimizer = op4, batchdata = batchData, epochs = epochs)epoch_acc = m_accuracy(F.softmax(SF4(features), 1), labels)epoch_acc = epoch_acc.cpu()train_acc.append(epoch_acc)# 繪制圖像查看準確率變化情況 plt.plot(list(range(num_epochs)), train_acc)finish = time.perf_counter() time_cost = finish - start print("計算時間:%s" % time_cost) #計算時間:14.391150299999936
從直觀感受上來看,在當前運算規模上,GPU的計算速度并不比CPU快。
限于在當前的運算規模,GPU運算對計算效率并不如CPU。但針對此模型,我們可以增加帶入訓練的數據集大小、減少每次訓練的小批數據量(增加每一個epoch的迭代次數)、增加整體迭代次數,在運算時間超過4小時時,GPU計算速度將超過CPU計算速度,如以下規模的計算:
【以下代碼運算量較大,謹慎運行!!!】
【以下代碼運算量較大,謹慎運行!!!】
【以下代碼運算量較大,謹慎運行!!!】
??通過此前的實驗我們發現,在數據量和運算規模較小的情況下(當然也是神經網絡層數較少的原因),GPU運算速度甚至要慢于CPU的計算速度,因此一般課上我們都采用CPU進行計算,待有接觸到大規模神經網絡的時候再開啟GPU加速,當然肯定還是處在CPU可運算的范圍。
【本節函數模塊添加】
本節課程結束后,需要將以下函數寫入torchLearning模塊,方便后續調用。
- sigmoid、logistic、cal、accuracy、cross_entropy
- acc_zhat、softmax、m_cross_entropy、m_accuracy
下節課開始,我們將正式進入到神經網絡優化算法部分內容。
總結
以上是生活随笔為你收集整理的Lesson 12.5 softmax回归建模实验的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Lesson 12.4 逻辑回归建模实验
- 下一篇: Lesson 13.2 模型拟合度概念介