基础数据预处理
🎤 面试现场作答(Java 研发岗・AI 方向)
面试官您好,关于 AI 基础数据预处理,我结合 Java 工程落地的实践经验来回答。核心本质一句话总结:数据决定模型的效果上限,预处理就是把数据质量拉到这个上限,从根源避免 “垃圾进、垃圾出”。
🔧 标准预处理全流程
核心红线原则:先划分数据集,再做预处理,绝对不能用全量数据的统计量处理训练集,否则会造成数据泄露,直接导致模型上线效果跳水。
📊 核心环节 & 高频考点
1. 数据清洗:解决数据 “脏” 的问题
- 缺失值:缺失占比 < 5% 可直接删除;数值型用中位数 / 均值填充,类别型用众数填充;高价值数据可用模型预测填充
- 异常值:用 3σ 原则、箱线图识别,业务场景可保留(如风控欺诈样本),通用场景可截断或替换
- 重复值:按主键去重,避免样本重复导致模型过拟合
2. 数据转换:统一特征尺度,让模型可学习
面试最高频的两类数值处理对比:
| 处理方式 | 计算逻辑 | 输出范围 | 适用场景 |
|---|---|---|---|
| 最小 - 最大归一化 | (x - 最小值) / (最大值 - 最小值) | 固定在 [0,1] 区间 | 数据分布均匀、无极端异常值,图像像素处理等 |
| Z-Score 标准化 | (x - 均值) / 标准差 | 均值为 0,标准差为 1 | 存在异常值、符合近似正态分布,梯度下降类模型 |
补充特征编码规则:
- 低基数类别特征(如性别、城市)用独热编码
- 高基数类别特征(如用户 ID)用标签编码 / 目标编码
- 文本特征先分词、去停用词,再转 TF-IDF 或词向量
3. 数据规约:降本提效
- 特征降维:PCA 主成分分析,保留核心信息的同时减少计算量
- 数据采样:正负样本不均衡时,做过采样 / 欠采样,避免模型偏向多数类
4. 数据集划分
- 常规比例:训练集:验证集:测试集 = 7:2:1
- 分类任务必须用分层抽样,保证各数据集的类别分布和原始数据一致
💡 Java 生态落地实践
Java 研发做 AI 工程化,预处理不会像算法岗纯依赖 Python,常用技术栈分四类:
- 小规模离线场景:用
Apache Commons Math做基础数值计算,手写归一化、编码逻辑,轻量无额外依赖 - 深度学习场景:用
Deeplearning4j自带的NormalizerStandardize、ImagePreProcessing工具链,和模型训练无缝衔接 - 海量数据场景:用
Spark MLlib的 Java API 做分布式预处理,适配 TB 级数据集的特征工程 - 线上推理场景:预处理逻辑必须和模型一起版本化,保证线上线下统计量完全一致,杜绝训练 - 推理偏差
⚡ 面试高频追问快答
问:为什么决策树类模型不用做归一化?
答:树模型基于信息增益 / 基尼系数分裂节点,特征的数值缩放不会改变分裂点的位置,对特征绝对值不敏感。
问:缺失值直接全删了不行吗?
答:仅缺失占比极低、样本量充足时可用;缺失占比高时删除会改变数据原始分布,导致模型泛化能力下降。
问:数据泄露最常见的诱因是什么?
答:先对全量数据做归一化 / 标准化,再划分数据集,导致测试集的信息提前泄露到训练过程中,是新手最容易踩的坑。
真实面试模拟
真实面试模拟
面试官 😊:
我看你简历上写了几个 AI 落地项目,那咱们从最基础的聊起。拿到一批原始数据,要做预处理,你一般会从哪几个大块入手?
候选人 👨💻:
好的面试官,我习惯把它拆成 洗、转、分、增 四步,就像做菜前先备料。
- 洗:数据清洗,处理缺失、异常、重复。
- 转:特征转换,编码、归一化、文本向量化。
- 分:数据划分,切出训练/验证/测试集。
- 增:数据增强,解决不平衡或扩充样本。
这四步里,我和团队踩过不少坑,下面我边答边细说。
面试官 👍:
不错,思路很清晰。那你先详细说说数据清洗,具体会遇到哪些脏数据?在 Java 技术栈里你是怎么解决的?
候选人 🧹:
好的。我遇到的脏数据主要有三类,对应的处理办法和 Java 实现大概是这样的:
| 脏数据类型 | 我的处理策略 | Java 落地小例子 |
|---|---|---|
| 缺失值 | 数值型用均值/中位数填;类别型设为“未知”;缺失超过 40% 的列直接删 | stream.map(v -> v==null ? mean : v) |
| 异常值 | 用 IQR 或 Z-Score 检测;不是业务异常的做盖帽处理 | 写个工具方法,用 Arrays.sort 找分位数 |
| 重复值 | 按主键去重,但要先和业务方确认时间窗口内的重复是否合理 | Spark 里 dropDuplicates("id") 或 stream.distinct() |
举个填缺失值的极简代码,用 Java 8 Stream 秒懂:
double mean = rawList.stream().filter(Objects::nonNull)
.mapToDouble(Double::doubleValue).average().orElse(0);
List<Double> cleaned = rawList.stream()
.map(v -> v == null || v.isNaN() ? mean : v)
.collect(Collectors.toList());我自己总结了一个小口诀:缺了补,乱了截,重了去,业务异常留日志 😄
面试官 😄:
口诀挺顺。那清洗完就得做特征工程了,比如你面对几百万个用户 ID 这种高基数类别变量,会怎么编码?
候选人 🔄:
面试官问到点上了。高基数直接 One-Hot 会爆维度,我会用 目标编码(Target Encoding) 或 Embedding。
- 像用户 ID 这种几十万级别的,通常先统计每个 ID 对应标签的均值,作为新特征,同时加平滑防止过拟合。
- 如果是神经网络,就把 ID 送入 Embedding 层学习低维稠密向量。
- 低基数的,比如性别、会员等级,老老实实 One-Hot,Smile 库一行搞定。
归一化我也顺带提一下:树模型(XGBoost)可以不做,但神经网络、K-Means 必须做。我一般写个 Normalizer 工具,先划分再归一化,这个顺序是铁律,不然数据泄露上线必炸 💣
面试官 ☕:
说到了划分,那数据划分你具体怎么切?为什么强调顺序?
候选人 ✂️:
是的,这是个血泪坑。
- 比例:常规
6:2:2或8:1:1。 - 时序数据:绝不能随机打乱,必须按时间前好后切,用未来数据训练预测过去,评估就是自欺欺人。
- 类别不平衡:必须用分层抽样,保证各集合里正负比例一致。
- 顺序铁律:一定先划分,再用训练集的均值和标准差去标准化验证集和测试集。如果先全局归一化再切分,等于验证集信息漏到了训练过程,离线指标虚高 3~5 个点,上线直接腰斩。
Java 里用 Spark 的分层抽样可以这么写:
Map<String, Object> fractions = Map.of("0", 0.8, "1", 0.8);
JavaRDD<LabeledPoint> train = data.sampleByKey(true, fractions, seed);这样能保证每个类别的样本都被按比例抽取。
面试官 🤔:
那如果数据本身就不平衡,比如欺诈交易只占 0.1%,你有哪些数据增强手段?
候选人 🔁:
主要从采样层面下手,兼顾算法。
- 过采样:SMOTE 是常用,利用 K 近邻合成少数类新样本。Java 里 Smile 库有现成实现:
SMOTE smote = new SMOTE(features, labels, k=5);
var augmented = smote.augment(ratio);- 欠采样:随机丢弃多数类,但可能丢掉重要信息,通常配合集成学习。
- 文本/图像:回译、同义词替换、随机裁剪旋转,用 Deeplearning4j 或直接调 Python 服务。
核心原则是:增强只作用在训练集,验证集和测试集保持原分布不变,这样才能测出真实泛化能力。
面试官 📊:
前面聊得挺细,你能不能用一个图把整个预处理流程串起来?让我看看你的全局视角。
候选人 🖼️:
没问题,我用一张流程图总结,这个图我和团队新人分享时经常用:
面试官 👏:
总结得很清楚。那你觉得作为一个 Java 研发,和数据预处理打交道,最重要的工程化思维是什么?
候选人 💡:
两个点我觉得最关键:
- 离线在线一致性:训练时怎么处理特征,线上推理就必须用完全相同的逻辑。我们团队后来引入了 Feature Store,统一管理特征定义和计算,彻底避免了线上线下两份代码的维护地狱。
- 数据决定上限:预处理往往占项目时间的 70% 以上,Java 程序员不能只会 CRUD,要善用 Spark/Flink 做流批一体的 ETL,还要懂分区倾斜、Shuffle 优化这些大数据基本功。
面试官 😄:
不错,看得出来你不只是调包,工程落地想得很细。今天的预处理环节就聊到这儿,谢谢你的时间。
