1. import os
  2. import datetime
  3. #打印时间
  4. def printbar():
  5. nowtime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  6. print("\n"+"=========="*8 + "%s"%nowtime)
  7. #mac系统上pytorch和matplotlib在jupyter中同时跑需要更改环境变量
  8. os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"

准备数据

titanic数据集的目标是根据乘客信息预测他们在Titanic号撞击冰山沉没后能否生存。
结构化数据一般会使用Pandas中的DataFrame进行预处理。

  1. import numpy as np
  2. import pandas as pd
  3. import matplotlib.pyplot as plt
  4. import torch
  5. from torch import nn
  6. from torch.utils.data import Dataset,DataLoader,TensorDataset
  7. dftrain_raw = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/20天吃掉那只Pytorch/data/titanic/train.csv')
  8. dftest_raw = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/20天吃掉那只Pytorch/data/titanic/test.csv')
  9. dftrain_raw.head(10)

image.png
字段说明:

  • 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】

label分布情况

  1. dftrain_raw['Survived'].value_counts()

image.png

  1. %matplotlib inline
  2. %config InlineBackend.figure_format = 'png'
  3. ax = dftrain_raw['Survived'].value_counts().plot(kind = 'bar',
  4. figsize = (12,8),fontsize=15,rot = 0)
  5. ax.set_ylabel('Counts',fontsize = 15)
  6. ax.set_xlabel('Survived',fontsize = 15)
  7. plt.show()

image.png

年龄分布情况

  1. dftrain_raw['Age']

image.png

  1. %matplotlib inline
  2. %config InlineBackend.figure_format = 'png'
  3. ax = dftrain_raw['Age'].plot(kind = 'hist',bins = 20,color= 'purple',
  4. figsize = (12,8),fontsize=15)
  5. ax.set_ylabel('Frequency',fontsize = 15)
  6. ax.set_xlabel('Age',fontsize = 15)
  7. plt.show()

image.png

年龄和label的相关性

  1. %matplotlib inline
  2. %config InlineBackend.figure_format = 'png'
  3. ax = dftrain_raw.query('Survived == 0')['Age'].plot(kind = 'density',
  4. figsize = (12,8),fontsize=15)
  5. dftrain_raw.query('Survived == 1')['Age'].plot(kind = 'density',
  6. figsize = (12,8),fontsize=15)
  7. ax.legend(['Survived==0','Survived==1'],fontsize = 12)
  8. ax.set_ylabel('Density',fontsize = 15)
  9. ax.set_xlabel('Age',fontsize = 15)
  10. plt.show()

image.png

数据预处理

  1. def preprocessing(dfdata):
  2. dfresult= pd.DataFrame()
  3. #Pclass
  4. dfPclass = pd.get_dummies(dfdata['Pclass']) # 独热编码
  5. dfPclass.columns = ['Pclass_' +str(x) for x in dfPclass.columns ]
  6. dfresult = pd.concat([dfresult,dfPclass],axis = 1)
  7. #Sex
  8. dfSex = pd.get_dummies(dfdata['Sex'])
  9. dfresult = pd.concat([dfresult,dfSex],axis = 1)
  10. #Age
  11. dfresult['Age'] = dfdata['Age'].fillna(0)
  12. dfresult['Age_null'] = pd.isna(dfdata['Age']).astype('int32')
  13. #SibSp,Parch,Fare
  14. dfresult['SibSp'] = dfdata['SibSp']
  15. dfresult['Parch'] = dfdata['Parch']
  16. dfresult['Fare'] = dfdata['Fare']
  17. #Carbin
  18. dfresult['Cabin_null'] = pd.isna(dfdata['Cabin']).astype('int32')
  19. #Embarked
  20. dfEmbarked = pd.get_dummies(dfdata['Embarked'],dummy_na=True)
  21. dfEmbarked.columns = ['Embarked_' + str(x) for x in dfEmbarked.columns]
  22. dfresult = pd.concat([dfresult,dfEmbarked],axis = 1)
  23. return(dfresult)
  24. x_train = preprocessing(dftrain_raw).values
  25. y_train = dftrain_raw[['Survived']].values
  26. x_test = preprocessing(dftest_raw).values
  27. y_test = dftest_raw[['Survived']].values
  28. print("x_train.shape =", x_train.shape )
  29. print("x_test.shape =", x_test.shape )
  30. print("y_train.shape =", y_train.shape )
  31. print("y_test.shape =", y_test.shape )

image.png

进一步使用DataLoader和TensorDataset封装成可以迭代的数据管道。

  1. dl_train = DataLoader(TensorDataset(torch.tensor(x_train).float(),torch.tensor(y_train).float()),
  2. shuffle = True, batch_size = 8)
  3. dl_valid = DataLoader(TensorDataset(torch.tensor(x_test).float(),torch.tensor(y_test).float()),
  4. shuffle = False, batch_size = 8)
  1. # 测试数据管道
  2. for features,labels in dl_train:
  3. print(features,labels)
  4. break

image.png


定义模型

