机器学习从入门到实战:核心原理、工程流程与经典算法 🤖
从“机器如何学习”出发,建立一条贯穿 数据、模型、损失函数、优化、评估与泛化 的完整知识主线。
阅读导航
- 认知起点:人工智能、机器学习、深度学习分别是什么?
- 学习范式:监督学习、无监督学习、强化学习如何区分?
- 监督学习主线:线性回归 → 数据预处理 → 模型评估 → 欠拟合与过拟合。
- 分类模型:逻辑回归、交叉熵、混淆矩阵与 ROC/AUC。
- 树模型与集成学习:决策树、随机森林、GBDT、XGBoost、LightGBM、CatBoost。
- 无监督学习:K-Means 聚类与 PCA 降维。
⭐ 核心结论 / 面试高频
机器学习的统一范式可以概括为:用数据寻找规律,用模型表达规律,用损失函数衡量误差,再用优化算法更新参数,并通过独立数据评估泛化能力。
1. 从智能到机器学习
1.1 什么是智能
人工智能发展:
- 1956年:“人工智能”(Artificial Intelligence)概念正式提出。
- 1997年:IBM深蓝依靠强大算力击败国际象棋冠军。
- 2016年:DeepMind AlphaGo通过策略网络+强化学习击败人类围棋冠军。
- 2020年:广泛落地于人脸识别、智能驾驶等领域。
⚡ 避坑指南:专用人工智能(ANI)的边界
- 它们在特定任务上表现卓越,但很难跨任务、跨场景迁移能力。
1.2 智能的核心:泛化与迁移
💡 通俗理解:举一反三
- 👶孩子与开水:被烫过一次后,不仅避开那个杯子,还会警惕所有冒热气的物体。不需要把“所有会烫的东西”都试一遍,就能学会避险。
这种“从有限经验中抽取规律,并在陌生环境中做出正确判断”的能力,被称为泛化(Generalization)。
通用人工智能(AGI):一种具备类人理解、推理与学习能力,并能在不同领域中解决问题的系统。
1.3 从仿生学习到数据驱动
💡 通俗理解:人类如何认识苹果
在反复见过无数次苹果后,大脑自动从海量样本中:
- 特征提取:轮廓的弧度、表皮的光泽、果柄的位置、切面的纹理……
- 泛化迁移:面对从未见过的品种(如深紫色、扁平的),依然能快速识别。
神经元的连接在不断的刺激中自行调整,最终在脑海中形成了一个模糊却精准的概念————“苹果”。
这就是现代AI模仿的学习方式。
1.4 AI 的两条发展路线
早期 AI 采用“专家系统”路线,由工程师以“上帝视角”编写规则来定义世界。

主要瓶颈:规则无法穷举。被咬了一口、腐烂一半或处于复杂光照下的物体,是否仍算苹果?
1.5 AI 爆发的基础:数据与算力
经过数百万年演化的人类大脑,能以仅约20瓦的功率高效完成复杂的认知任务。而机器要模拟这种智能,本质上是一场依赖“暴力计算”的模拟游戏。这种“大力出奇迹”的做法,直到最近才集齐了两个关键拼图:
- 海量数据(Data):数据是模型训练的养料。如同人类通过经验学习,AI 需要从大量样本中提取规律。互联网时代积累的文本、图像与视频——从维基百科到社交网络,从数字化典籍到网页记录——构成了规模空前的训练语料与数据集,缓解了早期人工智能的“数据匮乏”问题。
- 强大算力(Compute):深度神经网络的训练涉及大规模参数优化,每次更新都需要大量矩阵运算。GPU 并行计算、专用 AI 芯片与分布式训练系统的发展,使大模型训练成为可能。
数据与算力,是智能得以落地的物理基础,二者缺一不可。正是它们在近十年的同步跃升,才把人工智能从实验室里的论文与原型,推向了每个人的手机屏幕。
1.6 模型的本质:对规律的函数化表达
在数学中,我们通过一个函数曲线来拟合坐标系中的数据点。机器学习本质上也是在拟合一个函数,使其可以:
- 输入一张图片x --> 输出其代表的分类y
- 输入一段语音x --> 输出对应的文字y
- 输入前半句话x --> 输出后半句y
机器学习“找到”的这个函数,就是模型,训练一个AI模型,本质上就是在海量的参数空间里,通过不断试错与优化,搜索出那个最能解释数据背后规律的函数。
机器学习探索:机器学习将这种探索规模化、可重复。在庞大的参数空间里,通过海量计算持续逼近那个能完美描述世界规律的“函数”。
1.7 从机器学习到生成式 AI
- 数据驱动:不再完全依赖预设规则,而是从数据中学习统计关系。
- 典型案例:根据词频与上下文识别垃圾邮件。
- 主要局限:往往依赖人工特征工程。
💡 通俗理解:为什么语言模型可能呈现通用能力?
维特根斯坦认为:语言是世界的压缩。人类几千年的文字记录,编码了我们对世界全量的认知。
说“因为...所以...” --> 编码了因果律
说“虽然...但是...” --> 编码了逻辑转折
讲述一个故事 --> 编码了时间、空间、社会规范
1.8 语言模型:用预测任务学习世界知识
LLM的训练目标看似简单:预测下一个字(Next Token Prediction)
但为了准确预测,模型被迫去“理解”深层逻辑:
- 要预测物理题的答案 --> 必须学会物理规律
- 要预测代码的下一行 --> 必须学会编程逻辑
- 要预测故事的结局 --> 必须学会人物动机
通过学习大规模人类文本,LLM 会捕捉语法、语义、事实关联与部分推理模式,并可能“涌现”出代码生成、问答和多步推理等能力。需要注意的是,高质量的下一个 Token 预测并不等同于拥有与人类完全相同的理解;模型仍可能产生幻觉、偏见和不可靠推理。
⚡ 避坑指南:预测能力不等于完美理解
Next Token Prediction 能促使模型学习大量统计结构和知识,但不能保证事实始终正确,也不能自动消除偏见、幻觉或因果推理缺陷。
当数据与经验可以被大规模获取时,关键问题变成了:
如何把这些数据变成可学习的样本,让机器在反复试错中自行归纳规律?
2. 机器学习的基本概念
人工智能(Artificial Intelligence,AI)是计算机科学的一个分支,致力于创建能够模拟、延伸和扩展人类智能的理论、方法、技术及应用系统。
简单来说,就是让机器具备感知、学习、推理和决策的能力。
机器学习:AI的核心实现路径
机器学习(Machine Learning,ML)研究如何让计算机在无需显式编程的情况下,通过数据自动学习规律,对未来进行预测。
- 人类的常识:发现数据背后的规律(例如:戒烟 + 运动 --> 健康状况改善)。
- 机器学习的意义正是:由计算机来自动完成寻找规律的过程。
传统编程的困境
传统编程必须清楚描述每个步骤,这只适合简单的任务。对于复杂任务(如视觉识别)则束手无策。
难题:如何编写程序识别图像中是否包含苹果?
“写程序覆盖苹果的几何参数、纹理方程或颜色阈值...。这个任务几乎不可能。”
从认知到机器学习
人类如何认识苹果:
- 反复观察无数实物、照片、视频。
- 大脑自动提取关键特征:轮廓的弧度、表皮、光泽、纹理。
- 神经元自行调整,最终形成苹果的概念。
机器学习如何解决:
- 向计算机展示大量图像,并标注是否包含苹果。
- 重复训练,直到计算机自动捕捉模式。
- 面对新图像,使用学习到的模式做出判断。
我们依然需要编写程序,但不是编写最终答案,只是在告诉计算机应该如何学习。
深度学习:基于神经网络的ML
深度学习(DL)本质上仍是机器学习,但它使用了多层神经网络架构,能够使提取更深层次的抽象特征。
- 通常依赖人工设计或筛选特征。
- 擅长处理表格等结构化数据。
- 数据与算力门槛相对较低,广泛用于金融风控、推荐与预测。
AI、ML与DL的包含关系

