原文鏈接:http://tecdat.cn/?p=8522
分類問題屬於機器學習問題的類別,其中給定一組功能,任務是預測離散值。分類問題的一些常見示例是,預測腫瘤是否為癌症,或者學生是否可能通過考試。
在本文中,鑒於銀行客戶的某些特征,我們將預測客戶在6個月后是否可能離開銀行。客戶離開組織的現象也稱為客戶流失。因此,我們的任務是根據各種客戶特征預測客戶流失。
$ pip install pytorch
數據集
讓我們將所需的庫和數據集導入到我們的Python應用程序中:
import torch
import torch.nn as nn
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
我們可以使用庫的read_csv()
方法pandas
來導入包含我們的數據集的CSV文件。
dataset = pd.read_csv(r'E:Datasets\customer_data.csv')
讓我們打印數據集 :
dataset.shape
輸出:
(10000, 14)
輸出顯示該數據集具有1萬條記錄和14列。
我們可以使用head()
pandas數據框的方法來打印數據集的前五行。
dataset.head()
輸出:
您可以在我們的數據集中看到14列。根據前13列,我們的任務是預測第14列的值,即Exited
。
探索性數據分析
讓我們對數據集進行一些探索性數據分析。我們將首先預測6個月后實際離開銀行並使用餅圖進行可視化的客戶比例。
讓我們首先增加圖形的默認繪圖大小:
fig_size = plt.rcParams["figure.figsize"]
fig_size[0] = 10
fig_size[1] = 8
plt.rcParams["figure.figsize"] = fig_size
以下腳本繪制該Exited
列的餅圖。
dataset.Exited.value_counts().plot(kind='pie', autopct='%1.0f%%', colors=['skyblue', 'orange'], explode=(0.05, 0.05))
輸出:
輸出顯示,在我們的數據集中,有20%的客戶離開了銀行。這里1代表客戶離開銀行的情況,0代表客戶沒有離開銀行的情況。
讓我們繪制數據集中所有地理位置的客戶數量:
輸出:
輸出顯示,幾乎一半的客戶來自法國,而西班牙和德國的客戶比例分別為25%。
現在,讓我們繪制來自每個唯一地理位置的客戶數量以及客戶流失信息。我們可以使用庫中的countplot()
函數seaborn
來執行此操作。
輸出:
輸出顯示,盡管法國客戶總數是西班牙和德國客戶總數的兩倍,但法國和德國客戶離開銀行的客戶比例是相同的。同樣,德國和西班牙客戶的總數相同,但是離開銀行的德國客戶數量是西班牙客戶的兩倍,這表明德國客戶在6個月后離開銀行的可能性更大。
數據預處理
在訓練PyTorch模型之前,我們需要預處理數據。如果查看數據集,您將看到它具有兩種類型的列:數值列和分類列。數字列包含數字信息。CreditScore
,Balance
,Age
等。類似地,Geography
和Gender
是分類列,因為它們含有分類信息,如客戶的位置和性別。有幾列可以視為數字列和類別列。例如,該HasCrCard
列的值可以為1或0。但是,那HasCrCard
列包含有關客戶是否擁有信用卡的信息。 但是,這完全取決於數據集的領域知識。
讓我們再次輸出數據集中的所有列,並找出哪些列可以視為數字列,哪些列應該視為類別列。columns
數據框的屬性顯示所有列名稱:
輸出:
Index(['RowNumber', 'CustomerId', 'Surname', 'CreditScore', 'Geography',
'Gender', 'Age', 'Tenure', 'Balance', 'NumOfProducts', 'HasCrCard',
'IsActiveMember', 'EstimatedSalary', 'Exited'],
dtype='object')
從我們的數據列,我們將不使用的RowNumber
,CustomerId
以及Surname
列,因為這些列的值是完全隨機的,並與輸出無關。例如,客戶的姓氏對客戶是否離開銀行沒有影響。其中列的其余部分,Geography
,Gender
,HasCrCard
,和IsActiveMember
列可以被視為類別列。讓我們創建這些列的列表:
除該列外,其余所有
列均可視為數字列。
numerical_columns = ['CreditScore', 'Age', 'Tenure', 'Balance', 'NumOfProducts', 'EstimatedSalary']
最后,輸出(Exited
列中的值)存儲在outputs
變量中。
我們已經創建了分類,數字和輸出列的列表。但是,目前,分類列的類型不是分類的。您可以使用以下腳本檢查數據集中所有列的類型:
輸出:
RowNumber int64
CustomerId int64
Surname object
CreditScore int64
Geography object
Gender object
Age int64
Tenure int64
Balance float64
NumOfProducts int64
HasCrCard int64
IsActiveMember int64
EstimatedSalary float64
Exited int64
dtype: object
您可以看到Geography
and Gender
列的類型是object,HasCrCard
and IsActive
列的類型是int64。我們需要將分類列的類型轉換為category
。我們可以使用astype()
函數來做到這一點,如下所示:
現在,如果再次繪制數據集中各列的類型,您將看到以下結果:
輸出量
RowNumber int64
CustomerId int64
Surname object
CreditScore int64
Geography category
Gender category
Age int64
Tenure int64
Balance float64
NumOfProducts int64
HasCrCard category
IsActiveMember category
EstimatedSalary float64
Exited int64
dtype: object
現在讓我們查看Geography
列中的所有類別:
輸出:
Index(['France', 'Germany', 'Spain'], dtype='object')
當您將列的數據類型更改為類別時,該列中的每個類別都會分配一個唯一的代碼。例如,讓我們繪制列的前五行,Geography
並打印前五行的代碼值:
輸出:
0 France
1 Spain
2 France
3 France
4 Spain
Name: Geography, dtype: category
Categories (3, object): [France, Germany, Spain]
以下腳本在該列的前五行中繪制了值的代碼Geography
:
輸出:
0 0
1 2
2 0
3 0
4 2
dtype: int8
輸出顯示法國已編碼為0,西班牙已編碼為2。
將分類列與數字列分開的基本目的是,可以將數字列中的值直接輸入到神經網絡中。但是,必須首先將類別列的值轉換為數字類型。分類列中的值的編碼部分地解決了分類列的數值轉換的任務。
由於我們將使用PyTorch進行模型訓練,因此需要將分類列和數值列轉換為張量。
首先讓我們將分類列轉換為張量。在PyTorch中,可以通過numpy數組創建張量。我們將首先將四個分類列中的數據轉換為numpy數組,然后將所有列水平堆疊,如以下腳本所示:
geo = dataset['Geography'].cat.codes.values
...
上面的腳本打印出分類列中前十條記錄,這些記錄是水平堆疊的。輸出如下:
輸出:
array([[0, 0, 1, 1],
[2, 0, 0, 1],
[0, 0, 1, 0],
[0, 0, 0, 0],
[2, 0, 1, 1],
[2, 1, 1, 0],
[0, 1, 1, 1],
[1, 0, 1, 0],
[0, 1, 0, 1],
[0, 1, 1, 1]], dtype=int8)
現在要從上述numpy數組創建張量,您只需將數組傳遞給模塊的tensor
類torch
。
輸出:
tensor([[0, 0, 1, 1],
[2, 0, 0, 1],
[0, 0, 1, 0],
[0, 0, 0, 0],
[2, 0, 1, 1],
[2, 1, 1, 0],
[0, 1, 1, 1],
[1, 0, 1, 0],
[0, 1, 0, 1],
[0, 1, 1, 1]])
在輸出中,您可以看到類別數據的numpy數組現在已轉換為tensor
對象。
同樣,我們可以將數值列轉換為張量:
numerical_data = np.stack([dataset[col].values for col in numerical_columns], 1)
...
輸出:
tensor([[6.1900e+02, 4.2000e+01, 2.0000e+00, 0.0000e+00, 1.0000e+00, 1.0135e+05],
[6.0800e+02, 4.1000e+01, 1.0000e+00, 8.3808e+04, 1.0000e+00, 1.1254e+05],
[5.0200e+02, 4.2000e+01, 8.0000e+00, 1.5966e+05, 3.0000e+00, 1.1393e+05],
[6.9900e+02, 3.9000e+01, 1.0000e+00, 0.0000e+00, 2.0000e+00, 9.3827e+04],
[8.5000e+02, 4.3000e+01, 2.0000e+00, 1.2551e+05, 1.0000e+00, 7.9084e+04]])
在輸出中,您可以看到前五行,其中包含我們數據集中六個數字列的值。
最后一步是將輸出的numpy數組轉換為tensor
對象。
...
輸出:
tensor([1, 0, 1, 0, 0])
現在,讓我們繪制分類數據,數值數據和相應輸出的形狀:
...
輸出:
torch.Size([10000, 4])
torch.Size([10000, 6])
torch.Size([10000])
在訓練模型之前,有一個非常重要的步驟。我們將分類列轉換為數值,其中唯一值由單個整數表示。例如,在該Geography
列中,我們看到法國用0表示,德國用1表示。我們可以使用這些值來訓練我們的模型。但是,更好的方法是以N維向量的形式表示分類列中的值,而不是單個整數。
我們需要為所有分類列定義嵌入尺寸(矢量尺寸)。關於維數沒有嚴格的規定。定義列的嵌入大小的一個好的經驗法則是將列中唯一值的數量除以2(但不超過50)。例如,對於該Geography
列,唯一值的數量為3。該Geography
列的相應嵌入大小將為3/2 = 1.5 = 2(四舍五入)。
以下腳本創建一個元組,其中包含所有類別列的唯一值數量和維度大小:
categorical_column_sizes = [len(dataset[column].cat.categories) for column in categorical_columns]
...
輸出:
[(3, 2), (2, 1), (2, 1), (2, 1)]
使用訓練數據對監督型深度學習模型(例如我們在本文中開發的模型)進行訓練,並在測試數據集上評估模型的性能。因此,我們需要將數據集分為訓練集和測試集,如以下腳本所示:
total_records = 10000
....
我們的數據集中有1萬條記錄,其中80%的記錄(即8000條記錄)將用於訓練模型,而其余20%的記錄將用於評估模型的性能。注意,在上面的腳本中,分類和數字數據以及輸出已分為訓練集和測試集。
為了驗證我們已正確地將數據分為訓練和測試集:
print(len(categorical_train_data))
print(len(numerical_train_data))
print(len(train_outputs))
print(len(categorical_test_data))
print(len(numerical_test_data))
print(len(test_outputs))
輸出:
8000
8000
8000
2000
2000
2000
創建預測模型
我們將數據分為訓練集和測試集,現在是時候定義訓練模型了。為此,我們可以定義一個名為的類Model
,該類將用於訓練模型。看下面的腳本:
class Model(nn.Module):
def __init__(self, embedding_size, num_numerical_cols, output_size, layers, p=0.4):
super().__init__()
self.all_embeddings = nn.ModuleList([nn.Embedding(ni, nf) for ni, nf in embedding_size])
self.embedding_dropout = nn.Dropout(p)
self.batch_norm_num = nn.BatchNorm1d(num_numerical_cols)
...
return x
接下來,要查找輸入層的大小,將類別列和數字列的數量加在一起並存儲在input_size
變量中。之后,for
循環迭代,並將相應的層添加到all_layers
列表中。添加的層是:
Linear
:用於計算輸入和權重矩陣之間的點積ReLu
:用作激活功能BatchNorm1d
:用於對數字列應用批量歸一化Dropout
:用於避免過度擬合
在后for
循環中,輸出層被附加到的層的列表。由於我們希望神經網絡中的所有層都按順序執行,因此將層列表傳遞給nn.Sequential
該類。
接下來,在該forward
方法中,將類別列和數字列都作為輸入傳遞。類別列的嵌入在以下幾行中進行。
embeddings = []
...
數字列的批量歸一化可通過以下腳本應用:
x_numerical = self.batch_norm_num(x_numerical)
最后,將嵌入的分類列x
和數字列x_numerical
連接在一起,並傳遞給sequence layers
。
訓練模型
要訓練模型,首先我們必須創建Model
在上一節中定義的類的對象。
...
您可以看到我們傳遞了分類列的嵌入大小,數字列的數量,輸出大小(在我們的例子中為2)以及隱藏層中的神經元。您可以看到我們有三個分別具有200、100和50個神經元的隱藏層。您可以根據需要選擇其他尺寸。
讓我們打印模型並查看:
print(model)
輸出:
Model(
(all_embeddings): ModuleList(
...
)
)
您可以看到,在第一線性層中,in_features
變量的值為11,因為我們有6個數字列,並且類別列的嵌入維數之和為5,因此6 + 5 = 11。out_features
的值為2,因為我們只有2個可能的輸出。
在實際訓練模型之前,我們需要定義損失函數和將用於訓練模型的優化器。
以下腳本定義了損失函數和優化器:
loss_function = nn.CrossEntropyLoss()
...
現在,我們擁有訓練模型所需的一切。以下腳本訓練模型:
epochs = 300
aggregated_losses = []
for i in range(epochs):
...
print(f'epoch: {i:3} loss: {single_loss.item():10.10f}')
神經元元數設置為300,這意味着要訓練模型,完整的數據集將使用300次。for
為300倍和在每次迭代期間循環的執行方式,損失是使用損耗函數來計算。每次迭代過程中的損失將添加到aggregated_loss
列表中。要更新權重,將backward()
調用single_loss
對象的功能。最后,函數的step()
方法optimizer
更新漸變。
上面腳本的輸出如下:
epoch: 1 loss: 0.71847951
epoch: 26 loss: 0.57145703
epoch: 51 loss: 0.48110831
epoch: 76 loss: 0.42529839
epoch: 101 loss: 0.39972275
epoch: 126 loss: 0.37837571
epoch: 151 loss: 0.37133673
epoch: 176 loss: 0.36773482
epoch: 201 loss: 0.36305946
epoch: 226 loss: 0.36079505
epoch: 251 loss: 0.35350436
epoch: 276 loss: 0.35540250
epoch: 300 loss: 0.3465710580
以下腳本繪制了各個時期的損失:
plt.plot(range(epochs), aggregated_losses)
plt.ylabel('Loss')
plt.xlabel('epoch');
輸出:
輸出顯示,最初損耗迅速降低。在第250個時代之后,損失幾乎沒有減少。
做出預測
最后一步是對測試數據進行預測。為此,我們只需要將categorical_test_data
和傳遞numerical_test_data
給model
該類。然后可以將返回的值與實際測試輸出值進行比較。以下腳本對測試類進行預測,並打印測試數據的交叉熵損失。
with torch.no_grad():
...
輸出:
Loss: 0.36855841
測試集上的損失為0.3685,比訓練集上獲得的0.3465略多,這表明我們的模型有些過擬合。
由於我們指定輸出層將包含2個神經元,因此每個預測將包含2個值。例如,前5個預測值如下所示:
print(y_val[:5])
輸出:
tensor([[ 1.2045, -1.3857],
[ 1.3911, -1.5957],
[ 1.2781, -1.3598],
[ 0.6261, -0.5429],
[ 2.5430, -1.9991]])
這種預測的思想是,如果實際輸出為0,則索引0處的值應大於索引1處的值,反之亦然。我們可以使用以下腳本檢索列表中最大值的索引:
y_val = np.argmax(y_val, axis=1)
輸出:
現在讓我們再次打印y_val
列表的前五個值:
print(y_val[:5])
輸出:
tensor([0, 0, 0, 0, 0])
由於在最初預測的輸出列表中,對於前五個記錄,零索引處的值大於第一索引處的值,因此可以在已處理輸出的前五行中看到0。
最后,我們可以使用confusion_matrix
,accuracy_score
以及classification_report
類從sklearn.metrics
模塊找到了准確度,精密度和召回值測試集,與混淆矩陣一起。
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score
print(confusion_matrix(test_outputs,y_val))
print(classification_report(test_outputs,y_val))
print(accuracy_score(test_outputs, y_val))
輸出:
[[1527 83]
[ 224 166]]
precision recall f1-score support
0 0.87 0.95 0.91 1610
1 0.67 0.43 0.52 390
micro avg 0.85 0.85 0.85 2000
macro avg 0.77 0.69 0.71 2000
weighted avg 0.83 0.85 0.83 2000
0.8465
輸出結果表明,我們的模型達到了84.65%的精度,考慮到我們隨機選擇神經網絡模型的所有參數這一事實,這非常令人印象深刻。我建議您嘗試更改模型參數,例如訓練/測試比例,隱藏層的數量和大小等,以查看是否可以獲得更好的結果。
結論
PyTorch是Facebook開發的常用深度學習庫,可用於各種任務,例如分類,回歸和聚類。本文介紹了如何使用PyTorch庫對表格數據進行分類。