机器学习开篇

        在机器学习领域,一直流传着一句经典名言:数据和特征决定了模型的上限,而算法和模型只是逼近这个上限而已。” 对于刚入门的新手来说,往往容易陷入 “重模型、轻数据” 的误区 —— 拿到数据直接训练,结果模型效果差、收敛慢,却不知道问题出在数据本身。数据预处理作为机器学习 pipeline 中最基础、最耗时(通常占整个项目 60% 以上时间)的环节,直接决定了后续模型训练的稳定性和最终性能。本文将从实战角度,梳理机器学习训练前数据预处理的全流程,涵盖核心步骤、代码实现和避坑要点,帮你打好机器学习的 “地基”

一、为什么数据预处理如此重要?

        在真实业务场景中,原始数据往往存在各种 “缺陷”:缺失值、异常值、数据量纲不统一、特征冗余、数据分布倾斜等。这些问题会直接导致模型训练出现以下问题:

  1. 模型收敛困难:比如特征量纲差异过大(如年龄 1-100、收入 1000-100000),会导致梯度下降算法震荡,难以收敛到最优解;
  2. 模型性能下降:缺失值、异常值会干扰模型学习真实规律,冗余特征会增加计算成本且引入噪声;
  3. 结果不可靠:类别型特征无法直接输入模型,数据分布不平衡会导致模型偏向多数类,预测结果失真。

数据预处理的核心目标,就是清洗数据、规范特征、挖掘有效信息,让数据更适配模型的输入要求,从而最大化模型的学习能力。

二、数据预处理全流程实战(附代码)

        本文以经典的糖尿病数据集(sklearn 内置)泰坦尼克号数据集(Kaggle 经典) 为例,覆盖分类、回归任务的通用预处理步骤,代码基于 Python,依赖库:pandasnumpysklearnmatplotlib

步骤 1:环境准备与数据加载

首先导入所需库,加载数据并快速查看数据基本信息,这是预处理的第一步 ——“知己知彼”

# 导入基础库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split

# 1. 加载回归任务数据(糖尿病数据集)
diabetes = load_diabetes()
X_diabetes = pd.DataFrame(diabetes.data, columns=diabetes.feature_names)
y_diabetes = diabetes.target
print("糖尿病数据集基本信息:")
print(X_diabetes.info())
print(X_diabetes.head())

# 2. 加载分类任务数据(泰坦尼克号数据集,需下载)
# 下载地址:https://www.kaggle.com/c/titanic/data
titanic = pd.read_csv("titanic.csv")
print("\n泰坦尼克号数据集基本信息:")
print(titanic.info())
print(titanic.head())

核心动作:通过info()查看数据类型、缺失值情况,通过head()快速预览数据特征,明确后续处理重点。

步骤 2:数据清洗

        数据清洗是预处理的核心,主要解决缺失值异常值两大问题,保证数据的完整性和准确性。

        2.1 缺失值处理

        缺失值是原始数据中最常见的问题,处理前需先统计缺失率,再根据缺失情况选择策略。

# 1. 统计缺失值
# 泰坦尼克号数据集缺失值统计
print("泰坦尼克号各特征缺失率:")
missing_rate = titanic.isnull().sum() / len(titanic) * 100
print(missing_rate[missing_rate > 0])  # 仅显示有缺失的特征

# 2. 缺失值处理策略
# 策略1:删除法(缺失率>50%,或特征无意义)
titanic = titanic.drop(columns=["Cabin"])  # Cabin缺失率77%,直接删除

# 策略2:填充法(数值型特征+类别型特征)
# 数值型特征:用均值/中位数填充(中位数对异常值更鲁棒)
titanic["Age"] = titanic["Age"].fillna(titanic["Age"].median())

# 类别型特征:用众数填充
titanic["Embarked"] = titanic["Embarked"].fillna(titanic["Embarked"].mode()[0])

# 验证缺失值是否处理完成
print("\n处理后缺失值统计:")
print(titanic.isnull().sum().sum())  # 输出0即处理完成

