保险理赔欺诈检测

目录

创建工程

数据集

为可疑模式建模

纯方法

数据集重整

完整代码


首先了解可疑行为检测,它的目标是学习未知欺诈模式,这些模式符合“已知的已知”模型。

创建工程

接着前面的项目,导入jar包和数据。

数据集

我们将使用一个描述保险交易的数据集,可以在Oracle Database Online Documentation (2015)中找到它.

这个数据集描述了一个未公开的保险公司的车辆事故保险理赔情况。其中包含15 430个理赔 案例,每个理赔用33个属性进行描述:

 客户详细信息(年龄、性别、婚姻状况等)

 购买保单(保单种类、车辆类型、增补数、代理类型等)

 理赔情况(日/月/周理赔,保单报告归档、证人、事故报告间隔日期、事故理赔等)

 其他客户数据(车辆数、上一次理赔、司机评级等)

 有无欺诈(有或无)

我们的任务是创建一个模型,将来用于识别可疑的理赔。这项任务面临的挑战是,这些索 赔中只有6%是可疑的。如果我们创建了一个虚拟分类器,说所有索赔都不可疑,那在94%的索 赔案例中它的判断都是对的。因此,这项任务中,我们将使用不同的精确度测量方法:准确率 与召回率。

表格中有4种可能的结果,分别是真正、假正、假负、真负,由此可推出

准确率与召回率定义如下。
 准确率等于在被分类器判定为正的所有样本实例(TP+FP)中,正确判断为正(TP)的 正例样本所占比重,如下:

 召回率等于在总正例样本(TP+FN)中,被正确判定为正(TP)的正例所占比重,如下:

 若使用这些指标,我们的虚拟分类器得分是:Pr=0、Re=0,因为它从不会将任何实例标 记为欺诈(TP=0)。实际上,我们希望使用这两个指标评价分类器,即F值(F-measure), 它是一种在准确率与召回率之间计算调和平均数的方法,如下:

接下来,继续设计一个真实的分类器。

为可疑模式建模

为了设计一个分类器,我们将遵循第1章讲解的有关监督学习的标准步骤。此处还要加入其 他一些步骤,以处理非平衡数据集,并对基于准确率与召回率的分类模型做评估。详细步骤如下。

 加载.csv格式的数据;

 指派分类属性;

 将所有属性从数值型(numeric)转换为名义型(nominal),确保没有错误加载的数值;

 Experiment 1:使用k折交叉验证评价模型;

 Experiment 2:重新调整数据集,使之拥有更均衡的类分布,并且手动交叉验证;

 使用召回率、精确率与F值评价分类器。

首先,使用CSVLoader类加载数据.

        // 获取CSV文件的路径
        String filePath = ClassUtils.getDefaultClassLoader().getResource("data/test07/claims.csv").getPath();
        // 创建CSVLoader对象,用于加载CSV文件
        CSVLoader loader = new CSVLoader();
        // 设置CSV文件的分隔符为逗号
        loader.setFieldSeparator(",");
        // 将文件路径设置为CSVLoader的输入源
        loader.setSource(new File(filePath));
        // 使用CSVLoader加载数据并将其转换为Weka的Instances对象
        Instances data = loader.getDataSet();

然后,确保所有属性的类型都是名义型。在数据导入期间,Weka会使用一些启发式方法猜 测属性最有可能的类型,即数值型、名义型、字符串型、日期型。由于启发式方法并非总能正确 猜中属性类型,所以要手动设置属性类型

        /*
         * 配置数据集
         */
        // 定义属性索引
        int CLASS_INDEX = 15; // 类标签的索引(欺诈类别)
        int POLICY_INDEX = 17; // 政策属性的索引(不相关)
        int NO_FRAUD = 0, FRAUD = 1; // 欺诈类别的标签值
        int FOLDS = 3; // K折交叉验证的折数
        // 将所有数值属性转换为标称属性(分类属性)
        NumericToNominal toNominal = new NumericToNominal();
        toNominal.setInputFormat(data);
        data = Filter.useFilter(data, toNominal);

继续下一步之前,还要指定待预测的属性。

        // 设置类标签索引
        data.setClassIndex(CLASS_INDEX);

接着,剔除一个描述保单编号的属性,因为它不具有预测价值。只要简单使用Remove过滤 器即可移除。

        // 移除不相关的属性(政策属性)
        Remove remove = new Remove();
        remove.setInputFormat(data);
        remove.setOptions(new String[]{"-R", "" + POLICY_INDEX});
        data = Filter.useFilter(data, remove);