人工智能(AI)
最宽泛概念:让机器表现出类似人类的智能。
机器学习(ML)
不靠人工规则,从数据中自动寻找规律。
深度学习(DL)
使用神经网络,提取深层抽象模式。
什么是模型(Model)
广义上的模型:
- 对现实世界复杂事物的简化与抽象。
- 目的:更清晰地理解、分析、预测或控制错综复杂的现象。
机器学习领域的模型:
- 本质上是计算机通过学习大量数据后,自主总结出的一套规则集合,用于对未来的新数据进行预测或决策。
算法与模型
- 是学习的方法或步骤。
- 规定如何利用数据、计算损失并更新参数。
- 例如:梯度下降、决策树分裂算法、K-Means 迭代过程。
⭐ 核心结论 / 面试高频
算法负责构建模型,模型负责对新数据进行推断。
建立模型:垃圾邮件分类
场景:周末收到朋友小张的邮件,如何在不打开的情况下判断是否是垃圾邮件。
示例1:基于历史概率的盲猜
回忆历史数据:最近10封邮件,就有6封垃圾邮件。
模型1.0:小张每发送10封邮件,就有6封是垃圾邮件。
预测结构:新邮件有60%的可能是垃圾邮件,盲猜其为垃圾邮件。
示例2:寻找单一特征规律
深入分析邮件的发送时间特征:
| 星期一 | ✅ 正常 | 星期六 | ❌ 垃圾 |
|---|---|---|---|
| 星期二 | ✅ 正常 | 星期六 | ❌ 垃圾 |
| 星期三/四 | ✅ 正常 | 星期日 | ❌ 垃圾 |
模型2.0:工作日发送 = 正常邮件,周末发送 = 垃圾邮件
预测结果:今天是周末,非常自信地看都不看直接扔进垃圾桶!
示例3:多维特征组合
小张周末发的“徒步通知”被误杀!只看时间太绝对,引入新特征:文件大小。
| ✅ 正常 | ❌ 垃圾 | ||
|---|---|---|---|
| 3 KB | ✅ 正常 | 20~35 KB | ❌ 垃圾 |
模型 3.0:如果是周末发送且大小
预测结果:成功拦截周末发的大附件广告,并顺利接收了周末3KB的徒步通知。
机器学习本质:寻找最优权重
“发送时间”、“邮件大小”被称为预测的特征(Feature)。
人类一次智能处理少量特征,计算机能快速分析数百万邮件和上万特征。算法会为不同特征分配权重(Weight)。
- 如果包含“免费”,权重极高(+50分)
- 如果大于10KB,权重较高(+30分)
- 若总得分 > 60分 --> 判定为垃圾邮件
如何精准确定每一个特征的权重?
让计算机通过大量历史数据,不断自动调整寻找这组最优权重,这就是机器学习。
机器学习的核心思想:
- 用数据寻找规律。
- 用模型表达规律。
- 用损失函数衡量误差。
- 用梯度下降优化参数。
- 不断训练让模型变得更准确。
几乎所有的机器学习和深度学习模型,都可以看作是对上述基本范式的扩展和升级。
3. 机器学习的三种基本范式
数据是用于训练和测试机器学习模型的原始信息。在日常应用中,我们最常遇到的是结构化数据,即以表格形式(如Excel或数据库)存储的数据:
- 每一行代表一个具体的实体。
- 每一列代表一项特定的属性。
结构化数据集(具体的房价数据集表格):
| 面积 (㎡) | 卧室数量 | 浴室数量 | 地段评分 | 房龄 (年) | 房价 (万元) |
|---|---|---|---|---|---|
| 85 | 2 | 1 | 6 | 15 | 180 |
| 110 | 3 | 1 | 7 | 8 | 320 |
| 142 | 4 | 2 | 9 | 3 | 510 |
| 73 | 2 | 1 | 4 | 20 | 120 |
| 128 | 3 | 2 | 8 | 5 | 450 |
基础概念解析:
- 数据集(Dataset):通常指上面这一整张数据表格。
- 数据点(Data Point):也称样本,数据集中的单条记录,表格的每一行就是一个数据点。
- 特征(Feature):表示数据的属性或性质。在表格中,特征就是表格的列。例如,每个房屋有5个特征,我们就称这个数据集的维度(Dimension)为5。
- 标签(Label):模型需要重点预测的“目标变量”(即表格最后一列)。所谓预测,本质上就是让机器通过学习特征,去推算数据中的标签。
机器学习的三大分支
根据数据集是否带有标签,以及学习方式的不同,机器学习衍生出三大主要分支:
数据集包含标签。模型学习特征到标签的映射,用于完成回归或分类。
监督学习
监督学习:处理标签数据
监督学习的应用十分广泛,涵盖图像识别、文本处理、推荐系统等众多领域。
- 其核心目标是根据已有数据预测标签。
在房价数据集中,“房价”就是我们要预测的标签。模型通过学习这些现有的历史数据找出规律,进而预测新数据的标签。这意味着,如果我们向模型输入一套缺失价格信息的新房子的数据,模型就能自动预测出它的价格。
监督学习的两类核心模型
针对不同类型的标签数据,监督学习衍生出了两类模型:
- 输出:连续数值。
- 场景:房价预测、销量预测、能耗预测。
- 算法:线性回归、回归树、随机森林回归、XGBoost 回归。
线性回归
线性回归(Linear Regression)是机器学习中最经典的模型之一,它通过构建线性方程来拟合数据的内在分布规律,从而实现对连续数值的预测。
在数学上,线性描述的是输入与输出之间按比例变化、可叠加的关系。一个映射
- 齐次性:
- 可加性:
几何解释:在一维空间中,线性关系表现为一条直线;在高维空间中,表现为平面或超平面。
高尔顿的“回归均值”
“回归”(Regression)一词源于统计学家佛朗西斯·高尔顿(Francis Galton)的经典研究。他观察到,身高极高或极矮的父母,其子女的身高往往会向群体平均值靠拢,这一现象被称为“回归均值”。
在机器学习领域中,回归的概念被进一步引申:旨在通过数据建模变量间的映射关系,进而实现对连续数值的预测。
模型公式
最简单的线性回归模型只有一个特征(如房屋面积),预测公式为:
:模型的预测值(如预测房价)。 :输入特征(如面积)。 :权重(weight,斜率)————面积每增加1 ,房价变化多少 。 :偏置(bias,截距)————当面积为0时的理论基准值。 这里的w和b就是模型需要学习的参数。训练模型本质上就是搜索一组最合适的w和b。
损失函数:衡量模型准不准
模型要找到最好的参数,首先得回答一个问题:当前这组参数到底好不好?
我们需要一种方法来度量预测值与真实值之间的偏差————这就是损失函数(Loss Function)。
均方误差(Mean Squared Error,MSE):
- 对每个样本,计算预测值 - 真实值(即误差)
- 将误差取平方(消除正负号、放大较大误差)
- 对所有样本求平均
- 预测值离真实值越远,损失越大。模型训练的目标,就是让损失函数的值尽可能小。
使用MSE的两个优点:
- 消除正负号的影响:让误差绝对值化,防止求和之后正负误差互相抵消。
- 放大误差便于优化:例如误差为3和0.4,平方后变为9和0.16,差异更明显。(惩罚更大)
梯度下降:寻找最优解
知道当前模型有多不准之后,如何自动调整w和b?
最通用的算法是梯度下降(Gradient Descent):沿着损失函数下降最快的方向,逐步调整参数,直至找到最优解。
形象比喻:蒙眼下山
- 感知坡度(计算梯度):盲人用脚探知当前所处的坡度方向,确定哪里是下坡。
- 迈出一小步(更新参数):顺着最陡峭的下坡方向(负梯度方向),迈出一步。
- 循环往复(迭代优化):在新位置继续感知坡度,继续向下走。
- 到达谷底(模型收敛):当走到再也没有下坡路的地方,即认为到达了最低点。
模型训练的核心循环
把下山的比喻和机器学习概念对应起来:
- 山的当前高度= 损失函数的值(Loss)
- 下坡的方向 = 梯度(Gradient,即导数)
- 每一步迈多大 = 学习率(Learning Rate)
⭐ 核心结论 / 面试高频
模型训练的闭环是:前向计算预测 → 计算损失 → 反向计算梯度 → 更新参数 → 在验证数据上检查泛化能力。
每完成一轮循环参数就更接近一点,经过多轮迭代模型逐渐收敛到最优解。
多特征线性回归
影响房价的因素包括:面积、楼层、距地铁距离、房龄等。
| 面积 (㎡) | 楼层 | 距地铁 (km) | 房龄 (年) | 总价 (万元) |
|---|---|---|---|---|
| 50 | 3 | 0.5 | 5 | 180 |
| 70 | 15 | 1.2 | 10 | 210 |
| 90 | 8 | 0.3 | 2 | 320 |
| 110 | 20 | 2.0 | 15 | 280 |
| 130 | 12 | 0.8 | 8 | 380 |
现在有d个特征,模型自然地扩展为:
(面积):➕正,面积越大,房价越高。 (楼层):➕正,中高楼层通常更贵。 (距地铁):➖负,离地铁越远,房价越低。 (房龄):➖负,房子越老,房价越低。
代码实现:
使用Python的scikit-learn库可以非常方便地完成多特征线性回归的训练和预测:
🔍 展开查看:代码实现
import numpy as np
from sklearn.linear_model import LinearRegression
# 1. 准备数据:4个特征(面积、楼层、距地铁、房龄)
X = np.array([
[50, 3, 0.5, 5],
[70, 15, 1.2, 10],
[90, 8, 0.3, 2],
[110, 20, 2.0, 15],
[130, 12, 0.8, 8],
])
y = np.array([180, 210, 320, 280, 380]) # 总价(万元)
# 2. 训练模型
model = LinearRegression()
model.fit(X, y)
# 3. 查看学到的权重
feature_names = ['面积', '楼层', '距地铁', '房龄']
print("各特征权重:")
for name, w in zip(feature_names, model.coef_):
print(f" {name}: {w:+.2f}")
print(f" 偏置b: {model.intercept_:.2f}")
# 4. 预测新数据:[100, 10, 0.6, 5]
new_house = np.array([[100, 10, 0.6, 5]])
pred = model.predict(new_house)
print(f"\n预测房价:{pred[0]:.1f}万元")2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
输出结果:
🔍 展开查看:代码实现
各特征权重:
面积: +2.63
楼层: +1.77
距地铁: -1.23
房龄: -8.60
偏置b: 86.91
预测房价:323.7万元2
3
4
5
6
7
8
用向量简化表达
当特征很多时,把公式全部展开会非常冗长。借助向量记号,可以写得更简洁:
其中:
- 不管是1个特征还是100个特征,模型的本质结构完全一样————都是特征的加权求和再加偏置。
多项式回归:当直线不够用时
现实中的数据并不总是沿着一条直线分布。比如当房价随面积的增长先慢后快时,直线就无法很好地拟合了。这时可以引入多项式回归:
- 当
时,就是普通的线性回归。 - 当
时,拟合出的是一条抛物线;阶数越高,曲线越灵活。
尽管公式中出现了
无监督学习
无监督学习:处理无标签的数据
在现实的业务场景中,获取带有标签的数据往往成本很高,我们手中绝大多数的数据其实是没有标签的。
- 无监督学习的目标不再是去预测某个具体的数值或类别,而是让模型自行探索,发现数据内部隐藏的结构、模式或规律。
无监督学习在实际业务中同样十分常见,主要包括三个方向:聚类、降维与生成算法。
聚类
核心作用:将数据集分组
面对海量数据时分类无从下手?聚类算法会计算数据之间的相似度,将行为模式相似的数据自动归拢到一个组(簇)里,实现物以类聚。
代表算法:K-means算法
案例:电商百万用户精准营销
面对海量浏览记录,人工无法打标签。通过聚类,平台能在无干预情况下,自动梳理出“价格敏感型”或“品牌忠诚型”等清晰用户画像,方便精准推荐。
降维
核心作用:信息整合与特征重构
在处理复杂数据时,特征过载会导致“维度灾难”。降维的目标是在尽量不丢失关键信息的前提下,极大地简化数据集。
代表算法:主成分分析(PCA)
案例:房价预测中的周边环境特征
“距离地铁站米数”、“学校数量”、“商场个数”等几十个特征会让计算缓慢且冗余。降维算法能捕捉这些特征的相关性,将它们合并压缩成一个新特征,如“综合地段评分”。
生成
生成算法(AIGC)
核心作用:从数据中创造全新内容
当面临数据匮乏(如医疗AI隐私限制)或创作瓶颈时,生成算法通过深度学习真实数据的分布规律,生成现实中不存在的全新数据点。
代表算法:生成对抗网络(GAN)
应用场景:
- 生成极其逼真的虚拟人脸或病理切片,低成本扩充模型训练集。
- AIGC应用:AI绘画工具仅凭文本提示生成高质量画作。
强化学习
强化学习:在动态环境中试错
在复杂的现实场景中(如自动驾驶实时决策、围棋落子),我们面临的是高度动态的挑战,并没有现成的静态历史数据集。
- 强化学习的核心是:在不断试错的过程中,学习如何做出更优决策。
运行机制:设定一个智能体(Agent)置身于环境中。智能体采取动作,环境根据动作的好坏实时反馈奖励(Reward)或惩罚(Penalty)。终极目标是探索出一套能获得最大长期累计奖励的策略。
强化学习的里程碑与应用
最广为认知的里程碑是2016年击败人类世界冠军的AlphaGo,其通过左右互搏、在数百万局的试错中进化出了超越人类的策略。
关键技术:
经典的Q-Learning算法,以及结合了神经网络的深度强化学习(DRL)技术,正在让AI学会像人类一样思考与行动。
广泛应用领域:
- 复杂棋牌与电子游戏
- 机器人姿态控制
- 工业机械臂调度
- 量化交易策略制定
- 大型语言模型的人类意图对齐
4. 监督学习完整工作流:线性回归
梯度下降
损失函数与调参方向
损失函数用来度量模型预测值
- 损失函数能告诉我们当前参数有多不准。
- 训练的目标:让损失函数的值尽可能小。
- 问题:如何决定是把参数调大一点,还是调小一点?
💡我们需要一种自动调参的算法————梯度下降(Gradient Descent)。
偏导数:多变量函数的变化率
损失函数通常依赖于多个参数(如w和b)。我们需要固定其他变量不变,只对其中一个变量求导。
梯度下降核心原理
把所有变量的偏导数组合成一个向量,就得到了梯度(Gradient)。
- 梯度的方向,是函数值增长最快的方向。
- 梯度的反方向,是函数值下降最快的方向。
梯度下降的迭代公式
基于梯度下降的性质,参数更新公式为:
:当前位置。 :脚下最陡的上坡方向。 :学习率(Learning Rate),控制每一步迈多大。 :朝下坡方向迈一步。
🤔为什么要减去梯度?
🤓因为我们要最小化损失函数,所以需要往反方向走。
线性回归的梯度推导
🔍 展开查看:MSE 对权重与偏置的求导
损失函数为:
使用链式法则分别对
因此,误差项
房价与面积线性回归
假设房价与面积呈线性关系
我们的目标是通过梯度下降算法,从数据中学习出参数w和b。
1.生成模拟数据
我们生成100套房屋的数据,面积范围为 90~150 平方米。
为了使梯度下降更稳定,我们将面积单位转换为百平米,这样数据范围变为0.9-1.5。
真实规律:
等价于:
🔍 展开查看:代码实现
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
np.random.seed(42)
n_samples = 100
areas_m2 = np.random.uniform(90, 150, n_samples)
areas = areas_m2 / 100
prices = 250 * areas + 30 + np.random.randn(n_samples) * 15
print(f"房屋数量: {len(areas)} 套")
print(f"面积范围: {areas_m2.min():.1f} - {areas_m2.max():.1f} 平米 ({areas.min():.2f} - {areas.max():.2f} 百平米)")
print(f"价格范围: {prices.min():.1f} - {prices.max():.1f} 万")
print(f"真实规律: 房价 = 2.5 × 面积(平米) + 30")2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2.梯度下降算法
梯度下降通过计算损失函数对参数的梯度,不断更新参数:
其中
🔍 展开查看:代码实现
# 梯度下降
def gradient_descent(X, y, lr = 0.1, epochs = 200):
# 初始化参数
w, b = 0.0, 0.0
n = len(X)
# 初始化历史记录,使用记录初始参数和损失
history = {'w': [w], 'b': [b], 'loss': [np.mean((w * X + b - y) ** 2)]}
# 进行迭代训练,更新参数
for epoch in range(epochs):
# 计算预测值和损失
y_pred = w * X + b
loss = np.mean((y_pred - y) ** 2)
# 计算梯度
dw = (2/n) * np.sum((y_pred - y) * X)
db = (2/n) * np.sum(y_pred - y)
# 更新参数
w -= lr * dw
b -= lr * db
# 记录历史参数和损失
history['w'].append(w)
history['b'].append(b)
history['loss'].append(loss)
# 每隔 1% 的训练周期打印一次当前参数和损失
if epoch % (epochs // 100) == 0:
print(f"Epoch {epoch} - w: {w:.2f}, b: {b:.2f}, Loss: {loss:.2f}")
return history2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
3.可视化结果
左图展示拟合效果,中图展示参数收敛过程,右图展示损失函数下降过程。

学习率的作用
学习率(Learning Rate)控制参数更新的步长:
- 学习率太小:收敛速度慢,需要更多迭代。
- 学习率太大:可能震荡甚至发散,无法收敛。
- 合适的学习率:快速稳定地收敛到最优解。
下面我们对比不同学习率的效果。


梯度下降的优化算法
标准梯度下降存在收敛慢、易陷入局部最优等局限。业界发展出了多种优化器:
- 动量法(Momentum):借鉴物理学惯性,给梯度加上“速度”,减少狭长山谷中的震荡。
- AdaGrad:为每个参数自动维护独立的学习率,适合处理稀疏数据。
- RMSProp:AdaGrad的改良,关注近期的梯度变化,避免学习率降到零,适合非平稳目标(如RNN)。
- Adam(自适应矩估计):结合了动量(方向加速)和RMSProp(自适应步幅)。它同时维护梯度的方向趋势和大小变化,既跑得快又稳。收敛快且对学习率不敏感,是目前深度学习中最广泛使用的默认首选优化器。
使用Adam优化器加速训练
🔍 展开查看:代码实现
def adam_optimizer(X, y, lr = 0.1, epochs = 200, beta1 = 0.9, beta2 = 0.999, epsilon = 1e-8):
# 初始化参数
w, b = 0.0, 0.0
n = len(X)
# 初始化一阶矩和二阶矩
m_w, m_b = 0.0, 0.0
v_w, v_b = 0.0, 0.0
# 初始化历史记录,使用记录初始参数和损失
history = {'w': [w], 'b': [b], 'loss': [np.mean((w * X + b - y) ** 2)]}
# 进行迭代训练,更新参数
for epoch in range(epochs):
# 计算预测值和损失
y_pred = w * X + b
loss = np.mean((y_pred - y) ** 2)
# 计算梯度
dw = (2/n) * np.sum((y_pred - y) * X)
db = (2/n) * np.sum(y_pred - y)
# 更新一阶矩和二阶矩
m_w = beta1 * m_w + (1 - beta1) * dw
m_b = beta1 * m_b + (1 - beta1) * db
v_w = beta2 * v_w + (1 - beta2) * dw ** 2
v_b = beta2 * v_b + (1 - beta2) * db ** 2
# 偏置修正
m_w_hat = m_w / (1 - beta1 ** (epoch + 1))
m_b_hat = m_b / (1 - beta1 ** (epoch + 1))
v_w_hat = v_w / (1 - beta2 ** (epoch + 1))
v_b_hat = v_b / (1 - beta2 ** (epoch + 1))
# 更新参数
w -= lr * m_w_hat / (np.sqrt(v_w_hat) + epsilon)
b -= lr * m_b_hat / (np.sqrt(v_b_hat) + epsilon)
# 记录历史参数和损失
history['w'].append(w)
history['b'].append(b)
history['loss'].append(loss)
# 每隔 1% 的训练周期打印一次当前参数和损失
if epoch % (epochs // 100) == 0:
print(f"Epoch {epoch} - w: {w:.2f}, b: {b:.2f}, Loss: {loss:.2f}")
return history
# ==================== 运行测试 ====================
# 提示:Adam 的收敛速度通常比普通梯度下降快很多。
# 原来需要 100,000 次,现在可能 10,000 ~ 50,000 次就足够收敛了。
# 学习率对 Adam 来说通常设为 0.1,0.01 或 0.001 都可以自适应调整。
history = adam_optimizer(areas, prices, lr=0.1, epochs=50000)
print(f"最终结果:房价 = {history['w'][-1]/100:.3f} × 面积(平米) + {history['b'][-1]:.2f}")2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
梯度下降的变体
每次更新参数时,该用多少数据来计算梯度?
| 变体 | 数据量 | 特点 |
|---|---|---|
| 全量 (BGD) | 全部数据 | 梯度精确,稳定,但计算开销极大。 |
| 随机 (SGD) | 1 条数据 | 轻量,有随机性,但噪声大、震荡剧烈。 |
| 小批量 (Mini-batch) | 一小批 (如 64) | 折中方案。能利用 GPU 并行,兼顾精度与效率。 |
Mini-batch已经成为深度学习训练的业界主流选择。
数据预处理
真实世界中的数据往往充满各种问题:
- 存在缺失值和异常值。
- 包含文本、类别等非数值类型。
- 量纲差异巨大(房价是百万级,房间是个位数)。
数据处理的四大常用工具:
Pandas:
- 表格数据处理的瑞士军刀,提供DataFrame等核心数据结构。
Matplotlib:
- Python最基础的数据可视化库。
Seaborn:
- 基于Matplotlib的高级可视化库,语法更简洁、图表更美观。
Scikit-Learn(sklearn):
- 机器学习标准库,包含丰富的预处理工具和常用算法实现。
准备环境与数据
准备环境与数据
以经典的Titanic(泰坦尼克号)生存预测数据集为例:
🔍 展开查看:代码实现
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
# 设置绘画风格
sns.set_style("whitegrid")
# 中文标签
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans'] # 设置中文字体为SimHei
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# 从网络读取泰坦尼克号数据集
url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
df = pd.read_csv(url)
# 查看数据概览
print("数据集形状:", df.shape)
print("\n数据集前五行:")
display(df.head())
print("\n数据类型与缺失值情况:")
print(df.info())2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
观察df.info()的输出会发现Age(年龄)、Cabin(客舱号)和Embarked(登船港口)存在缺失值,且包含文本类型的列,这正是我们需要处理的“脏数据”。
数据集划分
正确的做法是先划分数据集,再做预处理。通常将数据集划分为三个部分:
- 训练集(60%~80%):训练模型,学习数据中的规律。
- 验证集(10%~20%):调整超参数,进行模型选择与早停。
- 测试集(10%~20%):仅在流程定型后用于一次或少量最终评估,以估计泛化能力。
⚡ 避坑指南:防止数据泄漏
所有需要学习统计量或结构的 fit 操作——包括缺失值填充、标准化、特征选择、PCA 与模型训练——都只能使用训练数据。验证集、测试集只执行 transform / predict。交叉验证时应将这些步骤放入 Pipeline。
🔍 展开查看:代码实现
from sklearn.model_selection import train_test_split
# 定义特征X和目标标签y(生存情况)
X = df.drop('Survived', axis=1)
y = df['Survived']
# 将数据集划分为训练集和测试集,比例为80%训练集和20%测试集
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
random_state=42, # 固定随机种子,以确保结果可复现
stratify=y # 按照目标标签y进行分层抽样,确保训练集和测试集中生存情况的比例相同
)
print(f"训练集:{X_train.shape[0]}条数据")
print(f"测试集:{X_test.shape[0]}条数据")
print(f"训练集中的生存率:{y_train.mean():.2%}")
print(f"测试集中的生存率:{y_test.mean():.2%}")2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
设置stratify=y后,sklearn会确保两者中正负样本比例与原始数据保持一致。接下来的所有探索和规则制定都必须只在X_train上进行!
数据探索分析
数据探索分析(EDA,Exploratory Data Analysis)就像是给数据做一次全面体检,直观的发现数据中的问题和规律。
基本信息
使用.info()查看数据集的数据类型和缺失值情况:
# 查看训练集数据类型和缺失值情况
print("\n训练集数据类型与缺失值情况:")
print(X_train.info())2
3
🔍 展开查看:代码实现
# 缺失值统计
missing = X_train.isnull().sum()
missing_pct = ((missing / len(X_train)) * 100).round(1)
pd.DataFrame({
'缺失值数量': missing,
'缺失值比例(%)': missing_pct
}).query('缺失值数量 > 0')2
3
4
5
6
7
8
Cabin缺失77.5% ———— 缺失极多,信息量匮乏。Age缺失19.2% ———— 有明显缺失,需要填充。Embarked缺失0.3% ———— 仅2条记录缺失。
数值特征统计
X_train.describe().style.format("{:.1f}")
| PassengerId | Pclass | Age | SibSp | Parch | Fare | |
|---|---|---|---|---|---|---|
| count | 712.0 | 712.0 | 575.0 | 712.0 | 712.0 | 712.0 |
| mean | 444.4 | 2.3 | 29.8 | 0.5 | 0.4 | 31.8 |
| std | 257.5 | 0.8 | 14.5 | 1.1 | 0.8 | 48.1 |
| min | 1.0 | 1.0 | 0.4 | 0.0 | 0.0 | 0.0 |
| 25% | 222.8 | 2.0 | 21.0 | 0.0 | 0.0 | 7.9 |
| 50% | 439.5 | 3.0 | 28.5 | 0.0 | 0.0 | 14.5 |
| 75% | 667.2 | 3.0 | 39.0 | 1.0 | 0.0 | 31.0 |
| max | 891.0 | 3.0 | 80.0 | 8.0 | 6.0 | 512.3 |
- 注意到
Fare最大512,最小0,量纲差异巨大,后续需要缩放。
数据可视化
使用Matplotlib和Seaborn对数据进行可视化,看出更多端倪:

通过以上可视化,我们得出六条关键发现,它们将直接指导后续的预处理策略:
| 发现 | 对应处理 |
|---|---|
| 女性存活率远高于男性 (~74% vs ~19%) | Sex 是强特征,需编码为数值 |
| 一等舱存活率最高 (~63%) | Pclass 是强特征,考虑保留或独热编码 |
| Age 缺失约 20% | 需要填充,分布略偏态,选用中位数 |
| Cabin 缺失 77% | 直接填充意义不大,考虑转化为"有/无客舱信息" |
| Fare 严重右偏 (均值 > 中位数,长尾分布) | 可能需要对数变换或截断处理 |
| PassengerId、Name、Ticket 对预测无直接帮助 | 考虑删除,或从 Name 中提取有用信息 |
数据清洗
处理重复值:数据清洗的核心环节通常涵盖重复值剔除、缺失值填补以及异常值检测与处理。
对于数据中重复的行直接删除即可:
🔍 展开查看:代码实现
# 检查重复行
duplicate_count = X_train.duplicated().sum()
print(f"训练集中重复行的数量:{duplicate_count}")
# 如果有重复行,删除
if duplicate_count > 0:
X_train = X_train.drop_duplicates()
y_train = y_train[X_train.index] # 同步删除对应的标签行
print(f"删除重复行后,训练集形状:{X_train.shape}")2
3
4
5
6
7
8
9
处理缺失值:当数据集中出现缺失值时,处理策略需根据缺失比例及特征类型灵活选择:
按缺失比例决定:
- 缺失极低(
): - 且为随机缺失 -> 直接删除对应样本行
- 缺失极高(
): - 该特征信息量匮乏,通常直接删除整列
按特征类型选择:
- 数值型特征:
- 用均值或中位数填充。中位数对极端值不敏感,在数据偏态时更加稳健。
- 类别型特征:
- 用众数填充,或新增“Unknown”类别保留业务信息。
使用sklearn.impute.SimpleImputer处理缺失值:
🔍 展开查看:代码实现
from sklearn.impute import SimpleImputer
# —— Age: 中位数填充(因为年龄分布可能有偏,使用中位数更稳健)——
age_imputer = SimpleImputer(strategy='median')
age_imputer.fit(X_train[['Age']]) # 只拟合训练集的Age列
X_train['Age'] = age_imputer.transform(X_train[['Age']]).ravel() # 填充训练集的Age列
X_test['Age'] = age_imputer.transform(X_test[['Age']]).ravel() # 同样用训练集的中位数填充测试集的Age列
# —— Embarked: 众数填充(因为Embarked是分类变量且只缺2个,使用众数填充最合理)——
embarked_imputer = SimpleImputer(strategy='most_frequent')
embarked_imputer.fit(X_train[['Embarked']]) # 只拟合训练集的Embarked列
X_train['Embarked'] = embarked_imputer.transform(X_train[['Embarked']]).ravel() # 填充训练集的Embarked列
X_test['Embarked'] = embarked_imputer.transform(X_test[['Embarked']]).ravel() # 同样用训练集的众数填充测试集的Embarked列
# —— Cabin: 缺失 77%,先转化为“有无客舱信息”,再删除原列 ——
X_train['Has_Cabin'] = X_train['Cabin'].notna().astype(int) # 有客舱信息为1,无为0
X_test['Has_Cabin'] = X_test['Cabin'].notna().astype(int) # 同样处理测试集
X_train.drop(columns=['Cabin'], inplace=True) # 删除原Cabin列
X_test.drop(columns=['Cabin'], inplace=True) # 同样删除测试集的Cabin列
# 查看训练集和测试集的情况
print("\n处理缺失值后训练集数据类型与缺失值情况:")
print(X_train.info())
print("\n处理缺失值后测试集数据类型与缺失值情况:")
print(X_test.info())2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
异常值检测
异常值(Outliers)是指显著偏离其他观测值的极端值。可以使用箱线图检测:

- Age(年龄):集中在20-40岁,60-80岁有离群点(高龄乘客,真实存在)。
- Fare(票价):极度右偏。绝大多数票价极低,极少数超过500美元。不处理会让模型过度关注高价票。
- SibSp(亲属数):大部分是0或1,少数大家庭(3-8个亲属)也是真实的异常值。
处理异常值
使用统计学方法(IQR)识别并处理票价(Fare)中的极端异常值。采用截断策略,将过高的票价强行拉回到合理范围内。
🔍 展开查看:代码实现
# 用IQR方法检测
def detect_outliers_iqr(data, column, factor=1.5):
Q1 = data[column].quantile(0.25) # 计算25分位数
Q3 = data[column].quantile(0.75) # 计算75分位数
IQR = Q3 - Q1
lower = max(0, Q1 - factor * IQR) # 下界不能小于0
upper = Q3 + factor * IQR
outliers = data[(data[column] < lower) | (data[column] > upper)]
print(f"{column}: Q1={Q1:.2f}, Q3={Q3:.2f}, IQR={IQR:.2f}")
print(f"{column}: 下界={lower:.2f}, 上界={upper:.2f}")
print(f"{column}: 异常值数量={len(outliers)}(占比{len(outliers)/len(data)*100:.2f}%)")
return lower, upper
lower, upper = detect_outliers_iqr(X_train, 'Fare')
# 对于 Fare,我们选择截断而不是删除
# 因为高票价可能是真实的,头等舱确实很贵
X_train['Fare'] = X_train['Fare'].clip(lower=lower, upper=upper) # 将Fare限制在合理范围内
X_test['Fare'] = X_test['Fare'].clip(lower=lower, upper=upper) # 同样处理测试集的Fare列2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
特征编码
目的:将文本映射为数值,使模型能进行数学运算。
核心区别:是否保留顺序、是否引入数值大小。
- 序数编码:保留严格顺序。
- 独热编码:无大小顺序之分。
- 标签编码:引入数值大小(不保证顺序)。
序数编码(Ordinal Encoding)
将类别映射为整数,严格保留顺序关系(如:低=0,中=1,高=2)。
适用场景:特征本身具有明确等级(如学历、满意度、舱位)。
🔍 展开查看:代码实现
from sklearn.preprocessing import OrdinalEncoder, OneHotEncoder
# 构造模拟数据:['Low', 'Medium', 'High']
oe = OrdinalEncoder(categories=[['Low', 'Medium', 'High']]) # 显式指定类别顺序
df_ordinal['fare_cat_encoded'] = oe.fit_transform(df_ordinal[['fare_category']])
"""
顺序定义:Low < Medium < High -> 0 < 1 < 2
fare_category fare_cat_encoded
0 Low 0.0
1 Medium 1.0
2 High 2.0
"""2
3
4
5
6
7
8
9
10
11
12
13
独热编码(One-Hot Encoding)
核心思想:把每个类别编码成独立列,用0和1表示,消除虚假的大小关系。
使用场景:无序分类特征(如颜色、城市)。
| 样本 | 颜色 | 编码 |
|---|---|---|
| A | 🔴 红 | 1 |
| B | 🟢 绿 | 2 |
| C | 🔵 蓝 | 3 |
模型会误以为:蓝 = 红 × 3
🔍 展开查看:代码实现
from sklearn.preprocessing import OneHotEncoder
# 初始化 OneHotEncoder
# handle_unknown='ignore': 遇到训练集中未出现的类别时,不会报错而是忽略(置为全0)
# sparse_output=False: 输出为密集数组 (numpy array),而不是稀疏矩阵
ohe = OneHotEncoder(handle_unknown='ignore', sparse_output=False)
# 对 'embark_town' 列进行拟合并转换
# 注意:输入必须是二维数组,所以使用双括号 [['embark_town']]
ohe_result = ohe.fit_transform(df_ohe[['embark_town']])
# --- 将结果转换为 DataFrame 以便查看和拼接 ---
# 1. 获取新生成的列名 (scikit-learn 1.0+ 推荐使用 get_feature_names_out)
ohe_columns = ohe.get_feature_names_out(['embark_town'])
# 2. 构建 DataFrame,并保持与原表相同的索引
ohe_df = pd.DataFrame(ohe_result, columns=ohe_columns, index=df_ohe.index)
# 此时 ohe_df 包含了独热编码后的新特征,可以进一步与主表拼接
# final_df = pd.concat([df_ohe, ohe_df], axis=1)
# 生成的新列:
# ['embark_town_Cherbourg', 'embark_town_Queenstown', 'embark_town_Southampton']
# 表格数据:
# survived sex pclass embark_town_Cherbourg embark_town_Queenstown embark_town_Southampton
# 0 0 male 3 0.0 0.0 1.0
# 1 1 female 1 1.0 0.0 0.02
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
缺点(维度爆炸):N种类别会增加N-1列。类别极多时会导致数据稀疏,建议该用Embedding。
标签编码(Label Encoding)
简单映射为整数(0,1,2...),不保证顺序,但引入了数值大小。
✅适用场景
- 二分类特征(如性别,0/1等价于独热)。
- 分类任务的目标变量/标签列。
- 树模型(如XGBoost、随机森林)。
⚡ 避坑指南:类别不等于大小
无序类别若直接编码为
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
X_train_encoded['Sex'] = le.fit_transform(X_train['Sex']) # 映射关系2
3
4
特征构造与删除
原始数据只是原材料,接下来我们将基于对泰坦尼克号背景的理解,对数据集做三个关键处理,挖掘出数据背后真正的规律:
- 合并特征:将分散的亲属数量合并为“家庭规模”,并标记出“独自一人”的乘客(家庭结构可能直接影响逃生策略)。
- 提取信息:从看似无用的姓名中提取出头衔(如Mr,Mrs,Master),帮助识别社会地位和特殊群体。
- 删减噪音:果断删除缺失太多、格式混乱或会导致数据泄漏的列,只保留最核心的特征。
目标:去粗取精,将杂乱的原始记录转化为模型更容易理解的高质量特征。
🔍 展开查看:代码实现
def create_features(df):
df = df.copy() # 避免修改原始数据
# 1. 家庭成员总数
df['FamilySize'] = df['SibSp'] + df['Parch'] + 1 # +1 是为了包括乘客本人
# 2. 是否独自一人
df['IsAlone'] = (df['FamilySize'] == 1).astype(int) # 独自一人则为1,否则为0
# 3. 从姓名提取头衔并归并稀有头衔
df['Title'] = df['Name'].str.extract(' ([A-Za-z]+)\.', expand=False) # 提取头衔
rare_titles = ['Lady', 'Countess', 'Capt', 'Col', 'Don', 'Dr',
'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona']
df['Title'] = df['Title'].replace(rare_titles, 'Rare')
df['Title'] = df['Title'].replace({'Ms': 'Miss', 'Mlle': 'Miss', 'Mme': 'Mrs'}) # 将Mlle和Ms归并为Miss,Mme归并为Mrs
# 4. 删除无用特征
drop_cols = ['PassengerId', 'Name', 'Ticket'] # 这些特征对模型没有帮助,且可能引入噪声
df = df.drop(columns=[c for c in drop_cols if c in df.columns]) # 只删除存在的列
return df
X_train_fe = create_features(X_train)
X_test_fe = create_features(X_test)
# 查看训练集情况
print("\n特征工程后训练集数据类型与缺失值情况:")
print(X_train_fe.shape)
print(X_train_fe[['FamilySize', 'IsAlone', 'Title']].head())2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
特征放缩
在数据集中,部分特征的量纲差异可能极大:
Age: 0 ~ 80 (十位数)
Fare: 0 ~ 100+ (百位数)
SibSp: 0 ~ 8 (个位数)
Embarked_C: 0 or 1 (0-1)2
3
4
如果不进行缩放,模型会错误地认为数值较大的特征更重要,赋予其过大的权重。
导致梯度下降在更新参数时反复震荡,需要极多的迭代次数才能找到最优解,甚至可能无法收敛。
标准化(Standardization)
将数据转换为均值为0、标准差为1的分布。最常用的方法,尤其适用于数据分布近似正态分布的场景。
特点与影响:
- 不会强制压缩到特定区间(可以是负数)。
- 基于均值和标准差计算,易受异常值影响。
- 得益于前面的异常值清洗,整体相对稳定。
适用场景:
- 线性模型
- 逻辑回归
- 神经网络
- 支持向量机(SVM)
🔍 展开查看:代码实现
from sklearn.preprocessing import StandardScaler
# 选取需要缩放的数值型列(例如 'Age' 和 'Fare')
numeric_cols = ['Age', 'Fare']
# 标准化
scaler = StandardScaler()
# 只在训练集上拟合,获取均值和标准差
scaler.fit(X_train[numeric_cols])
# 使用训练集的均值和标准差来转换训练集和测试集
X_train_scaled = X_train.copy()
X_test_scaled = X_test.copy()
X_train_scaled[numeric_cols] = scaler.transform(X_train[numeric_cols])
X_test_scaled[numeric_cols] = scaler.transform(X_test[numeric_cols])
print(X_train_scaled[numeric_cols].head())
# 均值和标准差
print("训练集数值型特征的均值:")
print(X_train_scaled[numeric_cols].mean())
print("\n训练集数值型特征的标准差:")
print(X_train_scaled[numeric_cols].std())2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
归一化(Normalization)
将数据线性变换到固定区间,通常是
特点与影响:
- 严格限定数据范围,确保结果不会出现负数。
- 受异常值影响极其明显(极大离群点会破坏正常数值分布)。
适用场景:
- 图像数据处理(像素天然有0~255的边界)。
- 神经网络的输入(Sigmoid等激活函数是有界的)。
- 算法强制要求数据范围时。
工程实践
工程最佳实践(Pipeline)
使用 Pipeline 和 ColumnTransformer将整个流程串起来,防止数据泄漏,代码更简洁,便于部署。
🔍 展开查看:代码实现
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
# 1. 数值列流水线:填补缺失值 -> 标准化
numeric_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')), # 用中位数填补数值列的缺失值
('scaler', StandardScaler()) # 标准化数值列
])
# 2. 类别列流水线:填补缺失值 -> 独热编码
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')), # 用众数填补类别列的缺失值
('onehot', OneHotEncoder(handle_unknown='ignore', drop='first')) # 独热编码
])
# 3. 组合流水线
preprocessor = ColumnTransformer(
transformers=[
('num', numeric_transformer, ['Age', 'Fare', 'SibSp', 'Parch']), # 数值列
('cat', categorical_transformer, ['Sex', 'Pclass', 'Embarked']) # 类别列
])
# 4. 创建完整的流水线(预处理 + 模型)
X_train_ready = preprocessor.fit_transform(X_train)
X_test_ready = preprocessor.transform(X_test)
# 现在 X_train_ready 和 X_test_ready 已经是预处理后的特征矩阵,可以直接用于模型训练和预测
print("预处理后训练集特征矩阵形状:", X_train_ready.shape)
print("预处理后测试集特征矩阵形状:", X_test_ready.shape)
# 预处理后训练集特征矩阵形状: (712, 9)
# 预处理后测试集特征矩阵形状: (179, 9)2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
模型评估
拟合的前提,是正确判断
基线模型
Baseline(基线模型)是指首个快速实现、相对简单但具备完整流程的模型。
它的核心价值不在于极致精度,而在于建立参照系————后续的所有优化尝试,都需以此为基准进行对比。
先把Baseline跑起来!在深入评估之前,我们需要一个可靠的起点。
🔍 展开查看:代码实现
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler
# 加载数据
housing = fetch_california_housing()
X = pd.DataFrame(housing.data, columns=housing.feature_names)
y = pd.Series(housing.target, name='MedHouseVal')
print("数据集基本信息:")
print(f"样本数量: {X.shape[0]}, 特征数量: {X.shape[1]}")
# 房价上界和下界和均值
print(f"房价范围[{y.min():.2f}, {y.max():.2f}], 房价均值: {y.mean():.2f}")
print(X.head().round(2))2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 数据规模:20,640 条记录
- 特征维度:8 个数值型特征
- 预测目标:房屋价格中位数
训练基线模型
我们先划分训练集与测试集,再只使用训练集拟合标准化器,最后训练线性回归基线模型。
🔍 展开查看:代码实现
# 先划分,再只在训练集上拟合标准化器
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
print(f"训练集样本数量: {X_train.shape[0]}, 测试集样本数量: {X_test.shape[0]}")
# 训练baseline模型
model = LinearRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
for i in range(5):
print(f"真实值: {y_test.iloc[i]:.3f}, 预测值: {y_pred[i]:.3f}")2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
评估指标
评估指标
模型训练完成后,我们需要在测试集上评估其泛化能力。
MAE(平均绝对误差)
- 核心思想:计算预测值与真实值偏差的绝对值,再求平均。
- 优点:直观易懂,单位与目标变量完全一致;对异常值相对不敏感,不会过度放大极端错误。
- 缺点:在数学上绝对值在零点不可导,作为损失函数优化时不如平方项方便。
MSE(均方误差)
- 核心思想:计算预测值与真实值偏差的平方,再求平均。
- 特点:平方操作会按二次关系放大较大误差的影响。如果业务场景对大偏差零容忍(医疗、自动驾驶),MSE是很好的指标。
- 缺点:单位被平方了(例如“平方万美元”),失去了直观的业务含义。
RMSE(均方根误差)
- 核心思想:对MSE开根号。
- 单位还原:量纲回到了和目标变量一致,业务上容易解释。
- 惩罚大误差:继承了MSE对大偏差敏感的特性。
- 在实际工程和数据科学竞赛(如Kaggle)中,RMSE是回归任务的默认标准。
核心问题:我们的模型比“瞎猜”(直接用均值预测)好多少?
- 无量纲指标:衡量模型消除了总方差中多大比例的不确定性,可在不同数据集间横向比较。
:完美预测,没有任何误差。 :模型表现和直接预测所有样本的平均值一样,等于没学到有效特征。 :模型预测得比直接猜平均值还差(通常是因为选错了模型或存在严重Bug)。
🔍 展开查看:代码实现
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(y_test, y_pred)
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_test, y_pred)
# 输出评估指标
print(f"MAE: {mae:.4f} (平均偏差 {mae*10:.2f}万美元)")
print(f"MSE: {mse:.4f}")
print(f"RMSE: {rmse:.4f} (平均偏差 {rmse*10:.2f}万美元)")
print(f"R²: {r2:.4f} (解释了{r2*100:.1f}%的方差)")2
3
4
5
6
7
8
9
10
11
12
13
- RMSE > MAE:说明存在一些偏差较大的样本把RMSE拉高了。
:解释了约57.6%的房价方差,剩余42.4%未捕获。 - 对于只用8个特征的线性模型,这是个合理的起点,但有提升空间。
残差分析
残差分析:数字之外的诊断
指标只给出全局汇总,残差分析能告诉我们模型在哪里表现好、哪里差。

