天天看點

Pytorch全連接配接網絡

本篇開始學習搭建真正的神經網絡,前一部分讨論深度學習中預處理資料的基本流程;後一部分建構了兩種全連接配接網絡,用三種不同方案拟合時序資料;并在例程中詳細分析誤差函數,優化器,網絡調參,以及資料反向求導的過程。

資料預處理

本篇使用航空乘客資料AirPassengers.csv,其中包括從1949-1960年每月旅客的數量,程式則用于預測未來幾年中每月的旅客數量,資料可從以下Git項目中下載下傳。

​​https://github.com/aarshayj/analytics_vidhya/blob/master/Articles/Time_Series_Analysis/AirPassengers.csv​​

1.讀取資料

首先,引入必要的頭檔案,并從檔案中讀入資料:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

import torch
import torch.nn as nn
from torch.autograd import Variable

df = pd.read_csv('data/AirPassengers.csv')
plt.plot(df['#Passengers'])
plt.show()       

程式輸出如下圖所示:

Pytorch全連接配接網絡

2.歸一化

無論機器學習還是深度學習,使用哪一種架構,歸一化都是必要環節。歸一化的目标是将每一維特征壓縮到一定範圍之内,以免不同特征因取值範圍不同而影響其權重。非常大或非常小的值搭配上不恰當的學習率,往往使得收斂過慢,或者因每次調整的波動太大最終無法收斂。歸一化去除了這些不穩定因素。

歸一化的具體做法是将某一列特征轉換成均值為 0、标準差為1的資料,在圖像處理過程中,也常把0-255之間的顔色值轉換為0-1之間的小數。

本例中使用了均值和标準差編寫了歸一化和反歸一化函數:

def feature_normalize(data):
    mu = np.mean(data,axis=0) # 均值
    std = np.std(data,axis=0) # 标準差
    return (data - mu)/std

def feature_unnormalize(data, arr):
    mu = np.mean(data,axis=0)
    std = np.std(data,axis=0)
    return arr * std + mu      

3.提取新特征

提取新特征是指從現有特征中提取更多可以代入模型的資訊,進而生成新特征,本例中的資料包括兩列,第一列“Month”是字元串類型的時間,第二列“#Passengers”是乘客量,也就是需要預測的資料y。下面通過拆分和類型轉換,從第一列中提取具體的年“year”和月“mon”,将索引列變為特征“x”,并使用上面定義的函數實作歸一化功能。

df['year'] = df['Month'].apply(lambda x: float(x[:4]))
df['mon'] = df['Month'].apply(lambda x: float(x[5:]))
df['x'] = feature_normalize(df.index)
df['y'] = feature_normalize(df['#Passengers'])
df['year'] = feature_normalize(df['year'])
df['mon'] = feature_normalize(df['mon'])
df['real'] = feature_unnormalize(df['#Passengers'], df['y'])      

處理後的資料如下圖所示:

Pytorch全連接配接網絡

4.處理缺失值和異常值

處理缺失值和異常值也是特征工程的重要環節,有時花費的時間比模組化還多。處理缺失值的常用方法是删除重要特征缺失的item,或者用均值,前後值填充;處理異常值是監測資料中不正常的值,并做出相應處理,由于本例中資料比較“幹淨”,無需做缺失值和異常值處理。

5.向量化

向量化是将讀出的資料轉換成模型需要的資料格式,根據不同的模型做法不同,本例中的向量化将在後面的模型部分實作。

6.切分訓練集和測試集

訓練前還需要把資料切分成訓練集和測試集,以避免過拟合,本例中将70%的資料用于訓練,最終模型将對所有資料預測并做圖。

TRAIN_PERCENT = 0.7
train_size = int(len(df) * TRAIN_PERCENT)
train = df[:train_size]      

拟合直線

拟合程式分成三部分:定義模型、優化器和誤差函數;訓練模型;預測并做圖。

1.定義模型、優化器、誤差函數

