写点什么

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

  • 2020-09-25
  • 本文字数:5737 字

    阅读完需:约 19 分钟

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

本文最初发表于 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-09-25 08:004233
用户头像
刘燕 InfoQ高级技术编辑

发布了 1112 篇内容, 共 567.9 次阅读, 收获喜欢 1978 次。

关注

评论

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

如何使用免适配云鹰模组实现多网可切?——实践类

阿里云AIoT

安全 物联网 物联网安全 技术标签

JDBC的基本概念

Geek_7ubdnf

Java

版本控制 | 设计师和美术人员的理想版本控制软件是?

龙智—DevSecOps解决方案

版本控制 版本控制软件

Getaverse入选KuCoin Labs首批孵化项目

Geek_Web3

#区块链# 元宇宙 web3

软件测试/测试开发 | 单元测试体系集成

测试人

软件测试 单元测试 自动化测试 JUnit 测试开发

TiDB Operator高可用配置

TiDB 社区干货传送门

集群管理 管理与运维 安装 & 部署

数维图可视化编辑器超10项功能升级,您的需求就在其中

2D3D前端可视化开发

数据可视化 数字孪生 三维可视化 web3d web组态软件

PyFlink 最新进展解读及典型应用场景介绍

Apache Flink

大数据 flink 实时计算

收官!OceanBase第五届技术征文大赛获奖名单公布!

OceanBase 数据库

数据库 oceanbase

企业移动应用APP是否能实现统一整合与管理呢?

BeeWorks

再获殊荣!图数据库 NebulaGraph 获得 ITPUB 2022 创新产品奖

最新动态

【UE虚幻引擎】手把手教学,UE新手打包全攻略!

3DCAT实时渲染

游戏开发 虚幻引擎 虚幻引擎5 UE5 游戏开发引擎

TiDB 生产集群与加密通讯TLS的辛酸苦辣 - 工具篇

TiDB 社区干货传送门

集群管理 管理与运维 备份 & 恢复

35张图,直观理解Stable Diffusion

OneFlow

人工智能 深度学习 Stable Diffusion

企业真的需要一个私有化的即时通讯吗?

BeeWorks

TiCDC 集群工作过程解析

TiDB 社区干货传送门

火山引擎DataTester:一次A/B测试,帮助产品分享率提升超20%

字节跳动数据平台

大数据 AB testing实战

【Unity渲染】一文看懂!Unity通用渲染管线URP介绍

3DCAT实时渲染

Unity 渲染 实时云渲染 渲染服务 Unity3D

电脑音视频暂停再继续,声音突然变大

Geek_7ubdnf

windows

Win10桌面图标显示问题

Geek_7ubdnf

windows

【1.6-1.13】写作社区优秀技术博文一览

InfoQ写作社区官方

热门活动

软件测试/测试开发 | 静态扫描体系集成

测试人

软件测试 持续集成 jenkins 自动化测试 测试开发

微信小程序实验案例:简易成语小词典

TiAmo

小程序 微信小程序

互联网医疗月度观察:规范化、合法化的网络售药新时代到来

易观分析

互联网医疗

Hackathon特别策划 | 72小时灵感冲刺,创意就该这么玩

LigaAI

敏捷开发 研发管理 hackathon 黑客马拉松 企业号 1 月 PK 榜

【社区智慧合集】TiDB 相关 SQL 脚本大全

TiDB 社区干货传送门

Vue实现登录功能

Geek_7ubdnf

Vue

TiDB Operator升级

TiDB 社区干货传送门

实践案例 集群管理 管理与运维 安装 & 部署

2022年IAA行业品类年度表现总结

易观分析

视频 IAA

如何理解鲁棒性?为什么robustness会翻译为鲁棒性?

九章云极DataCanvas

代码质量与安全 | 展望:2023年商业软件开发的五大关键目标

龙智—DevSecOps解决方案

静态代码分析

实战贴:如何使用机器学习检测欺诈?_AI&大模型_Kurtis Pykes_InfoQ精选文章