- 预测值与真实值对比图:
- 真实值较高时,预测值普遍偏低,说明模型倾向于低估高价房产。右侧的垂直聚集现象是数据截断效应(最高50万美元)。
- 残差与预测值关系图:
- 随着预测值增大,残差的发散程度也在变大(漏斗形),说明模型预测高房价时非常不稳定。
- 残差分布直方图:
- 残差整体呈现右偏且带有长尾————再次印证了模型对部分高房价样本严重低估的问题。
交叉验证
交叉验证:剔除随机性带来的偏差
单次train/test split 的评估结果可能依赖于运气。为了获得更稳定、更具统计显著性的评估结果,我们采用K-Fold交叉验证。
- 将数据均匀分成K份。每一轮取1份作为测试集,剩余K-1份作为训练集。重复K轮,最终报告K次评估结果的均值和标准差。
🔍 展开查看:代码实现
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import make_pipeline
# 把标准化放入 Pipeline,确保每一折都只用该折训练数据拟合 scaler
cv_model = make_pipeline(StandardScaler(), LinearRegression())
scores = cross_val_score(
cv_model, X, y,
cv=5,
scoring='neg_mean_squared_error'
)
# 将负MSE转换回RMSE
rmse_scores = np.sqrt(-scores)
print("每折 RMSE:")
for i, score in enumerate(rmse_scores):
print(f" Fold {i + 1}: {score:.4f}")
print(f"\n平均 RMSE: {rmse_scores.mean():.4f} "
f"± {rmse_scores.std():.4f}")2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
各折 RMSE 的标准差约为 0.0437,说明该模型在这组划分下波动不大;仍应结合业务容忍度判断是否足够稳定。
深入诊断:训练集 vs测试集
🔍 展开查看:代码实现
from sklearn.model_selection import cross_validate
cv_results = cross_validate(
make_pipeline(StandardScaler(), LinearRegression()), X, y,
cv=5,
scoring={
'rmse': 'neg_root_mean_squared_error',
'mae': 'neg_mean_absolute_error',
'r2': 'r2'
},
return_train_score=True # 同时返回训练集上的得分(用于诊断过拟合)
)
print(f"{'指标':<2} {'训练集':>12} {'测试集':>10} {'差距':>8}")
print("-" * 48)
for metric in ['rmse', 'mae', 'r2']:
train_key = f'train_{metric}'
test_key = f'test_{metric}'
if metric == 'r2':
train_mean = cv_results[train_key].mean()
test_mean = cv_results[test_key].mean()
else:
train_mean = -cv_results[train_key].mean()
test_mean = -cv_results[test_key].mean()
gap = abs(train_mean - test_mean)
print(f"{metric.upper():<6} {train_mean:>12.4f} "
f"{test_mean:>12.4f} {gap:>10.4f}")2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
核心发现:
- 未过拟合:训练集和测试集指标非常接近。
- 存在欠拟合:训练集上的表现本身也不算出色(
只有0.61),说明模型可能还没有充分挖掘数据中的信息。
欠拟合与过拟合
训练模型的困境
机器学习目标是训练出一个能够捕捉数据背后潜在规律的模型,使其不仅能够很好地拟合训练数据,还能对从未见过的新数据做出准确预测,这种能力被称为泛化能力。
💡 通俗理解:火候与泛化
然而,模型并不总能具备良好的泛化能力,就像做菜时的火候————
🔥火太小,食材夹生(学得太少)
🔥火太大,外焦里糊(学得太多)
模型训练也是一样:学得太少或学得太多,都会导致预测效果不佳。
欠拟合、良好拟合与过拟合
- 表现:训练集差,验证集/测试集也差。
- 原因:模型表达能力不足、特征不足或训练不充分。
- 通俗理解:学生只背了目录,课堂例题和新题都不会做。
⚡ 避坑指南:不要只看训练集分数
训练误差低并不等于泛化能力强。模型选择、调参与早停都应参考验证集或交叉验证,测试集只用于最终评估。
学习曲线
诊断拟合情况的工具:学习曲线
学习曲线可以展示误差随样本增加的变化趋势,假设数据的真实规律是
- degree = 1:线性模型,无法捕捉二次关系,导致欠拟合。
- degree = 2:二次多项式,与真实数据生成过程匹配,良好拟合。
- degree = 15:高次多项式,过度拟合训练数据中的噪声,导致过拟合。
三种情况实现:

结果分析:
- 图1(欠拟合):直线无法捕捉数据的弯曲趋势。
- 图2(良好拟合):二次多项式精准捕捉抛物线趋势,不被噪声带偏。
图 3(高方差示例):15 次多项式试图追随训练样本中的噪声,预测曲线明显扭曲。是否构成过拟合应结合独立验证集或交叉验证判断。
绘制学习曲线

结果分析:
- 欠拟合:训练和验证误差都高,且差距不大,加数据也没用。
- 良好拟合:训练和验证误差都低,逐渐收敛。
- 过拟合倾向:训练误差很低,验证误差明显更高,二者差距较大;增加有代表性的数据可能有帮助。
解决欠拟合
核心思路:让模型更有表达力(做加法)
1.特征工程
- 多项式特征:添加高次项。
- 特征交叉:组合多个特征。
- 数学变换:对数、指数等。
- 领域特征:结合业务知识。
2.增加训练轮数
增加 epochs,给模型更多学习时间,直到验证损失不再改善。
3.增加模型复杂度
从简单模型(如线性回归)切换到复杂模型(如随机森林、决策树)。
🔍 展开查看:代码实现
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import StandardScaler
# 增加模型复杂度
models = {
"线性回归": make_pipeline(StandardScaler(), LinearRegression()),
"决策树(max_depth=5)": DecisionTreeRegressor(max_depth=5, random_state=42),
"随机森林(100棵树)": RandomForestRegressor(
n_estimators = 100, max_depth = 10, random_state = 42, n_jobs = -1
),
}
results = []
for name, model in models.items():
model.fit(X_train, y_train)
train_r2 = r2_score(y_train, model.predict(X_train))
test_r2 = r2_score(y_test, model.predict(X_test))
results.append({
"模型": name,
"训练 R²": train_r2,
"测试 R²": test_r2,
"差距 (R²)": train_r2 - test_r2,
})
df_results = pd.DataFrame(results)
print(df_results.to_string(index=False, float_format="%.4f"))
# 输出结果:
# 模型 训练 R² 测试 R² 差距 (R²)
# 线性回归 0.6107 0.5957 0.0150
# 决策树(max_depth=5) 0.7086 0.6905 0.0181
# 随机森林(100棵树) 0.9136 0.8068 0.10682
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
解决过拟合
核心思路:让模型别太极端(做减法)
- 正则化(最通用):给模型复杂度增加惩罚,限制权重大小,防止死记硬背。
- 增加训练数据:更多数据让噪声被稀释(数据获取成本低时)。
- 降低模型复杂度:使用更简单的模型(如减少多项式阶数或树的深度)。
- 特征选择:主动去掉无关或冗余的特征,减少干扰信号。
- 早停(Early Stopping):在验证集误差开始上升时,及时停止训练过程。
- Dropout / 数据增强:深度学习中的常用防过拟合高级技巧。
正则化
核心思想:在损失函数中额外加一个惩罚项,系数越大罚得越重,让模型尽可能用小的系数拟合。
惩罚所有权重的平方和:
它会将权重平滑地压缩到接近
名词溯源:
正则化(regulation)
本意是使事物变得规则、整齐、正常。没加正则化时,模型为了迎合噪声会疯狂扭曲(不正常);加上正则化后,强制让模型“别太极端”,回归到平滑、正常的简单形态。
L2(Ridge)
没有正则化时,损失函数像一个深邃的峡谷。加上L2惩罚后,损失函数的集合形状会在原点附近隆起一道“山脊(Ridge)”,把参数“顶住”,限制在较小范围内,防止它们在峡谷深处失控。
Lasso的由来
Lasso是缩写(最小绝对值收缩与选择因子,Least Absolute Shrinkage and Selection Operator)。恰好与英文lasso(套索)同形,非常形象————它就像牛仔的套索,把不重要的无用特征直接“勒”到0,天然具备特征选择能力。
-2.0143x^1 +21.5388x^2 +30.3905x^3 +-297.6976x^4 +-304.5768x^5 +1753.8255x^6 +1812.4747x^7 +-5043.0462x^8 +-5718.8704x^9 +7569.2743x^10 +9363.0127x^11 +-5676.3405x^12 +-7504.0952x^13 +1676.5888x^14 +2325.6142x^15
-0.0869x^1 +1.4014x^2 +-0.0688x^3 +0.8328x^4 +-0.0097x^5 +0.4300x^6 +0.0421x^7 +0.1767x^8 +0.0737x^9 +0.0174x^10 +0.0924x^11 +-0.0878x^12 +0.1048x^13 +-0.1619x^14 +0.1149x^15
2.4803x^2 +0.0809x^4 +0.0064x^13 +0.0729x^152
3