模型繼承自mm.Module,并實作了兩個核心函數,init用于初始化模型結構,forward用于定義前向傳播的過程。本例中實作了最為簡單的模型,其中隻包含一個全連接配接層,使用nn.Linear定義,torch.nn中定義了常用的網絡層實作。

class LinearRegression(nn.Module):
    def __init__(self):
        super(LinearRegression, self).__init__()
        self.linear = nn.Linear(1, 1) # 輸入和輸出的次元都是1

    def forward(self, x):
        x = self.linear(x)
        return x

model = LinearRegression()
criterion = nn.MSELoss() # 損失函數:均方誤差
optimizer = torch.optim.SGD(model.parameters(), lr=0.001) # 優化算法:随機梯度下降      

損失函數使用了均方誤差 MSELoss,它計算的是預測值與真值之差平方的期望值,MSELoss也是回歸中最常用的損失函數,torch.nn中實作了一些常用的損失函數,可以直接使用,

優化的目标是更好地更新參數,使模型快速收斂。優化算法就是調整模型參數更新的政策,優化器是優化算法的具體實作。本例中優化器optimizer使用了最基礎的随機梯度下降optim.SGD優化方法,torch.optim中定義了常用的優化器。在參數中設定了學習率為0.001,并将模型的參數句柄傳入優化器,優化器後期将調整這些參數。

注意:學習率是一個重要參數,最好從小到大設定,如果設定太大,可能造成每次對參數修改過大,造成抖動,使得最終無法收斂。

2.訓練模型

訓練之前,先把資料轉換成模型需要的資料格式,将pandas的資料格式轉換為float32格式的Tensor張量,然後用unsqueeze擴充次元到2維(unsqueeze已在上一篇詳細介紹)。

x = torch.unsqueeze(torch.tensor(np.array(train['x']), dtype=torch.float32), dim=1)
y = torch.unsqueeze(torch.tensor(np.array(train['y']), dtype=torch.float32), dim=1)

for e in range(10000):
    inputs = Variable(x)
    target = Variable(y)
    out = model(inputs) # 前向傳播
    loss = criterion(out, target) # 計算誤差
    optimizer.zero_grad() # 梯度清零
    loss.backward() # 後向傳播
    optimizer.step() # 調整參數
    if (e+1) % 1000 == 0: # 每1000次疊代列印一次誤內插補點
        print('Epoch:{}, Loss:{:.5f}'.format(e+1, loss.item()))      

後面的循環部分進行了10000次疊代,也就是說将所有資料放進模型訓練了10000次,進而使模型收斂。每一次循環之中,将x,y分别轉換成變量Variable格式。

然後進行前先傳播,model(inputs)調用的是nn.Module 的call()函數(call是Python類中的一個特殊方法,如果類中定義了此方法,可以通過執行個體名加括号的方式調用該方法)父類的call()調用了前向函數forward()将資料傳入層中處理。

接下來是誤差函數和優化器配合調整模型參數,此處到底修改了哪些值,又是如何修改的,是最難了解的部分。先通過定義的誤差函數計算誤差,從loss值可以看到每一次疊代之後誤差的情況。

下一步是優化器清零,調用優化器的zero_grad方法,清除了model.parameters中的梯度grad。

之後是反向傳播,誤差函數的backward,調用了torch.autograd.backward()函數,backward()是上面定義的forward()的反向過程,對每層每一個參數求導,并填充在model.parameters的grad中。

最後調用優化器的step方法(step的具體實作可參考torch源碼中optim/sgd.py中的step函數),它使用model.parameters中的梯度grad和設定的學習率、動量等參數計算出model.parameters的新data值,形如:weight = weight - learning_rate * gradient。

可以說,最後幾步都是針對model.parameters模型參數的修改。整個過程可以通過跟蹤model.parameters的data和grad的内容變化來分析。方法如下:

for p in model.parameters():
    print(p.data, p.grad)      

也可以在程式中加入以下代碼,用于跟蹤後向傳播的過程:

f = loss.grad_fn
while True:
    print(f)
    if len(f.next_functions) == 0:
        break
    f = f.next_functions[0][0]      

3.預測和做圖

本例中用70%資料作為訓練集,用所有資料作為測試集,是以,用全部資料重新計算了x,y值;使用eval函數将模型轉換為測試模式(有一些層在訓練模型和預測模型時有差别);将資料代入模型預測,并轉換成numpy格式作圖顯示。

x = torch.unsqueeze(torch.tensor(np.array(df['x']), dtype=torch.float32), dim=1)
y = torch.unsqueeze(torch.tensor(np.array(df['y']), dtype=torch.float32), dim=1)
model.eval() #将模型變為測試模式
predict = model(Variable(x)) # 預測
predict = predict.data.numpy() # 轉換成numpy格式
plt.plot(x.numpy(), y.numpy(), 'y')
plt.plot(x.numpy(), predict)
plt.show()      

程式運作結果如下圖所示,可以看到模型用一條直線拟合曲線,在前70%的訓練資料中表現更好。

Pytorch全連接配接網絡

多特征拟合

直線拟合的原理是y=kx+b,求斜率k和截距b。其中的x是資料産生的時間,從資料表的索引号轉換求得,y是乘客量。還可以使用另一些方法進一步拟合曲線。如:

  • 方法一曲線拟合:從圖像資料可以看出,乘客資料走勢更拟合一條微微上翹的曲線,設y是x的多項式函數,可使用多項式拟合:y=ax3+bx2+cx+d。
  • 方法二多特征拟合:代入更多條件,比如利用年份、月份作為參數代入模型。

多參數拟合人x不止一個,可能是{x1,x2,x3...},設計模型時隻需要把輸入參數變成多個即可。

1.定義模型、優化器、誤差函數

與直線拟合的差異是将輸入次元變為3維,模型、優化器、誤差函數不變。

class Net2(torch.nn.Module):
    def __init__(self):
        super(Net2, self).__init__()
        self.linear = nn.Linear(3, 1) # 輸入3維,輸出1維

    def forward(self, x):
        x = self.linear(x)
        return x

model = Net2()
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.001)      

2.訓練模型

訓練模型步分的主要差異在于處理輸入資料,get_data函數分别提供了兩種方法,前一種方法用于多項式拟合,後一種方法将年,月資訊也作為代入模型的特征,此處,可以更好地了解全連接配接層的兩維輸入,其中一維是執行個體個數,另一維是執行個體中的各個特征。

def get_data(train):
    if False: # 可切換兩種方法
        inputs = [[i, i*i, i*i*i] for i in train['x']] # 一個x變成3維輸入資料
    else:
        inputs = [[item['x'], item['year'], item['mon']] for idx,item in train.iterrows()]
    X = torch.tensor(np.array(inputs), dtype=torch.float32)
    y = torch.unsqueeze(torch.tensor(np.array(train['y']), dtype=torch.float32), dim=1)
    return X, y

X, y = get_data(train)
for e in range(20000):
    inputs = Variable(X)
    target = Variable(y)
    out = model(inputs) # 前向傳播
    loss = criterion(out, target) # 計算誤差
    optimizer.zero_grad() # 清零
    loss.backward() # 後向傳播
    optimizer.step() # 調整參數
    if (e+1) % 1000 == 0:
        print('Epoch:{}, Loss:{:.5f}'.format(e+1, loss.item()))      

3.預測和做圖

預測和做圖隻有取資料部分與直線拟合不同。

model.eval() #将模型變為測試模式
X, y = get_data(df)
predict = model(Variable(X)) # 預測
predict = predict.data.numpy() # 轉換成numpy格式
plt.plot(y.numpy(), 'y')
plt.plot(predict)
plt.show()      

兩種方法拟合的曲線如下圖所示:

Pytorch全連接配接網絡
Pytorch全連接配接網絡

總結