以上就是建模的准备工作。

纯方法

纯方法(Vanilla approach)是指直接应用第3章介绍的方法,既不需要做预处理,也不需要 考虑数据集具体细节。为了说明Vanilla方法的缺点,我们将创建一个带有默认参数的简单模型, 并且应用k折交叉验证。
首先,添加要测试的分类器:

        /*
         * 朴素方法(不进行数据重平衡)
         */
        // 定义分类器列表
        List<Classifier> models = new ArrayList<Classifier>();
        models.add(new J48()); // C4.5决策树
        models.add(new RandomForest()); // 随机森林
        models.add(new NaiveBayes()); // 朴素贝叶斯
        models.add(new AdaBoostM1()); // AdaBoost
        models.add(new Logistic()); // 逻辑回归

然后,创建一个Evaluation对象,通过调用crossValidate(Classifier, Instances, int, Random, String[])方法进行k折交叉验证,最后输出准确率、召回率与F值

        // 创建评估对象
        Evaluation eval = new Evaluation(data);
        // 打印朴素方法的标题
        System.out.println("Vanilla approach\n----------------");
        // 对每个模型进行K折交叉验证
        for (Classifier model : models) {
            eval.crossValidateModel(model, data, FOLDS, new Random(1), new String[]{});
            // 打印召回率、精确率和F1分数
            System.out.println(model.getClass().getName() + "\n" + "\tRecall:    " + eval.recall(FRAUD) + "\n" + "\tPrecision: " + eval.precision(FRAUD) + "\n" + "\tF-measure: " + eval.fMeasure(FRAUD));
        }

最终输出的评估分数如下

Vanilla approach
----------------
weka.classifiers.trees.J48
	Recall:    0.03358613217768147
	Precision: 0.9117647058823529
	F-measure: 0.06478578892371996
weka.classifiers.trees.RandomForest
	Recall:    0.017876489707475622
	Precision: 0.9166666666666666
	F-measure: 0.03506907545164719
weka.classifiers.bayes.NaiveBayes
	Recall:    0.05597688696280245
	Precision: 0.25162337662337664
	F-measure: 0.0915805022156573
weka.classifiers.meta.AdaBoostM1
	Recall:    0.04198266522210184
	Precision: 0.25162337662337664
	F-measure: 0.07195914577530177
weka.classifiers.functions.Logistic
	Recall:    0.037486457204767065
	Precision: 0.2521865889212828
	F-measure: 0.06527070364082249

从上面这些输出可以看到,结果并不理想。从召回率(Recall)看,所有欺诈行为中,被发 现的欺诈只占1%~3%。也就是说,只有1~3/100个欺诈能被检测到。而从准确率(Precision)来 说,报警准确率有91%。这意味着申请理赔的9/10个案例中,模型都是正确的。

数据集重整

由于相比于正例,反例(即欺诈)数目非常少,所以学习算法要与归纳推理做“斗争”。为 帮助算法解决这个问题,我们可以给它们一个数据集。该数据集中,正例与反例的比重是可比的。 也就是说,这个问题可以通过数据集重整(Dataset rebalancing)得到解决。

Weka有一个内置的过滤器Resample,用于从一个数据集随机抽选子样本,它使用重置抽样 或不重置抽样。这个过滤器也可以将一个分布调整为类均匀分布。 我们将手工实现k折交叉验证。

