简介:一份基于Hadoop与Spark的大数据金融信贷风险控系统毕业设计源码,面向计算机相关专业的在校生、教师或从业者,适用于毕设、课设、项目初期立项演示,也适合对大数据风控感兴趣的学习者参考进阶。素材包共23个文件,压缩后仅53KB,以Scala源码为核心,配合XML与properties配置文件搭建项目依赖与运行参数,另有README说明文档和少量前端静态资源,整体轻量紧凑。目前已有2380人学习或浏览,积累了一定的参考热度。内容围绕信贷风控场景,覆盖Spark流式数据处理组件、数据接入模块及前端展示框架,源码经过运行验证,功能可用。读者可快速导入开发环境,理清Hadoop+Spark在大数据风控中的应用链路,并在此基础上扩展规则引擎或模型算法,以完成毕设功能迭代或业务演示,从而掌握从数据接入到结果展示的完整流程。无论是用于课程设计,还是作为入门大数据工程的项目样例,都能提供一个完整可运行的起点,帮助读者压缩搭建时间。
1. 这份“信贷风控系统”源码包,解压之前先想清楚它到底能给你什么
很多人看到“Hadoop+Spark”就觉得这是个大工程,其实真正上手之后你会发现,毕业设计要的从来不是“造一个银行级风控引擎”,而是把一条“数据进得来、算得动、结果出得去”的链路跑通。这个标题里的源代码包,大概率是一套这样的骨架:HDFS 负责把申请记录和还款流水存下来,Hive 做分层,Spark 负责清洗、特征加工和风险打分,最后落回一张结果表。你能用它答辩、能讲清楚原理、能改参数演示,这就够了。如果你是准备大数据方向毕业设计、或者想转大数据开发但手里缺一个完整项目的人,这套方向是值得花时间吃透的。但别指望解压就能跑,环境、版本、内存这些坑都在后面等着。
2. 信贷风控为什么要押注 Hadoop + Spark:先把数据链路画清楚
2.1 金融信贷风控的离线批处理链路:从申请记录到风险分
信贷风控在真实业务里分贷前、贷中、贷后,毕业设计一般只做贷前这一环:用户提交贷款申请,系统根据年龄、收入、负债、历史逾期次数、申请金额等字段,给一个风险评分,再决定放不放款。这个场景特别适合用大数据技术栈,不是因为数据量真的到了“海量”,而是因为特征计算逻辑复杂、数据来源多、需要批量调度,用 Hadoop+Spark 能把“多份数据 → 一张评分表”的加工过程做得规规矩矩。
常见的数据链路是四层:先是原始数据层,存申请记录、流水、第三方征信数据,对应 ODS 层;再是清洗层,把缺字段、异常值、格式错乱的记录处理掉,对应 DWD 层;然后是汇总层,按客户维度聚合出特征,对应 DWS 层;最后是应用层,输出每个客户的风险分和风险等级,对应 ADS 层。你拿到任何一份这样的毕设源码,都可以按这四层去找它的代码结构,找不到就说明这个毕设本身分层不清楚。
Hadoop 在这条链路里的角色是“底座”:HDFS 管存储,Hive 管数仓,MapReduce 虽然也能算,但写起来太痛苦,所以重活都交给 Spark。Spark 拿手的正好是内存计算和 DAG 调度,无论是读 JSON 原始文件做 ETL,还是做客户维度聚合、特征宽表拼接,都比 MapReduce 快一个数量级,代码也短得多。对这个项目而言,Spark 就是那个“算”的角色,Hadoop 是那个“存”的角色。
2.2 Hadoop 在风控里的角色:HDFS 目录规划与 Hive 分层
HDFS 上怎么规划目录,决定了你后续所有脚本好不好写。常见的做法是建一个统一根目录,比如/risk,下面分/risk/ods、/risk/dwd、/risk/dws、/risk/ads,每层下面再按日期分区。这样做的好处是:跑批任务时只要指定“读哪一层、写哪一层”,不会出现数据乱窜。
对应的 Hive 建表语句大概长这样,这张表是用来放 ODS 层原始申请记录的:
CREATE EXTERNAL TABLE ods_credit_apply ( customer_id STRING, apply_date STRING, age INT, annual_income DOUBLE, debt DOUBLE, overdue_times INT, apply_amount DOUBLE ) PARTITIONED BY (dt STRING) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' STORED AS TEXTFILE LOCATION 'hdfs://localhost:9000/risk/ods/credit_apply';用 EXTERNAL TABLE 而不是内部表,是因为原始数据经常是外部脚本生成的,删表不能把数据文件也删了。PARTITIONED BY (dt STRING)这个分区字段很关键,你后面跑 Spark 任务时按天增量处理,就是靠这个 dt 来限定数据范围。LOCATION指向的路径要和第 3 章里 HDFS 的fs.defaultFS对应上。
在真实项目里,ODS 层通常会比上面这张表多很多字段:渠道来源、设备指纹、IP 归属地、填表耗时等等,这些字段对反欺诈有用。毕业设计不追求这么多,但你要在答辩时说出“为什么 ODS 层要尽可能保留原始字段、不做过滤”——因为过滤逻辑在下游做,原始数据一旦丢了就补不回来。这句话能体现你理解数仓分层的用意。
2.3 Spark 在风控里的角色:ETL 清洗、特征加工、风险打分三件事
Spark 在这个系统里不是只跑一个任务,而是按职责拆成三条线:第一条是 ETL 清洗,把 ODS 层 JSON 转成结构化 DataFrame,处理空值和异常值;第二条是特征加工,把清洗后的明细按客户维度做聚合,比如算“过去 6 个月平均申请金额”“负债收入比”“历史逾期次数分段”这类特征;第三条是风险打分,把特征加权求和或者套一个简单的评分卡,得到 risk_score 和 risk_level。
这三个阶段在代码上最好拆成三个独立的方法或者三个独立的 Spark Job,不要揉在一起。原因很实际:你调参的时候,比如想改打分权重,只需要重新跑第三段,前面清洗结果还在 Hive 表里存着,不用从头再来。如果揉成一个 Job,每次改一个系数就要把全链路重跑一遍,程序是能跑,但你会浪费大量等待时间。
也要想清楚什么时候用 SparkSession、什么时候用 SparkContext。现在统一用 SparkSession 就行,它把 SQL、DataFrame、Streaming 的入口都收拢了。跑批任务里要注意的是 driver 和 executor 的分工:driver 负责调度和收集结果,executor 负责实际计算。如果你把几百万条结果都 collect 回 driver,内存直接爆掉。这也是为什么写完评分以后,正确姿势是write.saveAsTable落回 Hive,而不是打印到控制台。
3. 把 Hadoop + Spark 跑起来:伪分布式搭建与最小集群参数
3.1 环境选择:伪分布式够用吗?先算清楚你的数据量
很多人在环境搭建这一步就卡了两三天,原因不是技术难,而是没想清楚“我到底需要多大环境”。毕业设计的数据量,撑死几百万条,单机伪分布式完全扛得住。所谓伪分布式,就是一台机器上同时跑 NameNode、DataNode、ResourceManager、NodeManager 这四个进程,HDFS 的副本数设为 1。它和真正集群的区别只在“副本数”和“进程分布”,API 用法一模一样。
如果你只想在 Windows 笔记本上把流程跑通,可以装虚拟机或者直接用 Docker 起 Hadoop 镜像;但如果你后面要写“三节点集群搭建”这种章节来凑字数,那就老老实实准备三台虚拟机。我的建议是:不要把时间浪费在搭集群上,伪分布式能让你把 80% 精力放在业务代码和答辩逻辑上。面试官问“集群部署策略”,你能说出“生产环境 NameNode 和 ResourceManager 要分离部署、DataNode 按机架划分”就足够了,毕设演示机器上不需要真做。
版本搭配也是一个大坑。常见问题是拿着 Hadoop 2.x 的教程配 Hadoop 3.x,或者 Spark 版本和 Hadoop 版本编译不兼容。毕业设计我一般建议这套组合:Hadoop 3.2.x + Spark 3.x,JDK 用 8 或 11。Spark 3.x 对 Hive 的支持更顺滑,saveAsTable直接写 Hive 表不容易踩序列化坑。不要追新版本,JDK 17 + 最新 Hadoop 的组合在配置上会有额外麻烦。
3.2 伪分布式最小配置:core-site.xml、hdfs-site.xml、yarn-site.xml 三件套
Hadoop 跑起来只需要改三个配置文件,都在$HADOOP_HOME/etc/hadoop目录下。第一个是core-site.xml,指定默认文件系统和临时目录:
<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://localhost:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/opt/hadoop/tmp</value> </property> </configuration>fs.defaultFS是所有 HDFS 路径的前缀,后面代码里写hdfs://localhost:9000/risk/input/apply.json就是在用它。hadoop.tmp.dir是 NameNode 和 DataNode 存放元数据与数据块的根目录,如果这个目录没有创建权限或者磁盘满了,后面格式化会失败。
第二个是hdfs-site.xml,核心是把副本数设为 1,并显式指定 NameNode 和 DataNode 的数据目录:
<configuration> <property> <name>dfs.namenode.name.dir</name> <value>file:///opt/hadoop/namenode</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>file:///opt/hadoop/datanode</value> </property> <property> <name>dfs.replication</name> <value>1</value> </property> </configuration>这里有一个很容易被忽略的点:dfs.namenode.name.dir和dfs.datanode.data.dir如果配置的父目录不存在,Hadoop 不会自动创建,启动会直接报错。你需要在配置文件改好以后手动mkdir -p /opt/hadoop/namenode和mkdir -p /opt/hadoop/datanode。
第三个是yarn-site.xml,资源调度全靠它。伪分布式下最容易翻车的是内存配置过大,笔记本根本给不起:
<configuration> <property> <name>yarn.nodemanager.resource.memory-mb</name> <value>8192</value> </property> <property> <name>yarn.scheduler.maximum-allocation-mb</name> <value>4096</value> </property> <property> <name>yarn.nodemanager.resource.cpu-vcores</name> <value>4</value> </property> </configuration>yarn.nodemanager.resource.memory-mb是 NodeManager 能使用的总内存,yarn.scheduler.maximum-allocation-mb是单个容器能申请的最大内存。如果你的机器只有 8G 内存,建议把前者改成 4096,后者改成 2048,否则启动以后 Spark 任务很容易因为申请不到资源而一直卡在 ACCEPTED 状态。
3.3 一键拉起与验证:格式化、启动、看进程、跑第一个 Spark SQL
配置文件改完以后的启动顺序是有讲究的:先格式化 NameNode,再启动 HDFS,再启动 YARN。格式化只要一次,第二次启动不用再格式化,这是新手最容易犯的错——每次重新格式化会导致元数据丢失,DataNode 和 NameNode 的集群 ID 对不上,启动直接失败。
# 第一次使用必须格式化 NameNode hdfs namenode -format # 启动 HDFS start-dfs.sh # 启动 YARN start-yarn.sh # 确认四个进程都在 jpsjps输出里应该看到四个名字:NameNode、DataNode、ResourceManager、NodeManager。少一个都说明配置有问题,这时候去查对应日志,重点看$HADOOP_HOME/logs目录下的hadoop-hadoop-namenode-*.log和hadoop-hadoop-datanode-*.log。
启动完成以后,别急着跑业务代码,先验证 Spark 能和 HDFS、YARN 正常通信。最简单的办法是提交一个 Spark SQL 任务,让它读 HDFS 上的一个小文件:
# 先往 HDFS 放一个测试文件 echo "hello hadoop" | hdfs dfs -put - /test.txt # 用 spark-sql 读 HDFS 文件,能返回结果就说明链路通了 spark-sql --master yarn --deploy-mode client \ -e "SELECT count(*) FROM (SELECT 1 AS id UNION ALL SELECT 2) t;"这里用--master yarn让 Spark 跑在 YARN 上,而不是本地模式。很多人的毕设代码在本地能跑,一到集群就挂,就是因为 master 没配成 yarn。等这一步通了,就可以进入下一章,开始处理真正的信贷数据。
4. 让源码包里的风控逻辑跑通业务闭环:数据造数、Spark 读取 JSON、打分落库
4.1 造一套信贷模拟数据:Python 写申请记录生成器
如果你拿到的源码包没有配套的数据文件,那就必须自己造一套。不要用手工敲的几十条数据,那个量级跑 Spark 没有说服力。我一般用 Python 写一个生成器,把客户 ID、年龄、年收入、负债、历史逾期次数、申请金额写成一行为一条 JSON 的文件,模拟真实申请记录。
import json import random from datetime import datetime, timedelta random.seed(42) rows = [] for i in range(100000): customer_id = f"C{100000 + i}" age = random.randint(20, 60) annual_income = random.randint(60000, 1200000) debt = random.randint(0, int(annual_income * 0.8)) # 大部分客户没有逾期,少数逾期 1-3 次 overdue_times = random.choices([0, 1, 2, 3], weights=[0.80, 0.12, 0.05, 0.03])[0] apply_amount = random.randint(10000, 500000) rows.append({ "customer_id": customer_id, "apply_date": (datetime(2024, 1, 1) + timedelta(days=random.randint(0, 365))).strftime("%Y-%m-%d"), "age": age, "annual_income": annual_income, "debt": debt, "overdue_times": overdue_times, "apply_amount": apply_amount }) with open("credit_apply.json", "w", encoding="utf-8") as f: for record in rows: f.write(json.dumps(record, ensure_ascii=False) + "\n") print(f"generated {len(rows)} rows")random.seed(42)保证每次生成的数据一样,这一点在答辩时很重要——评审老师如果让你现场重跑,你不想让结果每次都变。overdue_times用random.choices按权重抽样,让 80% 客户是 0 次逾期、少数客户 1 到 3 次,这样后面打分评出来的风险等级才符合业务直觉:大部分是低风险,小部分是高风险。生成的是 JSON Lines 格式,一行一条记录,Spark 可以直接读。
4.2 Spark 读取 JSON 做特征清洗:用 DataFrame API 的完整代码
数据生成以后,把它传到 HDFS 上,然后编写 Spark 清洗任务。这一步的目标是:读入 JSON → 过滤_corrupt_record→ 剔除年龄、收入不合理的记录 → 计算年月分区字段 → 计算负债收入比特征 → 写入 Hive 表。
import org.apache.spark.sql.SparkSession import org.apache.spark.sql.functions._ val spark = SparkSession.builder() .appName("credit-risk-etl") .enableHiveSupport() .getOrCreate() val raw = spark.read .option("mode", "PERMISSIVE") .option("columnNameOfCorruptRecord", "_corrupt_record") .json("hdfs://localhost:9000/risk/input/credit_apply.json") val cleaned = raw .filter(col("_corrupt_record").isNull) .drop("_corrupt_record") .filter(col("age").between(18, 65)) .filter(col("annual_income") > 0) .withColumn("apply_year_month", date_format(to_date(col("apply_date")), "yyyyMM")) .na.fill(0, Seq("overdue_times")) .withColumn("debt_ratio", round(col("debt") / col("annual_income"), 4)) cleaned.write .mode("overwrite") .partitionBy("apply_year_month") .format("parquet") .saveAsTable("dwd_credit_apply")这段代码的核心是mode("PERMISSIVE"),它会把解析失败的记录塞进_corrupt_record字段而不是让整个任务直接崩掉。紧接着.filter(col("_corrupt_record").isNull)把脏数据过滤掉,再.drop删掉这个辅助字段。这一步如果你做反了——先 drop 再 filter——脏数据就被丢掉了,但你也永远不知道到底有多少脏数据,这对风控场景来说是致命的。
.partitionBy("apply_year_month")是让结果在 Hive 里按月份分区存储,后续查“某个月有多少高风险客户”时只需扫描一个分区。saveAsTable会同时建表和写数据,如果表已存在,mode("overwrite")会覆盖数据,但注意它默认不会替换表结构。
4.3 规则引擎打分与结果写回 Hive:从 risk_score 到可用报表
清洗后的表在dwd_credit_apply,下一步是打分。毕业设计里的模型一般不会用真正训练出来的机器学习模型,而是用简化的评分卡:把收入、逾期次数、负债收入比三个特征分别映射成分值,加总得到risk_score,再按阈值切成低中高三个风险等级。
val scored = cleaned .withColumn("income_score", when(col("annual_income") >= 500000, 30) .when(col("annual_income") >= 200000, 20) .otherwise(10)) .withColumn("overdue_score", when(col("overdue_times") === 0, 40) .when(col("overdue_times") === 1, 20) .otherwise(0)) .withColumn("debt_score", when(col("debt_ratio") < 0.3, 30) .when(col("debt_ratio") < 0.6, 15) .otherwise(0)) .withColumn("risk_score", col("income_score") + col("overdue_score") + col("debt_score")) .withColumn("risk_level", when(col("risk_score") >= 80, "low") .when(col("risk_score") >= 50, "medium") .otherwise("high")) scored.write .mode("overwrite") .format("parquet") .saveAsTable("ads_credit_risk")这里的三个when就是一张最简单的评分卡,权重完全是可调的。你把收入阈值从 50 万调到 30 万,高分客户的数量立刻变多。答辩时你可以现场演示“调高收入权重之后,高风险客群占比从 15% 降到了 9%”,这是很加分的操作。
打分完成以后,任务往往需要提交到集群上跑。在源码包里通常是一个打好的 jar,你只需要把主类和输入输出路径换成自己的:
spark-submit \ --master yarn \ --deploy-mode cluster \ --executor-memory 2g \ --num-executors 2 \ --executor-cores 1 \ --driver-memory 1g \ --class com.risk.CreditRiskApp \ credit-risk-1.0.jar \ hdfs://localhost:9000/risk/input/credit_apply.json \ hdfs://localhost:9000/risk/result--executor-memory 2g是指每个 executor 分配 2G 堆内存,--num-executors 2是启动两个 executor。这两个参数在伪分布式下不要贪大,前面 yarn-site.xml 里的maximum-allocation-mb配置是多少,executor 内存就不能超过多少,否则任务会一直卡在 ACCEPTED 状态。--deploy-mode cluster会让 driver 跑在集群里,关掉客户端也不影响任务执行,适合放在nohup后面挂后台。
5. 实战踩坑:Hadoop+Spark 跑信贷风控任务的 5 个高频故障
5.1 NameNode 起不来,jps 里永远少一个进程
现象:执行start-dfs.sh后,用jps只能看到 DataNode 和 ResourceManager,NameNode 进程不存在,查看日志发现报错信息里有file:///opt/hadoop/namenode has been used by another process或者Incompatible clusterIDs。
原因:最常见的是两种,一是/opt/hadoop/namenode目录里已经有了旧元数据,你第二次执行了hdfs namenode -format,生成了新的 cluster ID,和 DataNode 里记录的 cluster ID 不一致;二是端口 9000 被占用,NameNode 启动时绑定端口失败。
解决:如果没有重要数据,最简单的方法是彻底清理之后重新格式化一次。把hadoop.tmp.dir下所有内容删除,包括 namenode、datanode、tmp 三个目录,然后重新mkdir -p建目录,再执行hdfs namenode -format。千万不要保留旧目录直接格式化。如果是端口占用,用ss -lntp | grep 9000找到占用进程处理掉再重启。
5.2 Spark 任务提交后一直卡在 Accepted 状态,等十分钟都不跑
现象:spark-submit执行后,客户端一直打印INFO YarnClientImpl: Submitted application ...,然后状态停在ACCEPTED,没有 Executor 启动的日志。
原因:申请不到资源。要么是yarn.nodemanager.resource.memory-mb小,容器申请的内存大于上限;要么是--executor-memory加--driver-memory的总和超过了 NodeManager 可用内存,YARN 把 ApplicationMaster 都调度不出来。
解决:先去 YARN 的 Web UI(http://localhost:8088)看集群内存总量和可用量,确认没看错以后,把--executor-memory改小到 1g,--num-executors改成 1,再把 yarn-site.xml 里的yarn.scheduler.maximum-allocation-mb调到不低于 2g。伪分布式环境下资源和代码一样重要,调参本来就是毕设的一部分。
5.3 按客户聚合特征时数据倾斜,某个 reduce 直接 OOM
现象:跑“按 customer_id 聚合申请金额、次数”这一类groupBy操作时,大部分 task 秒完成,但有个别 task 要跑几分钟甚至报GC overhead limit exceeded。看 Spark UI 的 Stage 页,某个 Executor 的 Shuffle Read 量是其他 Executor 的几十倍。
原因:信贷数据里存在“羊毛党”或“大客户”,一个人可能提交了几万次申请,生成数据时如果没控制分布,customer_id这一个 key 就占了大头。这个 key 所在的 reducer 被迫处理远高于平均的数据量。
解决:做两阶段聚合,先加盐拆开热点 key,聚一次,再去掉盐做第二次聚合。代码很短:
val salted = detail .withColumn("salt", (rand() * 10).cast("int")) .withColumn("group_key", concat(col("customer_id"), lit("_"), col("salt"))) val partial = salted .groupBy("group_key") .agg(sum("apply_amount").as("partial_amount")) val finalResult = partial .withColumn("customer_id", split(col("group_key"), "_").getItem(0)) .groupBy("customer_id") .agg(sum("partial_amount").as("total_amount"))加盐字段只影响中间分组,最终结果不受影响。盐的粒度可以调,10 不够就加到 100,但不要无限加,否则小文件也会变多。这个方案不只能用在风控场景,任何热点 key 导致的倾斜都能用。
5.4 JSON 解析失败导致整个批次静默丢数据
现象:清洗任务跑完,dwd_credit_apply表里记录数比 ODS 层少了上千条,但日志里没有任何 ERROR,连 warning 都很少。仔细看才发现某些钱多的客户被过滤掉了。
原因:spark.read.json默认的mode是PERMISSIVE没错,但如果你没有设置columnNameOfCorruptRecord并且在后续代码里显式 filter,脏数据会被直接 discard,而且不报错。你的造数脚本里如果出现了中文字段值或者空数组,就可能触发这个问题。
解决:用上一章 4.2 里的写法,设置columnNameOfCorruptRecord为_corrupt_record,然后必须紧跟在 read 之后马上 filter,再用negate或isNull判断。事后补救的办法也有:如果数据已经写进 Hive 表,可以用spark.read.table("dwd_credit_apply").filter("_corrupt_record is null")再把脏数据筛掉,但更靠谱的做法是改代码重跑。
5.5 写入 Hive 分区表后小文件爆炸,查询慢到怀疑人生
现象:任务正常结束,但dwd_credit_apply表按apply_year_month分区,每个分区下有几百个几 KB 大小的文件,SELECT count(*)要跑几十秒。
原因:Spark 写文件时,每个 shuffle 分区都会为每个目标分区写文件。如果spark.sql.shuffle.partitions默认是 200,而你的数据只有几十万行,每个文件都特别小。Hive 读大量小文件比读一个大文件慢得多。
解决:写完以后合并小文件,或者在写之前控制分区数。最简单的是重跑时加一行配置:
spark-sql --master yarn \ --conf spark.sql.shuffle.partitions=10 \ -e "INSERT OVERWRITE TABLE dwd_credit_apply PARTITION(apply_year_month) SELECT ... FROM ods_credit_apply"spark.sql.shuffle.partitions设置为 10,能让每个分区只写一个文件。已经产生的小文件,可以用ALTER TABLE dwd_credit_apply PARTITION(apply_year_month='202401') CONCATENATE;在 Hive 里合并掉。你可以在答辩时说“如果不控制 shuffle 分区数,小文件会让 NameNode 内存吃紧”——这句话是加分项,说明你懂小文件对 Hadoop 的危害。
6. 把毕设从“能跑”变成“能讲”:两个改造方向和一个验证技巧
6.1 改造方向一:给 HDFS 加 ZooKeeper 高可用,让系统更完整
伪分布式做的是单 NameNode,面试时被问到“NameNode 挂了怎么办”,你要能接住这个话题。常见的做法是引入 ZooKeeper 做 NameNode HA:两个 NameNode 一主一备,共享编辑日志存在 JournalNode 里,ZooKeeper 负责故障切换。这个改造不用在毕设演示机上真的做,但你要会画架构图、能说清楚 ZK 选主的过程。给源码加一个ha分支或配套文档模板,比硬写代码更实际。这里踩过的坑是:ZooKeeper 的 myid 写错会导致选主失败,日志里全是can't connect to quorum,排错时先zkServer.sh status看每个节点的 state,再检查防火墙和地址配置。
6.2 改造方向二:用 Spark Streaming 做一个风险预警模块
纯离线的信贷风控系统只能做贷前审批,如果能加一个实时模块,比如监控还款流水,当某个客户出现“短时间多笔大额取现”时实时拉高风险等级,整个项目的完整度会上一个台阶。Spark Streaming 现在的写法是用 Structured Streaming,读 Kafka 或文件源,窗口聚合判断异常,写回 Hive 或 Redis。在毕设里你不需要接真数据,用文件源加上一个模拟数据脚本就能演示。
6.3 验证技巧:用影子对照实验证明评分有效
答辩时最怕的是被问“你怎么证明你的评分是有效的”。不需要上机器学习那套 AUC,你只要做一个简单的分布验证:分别计算 low、medium、high 三组客户的平均逾期次数,然后对比。
spark-sql -e " SELECT risk_level, count(*) AS cnt, round(avg(overdue_times), 2) AS avg_overdue FROM dwd_credit_apply GROUP BY risk_level ORDER BY risk_level; "如果 high 组的avg_overdue明显高于 low 组,说明你定的阈值有区分度。反过来说,如果三组的平均逾期次数几乎一样,说明你的评分卡权重是拍脑袋拍的,回去调权重再跑一次。我当年做这个项目时最吃亏的一件事,就是只顾着把链路跑通,没准备“证明它有效”这一步,答辩被问住以后才补的这套对照实验。这个习惯我现在做任何数据项目都会保留——先想好怎么证明结果是对的,再动手写代码。希望帮到你。
本文还有配套的精品资源,点击获取