【锁定直播】字节、华为云、阿里云等技术专家讨论如何将大模型接入 AIOps 解决实际问题,戳>>> 了解详情
写点什么

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

  • 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


公众号推荐:

2024 年 1 月,InfoQ 研究中心重磅发布《大语言模型综合能力测评报告 2024》,揭示了 10 个大模型在语义理解、文学创作、知识问答等领域的卓越表现。ChatGPT-4、文心一言等领先模型在编程、逻辑推理等方面展现出惊人的进步,预示着大模型将在 2024 年迎来更广泛的应用和创新。关注公众号「AI 前线」,回复「大模型报告」免费获取电子版研究报告。

AI 前线公众号
2020-09-25 08:002281
用户头像
刘燕 InfoQ高级技术编辑

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

关注

评论

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

九章云极DataCanvas公司携因果学习开源重器登录WAIC!

九章云极DataCanvas

人工智能

INFINI 产品更新啦 20220826

极限实验室

elasticsearch console Gateway agent INFINI Labs

(WebFlux)003、多数据源R2dbc事务失效分析

编号94530

spring 事务 spring-data-r2dbc 多数据源 SpringWebflux

论企业级微服务架构必备能力

穿过生命散发芬芳

微服务架构 8月月更

C/CPP基础练习题(二)简单循环(2 + 22 + 222…;斐波那契数列)

CtrlX

c c++ 8月月更

8月书讯 | 10 本新书上市,本本精选

图灵教育

AI模型集成到业务系统的方式演化

felix

tensorflow serving 模型开发 工程

Solana流支付协议Zebec又完成一笔850万美元融资

鳄鱼视界

豆瓣 TOP3 的 Python 书,千万别错过

图灵社区

Spring @Autowired 注解静态变量

HoneyMoose

Spring @Autowired 注解静态变量

HoneyMoose

关键软件密码应用研讨会|海泰方圆国产浏览器密码应用分析研究

电子信息发烧客

leetcode 647. Palindromic Substrings回文子串(中等)

okokabcd

LeetCode 算法与数据结构

让数据成为企业核心生产力

IT资讯搬运工

每日一 R「17」类型系统进阶(一)

Samson

学习笔记 8月月更 ​Rust

Spring @Repository 注解

HoneyMoose

数字化智慧园区

科技云未来

Python自学教程8-数据类型有哪些注意事项

和牛

8月月更 python数据类型

K8s 长什么样子,一文道清它的整体架构

网管

架构 k8s 后端

中台 vs 平台

agnostic

中台

Spring 最常用的几个注解

HoneyMoose

rocksdb无法alter的解决方案

趁早

面试中的Spring,我们该怎么去回答

TimeFriends

8月月更

低代码实现探索(四十九)重新梳理前端

零道云-混合式低代码平台

华为云智能监管

科技云未来

[JS入门到进阶] 7条关于 async await 的使用口诀,新学 async await?背10遍,以后要考!快收藏

HullQin

CSS JavaScript html 前端 8月月更

闲谈游戏项目管理——篇一:稳定生产的流程管理

南方

项目管理 游戏

Python 教程之数据分析(2)—— 探索性数据分析

海拥(haiyong.site)

Python 8月月更

权限认证与授权三问三答

浅羽技术

框架 CSRF 认证授权 权限验证 8月月更

头脑风暴:翻转数位

HelloWorld杰少

算法 LeetCode 8月月更

云原生、云支持与基于云

CnosDB

时序数据库 开源社区 CnosDB infra

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