使用Pytorch通常有三种方式构建模型:
使用nn.Sequential按层顺序构建模型 ;
继承nn.Module基类构建自定义模型;
继承nn.Module基类构建模型并辅助应用模型容器进行封装。
此处选择使用最简单的nn.Sequential,按层顺序模型。

  1. def create_net():
  2. net = nn.Sequential()
  3. net.add_module("linear1",nn.Linear(15,20))
  4. net.add_module("relu1",nn.ReLU())
  5. net.add_module("linear2",nn.Linear(20,15))
  6. net.add_module("relu2",nn.ReLU())
  7. net.add_module("linear3",nn.Linear(15,1))
  8. net.add_module("sigmoid",nn.Sigmoid())
  9. return net
  10. net = create_net()
  11. print(net)

image.png


训练模型

Pytorch通常需要用户编写自定义训练循环,训练循环的代码风格因人而异。
有3类典型的训练循环代码风格:
脚本形式训练循环;
函数形式训练循环;
类形式训练循环。
此处介绍一种较通用的脚本形式。

  1. from sklearn.metrics import accuracy_score
  2. loss_func = nn.BCELoss()
  3. optimizer = torch.optim.Adam(params=net.parameters(),lr = 0.01)
  4. metric_func = lambda y_pred,y_true: accuracy_score(y_true.data.numpy(),y_pred.data.numpy()>0.5)
  5. metric_name = "accuracy"
  1. epochs = 10
  2. log_step_freq = 30
  3. dfhistory = pd.DataFrame(columns = ["epoch","loss",metric_name,"val_loss","val_"+metric_name])
  4. print("Start Training...")
  5. nowtime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  6. print("=========="*8 + "%s"%nowtime)
  7. for epoch in range(1,epochs+1):
  8. # 1,训练循环-------------------------------------------------
  9. net.train()
  10. loss_sum = 0.0
  11. metric_sum = 0.0
  12. step = 1
  13. for step, (features,labels) in enumerate(dl_train, 1):
  14. # 梯度清零
  15. optimizer.zero_grad()
  16. # 正向传播求损失
  17. predictions = net(features)
  18. loss = loss_func(predictions,labels)
  19. metric = metric_func(predictions,labels)
  20. # 反向传播求梯度
  21. loss.backward()
  22. optimizer.step()
  23. # 打印batch级别日志
  24. loss_sum += loss.item()
  25. metric_sum += metric.item()
  26. if step%log_step_freq == 0:
  27. print(("[step = %d] loss: %.3f, "+metric_name+": %.3f") %
  28. (step, loss_sum/step, metric_sum/step))
  29. # 2,验证循环-------------------------------------------------
  30. net.eval()
  31. val_loss_sum = 0.0
  32. val_metric_sum = 0.0
  33. val_step = 1
  34. for val_step, (features,labels) in enumerate(dl_valid, 1):
  35. # 关闭梯度计算
  36. with torch.no_grad():
  37. predictions = net(features)
  38. val_loss = loss_func(predictions,labels)
  39. val_metric = metric_func(predictions,labels)
  40. val_loss_sum += val_loss.item()
  41. val_metric_sum += val_metric.item()
  42. # 3,记录日志-------------------------------------------------
  43. info = (epoch, loss_sum/step, metric_sum/step,
  44. val_loss_sum/val_step, val_metric_sum/val_step)
  45. dfhistory.loc[epoch-1] = info
  46. # 打印epoch级别日志
  47. print(("\nEPOCH = %d, loss = %.3f,"+ metric_name + \
  48. " = %.3f, val_loss = %.3f, "+"val_"+ metric_name+" = %.3f")
  49. %info)
  50. nowtime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
  51. print("\n"+"=========="*8 + "%s"%nowtime)
  52. print('Finished Training...')

image.png


评估模型

我们首先评估一下模型在训练集和验证集上的效果。

dfhistory

image.png

%matplotlib inline
%config InlineBackend.figure_format = 'svg'

import matplotlib.pyplot as plt

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")

image.png

plot_metric(dfhistory,"accuracy")

image.png


使用模型

#预测概率
y_pred_probs = net(torch.tensor(x_test[0:10]).float()).data
y_pred_probs

image.png

#预测类别
y_pred = torch.where(y_pred_probs>0.5,
        torch.ones_like(y_pred_probs),torch.zeros_like(y_pred_probs))
y_pred

image.png


保存模型

Pytorch 有两种保存模型的方式,都是通过调用pickle序列化方法实现的。
第一种方法只保存模型参数;
第二种方法保存完整模型。
推荐使用第一种,第二种方法可能在切换设备和目录的时候出现各种问题。
1、保存模型参数(推荐)

print(net.state_dict().keys())

image.png

# 保存模型参数

torch.save(net.state_dict(), "/content/drive/MyDrive/Colab Notebooks/20天吃掉那只Pytorch/一、Pytorch的建模流程/model/1_1_net_parameter.pkl")

net_clone = create_net()
net_clone.load_state_dict(torch.load("/content/drive/MyDrive/Colab Notebooks/20天吃掉那只Pytorch/一、Pytorch的建模流程/model/1_1_net_parameter.pkl"))

net_clone.forward(torch.tensor(x_test[0:10]).float()).data

image.png

2、保存完整模型(不推荐)


torch.save(net, '/content/drive/MyDrive/Colab Notebooks/20天吃掉那只Pytorch/一、Pytorch的建模流程/model/1_1_net_model.pkl')
net_loaded = torch.load('/content/drive/MyDrive/Colab Notebooks/20天吃掉那只Pytorch/一、Pytorch的建模流程/model/1_1_net_model.pkl')
net_loaded(torch.tensor(x_test[0:10]).float()).data

image.png