首先,把数据集均等地分成k折,其中第k折用作测试,其他 折用来学习。使用StratifiedRemoveFolds过滤器划分数据集,划分后的各折中仍然保持着相 同的类分布

        /*
         * 手工进行K折交叉验证并进行数据重平衡
         */
        // 创建StratifiedRemoveFolds对象,用于分层K折交叉验证
        StratifiedRemoveFolds kFold = new StratifiedRemoveFolds();
        kFold.setInputFormat(data);
        // 用于存储每个模型的召回率、精确率和F1分数
        double measures[][] = new double[models.size()][3];
        // 打印数据重平衡的标题
        System.out.println("\nData rebalancing\n----------------");
        // 进行K折交叉验证
        for (int k = 1; k <= FOLDS; k++) {
            // 将数据集分为测试集和训练集
            kFold.setOptions(new String[]{"-N", "" + FOLDS, "-F", "" + k, "-S", "1"});
            Instances test = Filter.useFilter(data, kFold);
            kFold.setOptions(new String[]{"-N", "" + FOLDS, "-F", "" + k, "-S", "1", "-V"}); // 反向选择
            Instances train = Filter.useFilter(data, kFold);
            // 打印当前折的训练集和测试集大小
            System.out.println("Fold " + k + "\n\ttrain: " + train.size() + "\n\ttest: " + test.size());

然后,重整训练数据集,-Z参数指定要重抽样数据集的比例,-B把类分布调整成均匀分布

            // 对训练集进行重采样以平衡数据
            Resample resample = new Resample();
            resample.setInputFormat(data);
            resample.setOptions(new String[]{"-Z", "100", "-B", "1"}); // 有放回采样
            Instances balancedTrain = Filter.useFilter(train, resample);

接着,创建分类器并做评估。

            // 遍历每个分类器
            for (ListIterator<Classifier> it = models.listIterator(); it.hasNext(); ) {
                Classifier model = it.next();
                // 构建分类器模型
                model.buildClassifier(balancedTrain);
                // 创建评估对象
                eval = new Evaluation(balancedTrain);
                // 评估模型在测试集上的表现
                eval.evaluateModel(model, test);
                // 打印召回率、精确率和F1分数
                System.out.println("\n\t" + model.getClass().getName() + "\n" + "\tRecall:    " + eval.recall(FRAUD) + "\n" + "\tPrecision: " + eval.precision(FRAUD) + "\n" + "\tF-measure: " + eval.fMeasure(FRAUD));
                // 保存结果以计算平均值
                measures[it.previousIndex()][0] += eval.recall(FRAUD);
                measures[it.previousIndex()][1] += eval.precision(FRAUD);
                measures[it.previousIndex()][2] += eval.fMeasure(FRAUD);
            }
        }

最后,计算平均数,输出最佳模型

        // 计算每个模型的平均召回率、精确率和F1分数
        for (int i = 0; i < models.size(); i++) {
            measures[i][0] /= 1.0 * FOLDS;
            measures[i][1] /= 1.0 * FOLDS;
            measures[i][2] /= 1.0 * FOLDS;
        }
        // 输出结果并选择最佳模型
        Classifier bestModel = null;
        double bestScore = -1;
        for (ListIterator<Classifier> it = models.listIterator(); it.hasNext(); ) {
            Classifier model = it.next();
            double fMeasure = measures[it.previousIndex()][2];
            System.out.println(model.getClass().getName() + "\n" + "\tRecall:    " + measures[it.previousIndex()][0] + "\n" + "\tPrecision: " + measures[it.previousIndex()][1] + "\n" + "\tF-measure: " + fMeasure);
            if (fMeasure > bestScore) {
                bestScore = fMeasure;
                bestModel = model;
            }
        }
        // 打印最佳模型
        System.out.println("Best model: " + bestModel.getClass().getName());

现在,模型性能得到显著提升,如下:

Data rebalancing
----------------
Fold 1
	train: 10280
	test: 5140

	weka.classifiers.trees.J48
	Recall:    0.4527687296416938
	Precision: 0.15191256830601094
	F-measure: 0.22749590834697217

	weka.classifiers.trees.RandomForest
	Recall:    0.14332247557003258
	Precision: 0.24444444444444444
	F-measure: 0.1806981519507187

	weka.classifiers.bayes.NaiveBayes
	Recall:    0.8110749185667753
	Precision: 0.12345066931085771
	F-measure: 0.2142857142857143

	weka.classifiers.meta.AdaBoostM1
	Recall:    0.9022801302931596
	Precision: 0.11888412017167382
	F-measure: 0.21008722032612817

	weka.classifiers.functions.Logistic
	Recall:    0.7687296416938111
	Precision: 0.13280810354530106
	F-measure: 0.22648752399232247
Fold 2
	train: 10280
	test: 5140

	weka.classifiers.trees.J48
	Recall:    0.4675324675324675
	Precision: 0.1470888661899898
	F-measure: 0.22377622377622378

	weka.classifiers.trees.RandomForest
	Recall:    0.13636363636363635
	Precision: 0.25149700598802394
	F-measure: 0.17684210526315788

	weka.classifiers.bayes.NaiveBayes
	Recall:    0.8441558441558441
	Precision: 0.12566457225712904
	F-measure: 0.21876314682372738

	weka.classifiers.meta.AdaBoostM1
	Recall:    0.9318181818181818
	Precision: 0.12328178694158076
	F-measure: 0.21775417298937788

	weka.classifiers.functions.Logistic
	Recall:    0.7824675324675324
	Precision: 0.13693181818181818
	F-measure: 0.23307543520309476