欠拟合vs过拟合
| 诊断结果 | 核心问题 | 解决方案 |
|---|---|---|
| 欠拟合 (Underfitting) | 模型表达能力不足 | ➕ 做加法: 增加特征、增加训练轮数、更复杂的模型 |
| 过拟合 (Overfitting) | 模型过于敏感、死记硬背 | ➖ 做减法: 引入正则化、增加数据、简化模型、早停 |
线性回归总结
线性回归是整个机器学习乃至深度学习的基本原子:
- 逻辑回归不过是在线性回归的输出上套了一个激活函数;
- 深度学习的本质是将多个线性回归通过非线性函数层层相连;
- 即便是大语言模型,其核心运算依然可以理解为多元线性回归的大规模批量处理。
小结
万变不离其宗,把线性回归真正吃透,后面的学习就是在这个地基上不断叠加新结构。
5. 逻辑回归:从回归走向分类
分类问题
线性回归能预测房价、股票价格这样的连续数值。但现实业务中,有大量的问题本质上是在做选择题:
- 这封邮件是不是垃圾邮件?
- 这个肿瘤是良性还是恶性?
- 用户会不会点击广告?
我们把预测离散标签、输出有限个互斥类别的任务称为分类(Classification)。
总结:
线性回归解决是多少的问题,逻辑回归解决属于哪一类的问题。
为什么需要逻辑回归?
为什么不能直接用线性回归预测,大于0.5算1、小于0.5算0?
线性回归输出范围为
💡解决方案:为线性回归叠加一个变形器————这就是逻辑回归。
Sigmoid函数
为了将任意实数映射到0到1之间的概率值,逻辑回归引入了Sigmoid函数:
把线性回归结果
- 逻辑回归 = 线性回归 + Sigmoid。前半段算分数,Sigmoid把分数转成概率。

