ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

俄罗斯刑事犯罪数据集分析与预测实战

俄罗斯刑事犯罪数据集分析与预测实战 简介面向机器学习与数据分析初学者的俄罗斯刑事犯罪数据集分析预测实例以2008—2023年俄罗斯犯罪数据为主线覆盖读取、清洗、可视化、回归预测与聚类分群等完整流程适合课程设计、毕业设计或Kaggle风格练习。压缩包共5个文件含3个可运行的Python源代码、1个说明txt和1个CSV数据集CSV约589.70 KB下载包约107KB源码基于pandas、numpy、seaborn、matplotlib、sklearn、scipy、plotly实现随机森林回归、KMeans聚类与交互图表手工整理无语法错误。已有55人学习附readme便于理解目录结构。读者可获得犯罪趋势分析、酒类消费与犯罪率回归建模、随机森林预测及可视化表达的可复用实现方案。1. 为什么拿俄罗斯刑事犯罪数据集做AI实战AI实战这个词被人听烂了很多教程一上来就是图像分类与目标检测等真正拿到一张几千行、充满缺失值和西里尔字母的表格时反而手忙脚乱。俄罗斯刑事犯罪数据集分析预测这个压缩包恰好补上这块短板不到600 KB的一些CSV配合三个Python源代码文件就可以把数据清洗、特征工程、分类模型训练、结果评估一条龙走完。犯罪数据是表格型监督学习里最典型的场景之一目标列天然不平衡字段同时混着时间、地区、犯罪类型和人口数训练过程中遇到的坑比其他干净数据集多得多。下面直接进入实操先把ZIP包拆开看看里面的数据到底长什么样。2. 拆开ZIP先做勘察俄罗斯犯罪数据集的特征工程第一步拿到压缩包后的第一反应通常是解压、打开CSV、直接跑模型但这一步跳太快会在后面花十倍时间排错。犯罪数据集的表结构往往不是教科书的干净模样列名大小写不统一、日期格式混杂、地区名包含俄语字母或转写后缀这些都会让pandas的读取和join静默出错。建议先把勘察动作固定成三步看包内容、查缺失和重复、统一字段编码。2.1 先看包里有什么unzip -l 与 Python zipfile 双保险用命令行看一眼压缩包内部结构避免直接解压后找不到文件unzip -l crime_dataset.zip如果运行环境没有安装unzipWindows上常见可以改用Python内置的zipfile模块import zipfile with zipfile.ZipFile(crime_dataset.zip) as zf: for info in zf.infolist(): print(f{info.file_size / 1024:8.2f} KB {info.filename})这段代码不会把文件真正解压出来只是遍历压缩包内的文件信息适合确认里面到底有几个CSV、有没有嵌套目录。压缩包内通常会有一个主数据文件比如crime_data.csv或类似名称外加三个.py脚本命名风格可能类似baseline_lr.py、random_forest.py、lightgbm_run.py。拿到清单后再把需要的文件解压到工作目录unzip crime_dataset.zip -d ./crime这里有个容易被忽略的排查点如果压缩包来自Windows打包环境CSV文件名里可能带空格或非ASCII字符解压后文件名看起来正常但pandas读取时路径对不上。建议解压后执行ls -la看一遍实际文件名。数据处理的第一步不是写模型而是确认磁盘上到底有什么。2.2 缺失值、重复项与目标列清洗顺序决定模型上限数据集字段通常包括案发日期、地区、犯罪类型、案件数、人口数。由于是俄语来源字段名可能是英文也可能是用UTF-8保存的西里尔字母读进DataFrame后先做三件事查缺失、查重复、看目标分布。import pandas as pd df pd.read_csv(crime_data.csv, encodingutf-8-sig) print(df.columns.tolist()) missing df.isnull().mean() print(缺失率超过10%的列) print(missing[missing 0.1])utf-8-sig和utf-8的区别在于有没有开头BOM。Excel另存的CSV常带BOM用utf-8读会得到第一列列名带\ufeff后续groupby会莫名其妙多出一个空类别。字段名确认后重复项处理不能用df.drop_duplicates()一把梭。常见的重复是“同一地区同一犯罪类型在同年月出现两次”这是录入重复可以删但如果只是“同一地区不同犯罪类型”那就不是重复。正确做法dup_keys df.duplicated(subset[region, category, date]) print(f严格重复记录数: {dup_keys.sum()}) df df.drop_duplicates(subset[region, category, date])目标列的设计决定整个预测任务的性质。如果目标是“某地区下个月案件数”那就是回归如果压缩包里的三份源代码都是分类器则多数是把案件数按中位数切分成高发/低发两类。读代码前先确认目标分布target df[count] print(target.describe())下面是我在做这类数据时常用的清洗规则字段名以常见示例为准字段常见类型建议处理方式dateobject转datetime拆出year和monthregionobject做frequency编码避免one-hot爆炸categoryobject用LabelEncoder给树模型或ordinal编码countnumeric目标列按实际业务定回归或二分类populationnumeric直接进特征或与count做per_capita比例如果count列存在缺失通常直接删除该行。它是监督学习的标签补0会引入大量假阴性模型会被带偏。2.3 时间与地区字段编码成数值特征日期字段不能把字符串原样塞进模型。对犯罪数据年份和月份最有预测力季节性犯罪在冬季和夏季有明显差异部分犯罪类型在节假日月份波动明显。先把日期拆开df[date] pd.to_datetime(df[date]) df[year] df[date].dt.year df[month] df[date].dt.month region_freq df[region].value_counts() df[region_freq] df[region].map(region_freq)为什么用frequency encoding而不是one-hot地区字段类别数可能超过50个one-hot会把特征维度撑到上百列逻辑回归和LightGBM都会变慢且很多低频地区在训练集和测试集中分布不同。frequency encoding的缺点是数值大小和频率强相关线性模型里需要再做标准化对树模型则完全可以保留原始数值。如果还想继续深挖可以再加地区平均案件数做target encoding但这一步极其容易造成标签泄漏。安全写法是只在训练集上计算平均值再映射到验证集最后加上平滑系数否则验证集效果会虚高到没有参考价值。特征工程到这里就可以进模型了下一章把三份源代码分别对应到三个模型上。3. 三份源代码对应三种预测模型从线性基线到树模型集成一个常见误区是以为三个源代码文件代表三个独立的竞赛方案最后找出“最好的那个”就行。实际上这三份代码更合理的分工是一份跑逻辑回归做基线一份跑随机森林做对照一份跑LightGBM做最终落地。三者的取舍不是精度从低到高而是解释性与性能的权衡。3.1 三个模型的差异不在精度在解释成本模型优势代价适用阶段LogisticRegression系数可解释、训练秒完难以表达非线性交互第一版基线RandomForest自动处理交互、无需标准化预测不稳定、难解释中间对照LightGBM高精度、原生支持类别特征参数敏感、易过拟合最终优化数据集只有几百KB训练成本可以忽略所以三份代码可以在同一份数据上依次跑用同一套评估函数做对照最后选择稳定性和业务可解释性都能接受的那个。下面按源码的通常顺序拆开讲。3.2 逻辑回归给预测结果定基线第一份源码通常是一个最简逻辑回归跑通它能快速验证特征工程是否传对from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline features [year, month, population, region_freq] X df[features] y (df[count] df[count].median()).astype(int) lr make_pipeline( StandardScaler(), LogisticRegression(max_iter1000, class_weightbalanced) ) lr.fit(X_train, y_train)把count目标二值化的阈值用中位数逻辑回归预测的就是“此记录是否高于历史平均水平”。StandardScaler把year、month、region_freq这些量纲差异过大的列压到同一尺度不然population几千和month几会对系数产生误导。class_weightbalanced的作用是按类别频率自动放大少数类的损失权重如果高发记录只占三成这个参数让模型不会一味地输出多数类。跑完后输出验证集准确率但这个分数只能当粗粒度参考后一章会换成对不平衡数据更友好的评估。3.3 随机森林自动处理类别间的交互随机森林对表格数据的优势是不需要标准化也不需要手动构造多项式特征决策树自然会把月份和地区频次组合出来。第二份源代码用随机森林时关键参数在n_estimators和min_samples_leaffrom sklearn.ensemble import RandomForestClassifier rf RandomForestClassifier( n_estimators300, max_depth10, min_samples_leaf5, class_weightbalanced_subsample, n_jobs-1, random_state42 ) rf.fit(X_train, y_train)n_estimators建议取值100到500数据集只有几千行时跑500棵也很快但收益在300之后明显减少。min_samples_leaf5防止树把单个样本的噪声当作规则学进去设成1时训练集accuracy会非常好看验证集立刻垮掉这是判断是否过拟合的最直接信号。class_weightbalanced_subsample和逻辑回归里的balanced略有不同它会在每棵树的随机子样本里重新计算类别权重配合bootstrap采样让每个弱学习器看到更均衡的分布。随机森林的预测结果通常比逻辑回归高几个点但它对噪声更敏感同一份数据跑两次会因bootstrap随机性出现轻微差异。如果两次结果差异超过2%说明树的数量不够或数据集太小需要回头调n_estimators或min_samples_leaf。3.4 LightGBM带early stopping的最终迭代第三份代码通常是LightGBM这也是整个压缩包里最值得逐行读的部分。小数据集上LightGBM容易过拟合所以代码里必须有早停import lightgbm as lgb from sklearn.model_selection import train_test_split X_train, X_val, y_train, y_val train_test_split( X, y, test_size0.2, random_state42 ) model_lgb lgb.LGBMClassifier( objectivebinary, n_estimators2000, learning_rate0.05, num_leaves31, min_child_samples20, class_weightbalanced, random_state42 ) model_lgb.fit( X_train, y_train, eval_set[(X_val, y_val)], callbacks[lgb.early_stopping(50)] )LightGBM的n_estimators先给到2000搭配early_stopping才安全否则最后几十轮往往在过拟合上反复横跳。learning_rate0.05是中间层的稳妥选择0.1会更快但早停前容易冲过头0.01在小数据集上要跑很久且收益不明显。num_leaves31控制树的复杂度数据量越小越要调小到15左右。min_child_samples20限制每个叶子至少包含20个样本和随机森林里的min_samples_leaf是同一个防御思路。新版lightgbm里early_stopping_rounds参数已被标记为弃用改成回调形式callbacks[lgb.early_stopping(50)]是更稳的写法如果运行环境是旧版本仍然可以用model_lgb.fit(..., early_stopping_rounds50)两种写法都需要单独传eval_set。拿到早停后的最佳迭代数再用这个num_iterations在完整训练集上重新fit一次最后用验证集评估这是这三份代码里最值得保留的收尾动作。如果不想引入LightGBM的额外依赖第三份代码也可以用XGBClassifier替代参数逻辑几乎一样区别是XGBoost的scale_pos_weight需要自己按负样本数除以正样本数计算而LightGBM的class_weight更直接。4. 分析预测要过三关不均衡、时间泄漏、过拟合三个模型训练完不等于项目做完这部分才是这类数据集的真正考验。犯罪数据有个共同点高发记录数量远少于低发记录。用默认阈值0.5预测时模型倾向于把所有样本都判成低发准确率看起来很高业务上却完全没用。4.1 类别不平衡用混淆矩阵和AUC而不是accuracy先看分布再决定评估指标print(y.value_counts(normalizeTrue))如果正例占比低于30%评估代码需要一次性输出混淆矩阵和ROC AUCfrom sklearn.metrics import classification_report, confusion_matrix, roc_auc_score pred_rf rf.predict(X_test) print(confusion_matrix(y_test, pred_rf)) print(classification_report(y_test, pred_rf, target_names[低发, 高发])) print(fROC AUC: {roc_auc_score(y_test, rf.predict_proba(X_test)[:, 1]):.3f})在多份源码里同一套评估代码建议复制三份这样可以在一个屏幕里横向对比模型。混淆矩阵看的是固定阈值下的精确率与召回率ROC AUC看的是模型对所有样本排序能力。犯罪预测里对业务更重要的是高发类召回率宁可误报多一些也不能漏掉真正的高发地区所以最终选型应该先看高发类召回率再看AUC。4.2 按时间切分数据集而不是随机洗牌这是几份源代码里最容易出问题的地方。预测任务是面向未来做的如果数据年份从2015到2022就不能在随机切分后让模型看到2020年的测试数据。正确做法split_year 2021 train df[df[year] split_year].copy() test df[df[year] split_year].copy() X_train train[features] y_train (train[count] train[count].median()).astype(int) X_test test[features] y_test (test[count] test[count].median()).astype(int)注意阈值计算也有讲究y_train的中位数只能从train里算出如果用全量数据算中位数再切分测试集的标签在训练阶段就已被“看到”。下面是随机切分与时间切分对结果的影响切分方式训练/测试年份模型表现可信度train_test_split随机混着年份AUC虚高0.05到0.1低业务上不可信按年份前四年/后一年时间有序指标略降但真实高可上线如果压缩包内数据只覆盖一个自然年时间切分不适用此时应改用分组K折按region分组。分组K折确保同一个地区的多条记录不会同时出现在训练和验证两个集合里避免地理位置泄漏。4.3 过拟合识别特征重要性与早停验证LightGBM使用了早停但早停只防最后一轮不代表整个模型没有过拟合。我一般会在代码里输出特征重要性观察是否有单个特征独占80%以上的重要性lgb.plot_importance(model_lgb, figsize(8, 4))如果region_freq重要性异常高就要怀疑它是否间接编码了目标。region_freq计算的是地区出现次数如果高发地区恰好也是记录多的地区这个特征会变成目标的替身。替换方案是把region_freq从特征列表中去掉只保留category、year、month重新训练对比AUC。最后看一眼早停曲线验证集损失下降变平或开始抬升的轮数就是模型真正的容量边界。把这个轮数配置到全量数据上的n_estimators既能保留性能又能降低过拟合风险。三关过后模型才算真正可用。5. 把预测结果用起来地区风险排序与单条可解释性预测结束后不能只交一张准确率报告得能用一句话解释“为什么某个地区下个月被预测为高发”。常见做法是用shap库对LightGBM模型做解释import shap explainer shap.TreeExplainer(model_lgb) shap_values explainer.shap_values(X_test.iloc[[0]]) shap.initjs() shap.force_plot(explainer.expected_value, shap_values, X_test.iloc[0])如果最终模型是逻辑回归则不需要额外装shap包直接看系数正负再乘上标准化后的特征值即可。shap.force_plot在Jupyter里会输出交互图红色部分是推高预测值的特征蓝色部分是压低预测值的特征。对小数据集这一张图比任何指标都能说服非技术角色。实践中还有一个高频场景另一张新表没有人口数或region_freq只有年份、月份、犯罪类型和案件数这时候要做的是冷启动迁移。原则是模型只在见过的特征空间内有效。我会先构造一个降级版本砍掉population在内部验证集上跑一次如果AUC下降超过0.1说明原模型高度依赖人口特征迁移时要重新收集数据而不是只替换特征列。如果下降在可接受范围内说明预测规律主要由时间与犯罪类型驱动可以直接把三份源代码里的特征列表替换成精简字段再重新走一遍早停。把三份代码连同最终报告归档时保存一个带哈希文件名的时间切分后测试集副本保证下次复现时看到的是同一份结果。这个习惯比任何调参技巧都更能节省返工时间。本文还有配套的精品资源点击获取
返回列表