sklearn 邏輯迴歸
Alex一晚上都沒睡好覺,被IEEE-CIS Fraud Detection折磨的死去活來,不管怎麼調參,用什麼樣的策略,評分就是上不去,這可不行,什麼時候認過輸,生死看淡,不服就幹!結果:
第二天,Alex打算去工作室問問Bachelor,這傢伙肯定還藏了不少東西沒說,結果Bachelor不知道是因爲心虛還是咋的,竟然沒來,工作室只有一個膚白貌美大長腿的實習生MM在,聽Bachelor說這個是實習生是個高手,也是MIT過來的,而且人家主修的就是人工智能這方面,Alex決定厚着臉皮去問問。
Alex:“Hi Coco, Do you know the credit card fraud detection we received recently?”
Coco:“你說啥?”
Alex:“我靠,你會說中文。”
Coco:“相較於你的英文,我還是聽懂了你的中文。”
ALex:“。。。。。。”
ALex:“我說你知道咱們工作室最近接的那個信用卡欺詐檢測的項目麼?”
Coco:“知道啊,那個項目就是我接的,只不過現在是Bachelor負責,我跟進。”
Alex:“那太好了,Bachelor昨天給我講了講,但是我回去自己做的時候準確率出奇的低。”
Coco:“你跟我說說Bachelor講了啥,我幫你看看吧。”
於是Alex就把昨天的邏輯迴歸又說了一遍…
Coco:“整體看來沒問題,只不過Bachelor只講了邏輯迴歸的原理,應用在這個項目上還需要一些處理。”
於是,Alex一邊聽着Coco的講解,一邊擦口水…
Coco倒是沒多想,專心的給Alex講解:
這樣吧,我就給你捋一下工作室目前是怎麼做的。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
train_identity = pd.read_csv('/kaggle/input/ieee-fraud-detection/train_identity.csv')
train_transaction = pd.read_csv('/kaggle/input/ieee-fraud-detection/train_transaction.csv')
data = pd.merge(train_transaction, train_identity, on="TransactionID", how="left")
data.drop("TransactionID", axis=1, inplace=True) # TransactionID說實話沒啥用
data.drop("TransactionDT", axis=1, inplace=True) # TransactionDT類似於時間戳,也沒啥用
del train_identity, train_transaction
上來還是先把兩張表的數據讀進內存裏,然後通過TransactionID連接起來製作一個train表,之後既可以把原來的兩張表刪了,不然會佔用內存。
pd.set_option('display.max_columns',None) # 設置pandas顯示列不限制數量
pd.set_option('display.max_rows',None) # 設置pandas顯示行不限制數量
data.head()
數據預處理
首先還是去掉缺失值超過50%的特徵:
na_count = data.isnull().sum().sort_values(ascending=False)
na_rate = na_count / len(data)
na_data = pd.concat([na_count,na_rate],axis=1,keys=['count','ratio'])
data.drop(na_data[na_data['ratio'] > 0.3].index, axis=1, inplace=True)
將所有的離散型數據對應爲數值型數據,缺失值用均值填充:
for col in data.columns:
if data[col].dtypes == "object":
data[col], uniques = pd.factorize(data[col])
data[col].fillna(data[col].mean(), inplace=True)
data.head()
其實在這個項目裏,重要的是數據預處理而不是邏輯迴歸的算法。
拿到數據之後,先簡單看一下,isFraud表示有沒有沒詐騙,也就是我們的y值,0表示沒有被詐騙,1表示被詐騙了,但是在日常生活中,雖然詐騙比較多,但還是正常樣本佔大多數,所以我們先看一下正負樣本的統計。
count_isFraud = pd.value_counts(data["isFraud"], sort=True)
count_isFraud.plot(kind="bar") # 條形圖
plt.title("Fraud statistics")
plt.xlabel("isFraud")
plt.ylabel("Frequency")
X = data.iloc[:, data.columns != "isFraud"]
Y = data.iloc[:, data.columns == "isFraud"]
通過條形圖我們可以發現,正負樣本量是非常不均衡的,這會對我們的模型計算產生很大的影響,我估計你昨天測試集的結果應該全都是0吧。
對於這種樣本不均衡的數據最常使用的有兩種解決方案:
1.過採樣:通過1樣本數據製造一些數據,讓兩種樣本數據保持平衡;
2.下采樣:在0樣本數據中隨機抽取跟1樣本數據一樣多的數據,讓兩種樣本數據保持均衡。
# 過採樣——通過第三方庫可以很方便的實現
from imblearn.over_sampling import SMOTE
over_sample = SMOTE()
X_over_sample_data, Y_over_sample_data = over_sample.fit_sample(X.values, Y.values)
X_over_sample_data = pd.DataFrame(X_over_sample_data)
Y_over_sample_data = pd.DataFrame(Y_over_sample_data)
# 看一下過採樣數據集的長度
print("Total number of normal =", len(Y_over_sample_data[Y_over_sample_data == 0]))
print("Total number of fraud =", len(Y_over_sample_data[Y_over_sample_data == 1]))
# 下采樣
number_of_fraud = len(data[data.isFraud == 1]) # 統計被詐騙數據量
fraud_indices = np.array(data[data.isFraud == 1].index) # 被詐騙數據索引
normal_indices = np.array(data[data.isFraud == 0].index) # 正常數據索引
# 在正常數據中隨機選擇與被詐騙數據量相等的正常數據的索引
random_normal_indices = np.array(np.random.choice(normal_indices, number_of_fraud, replace=False))
# 將所有被詐騙的數據索引和隨機選擇的等量正常數據索引合併
under_sample_indices = np.concatenate([fraud_indices, random_normal_indices])
# 從原始數據中取出下采樣數據集
under_sample_data = data.iloc[under_sample_indices, :]
X_under_sample = under_sample_data.iloc[:, under_sample_data.columns != "isFraud"]
Y_under_sample = under_sample_data.iloc[:, under_sample_data.columns == "isFraud"]
# 看一下下采樣數據集的長度
print("Total number of under_sample_data =", len(under_sample_data))
print("Total number of normal =", len(under_sample_data[data.isFraud == 0]))
print("Total number of fraud =", len(under_sample_data[data.isFraud == 1]))
標準化
在我們做機器學習模型的時候,要保證特徵之間的分佈數據是差不多,也就是保證初始情況下每一列特徵的重要程度是相似的,比如說card1這一列,它的數據相比如其它的數據都非常大,在訓練模型的時候機器可能認爲card1這行數據非常重要,但實際上並不能確定。
因此,我們需要對data進行標準化的處理,通過sklearn的preprocessing模塊可以快速的幫助我們隊數據做標準化:
from sklearn.preprocessing import StandardScaler
cols = X_under_sample.columns
for col in cols:
X[col] = StandardScaler().fit_transform(X[col].values.reshape(-1, 1))
for col in cols:
X_under_sample[col] = StandardScaler().fit_transform(X_under_sample[col].values.reshape(-1, 1))
for col in cols:
X_over_sample_data[col] = StandardScaler().fit_transform(X_over_sample_data[col].values.reshape(-1, 1))
交叉驗證
爲了在不使用測試集的情況下驗證模型的效果,通常在訓練一個機器學習的模型之前,會對train數據集進行切分:
我們把訓練集分成5份,然後進行五輪訓練:
第一輪:第1份數據留作驗證,2、3、4、5份數據用作訓練模型;
第二輪:第2份數據留作驗證,1、3、4、5份數據用作訓練模型;
第三輪:第3份數據留作驗證,1、2、4、5份數據用作訓練模型;
第四輪:第4份數據留作驗證,1、2、3、5份數據用作訓練模型;
第五輪:第5份數據留作驗證,1、2、3、4份數據用作訓練模型;
最後針對每一輪訓練的結果取一個平均的效果,會讓我們的模型變得更加優秀,當然這個效果不用我們來實現,sklearn已經幫我們實現好了:
from sklearn.model_selection import train_test_split
簡單來說,train_test_split就是輸入訓練集的X和Y,返回切分後的訓練集X、Y,驗證集X、Y。
# 對原始的全部數據進行切分
X_train, X_test, Y_train, y_test = train_test_split(X, Y, test_size=0.2)
# 對下采樣數據集進行切分
X_under_sample_train, X_under_sample_test, Y_under_sample_train, Y_under_sample_test = train_test_split(X_under_sample, Y_under_sample, test_size=0.2)
# 對上採樣數據集進行切分
X_over_sample_train, X_over_sample_test, Y_over_sample_train, Y_over_sample_test = train_test_split(X_over_sample_data, Y_over_sample_data, test_size=0.2)
模型評估
一個模型的好壞不能只看它的準確率,舉一個簡單的例子:
假設1000個人中,有900個人是正常的,有100個人被詐騙。
從這1000個人中抽出100人來測試某個模型,這100個人中有90個人是正常的,有10個人是被詐騙的。
有一個非常粗暴的模型,將輸入的樣本都判斷爲正常,那麼這個模型在處理這100個數據的時候,會預測準那90個正常的,但剩下10個預測錯誤。
模型的準確率爲90%,但是這顯然不是一個好模型。
所以,在樣本不均衡的情況下不能夠使用準確率作爲模型評估的標準,而要使用recall,也就是召回率。
計算recall需要先看一個表格:
正類 | 負類 | |
---|---|---|
檢索到 | True Positive(TP),正類判斷爲正類 | False Positive(FP),負類判斷爲正類 |
未檢索到 | False Negative(FN),正類判斷爲負類 | True Negative(TN),負類判斷爲負類 |
這看起來不是很好理解,我們再來舉一個例子:
還是1000個人中,有900個人是正常的,有100個人被詐騙。
又有一個不靠譜的模型,將輸入的樣本50%判斷爲正常,50%判斷爲異常,我們的目的是找出所有被詐騙的。
經過這個模型的計算,得到500個模型認爲的被詐騙人,但是500個人中只有50個人是被詐騙的,剩下450個都是正常的。
檢索到的500個人中:
50個異常數據被判斷爲異常,TP=50;
450個正常數據被判斷爲異常,FP=450。
未被檢索到的500個人中:
50個異常數據被判斷爲正常,FN=50;
450個正常數據被判斷爲正常,TN=450。
recall = 50 / (50 + 50) = 0.5
正則化懲罰項
我們訓練模型的目標其實就是求出參數θ,假設通過計算得到θ1和θ2兩個模型,儘管參數值截然不同,但在預測中有可能會得到相同的結果。
那麼對於這兩個模型,我們到底要選擇哪一個呢?
在回答這個問題之前,我們要了解一個知識點,過擬合。
過擬合問題是機器學習中很讓人頭疼的一件事情,舉個例子:
暫時不用管這是什麼算法,我們的目標是對紅綠色的點進行分類,可以看到對於大部分數據區分的還是比較完美的,但是綠色範圍在左下方突出了一個角,爲了去擬合在紅色堆裏那個按照正常的邏輯應該判定爲紅色點的綠色點,但是,有可能那個離羣的綠色點是個錯誤數據。
這就是過擬合,只能說我們的模型對於訓練集來說太過完美,這可並不是一件好事,我們的目的是想讓模型能夠匹配所有的數據,不僅僅侷限於訓練集。
過擬合通常發生在訓練數據不夠多或者訓練過度(overtrainging)的情況下,而正則化方法就是爲了解決過擬合的問題而誕生的,通過在損失函數中引入額外的計算項,可以有效的防止過擬合,提高模型的泛化能力。
目前有兩種正則化懲罰項:
L1參數正則化:
L2參數正則化:
邏輯迴歸模型
我們已經學習過邏輯迴歸算法的推導過程,能夠將計算過程由代碼實現,帶如果每次使用邏輯迴歸都要再寫一遍代碼顯然是非常繁瑣的,sklearn包幫我們實現好了一個很優秀的邏輯迴歸訓練器,只需要輸入相應的參數,就可以造出一個訓練器。
import time
from sklearn.metrics import recall_score
from sklearn.model_selection import KFold
from sklearn.linear_model import LogisticRegression
def kfold_scores(x_train_data, y_train_data):
start_time = time.time()
fold = KFold(3, shuffle=True) # 3折交叉驗證
c_param_range = [10, 100, 1000] # 懲罰力度,正則化懲罰項的係數
# 做可視化展示
results_table = pd.DataFrame(index=range(len(c_param_range), 2), columns=["C_parameter", "Mean recall scores"])
results_table["C_parameter"] = c_param_range
# 不確定哪一個正則化懲罰項的係數更好,因此採用循環確認
index = 0
for c_param in c_param_range:
print('--------------------------------------------------------------------------------')
print("If C parameter =", c_param, end="\n\n")
# 做交叉驗證
recall_accs = []
lr = LogisticRegression(C=c_param, penalty='l1', solver='liblinear', max_iter=10000)
for iteration, indices in enumerate(fold.split(x_train_data)):
# 擬合訓練數據
lr.fit(x_train_data.iloc[indices[0], :], y_train_data.iloc[indices[0], :].values.ravel())
# 使用驗證集得出預測數據
y_predicted_undersample = lr.predict(x_train_data.iloc[indices[1], :])
# 計算recall
recall_acc = recall_score(y_train_data.iloc[indices[1], :], y_predicted_undersample)
recall_accs.append(recall_acc)
print('\tIteration ', iteration, ': recall score = ', recall_acc)
index += 1
# 計算recall的平均值
results_table.loc[index, "Mean recall scores"] = np.mean(recall_accs)
print('Mean recall score = ', results_table.loc[index, "Mean recall scores"], end="\n\n")
print('--------------------------------------------------------------------------------')
best_c_param = results_table.loc[results_table['Mean recall scores'].astype(float).idxmax()]['C_parameter']
print('Best C parameter = ', best_c_param, "\t duration: ", time.time() - start_time)
return lr, best_c_param
# 對原始全部數據進行測試
# lr1, param1 = kfold_scores(X_train, Y_train)
del X_train
del Y_train
# 對下采樣數據進行測試
lr2, param2 = kfold_scores(X_under_sample_train, Y_under_sample_train)
del X_under_sample_train
del Y_under_sample_train
# 對上採樣數據進行測試
# lr3, param3 = kfold_scores(X_over_sample_train, Y_over_sample_train)
del X_over_sample_train
del Y_over_sample_train
test_identity = pd.read_csv('/kaggle/input/ieee-fraud-detection/test_identity.csv')
test_transaction = pd.read_csv('/kaggle/input/ieee-fraud-detection/test_transaction.csv')
data = pd.merge(test_transaction, test_identity, on="TransactionID", how="left")
test_ID = data[["TransactionID"]]
data.drop("TransactionID", axis=1, inplace=True) # TransactionID說實話沒啥用
data.drop("TransactionDT", axis=1, inplace=True) # TransactionDT類似於時間戳,也沒啥用
for col in data.columns:
if col.startswith("id"):
newcol = col.replace("-", "_")
data.rename(columns={col: newcol},inplace=True)
del test_identity, test_transaction
data.drop(na_data[na_data['ratio'] > 0.3].index, axis=1, inplace=True)
for col in data.columns:
if data[col].dtypes == "object":
data[col], uniques = pd.factorize(data[col])
data[col].fillna(data[col].mean(), inplace=True)
for col in cols:
data[col] = StandardScaler().fit_transform(data[col].values.reshape(-1, 1))
test_predict = lr2.predict(data.values)
submission = pd.concat([test_ID, pd.Series(test_predict)], axis=1, keys=["TransactionID", "isFraud"])
submission.to_csv("submission1.csv", index=False)
好了,這就是通過sklearn來做這個邏輯迴歸的項目,咱們可以吧結果提交評測網站IEEE-CIS Fraud Detection看看效果怎麼樣:
Coco:“你看,這麼做是不是效果比你之前那個好一點了。”
Alex:“我靠,真大。。。。”
Coco:“什麼真大?”
Alex:“額,分數啊。”
Coco:“行,大概就是這個套路,剩下的是就是細節的問題了,你回去也可以試試過採樣和正常數據能達到什麼效果。”
Alex:“不用回去了,我就在這試。”