第1章:绪论(Introduction)
一、导读
1.1 本章学习目标
第1章是整本《机器学习》(西瓜书)的开篇之作,它不深入讲解某个具体的算法细节,而是从全局视角为读者建立对机器学习这门学科的整体认知。通过本章的学习,你将理解机器学习的基本概念、主要类型和核心思想,为后续章节的深入学习奠定基础。
本章的核心学习目标包括:
- 理解机器学习的定义:什么是机器学习?它与传统的编程方式有何本质区别?Tom Mitchell 的形式化定义如何描述学习过程?
- 掌握学习的基本类型:监督学习、无监督学习、半监督学习和强化学习各自的特征是什么?它们分别适用于什么样的问题?
- 理解假设空间与版本空间:机器学习算法在什么样的假设空间中搜索?什么是版本空间?它与问题的归纳偏性有什么关系?
- 理解归纳偏好:为什么算法会偏好某些假设?奥卡姆剃刀原则在机器学习中如何体现?
- 理解没有免费的午餐定理(NFL):为什么不存在一个在所有问题上都最优的算法?这对算法选择有什么启示?
- 了解机器学习的典型应用与发展脉络:机器学习在哪些领域取得了成功?它的发展历程是怎样的?
1.2 为什么第一章如此重要
很多初学者会急于跳入具体的算法细节——决策树怎么分裂?SVM 怎么求解?神经网络怎么反向传播?——而忽视第一章的基础概念。这种想法是危险的。正如学习编程之前需要理解什么是变量、什么是控制流,学习机器学习也需要先理解"学习"本身的含义和边界。
周志华教授在第一章中精心安排了几个关键概念:假设空间、归纳偏好和没有免费的午餐定理。这三个概念构成了理解机器学习算法的理论基石。假设空间告诉我们算法"能学到什么",归纳偏好告诉我们算法"倾向于学什么",而 NFL 定理则提醒我们"没有万能的学习算法"。不理解这些概念,后续学习就容易陷入"只知用其然,不知知其所以然"的困境。
此外,第一章对监督学习、无监督学习等类型的划分,为后续章节的组织提供了框架。第3-8章主要讨论监督学习方法,第9章讨论聚类(无监督学习),第10-11章讨论特征工程,第12-14章涉及理论和其他学习范式,第15-16章讨论规则学习和强化学习。理解了第一章的分类框架,你就能更好地把握全书的知识结构。
1.3 核心问题引导
在阅读本章时,请带着以下问题思考:
如果让计算机从一堆西瓜数据中学会判断"好瓜"和"坏瓜",这个过程与人类学习有什么异同?
为什么说"机器学习没有免费的午餐"?这是否意味着我们不需要研究新算法了?
假设空间太大或太小分别会导致什么问题?如何在二者之间取得平衡?
监督学习和无监督学习的本质区别是什么?半监督学习为什么能利用未标记数据?
奥卡姆剃刀原则在机器学习中意味着什么?模型越简单一定越好吗?
机器学习的"归纳偏好"与人类的"先验知识"有什么联系?
二、核心概念详解
2.1 机器学习的基本定义
2.1.1 什么是机器学习
机器学习(Machine Learning)研究的是如何让计算机从数据中"学习"到规律,并利用这些规律对新数据做出预测或决策。与传统的编程方式不同,机器学习程序不需要显式地编写规则,而是通过算法自动从数据中发现模式。
Tom Mitchell 在 1997 年给出了一个广为接受的定义:
对于某类任务 T 和性能度量 P,如果一个计算机程序在 T 上以 P 衡量的性能随着经验 E 而自我完善,那么我们称这个计算机程序正在从经验中学习。
用西瓜的例子来说:任务 T 是"判断西瓜好不好",性能度量 P 是"判断的准确率",经验 E 是"已经看过的西瓜数据"。机器学习算法通过分析已有的西瓜数据(经验 E),学会了一个判断模型,使得它在新西瓜上的判断准确率(性能 P)越来越高。
# 传统编程 vs 机器学习
# 传统编程:人写规则
def is_good_watermelon_traditional(weight, color, root_sound):
if weight > 3 and color == 'dark_green' and root_sound == '浊响':
return '好瓜'
else:
return '坏瓜'
# 机器学习:从数据中学规则
def learn_from_data(data):
"""
data: [(weight, color, root_sound, label), ...]
算法自动从数据中学习判断规则
"""
# 这里用简单的决策树作为示例
from collections import Counter
labels = [d[3] for d in data]
return Counter(labels).most_common(1)[0][0] # 返回多数类这个定义中有三个关键词:任务(T)、性能度量(P)和经验(E)。不同的机器学习问题,这三者的具体内容不同:
| 应用场景 | 任务 T | 性能 P | 经验 E |
|---|---|---|---|
| 图像分类 | 识别图像中的物体 | 分类准确率 | 标注好的图像数据集 |
| 机器翻译 | 将一种语言翻译为另一种 | BLEU 分数 | 双语对照语料 |
| 推荐系统 | 预测用户偏好 | 推荐准确率/用户满意度 | 用户历史行为数据 |
| 自动驾驶 | 控制车辆行驶 | 安全性/到达目的地 | 传感器数据和驾驶经验 |
2.1.2 为什么需要机器学习
传统编程方法在很多问题上面临困难:
- 规则过于复杂:如图像识别,人无法用明确的规则描述"猫"的特征
- 规则难以穷举:如垃圾邮件检测,垃圾邮件的形式不断变化
- 需要适应变化:如推荐系统,用户偏好随时间变化
机器学习的优势在于:它能自动从数据中发现人难以手工总结的规律,并且能随着数据的增加不断改进。
2.2 学习的基本类型
2.2.1 监督学习(Supervised Learning)
监督学习是机器学习中最重要的范式。在监督学习中,训练数据由一组样本-标签对组成,算法的目标是学习一个从样本空间到标签空间的映射。
import numpy as np
# 西瓜数据集(简化版)
# 特征:色泽(0=青绿,1=乌黑), 根蒂(0=蜷缩,1=稍蜷,2=硬挺), 敲声(0=浊响,1=沉闷)
X_train = np.array([
[0, 0, 0], # 青绿, 蜷缩, 浊响
[1, 0, 0], # 乌黑, 蜷缩, 浊响
[1, 0, 1], # 乌黑, 蜷缩, 沉闷
[0, 0, 0], # 青绿, 蜷缩, 浊响
[1, 1, 0], # 乌黑, 稍蜷, 浊响
[0, 2, 0], # 青绿, 硬挺, 浊响
[1, 0, 0], # 乌黑, 蜷缩, 浊响
[0, 1, 1], # 青绿, 稍蜷, 沉闷
])
y_train = np.array([1, 1, 0, 1, 1, 0, 1, 0]) # 1=好瓜, 0=坏瓜监督学习根据标签的类型可以分为:
- 分类(Classification):标签是离散的类别,如"好瓜/坏瓜"
- 回归(Regression):标签是连续的数值,如预测房价
二分类是最基础的分类任务,多分类可以转化为多个二分类问题。回归任务的输出是连续值,如预测西瓜的重量。
2.2.2 无监督学习(Unsupervised Learning)
无监督学习的训练数据只有样本,没有标签。算法需要自己发现数据中的结构和模式。
import numpy as np
from collections import defaultdict
def simple_kmeans(X, k, max_iter=100):
"""
K-Means 聚类算法
X: 数据矩阵 (n_samples, n_features)
k: 聚类数
"""
n_samples = X.shape[0]
# 随机初始化聚类中心
centroids = X[np.random.choice(n_samples, k, replace=False)]
for iteration in range(max_iter):
# 分配样本到最近的聚类中心
distances = np.sqrt(((X[:, np.newaxis] - centroids) ** 2).sum(axis=2))
labels = np.argmin(distances, axis=1)
# 更新聚类中心
new_centroids = np.array([
X[labels == i].mean(axis=0) if np.sum(labels == i) > 0
else centroids[i]
for i in range(k)
])
# 检查收敛
if np.allclose(centroids, new_centroids):
print(f"在第 {iteration + 1} 次迭代后收敛")
break
centroids = new_centroids
return labels, centroids
# 生成模拟数据:两簇西瓜
np.random.seed(42)
cluster1 = np.random.randn(20, 2) + [2, 2] # 好瓜簇
cluster2 = np.random.randn(20, 2) + [-1, -1] # 坏瓜簇
X_unlabeled = np.vstack([cluster1, cluster2])
labels, centroids = simple_kmeans(X_unlabeled, k=2)
print(f"聚类中心:\n{centroids}")常见的无监督学习任务包括:
- 聚类(Clustering):将数据分成若干组,使组内相似、组间不同
- 降维(Dimensionality Reduction):将高维数据映射到低维空间
- 密度估计(Density Estimation):估计数据的概率分布
- 异常检测(Anomaly Detection):发现数据中的异常点
2.2.3 半监督学习(Semi-supervised Learning)
半监督学习介于监督学习和无监督学习之间:训练数据中只有少量样本有标签,大量样本没有标签。这在现实中非常常见——获取标签通常需要人工标注,成本高昂;而未标记数据则容易大量获取。
半监督学习的核心假设是聚类假设或流形假设:如果两个样本在特征空间中距离很近,那么它们很可能属于同一类。利用这个假设,半监督学习可以用未标记数据来改善学习效果。
# 半监督学习的直觉:标签传播
# 假设有少量标记数据和大量未标记数据
# 核心思想:相似的样本应该有相似的标签
def label_propagation_simple(X_labeled, y_labeled, X_unlabeled, k=3):
"""
简单的KNN标签传播
"""
y_unlabeled = np.zeros(len(X_unlabeled), dtype=int)
X_all = np.vstack([X_labeled, X_unlabeled])
for i, x in enumerate(X_unlabeled):
# 计算与所有标记样本的距离
distances = np.sqrt(((X_labeled - x) ** 2).sum(axis=1))
# 找到最近的k个标记样本
nearest_indices = np.argsort(distances)[:k]
# 投票决定标签
nearest_labels = y_labeled[nearest_indices]
y_unlabeled[i] = np.bincount(nearest_labels).argmax()
return y_unlabeled2.2.4 强化学习(Reinforcement Learning)
强化学习与前面三种学习方式有本质区别。在强化学习中,智能体(agent)通过与环境(environment)的交互来学习。智能体执行动作(action),环境返回奖励(reward)和新状态(state),智能体的目标是最大化累积奖励。
强化学习的核心挑战是探索与利用的权衡(exploration vs. exploitation):是尝试未知的动作以获取新信息(探索),还是选择已知最优的动作以获得奖励(利用)?
# K-摇臂赌博机:最简单的强化学习问题
import numpy as np
class KArmedBandit:
def __init__(self, k=10):
self.k = k
# 每个摇臂的真实奖励均值(智能体不知道)
self.true_values = np.random.randn(k)
def pull(self, action):
"""拉动某个摇臂,返回奖励"""
return np.random.normal(self.true_values[action], 1.0)
class EpsilonGreedy:
def __init__(self, k, epsilon=0.1):
self.k = k
self.epsilon = epsilon
self.q_estimates = np.zeros(k) # 估计价值
self.action_counts = np.zeros(k) # 动作计数
def select_action(self):
if np.random.random() < self.epsilon:
return np.random.randint(self.k) # 探索
return np.argmax(self.q_estimates) # 利用
def update(self, action, reward):
self.action_counts[action] += 1
# 增量更新估计价值
n = self.action_counts[action]]
self.q_estimates[action] += (reward - self.q_estimates[action]) / n
# 模拟
bandit = KArmedBandit(k=10)
agent = EpsilonGreedy(k=10, epsilon=0.1)
rewards = []
for _ in range(1000):
action = agent.select_action()
reward = bandit.pull(action)
agent.update(action, reward)
rewards.append(reward)
print(f"真实最优动作: {np.argmax(bandit.true_values)}")
print(f"估计最优动作: {np.argmax(agent.q_estimates)}")
print(f"平均奖励: {np.mean(rewards[-100:]):.2f}")2.3 假设空间与版本空间
2.3.1 假设空间
机器学习的过程可以看作是在假设空间(hypothesis space)中搜索的过程。假设空间是所有可能的模型(假设)的集合。
以西瓜分类为例,如果我们只考虑色泽、根蒂、敲声三个特征,每个特征有若干取值,那么假设空间就是由这些特征的所有可能组合构成的集合。
# 假设空间的枚举
# 简化版:每个特征有3种取值 + "任意"(通配符*)
color_options = ['青绿', '乌黑', '*'] # * 表示任意色泽
root_options = ['蜷缩', '稍蜷', '硬挺', '*']
sound_options = ['浊响', '沉闷', '*']
hypothesis_space = []
for c in color_options:
for r in root_options:
for s in sound_options:
if c == '*' and r == '*' and s == '*':
continue # 排除"什么都行"的假设
hypothesis_space.append((c, r, s))
print(f"假设空间大小: {len(hypothesis_space)}")
# 输出: 假设空间大小: 3*4*3 - 1 = 35假设空间的大小直接影响学习的难度:
- 假设空间太小:可能无法包含真正的目标概念,导致欠拟合
- 假设空间太大:搜索效率低,容易过拟合
2.3.2 版本空间
版本空间(version space)是假设空间中的一个子集,包含了与训练数据一致的所有假设。
# 训练数据
training_data = [
{'color': '青绿', 'root': '蜷缩', 'sound': '浊响', 'label': '好瓜'},
{'color': '乌黑', 'root': '蜷缩', 'sound': '浊响', 'label': '好瓜'},
{'color': '乌黑', 'root': '蜷缩', 'sound': '沉闷', 'label': '坏瓜'},
{'color': '青绿', 'root': '蜷缩', 'sound': '浊响', 'label': '好瓜'},
]
# 版本空间:与训练数据一致的所有假设
# 好瓜样本的色泽:青绿、乌黑 → 色泽不能确定(用*表示)
# 好瓜样本的根蒂:都是蜷缩 → 根蒂=蜷缩
# 好瓜样本的敲声:都是浊响 → 敲声=浊响
# 但还要检查是否与坏瓜样本不矛盾
# 坏瓜样本3:乌黑+蜷缩+沉闷 → 与假设 (*,蜷缩,浊响) 不矛盾(因为敲声不同)
# 版本空间中的一个假设示例
version_space_example = ('*', '蜷缩', '浊响')
print(f"版本空间中的一个假设: 色泽=任意, 根蒂=蜷缩, 敲声=浊响")版本空间的概念告诉我们:训练数据不能唯一确定最优假设,版本空间中可能有多个假设都与训练数据一致。选择哪个假设,取决于算法的归纳偏好。
2.4 归纳偏好
2.4.1 什么是归纳偏好
机器学习算法在学习过程中会隐含地偏好某些类型的假设,这种偏好称为归纳偏好(inductive bias)。归纳偏好是算法"认为什么样的假设更好"的先验信念。
一个经典的类比是奥卡姆剃刀原则(Occam's Razor):如有多个假设与观察一致,则选择最简单的那个。
# 奥卡姆剃刀的直觉
# 假设有两个假设都能完美拟合训练数据:
# 假设1(简单):如果色泽=乌黑且根蒂=蜷缩,则是好瓜
# 假设2(复杂):如果(色泽=乌黑且根蒂=蜷缩且敲声=浊响)
# 或(色泽=青绿且根蒂=蜷缩且敲声=浊响且重量>3),则是好瓜
# 奥卡姆剃刀倾向于选择假设1
# 在决策树中,这体现为偏好更浅的树
# 在线性模型中,这体现为正则化(偏好权重更小的模型)2.4.2 归纳偏好的重要性
归纳偏好是机器学习算法能够泛化到新数据的关键。没有归纳偏好,算法就无法在版本空间的多个假设中做出选择,也就无法对新数据做出预测。
不同的算法有不同的归纳偏好:
- 决策树:偏好更浅的树(奥卡姆剃刀)
- 线性模型:偏好线性决策边界
- 神经网络:偏好平滑的函数映射
- SVM:偏好最大间隔的超平面
- KNN:偏好局部相似的假设
2.5 没有免费的午餐定理
2.5.1 NFL 定理的内容
没有免费的午餐定理(No Free Lunch Theorem)是机器学习理论中最重要的定理之一。它的核心结论是:
对所有可能的问题(目标函数),所有学习算法的期望性能相同。
更具体地说:如果算法 A 在某些问题上优于算法 B,那么必然存在另一些问题,算法 B 优于算法 A。不存在一个在所有问题上都最优的算法。
# NFL定理的直觉演示
import numpy as np
def evaluate_algorithm(algo_func, target_func, X_test):
"""评估算法在某个目标函数上的表现"""
predictions = algo_func(X_test)
true_labels = target_func(X_test)
return np.mean(predictions == true_labels)
# 算法A:简单线性分类器
def algo_a(X):
return (X[:, 0] + X[:, 1] > 0).astype(int)
# 算法B:基于距离的分类器
def algo_b(X):
return (np.sqrt(X[:, 0]**2 + X[:, 1]**2) > 1).astype(int)
# 在问题1上,A可能更好
# 在问题2上,B可能更好
# NFL定理说:对所有可能的问题取平均,A和B的期望性能相同2.5.2 NFL 定理的启示
NFL 定理的实践意义深远:
没有万能算法:不要寻找"最好的"算法,而要寻找"最适合当前问题的"算法
算法选择需要结合问题:了解数据的特性、问题的结构,才能选择合适的算法
算法设计的核心是归纳偏好:好的算法应该有与问题匹配的归纳偏好
实验验证的重要性:理论分析无法告诉我们哪个算法最好,必须通过实验比较
# NFL定理的实践启示
# 不要这样做:
# "我听说XGBoost是最好的算法,所有问题都用它"
# 应该这样做:
# 1. 理解数据特性(线性?非线性?高维?稀疏?)
# 2. 根据问题选择合适的候选算法
# 3. 通过交叉验证比较算法性能
# 4. 选择性能、效率、可解释性的最佳平衡
problem_characteristics = {
'数据量': '大/中/小',
'特征维度': '高维/低维',
'特征类型': '连续/离散/混合',
'线性可分性': '线性/非线性',
'噪声水平': '高/中/低',
'可解释性需求': '高/低',
}2.6 机器学习的发展脉络
机器学习的发展历程可以大致分为几个阶段:
- 1950s-1960s:萌芽期:感知机(Rosenblatt, 1957)、决策树(Hunt等, 1966)
- 1970s-1980s:第一次低谷与复兴:感知机局限性被发现(Minsky & Papert, 1969),专家系统兴起,反向传播算法重新被发现(Rumelhart等, 1986)
- 1990s:统计学习兴起:SVM(Vapnik, 1995)、集成学习、贝叶斯方法
- 2000s-2010s:深度学习革命:深度神经网络在图像识别、语音识别、自然语言处理等领域取得突破
- 2020s:大模型时代:大规模预训练模型(GPT、BERT等)引领新一轮发展
# 机器学习算法发展时间线(简化)
timeline = {
1957: "感知机 (Perceptron)",
1967: "最近邻算法 (KNN)",
1979: "决策树 (CART)",
1986: "反向传播算法 (Backpropagation)",
1995: "支持向量机 (SVM)",
1997: "AdaBoost",
2001: "随机森林 (Random Forest)",
2006: "深度学习 (Deep Learning)",
2012: "AlexNet - ImageNet竞赛冠军",
2014: "GAN - 生成对抗网络",
2017: "Transformer - Attention Is All You Need",
2020: "GPT-3 - 大规模预训练语言模型",
}
for year, event in sorted(timeline.items()):
print(f"{year}: {event}")三、实战案例
3.1 案例一:西瓜数据集上的简单分类
import numpy as np
from collections import Counter
# 西瓜数据集(周志华《机器学习》表1.1)
watermelon_data = [
{'色泽': '青绿', '根蒂': '蜷缩', '敲声': '浊响', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑', '好瓜': True},
{'色泽': '乌黑', '根蒂': '蜷缩', '敲声': '沉闷', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑', '好瓜': True},
{'色泽': '乌黑', '根蒂': '蜷缩', '敲声': '浊响', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑', '好瓜': True},
{'色泽': '青绿', '根蒂': '蜷缩', '敲声': '沉闷', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑', '好瓜': True},
{'色泽': '乌黑', '根蒂': '蜷缩', '敲声': '浊响', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑', '好瓜': True},
{'色泽': '青绿', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '清晰', '脐部': '稍凹', '触感': '软粘', '好瓜': False},
{'色泽': '乌黑', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '稍糊', '脐部': '稍凹', '触感': '软粘', '好瓜': False},
{'色泽': '乌黑', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '清晰', '脐部': '稍凹', '触感': '硬滑', '好瓜': False},
{'色泽': '青绿', '根蒂': '稍蜷', '敲声': '沉闷', '纹理': '稍糊', '脐部': '稍凹', '触感': '硬滑', '好瓜': False},
{'色泽': '乌黑', '根蒂': '稍蜷', '敲声': '沉闷', '纹理': '稍糊', '脐部': '凹陷', '触感': '硬滑', '好瓜': False},
]
# 简单的统计学习:计算每个特征值与好瓜的关联
def analyze_feature(data, feature_name):
"""分析单个特征与标签的关联"""
feature_values = set(d[feature_name] for d in data)
print(f"\n特征: {feature_name}")
for val in sorted(feature_values):
subset = [d for d in data if d[feature_name] == val]
good_count = sum(1 for d in subset if d['好瓜'])
bad_count = len(subset) - good_count
print(f" {val}: 好瓜 {good_count}/{len(subset)}, "
f"坏瓜 {bad_count}/{len(subset)}, "
f"好瓜率 {good_count/len(subset):.1%}")
for feature in ['色泽', '根蒂', '敲声', '纹理', '脐部', '触感']:
analyze_feature(watermelon_data, feature)运行结果分析:
- "根蒂=蜷缩"的西瓜中,好瓜比例最高
- "纹理=清晰"的西瓜中,好瓜比例较高
- "触感=硬滑"的西瓜中,好瓜比例较高
- 这些统计规律就是机器学习算法试图自动发现的"模式"
3.2 案例二:NFL 定理的数值验证
import numpy as np
from itertools import product
def nfl_simulation(n_problems=1000, n_train=10, n_test=10):
"""
数值模拟NFL定理
在所有可能的目标函数上比较两个算法的平均性能
"""
n_samples = n_train + n_test
n_features = 2
X_all = np.random.randn(n_samples, n_features)
# 生成所有可能的二分类目标函数
# 对于n个样本,有2^n种可能的标签组合
all_possible_targets = list(product([0, 1], repeat=n_samples))
algo_a_scores = []
algo_b_scores = []
for target_labels in all_possible_targets[:n_problems]: # 取部分问题
y = np.array(target_labels)
X_train, X_test = X_all[:n_train], X_all[n_train:]
y_train, y_test = y[:n_train], y[n_train:]
# 算法A:最近邻
pred_a = np.zeros(n_test)
for i, x in enumerate(X_test):
dists = np.sqrt(((X_train - x) ** 2).sum(axis=1))
pred_a[i] = y_train[np.argmin(dists)]
# 算法B:多数类
majority_class = 1 if y_train.sum() > n_train / 2 else 0
pred_b = np.full(n_test, majority_class)
algo_a_scores.append(np.mean(pred_a == y_test))
algo_b_scores.append(np.mean(pred_b == y_test))
print(f"算法A(最近邻)平均准确率: {np.mean(algo_a_scores):.4f}")
print(f"算法B(多数类)平均准确率: {np.mean(algo_b_scores):.4f}")
print(f"差异: {abs(np.mean(algo_a_scores) - np.mean(algo_b_scores)):.4f}")
print("NFL定理预测:在所有问题上取平均,两个算法性能接近")
nfl_simulation(n_problems=100)3.3 案例三:监督学习 vs 无监督学习对比
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
# 生成西瓜数据
np.random.seed(42)
n_good = 30
n_bad = 30
# 好瓜:重量大、颜色深、声音浊
good_weight = np.random.normal(3.5, 0.5, n_good)
good_density = np.random.normal(0.8, 0.1, n_good)
# 坏瓜:重量小、颜色浅、声音闷
bad_weight = np.random.normal(2.0, 0.5, n_bad)
bad_density = np.random.normal(0.4, 0.1, n_bad)
X = np.vstack([
np.column_stack([good_weight, good_density]),
np.column_stack([bad_weight, bad_density])
])
y = np.array([1] * n_good + [0] * n_bad)
# 监督学习:用标签训练分类器
from collections import defaultdict
def knn_predict(X_train, y_train, X_test, k=3):
"""KNN分类器"""
predictions = []
for x in X_test:
dists = np.sqrt(((X_train - x) ** 2).sum(axis=1))
nearest = np.argsort(dists)[:k]
label = np.bincount(y_train[nearest].astype(int)).argmax()
predictions.append(label)
return np.array(predictions)
# 无监督学习:K-Means聚类
def kmeans(X, k, max_iter=100):
centroids = X[np.random.choice(len(X), k, replace=False)]
for _ in range(max_iter):
dists = np.sqrt(((X[:, np.newaxis] - centroids) ** 2).sum(axis=2))
labels = np.argmin(dists, axis=1)
new_centroids = np.array([X[labels == i].mean(axis=0) for i in range(k)])
if np.allclose(centroids, new_centroids):
break
centroids = new_centroids
return labels
# 对比
# 监督学习:使用训练数据的标签
train_idx = np.random.choice(len(X), 40, replace=False)
test_idx = np.setdiff1d(np.arange(len(X)), train_idx)
pred_supervised = knn_predict(X[train_idx], y[train_idx], X[test_idx], k=3)
acc_supervised = np.mean(pred_supervised == y[test_idx])
# 无监督学习:不使用标签
cluster_labels = kmeans(X, k=2)
# 聚类结果需要与真实标签对齐
from scipy import stats
mode_labels = np.zeros_like(cluster_labels)
for c in range(2):
mask = cluster_labels == c
if mask.sum() > 0:
mode_labels[mask] = stats.mode(y[mask], keepdims=True).mode[0]
acc_unsupervised = np.mean(mode_labels == y)
print(f"监督学习(KNN)准确率: {acc_supervised:.2%}")
print(f"无监督学习(K-Means)准确率: {acc_unsupervised:.2%}")
print("监督学习利用标签信息,通常效果更好")
print("无监督学习不需要标签,但性能通常较低")四、本章小结
第1章为全书奠定了概念基础,核心知识点包括:
机器学习的定义:从数据中学习规律,利用经验改进性能。Tom Mitchell 的形式化定义明确了任务(T)、性能(P)和经验(E)三要素。
学习的基本类型:监督学习(有标签)、无监督学习(无标签)、半监督学习(少量标签+大量无标签)、强化学习(与环境交互获取奖励)。
假设空间与版本空间:机器学习是在假设空间中搜索的过程,版本空间是与训练数据一致的所有假设的集合。
归纳偏好:算法对"好假设"的偏好是泛化的关键。奥卡姆剃刀是最常见的归纳偏好——偏好简单的模型。
没有免费的午餐定理:不存在在所有问题上都最优的算法。算法的选择必须结合具体问题。
机器学习的发展脉络:从感知机到深度学习,经历了多次起伏,数据量和计算力的增长是推动发展的关键因素。
本章最重要的启示是:理解"学习"本身的机制比掌握具体算法更重要。归纳偏好决定了算法的适用场景,NFL 定理提醒我们不存在万能算法。这些基本认识将贯穿后续所有章节的学习。
五、思考与练习
思考题
考虑"判断邮件是否为垃圾邮件"这个任务,请按照 Tom Mitchell 的定义,明确指出任务 T、性能 P 和经验 E 分别是什么。随着时间推移,垃圾邮件的形式不断变化,这对学习算法提出了什么要求?
假设你有一个西瓜数据集,其中假设空间包含 1000 个假设,训练数据排除了其中 800 个。那么版本空间有多大?如果训练数据增加,版本空间会如何变化?
如果一个算法的归纳偏好是"偏好复杂的模型",这可能导致什么问题?与奥卡姆剃刀原则矛盾吗?在什么情况下这种偏好可能是合理的?
NFL 定理说所有算法在所有问题上的平均性能相同,但在实际中,为什么深度学习在图像识别上远优于决策树?这是否与 NFL 定理矛盾?
半监督学习在什么假设下能够利用未标记数据?如果这个假设不成立,半监督学习会怎样?
编程练习
实现一个简单的"懒惰学习器":它不学习任何规则,对每个新样本直接返回训练集中多数类的标签。在西瓜数据集上测试它的性能,与一个简单的规则学习器比较。
验证 NFL 定理:编写程序,在随机生成的二分类问题上,比较至少三种不同的分类算法(如 KNN、决策树、线性分类器)的平均性能。
探索假设空间:对于只包含色泽和根蒂两个特征的简化西瓜数据,枚举完整的假设空间和版本空间。观察训练数据增加时版本空间的变化。