我的實踐:通過一個簡單線性回歸入門pytorch

機器學習簡單來講就是要在數據中訓練出一個模型,能夠將輸入映射成合理的輸出。所以,在訓練模型之前,我們首先準備好輸入、輸出對;然后再利用這些輸入、輸出對來優化模型,使模型的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')

上述代碼生成的數據分布如下圖所示,


訓練數據![Figure_2.png](https://upload-images.jianshu.io/upload_images/2656792-fc5b5ea73db94e12.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

模型的設計

有了輸入、輸出對數據,接下來我們再來設計模型。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, leninit一般告訴代碼要加載的數據集存在哪個位置。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)

模型的訓練與測試

模型的訓練過程大致如下:

  1. 從數據集中取出一個btachsize的輸入、輸出對。
  2. 把輸入扔給模型,得到預測輸出
  3. 計算預測輸出和真實輸出之間的LOSS
  4. 反向傳播計算梯度,并優化一次模型參數
  5. 回到第1步,直到從數據集中取出所有數據,完成一次完整的訓練
  6. 重復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的變化如下圖,


Figure_2.png

測試LOSS的變化如下圖,


Figure_3.png

線性模型的兩個參數:權重為2.1539,偏置為4.1611。可以看到訓練后的參數和預期參數接近,但也存在一定的偏差。可以嘗試設置不同學習率、采用不同的優化器、使用不同的LOSS函數等,會對結果產生很大的影響,這就是無聊的調參了。
到此為止,基于pytorch的深度學習框架就入門了,確實很簡單!
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372