Fold 3
	train: 10280
	test: 5140

	weka.classifiers.trees.J48
	Recall:    0.40584415584415584
	Precision: 0.13812154696132597
	F-measure: 0.2061005770816158

	weka.classifiers.trees.RandomForest
	Recall:    0.1038961038961039
	Precision: 0.19753086419753085
	F-measure: 0.13617021276595745

	weka.classifiers.bayes.NaiveBayes
	Recall:    0.8376623376623377
	Precision: 0.1315655277919429
	F-measure: 0.22741295724988983

	weka.classifiers.meta.AdaBoostM1
	Recall:    0.9318181818181818
	Precision: 0.12532751091703057
	F-measure: 0.22093918398768284

	weka.classifiers.functions.Logistic
	Recall:    0.75
	Precision: 0.13548387096774195
	F-measure: 0.22950819672131148
weka.classifiers.trees.J48
	Recall:    0.44204845100610574
	Precision: 0.14570766048577555
	F-measure: 0.21912423640160392
weka.classifiers.trees.RandomForest
	Recall:    0.12786073860992428
	Precision: 0.23115743820999976
	F-measure: 0.16457015665994468
weka.classifiers.bayes.NaiveBayes
	Recall:    0.8309643667949856
	Precision: 0.12689358978664322
	F-measure: 0.2201539394531105
weka.classifiers.meta.AdaBoostM1
	Recall:    0.9219721646431743
	Precision: 0.12249780601009504
	F-measure: 0.21626019243439631
weka.classifiers.functions.Logistic
	Recall:    0.7670657247204478
	Precision: 0.13507459756495374
	F-measure: 0.22969038530557626
Best model: weka.classifiers.functions.Logistic
Class transformation time: 0.0243242s for 692 classes or 3.5150578034682084E-5s per class

从上述结果可以看到,所有模型的得分都有了显著提高,比如最佳模型——逻辑回归,它发 现欺诈的准确率达到了76%;而误报率相对合理,被标记为“欺诈”的索赔中,只有13%是真正 的欺诈。如果“漏掉一个坏人”(未检测到欺诈行为)所付出的代价要比“误杀一个好人”(欺诈 误报)的代价大得多,那么“宁可误杀也不错放”就是理所应当的了。

模型的整体性能可能还有一些提升空间,比如我们可以做属性选择与特征生成,并且应用更 复杂的模型学习,相关内容已经在第3章讲解过。

完整代码

