機器學習簡單來講就是要在數據中訓練出一個模型,能夠將輸入映射成合理的輸出。所以,在訓練模型之前,我們首先準備好輸入、輸出對;然后再利用這些輸入、輸出對來優化模型,使模型的LOSS(預測輸出和實際輸出的誤差)盡可能小。模型優化的基本原理是梯度下降法。pytorch為實現上述任務提供了一個很好的框架,或者說一個很好的模板,使得做深度學習變得非常簡單,簡單到一兩個小時就能入門。本文借助一個簡單線性回歸的例子,簡要介紹了Pytorch框架中的數據加載及模型訓練等。
生成輸入輸出對
在訓練模型之前,我們首先生成一些輸入、輸出對,作為模型訓練數據和測試數據。本例通過一個線性函數疊加一些噪聲來生成。比如下面這段代碼,取權重值為2,偏置為5的線性函數,然后疊加一個標準正態分布的噪聲,生成100個數據點。生成兩次,一次用于模型訓練,一次用于模型測試。通過該數據訓練的線性模型,我們希望權重越接近2越好,偏置越接近5越好。
import random
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
# 生成數據
x = random.sample(range(0, 100), 100)
x = 0.1*np.array(x)
y = x*2+5+np.random.randn(100)
plt.plot(x, y, 'o')
plt.show()
data = [list(x), list(y)]
# 矩陣轉置
data = list(zip(*data))
column = ['x', 'y']
# list轉換成dataFrame
dataset = pd.DataFrame(data=data, columns=column)
# 將數據保存到'.csv'文件中
dataset.to_csv('data/trainData.csv')
# dataset.to_csv('data/testData.csv')
上述代碼生成的數據分布如下圖所示,
模型的設計
有了輸入、輸出對數據,接下來我們再來設計模型。pytorch為我們提供了一個框架,使得網絡模型的搭建非常簡單,簡單得像是在搭積木。pytorch不僅提供了搭積木的框架,還提供了大量的積木塊,例如Linear層、卷積層、激活函數等等,我們只需要根據任務需求將這些積木堆在一起就行了。我們通過線性回歸這個最簡單的例子來學習一下pytorch的模型搭建框架。
pytorch的積木搭建框架以及積木塊都在pytorch的nn模塊中,因此首先要導入nn模塊。pytorch的積木搭建框架是一個叫做Module的類,在搭建自己的網絡模型是需要繼承這個類。在這個類里面,有兩個函數為我們搭建積木提供了支撐,需要改寫,一個是init函數,一個是forward函數。init函數列出我們需要用的積木塊,并根據需要設置好這些積木塊的相關參數;在設置參數的時候一定要注意上一層積木的輸出維度和下一層積木的輸入維度匹配。forward函數將這些積木塊壘在一起,使輸入能順利地通過一層層的積木塊,最后輸出。我們這里是一個簡單的線性回歸問題,所以只需要一塊積木,那就是nn.Linear,該積木塊提供了線性變換功能。nn.Linear有三個參數,分別是in_features, out_features和bias,分別代表了輸入的維度、輸出的維度和是否需要偏置(默認的情況下偏置保留)。在我們這個例子中,輸入和輸出的維度都是1,需要偏置。模型搭建的代碼如下,代碼保存在model.py中。
from torch import nn
# 定義模型時繼承nn.Module
class LinearRegress(nn.Module):
# __init__函數列出積木塊并設置積木的參數,這里的參數由模型實例化時給出
def __init__(self, inputsize, outputsize):
super(LinearRegress, self).__init__()
self.Linear1 = nn.Linear(in_features=inputsize, out_features=outputsize)
# forward函數搭積木,將積木壘在一塊,讓輸入依次通過積木塊最后輸出
def forward(self, x):
return self.Linear1(x)
數據的加載
為了簡化訓練數據和測試數據的加載過程,pytorch為我們提供了數據集模板Dataset以及數據加載器DataLoader。我們在訓練模型時需要從數據集中摳出輸入、輸出對,Dataset恰好為我們給我們提供了一個摳輸入、輸出對的模板。我們定義自己的數據集時,需要繼承Dataset,并改寫三個函數,分別是init, getitem, len。init一般告訴代碼要加載的數據集存在哪個位置。getitem從文件夾中讀入數據集并進行一些處理,返回輸入輸出對。這里要注意返回輸入、輸出對的格式是Tensor,并且輸入Tensor的維度一定要和模型的輸入維度一致,輸出Tensor的維度一定要和模型的輸出維度一致,否則會出錯。例如mn.Linear輸入、輸出都是二維Tensor,分別是[batchsize,in_features]、[bathchsize,outfeature]。所以,在加載了數據之后,首先要將輸入、輸出數據都轉換成Tensor,然后將1維Tensor轉換成二維Tensor。len函數返回數據集的長度。
DataLoader提供一些列參數設置,方便我們可以根據需要靈活的加載數據。例如一次加載的數據大小batchsize,是否打亂數據順序shuffer等,還有各種參數可以看和help中對DataLoader的解釋。下面是代碼,保存在dataProcess.py中。
import os
from torch.utils.data import DataLoader
from torch.utils.data import Dataset
import pandas as pd
# 將數據導入到DataSet
class MyDataSet(Dataset):
# 初始化時從文件中把數據讀進來
def __init__(self, dataDir, dataName):
DataPath = os.path.join(dataDir, dataName)
self.data = pd.read_csv(DataPath)
def __getitem__(self, idx):
# 將數據轉換成二維Tensor
x_tensor = torch.Tensor(self.data['x'].to_list()).reshape(-1, 1)
y_tensor = torch.Tensor(self.data['y'].to_list()).reshape(-1, 1)
return x_tensor[idx], y_tensor[idx]
def __len__(self):
return len(self.data)
#加載訓練數據
myTrainData = MyDataSet("data", "trainData.csv")
#將batch_size設置成50,表示每一次迭代取出50個數據。
myTrainDataLoader = DataLoader(dataset=myTrainData, batch_size=50, shuffle=True)
#加載測試數據
myTestData = MyDataSet("data", "testData.csv")
myTestDataLoader = DataLoader(dataset=myTestData, batch_size=50, shuffle=True)
模型的訓練與測試
模型的訓練過程大致如下:
- 從數據集中取出一個btachsize的輸入、輸出對。
- 把輸入扔給模型,得到預測輸出
- 計算預測輸出和真實輸出之間的LOSS
- 反向傳播計算梯度,并優化一次模型參數
- 回到第1步,直到從數據集中取出所有數據,完成一次完整的訓練
- 重復1-5步epoch次
為了測試模型的泛化能力,我們往往在優化模型的過程中還會使用一些測試數據來測試模型的預測效果。這里一定要注意測試數據和訓練數據不是同一個數據集,提前要把數據進行分割,分成訓練數據和測試數據。一般每進行一次完整的訓練后,對模型進行一次測試,也就是每一個epoch,測試一次模型。測試的時候也需要計算模型預測輸出和真實輸出之間的LOSS,只是測試不用再計算梯度和優化模型了。如果在訓練過程中發現訓練的LOSS在不斷減小,但是測試的LOSS卻在增加,這時候模型發生了過擬合問題,要提前終止訓練。
當然,我們為了看到訓練的效果,往往要畫LOSS隨著迭代次數的變化曲線,這個我們可以借助Tensorboard,也可以用一個list把訓練過程的LOSS保存下來,最后用matplotlib.pyplot畫出來。
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from model import *
from dataProcess import *
import matplotlib.pyplot as plt
#加載訓練數據
myTrainData = MyDataSet("data", "trainData.csv")
#將batch_size設置成50,表示每一次迭代取出50個數據。
myTrainDataLoader = DataLoader(dataset=myTrainData, batch_size=50, shuffle=True)
#加載測試數據
myTestData = MyDataSet("data", "testData.csv")
myTestDataLoader = DataLoader(dataset=myTestData, batch_size=50, shuffle=True)
# 創建網絡模型
myModel = LinearRegress(inputsize=1, outputsize=1)
# 損失函數
loss_fn = nn.MSELoss()
# 學習率
learning_rate = 5e-3
# 優化器
optimizer = torch.optim.SGD(myModel.parameters(), lr=learning_rate)
# 總共的訓練步數
total_train_step = 0
#總共的測試步數
total_test_step = 0
step = 0
epoch =500
# Tensorboard的writer實例,用于記錄訓練過程中的LOSS變化
writer = SummaryWriter("logs")
train_loss_his = []
test_totalloss_his = []
for i in range(epoch):
print(f"-------第{i}輪訓練開始-------")
# 這一部分是模型訓練
for data in myTrainDataLoader:
# 注意這里是取了一個batchsize的數據,該例batchsize=50,因此取了50個數據
x, y = data
# 把輸入扔給模型,得到預測輸出output
output = myModel(x)
# 計算預測輸出output和真是輸出y之間的LOSS
loss = loss_fn(output, y)
# 將梯度清零,好像這一步必須要
optimizer.zero_grad()
# 反向傳播,計算梯度
loss.backward()
# 優化一次參數
optimizer.step()
# 總的迭代次數加1
total_train_step = total_train_step+1
# 將當前的LOSS放到LOSS記錄的list中
train_loss_his.append(loss)
# 將當前的LOSS記錄到tensorboard的中
writer.add_scalar("train_loss", loss.item(), total_train_step)
print(f"訓練次數:{total_train_step},loss:{loss}")
# 下面這段代碼是模型測試
total_test_loss = 0
# 這里告訴代碼不用求梯度了
with torch.no_grad():
for data in myTestDataLoader:
x, y = data
output = myModel(x)
loss = loss_fn(output, y)
# 這里求一個epoch的總loss
total_test_loss = total_test_loss + loss
print(f"測試集上的loss:{total_test_loss}")
test_totalloss_his.append(total_test_loss)
writer.add_scalar("test_loss", total_test_loss.item(), i)
# 輸出線性模型的兩個參數,分別是權重和偏置
for parameters in myModel.parameters():
print(parameters)
writer.close()
# 畫出訓練損失變化曲線
plt.plot(train_loss_his)
plt.show()
# 畫出測試損失變化曲線
plt.plot(test_totalloss_his)
plt.show()
運行上述代碼,訓練LOSS的變化如下圖,
測試LOSS的變化如下圖,
線性模型的兩個參數:權重為2.1539,偏置為4.1611。可以看到訓練后的參數和預期參數接近,但也存在一定的偏差。可以嘗試設置不同學習率、采用不同的優化器、使用不同的LOSS函數等,會對結果產生很大的影響,這就是無聊的調參了。
到此為止,基于pytorch的深度學習框架就入門了,確實很簡單!