人工智能之核心基础 机器学习
第十五章 数据预处理
文章目录
- [人工智能之核心基础 机器学习](#人工智能之核心基础 机器学习)
- [15.1 数据探索性分析(EDA)](#15.1 数据探索性分析(EDA))
- [🎯 目标:**理解你的数据长什么样**](#🎯 目标:理解你的数据长什么样)
- 步骤1:识别数据类型
- 步骤2:基础统计描述
- [步骤3:缺失值 & 异常值初筛](#步骤3:缺失值 & 异常值初筛)
- 步骤4:特征分布分析
- [15.2 缺失值处理](#15.2 缺失值处理)
- [📌 原则:**先分析原因,再决定策略**](#📌 原则:先分析原因,再决定策略)
- [✅ 常用方法:](#✅ 常用方法:)
- [1. 删除法](#1. 删除法)
- [2. 填充法](#2. 填充法)
- [15.3 异常值处理](#15.3 异常值处理)
- [🔍 识别方法](#🔍 识别方法)
- [🛠️ 处理方法](#🛠️ 处理方法)
- [15.4 特征编码](#15.4 特征编码)
- [📌 分类特征处理](#📌 分类特征处理)
- [🔢 数值特征离散化](#🔢 数值特征离散化)
- [15.5 特征缩放](#15.5 特征缩放)
- [📏 两种主流方法](#📏 两种主流方法)
- [❓ 哪些算法需要缩放?](#❓ 哪些算法需要缩放?)
- [15.6 特征选择](#15.6 特征选择)
- [🔍 三大方法对比](#🔍 三大方法对比)
- [15.7 半监督/自监督专属数据处理](#15.7 半监督/自监督专属数据处理)
- [🧩 无标签数据清洗要点](#🧩 无标签数据清洗要点)
- [🎨 自监督数据增强技巧(关键!)](#🎨 自监督数据增强技巧(关键!))
- [🔗 少量标签与无标签数据的一致性处理](#🔗 少量标签与无标签数据的一致性处理)
- [15.8 特征工程实战:全流程预处理(含半监督场景)](#15.8 特征工程实战:全流程预处理(含半监督场景))
- [场景:电商用户行为预测(少量标签 + 大量无标签)](#场景:电商用户行为预测(少量标签 + 大量无标签))
- [🎯 本章终极总结:数据预处理 Checklist](#🎯 本章终极总结:数据预处理 Checklist)
- 资料关注
15.1 数据探索性分析(EDA)
🎯 目标:理解你的数据长什么样
步骤1:识别数据类型
| 类型 | 特点 | 示例 |
|---|---|---|
| 数值型 | 连续/离散数字 | 年龄、价格、点击次数 |
| 类别型 | 有限个取值 | 性别(男/女)、城市 |
| 有序型 | 类别但有顺序 | 评分(1~5星)、教育程度 |
| 文本型 | 自由文本 | 评论、商品描述 |
| 时间型 | 日期/时间 | 注册时间、交易时间 |
步骤2:基础统计描述
python
import pandas as pd
df = pd.read_csv('data.csv')
print(df.info()) # 数据类型、非空数
print(df.describe()) # 数值特征统计(均值、std、min、max等)
print(df['category'].value_counts()) # 类别分布
步骤3:缺失值 & 异常值初筛
python
# 缺失值比例
print(df.isnull().mean() * 100)
# 异常值:用箱线图快速查看
import matplotlib.pyplot as plt
df.boxplot(column=['income', 'age'])
plt.show()
步骤4:特征分布分析
python
# 数值特征:直方图看是否偏斜
df['income'].hist(bins=30)
plt.title("收入分布")
plt.show()
# 类别特征:条形图
df['city'].value_counts().plot(kind='bar')
plt.show()
💡 关键:
- 收入通常右偏 → 需要对数变换
- 某城市样本极少 → 可能需合并为"其他"
15.2 缺失值处理
📌 原则:先分析原因,再决定策略
| 缺失类型 | 特征 | 处理建议 |
|---|---|---|
| 完全随机缺失(MCAR) | 与任何变量无关 | 可删除或填充 |
| 随机缺失(MAR) | 与其它变量有关 | 推荐模型填充 |
| 非随机缺失(MNAR) | 与自身值有关(如高收入不愿填) | 谨慎处理,可能需新增"是否缺失"标志 |
✅ 常用方法:
1. 删除法
- 行删除 :
df.dropna()→ 仅当缺失比例<5% - 列删除 :
df.drop(columns=['col'])→ 当某列>50%缺失
2. 填充法
python
from sklearn.impute import SimpleImputer, KNNImputer
# 均值/中位数/众数填充
num_imputer = SimpleImputer(strategy='median') # 数值用中位数(抗异常值)
cat_imputer = SimpleImputer(strategy='most_frequent') # 类别用众数
# 插值填充(时间序列)
df['price'].interpolate(method='linear', inplace=True)
# KNN模型填充(利用相似样本)
knn_imputer = KNNImputer(n_neighbors=5)
X_filled = knn_imputer.fit_transform(X)
⚠️ 注意 :填充必须在训练集拟合,再应用于测试集,避免数据泄露!
15.3 异常值处理
🔍 识别方法
| 方法 | 公式/规则 | 适用场景 |
|---|---|---|
| Z-score | ∣ z ∣ > 3 |z| > 3 ∣z∣>3 | 数据近似正态分布 |
| IQR | Q 1 − 1.5 × I Q R Q1 - 1.5 \times IQR Q1−1.5×IQR 或 Q 3 + 1.5 × I Q R Q3 + 1.5 \times IQR Q3+1.5×IQR | 任意分布,推荐! |
| 箱线图 | 可视化IQR结果 | 快速检查 |
python
# IQR方法
Q1 = df['income'].quantile(0.25)
Q3 = df['income'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 标记异常值
df['is_outlier'] = (df['income'] < lower_bound) | (df['income'] > upper_bound)
🛠️ 处理方法
| 方法 | 代码示例 | 适用场景 |
|---|---|---|
| 删除 | df = df[~df['is_outlier']] |
异常值极少(<1%) |
| 修正 | df['income'] = np.clip(df['income'], lower_bound, upper_bound) |
传感器噪声 |
| 隔离 | 新增二值特征 is_income_high |
业务上异常值有意义(如高净值客户) |
💡 重要原则 :
不要盲目删除异常值!先问业务:这是错误数据,还是真实但稀有的情况?
15.4 特征编码
📌 分类特征处理
| 编码方式 | 适用场景 | 优缺点 |
|---|---|---|
| 标签编码(Label Encoding) | 树模型(决策树、XGBoost) | 简单,但引入虚假序关系 |
| 独热编码(One-Hot) | 线性模型、神经网络 | 无序关系,但维度爆炸 |
| 目标编码(Target Encoding) | 高基数类别(如城市ID) | 利用标签信息,但需防过拟合 |
python
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from category_encoders import TargetEncoder
# 标签编码(用于树模型)
le = LabelEncoder()
df['city_encoded'] = le.fit_transform(df['city'])
# 独热编码(用于线性模型)
ohe = OneHotEncoder(sparse_output=False)
city_ohe = ohe.fit_transform(df[['city']])
# 目标编码(需有标签y)
te = TargetEncoder()
df['city_target'] = te.fit_transform(df[['city']], y)
🔢 数值特征离散化
python
# 等宽分箱
df['age_bin'] = pd.cut(df['age'], bins=5, labels=False)
# 等频分箱(每箱样本数相同)
df['income_qbin'] = pd.qcut(df['income'], q=4, labels=False)
✅ 何时离散化?
- 树模型:通常不需要
- 线性模型:可捕捉非线性关系
15.5 特征缩放
📏 两种主流方法
| 方法 | 公式 | 适用算法 |
|---|---|---|
| 标准化(Z-score) | x ′ = x − μ σ x' = \frac{x - \mu}{\sigma} x′=σx−μ | SVM、逻辑回归、PCA、K-Means、神经网络 |
| 归一化(Min-Max) | x ′ = x − x min x max − x min x' = \frac{x - x_{\min}}{x_{\max} - x_{\min}} x′=xmax−xminx−xmin | 神经网络(尤其图像像素0~1) |
python
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# 标准化(推荐默认)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 归一化
minmax = MinMaxScaler()
X_minmax = minmax.fit_transform(X)
❓ 哪些算法需要缩放?
| 需要缩放 | 不需要缩放 |
|---|---|
| SVM、逻辑回归、PCA、K-Means、KNN、神经网络 | 决策树、随机森林、XGBoost |
💡 口诀 :
"基于距离或梯度的算法要缩放,基于规则的不用"
15.6 特征选择
🔍 三大方法对比
| 方法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 过滤式 | 单变量统计(方差、相关系数) | 快、简单 | 忽略特征交互 |
| 包裹式 | 用模型性能评价子集(如RFE) | 考虑交互,效果好 | 计算慢 |
| 嵌入式 | 模型训练中自动选(如Lasso) | 效率与效果平衡 | 依赖特定模型 |
python
from sklearn.feature_selection import VarianceThreshold, SelectKBest, f_classif, RFE
from sklearn.linear_model import LogisticRegression
# 过滤式:低方差特征删除
vt = VarianceThreshold(threshold=0.1)
X_vt = vt.fit_transform(X)
# 过滤式:基于统计检验
skb = SelectKBest(score_func=f_classif, k=10)
X_skb = skb.fit_transform(X, y)
# 包裹式:递归特征消除
estimator = LogisticRegression()
rfe = RFE(estimator, n_features_to_select=10)
X_rfe = rfe.fit_transform(X, y)
# 嵌入式:L1正则化(Lasso)
lasso = LogisticRegression(penalty='l1', solver='liblinear')
lasso.fit(X_scaled, y)
selected = lasso.coef_ != 0
✅ 实践建议 :
先用过滤式 快速降维,再用嵌入式精细筛选。
15.7 半监督/自监督专属数据处理
🧩 无标签数据清洗要点
-
一致性检查 :确保无标签数据与标签数据同分布
python# 用PCA/t-SNE可视化两者是否重叠 all_X = np.vstack([X_labeled, X_unlabeled]) labels = ['labeled']*len(X_labeled) + ['unlabeled']*len(X_unlabeled) # ... 画图检查 -
去重 :无标签数据常含大量重复(如爬虫数据)
pythondf_unlabeled.drop_duplicates(inplace=True)
🎨 自监督数据增强技巧(关键!)
| 模态 | 增强方法 |
|---|---|
| 图像 | 随机裁剪、颜色抖动、旋转、高斯模糊 |
| 文本 | 随机掩码、同义词替换、句子打乱 |
| 表格 | 随机掩码特征、加高斯噪声 |
python
# 图像增强示例(用于对比学习)
from torchvision import transforms
augment = transforms.Compose([
transforms.RandomResizedCrop(224, scale=(0.2, 1.0)),
transforms.RandomHorizontalFlip(),
transforms.ColorJitter(0.4, 0.4, 0.4, 0.1),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
🔗 少量标签与无标签数据的一致性处理
-
统一预处理管道 :
python# 错误做法:分别处理 X_labeled_scaled = scaler.fit_transform(X_labeled) X_unlabeled_scaled = scaler.fit_transform(X_unlabeled) # ❌ 数据泄露! # 正确做法:仅用标签数据拟合 scaler = StandardScaler().fit(X_labeled) X_labeled_scaled = scaler.transform(X_labeled) X_unlabeled_scaled = scaler.transform(X_unlabeled) # ✅
15.8 特征工程实战:全流程预处理(含半监督场景)
场景:电商用户行为预测(少量标签 + 大量无标签)
python
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.impute import SimpleImputer
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
# ========================
# 1. 加载数据
# ========================
# 模拟数据:user_id, age, income, city, clicks, purchase (0/1)
np.random.seed(42)
n_labeled = 200 # 少量标签
n_unlabeled = 2000 # 大量无标签
# 生成有标签数据
df_labeled = pd.DataFrame({
'age': np.random.randint(18, 70, n_labeled),
'income': np.random.exponential(50000, n_labeled),
'city': np.random.choice(['北京', '上海', '广州', '深圳', '其他'], n_labeled),
'clicks': np.random.poisson(10, n_labeled),
'purchase': np.random.binomial(1, 0.3, n_labeled)
})
# 生成无标签数据(同分布)
df_unlabeled = pd.DataFrame({
'age': np.random.randint(18, 70, n_unlabeled),
'income': np.random.exponential(50000, n_unlabeled),
'city': np.random.choice(['北京', '上海', '广州', '深圳', '其他'], n_unlabeled),
'clicks': np.random.poisson(10, n_unlabeled),
'purchase': -1 # 无标签标记为-1
})
# 合并(保留标识)
df_all = pd.concat([df_labeled, df_unlabeled], ignore_index=True)
df_all['is_labeled'] = df_all['purchase'] != -1
# ========================
# 2. EDA
# ========================
print("缺失值比例:\n", df_all.isnull().mean())
print("\n收入分布偏度:", df_all['income'].skew()) # 通常>1,右偏
# ========================
# 3. 缺失值处理(本例无缺失,模拟一个)
# ========================
# 假设income有10%缺失
df_all.loc[np.random.choice(df_all.index, size=int(0.1*len(df_all)), replace=False), 'income'] = np.nan
num_imputer = SimpleImputer(strategy='median')
df_all['income'] = num_imputer.fit_transform(df_all[['income']])
# ========================
# 4. 异常值处理
# ========================
Q1 = df_all['income'].quantile(0.25)
Q3 = df_all['income'].quantile(0.75)
IQR = Q3 - Q1
upper_bound = Q3 + 1.5 * IQR
df_all['income'] = np.clip(df_all['income'], None, upper_bound) # 修正上限
# ========================
# 5. 特征编码
# ========================
# 城市:目标编码(因有少量标签)
from category_encoders import TargetEncoder
te = TargetEncoder()
# 仅用有标签数据拟合
labeled_mask = df_all['is_labeled']
df_all['city_te'] = te.fit_transform(
df_all.loc[labeled_mask, ['city']],
df_all.loc[labeled_mask, 'purchase']
)
# 应用到全部数据
df_all['city_te'] = te.transform(df_all[['city']])['city']
# ========================
# 6. 特征缩放
# ========================
features = ['age', 'income', 'clicks', 'city_te']
scaler = StandardScaler()
# 仅用有标签数据拟合(避免数据泄露)
scaler.fit(df_all.loc[labeled_mask, features])
df_all[features] = scaler.transform(df_all[features])
# ========================
# 7. 特征选择(仅用有标签数据)
# ========================
X_labeled = df_all.loc[labeled_mask, features]
y_labeled = df_all.loc[labeled_mask, 'purchase']
selector = SelectKBest(score_func=f_classif, k=3)
X_selected = selector.fit_transform(X_labeled, y_labeled)
selected_features = np.array(features)[selector.get_support()]
print("选中的特征:", selected_features)
# ========================
# 8. 半监督训练:伪标签法
# ========================
# 步骤1: 用少量标签训练初始模型
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X_labeled[selected_features], y_labeled)
# 步骤2: 预测无标签数据,取高置信度
X_unlabeled = df_all.loc[~labeled_mask, selected_features]
proba = rf.predict_proba(X_unlabeled)
pseudo_labels = rf.predict(X_unlabeled)
high_conf = proba.max(axis=1) > 0.8
# 步骤3: 构建增强训练集
X_train = pd.concat([
X_labeled[selected_features],
X_unlabeled[high_conf]
])
y_train = pd.concat([
y_labeled,
pd.Series(pseudo_labels[high_conf], index=X_unlabeled[high_conf].index)
])
# 步骤4: 重新训练
rf_final = RandomForestClassifier(n_estimators=100, random_state=42)
rf_final.fit(X_train, y_train)
# ========================
# 9. 评估(假设我们有隐藏测试集)
# ========================
# 模拟测试集
X_test = pd.DataFrame({
'age': np.random.randint(18, 70, 500),
'income': np.random.exponential(50000, 500),
'clicks': np.random.poisson(10, 500)
})
# 目标编码需用原te
X_test['city_te'] = te.transform(pd.DataFrame({'city': np.random.choice(['北京','上海','其他'], 500)}))['city']
X_test_scaled = scaler.transform(X_test[features])
X_test_final = pd.DataFrame(X_test_scaled, columns=features)[selected_features]
y_test = np.random.binomial(1, 0.3, 500) # 简化
acc = accuracy_score(y_test, rf_final.predict(X_test_final))
print(f"\n半监督模型准确率: {acc:.2%}")
📊 典型收益:
- 仅用200个标签:准确率 ~70%
- 加入伪标签后:准确率 ~78%
- 若有2000全标签:准确率 ~82% → 用10%标注达到95%性能!
🎯 本章终极总结:数据预处理 Checklist
| 步骤 | 关键问题 | 工具 |
|---|---|---|
| EDA | 数据长什么样?缺失/异常多吗? | pandas, matplotlib |
| 缺失值 | 缺失机制是什么?如何填充? | SimpleImputer, KNNImputer |
| 异常值 | 是噪声还是真实值?如何处理? | IQR, np.clip |
| 编码 | 类别特征怎么转数字? | OneHotEncoder, TargetEncoder |
| 缩放 | 算法需要标准化吗? | StandardScaler |
| 特征选择 | 哪些特征真正有用? | SelectKBest, L1正则 |
| 半监督/自监督 | 无标签数据干净吗?增强够吗? | 数据增强、一致性检查 |
💡 黄金法则 :
"预处理管道必须在训练集上拟合,再应用于验证/测试/无标签数据" ------ 防止数据泄露!
📘 延伸建议:
- 自动化工具:
Feature-engine,tsfresh(时序) - 高级技巧:对抗验证(检查训练/测试分布一致性)
- 未来方向:自动化特征工程(AutoFeat, Tsfresh)
🌟 建议 :
80%的机器学习工作是数据预处理,20%才是建模。把数据弄干净,模型自然就强!
资料关注
公众号:咚咚王
gitee:https://gitee.com/wy18585051844/ai_learning
《Python编程:从入门到实践》
《利用Python进行数据分析》
《算法导论中文第三版》
《概率论与数理统计(第四版) (盛骤) 》
《程序员的数学》
《线性代数应该这样学第3版》
《微积分和数学分析引论》
《(西瓜书)周志华-机器学习》
《TensorFlow机器学习实战指南》
《Sklearn与TensorFlow机器学习实用指南》
《模式识别(第四版)》
《深度学习 deep learning》伊恩·古德费洛著 花书
《Python深度学习第二版(中文版)【纯文本】 (登封大数据 (Francois Choliet)) (Z-Library)》
《深入浅出神经网络与深度学习+(迈克尔·尼尔森(Michael+Nielsen)》
《自然语言处理综论 第2版》
《Natural-Language-Processing-with-PyTorch》
《计算机视觉-算法与应用(中文版)》
《Learning OpenCV 4》
《AIGC:智能创作时代》杜雨+&+张孜铭
《AIGC原理与实践:零基础学大语言模型、扩散模型和多模态模型》
《从零构建大语言模型(中文版)》
《实战AI大模型》
《AI 3.0》