第六节:Pytorch实现全连接神经网络
第六節:Pytorch實現全連接神經網絡
前面的五節中,我們講解了使用PyTorch搭建一個神經網絡中需要的需要各種技巧,包括:網絡的搭建、選擇不同的實踐技巧(優化器選擇、學習率下降等等)以及可視化訓練過程
接下來的幾章,我們將使用Pytorch搭建各種神經網絡
本章我們將使用PyTorch從頭到尾完整的搭建一個全連接神經網絡
我們使用垃圾郵件分類和加利福尼亞房價數據兩個數據集來進行訓練,分別對應機器學習中有監督學習的分類和回歸任務
分類任務:垃圾郵件分類
垃圾郵件分類的數據集可以在加利福尼亞大學爾灣分校的網站上下載
數據集一共包含三個文件,data文件是數據文件,其中的每一行都代表一個郵件,一共有4061個郵件,其中有1813個非垃圾郵件,2788個垃圾郵件
我們的目標是訓練一個全連接神經網絡來實現對垃圾郵件的預測
數據集一共有58列,其中前48列是某個關鍵詞在全文的頻率×100,例如you、make等詞語,每一個詞語以word_freq_xxx作為列索引,例如free的全文頻率以word_freq_free作為列索引;
49~54列是一些符號在全文所有符號中出現的評論×100,例如;,#,$等,同樣,這些列以char_freq_x的形式作為列名,例如;的列索引名稱為char_freq_;
55列是全文中所有連續的大寫單詞的平均長度,56列是大寫單詞的最長長度,57列是郵件中大寫字母的數量,58列是文件是否是垃圾郵件
names文件中包含所有的特征名稱
DOCUMENTATION中包含數據集的描述信息
準備工作
我們首先導入需要使用的庫
import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler,MinMaxScaler from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score,confusion_matrix,classification_report from sklearn.manifold import TSNE import torch import torch.nn as nn from torch.optim import SGD,Adam import torch.utils.data as Data import matplotlib.pyplot as plt import seaborn as sns import hiddenlayer as hl from torchviz import make_dot其中sklearn.preprocessing是用于對數據進行標準化預處理的模塊,幫助我們將所有的值映射到0~1之間,便于模型學習到數據的分布
由于我們是從csv文件中讀取的數據,因此我們還要使用sklearn.model_selection來幫助我們分割訓練集與測試集
我們使用sklearn.metrics來評估模型的預測效果
最后為了對數據集進行直觀的理解,我們使用sklearn.manifold來對輸入的數據(具有57個特征的郵件)進行降維,將57個特征按照重要性組合為2個特征從而能夠在平面上顯示,我們將使用這個模塊來降維以及可視化
數據清洗
我們首先對數據進行讀取、清洗、分割等預處理
在一個完整的機器學習的流程中,我們對給定數據集首先進行清洗、分割等預操作之后,還要根據對數據集進行了解,以確定我們會使用的機器學習算法,這里我們已經確定使用的是全連接神經網絡,但是為了體現一個完整的機器學習流程,我們還是會對數據集特征進行可視化與了解
data=pd.read_csv(filepath_or_buffer='./data/spambase/spambase.data',sep=',',header=None) index=pd.read_csv(filepath_or_buffer='./data/spambase/spambase.names',sep='\t',header=None) print(data.shape) print(data.head(2)) print('') print(index.shape) print(index.tail(20)) >>> (4601, 58)0 1 2 3 4 5 6 7 8 9 ... 48 49 \ 0 0.00 0.64 0.64 0.0 0.32 0.00 0.00 0.00 0.0 0.00 ... 0.0 0.000 1 0.21 0.28 0.50 0.0 0.14 0.28 0.21 0.07 0.0 0.94 ... 0.0 0.132 50 51 52 53 54 55 56 57 0 0.0 0.778 0.00 0.000 3.756 61 278 1 1 0.0 0.372 0.18 0.048 5.114 101 1028 1 [2 rows x 58 columns](87, 1)0 67 word_freq_parts: continuous. 68 word_freq_pm: continuous. 69 word_freq_direct: continuous. 70 word_freq_cs: continuous. 71 word_freq_meeting: continuous. 72 word_freq_original: continuous. 73 word_freq_project: continuous. 74 word_freq_re: continuous. 75 word_freq_edu: continuous. 76 word_freq_table: continuous. 77 word_freq_conference: continuous. 78 char_freq_;: continuous. 79 char_freq_(: continuous. 80 char_freq_[: continuous. 81 char_freq_!: continuous. 82 char_freq_$: continuous. 83 char_freq_#: continuous. 84 capital_run_length_average: continuous. 85 capital_run_length_longest: continuous. 86 capital_run_length_total: continuous.由于我們讀取的names文件夾中除了特征名以外,還有其他的內容,因此我們首先對names讀取出的特征內容進行清洗
首先通過抽樣確定特征開始的行
print(index.iloc[25:33]) >>>0 25 | i.e. unsolicited commercial e-mail. 26 | 27 | For more information, see file 'spambase.DOC... 28 | UCI Machine Learning Repository: http://www.... 29 1, 0. | spam, non-spam classes 30 word_freq_make: continuous. 31 word_freq_address: continuous. 32 word_freq_all: continuous.得知特征的名稱從30行開始,考慮到帶分割特征中每一行:前都是我們需要提取的數據,因此我們使用字符串的split方法
index=index.loc[30:].copy() print(index.head()) for i,word in enumerate(index.values):index.iloc[i]=index.iloc[i].values[0].split(':')[0] print(index.head()) >>>0 30 word_freq_make: continuous. 31 word_freq_address: continuous. 32 word_freq_all: continuous. 33 word_freq_3d: continuous. 34 word_freq_our: continuous.0 30 word_freq_make 31 word_freq_address 32 word_freq_all 33 word_freq_3d 34 word_freq_our接下來我們為添加上文件特征這一行之后,將其轉化為Index對象作為data對象的行名
index.loc[87]='label' newIndex=pd.Index(index.values.reshape(len(index.values))) data.columns=newIndex print(data.iloc[0:2,0:3]) >>>word_freq_make word_freq_address word_freq_all 0 0.00 0.64 0.64 1 0.21 0.28 0.50數據預處理
下面我們對數據進行預處理,以達到可以用于訓練的程度
首先劃分數據集,我們主要調用scikit-learn中的train_test_split來劃分數據集,我們指定測試集的大小以及隨機抽取的混亂度
X=data.iloc[:,0:57].values y=data['label'].values X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.25,random_state=123) print(y.shape) print(X_test.shape) print(X_train.shape) print(y_test.shape) print(y_train.shape) >>> (4601,) (1151, 57) (3450, 57) (1151,) (3450,)接下來標準化輸入的數據,使得輸入數據的范圍在0~1內,我們使用scikit-learn中的MinMaxScaler方法
最后我們為了檢驗標準化的結果,我們指定求出測試和訓練集中的每個特征的最大值,判斷是否為1,最小值是否為0
由于計算機的浮點數精度問題,實際上我們無法得到精確的1,只能得到一個和1相差為10-14~1017的數字,在計算機的角度來說我們就認為其為1
scaler=MinMaxScaler(feature_range=(0,1)) X_train=scaler.fit_transform(X_train) X_train=scaler.fit_transform(X_test) print((X_train.max(axis=0)>(1-1e-7)).sum(),(X_train.min(axis=0)==0).sum()) >>> 57 57數據特征可視化
在訓練前我們對訓練數據集所有數據的某一個特征進行可視化
我們使用箱須圖來進行可視化,箱須圖中的箱體的三條線分別表示25%,50%,75%位置的值,而須線的上邊緣和下邊緣分別是75%值+1.5(75%的值-25%的值)和25%的值-(75%的值-25%的值)
colname=data.columns.values[:-1] plt.figure(figsize=(20,14)) for ii in range(len(colname)):plt.subplot(7,9,ii+1)sns.boxplot(x=y_train,y=X_train[:,ii])plt.title(colname[ii]) plt.subplots_adjust(hspace=0.4) plt.show()搭建網絡并可視化網絡結構
接下來我們將搭建出網絡并可視化網絡結構
class FullyConnectedNuralNetwork(nn.Module):def __init__(self):super(FullyConnectedNuralNetwork,self)self.hidden1=nn.Sequential(nn.Linear(in_features=57,out_features=30,bias=True),nn.ReLU())self.hidden2=nn.Sequential(nn.Linear(in_features=30,out_features=10,bias=True),nn.ReLU())self.hidden3=nn.Sequential(nn.Linear(in_features=10,out_features=2,bias=True),nn.Sigmoid())def forward(self,x):fc1=self.hidden1(x)fc2=self.hidden2(fc1)output=self.hidden3(fc2)return fc1,fc2,output接下來用前面講過的torchviz庫的make_dot函數來可視化網絡結構
FCNN1=FullyConnectedNuralNetwork() x=torch.randn(size=(1,57)).requires_grad_(True) y=FCNN1(x) FCArchitecture=make_dot(y,params=dict(list(FCNN1.named_parameters())+[('x',x)])) FCArchitecture.format='png' FCArchitecture.directory='../圖片/' FCArchitecture.view()訓練網絡
接下來我們將訓練我們的網絡,并使用前面講解的方法來檢測訓練
首先需要使用將數據直接處理為可用于訓練的tensor,并且使用dataloader分批
X_train=torch.from_numpy(X_train.astype(np.float32)) y_train=torch.from_numpy(y_train.astype(np.float32)) X_test=torch.from_numpy(X_test.astype(np.float32)) y_test=torch.from_numpy(y_test.astype(np.float32))train_data=Data.TensorDataset(X_train,y_train) train_loader=Data.DataLoader(dataset=train_data,batch_size=64,shuffle=True,num_workers=1) for step,(batch_x,batch_y) in enumerate(train_loader):if step>0:break print(step,batch_x.shape,batch_y.shape) >>> 1 torch.Size([64, 57]) torch.Size([64])然后定義需要使用的優化器和損失函數
optomizerAdam=torch.optim.Adam(FCNN1.parameters(),lr=0.01) lossFunc=nn.CrossEntropyLoss()由于我們是一個輕量級的網絡,因此使用HiddenLayer來進行可視化,注意我們如果把繪圖函數放在訓練過程內,那么就會得到動態的繪圖效果
history1=hl.History() canvas1=hl.Canvas() logStep=25 for epoch in range(15):for step,(batch_x,batch_y) in enumerate(train_loader):_,_,output=FCNN1(batch_x)train_loss=lossFunc(output,batch_y)optomizerAdam.zero_grad()train_loss.backward()optomizerAdam.step()niter=epoch*len(train_loader)+step+1if niter % logStep ==0:_,_,output=FCNN1(X_test)_,pre_lab=torch.max(output,1)test_accuracy=accuracy_score(y_test,pre_lab)history1.log(niter,train_loss=train_loss,test_accuracy=test_accuracy)with canvas1:canvas1.draw_plot(history1['train_loss'])canvas1.draw_plot(history1['test_accuracy'])最后的效果如下
最后,盡管我們訓練的準確度不穩定,但是我們的準確度卻依舊維持在了較高的水平
對于沒有得到穩定的準確度,一個可能的原因是訓練后期我們當前使用lr過大,導致一直在最優點之前震蕩而無法下降到最優點
理解網絡
我們上面訓練的網絡本質上是個黑箱模型,我們無法了解其中發生了什么事,下面我們對網絡中間進行可視化,來了解輸入數據在網絡中計算的時候發生了什么
落實到代碼上就是我們要得到中間層的輸出
得到中間層輸出有兩種方法,第一種就是直接利用我們前向傳播時候返回的中間值,第二種就是使用鉤子技術
鉤子技術可以理解為在不影響原業務的基礎上獲得我們希望的中間值
我們下面將使用鉤子技術來獲取中間值,鉤子技術的實現主要靠閉包
activations={} activations['origin']=X_test def getActivation(name):def hook(model,inputData,outputData):activations[name]=outputData.detach()return hook這里activations字典主要用于存儲中間層的輸出,hook需要定義的輸入實際上是Pytorch中以及規定好的,需要我們預留的,因此必須這樣寫
Pytorch中的每個層為我們預留了register_forward_hook函數,即預留了一個接口,我們如果調用這個接口,那么就會將隱藏在底層的輸入和輸出顯化
接下來Pytorch會將model填充為我們自定義的模型,input是指定層的輸入,output是指定層的輸出,這里由于我們只需要指定層的輸出,因此只需要將獲取的輸出保存在全局上的字典即可
接下來我們在獲取中間值的時候再進行一次正常的計算就能夠獲取中間值,獲取的原理就是上面說的,X_test的正向傳播時候隱藏在底層的hidden1的輸入和輸出顯化,并且按照我們設定的字典的模式保存
FCNN1.hidden1.register_forward_hook(getActivation('hidden1')) FCNN1.hidden2.register_forward_hook(getActivation('hidden2')) FCNN1.hidden3.register_forward_hook(getActivation('hidden3')) _,_,_=FCNN1(X_test)我們查看下保存的效果
print(len(activations)) for item in activations:print(type(activations[item]))print(activations[item].shape) >>> 4 <class 'torch.Tensor'> torch.Size([1151, 57]) <class 'torch.Tensor'> torch.Size([1151, 30]) <class 'torch.Tensor'> torch.Size([1151, 10]) <class 'torch.Tensor'> torch.Size([1151, 2])最后我們將每層得到的輸出,包括原始輸入使用TSNE方法進行降維,降維到二維以便于在圖像上顯示
plt.figure(figsize=(16,12)) for i,item in enumerate(activations):plt.subplot(2,2,i+1)value=TSNE(n_components=2).fit_transform(activations[item].data.numpy())plt.xlim([min(value[:,0]-1),max(value[:,0]+1)])plt.ylim([min(value[:,1]-1),max(value[:,0]+1)])plt.plot(value[y_test==0,0],value[y_test==0,1],'bo',label='Non-trash')plt.plot(value[y_test==1,0],value[y_test==1,1],'rd',label='Trash')plt.title(item) plt.legend() plt.subplots_adjust(hspace=0.4) plt.show()我們能夠看到,原始輸入的郵件具有57個特征,使用TSNE函數,即先使用PCA將57個特征根據重要程度壓縮為2個特征,然后可視化,我們發現這個時候垃圾郵件和非垃圾郵件是雜亂的摻雜的
但是經過第一個隱藏層之后,得到了有效的劃分,接下來再經過第二個隱藏層之后進一步得到到了劃分,一直直到最后一層
至此,第一個例子已經講解完畢
回歸任務:房價預測
下面我們將使用scikit-learning庫中的加利福尼亞州的房價數據來訓練我們的網絡,來完成對房價的預測
CA房價數據集來源于1990美國人口普查,這次人口普查將整個CA劃分為多個人口普查區域,每個普查區域通常有600~3000的人口
該數據集中的每一行都是一個普查區,一共包含20640個普查區,每個普查區有10個特征,例如:該區域收入平均數、房屋年齡、平均房間數等等
最后我們將搭建一個全連接神經網絡,來預測房屋的價格
具體的步驟和上面進行垃圾郵件分類的任務大體相似,只不過由于我們使用的是scikit-learn中現成的庫,因此免去了我們進行數據清洗的過程
準備工作
首先是導入庫
import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error,mean_absolute_error from sklearn.datasets import fetch_california_housing import torch import torch.nn as nn import torch.nn.functional as F from torch.optim import Adam import matplotlib.pyplot as plt import seaborn as sns數據預處理
我們直接使用fetch_california_housing來獲取需要處理的數據
但是由于使用這個函數將會訪問外網來下載數據集,經常會由于url無法打開而報錯,因此這里直接讀取已經下載好的csv文件即可
具體的資源csdn上搜索加利福尼亞房屋價格即可
houseData=pd.read_csv('./data/housing.csv',sep=',') print(houseData.shape) print(houseData.head()) >>> (20640, 10)longitude latitude housing_median_age total_rooms total_bedrooms \ 0 -122.23 37.88 41 880 129.0 1 -122.22 37.86 21 7099 1106.0 2 -122.24 37.85 52 1467 190.0 3 -122.25 37.85 52 1274 235.0 4 -122.25 37.85 52 1627 280.0 population households median_income median_house_value ocean_proximity 0 322 126 8.3252 452600 NEAR BAY 1 2401 1138 8.3014 358500 NEAR BAY 2 496 177 7.2574 352100 NEAR BAY 3 558 219 5.6431 341300 NEAR BAY 4 565 259 3.8462 342200 NEAR BAY這里median_house_value就是我們要預測的房屋價格
由于ocean_proximity這一列是字符,我們需要將其轉化為數值才能夠參與到后面的運算
我們首先查詢下一共有那些數值
types=[] for i in houseData['ocean_proximity'].values:if i not in types:types.append(i) print(types) >>> ['NEAR BAY', '<1H OCEAN', 'INLAND', 'NEAR OCEAN', 'ISLAND']我們根據順序,分別給分0,1,2,3,來進行轉化
houseData['ocean_proximity_value']=np.zeros_like(houseData['households']) for mark,location in enumerate(types):houseData.ocean_proximity_value[houseData.ocean_proximity==location]=mark newtype=[] for i in houseData['ocean_proximity_value']:if i not in newtype:newtype.append(i) print(newtype) >>> [0, 1, 2, 3, 4]我們首先添加了ocean_proximity_value這一列來儲存轉化的數值,初值全為0
enumerate函數的功能是將列表的值與索引綁定起來,形成一個元組,在這里是type最初為[‘NEAR BAY’, ‘<1H OCEAN’, ‘INLAND’, ‘NEAR OCEAN’, ‘ISLAND’]
我們使用enumerate綁定之后返回的結果就是[(0,‘NEAR BAY’), (1,’<1H OCEAN’), (2,‘INLAND’), (3,‘NEAR OCEAN’), (4,‘ISLAND’)]
然后我們使用元組賦值的方法來在每次迭代的時候同時賦值
每次迭代內部,我們對houseData的ocean_proximity_value這一列進行修改,需要注意的是,我們使用的屬性查值,而非索引查值,這樣避免了鏈式索引帶來的問題
接下來對數據進行分割
houseLabel=houseData['median_house_value'].copy() houseData.drop(['median_house_value','ocean_proximity'],axis=1,inplace=True) X_train,X_test,y_train,y_test=train_test_split(houseData.values,houseLabel.values,test_size=0.3,random_state=42) scaler=StandardScaler() X_train=scaler.fit_transform(X_train) X_test=scaler.fit_transform(X_test) print(X_train.shape) print(X_test.shape) >>> (14448, 9) (6192, 9)數據特征可視化
接下來我們訓練數據集的九個特征進行可視化
首先是箱須圖,來了解訓練數據集的9個特征的分布
colnames=houseData.columns.values print(colnames) plt.figure(figsize=(20,8)) for ii,name in enumerate(colnames):plt.subplot(5,2,ii+1)sns.boxplot(x=X_train[:,ii])plt.title(name) plt.subplots_adjust(hspace=0.6) plt.show()接下來我們繪制所有特征之間的相關系數熱力圖
dataCor=np.corrcoef(X_train,rowvar=0) dataCor=pd.DataFrame(dataCor,columns=colnames,index=colnames) plt.figure(figsize=(8,6)) sns.heatmap(dataCor,square=True,annot=True,fmt='.3f',linewidths=.5,cmap='YlGnBu',cbar_kws={'fraction':0.046,'pad':0.03}) plt.show()最后,我們將數據轉換為Tensor,便于下面的網絡計算
X_train=torch.from_numpy(X_train.astype(np.float32)) y_train=torch.from_numpy(y_train.astype(np.float32)) X_test=torch.from_numpy(X_test.astype(np.float32)) y_test=torch.from_numpy(y_test.astype(np.float32))train_data=Data.TensorDataset(X_train,y_train) test_data=Data.TensorDataset(X_test,y_test) train_loader=Data.DataLoader(dataset=train_data,batch_size=64,shuffle=True,num_workers=1)搭建網絡并可視化結構
我們首先搭建如下的網絡
class FullyConnectedNuralNetwork(nn.Module):def __init__(self):super(FullyConnectedNuralNetwork,self).__init__()self.hidden1=nn.Sequential(nn.Linear(in_features=9,out_features=100,bias=True),nn.ReLU())self.hidden2=nn.Sequential(nn.Linear(in_features=100,out_features=100,bias=True),nn.ReLu())self.hidden3=nn.Sequential(nn.Linear(in_features=100,out_features=50,bias=True),nn.ReLU())self.predict=nn.Sequential(nn.Linear(in_features=50,out_features=1,bias=True),nn.ReLU())def forward(self,x):x=self.hidden1(x)x=self.hidden2(x)x=self.hidden3(x)x=self.predict(x)return x接下來使用torchviz中的make_dot來進行可視化
from torchviz import make_dot fcNet=FullyConnectedNuralNetwork() x=torch.randn(size=(1,9)).requires_grad_(True) y=fcNet(x) fcNetArchitecture=make_dot(y,params=dict(list(fcNet.named_parameters())+[('x',x)])) fcNetArchitecture.directory='/home/jack/圖片/houseNet.png' fcNetArchitecture.view()訓練網絡
老生長談,上代碼
from sklearn.metrics import accuracy_score logStep=25 train_loss_all=[] for epoch in range(30):train_loss=0train_num=0for step,(batch_x,batch_y) in enumerate(train_loader):output=fcNet(batch_x)loss=lossFunc(output,batch_y)optimizerAdam.zero_grad()loss.backward()optimizerAdam.step()train_loss_=loss.item()*batch_x.size(0)train_num+=batch_x.size(0)train_loss_all.append(train_loss/train_num)由于是回歸問題,最后的輸出是一個值,因此直接記錄每次的損失即可
(未完待續)
最近大二剛開學,作業有點多,諸位看官見諒,最后一個訓練的代碼得到的訓練結果有問題,還沒debug,過幾天有時間了再寫
本教程會一直持續到使用Pytorch實現各種網絡
總結
以上是生活随笔為你收集整理的第六节:Pytorch实现全连接神经网络的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 推荐两个高质量程序猿国外接单网站—自由开
- 下一篇: 上海 中高考