1机器学习

绪论

机器学习是什么

机器学习定义学习类型应用

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第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)越来越高。

python
# 传统编程 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)

监督学习是机器学习中最重要的范式。在监督学习中,训练数据由一组样本-标签对组成,算法的目标是学习一个从样本空间到标签空间的映射。

python
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)

无监督学习的训练数据只有样本,没有标签。算法需要自己发现数据中的结构和模式。

python
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)

半监督学习介于监督学习和无监督学习之间:训练数据中只有少量样本有标签,大量样本没有标签。这在现实中非常常见——获取标签通常需要人工标注,成本高昂;而未标记数据则容易大量获取。

半监督学习的核心假设是聚类假设或流形假设:如果两个样本在特征空间中距离很近,那么它们很可能属于同一类。利用这个假设,半监督学习可以用未标记数据来改善学习效果。

python
# 半监督学习的直觉:标签传播
# 假设有少量标记数据和大量未标记数据
# 核心思想:相似的样本应该有相似的标签

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_unlabeled

2.2.4 强化学习(Reinforcement Learning)

强化学习与前面三种学习方式有本质区别。在强化学习中,智能体(agent)通过与环境(environment)的交互来学习。智能体执行动作(action),环境返回奖励(reward)和新状态(state),智能体的目标是最大化累积奖励。

强化学习的核心挑战是探索与利用的权衡(exploration vs. exploitation):是尝试未知的动作以获取新信息(探索),还是选择已知最优的动作以获得奖励(利用)?

python
# 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)中搜索的过程。假设空间是所有可能的模型(假设)的集合。

以西瓜分类为例,如果我们只考虑色泽、根蒂、敲声三个特征,每个特征有若干取值,那么假设空间就是由这些特征的所有可能组合构成的集合。

python
# 假设空间的枚举
# 简化版:每个特征有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)是假设空间中的一个子集,包含了与训练数据一致的所有假设。

python
# 训练数据
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):如有多个假设与观察一致,则选择最简单的那个。

python
# 奥卡姆剃刀的直觉
# 假设有两个假设都能完美拟合训练数据:
# 假设1(简单):如果色泽=乌黑且根蒂=蜷缩,则是好瓜
# 假设2(复杂):如果(色泽=乌黑且根蒂=蜷缩且敲声=浊响)
#               或(色泽=青绿且根蒂=蜷缩且敲声=浊响且重量>3),则是好瓜
# 奥卡姆剃刀倾向于选择假设1

# 在决策树中,这体现为偏好更浅的树
# 在线性模型中,这体现为正则化(偏好权重更小的模型)

2.4.2 归纳偏好的重要性

归纳偏好是机器学习算法能够泛化到新数据的关键。没有归纳偏好,算法就无法在版本空间的多个假设中做出选择,也就无法对新数据做出预测。

不同的算法有不同的归纳偏好:

  • 决策树:偏好更浅的树(奥卡姆剃刀)
  • 线性模型:偏好线性决策边界
  • 神经网络:偏好平滑的函数映射
  • SVM:偏好最大间隔的超平面
  • KNN:偏好局部相似的假设

2.5 没有免费的午餐定理

2.5.1 NFL 定理的内容

没有免费的午餐定理(No Free Lunch Theorem)是机器学习理论中最重要的定理之一。它的核心结论是:

对所有可能的问题(目标函数),所有学习算法的期望性能相同。

更具体地说:如果算法 A 在某些问题上优于算法 B,那么必然存在另一些问题,算法 B 优于算法 A。不存在一个在所有问题上都最优的算法。

python
# 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 定理的实践意义深远:

没有万能算法:不要寻找"最好的"算法,而要寻找"最适合当前问题的"算法

算法选择需要结合问题:了解数据的特性、问题的结构,才能选择合适的算法

算法设计的核心是归纳偏好:好的算法应该有与问题匹配的归纳偏好

实验验证的重要性:理论分析无法告诉我们哪个算法最好,必须通过实验比较

python
# 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等)引领新一轮发展
python
# 机器学习算法发展时间线(简化)
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 案例一:西瓜数据集上的简单分类

python
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 定理的数值验证

python
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 无监督学习对比

python
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、决策树、线性分类器)的平均性能。

探索假设空间:对于只包含色泽和根蒂两个特征的简化西瓜数据,枚举完整的假设空间和版本空间。观察训练数据增加时版本空间的变化。

1/16绪论
0%