用Python实战复现机器学习核心算法:从理论到代码的深度理解

期末考试临近,许多同学面对厚厚的机器学习教材和公式推导感到无从下手。传统的死记硬背不仅效率低下,更难以真正理解算法背后的数学之美。本文将带你用Python和Scikit-learn库,通过编写代码的方式,亲手实现决策树、神经网络和支持向量机(SVM)三大核心算法,让抽象的理论变得触手可及。

1. 环境准备与数据生成

在开始编码之前,我们需要搭建一个适合机器学习实验的环境。推荐使用Anaconda创建独立的Python环境,避免库版本冲突:

conda create -n ml_exam python=3.8
conda activate ml_exam
pip install numpy matplotlib scikit-learn pandas

为了演示算法原理,我们将使用Scikit-learn的 make_classification 函数生成模拟数据集。这种方法可以灵活控制数据特征,便于观察算法行为:

from sklearn.datasets import make_classification

# 生成二分类数据集
X, y = make_classification(
    n_samples=500, 
    n_features=2,
    n_informative=2,
    n_redundant=0,
    n_classes=2,
    random_state=42
)

提示:设置 random_state 参数可以确保每次生成相同的数据,便于结果复现

2. 决策树:从信息增益到可视化分类边界

决策树是机器学习中最直观的算法之一,它通过一系列if-then规则对数据进行分类。理解决策树的关键在于掌握三种划分标准:信息增益、增益率和基尼指数。

2.1 信息增益计算实战

信息增益基于信息熵的概念,衡量特征对数据纯度的提升程度。让我们手动实现信息熵和信息增益的计算:

import numpy as np

def entropy(y):
    """计算信息熵"""
    hist = np.bincount(y)
    ps = hist / len(y)
    return -np.sum([p * np.log2(p) for p in ps if p > 0])

def information_gain(X, y, feature_idx, threshold):
    """计算信息增益"""
    parent_entropy = entropy(y)
    
    # 根据阈值划分数据集
    left_indices = X[:, feature_idx] <= threshold
    right_indices = X[:, feature_idx] > threshold
    
    n = len(y)
    n_left, n_right = sum(left_indices), sum(right_indices)
    
    if n_left == 0 or n_right == 0:
        return 0
    
    # 计算子节点熵
    e_left = entropy(y[left_indices])
    e_right = entropy(y[right_indices])
    
    # 计算信息增益
    child_entropy = (n_left/n) * e_left + (n_right/n) * e_right
    return parent_entropy - child_entropy

2.2 决策树可视化与剪枝策略

Scikit-learn提供了完整的决策树实现,我们可以训练并可视化决策边界:

from sklearn.tree import DecisionTreeClassifier, plot_tree
import matplotlib.pyplot as plt

# 训练决策树
dt = DecisionTreeClassifier(max_depth=3, criterion='gini')
dt.fit(X, y)

# 可视化决策树
plt.figure(figsize=(12,8))
plot_tree(dt, filled=True, feature_names=['Feature 1', 'Feature 2'])
plt.show()

决策树容易过拟合,剪枝是提高泛化能力的关键技术。Scikit-learn提供了多种剪枝参数:

参数 作用 推荐值
max_depth 限制树的最大深度 3-10
min_samples_split 节点分裂所需最小样本数 2-20
min_samples_leaf 叶节点最少样本数 1-10
ccp_alpha 复杂度剪枝参数 0.01-0.1

3. 神经网络:从感知机到多层网络

神经网络通过模拟人脑神经元的工作方式实现复杂函数逼近。理解神经网络需要从最简单的感知机开始。

3.1 感知机实现与局限性

感知机是最简单的神经网络,只能解决线性可分问题。我们用Python实现一个感知机:

class Perceptron:
    def __init__(self, learning_rate=0.01, n_iters=1000):
        self.lr = learning_rate
        self.n_iters = n_iters
        self.weights = None
        self.bias = None
    
    def fit(self, X, y):
        n_samples, n_features = X.shape
        
        # 初始化参数
        self.weights = np.zeros(n_features)
        self.bias = 0
        
        # 训练过程
        for _ in range(self.n_iters):
            for idx, x_i in enumerate(X):
                linear_output = np.dot(x_i, self.weights) + self.bias
                y_pred = np.where(linear_output >= 0, 1, 0)
                
                # 参数更新
                update = self.lr * (y[idx] - y_pred)
                self.weights += update * x_i
                self.bias += update
    
    def predict(self, X):
        linear_output = np.dot(X, self.weights) + self.bias
        return np.where(linear_output >= 0, 1, 0)

