写点什么

实战贴:如何使用机器学习检测欺诈?

2020 年 9 月 25 日

实战贴:如何使用机器学习检测欺诈?

本文最初发表于 Towards Data Science 博客,经原作者 Kurtis Pykes 授权,InfoQ 中文站翻译并分享。


机器学习是人工智能的一个子集,它赋予了系统从经验中自动学习和改进的能力,无需进行显式编程。如此说来,我们(人类)已经可以向计算机提供大量的数据集,让计算机学习模式,这样它在面对一个或多个新实例时,能够学习如何作出决定——当我发现这一见解时,我立即知道世界即将发生改变。


报告显示,欺诈行为给全球经济造成了 3.89 万亿英镑的损失,在过去十年里损失上升了 56%。

——Crowe UK


作为欺诈行为的受害者,我萌生了防止这种情况再次发生在我(以及其他任何人)身上的想法,这促使我开始思考一个与我所习惯的完全不同的领域。


欺诈检测问题

在机器学习术语中,诸如欺诈检测之类的问题,可以被归类为分类问题,其目标是预测离散标签 0 或 1,其中,0 通常表示交易是非欺诈性的,1 表示交易似乎是欺诈性的。


因此,这个问题要求从业人员构建足够智能的模型,以便能够在给定各种用户交易数据的情况下,正确地检测出欺诈性和非欺诈性的交易。为了保护用户隐私,这些交易数据通常都经过匿名化处理。


由于完全依赖基于规则的系统并不是最有效的策略,因此,机器学习已成为许多金融机构用来解决这一类问题的方法。


这个问题(欺诈检测)之所以如此具有挑战性,是因为当我们在现实世界对其进行建模时,发生的大多数交易都是真实的交易,只有很小一部分是欺诈行为。这意味着我们要处理数据不平衡的问题:我写的文章《过采样和欠采样》(Oversampling and Undersampling)就是处理这一类问题的一种方法。然而,对于这篇文章,我们的主要重点将是开始我们的机器学习框架来检测欺诈行为——如果你不熟悉构建自己的框架,那你可能需要在阅读本文之前,先阅读这篇文章《构建机器学习项目》(Structuring Machine Learning Projects)。


数据

这些数据是由IEEE 计算智能协会(IEEE Computational Intelligence Society,IEEE-CIS)的研究人员整理出来的,用于预测欺诈性在线交易概率的任务,以二进制目标isFraud来表示。


注:数据部分是从 Kaggle 竞赛数据部分复制而来。


数据分成两个文件identitytransaction,这两个文件由TransactionID连接。但并非所有交易都有相应的身份信息。


类别特征——交易(Transaction)

  • ProductCD

  • card1-card6

  • addr1addr2

  • P_emaildomain

  • R_emaildomain

  • M1-M9


类别特征——身份信息(Identity)

  • DeviceType

  • DeviceInfo

  • id_12-id_38


TransactionDT特征是给定引用日期时间(不是实际时间戳)开始的时间间隔(timedelta)。


你可以从比赛主持人的这篇文章《数据描述(详情及讨论)》(Data Description (Details and Discussion))中了解更多有关数据的信息。


文件

  • train_{transaction, identity}.csv——训练集

  • test_{transaction, identity}.csv——测试集(你必须预测这些观察值的isFraud值)

  • sample_submission.csv——正确格式的样本提交文件


构建框架

在处理任何机器学习任务时,第一步是建立一个可靠的交叉验证策略。


注:该框架背后的总体思路来自于Abhishek Thakur

——GitHub


当面对不平衡的数据问题时,通常采用的方法是使用StratifiedKFold,它以这样一种方式随机地分割数据,以保持相同的类分布。


我实现了 create folds,作为preprocessing.py的一部分。