缺失值处理原则

  • 缺失率<5%:根据特征类型选择均值 / 中位数 / 众数填充;
  • 缺失率 5%-50%:可结合业务逻辑填充(如用相关特征预测缺失值),或引入 “缺失标记”;
  • 缺失率>50%:直接删除特征,避免引入过多噪声。

        2.2 异常值处理

        异常值是指偏离数据整体分布的极端值,会严重干扰模型学习,常用统计方法可视化方法检测。

# 1. 异常值检测(以糖尿病数据集的BMI特征为例)
plt.figure(figsize=(8, 4))
plt.boxplot(X_diabetes["bmi"], vert=False)
plt.title("BMI特征箱线图(异常值检测)")
plt.show()

# 2. 异常值处理策略
# 策略1:删除法(异常值为数据错误,且占比小)
Q1 = X_diabetes["bmi"].quantile(0.25)
Q3 = X_diabetes["bmi"].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 筛选正常数据
X_diabetes_clean = X_diabetes[(X_diabetes["bmi"] >= lower_bound) & (X_diabetes["bmi"] <= upper_bound)]
y_diabetes_clean = y_diabetes[X_diabetes_clean.index]

# 策略2:替换法(异常值为真实数据,用边界值替换)
# X_diabetes["bmi"] = np.where(X_diabetes["bmi"] > upper_bound, upper_bound, X_diabetes["bmi"])
# X_diabetes["bmi"] = np.where(X_diabetes["bmi"] < lower_bound, lower_bound, X_diabetes["bmi"])

异常值处理原则

  • 先通过箱线图、3σ 原则检测异常值,再结合业务判断是否为有效数据;
  • 若为数据录入错误,直接删除;若为真实极端值,用边界值替换或保留(如金融数据中的大额交易)。

步骤 3:特征预处理

        数据清洗完成后,需对特征进行规范化处理,让不同类型的特征适配模型输入要求,主要分为数值型特征类别型特征处理。

        3.1 数值型特征预处理

        数值型特征常见问题:量纲不统一、分布倾斜,需通过标准化归一化对数变换处理。        

from sklearn.preprocessing import StandardScaler, MinMaxScaler

# 1. 标准化(StandardScaler):均值为0,方差为1,适用于正态分布数据
# 以糖尿病数据集为例
scaler_standard = StandardScaler()
X_diabetes_standard = scaler_standard.fit_transform(X_diabetes_clean)
# 转换为DataFrame方便查看
X_diabetes_standard = pd.DataFrame(X_diabetes_standard, columns=diabetes.feature_names)
print("标准化后BMI特征均值:", X_diabetes_standard["bmi"].mean().round(2))  # 接近0
print("标准化后BMI特征方差:", X_diabetes_standard["bmi"].var().round(2))   # 接近1

# 2. 归一化(MinMaxScaler):缩放到[0,1]区间,适用于有明确上下限的数据
scaler_minmax = MinMaxScaler()
X_diabetes_minmax = scaler_minmax.fit_transform(X_diabetes_clean)
X_diabetes_minmax = pd.DataFrame(X_diabetes_minmax, columns=diabetes.feature_names)
print("归一化后BMI特征范围:", X_diabetes_minmax["bmi"].min(), "-", X_diabetes_minmax["bmi"].max())  # 0-1

# 3. 对数变换(处理右偏分布)
# 以泰坦尼克号Fare特征为例(右偏严重)
titanic["Fare_log"] = np.log1p(titanic["Fare"])  # log1p避免0值报错
plt.figure(figsize=(10, 4))
plt.subplot(121)
plt.hist(titanic["Fare"], bins=30)
plt.title("原始Fare分布(右偏)")
plt.subplot(122)
plt.hist(titanic["Fare_log"], bins=30)
plt.title("对数变换后Fare分布")
plt.show()

编码选择原则

  • 无序类别(如性别、城市):用独热编码,避免模型误判类别间的大小关系;
  • 有序类别(如学历、等级):用标签编码,保留类别间的顺序信息;
  • 高基数类别(如用户 ID、商品 ID):独热编码会导致维度爆炸,可采用目标编码、嵌入层等方法。

步骤 4:特征选择

        经过清洗和编码后,特征数量可能过多,存在冗余特征(如高度相关的特征、无关特征),需通过特征选择筛选有效特征、剔除冗余特征,降低计算成本,提升模型泛化能力。

