机器学习入门:数据脱敏 数据预处理
起因
前阵子接了个小活儿,要拿一批真实用户数据训练个分类模型,结果打开 csv 一看,姓名、手机号、身份证号全在里面。直接拿去训练肯定不行,万一哪天数据泄露出去,麻烦就大了。以前总觉得数据脱敏是安全团队该操心的事,真轮到自己头上才发现,这活儿不提前想好,等数据已经散出去了再补救,基本就是灾难现场。
顺便把预处理也一起捋一遍,毕竟脱敏完的数据也不能直接喂模型,中间还有一堆脏活累活要干。
数据脱敏:先别急着训练
脱敏的核心思路就一句话:让数据没法对应到具体的人,但尽量保留对模型有用的统计特征。常见的几种做法:
- 掩码:手机号中间四位打成
138****5678,最简单粗暴,但对模型来说这列基本废了 - 哈希:对敏感字段做 SHA256,能保留“两条记录是不是同一个人”的信息,但要小心彩虹表,最好加盐
- 假数据替换:用假名字假号码替换真数据,看起来最“真实”,适合要给别人演示的场景
- 数值扰动:比如薪资字段加减一点随机噪声,保住分布不跑偏
我用得最多的是 Faker 这个库,生成假数据是真的方便:
from faker import Faker
import pandas as pd
fake = Faker('zh_CN')
df = pd.read_csv('raw_data.csv')
df['name'] = [fake.name() for _ in range(len(df))]
df['phone'] = [fake.phone_number() for _ in range(len(df))]
跑完之后每行都换成了假名假号,但性别、年龄段这些特征还在,模型该学的东西不受影响。不然你就慢慢一列一列手动改吧,几千行数据改到天荒地老。
有个坑要注意:如果是拿哈希做脱敏,同一列里相同的原始值必须映射成相同的哈希值,别每行都重新加盐,不然“同一用户出现多次”这个信息就直接丢了。
数据预处理:脏活累活都在这里
脱敏完的数据直接喂模型基本是不行的。我第一次跑的时候报了一堆 NaN 的错,回去扫了一眼才发现,数据比想象中脏多了。
缺失值,先用 df.isnull().sum() 看一眼比例。缺得少的列我一般用中位数或者众数填,缺得太多的就直接删了,硬填进去反而引入噪声:
df['age'].fillna(df['age'].median(), inplace=True)
df = df.drop(columns=['fax_number']) # 缺了92%,留着没意义
标准化,数值特征量纲差太多必须处理,不然梯度下降会被量纲大的特征带偏。用 sklearn 的 StandardScaler 就行:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
df[['income', 'age']] = scaler.fit_transform(df[['income', 'age']])
这里有个经典错误:fit_transform 只能在训练集上做,测试集要用同一个 scaler 去 transform。我一开始图省事对全量数据 fit 了一遍,等反应过来这算数据泄露的时候,之前的实验结果全部作废,又重跑了一遍...
类别变量编码,性别这种类别少的直接 one-hot;城市这种几百个取值的,one-hot 出来维度爆炸,可以用 target encoding 或者按频次编码,视情况选。
顺序很重要
把上面这些串起来,正确的流程是:脱敏 → 处理缺失值 → 划分训练集和测试集 → 在训练集上 fit scaler、算编码映射,再应用到测试集。我一开始把划分放在最后,结果测试集的信息提前渗进了训练过程,指标虚高得离谱,一度以为自己调参天赋异禀,后来才发现是流程错了...
写在最后
整个流程说起来就四步,但每一步都有能踩的坑。我的建议是先把整条 pipeline 写成脚本跑通,再回头慢慢调,不然每改一步都手动敲一遍,太折磨了。数据这块儿功夫下够了,后面调模型的时候真的能省不少事,至少不用半夜对着 NaN 报错发呆了。
评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。