import configimport numpy as npimport pandas as pdfrom sklearn.model_selection import StratifiedKFolddef read_all_data():train_transactions = pd.read_csv(config.TRAIN_TRANSACTIONS)train_identity = pd.read_csv(config.TRAIN_IDENTITY)test_transactions = pd.read_csv(config.TEST_TRANSACTIONS)test_identity = pd.read_csv(config.TEST_IDENTITY)return train_transactions, train_identity, test_transactions, test_identitydef merge_data(df1, df2):# merge dataframe on the indexmerged_df = df1.merge(df2, how="left", on="TransactionID")return merged_dfdef create_folds(df):# create a new columndf["kfold"] = -1# shuffle datadf = df.sample(frac=1, random_state=42).reset_index(drop=True)# initialize kfoldskf = StratifiedKFold(n_splits=5, shuffle=False)for fold, (train_idx, val_idx) in enumerate(skf.split(X=df, y=df.isFraud.values)):print(len(train_idx), len(val_idx))df.loc[val_idx, 'kfold'] = folddf.to_csv(config.DATA_DIR + "train_folds.csv", index=False)if __name__ == "__main__":train_transactions, train_identity, test_transactions, test_identity = read_all_data()merged_test = merge_data(test_transactions, test_identity)merged_train = merge_data(train_transactions, train_identity)del train_transactions, train_identity, test_transactions, test_identity# renaming test id columnsfor col in merged_test.columns:if "id" in col:merged_test.rename(columns={col : col.replace("-", "_")}, inplace=True)merged_test.to_csv(config.DATA_DIR + "test_df.csv", index=False)create_folds(merged_train)
复制代码


这段代码合并了来自训练集和测试集的身份信息和交易数据,然后重命名了merded_test数据中的列名,因为 id 列使用的是“-”而不是“_”,这将导致稍后检查以确保测试中的列名完全相同时出现问题。接下来,我们在训练数据中添加一个名为kfold的列名,并根据它所在的 fold 设置索引,然后保存到 CSV 文件中。


你可能已经注意到,我们导入config并将其作为通向各种交易的路径。所有的config都是另一个脚本的变量,这样我们就不必在不同的脚本重复调用这些变量了。


# Directory PathsDATA_DIR = "../input/"MODEL_OUTPUT = "../models"# Training dataTRAINING_DATA = DATA_DIR + "train_folds.csv"TRAIN_TRANSACTIONS = DATA_DIR + "train_transaction.csv"TRAIN_IDENTITY = DATA_DIR + "train_identity.csv"# Test dataTEST_DATA = DATA_DIR + "test_df.csv"TEST_TRANSACTIONS = DATA_DIR + "test_transaction.csv"TEST_IDENTITY = DATA_DIR + "test_identity.csv"# Categorical FeaturesCATEGORICAL_FEATURES = ["ProductCD", "card1", "card2", "card3", "card4","card5", "card6", "addr1", "addr2", "P_emaildomain","R_emaildomain", "M1", "M2", "M3", "M4", "M5","M6", "M7", "M8", "M9", "DeviceType", "DeviceInfo","id_12", "id_13", "id_14", "id_15", "id_16", "id_17","id_18", "id_19", "id_20", "id_21", "id_22", "id_23","id_24", "id_25", "id_26", "id_27", "id_28", "id_29","id_30", "id_31", "id_32", "id_33", "id_34", "id_35","id_36", "id_37", "id_38"]
复制代码


在处理机器学习问题时,以允许快速迭代的方式快速构建管道是非常重要的,因此我们将构建的下一个脚本是model_dispatcher.py,我们将其称为分类器,而train.py是我们的训练模型的脚本。


让我们从model_dispatcher.py开始。


from sklearn import linear_model, ensemblemodels = {"logistic_regression": linear_model.LogisticRegression(verbose=True, max_iter=1000, random_state=10),"random_forest": ensemble.RandomForestClassifier(verbose=True, n_estimators=100, criterion="gini")}
复制代码


在这里,我们简单地导入了一个逻辑回归和随机森林,并创建了一个字典,这样我们就可以通过运行逻辑回归模型models["logistic_regression"]来将算法调用到我们的训练脚本中。


训练脚本如下所示:


import osimport configimport model_dispatcherimport joblibimport argparseimport pandas as pdfrom sklearn import preprocessingfrom sklearn import metricsdef pipe(fold:int, model:str):df = pd.read_csv(config.TRAINING_DATA)df_test = pd.read_csv(config.TEST_DATA)X_train = df[df["kfold"] != fold].reset_index(drop=True)X_valid = df[df["kfold"] == fold].reset_index(drop=True)y_train = X_train.isFraud.valuesy_valid = X_valid.isFraud.valuesX_train = X_train.drop(["isFraud", "kfold"], axis=1)X_valid = X_valid.drop(["isFraud", "kfold"], axis=1)X_valid = X_valid[X_train.columns]label_encoders = {}for c in config.CATEGORICAL_FEATURES:lbl = preprocessing.LabelEncoder()X_train.loc[:, c] = X_train.loc[:, c].astype(str).fillna("NONE")X_valid.loc[:, c] = X_valid.loc[:, c].astype(str).fillna("NONE")df_test.loc[:, c] = df_test.loc[:, c].astype(str).fillna("NONE")lbl.fit(X_train[c].values.tolist() +X_valid[c].values.tolist() +df_test[c].values.tolist())X_train.loc[:, c] = lbl.transform(X_train[c].values.tolist())X_valid.loc[:, c] = lbl.transform(X_valid[c].values.tolist())label_encoders[c] = lbl# data is ready to trainclf = model_dispatcher.models[model]clf.fit(X_train.fillna(0), y_train)preds = clf.predict_proba(X_valid.fillna(0))[:, 1]print(metrics.roc_auc_score(y_valid, preds))joblib.dump(label_encoders, f"{config.MODEL_OUTPUT}/{model}_{fold}_label_encoder.pkl")joblib.dump(clf, f"{config.MODEL_OUTPUT}/{model}_{fold}.pkl")joblib.dump(X_train.columns, f"{config.MODEL_OUTPUT}/{model}_{fold}_columns.pkl")if __name__ == "__main__":parser = argparse.ArgumentParser()parser.add_argument("--fold",type=int)parser.add_argument("--model",type=str)args = parser.parse_args()pipe(fold=args.fold,model=args.model)
复制代码


我希望你能读懂代码,但如果看不明白的话,我来总结一下这段代码所发生的的事情:将训练数据设置为列kfold中的值,并且与我们通过的 fold 相同的值就是测试集。然后,我们对分类变量进行标签编码,并用 0 填充所有缺失值,最后将数据训练到逻辑回归模型上。


我们得到当前的 fold 的预测,并打印出ROC_AUC


注:从目前的情况看,代码本身并不会运行,因此我们必须在运行每个 Fold 时,传递 fold 和 model 的值。


让我们看看逻辑回归模型的输出。


### Logistic Regression# Fold 0ROC_AUC_SCORE: 0.7446056326560758# Fold 1ROC_AUC_SCORE: 0.7476247589462117# Fold 2ROC_AUC_SCORE: 0.7395710927094167# Fold 3ROC_AUC_SCORE: 0.7365641912867861# Fold 4ROC_AUC_SCORE: 0.7115696956435416
复制代码


这些都是相当不错的结果,但让我们使用更强大的随机森林模型,看看是否还可以改善。


### Random Forest# Fold 0ROC_AUC_SCORE: 0.9280242455299264# Fold 1ROC_AUC_SCORE: 0.9281600723876517# Fold 2ROC_AUC_SCORE: 0.9265254015330469# Fold 3ROC_AUC_SCORE: 0.9224746067992484# Fold 4ROC_AUC_SCORE: 0.9196977372298685
复制代码


很明显,随机森林模型产生了更好的结果。让我们在 Kaggle 上进行后期提交,看看我们在排行榜上的位置。这是最重要的部分——要做到这一点,我们必须运行inference.py


import osimport pandas as pdimport numpy as npimport configimport model_dispatcherfrom sklearn import preprocessingfrom sklearn import metricsimport joblibdef predict(test_data_path:str , model_name:str, model_path:str):df = pd.read_csv(test_data_path)test_idx = df["TransactionID"].valuespredictions = Nonefor FOLD in range(5):df = pd.read_csv(test_data_path)encoders = joblib.load(os.path.join(model_path, f"{model_name}_{FOLD}_label_encoder.pkl"))cols = joblib.load(os.path.join(model_path, f"{model_name}_{FOLD}_columns.pkl"))for c in encoders:lbl = encoders[c]df.loc[:, c] = df.loc[:, c].astype(str).fillna("NONE")df.loc[:, c] = lbl.transform(df[c].values.tolist())clf = joblib.load(os.path.join(model_path, f"{model_name}_{FOLD}.pkl"))df = df[cols]preds = clf.predict_proba(df.fillna(0))[:, 1]if FOLD == 0:predictions = predselse:predictions += predspredictions /= 5sub = pd.DataFrame(np.column_stack((test_idx, predictions)), columns=["TransactionID", "isFraud"])return subif __name__ == "__main__":submission = predict(test_data_path=config.TEST_DATA,model_name="random_forest",model_path=f"{config.MODEL_OUTPUT}/")submission.loc[:, "TransactionID"] = submission.loc[:, "TransactionID"].astype(int)submission.to_csv(f"{config.DATA_DIR}/rf_submission.csv", index=False)
复制代码