3.2 多层神经网络与BP算法

对于非线性问题,我们需要多层神经网络。Scikit-learn的MLPClassifier实现了BP算法:

from sklearn.neural_network import MLPClassifier

# 创建多层感知机
mlp = MLPClassifier(
    hidden_layer_sizes=(5, 3),  # 两个隐藏层,分别有5和3个神经元
    activation='relu',         # 使用ReLU激活函数
    solver='adam',             # 优化算法
    max_iter=1000,
    random_state=42
)

mlp.fit(X, y)

# 可视化决策边界
def plot_decision_boundary(model, X, y):
    # 设置绘图范围
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    
    # 生成网格点
    xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
                         np.arange(y_min, y_max, 0.02))
    
    # 预测每个点的类别
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    
    # 绘制等高线图
    plt.contourf(xx, yy, Z, alpha=0.4)
    plt.scatter(X[:, 0], X[:, 1], c=y, s=20, edgecolor='k')
    plt.show()

plot_decision_boundary(mlp, X, y)

4. 支持向量机:从最大间隔到核技巧

SVM通过寻找最大间隔超平面实现分类,是机器学习中的经典算法。

4.1 线性SVM与间隔最大化

让我们用Scikit-learn实现线性SVM并观察支持向量:

from sklearn.svm import SVC

# 训练线性SVM
svm = SVC(kernel='linear', C=1.0)
svm.fit(X, y)

# 获取支持向量
support_vectors = svm.support_vectors_

# 绘制决策边界和支持向量
plt.scatter(X[:, 0], X[:, 1], c=y, s=30, cmap=plt.cm.Paired)
ax = plt.gca()
xlim = ax.get_xlim()
ylim = ax.get_ylim()

# 创建网格来评估模型
xx = np.linspace(xlim[0], xlim[1], 30)
yy = np.linspace(ylim[0], ylim[1], 30)
YY, XX = np.meshgrid(yy, xx)
xy = np.vstack([XX.ravel(), YY.ravel()]).T
Z = svm.decision_function(xy).reshape(XX.shape)

# 绘制决策边界和间隔
ax.contour(XX, YY, Z, colors='k', levels=[-1, 0, 1], alpha=0.5,
           linestyles=['--', '-', '--'])
# 绘制支持向量
ax.scatter(support_vectors[:, 0], support_vectors[:, 1], s=100,
           linewidth=1, facecolors='none', edgecolors='k')
plt.show()

4.2 核函数与非线性SVM

对于非线性可分数据,核函数可以将数据映射到高维空间。常用的核函数包括:

  • 多项式核: kernel='poly'
  • 高斯核(RBF): kernel='rbf'
  • Sigmoid核: kernel='sigmoid'
# 生成非线性可分数据
X_nonlinear, y_nonlinear = make_classification(
    n_samples=500, 
    n_features=2,
    n_informative=2,
    n_redundant=0,
    n_classes=2,
    n_clusters_per_class=1,
    random_state=42,
    class_sep=0.8
)

# 使用RBF核训练SVM
svm_rbf = SVC(kernel='rbf', gamma=0.5, C=1.0)
svm_rbf.fit(X_nonlinear, y_nonlinear)

# 可视化决策边界
plot_decision_boundary(svm_rbf, X_nonlinear, y_nonlinear)

5. 算法对比与实战建议

通过前面的实践,我们已经实现了三种核心算法。下表总结了它们的特性和适用场景:

算法 优点 缺点 适用场景
决策树 解释性强,无需特征缩放 容易过拟合 结构化数据,需要解释性的场景
神经网络 强大的表示能力 需要大量数据,调参复杂 图像、语音等复杂模式识别
SVM 小样本效果好,核技巧强大 大规模数据效率低 中小规模数据,特别是高维数据

在实际项目中,我通常会遵循以下流程选择算法:

  1. 首先尝试简单的线性模型作为基准
  2. 对于结构化数据,使用决策树或随机森林
  3. 对于复杂模式识别问题,考虑神经网络
  4. 当数据量不大但特征维度高时,优先尝试SVM

注意:模型训练后一定要在独立的测试集上评估性能,避免使用训练数据评估导致的乐观估计

更多推荐