本文參考《20天吃透Pytorch》來實現titanic數據集的模型建立和訓練
在書中理論的同時加入自己的理解。
一,準備數據
數據加載
titanic數據集的目標是根據乘客信息預測他們在Titanic號撞擊冰山沉沒后能否生存。
結構化數據一般會使用Pandas中的DataFrame進行預處理。
import torch
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from torch import nn
from torch.utils.data import Dataset,DataLoader,TensorDataset
數據集字段如下:
字段說明:
Survived:0代表死亡,1代表存活【y標簽】
Pclass:乘客所持票類,有三種值(1,2,3) 【轉換成onehot編碼】
Name:乘客姓名 【舍去】
Sex:乘客性別 【轉換成bool特征】
Age:乘客年齡(有缺失) 【數值特征,添加“年齡是否缺失”作為輔助特征】
SibSp:乘客兄弟姐妹/配偶的個數(整數值) 【數值特征】
Parch:乘客父母/孩子的個數(整數值)【數值特征】
Ticket:票號(字符串)【舍去】
Fare:乘客所持票的價格(浮點數,0-500不等) 【數值特征】
Cabin:乘客所在船艙(有缺失) 【添加“所在船艙是否缺失”作為輔助特征】
Embarked:乘客登船港口:S、C、Q(有缺失)【轉換成onehot編碼,四維度 S,C,Q,nan】
加載數據集:
#數據讀取
train_data = pd.read_csv('./data/titanic/train.csv')
test_data = pd.read_csv('./data/titanic/test.csv')
test_datay = pd.read_csv('./data/titanic/titanic.csv')
#print(train_data.head(10)) #打印訓練數據前十個
當我們獲得數據集后,首先要查看數據中是否有缺失!!!這個很重要!
train_data.info() #查看訓練數據有沒有未知的的
test_data.info() #查看測試數據有沒有未知的的
很明顯,Age和Cabin數據都有缺失
接下來,先利用Pandas的數據可視化分析數據:
幸存情況
#幸存情況
ax = train_data['Survived'].value_counts().plot(kind = 'bar',figsize = (12,8),fontsize =15,rot = 0)
#value_counts是查詢有多少個不同值且每個不同值有多少個重復的
ax.set_ylabel('Counts',fontsize = 15)
ax.set_xlabel('Survived',fontsize = 15)
plt.show()
年齡分布情況
#年齡分布情況
ax = train_data['Age'].plot(kind = 'hist',bins = 20,color = 'purple',figsize = (12,8),fontsize = 15)
"""
hist方法常用的參數有以下幾個
1. bins,控制直方圖中的區間個數
2. color,指定柱子的填充色
3. edgecolor, 指定柱子邊框的顏色
4. density,指定柱子高度對應的信息,有數值和頻率兩種選擇
5. orientation,指定柱子的方向,有水平和垂直兩個方向
6. histtype,繪圖的類型
"""
ax.set_ylabel('Frequency',fontsize = 15)
ax.set_xlabel('Age',fontsize = 15)
plt.show()
年齡和label的相關性
#年齡和label的相關性
ax = train_data.query('Survived == 0')['Age'].plot(kind = 'density',figsize = (12,8),fontsize = 15)
#使用python.query()函數對數據框進行(挑選行)的操作
train_data.query('Survived == 1')['Age'].plot(kind = 'density',figsize = (12,8),fontsize = 15)
ax.legend(['Survived ==0','Survived ==1'],fontsize = 12)
#plt.legend()函數主要的作用就是給圖加上圖例,plt.legend([x,y,z])里面的參數使用的是list的的形式將圖表的的名稱喂給這和函數。
ax.set_ylabel('Density',fontsize = 15)
ax.set_xlabel('Age',fontsize = 15)
plt.show()
數據預處理
這個步驟非常非常重要! 不僅要加載數據集,更重要的是如何處理NULL數據!
"""
數據預處理
"""
def preprocessing(dfdata):dfresult = pd.DataFrame() #存儲結果#DataFrame是Python中Pandas庫中的一種數據結構,它類似excel,是一種二維表。#Pclass處理dfPclass = pd.get_dummies(dfdata['Pclass'])#對Pclass進行get_dummies,將該特征離散化dfPclass.columns = ['Pclass_'+str(x) for x in dfPclass.columns]dfresult = pd.concat([dfresult,dfPclass],axis=1)#concat函數是在pandas底下的方法,可以將數據根據不同的軸作簡單的融合,axis: 需要合并鏈接的軸,0是行,1是列#SexdfSex = pd.get_dummies(dfdata['Sex'])dfresult = pd.concat([dfresult, dfSex], axis=1)#Agedfresult['Age'] = dfdata['Age'].fillna(0)dfresult['Age_null'] = pd.isna(dfdata['Age']).astype('int32')#pandas.isna(obj)檢測array-like對象的缺失值# SibSp,Parch,Faredfresult['SibSp'] = dfdata['SibSp']dfresult['Parch'] = dfdata['Parch']dfresult['Fare'] = dfdata['Fare']# Carbindfresult['Cabin_null'] = pd.isna(dfdata['Cabin']).astype('int32')print(dfresult['Cabin_null'])# EmbarkeddfEmbarked = pd.get_dummies(dfdata['Embarked'], dummy_na=True)dfEmbarked.columns = ['Embarked_' + str(x) for x in dfEmbarked.columns]#DataFrame.columns屬性以返回給定 DataFrame 的列標簽。dfresult = pd.concat([dfresult, dfEmbarked], axis=1)return dfresult#獲得訓練x,y
x_train = preprocessing(train_data).values
y_train = train_data[['Survived']].values#獲得測試x,y
x_test = preprocessing(test_data).values
y_test = test_datay[['Survived']].values# print("x_train.shape =", x_train.shape )
# print("x_test.shape =", x_test.shape )
# print("y_train.shape =", y_train.shape )
# print("y_test.shape =", y_test.shape )
這里重點講解一下對數據缺失部分的處理!
以Age字段,我們通過fillna(0)
,將Age字段中的NaN替換成0
然后通過 pd.isna
將空值點的地方記錄下來(添加“年齡是否缺失”作為輔助特征)
這里我把測試數據中的Age部分除了前兩個后面全設置為NULL
然后把dfresult['Age']
,dfresult['Age_null']
打印出來:
可以看看下面這個文章,我收到了很多啟發。
data是一個pandas.DataFrame數據對象,是從mysql讀取的數據。由于有的列在數據庫是int類型,而且有空值(Null),因此在從數據庫抽取到df對象后,pandas自動將int轉成float,比如10變成了10.0,15902912345變成了1.5902912345E10,Null變成了NaN。這種列由于存在NaN,因此不能用DataFrame.astype()方法轉成int類型。
我們的目的就是盡量讓pandas抽取的數據跟數據庫“看上去”一致。比如原來是int類型的,如果被替換成float了,那么需要轉換回去,原來是Null的,被pandas改成NaN了,需要替換成空字符串。由于pandas列的int類型不能為空,所以需統一轉換成字符串類型。
為了達到將列轉換成int類型原來的展現形式(可以是object類型,相當于str,此時10還是展示為10),且NaN轉換成空值這個目的,可以采取如下步驟:
1.生成新的df對象,保存data里為NaN的位置標記
2.將data需要處理的列,NaN值替換為float能允許的類型,如0,下面會用到
3.將該列轉換成int類型,此時10.0轉換成10,1.5902912345E10轉換成15902912345
4.將該列轉換成object類型,此時所有數值按str類型原樣保存
5.用NaN位置標記的df對象作為索引,替換原df對象中為0的值到空字符串
利用pandas.DataFrame.isna方法做替換(很棒的技巧)
進一步使用DataLoader和TensorDataset封裝成可以迭代的數據管道。
"""
進一步使用DataLoader和TensorDataset封裝成可以迭代的數據管道。
"""
dl_train = DataLoader(TensorDataset(torch.tensor(x_train).float(),torch.tensor(y_train).float()),shuffle = True, batch_size = 8)
dl_valid = DataLoader(TensorDataset(torch.tensor(x_test).float(),torch.tensor(y_test).float()),shuffle = False, batch_size = 8)# 測試數據管道
for features,labels in dl_train:print(features,labels)break
二,定義模型
使用Pytorch通常有三種方式構建模型:使用nn.Sequential按層順序構建模型,繼承nn.Module基類構建自定義模型,繼承nn.Module基類構建模型并輔助應用模型容器進行封裝。
此處選擇使用最簡單的nn.Sequential,按層順序模型。
"""
二,定義模型
"""def creat_net():net = nn.Sequential()net.add_module("linear1",nn.Linear(15,20))net.add_module("relu1",nn.ReLU())net.add_module("linear2", nn.Linear(20, 15))net.add_module("relu2", nn.ReLU())net.add_module("linear3", nn.Linear(15, 1))net.add_module("sigmoid", nn.Sigmoid())return netnet = creat_net()
#print(net)
三,訓練模型
"""
三,訓練模型
"""from sklearn.metrics import accuracy_scoreloss_func = nn.BCELoss()
optimizer = torch.optim.Adam(params=net.parameters(),lr=0.01)
metric_func = lambda y_pred,y_true:accuracy_score(y_true.data.numpy(),y_pred.data.numpy()>0.5)
#lambda表達式是起到一個函數速寫的作用。允許在代碼內嵌入一個函數的定義。
#accuracy_score是分類準確率分數是指所有分類正確的百分比。
metric_name = "accuracy"
#metric就是準確率epochs = 10
log_step_freq = 30
dfhistory = pd.DataFrame(columns = ["epoch","loss",metric_name,"val_loss","val_"+metric_name])for epoch in range(1,epochs+1):#開始訓練net.train()loss_sum = 0.0metric_sum = 0.0step = 1for step,(features,labels) in enumerate(dl_train,1):optimizer.zero_grad()#正向傳播predictions = net(features)loss = loss_func(predictions,labels)metric = metric_func(predictions,labels)#反向傳播loss.backward()optimizer.step()#打印batch日志loss_sum += loss.item()metric_sum += metric.item()if step%log_step_freq == 0:print(("[step = %d] loss: %.3f, " + metric_name + ": %.3f") %(step, loss_sum / step, metric_sum / step))#驗證循環net.eval()val_loss_sum = 0.0val_metric_sum = 0.0val_step = 1for val_step, (features, labels) in enumerate(dl_valid, 1):predictions = net(features)val_loss = loss_func(predictions, labels)val_metric = metric_func(predictions,labels)val_loss_sum += val_loss.item()val_metric_sum += val_metric.item()#記錄日志info = (epoch, loss_sum / step, metric_sum / step,val_loss_sum / val_step, val_metric_sum / val_step)dfhistory.loc[epoch - 1] = info# 打印epoch級別日志print(("\nEPOCH = %d, loss = %.3f," + metric_name + " = %.3f, val_loss = %.3f, " + "val_" + metric_name + " = %.3f")% info)
四,評估模型
"""
四,評估模型
"""
def plot_metric(dfhistory, metric):train_metrics = dfhistory[metric]val_metrics = dfhistory['val_'+metric]epochs = range(1, len(train_metrics) + 1)plt.plot(epochs, train_metrics, 'bo--')plt.plot(epochs, val_metrics, 'ro-')plt.title('Training and validation '+ metric)plt.xlabel("Epochs")plt.ylabel(metric)plt.legend(["train_"+metric, 'val_'+metric])plt.show()plot_metric(dfhistory,"loss")
plot_metric(dfhistory,"accuracy")
這里補充一下 dfhistory
dfhistory
來源于第三部分訓練模型中:
dfhistory = pd.DataFrame(columns = ["epoch","loss",metric_name,"val_loss","val_"+metric_name])
DataFrame是Python中Pandas庫中的一種數據結構,它類似excel,是一種二維表。
dfhistory的作用就是跟蹤數據,在訓練的過程中記錄每一步的訓練結果。
通過在dfhistory調取訓練數據和測試數據進行對比繪圖!
結果展示:
五,使用模型
"""
五,使用模型
"""
y_pred_probs = net(torch.tensor(x_test[0:10]).float()).data
y_pred = torch.where(y_pred_probs>0.5,torch.ones_like(y_pred_probs),torch.zeros_like(y_pred_probs))
六,保存模型
"""
# 六,保存模型
"""
#保存模型參數(推薦)
print(net.state_dict().keys())
# 保存模型參數
torch.save(net.state_dict(), "./data/net_parameter.pkl")
net_clone = creat_net()
net_clone.load_state_dict(torch.load("./data/net_parameter.pkl"))
net_clone.forward(torch.tensor(x_test[0:10]).float()).data#保存完整模型(不推薦)
torch.save(net, './data/net_model.pkl')
net_loaded = torch.load('./data/net_model.pkl')
net_loaded(torch.tensor(x_test[0:10]).float()).data
完整代碼:
import torch
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from torch import nn
from torch.utils.data import Dataset,DataLoader,TensorDataset"""
一,準備數據
"""#數據讀取
train_data = pd.read_csv('./data/titanic/train.csv')
test_data = pd.read_csv('./data/titanic/test.csv')
test_datay = pd.read_csv('./data/titanic/titanic.csv')
#print(train_data.head(10)) #打印訓練數據前十個train_data.info() #查看訓練數據有沒有未知的的
test_data.info() #查看測試數據有沒有未知的的# #查看各部分分布情況
#
# #幸存情況
# ax = train_data['Survived'].value_counts().plot(kind = 'bar',figsize = (12,8),fontsize =15,rot = 0)
# #value_counts是查詢有多少個不同值且每個不同值有多少個重復的
# ax.set_ylabel('Counts',fontsize = 15)
# ax.set_xlabel('Survived',fontsize = 15)
# plt.show()
#
# #年齡分布情況
# ax = train_data['Age'].plot(kind = 'hist',bins = 20,color = 'purple',figsize = (12,8),fontsize = 15)
# """
# hist方法常用的參數有以下幾個
# 1. bins,控制直方圖中的區間個數
# 2. color,指定柱子的填充色
# 3. edgecolor, 指定柱子邊框的顏色
# 4. density,指定柱子高度對應的信息,有數值和頻率兩種選擇
# 5. orientation,指定柱子的方向,有水平和垂直兩個方向
# 6. histtype,繪圖的類型
# """
# ax.set_ylabel('Frequency',fontsize = 15)
# ax.set_xlabel('Age',fontsize = 15)
# plt.show()
#
# #年齡和label的相關性
# ax = train_data.query('Survived == 0')['Age'].plot(kind = 'density',figsize = (12,8),fontsize = 15)
# #使用python.query()函數對數據框進行(挑選行)的操作
# train_data.query('Survived == 1')['Age'].plot(kind = 'density',figsize = (12,8),fontsize = 15)
# ax.legend(['Survived ==0','Survived ==1'],fontsize = 12)
# #plt.legend()函數主要的作用就是給圖加上圖例,plt.legend([x,y,z])里面的參數使用的是list的的形式將圖表的的名稱喂給這和函數。
# ax.set_ylabel('Density',fontsize = 15)
# ax.set_xlabel('Age',fontsize = 15)
# plt.show()
#
"""
數據預處理
"""
def preprocessing(dfdata):dfresult = pd.DataFrame() #存儲結果#DataFrame是Python中Pandas庫中的一種數據結構,它類似excel,是一種二維表。#Pclass處理dfPclass = pd.get_dummies(dfdata['Pclass'])#對Pclass進行get_dummies,將該特征離散化dfPclass.columns = ['Pclass_'+str(x) for x in dfPclass.columns]dfresult = pd.concat([dfresult,dfPclass],axis=1)#concat函數是在pandas底下的方法,可以將數據根據不同的軸作簡單的融合,axis: 需要合并鏈接的軸,0是行,1是列#SexdfSex = pd.get_dummies(dfdata['Sex'])dfresult = pd.concat([dfresult, dfSex], axis=1)#Agedfresult['Age'] = dfdata['Age'].fillna(0)dfresult['Age_null'] = pd.isna(dfdata['Age']).astype('int32')#pandas.isna(obj)檢測array-like對象的缺失值# SibSp,Parch,Faredfresult['SibSp'] = dfdata['SibSp']dfresult['Parch'] = dfdata['Parch']dfresult['Fare'] = dfdata['Fare']# Carbindfresult['Cabin_null'] = pd.isna(dfdata['Cabin']).astype('int32')# EmbarkeddfEmbarked = pd.get_dummies(dfdata['Embarked'], dummy_na=True)dfEmbarked.columns = ['Embarked_' + str(x) for x in dfEmbarked.columns]#DataFrame.columns屬性以返回給定 DataFrame 的列標簽。dfresult = pd.concat([dfresult, dfEmbarked], axis=1)return dfresult#獲得訓練x,y
x_train = preprocessing(train_data).values
y_train = train_data[['Survived']].values#獲得測試x,y
x_test = preprocessing(test_data).values
y_test = test_datay[['Survived']].values# print("x_train.shape =", x_train.shape )
# print("x_test.shape =", x_test.shape )
# print("y_train.shape =", y_train.shape )
# print("y_test.shape =", y_test.shape )
#
"""
進一步使用DataLoader和TensorDataset封裝成可以迭代的數據管道。
"""
dl_train = DataLoader(TensorDataset(torch.tensor(x_train).float(),torch.tensor(y_train).float()),shuffle = True, batch_size = 8)
dl_valid = DataLoader(TensorDataset(torch.tensor(x_test).float(),torch.tensor(y_test).float()),shuffle = False, batch_size = 8)
#
# # #測試數據管道
# for features,labels in dl_valid:
# print(features,labels)
# break
#
"""
二,定義模型
"""def creat_net():net = nn.Sequential()net.add_module("linear1",nn.Linear(15,20))net.add_module("relu1",nn.ReLU())net.add_module("linear2", nn.Linear(20, 15))net.add_module("relu2", nn.ReLU())net.add_module("linear3", nn.Linear(15, 1))net.add_module("sigmoid", nn.Sigmoid())return netnet = creat_net()
#print(net)"""
三,訓練模型
"""from sklearn.metrics import accuracy_scoreloss_func = nn.BCELoss()
optimizer = torch.optim.Adam(params=net.parameters(),lr=0.01)
metric_func = lambda y_pred,y_true:accuracy_score(y_true.data.numpy(),y_pred.data.numpy()>0.5)
#lambda表達式是起到一個函數速寫的作用。允許在代碼內嵌入一個函數的定義。
#accuracy_score是分類準確率分數是指所有分類正確的百分比。
metric_name = "accuracy"
#metric就是準確率epochs = 10
log_step_freq = 30
dfhistory = pd.DataFrame(columns = ["epoch","loss",metric_name,"val_loss","val_"+metric_name])for epoch in range(1,epochs+1):#開始訓練net.train()loss_sum = 0.0metric_sum = 0.0step = 1for step,(features,labels) in enumerate(dl_train,1):optimizer.zero_grad()#正向傳播predictions = net(features)loss = loss_func(predictions,labels)metric = metric_func(predictions,labels)#反向傳播loss.backward()optimizer.step()#打印batch日志loss_sum += loss.item()metric_sum += metric.item()if step%log_step_freq == 0:print(("[step = %d] loss: %.3f, " + metric_name + ": %.3f") %(step, loss_sum / step, metric_sum / step))#驗證循環net.eval()val_loss_sum = 0.0val_metric_sum = 0.0val_step = 1for val_step, (features, labels) in enumerate(dl_valid, 1):predictions = net(features)val_loss = loss_func(predictions, labels)val_metric = metric_func(predictions,labels)val_loss_sum += val_loss.item()val_metric_sum += val_metric.item()#記錄日志info = (epoch, loss_sum / step, metric_sum / step,val_loss_sum / val_step, val_metric_sum / val_step)dfhistory.loc[epoch - 1] = info# 打印epoch級別日志print(("\nEPOCH = %d, loss = %.3f," + metric_name + " = %.3f, val_loss = %.3f, " + "val_" + metric_name + " = %.3f")% info)"""
四,評估模型
"""
def plot_metric(dfhistory, metric):train_metrics = dfhistory[metric]val_metrics = dfhistory['val_'+metric]epochs = range(1, len(train_metrics) + 1)plt.plot(epochs, train_metrics, 'bo--')plt.plot(epochs, val_metrics, 'ro-')plt.title('Training and validation '+ metric)plt.xlabel("Epochs")plt.ylabel(metric)plt.legend(["train_"+metric, 'val_'+metric])plt.show()plot_metric(dfhistory,"loss")
plot_metric(dfhistory,"accuracy")"""
五,使用模型
"""
y_pred_probs = net(torch.tensor(x_test[0:10]).float()).data
y_pred = torch.where(y_pred_probs>0.5,torch.ones_like(y_pred_probs),torch.zeros_like(y_pred_probs))# """
# # 六,保存模型
# """
# #保存模型參數(推薦)
# print(net.state_dict().keys())
# # 保存模型參數
# torch.save(net.state_dict(), "./data/net_parameter.pkl")
# net_clone = creat_net()
# net_clone.load_state_dict(torch.load("./data/net_parameter.pkl"))
# net_clone.forward(torch.tensor(x_test[0:10]).float()).data
#
# #保存完整模型(不推薦)
# torch.save(net, './data/net_model.pkl')
# net_loaded = torch.load('./data/net_model.pkl')
# net_loaded(torch.tensor(x_test[0:10]).float()).data