public class Fraud {
    public static void main(String[] args) throws Exception {
        // 获取CSV文件的路径
        String filePath = ClassUtils.getDefaultClassLoader().getResource("data/test07/claims.csv").getPath();
        // 创建CSVLoader对象,用于加载CSV文件
        CSVLoader loader = new CSVLoader();
        // 设置CSV文件的分隔符为逗号
        loader.setFieldSeparator(",");
        // 将文件路径设置为CSVLoader的输入源
        loader.setSource(new File(filePath));
        // 使用CSVLoader加载数据并将其转换为Weka的Instances对象
        Instances data = loader.getDataSet();
        /*
         * 配置数据集
         */
        // 定义属性索引
        int CLASS_INDEX = 15; // 类标签的索引(欺诈类别)
        int POLICY_INDEX = 17; // 政策属性的索引(不相关)
        int NO_FRAUD = 0, FRAUD = 1; // 欺诈类别的标签值
        int FOLDS = 3; // K折交叉验证的折数
        // 将所有数值属性转换为标称属性(分类属性)
        NumericToNominal toNominal = new NumericToNominal();
        toNominal.setInputFormat(data);
        data = Filter.useFilter(data, toNominal);
        // 设置类标签索引
        data.setClassIndex(CLASS_INDEX);
        // 移除不相关的属性(政策属性)
        Remove remove = new Remove();
        remove.setInputFormat(data);
        remove.setOptions(new String[]{"-R", "" + POLICY_INDEX});
        data = Filter.useFilter(data, remove);
        // 打印数据集的摘要信息
        System.out.println(data.toSummaryString());
        // 打印类标签的统计信息
        System.out.println("Class attribute:\n" + data.attributeStats(data.classIndex()));
        /*
         * 朴素方法(不进行数据重平衡)
         */
        // 定义分类器列表
        List<Classifier> models = new ArrayList<Classifier>();
        models.add(new J48()); // C4.5决策树
        models.add(new RandomForest()); // 随机森林
        models.add(new NaiveBayes()); // 朴素贝叶斯
        models.add(new AdaBoostM1()); // AdaBoost
        models.add(new Logistic()); // 逻辑回归
        // 创建评估对象
        Evaluation eval = new Evaluation(data);
        // 打印朴素方法的标题
        System.out.println("Vanilla approach\n----------------");
        // 对每个模型进行K折交叉验证
        for (Classifier model : models) {
            eval.crossValidateModel(model, data, FOLDS, new Random(1), new String[]{});
            // 打印召回率、精确率和F1分数
            System.out.println(model.getClass().getName() + "\n" + "\tRecall:    " + eval.recall(FRAUD) + "\n" + "\tPrecision: " + eval.precision(FRAUD) + "\n" + "\tF-measure: " + eval.fMeasure(FRAUD));
        }
        /*
         * 手工进行K折交叉验证并进行数据重平衡
         */
        // 创建StratifiedRemoveFolds对象,用于分层K折交叉验证
        StratifiedRemoveFolds kFold = new StratifiedRemoveFolds();
        kFold.setInputFormat(data);
        // 用于存储每个模型的召回率、精确率和F1分数
        double measures[][] = new double[models.size()][3];
        // 打印数据重平衡的标题
        System.out.println("\nData rebalancing\n----------------");
        // 进行K折交叉验证
        for (int k = 1; k <= FOLDS; k++) {
            // 将数据集分为测试集和训练集
            kFold.setOptions(new String[]{"-N", "" + FOLDS, "-F", "" + k, "-S", "1"});
            Instances test = Filter.useFilter(data, kFold);
            kFold.setOptions(new String[]{"-N", "" + FOLDS, "-F", "" + k, "-S", "1", "-V"}); // 反向选择
            Instances train = Filter.useFilter(data, kFold);
            // 打印当前折的训练集和测试集大小
            System.out.println("Fold " + k + "\n\ttrain: " + train.size() + "\n\ttest: " + test.size());
            // 对训练集进行重采样以平衡数据
            Resample resample = new Resample();
            resample.setInputFormat(data);
            resample.setOptions(new String[]{"-Z", "100", "-B", "1"}); // 有放回采样
            Instances balancedTrain = Filter.useFilter(train, resample);
            // 遍历每个分类器
            for (ListIterator<Classifier> it = models.listIterator(); it.hasNext(); ) {
                Classifier model = it.next();
                // 构建分类器模型
                model.buildClassifier(balancedTrain);
                // 创建评估对象
                eval = new Evaluation(balancedTrain);
                // 评估模型在测试集上的表现
                eval.evaluateModel(model, test);
                // 打印召回率、精确率和F1分数
                System.out.println("\n\t" + model.getClass().getName() + "\n" + "\tRecall:    " + eval.recall(FRAUD) + "\n" + "\tPrecision: " + eval.precision(FRAUD) + "\n" + "\tF-measure: " + eval.fMeasure(FRAUD));
                // 保存结果以计算平均值
                measures[it.previousIndex()][0] += eval.recall(FRAUD);
                measures[it.previousIndex()][1] += eval.precision(FRAUD);
                measures[it.previousIndex()][2] += eval.fMeasure(FRAUD);
            }
        }
        // 计算每个模型的平均召回率、精确率和F1分数
        for (int i = 0; i < models.size(); i++) {
            measures[i][0] /= 1.0 * FOLDS;
            measures[i][1] /= 1.0 * FOLDS;
            measures[i][2] /= 1.0 * FOLDS;
        }
        // 输出结果并选择最佳模型
        Classifier bestModel = null;
        double bestScore = -1;
        for (ListIterator<Classifier> it = models.listIterator(); it.hasNext(); ) {
            Classifier model = it.next();
            double fMeasure = measures[it.previousIndex()][2];
            System.out.println(model.getClass().getName() + "\n" + "\tRecall:    " + measures[it.previousIndex()][0] + "\n" + "\tPrecision: " + measures[it.previousIndex()][1] + "\n" + "\tF-measure: " + fMeasure);
            if (fMeasure > bestScore) {
                bestScore = fMeasure;
                bestModel = model;
            }
        }
        // 打印最佳模型
        System.out.println("Best model: " + bestModel.getClass().getName());
        // ... 使用所有可用的(重采样)数据构建最佳模型
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

顾北辰20

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值