使用Sigmoid的好处
它完美契合了分类任务的三个核心需求:
- 输出天然具有概率含义
- 严格限制在(0, 1)之间,0.8就代表模型有80%的把握认为是正类。
- 自带决策分界点
。 判为正类, 判为负类。决策边界就是
- 处处可导,且导数极简
- 相比阶跃函数,sigmoid连续平滑,导数公式极其简洁:
,极大提升梯度下降效率。
- 相比阶跃函数,sigmoid连续平滑,导数公式极其简洁:
分类问题的损失函数
为什么不使用MSE作为损失函数?
线性回归 + MSE = 凸函数
像一个光滑的碗,无论初始参数怎么设置,梯度下降都能找到全局最优解。
逻辑回归 + MSE = 非凸函数
引入非线性的Sigmoid后,损失函数变得坑坑洼洼,极易陷入局部最优,且存在梯度消失问题。
分类问题损失的特征
对于二分类任务,模型输出的是概率p。一个合理的损失函数应该满足:
- 如果真实标签 y = 1,那么p越接近1越好,越接近0惩罚越大。
- 如果真实标签 y = 0,那么p越接近0越好,越接近1惩罚越大。
⚡ 避坑指南 / 易混淆点
常见误区:p = 0.5并不是损失最大,而是模型最不确定。
真正损失最大的时候,是模型非常自信但预测错了(如真实y=1,却预测
为了设计这样的函数,我们借用数学工具:对数函数(log)、伯努利分布 和 最大似然估计。
🔍 展开查看:从对数、伯努利分布到交叉熵
对数函数
对数函数(Logarithm)
log的优势:
- 单调性保持一致
- 对数函数是严格单调递增的。最大化某个概率,完全等价于最大化它的对数。这让优化过程变得简单。
- 防止数值下溢出
- 多个介于0和1之间的概率相乘,结果会迅速趋近于0,导致计算机无法精确表示。取对数后,乘法变成了加法:
- 多个介于0和1之间的概率相乘,结果会迅速趋近于0,导致计算机无法精确表示。取对数后,乘法变成了加法:

💡观察
当x -> 1时, y -> 0
当x -> 0时, y ->
这完美符合我们对“惩罚”的定义!
伯努利分布
二分类问题的本质,就是一个只有两种结果的随机试验(比如抛不均匀的硬币)。
- 统一概率公式
假设结果为1的概率p,结果为0的概率是1-p:
数学家巧妙地用一个公式把两种情况统一了起来:
- 结合逻辑回归
在逻辑回归中,模型预测的
将
最大似然估计(MLE)
最大似然估计:既然事情已经发生,那么让这件事情发生的概率最大的参数,就是最好的参数。
我们要找到一组参数
- 似然函数
所有样本预测正确的概率乘积。
- 取对数(Log-Likelihood)
将连乘变为连加,方便求导。
💡在统计学中希望最大化似然函数,但在机器学习中需要最小化损失函数。因此只需在前面加个负号并求平均,这就得到了交叉熵损失函数!
交叉熵损失
交叉熵损失(Cross-Entropy Loss)
| 真实标签 | 损失项 | 含义 |
|---|---|---|
| 预测概率越接近 1,损失越小;接近 0 时损失 | ||
| 预测概率越接近 0,损失越小;接近 1 时损失 |
对于无正则化二分类逻辑回归,交叉熵关于参数是凸函数;在常见条件下可使用凸优化方法寻找全局最优解。若数据完全线性可分且没有正则化,有限的最大似然解可能不存在,因此工程实现通常会加入正则项。
梯度下降
逻辑回归的梯度推导过程中,Sigmoid的导数和交叉熵的导数恰好大量约分抵消,最终结果和线性回归的形式一模一样。
| 线性回归 | 逻辑回归 | |
|---|---|---|
| 权重梯度 | ||
| 偏置梯度 | ||
分类问题实战
使用sklearn内置乳腺癌数据集演示完整训练流程。
目标:二分类————恶性(0)/良性(1)。
训练集:455条数据
测试集:114条数据
训练集恶性占比:37.36%
测试集恶性占比:36.84%
测试集准确率:0.9825
模型评估
分类任务不能只看准确率。混淆矩阵把所有预测结果按「真实类别 x 预测类别」排列,是分类评估的基础。
| 预测为正 | 预测为负 | |
|---|---|---|
| 实际为正 | TP (真正例)✅ | FN (假负例)❌ 漏报 |
| 实际为负 | FP (假正例)❌ 误报 | TN (真负例)✅ |
⚡ 避坑指南:医疗分类不能只看 Accuracy
FN(漏报恶性)在医疗场景中危害最大,患者可能因此延误治疗。
准确率、召回率、F1
从混淆矩阵推导出三个核心指标:
预测为正类的样本中,真正为正类的比例。它关注误报是否过多。
💡权衡:降低决策阈值可以提高召回率(漏报更少),但会降低精确率(误报更多)。在乳腺癌场景中,恶性的召回率是最重要的————宁可误报,不能漏报。
ROC曲线
Precision 与 Recall 依赖决策阈值。ROC 曲线通过遍历阈值展示二者对应的 TPR 与 FPR 关系:
- 横轴(FPR):假正率,负类被误判为正类的比例。
- 纵轴(TPR):真正率(召回率),正类被正确识别的比例。
AUC(Area Under Curve)
AUC 是 ROC 曲线下面积,取值通常在

多分类与深度学习
多分类进阶:Softmax
逻辑回归(Sigmoid)解决的是非黑即白的二分类问题。如果面临多个类别(如猫、狗、猪),我们需要升级为Softmax回归:
- 为每个类别计算一个独立得分。
- 通过Softmax函数,将所有得分转化为概率分布。
- 所有类别的概率之和严格为
。
通向深度学习的桥梁
逻辑回归不仅是传统的机器学习算法,它与深度学习有着极深的渊源:
本质
逻辑回归本质上就是一个没有隐藏层的单层神经网络!
- 输入层:特征
- 权重与偏置:神经网络的连接权重
和偏置 - 激活函数:Sigmoid / Softmax
- 输出层A:逻辑回归 + Softmax 正是现代神经网络最常用的输出层结构。
线性回归与逻辑回归对比
- 任务:预测连续数值。
- 输出:任意实数
。 - 常用损失:MSE。
- 常用指标:MAE、RMSE、
。
⭐ 核心结论 / 面试高频
逻辑回归是在线性决策函数 外叠加 Sigmoid 概率映射。它训练快、可解释性强,扩展到多分类时可使用 Softmax 回归。
6. 树模型与集成学习
决策树
例子:
小张这周末去打球吗?
小张是否出门打球,很大程度上取决于天气情况。下表纪录了过去10个周末的天气数据:
| Outlook (天气) | Temperature (温度) | Humidity (湿度) | Windy (刮风) | Play (打球) |
|---|---|---|---|---|
| Overcast | Hot | High | True | Yes |
| Overcast | Mild | Normal | False | Yes |
| Rain | Mild | High | False | Yes |
| Rain | Cool | Normal | False | Yes |
| Sunny | Cool | Normal | False | Yes |
| Sunny | Mild | High | True | No |
| Rain | Mild | High | True | No |
| Overcast | Cool | Normal | True | Yes |
| Sunny | Hot | High | False | No |
| Sunny | Mild | High | False | No |
像人类一样提问:
假设今天天气是“晴天、炎热、正常湿度、不刮风”,你的大脑会自动进行如下逻辑推理:
- 先看整体天气:只要是多云,风雨无阻,一定会去。
- 如果是晴天:看湿度。湿度高不去,湿度正常才去。
- 如果是雨天:看风力。刮风不去,不刮风才去。
决策树(Decision Tree)是一种模仿人类决策时层层提问方式的机器学习算法。它将数据按照不同特征进行递归分裂,最终形成倒立的树状结构。
- 内部节点:代表对某个特征的判断问题(如“天气是晴天吗?”)
- 树枝分支:代表判断后的不同答案(如:是/否)
- 叶节点:代表最终的决策结果(如:去打球/不去)
💡决策树是最具可解释性的机器学习模型之一,决策路径完全透明。
衡量纯度的指标
决策树做预测的过程非常简单,问题的关键在于如何从众多特征中挑选出在哪个特征上进行分裂。
模型怎么知道第一步该问天气而不是温度?
核心概念是:纯度(Purity)。让结果越来越纯
❌如果按“温度”分组(纯度低)
每个温度分组下,去与不去的结果混杂在一起,非常混乱。
| Temp | Play |
|---|---|
| Hot | No, Yes |
| Mild | Yes, Yes, No, No, Yes |
| Cool | Yes, Yes, Yes |
✅如果按“天气”分组(纯度高)
多云分组所有标签都是Yes,这种情况我们就说这个节点的纯度极高。
| Outlook | Play |
|---|---|
| Sunny | No, No, Yes, No |
| Overcast | Yes, Yes, Yes |
| Rain | Yes, Yes, No |
熵与信息增益
决策树在选择最佳分裂特征时,需要一个数学指标来量化节点的混乱程度或纯度。
1. 熵(Entropy)
概念来自于物理和信息论,用来衡量一个节点中各类别分布的混乱程度。
- 一半去打球、一半不去(50% vs 50%),悬念最大,熵值最高,最混乱。
- 全部去或全部不去(100% vs 0%),毫无悬念,熵值为 0,最纯净。
2. 信息增益(Information Gain)
用某个特征分裂之后,混乱程度下降了多少。
用这个特征提问后,消除了多少不确定性。信息增益越大,说明特征区分能力越强。
基尼系数
为了解决熵计算复杂的问题,工业界更倾向于使用基尼系数(Gini Impurity)。
直观理解:随机从节点中抽取两个样本,它们属于不同类别的概率。
- 基尼系数越小,说明节点越纯(完全纯净时为0)。
- 计算极快:只有乘法和加法,没有耗时的对数运算。
- 工业标准:是scikit-learn库中决策树(CART算法)的默认指标。
决策树的生长过程
构建决策树是一个不断寻找最优特征进行划分的递归流程:
- 计算指标:计算当前节点使用各项特征划分后的基尼系数。
- 选择最优:选择划分后基尼系数最小(纯度提升最大)的特征。
- 分裂数据:按该特征的不同取值,将数据分裂成子节点。
- 递归生长:对每个子节点重复上述过程,继续向下生长。
- 生成叶节点:满足停止条件时停止分裂,确定最终预测结果。
使用scikit-learn自带的经典鸢尾花(Iris)数据集(150朵花,4个特征,3个品种)。
🔍 展开查看:代码实现
import pandas as pd
from sklearn.datasets import load_iris
# 1. 加载数据集
iris = load_iris()
# 2. 为了方便阅读,我们将英文特征名和类别名翻译成中文
feature_names_cn = ['花萼长度(cm)', '花萼宽度(cm)', '花瓣长度(cm)', '花瓣宽度(cm)']
target_names_cn = ['山鸢尾', '变色鸢尾', '维吉尼亚鸢尾']
# 3. 转换成表格(DataFrame)形式展示
df = pd.DataFrame(iris.data, columns=feature_names_cn)
df['最终分类结果'] = [target_names_cn[i] for i in iris.target]
print("============ 1. 数据集前5行预览 ==============")
print(df.head())
print("============ 2. 数据集包含的分类及数量 =========== ")
print(df['最终分类结果'].value_counts())2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
决策树训练及展示
决策树模型的预测准确率为:100.00%
节点信息解读:
- samples:节点包含的总样本数。
- value:各类别样本分布。
- gini:基尼不纯度(0表示完全纯净)。
- class:当前节点的默认类别。
通过可视化,我们可以清晰地看到模型是如何一步步通过花瓣长度和花瓣宽度来将三种鸢尾花区分开的。
结论:花瓣尺寸决定了种类
这是根据图片内容转换的 Markdown 表格:
| 鸢尾花类别 | 关键阈值 | 节点纯度 |
|---|---|---|
| 山鸢尾 | 花瓣长度 ≤ 2.45 | 完全纯净 (gini=0) |
| 变色鸢尾 | 2.45 < 花瓣长度 ≤ 4.75 and 宽度 ≤ 1.6 | 完全纯净 (gini=0) |
| 变色鸢尾 | 花瓣长度 > 4.75 and 1.6 < 宽度 ≤ 1.75 | 混合较多 (gini=0.5, 与维吉尼亚鸢尾有重叠) |
| 维吉尼亚鸢尾 | 2.45 < 花瓣长度 ≤ 4.75 and 宽度 > 1.6 | 完全纯净 (gini=0) |
| 维吉尼亚鸢尾 | 花瓣长度 > 4.75 and 宽度 > 1.75 | 整体较纯 (gini≈0.06, 含少量杂质) |
- 花瓣越短 --> 越可能是山鸢尾
- 花瓣中等 --> 多为变色鸢尾
- 花瓣最长 --> 多为维吉尼亚鸢尾
- ⚠️注意:花瓣长度 >4.75、1.6 < 宽度
1.75 时变色鸢尾与维吉尼亚有重叠,这是模型容易误判的地方。
剪枝
剪枝(pruning)
防止过拟合
如果任由决策树肆意生长,它会为了把每一个异常值都分对,长出无数繁琐的分支,导致过拟合。我们需要剪掉多余细节,保留核心主干。
树在生长过程中满足约束便停止,例如:
- 限制最大深度
max_depth; - 限制叶节点最少样本数
min_samples_leaf; - 限制继续分裂所需的最少样本数
min_samples_split。
实现简单、训练成本低,是工程中的常见选择。
决策树总结
决策树直观、易解释,但单棵树稳定性差、容易过拟合。既然一棵树容易犯错,那我们就种一整片森林!这就是集成学习(Ensemble Learning)。
🌳 随机森林(Random Forest)
构建很多棵相互独立的决策树。每次随机抽取部分数据和特征。面对未知数据时,所有树独立预测,投票表决,少数服从多数,极大降低误判风险。
🚀 梯度提升树家族(GBDT / XGBoost / LightGBM)
树与树之间不再独立,而是前赴后继。第二棵树专门纠正第一棵树的错误,第三棵树纠正第二棵树...通过不断迭代,把模型精度推向极致。
随机森林
决策树的困境
决策树通过一连串“如果……那么……”的规则切分数据,决策路径透明,便于业务审查和结果解释。
- 为了处理决策树不稳定的风险,Leo Breiman于2001年正式提出了随机森林。
核心思想:集成学习
随机森林属于集成学习(Ensemble Learning):将多个弱学习器合成一个强学习器,以获得比单一模型更好的泛化性能。
💡 通俗理解:专家会诊
- 不再依赖一棵树做决定,而是通过随机采样建成百上千棵各不相同的决策树。
- 预测时让所有树独立给出判断,最终汇总结果(分类投票 / 回归求平均)。
- 有效中和了单棵树的偏见与错误,让模型既准确又稳定。
为什么叫随机森林?
顾名思义,这个名字包含两个核心词汇:“随机”和“森林”。
- 森林(Forest):由大量决策树组成。决策树是基础模型,通过一系列 IF-THEN 规则对数据逐层切分。
- 随机(Random):算法的灵魂。如果所有树都用完全一样的数据和特征训练,投票就失去了意义。
为了保证每棵树的多样性,算法引入了双重随机性。
双重随机性
样本随机
Bootstrap(有放回抽样)
假设训练集有N个样本,每次训练一棵树时:
- 从原数据集中随机放回地抽取N个样本,构成该树专属的训练子集。
- 由于是有放回的,部分样本会被重复抽到,部分样本则完全不会出现。
- 结果:使得每棵树使用的样本都略有不同,保证了基础的差异性。
特征随机
Random Subspace(特征子空间)
- 在决策树的每个节点进行分裂时,并不考虑全部特征。
- 而是随机抽取其中一个子集(通常取
个,d为总特征数),仅在这些候选特征中寻找最优分裂点。
为什么关键?
如果没有特征随机,所有树都会优先选择最强的特征(如“收入”)作为根节点,导致树结构高度相似。
加入特征随机后,有些树被迫用其他特征分裂,提供了不同的视角。个体不必完美,多样性才是力量。
OOB 样本价值
免费的验证集:袋外估计(OOB)
每次Bootstrap采样时,某个样本在N次抽取中始终未被选中的概率为:
- 每棵树大约有1/3的样本没参与训练,这些被称为袋外样本(Out-of-Bag,OOB)。
- 零浪费机制:天然的验证集,省去了额外划分验证集和交叉验证的繁琐开销。
- 所有数据都能直接投入训练并同步支持超参数调优,在小数据场景下尤为珍贵。
随机森林实战
算法工作流程:
- 构建专家团:
- 设定森林中树的数量
n_estimators(如100、500)。
- 设定森林中树的数量
- 逐棵生成决策树
- Bootstrap抽取N个样本作为训练集。
- 节点分裂时,随机选取m个候选特征找最佳切分点。
- 让树充分生长(通常不剪枝)。
- 集成输出:
- 分类任务:多数表决。
- 回归任务:简单平均。
使用100棵决策树组成随机森林,对鸢尾花数据集做训练与验证
🔍 展开查看:代码实现
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 加载数据
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.3,
random_state=42, # 固定随机种子,以确保结果可复现
)
# 构建随机森林
rf = RandomForestClassifier(
n_estimators=100, # 树的数量
max_features='sqrt', # 每次分裂时随机选取的特征数
oob_score=True, # 启用OOB估计
random_state=42,
n_jobs=-1 # 并行训练
)
rf.fit(X_train, y_train)
# 评估
print(f"OOB 准确率:{rf.oob_score_:.4f}")
print(f"测试集准确率:{accuracy_score(y_test, rf.predict(X_test)):.4f}")
# OOB 准确率:0.9429
# 测试集准确率:1.00002
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- OOB 准确率 0.9429:模型利用各棵树对应的袋外样本汇总得到约 94% 的估计准确率。
- 测试集准确率 100%:鸢尾花测试集规模较小且类别相对易分;该结果不能代表所有数据划分下都能达到 100%。
特征重要性
工业界最常用的能力之一:帮助理解哪些因素在起作用,哪些特征可以舍弃
统计某个特征在所有树节点上带来的加权不纯度下降。它计算快、可直接从模型读取,但可能偏好连续特征或高基数特征。