注:提交给 Kaggle 的过程并不在本文讨论的范畴,因此我将直接在排行榜上列出模型的得分以及它是如何做到的。



考虑到这个分数可以转换成 Kaggle 的私人排行榜(因为它是公共排行榜上的分数),我们在 Kaggle 的私人排行榜上排名为 3875/6351(前 61%)。虽然从 Kaggle 的角度来看,这个得分看起来并不咋样,但在现实世界的场景中,我们可能会根据任务的情况来解决这个分数。


但是,这个项目的目标并非提出最好的模型,而是创建我们自己的 API,我们将在后面的文章中讨论这个问题。


为了构建快速迭代的快速管道,我们拥有的代码是可以的,但是如果我们想部署这个模型的话,就必须做大量的清理工作,这样我们才能遵循软件工程最佳实践


总结

在现实世界中,欺诈检测是一个非常普遍且具有挑战性的问题,提高正确率对于防止在顾客在商店进行真正的交易时信用卡被拒的尴尬非常重要。我们已经构建了一种非常简单的方法,使用分类变量的标签编码,用 0 填充所有缺失值,并使用随机森林,没有任何调整或方法来处理数据的不平衡性,但我们的模型仍然得到了很高的分数。为了改进模型,我们可能要先从随机森林模型中寻找重要的特征,放弃不那么重要的特征,或者我们可以使用其他更为强大的模型,比如 Light Gradient Boosting Machine 和神经网络。


注:在编写这个脚本时,模块并不是最好的,但它的格式允许我进行快速迭代。在以后的工作中,我计划将这个模型作为 API 部署到云服务器上。


作者介绍:


Kurtis Pykes,痴迷于数据科学、人工智能和商业技术应用。


原文链接:


https://towardsdatascience.com/using-machine-learning-to-detect-fraud-f204910389cf


2020 年 9 月 25 日 08:00747
用户头像
刘燕 InfoQ记者

发布了 568 篇内容, 共 178.5 次阅读, 收获喜欢 1081 次。

关注

评论

发布
暂无评论
发现更多内容

产品经理训练营第二周作业

铭白

第二次作业及总结

青葵

学习笔记

成功逆袭:外包—苏宁—阿里 论我是怎么快速晋升的?

比伯

Java 编程 架构 面试 程序人生

如何对应用进行Android系统签名和常用adb命令总结

Changing Lin

android

第二周

Jove

理解利益相关者(Stake holder)

Geek_a32093

第二节课总结

Jove

产品经理训练营第二周作业

happy-黑皮

产品经理训练营

利益相关者分析

未月二九

第二章作业-产品的利益相关方

第二章:产品思维和产品意识 - 作业 - 为云g

Weiyung

第二章作业

Kasn

产品经理 产品经理训练营

产品利益相关方

高和不在家

产品经理训练营第二周作业:利益相关方分析

苏格图德

产品经理训练营

产品经理训练营第 2 次作业(分析利益相关方)

庞玉坤

产品经理训练营

产品训练营抽奖助手分析

innovator琳

产品

第二章学习总结

Kasn

产品经理 产品经理训练营

产品经理训练营 - 第二周作业

玖玖

第二周笔记

Ashley.

「产品经理训练营」作业 02

🌟

产品经理训练营 极客大学产品经理训练营 产品训练营

Stakeholder list

顾远山

作业 @ 第4课

糯米~

02-抽奖小助手与「谁」相关

sting

产品

Week2:抽奖助手StakeHolder思考

Y.

极客大学产品经理训练营

产品思维与产品意识学习总结

苏格图德

产品经理训练营

产品经理训练营-第四节课笔记

Geek_娴子

第二周作业 - 抽奖助手利益相关者

Geek_971380

第二周作业-APP的利益相关方

Geek_ce1551

极客产品训练营第二课作业

HaoJi

第二周作业-stack holder

Ashley.

意淫一下编排

JiangX

28天写作

2021年全国大学生计算机系统能力大赛操作系统设计赛 技术报告会

2021年全国大学生计算机系统能力大赛操作系统设计赛 技术报告会

实战贴:如何使用机器学习检测欺诈?-InfoQ