from sklearn.feature_selection import SelectKBest, f_regression, chi2
from sklearn.ensemble import RandomForestRegressor

# 1. 过滤法(Filter):基于统计指标选择特征(适用于回归任务)
# 糖尿病数据集:选择与目标相关性最高的5个特征
selector = SelectKBest(score_func=f_regression, k=5)
X_selected = selector.fit_transform(X_diabetes_standard, y_diabetes_clean)
print("过滤法选择的特征索引:", selector.get_support(indices=True))

# 2. 包装法/嵌入法(Embedded):基于模型权重选择特征(更精准)
# 随机森林特征重要性
rf = RandomForestRegressor(random_state=42)
rf.fit(X_diabetes_standard, y_diabetes_clean)
feature_importance = pd.Series(rf.feature_importances_, index=diabetes.feature_names)
# 选择重要性前5的特征
top_features = feature_importance.nlargest(5).index
X_selected_rf = X_diabetes_standard[top_features]
print("随机森林选择的重要特征:\n", feature_importance.nlargest(5))

特征选择原则

  • 特征数量少(<20):可保留所有特征,无需筛选;
  • 特征数量多(>50):优先用嵌入法(基于树模型、线性模型权重)选择特征,效果优于过滤法;
  • 避免特征冗余:若两个特征相关系数>0.8,可删除其中一个。

步骤 5:数据集划分

        预处理完成后,需将数据划分为训练集、验证集、测试集切记:数据划分必须在特征预处理之后! 避免数据泄露(用测试集信息训练预处理模型)。

# 1. 基础划分(训练集+测试集,比例8:2)
X_train, X_test, y_train, y_test = train_test_split(
    X_selected_rf, y_diabetes_clean, test_size=0.2, random_state=42, shuffle=True
)

# 2. 进阶划分(训练集+验证集+测试集,比例7:1:2)
X_temp, X_test, y_temp, y_test = train_test_split(X_selected_rf, y_diabetes_clean, test_size=0.2, random_state=42)
X_train, X_val, y_train, y_val = train_test_split(X_temp, y_temp, test_size=0.125, random_state=42)  # 0.125=0.1/0.8

print("训练集形状:", X_train.shape)
print("验证集形状:", X_val.shape)
print("测试集形状:", X_test.shape)

划分注意事项

  • 分类任务:若数据不平衡,需使用stratify=y保证分层抽样,让训练 / 测试集的类别分布一致;
  • 时间序列任务:不能随机划分,需按时间顺序划分(如用前 80% 训练,后 20% 测试);
  • 固定random_state:保证实验可复现。

三、数据预处理避坑指南

  1. 数据泄露问题:预处理模型(如 StandardScaler、OneHotEncoder)必须仅用训练集拟合,再转换训练集和测试集,严禁用全量数据拟合,否则会引入测试集信息,导致模型评估失真;
  2. 盲目处理缺失值 / 异常值:处理前需结合业务逻辑判断,比如医疗数据中的 “异常指标” 可能是关键特征,不能随意删除;
  3. 忽略特征分布:线性模型对数据分布敏感,需先做正态化处理;树模型对分布不敏感,但归一化能加速训练;
  4. 特征冗余未处理:过多冗余特征会增加计算成本,还可能导致过拟合,需及时筛选;
  5. 分类任务数据不平衡:若类别比例>10:1,需通过过采样(SMOTE)、欠采样、类别权重调整等方法处理,避免模型偏向多数类。

四、总结

        数据预处理不是 “一次性操作”,而是一个迭代优化的过程:从数据清洗到特征处理,再到特征选择,每一步都需要结合数据特性和业务场景调整。对于机器学习新手来说,与其纠结模型的调参,不如先把数据预处理做扎实 —— 一个干净、规范的数据集,往往比复杂的模型更能带来性能提升。

      注: 本文梳理的预处理流程是通用框架,在实际项目中,还需根据任务类型(分类 / 回归 / 聚类)、数据规模、业务需求灵活调整。建议新手从经典数据集入手,反复练习预处理步骤,逐步形成自己的预处理方法论。数据预处理的核心是 “让数据适配模型,而非让模型适配数据”,做好预处理,你的机器学习项目就成功了一半!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