随机森林优劣
⭐ 核心结论 / 面试高频
随机森林通过样本随机 + 特征随机 降低树之间的相关性,再通过平均或投票降低整体方差。树的数量增加通常会让结果趋于稳定,但不能消除系统性偏差。
- 降低方差:相比单棵深树,随机森林通常具有更强的抗过拟合能力。
- 预处理要求相对较少:通常无需特征缩放;但多数实现仍要求先处理缺失值,异常值也可能影响分裂。
- 可处理较多特征:但在超高维稀疏数据中仍需评估训练成本与泛化效果。
- 附赠特征重要性:自然输出特征贡献度,利于业务归因分析。
- 天然支持并行计算:树的生成完全独立,完美利用多核CPU加速。
局限性:
⚡ 随机森林的局限
黑盒模型:虽然单棵决策树可解释性极强,但成百上千棵树组合在一起后,就变成了难以直观解释的黑盒。
计算与内存开销大:保存海量的树结构需要较大的内存空间;预测时每条数据需穿过所有树,预测速度相对较慢,不适合极其追求毫米级延迟的在线场景。
极端噪音敏感:在噪音极大的分类任务中,仍然有一定概率出现过拟合。
Boosting 模型
Bagging vs Boosting
集成学习有两大主流思想,目标都是把一群弱模型组合成一个强模型,但实现路径截然不同。
- 使用 Bootstrap 为不同基学习器构造不同训练集。
- 各模型可以并行、独立 训练。
- 分类时投票,回归时求平均。
- 主要作用是降低方差,代表算法是随机森林。
Boosting:接力纠错
💡 通俗理解:Boosting 就像连续击球
目标是把球送到 350 米处:
- 🏌️♂️第一杆(第1棵树):大力开球,打了300米。离球洞还差:350 - 300 = 50米。
- 🏌️♂️第二杆(第2棵树):目标不是350米,而是“补上这50米”。打了45米。离球洞还差:5米。
- 🏌️♂️第三杆(第3棵树):目标是“补上这5米”。打了4.5米。离球洞还差:0.5米。
- 🏌️♂️第四杆(第4棵树):推杆,补上最后0.5米。进洞!⛳️
最终结果 = 300 + 45 + 4.5 + 0.5 = 350 米
每个新模型都在拟合前面模型的残差(错误),接力合作,逐步逼近真实答案。
| 维度 | Bagging(随机森林) | Boosting(梯度提升树) |
|---|---|---|
| 训练方式 | 多棵树并行独立训练 | 多棵树串行依次训练 |
| 每棵树的目标 | 学习原始目标 | 学习前面模型的残差 |
梯度提升决策树
GBDT(Gradient Boosting Decision Tree)核心原理
前面说每棵新树学习上一轮的残差,为什么偏偏要叫梯度提升树?
(用梯度下降的方式来让能力越来越强)
GBDT的模型是多棵树的累加:
它不像线性回归有固定维度的参数
这是根据图片内容转换的 Markdown 表格:
| 经典梯度下降 | GBDT | |
|---|---|---|
| 优化对象 | 参数 | 函数 |
| 下降方向 | 负梯度 | 负梯度 |
| 更新方式 |
GBDT每一轮在做什么?
- 计算每个样本上的负梯度,即当前模型最应该修正的方向。
- 训练一棵决策树去拟合这些负梯度值。
- 把这棵树乘以学习率加到现有模型中,相当于沿着下坡方向走一小步。
当损失函数是均方误差(MSE)时:
对
在MSE下,负梯度恰好就是残差。这就是为什么前面说每棵新树拟合残差。
损失函数与负梯度
换成其他损失函数,负梯度就不再是简单的残差了:
| 损失函数 | 负梯度(每棵新树拟合的目标) |
|---|---|
| 均方误差(MSE) | |
| 绝对误差(MAE) | |
| 对数损失(分类) |
- Gradient(梯度):用负梯度来指引每棵新树的学习方向。
- Boosting(提升):多棵弱决策树逐步增加,模型能力不断提升。
手动实现一个简易版GBDT,并可视化逐步纠错的过程:

残差逐轮缩小的过程:

GBDT的优缺点
优点:
- 预测精度高:在结构化/表格数据上长期霸占各类竞赛和业务场景。
- 处理混合特征:数值型、类别型都能用。
- 自带特征重要性:帮助理解哪些特征影响最大。
- 对特征放缩不敏感:不需要标准化或归一化。
- 损失函数灵活:适配各种业务需求。
缺点:
- 训练速度较慢:串行训练,树之间无法并行。
- 容易过拟合:数据量少、噪声大时需仔细调参。
- 对超参数敏感:学习率、树的数量、深度等需认真调节。
- 不擅长超高维稀疏数据:如文本的one-hot编码。
- 类别特征处理不够原生:经典GBDT需手动编码。
工业级 Boosting 框架
由陈天奇发起的 XGBoost(eXtreme Gradient Boosting)具有以下关键设计:
- 在目标函数中显式加入树复杂度正则项;
- 同时利用一阶与二阶梯度信息;
- 支持行/列采样、稀疏感知、缓存优化与外存计算;
- 支持 Early Stopping,使训练过程更可控。
Boost模型对比
| 场景 | 推荐框架 |
|---|---|
| Kaggle 竞赛,追求极致精度 | XGBoost 或 LightGBM |
| 大数据集(>100万样本),需要快速训练 | LightGBM |
| 数据中有大量类别特征 | CatBoost |
==================================================
指标 随机森林 XGBoost
==================================================
MSE 0.2951 0.2132
R^2 Score 0.7748 0.8373
训练时间(秒) 0.99 0.222
3
4
5
6
XGBoost的早停机制:
[0] validation_0-rmse:1.11974
[50] validation_0-rmse:0.58075
[100] validation_0-rmse:0.51449
[150] validation_0-rmse:0.49533
.....
[1200] validation_0-rmse:0.44035
[1206] validation_0-rmse:0.44039
最佳迭代轮数:1186
最佳 MSE:0.193878360650528162
3
4
5
6
7
8
9
10
Early Stopping 让你不用纠结到底该设多少棵树————设一个足够大的上限,让算法自己找到最佳停止点。
XGB训练可视化:

验证集的RMSE先快速下降,然后趋于平缓,最后可能略微上升(过拟合信号),Early Stopping恰好在最低点附近停下来。
总结
这是根据图片内容转换的 Markdown 表格及下方的总结文本:
| 维度 | 决策树 | 随机森林 | GBDT / XGBoost |
|---|---|---|---|
| 模型数量 | 1棵 | 多棵(并行) | 多棵(串行) |
| 集成策略 | 无 | Bagging:投票/平均 | Boosting:逐步累加 |
| 单棵树特点 | 可深可浅 | 深树(低偏差高方差) | 浅树(高偏差低方差) |
| 核心目标 | 基线模型 | 降低方差 | 降低偏差 |
| 过拟合风险 | 高 | 低 | 中等 |
总结:
- 决策树是基础模型——简单符合直觉,但容易过拟合。
- 随机森林用 Bagging 思想,让多棵树并行投票,降低方差。
- GBDT用 Boosting 思想,让多棵树串行纠错,降低偏差。
XGBoost / LightGBM / CatBoost 是工业级扩展,在正则化、训练效率、类别特征处理等方面各有侧重。
7. 无监督学习:聚类与降维
无监督聚类问题
现实中我们经常会遇到没有标签的数据。数据本身不会直接告诉我们答案,但隐含着某种结果或规律,需要通过算法自动发现。
- 用户画像与分群:根据购买频次、消费金额等,将用户划分为高价值、潜力或流失风险用户。
- 图像压缩:从数十万种颜色种自动找出若干典型代表,减少存储空间。
- 异常检测:识别偏离常规的数据点(如异常访问、恶意攻击),通常通过数据分布特征去识别。
聚类
所谓聚类,就是在没有标签的前提下,根据样本之间的相似性,将数据自动划分为若干组,使得同组内样本尽可能相似,不同组之间尽可能有差异。
K-Means
K-Means是一种经典的聚类算法,用于将一批无标签数据自动划分为K个簇。
- K:预先指定的簇数。
- Means:均值,即每个簇的中心位置由该簇内所有样本的均值来表示。

目标:
找到K个具有代表性的中心点,使每个样本归属于离自己最近的中心,并尽可能减小样本到中心的距离。
质心与距离度量
- 质心(Centroid)
质心就是一个簇的中心位置,通常取该簇所有点在各个维度上的均值。
例如,点
- 距离度量
最常用的距离度量方式是欧氏距离(直线距离)。
二维平面中A、B两点的距离:
当维度扩展到n维时:
簇内相似度
簇内相似度
K-Means追求的是:同一个簇中的样本尽可能彼此接近,并且尽量靠近该簇的质心。
通常通过簇内平方和SSE(Sum of Squared Errors)来衡量:
表示第 个簇, 表示第 个簇的质心。 表示样本到质心的平方欧氏距离。- SSE越小,说明簇内样本越紧凑,聚类效果通常越好。
K-Means的迭代流程
K-Means的核心就是不断循环:选中心 -> 分簇 -> 更新中心 -> 再分簇 -> 直到稳定。
- 初始化质心:预先指定K,随机选取K个初始中心点。
- 分配样本:计算每个样本到各质心的距离,划分到距离最近的簇中。
- 更新质心:根据当前簇中的所有样本,更新计算该簇的中心点(均值)。
- 重复迭代:重新分配样本并更新质心,不断循环。
- 收敛停止:当质心位置不在变化、样本归属不再变化,或达到最大迭代次数时停止。

算法在第5次迭代后完全收敛!中心点不在移动。
K-Means的优化
优化1:初始质心选择(K-Means++)
随机选择初始质心可能导致算法陷入较差的局部最优解。不同的初始化方式,最终得到的聚类结果可能存在明显差异。
解决方案:K-Means++
先随机选取一个样本点作为第一个质心;之后,在选择下一个质心时,优先考虑那些距离当前已有质心较远的样本点。重复这一过程,直到选出 K 个初始质心。
SSE: 362.47
迭代次数: 3
优化2:肘部法则选择K值
K-Means最大的问题是簇数
- 依次尝试不同的K值,计算对应的SSE。
- 随着K增大,SSE会持续下降。
- 当K超过某个值,SSE下降幅度明显减小,曲线趋于平缓。
- 这个斜率突然减小、曲线由陡转平的拐点(肘部),就是较优的K值选择。

K-Means总结
- 思路简单、流程清晰、容易实现;
- 计算效率高,适合较大规模数据;
- 每个簇都有明确质心,结果较易解释。
⚡ 避坑指南:使用 K-Means 前先检查尺度
欧氏距离直接受量纲影响。若各特征单位不同,应只在训练数据上拟合标准化器,再对全部待聚类数据执行一致变换。
其他聚类模型
与 K-Means 使用均值不同,K-Medoids 选择真实存在的样本点作为中心,因此对异常值通常更鲁棒,但计算成本更高。
聚类模型常见应用
聚类算法常作为数据探索、特征工程和系统冷启动等环节的重要组成部分。
- 客户分群:电商用户RFM分层,自动识别用户价值层级,支撑精准运营。
- 图像处理:颜色量化、图像分割,压缩颜色空间,划分图像语义区域。
- 异常检测:网络入侵、信用卡欺诈,将远离所有簇中心的样本标记为潜在异常。
- 推荐系统:协同过滤的冷启动,将新用户/物品归入相似簇,快速生成初始推荐。
图像颜色压缩
使用K-Means把彩色图片里成千上万种颜色,压缩成 4 种、16 种、64 种颜色:

PCA主成分分析
特征维度过多带来的问题
在日常实践中,我们常遇到包含成百上千个特征的数据集。当特征维度不断增长时,一系列问题随之而来,被称为维度灾难:
计算成本急剧增长
参数量变大,导致严重的算力和时间开销。
数据稀疏性与过拟合风险
高维空间数据变得稀疏,所需数据量呈指数级增加,极易记住噪声导致过拟合
距离度量失效
高维空间中任意两点距离趋于相等,KNN、K-Means等基于距离的算法几乎失效。
多重共线性问题
特征之间往往存在强相关性(如“面积”与“房间数”),干扰模型评估。
传统的降维方法
为了应对数据特征过多问题,我们需要对数据进行预处理,减少特征数量,称之为降维。
直接保留有价值的原始特征,删除无关或冗余特征。
- 常见方法:方差阈值、相关性筛选、L1 正则化、树模型重要性;
- 优势:保留原始业务含义;
- 局限:删除特征时可能永久丢失部分信息。
什么是PCA?
主成分分析(Principal Component Analysis,PCA)
是一种无监督的线性降维算法。核心思想是将高维数据投影到低维空间,同时尽可能保留数据中存在的最大方差(即信息量)。

PCA就是在数学空间中帮你寻找这个绝佳角度的算法,这些角度就是所谓的主成分。
身高与体重
假设我们正在构建一个健康风险预测模型,数据集中有身高和体重两个特征:
- 身高越高的人,体重往往也越重。
- 数据点大致沿着从左下到右上的对角线排列。
这两个特征之间存在强烈的正相关关系,携带着重叠的信息。
寻找最佳代表方向
PCA会找到那条最能代表数据走势的对角线方向,然后把所有数据点都投影到这条线上。
- 投影之后,每个人只需要一个数值就能代表他的位置(从2维降到1维)。
- 这个新特征非常近似于医学上熟知的概念————BMI(体重指数)。
PCA在不知道医学知识的情况下,纯粹从数据的数学结果出发自动发现了这个组合特征。

复杂案例:房子的“地段”
假设构建二手房预测模型,有6个特征:地铁距离、公交线路数、商场数量、学校评分、医院等级、公园面积。
直接使用6维特征的问题
- 多重共线性:如地铁和公交线路数高度相关,难以评估作用。
- 特征冗余:大量信息重复,增加模型复杂度。
- 难以解释:某些特征权重对业务人员难以理解。
背后的隐藏因子:地段
这6个特征表面各不相同,但背后都与“地段好坏”密切相关。
一个好地段的小区,往往同时具备地铁近、公交多、商场多、学校好、医院近等特点。它们携带着高度重叠的信息。
PCA的压缩结果
PCA自动分析相关性,将这6个特征压缩成少数几个综合成分:
第一主成分(PC1)
赋予每个特征权重。可能解释了原始数据约70%的方差信息。
第二主成分(PC2)
捕捉另一维度的差异:郊区宜居型 vs 市中心商业型。可能解释了额外15%的方差。
这两个主成分合集解释了约85%的信息。PCA通过数学方法把隐藏因子挖掘出来,用少数维度替代大量冗余特征。
数学原理与处理流程
🔍 展开查看:PCA 的矩阵推导与手算示例
方差(Variance):
- 每个数减去均值,平方后再平均。
- 数据越分散,方差越大。
协方差(Covariance):
- 两个变量各自减去均值后相乘再平均。
- 正负反映两个变量是否同步变化。
PCA的底层逻辑建立在协方差矩阵和特征值分解之上。假设我们有一个包含n个样本、d个特征的数据集矩阵

PCA处理过程
- 数据去中心化
首先,将每个特征减去其均值,使中心化后的均值为 。注意:中心化不会消除量纲差异;若特征尺度差异明显,通常应在 PCA 前执行标准化:
假设我们测量了5个人的身高、坐高、体重:
| 人 | 身高(cm) | 坐高(cm) | 体重(kg) |
|---|---|---|---|
| A | 160 | 90 | 55 |
| B | 170 | 95 | 65 |
| C | 180 | 100 | 80 |
| D | 150 | 85 | 45 |
| E | 175 | 98 | 72 |
中心化后的数据矩阵
- 计算协方差矩阵
协方差可以衡量两个变量之间的相关性,我们计算去中心化后数据矩阵 的协方差矩阵 :
- 对角线元素是各特征的方差(如:身高145,坐高37.3,体重190.3)。
- 非对角线元素是特征之间的协方差(如:身高与体重的协方差165.25,说明两者正相关较强)。
- 计算特征值与特征向量
对协方差矩阵 进行特征分解,求解所有特征值 和对应的特征向量 :
- 特征向量:新的坐标轴方向,即主成分。
- 特征值:代表数据投影到对应特征向量上的方差大小,也就是主成分的重要性。
⭐ 特征分解结果
该协方差矩阵的近似特征值为:
对应的一组单位特征向量为:
的分量均为正,可理解为综合“体型大小”; 主要刻画身高、坐高与体重之间的比例差异; 很小,说明第三个方向上的信息极少,三个原始特征高度冗余。
特征向量整体乘以
仍表示同一主成分方向,因此不同软件输出的符号可能相反。
- 选取主成分并构建投影矩阵
一般会根据累积解释方差率决定降到什么维度,选出前k大的特征值加起来,除以总和,找到能超过阈值(如95%)的最小k:
总方差约为
- PC1 解释率约为
; - PC2 解释率约为
; - PC3 解释率约为
。
若阈值设为
形成投影矩阵
- 数据投影
最后,将原始中心化数据矩阵
- 原始
矩阵,经PCA降维后用两个新特征完整表达了原始信息。 - PC1(体型大小):从左到右大致是从瘦小到魁梧(D -> A -> B -> E -> C)。
- PC2(胖瘦比例):在同一体型水平上区分相对胖瘦。
- 总结:三个高度相关的物理量被压缩为两个独立主成分,消除了冗余,便于2D可视化。
PCA代码实现:
原始数据维度:(150, 4)
特征相关系数矩阵:
[[ 1. -0.118 0.872 0.818]
[-0.118 1. -0.428 -0.366]
[ 0.872 -0.428 1. 0.963]
[ 0.818 -0.366 0.963 1. ]]
各主成分特征值(方差): [2.9381 0.9202 0.1477 0.0209]
各主成分方差解释比: [0.7296 0.2285 0.0367 0.0052]
累积方差解释比: [0.7296 0.9581 0.9948 1. ]
主成分载荷矩阵(每列是一个主成分方向):
sepal length (cm) : PC1=+0.521 PC2=+0.377
sepal width (cm) : PC1=-0.269 PC2=+0.923
petal length (cm) : PC1=+0.580 PC2=+0.024
petal width (cm) : PC1=+0.565 PC2=+0.0672
3
4
5
6
7
8
9
10
11
12
13
14
15

- PC1(73.0%):花瓣长宽和花萼长度正相关,代表整体大小。
- PC2(22.9%):由花萼宽主导,代表形状差异。
- 仅用2个主成分捕获了95.8%信息。
- 从降维的2D图中能清晰地看到三个类别的分布情况,大幅简化了复杂度。
总结与局限性
- 无需标签,基于方差进行正交变换;
- 可消除变换后主成分之间的线性相关性;
- 能压缩高维数据,并常用于探索性可视化;
- 丢弃低方差方向有时能过滤噪声、缓解过拟合。
⚡ 避坑指南:PCA 必须防止数据泄漏
应只在训练集上执行 scaler.fit 与 pca.fit,再用同一变换处理验证集、测试集和线上数据。
随机森林 vs PCA
随机森林也可以通过特征重要度来做降维(特征选择),它们的区别是:
- 监督方式:需要标签,并以预测任务为导向;
- 关系类型:可以捕捉非线性与特征交互;
- 输出结果:保留原始特征,业务解释性较强;
- 注意事项:重要性有偏差风险,应在验证集上确认筛选效果。
二者可以串联使用,但是否有效必须通过交叉验证确认,不能默认组合后一定更好。
