机器学习的核心,是让计算机从数据中寻找规律,并利用这些规律对未知数据进行预测或决策。本文将系统介绍决策树、随机森林、逻辑回归、支持向量机、朴素贝叶斯、K 近邻、K 均值、AdaBoost、神经网络和马尔可夫模型等经典方法。

前言:什么是机器学习

机器学习(Machine Learning,ML)是人工智能的一个重要分支。它不要求开发人员为每一种情况编写明确规则,而是通过历史数据训练模型,让模型自己学习输入与输出之间的关系。

传统编程通常可以表示为:

数据 + 人工规则 = 计算结果

机器学习则可以表示为:

历史数据 + 正确结果 = 模型
模型 + 新数据 = 预测结果

例如,在垃圾邮件识别系统中,我们不必手动编写“出现某个词就是垃圾邮件”的全部规则,而是可以准备大量已经标注为“正常邮件”或“垃圾邮件”的样本,让机器学习模型自动总结其中的规律。

机器学习的主要类型

类型数据特点主要任务常见算法
监督学习数据带有标签分类、回归决策树、随机森林、逻辑回归、SVM
无监督学习数据没有标签聚类、降维、异常检测K 均值、DBSCAN、PCA
半监督学习少量数据有标签利用未标注数据增强训练标签传播、伪标签
强化学习通过奖励和惩罚学习连续决策、策略优化Q-Learning、DQN、PPO

分类与回归

监督学习中最常见的两类任务是分类和回归。

  • 分类:预测离散类别,例如“是否违约”“图片中是猫还是狗”。
  • 回归:预测连续数值,例如“房屋价格”“未来销售额”。

如果模型要预测一个用户是否会流失,那么输出通常是:

流失 / 不流失

这是一个分类问题。

如果模型要预测用户未来一个月的消费金额,那么输出可能是:

128.50、560.00、1024.30

这是一个回归问题。

一个完整的机器学习流程

一个较为标准的机器学习项目通常包含以下步骤:

  1. 明确业务问题和预测目标。
  2. 收集并理解数据。
  3. 清洗缺失值、异常值和重复数据。
  4. 进行特征工程。
  5. 划分训练集、验证集和测试集。
  6. 选择算法并训练模型。
  7. 使用合适的指标评估模型。
  8. 调整超参数。
  9. 部署模型。
  10. 持续监控数据漂移和模型效果。

安装示例环境

本文的代码示例主要使用 Python 和 scikit-learn:

pip install numpy pandas matplotlib scikit-learn

1. 决策树

决策树(Decision Tree)是一种通过不断判断特征条件完成预测的机器学习算法。

它的工作过程类似于人做选择:

天气是否晴朗?
├── 是:温度是否超过 30℃?
│   ├── 是:不适合户外运动
│   └── 否:适合户外运动
└── 否:是否下雨?
    ├── 是:不适合户外运动
    └── 否:适合户外运动

决策树可以用于:

  • 分类任务,例如判断用户是否会流失;
  • 回归任务,例如预测房屋价格;
  • 多分类任务,例如识别商品类别。

1.1 决策树的基本结构

决策树主要由以下几部分组成:

  • 根节点:整棵树的起点,包含全部训练数据。
  • 内部节点:表示对某个特征进行判断。
  • 分支:表示判断条件的结果。
  • 叶子节点:输出最终预测结果。

训练决策树的关键问题是:

每次应该选择哪个特征、哪个切分点,才能让切分后的数据更加“纯净”?

分类树常用基尼指数或信息增益选择节点,回归树通常使用均方误差。

1.2 基尼指数

基尼指数用于衡量数据集合的不纯度:

Gini(D)=1−∑k=1Kpk2 Gini(D)=1-\sum_{k=1}^{K}p_k^2 Gini(D)=1k=1Kpk2

其中:

  • KKK 表示类别数量;
  • pkp_kpk 表示第 kkk 类样本所占比例。

基尼指数越小,说明数据越纯。

假设一个节点中有 10 个样本,其中:

  • 8 个属于正类;
  • 2 个属于负类。

那么:

Gini(D)=1−(0.82+0.22)=0.32 Gini(D)=1-(0.8^2+0.2^2)=0.32 Gini(D)=1(0.82+0.22)=0.32

如果一个节点中的样本全部属于同一类别,那么基尼指数为 0。

1.3 信息熵与信息增益

信息熵的计算公式为:

H(D)=−∑k=1Kpklog⁡2pk H(D)=-\sum_{k=1}^{K}p_k\log_2p_k H(D)=k=1Kpklog2pk

熵越大,表示数据越混乱;熵越小,表示数据越纯。

使用某个特征 AAA 划分数据后,信息增益为:

Gain(D,A)=H(D)−H(D∣A) Gain(D,A)=H(D)-H(D|A) Gain(D,A)=H(D)H(DA)

信息增益越大,说明该特征带来的不确定性下降越明显。

1.4 决策树的优缺点

优点:

  • 结果容易理解和解释;
  • 不要求特征具有线性关系;
  • 通常不需要进行特征标准化;
  • 能够处理数值特征和类别特征;
  • 可以表达复杂的非线性决策边界。

缺点:

  • 很容易过拟合;
  • 对训练数据的小幅变化比较敏感;
  • 树过深时泛化能力较差;
  • 贪心切分不一定能得到全局最优树。

1.5 防止决策树过拟合

常见方法包括:

  • 限制树的最大深度;
  • 限制叶子节点的最少样本数;
  • 限制内部节点继续分裂所需的最少样本数;
  • 限制叶子节点数量;
  • 使用代价复杂度剪枝。

常用参数:

参数含义
max_depth树的最大深度
min_samples_split节点继续分裂所需的最少样本数
min_samples_leaf叶子节点最少样本数
max_leaf_nodes最大叶子节点数量
ccp_alpha代价复杂度剪枝强度

1.6 Python 示例

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score, classification_report

# 加载鸢尾花数据集
data = load_iris()
X, y = data.data, data.target

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y
)

# 创建并训练决策树
model = DecisionTreeClassifier(
    criterion="gini",
    max_depth=4,
    min_samples_leaf=2,
    random_state=42
)
model.fit(X_train, y_train)

# 预测
y_pred = model.predict(X_test)

print("准确率:", accuracy_score(y_test, y_pred))
print(classification_report(y_test, y_pred))

1.7 适用场景

决策树适合以下场景:

  • 需要清晰解释预测过程;
  • 特征之间存在复杂非线性关系;
  • 数据中同时存在不同类型的特征;
  • 需要快速建立一个可解释的基线模型。

2. 随机森林算法

随机森林(Random Forest)是一种集成学习算法。它训练多棵存在差异的决策树,再综合所有树的结果得到最终预测。

分类任务通常使用多数投票:

y^=mode⁡{h1(x),h2(x),…,hT(x)} \hat{y}=\operatorname{mode}\{h_1(x),h_2(x),\ldots,h_T(x)\} y^=mode{h1(x),h2(x),,hT(x)}

回归任务通常计算平均值:

y^=1T∑t=1Tht(x) \hat{y}=\frac{1}{T}\sum_{t=1}^{T}h_t(x) y^=T1t=1Tht(x)

其中,TTT 表示决策树数量。

2.1 随机森林中的“随机”

随机森林主要引入了两种随机性。

样本随机

每棵树通过 Bootstrap 方法从原始训练集中有放回地抽取样本。

由于是有放回抽样:

  • 某些样本可能被抽到多次;
  • 某些样本可能没有被抽到;
  • 不同决策树看到的数据并不完全相同。
特征随机

每次节点分裂时,不检查全部特征,而是随机选择一部分特征,再从中寻找最佳切分点。

这会降低不同决策树之间的相关性,使多棵树的组合比单棵树更加稳定。

2.2 为什么随机森林有效

如果所有决策树都完全相同,那么无论训练多少棵树,最终结果都不会得到明显改善。

随机森林通过随机样本和随机特征制造差异,然后对多个弱相关模型进行组合,从而降低单棵决策树的方差和过拟合风险。

可以简单理解为:

多个能力不错、观点存在差异的模型共同投票,
通常比单个模型独立决策更加稳定。

2.3 OOB 袋外评估

Bootstrap 抽样后,每棵树大约会有一部分样本没有参与该树的训练,这些样本称为袋外样本(Out-of-Bag Samples)。

可以使用袋外样本估计模型泛化能力:

RandomForestClassifier(
    n_estimators=300,
    oob_score=True,
    random_state=42
)

训练完成后,可以读取:

print(model.oob_score_)

2.4 常用参数

参数含义
n_estimators决策树数量
max_depth每棵树的最大深度
max_features节点分裂时考虑的最大特征数
min_samples_leaf每个叶子节点的最少样本数
bootstrap是否使用有放回抽样
class_weight类别不平衡时的类别权重
n_jobs并行使用的 CPU 核心数

2.5 Python 示例

from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, roc_auc_score

data = load_breast_cancer()
X, y = data.data, data.target

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y
)

model = RandomForestClassifier(
    n_estimators=300,
    max_depth=8,
    min_samples_leaf=2,
    class_weight="balanced",
    oob_score=True,
    n_jobs=-1,
    random_state=42
)

model.fit(X_train, y_train)

y_pred = model.predict(X_test)
y_probability = model.predict_proba(X_test)[:, 1]

print("准确率:", accuracy_score(y_test, y_pred))
print("AUC:", roc_auc_score(y_test, y_probability))
print("OOB 得分:", model.oob_score_)

2.6 特征重要性

随机森林可以给出特征重要性:

import pandas as pd

importance = pd.Series(
    model.feature_importances_,
    index=data.feature_names
).sort_values(ascending=False)

print(importance.head(10))

不过需要注意,基于不纯度下降计算的特征重要性可能偏向取值范围较多或连续型特征。需要更可靠的解释时,可以结合排列重要性或 SHAP 等方法。

2.7 优缺点

优点:

  • 相比单棵决策树更不容易过拟合;
  • 能处理非线性关系;
  • 对异常值和噪声通常有一定容忍度;
  • 可以评估特征重要性;
  • 分类和回归任务都适用。

缺点:

  • 模型体积比单棵树大;
  • 预测速度可能较慢;
  • 可解释性低于单棵决策树;
  • 在高维稀疏数据上不一定优于线性模型。

3. 逻辑回归

逻辑回归(Logistic Regression)虽然名称中包含“回归”,但它主要用于分类任务。

逻辑回归首先计算特征的线性组合:

z=wTx+b z=w^Tx+b z=wTx+b

然后使用 Sigmoid 函数将结果映射到 0 到 1:

σ(z)=11+e−z \sigma(z)=\frac{1}{1+e^{-z}} σ(z)=1+ez1

输出可以解释为样本属于正类的概率:

P(y=1∣x)=σ(wTx+b) P(y=1|x)=\sigma(w^Tx+b) P(y=1∣x)=σ(wTx+b)

3.1 分类过程

假设模型输出:

P(y=1|x) = 0.82

如果分类阈值是 0.5,则模型预测为正类。

但阈值并不是必须固定为 0.5。例如,在疾病筛查中,为了减少漏诊,可以降低阈值,提高召回率。

3.2 对数几率

将正类概率记为 ppp,负类概率为 1−p1-p1p,则几率为:

p1−p \frac{p}{1-p} 1pp

对几率取对数:

log⁡p1−p=wTx+b \log\frac{p}{1-p}=w^Tx+b log1pp=wTx+b

因此,逻辑回归实际上假设特征与对数几率之间存在线性关系。

3.3 损失函数

逻辑回归通常使用二元交叉熵损失:

L=−1m∑i=1m[yilog⁡y^i+(1−yi)log⁡(1−y^i)] L=-\frac{1}{m}\sum_{i=1}^{m} \left[ y_i\log \hat{y}_i+ (1-y_i)\log(1-\hat{y}_i) \right] L=m1i=1m[yilogy^i+(1yi)log(1y^i)]

如果真实标签为 1,而模型预测概率接近 0,损失会非常大。

3.4 正则化

为了减少过拟合,可以在线性模型中加入正则化项。

L1 正则化:

λ∑j=1n∣wj∣ \lambda\sum_{j=1}^{n}|w_j| λj=1nwj

L1 正则化可能使部分权重变成 0,因此具有一定的特征选择效果。

L2 正则化:

λ∑j=1nwj2 \lambda\sum_{j=1}^{n}w_j^2 λj=1nwj2

L2 正则化会限制权重过大,使模型更加稳定。

在 scikit-learn 中,参数 C 表示正则化强度的倒数:

  • C 越小,正则化越强;
  • C 越大,正则化越弱。

3.5 Python 示例

逻辑回归对特征尺度比较敏感,通常应该先标准化:

from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

data = load_breast_cancer()
X, y = data.data, data.target

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y
)

model = Pipeline([
    ("scaler", StandardScaler()),
    ("classifier", LogisticRegression(
        C=1.0,
        max_iter=2000,
        class_weight="balanced",
        random_state=42
    ))
])

model.fit(X_train, y_train)

y_pred = model.predict(X_test)
y_probability = model.predict_proba(X_test)[:, 1]

print(classification_report(y_test, y_pred))
print("AUC:", roc_auc_score(y_test, y_probability))

3.6 优缺点

优点:

  • 训练和预测速度快;
  • 模型结构简单;
  • 可以输出类别概率;
  • 模型权重具有一定可解释性;
  • 适合作为二分类基线模型。

缺点:

  • 默认只能学习线性决策边界;
  • 对异常值比较敏感;
  • 特征之间存在复杂非线性关系时效果有限;
  • 特征高度相关时,模型系数可能不稳定。

3.7 适用场景

逻辑回归常用于:

  • 用户流失预测;
  • 信贷违约预测;
  • 广告点击率预测;
  • 医疗风险预测;
  • 垃圾邮件识别;
  • 需要输出概率并解释特征方向的任务。

4. SVM

支持向量机(Support Vector Machine,SVM)是一种经典监督学习算法,可以用于分类、回归和异常检测。

SVM 的核心思想是:

在不同类别之间寻找一条间隔最大的决策边界。

对于二维数据,决策边界是一条直线;对于三维数据,它是一个平面;在更高维空间中,它被称为超平面。

4.1 最大间隔

线性分类超平面可以写成:

wTx+b=0 w^Tx+b=0 wTx+b=0

样本点到超平面的距离为:

∣wTx+b∣∥w∥ \frac{|w^Tx+b|}{\|w\|} wwTx+b

SVM 希望距离决策边界最近的样本也尽可能远。因此,它会寻找最大间隔超平面。

真正决定边界位置的少量样本称为支持向量。

4.2 软间隔

真实数据通常无法被一条直线完全分开,因此 SVM 允许少量样本违反间隔约束。

软间隔 SVM 的优化目标可以写成:

min⁡w,b12∥w∥2+C∑i=1mξi \min_{w,b} \frac{1}{2}\|w\|^2+ C\sum_{i=1}^{m}\xi_i w,bmin21w2+Ci=1mξi

其中:

  • ξi\xi_iξi 表示样本违反间隔的程度;
  • CCC 控制间隔宽度与分类错误之间的权衡。

C 较大时:

  • 更重视训练样本是否分类正确;
  • 决策边界可能更复杂;
  • 过拟合风险可能增加。

C 较小时:

  • 允许更多训练误差;
  • 决策边界通常更加平滑;
  • 正则化效果更强。

4.3 核函数

当数据不能被线性边界分开时,可以通过核函数隐式地将数据映射到更高维空间。

常见核函数包括:

核函数说明
linear线性核,适合高维稀疏数据
poly多项式核
rbf高斯径向基核,最常用
sigmoidSigmoid 核

RBF 核函数为:

K(xi,xj)=exp⁡(−γ∥xi−xj∥2) K(x_i,x_j)=\exp(-\gamma\|x_i-x_j\|^2) K(xi,xj)=exp(γxixj2)

其中,γ\gammaγ 控制单个样本的影响范围。

  • γ\gammaγ 较大:影响范围较小,边界可能更复杂;
  • γ\gammaγ 较小:影响范围较大,边界通常更平滑。

4.4 Python 示例

from sklearn.datasets import load_breast_cancer
from sklearn.metrics import classification_report, roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC

data = load_breast_cancer()
X, y = data.data, data.target

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y
)

model = Pipeline([
    ("scaler", StandardScaler()),
    ("classifier", SVC(
        kernel="rbf",
        C=2.0,
        gamma="scale",
        probability=True,
        class_weight="balanced",
        random_state=42
    ))
])

model.fit(X_train, y_train)

y_pred = model.predict(X_test)
y_probability = model.predict_proba(X_test)[:, 1]

print(classification_report(y_test, y_pred))
print("AUC:", roc_auc_score(y_test, y_probability))

4.5 优缺点

优点:

  • 在中小规模数据集上通常表现较好;
  • 可以通过核函数学习复杂边界;
  • 对高维数据有效;
  • 决策结果主要由支持向量决定。

缺点:

  • 大数据集上的训练成本较高;
  • 对特征尺度敏感;
  • Cgamma 和核函数需要调优;
  • 非线性 SVM 的解释难度较大;
  • 概率输出通常需要额外校准。

5. 朴素贝叶斯

朴素贝叶斯(Naive Bayes)是一类基于贝叶斯定理的分类算法。

贝叶斯定理为:

P(y∣x)=P(x∣y)P(y)P(x) P(y|x)=\frac{P(x|y)P(y)}{P(x)} P(yx)=P(x)P(xy)P(y)

对于分类问题,我们希望找到:

y^=arg⁡max⁡yP(y∣x) \hat{y}=\arg\max_y P(y|x) y^=argymaxP(yx)

由于对于不同类别来说 P(x)P(x)P(x) 相同,因此可以写成:

y^=arg⁡max⁡yP(x∣y)P(y) \hat{y}=\arg\max_y P(x|y)P(y) y^=argymaxP(xy)P(y)

5.1 “朴素”是什么意思

朴素贝叶斯作出了一个很强的假设:

在给定类别的条件下,不同特征之间相互独立。

如果输入包含多个特征:

x=(x1,x2,…,xn) x=(x_1,x_2,\ldots,x_n) x=(x1,x2,,xn)

那么:

P(x∣y)=∏i=1nP(xi∣y) P(x|y)=\prod_{i=1}^{n}P(x_i|y) P(xy)=i=1nP(xiy)

于是分类规则变成:

y^=arg⁡max⁡yP(y)∏i=1nP(xi∣y) \hat{y}= \arg\max_y P(y)\prod_{i=1}^{n}P(x_i|y) y^=argymaxP(y)i=1nP(xiy)

现实中的特征通常并不完全独立,但朴素贝叶斯在文本分类等场景中仍然经常有效。

5.2 常见类型

高斯朴素贝叶斯

假设连续特征在每个类别下服从高斯分布,适合连续数值特征。

多项式朴素贝叶斯

适合词频、词袋等非负计数特征,常用于文本分类。

伯努利朴素贝叶斯

适合二值特征,例如某个词是否出现。

5.3 拉普拉斯平滑

如果训练集中某个类别从未出现某个词,那么对应概率可能变成 0,最终导致整个概率乘积为 0。

拉普拉斯平滑通过增加一个平滑项避免零概率:

P(xi∣y)=Niy+αNy+αn P(x_i|y)= \frac{N_{iy}+\alpha} {N_y+\alpha n} P(xiy)=Ny+αnNiy+α

其中,α\alphaα 通常取 1。

5.4 文本分类示例

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics import classification_report
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline

texts = [
    "免费领取优惠券点击链接",
    "今晚一起吃饭吗",
    "限时促销立即购买",
    "项目会议调整到下午",
    "恭喜中奖请填写账号",
    "请查收本周工作报告",
    "低价商品最后一天",
    "明天上午讨论需求"
]

# 1 表示营销或垃圾信息,0 表示正常信息
labels = [1, 0, 1, 0, 1, 0, 1, 0]

X_train, X_test, y_train, y_test = train_test_split(
    texts,
    labels,
    test_size=0.25,
    random_state=42,
    stratify=labels
)

model = Pipeline([
    ("tfidf", TfidfVectorizer(analyzer="char", ngram_range=(1, 2))),
    ("classifier", MultinomialNB(alpha=1.0))
])

model.fit(X_train, y_train)
y_pred = model.predict(X_test)

print(classification_report(y_test, y_pred, zero_division=0))

实际中文文本任务还需要考虑分词、停用词、拼写变体、样本规模和类别不平衡等问题。

5.5 优缺点

优点:

  • 训练和预测速度快;
  • 对小数据集比较友好;
  • 适合高维稀疏特征;
  • 在文本分类中经常具有良好效果;
  • 能够输出类别概率。

缺点:

  • 特征条件独立假设通常不完全成立;
  • 特征高度相关时效果可能下降;
  • 预测概率不一定经过良好校准;
  • 连续特征分布不符合假设时效果有限。

6. K 近邻算法

K 近邻算法(K-Nearest Neighbors,KNN)的核心思想是:

一个样本的类别通常与它附近样本的类别相似。

预测一个新样本时,KNN 会寻找距离它最近的 KKK 个训练样本,再根据这些邻居的结果进行预测。

分类任务通常使用投票,回归任务通常使用平均值或距离加权平均值。

6.1 距离计算

最常见的是欧氏距离:

d(x,z)=∑i=1n(xi−zi)2 d(x,z)= \sqrt{\sum_{i=1}^{n}(x_i-z_i)^2} d(x,z)=i=1n(xizi)2

曼哈顿距离为:

d(x,z)=∑i=1n∣xi−zi∣ d(x,z)= \sum_{i=1}^{n}|x_i-z_i| d(x,z)=i=1nxizi

更一般的闵可夫斯基距离为:

d(x,z)=(∑i=1n∣xi−zi∣p)1/p d(x,z)= \left( \sum_{i=1}^{n}|x_i-z_i|^p \right)^{1/p} d(x,z)=(i=1nxizip)1/p

p=1p=1p=1 时是曼哈顿距离,当 p=2p=2p=2 时是欧氏距离。

6.2 K 值的影响

如果 KKK 太小:

  • 模型容易受噪声影响;
  • 决策边界比较复杂;
  • 过拟合风险较高。

如果 KKK 太大:

  • 局部结构容易被忽略;
  • 少数类别可能被多数类别压制;
  • 欠拟合风险增加。

K 值通常通过交叉验证选择。

6.3 为什么需要标准化

假设数据包含两个特征:

年龄:18~60
年收入:30000~1000000

如果直接计算欧氏距离,收入特征会因为数值范围更大而主导距离。

因此,KNN 通常需要进行标准化:

z=x−μσ z=\frac{x-\mu}{\sigma} z=σxμ

6.4 Python 示例

from sklearn.datasets import load_iris
from sklearn.metrics import classification_report
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

data = load_iris()
X, y = data.data, data.target

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y
)

pipeline = Pipeline([
    ("scaler", StandardScaler()),
    ("classifier", KNeighborsClassifier())
])

parameters = {
    "classifier__n_neighbors": [3, 5, 7, 9, 11],
    "classifier__weights": ["uniform", "distance"],
    "classifier__p": [1, 2]
}

search = GridSearchCV(
    pipeline,
    parameters,
    cv=5,
    scoring="accuracy"
)

search.fit(X_train, y_train)

print("最佳参数:", search.best_params_)

y_pred = search.predict(X_test)
print(classification_report(y_test, y_pred))

6.5 优缺点

优点:

  • 原理简单;
  • 几乎不需要显式训练;
  • 能处理非线性分类边界;
  • 分类和回归都可以使用。

缺点:

  • 预测时需要计算新样本与训练样本的距离;
  • 数据量大时预测速度较慢;
  • 对特征尺度敏感;
  • 对无关特征和异常值敏感;
  • 在高维空间中容易受到“维度灾难”影响。

7. K 均值算法

K 均值(K-Means)是一种无监督聚类算法,用于将没有标签的数据划分为 KKK 个簇。

算法希望同一个簇内的样本尽可能相似,不同簇之间的样本尽可能不同。

7.1 优化目标

K-Means 的目标是最小化簇内平方和:

J=∑k=1K∑xi∈Ck∥xi−μk∥2 J= \sum_{k=1}^{K} \sum_{x_i\in C_k} \|x_i-\mu_k\|^2 J=k=1KxiCkxiμk2

其中:

  • CkC_kCk 表示第 kkk 个簇;
  • μk\mu_kμk 表示第 kkk 个簇的中心;
  • KKK 表示簇的数量。

7.2 算法步骤

K-Means 的基本过程如下:

  1. 随机选择 KKK 个初始聚类中心。
  2. 计算每个样本到所有聚类中心的距离。
  3. 将样本分配给距离最近的聚类中心。
  4. 重新计算每个簇的中心。
  5. 重复分配与更新步骤,直到结果收敛。

7.3 初始化问题

传统随机初始化可能产生较差的初始中心,导致:

  • 收敛到较差的局部最优解;
  • 不同运行得到不同结果;
  • 收敛速度较慢。

K-Means++ 会尽量让初始中心彼此远离,通常比完全随机初始化更稳定。scikit-learn 默认使用的就是 K-Means++。

7.4 如何选择 K

肘部法

计算不同 K 值对应的簇内平方和。

随着 K 增大,簇内平方和会下降。当下降速度明显变缓时,对应位置类似手肘,可以作为候选 K 值。

轮廓系数

单个样本的轮廓系数为:

s(i)=b(i)−a(i)max⁡{a(i),b(i)} s(i)=\frac{b(i)-a(i)} {\max\{a(i),b(i)\}} s(i)=max{a(i),b(i)}b(i)a(i)

其中:

  • a(i)a(i)a(i) 表示样本与同簇其他样本的平均距离;
  • b(i)b(i)b(i) 表示样本与最近其他簇样本的平均距离。

轮廓系数范围为 [−1,1][-1,1][1,1]

  • 接近 1:聚类结果较好;
  • 接近 0:样本可能位于簇边界;
  • 小于 0:样本可能被分到了错误的簇。

7.5 Python 示例

from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
from sklearn.metrics import silhouette_score
from sklearn.preprocessing import StandardScaler

# 生成没有标签参与训练的模拟数据
X, _ = make_blobs(
    n_samples=500,
    centers=4,
    cluster_std=1.2,
    random_state=42
)

X_scaled = StandardScaler().fit_transform(X)

for k in range(2, 7):
    model = KMeans(
        n_clusters=k,
        init="k-means++",
        n_init=20,
        random_state=42
    )

    labels = model.fit_predict(X_scaled)
    score = silhouette_score(X_scaled, labels)

    print(
        f"K={k}, "
        f"簇内平方和={model.inertia_:.2f}, "
        f"轮廓系数={score:.4f}"
    )

7.6 K-Means 的限制

K-Means 更适合:

  • 簇的形状接近球形;
  • 各簇大小和密度比较接近;
  • 使用欧氏距离能够合理表达相似度的数据。

它不擅长:

  • 弯月形、环形等非凸簇;
  • 不同簇密度差异很大的数据;
  • 包含大量离群点的数据;
  • 类别特征;
  • 无法用均值代表中心的数据。

7.7 应用场景

K-Means 常用于:

  • 用户分群;
  • 商品聚类;
  • 图像颜色压缩;
  • 文档聚类;
  • 数据预处理;
  • 异常分析的辅助步骤。

聚类编号本身没有业务含义。例如,簇 0 并不一定比簇 1 更重要,需要结合每个簇的特征进行业务解释。


8. AdaBoost 算法

AdaBoost 的全称是 Adaptive Boosting,即自适应提升算法。

它会按照顺序训练多个弱学习器,每一轮重点关注上一轮被错误分类的样本,最后将多个弱学习器组合成一个强学习器。

常用弱学习器是深度为 1 的决策树,也称为决策树桩。

8.1 基本思想

AdaBoost 的训练过程可以概括为:

  1. 为所有训练样本设置相同权重。
  2. 使用当前权重训练一个弱分类器。
  3. 计算弱分类器的错误率。
  4. 提高错误分类样本的权重。
  5. 降低正确分类样本的权重。
  6. 训练下一个弱分类器。
  7. 根据每个分类器的表现确定其组合权重。

最终分类器可以写成:

F(x)=sign⁡(∑t=1Tαtht(x)) F(x)=\operatorname{sign} \left( \sum_{t=1}^{T}\alpha_t h_t(x) \right) F(x)=sign(t=1Tαtht(x))

其中:

  • ht(x)h_t(x)ht(x) 表示第 ttt 个弱分类器;
  • αt\alpha_tαt 表示该分类器的组合权重;
  • TTT 表示弱分类器数量。

8.2 弱学习器权重

假设第 ttt 个弱学习器的加权错误率为 ϵt\epsilon_tϵt,它的权重可表示为:

αt=12ln⁡(1−ϵtϵt) \alpha_t= \frac{1}{2} \ln \left( \frac{1-\epsilon_t}{\epsilon_t} \right) αt=21ln(ϵt1ϵt)

错误率越低,弱学习器在最终模型中的权重越高。

随后,根据样本是否分类正确更新样本权重:

$$
w_i^{(t+1)}

w_i^{(t)}
\exp
\left(
-\alpha_t y_i h_t(x_i)
\right)
$$

被错误分类的样本会获得更高权重,下一轮模型会更加关注它们。

8.3 Python 示例

from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import AdaBoostClassifier
from sklearn.metrics import classification_report, roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier

data = load_breast_cancer()
X, y = data.data, data.target

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y
)

weak_learner = DecisionTreeClassifier(
    max_depth=1,
    random_state=42
)

model = AdaBoostClassifier(
    estimator=weak_learner,
    n_estimators=200,
    learning_rate=0.05,
    random_state=42
)

model.fit(X_train, y_train)

y_pred = model.predict(X_test)
y_probability = model.predict_proba(X_test)[:, 1]

print(classification_report(y_test, y_pred))
print("AUC:", roc_auc_score(y_test, y_probability))

8.4 主要参数

参数含义
estimator使用的弱学习器
n_estimators弱学习器数量
learning_rate每个弱学习器的贡献缩放比例
random_state随机种子

n_estimatorslearning_rate 通常需要共同调节:

  • 学习率小,可能需要更多弱学习器;
  • 学习率大,拟合速度更快,但可能对噪声更加敏感。

8.5 优缺点

优点:

  • 可以将多个简单模型组合成较强模型;
  • 原理清晰;
  • 通常不容易像单棵深树那样严重过拟合;
  • 可以用于分类和回归。

缺点:

  • 对错误标签和异常值比较敏感;
  • 串行训练,不像随机森林那样容易完全并行;
  • 样本权重不断聚焦错误样本,噪声可能被持续放大;
  • 在复杂表格数据上,通常还需要与梯度提升树进行比较。

9. 神经网络

神经网络(Neural Network)是一类受生物神经系统启发的计算模型。

一个基础前馈神经网络通常包含:

  • 输入层;
  • 一个或多个隐藏层;
  • 输出层。

每一层由多个神经元组成,神经元接收上一层输出,经过加权求和和非线性激活后,将结果传递给下一层。

9.1 单个神经元

单个神经元首先执行线性计算:

z=∑i=1nwixi+b z=\sum_{i=1}^{n}w_ix_i+b z=i=1nwixi+b

然后通过激活函数:

a=f(z) a=f(z) a=f(z)

如果没有非线性激活函数,无论叠加多少层,整个网络本质上仍然只是线性变换。

9.2 常见激活函数

Sigmoid

σ(z)=11+e−z \sigma(z)=\frac{1}{1+e^{-z}} σ(z)=1+ez1

输出范围为 (0,1)(0,1)(0,1),常用于二分类模型的输出层。

缺点是输入绝对值较大时容易出现梯度接近 0 的问题。

Tanh

tanh⁡(z)=ez−e−zez+e−z \tanh(z)= \frac{e^z-e^{-z}} {e^z+e^{-z}} tanh(z)=ez+ezezez

输出范围为 (−1,1)(-1,1)(1,1),以 0 为中心,但仍可能出现梯度消失。

ReLU

ReLU(z)=max⁡(0,z) ReLU(z)=\max(0,z) ReLU(z)=max(0,z)

ReLU 计算简单,是隐藏层中最常见的激活函数之一。

Softmax

多分类任务中,Softmax 可以将多个输出转换为概率:

P(y=k∣x)=ezk∑j=1Kezj P(y=k|x)= \frac{e^{z_k}} {\sum_{j=1}^{K}e^{z_j}} P(y=kx)=j=1Kezjezk

所有类别概率之和为 1。

9.3 前向传播

假设网络有一层隐藏层:

z(1)=W(1)x+b(1) z^{(1)}=W^{(1)}x+b^{(1)} z(1)=W(1)x+b(1)

a(1)=f(z(1)) a^{(1)}=f(z^{(1)}) a(1)=f(z(1))

z(2)=W(2)a(1)+b(2) z^{(2)}=W^{(2)}a^{(1)}+b^{(2)} z(2)=W(2)a(1)+b(2)

y^=g(z(2)) \hat{y}=g(z^{(2)}) y^=g(z(2))

从输入层逐层计算到输出层的过程称为前向传播。

9.4 反向传播

得到预测结果后,模型会计算损失函数:

L(y,y^) L(y,\hat{y}) L(y,y^)

然后利用链式法则,从输出层向输入层计算每个参数对损失的梯度:

∂L∂W \frac{\partial L}{\partial W} WL

最后通过梯度下降更新参数:

W←W−η∂L∂W W \leftarrow W-\eta\frac{\partial L}{\partial W} WWηWL

其中,η\etaη 是学习率。

9.5 常见优化器

优化器特点
SGD结构简单,可能需要仔细调整学习率
Momentum使用历史梯度方向加速收敛
RMSProp根据梯度平方调整学习率
Adam结合动量和自适应学习率,使用广泛

9.6 防止过拟合

神经网络参数很多,容易过拟合。常见方法包括:

  • 增加训练数据;
  • 数据增强;
  • L1 或 L2 正则化;
  • Dropout;
  • 早停;
  • 批归一化;
  • 降低网络复杂度;
  • 使用交叉验证或独立验证集。

9.7 Python 示例

下面使用 scikit-learn 的多层感知机完成分类:

from sklearn.datasets import load_breast_cancer
from sklearn.metrics import classification_report, roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.neural_network import MLPClassifier
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

data = load_breast_cancer()
X, y = data.data, data.target

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y
)

model = Pipeline([
    ("scaler", StandardScaler()),
    ("classifier", MLPClassifier(
        hidden_layer_sizes=(64, 32),
        activation="relu",
        solver="adam",
        alpha=0.001,
        learning_rate_init=0.001,
        max_iter=1000,
        early_stopping=True,
        validation_fraction=0.15,
        random_state=42
    ))
])

model.fit(X_train, y_train)

y_pred = model.predict(X_test)
y_probability = model.predict_proba(X_test)[:, 1]

print(classification_report(y_test, y_pred))
print("AUC:", roc_auc_score(y_test, y_probability))

9.8 常见神经网络类型

多层感知机

适合一般分类和回归任务,是最基础的全连接神经网络。

卷积神经网络

卷积神经网络(CNN)擅长处理具有空间结构的数据,常用于:

  • 图像分类;
  • 目标检测;
  • 图像分割;
  • 医学影像分析。
循环神经网络

循环神经网络(RNN)用于处理序列数据,例如文本和时间序列。

LSTM 和 GRU 通过门控机制缓解普通 RNN 的长期依赖问题。

Transformer

Transformer 通过注意力机制建模序列中不同位置之间的关系,已广泛应用于:

  • 自然语言处理;
  • 大语言模型;
  • 计算机视觉;
    -语音识别;
  • 多模态模型。

9.9 优缺点

优点:

  • 能够学习复杂的非线性关系;
  • 可以自动提取高级特征;
  • 适合图像、语音和文本等非结构化数据;
  • 模型规模和数据规模增加后可能获得更强能力。

缺点:

  • 通常需要较多训练数据;
  • 训练成本较高;
  • 超参数较多;
  • 可解释性较弱;
  • 对数据质量、初始化和训练过程比较敏感。

10. 马尔可夫模型

“马尔可夫”并不是一个单独的分类算法,而是一类用于描述随机状态转移过程的数学模型。

马尔可夫模型的核心假设是:

系统下一时刻的状态只依赖当前状态,不依赖更早的完整历史。

这种性质称为马尔可夫性。

如果系统状态序列为:

X0,X1,X2,…,Xt X_0,X_1,X_2,\ldots,X_t X0,X1,X2,,Xt

一阶马尔可夫性质可以表示为:

$$
P(X_{t+1}|X_t,X_{t-1},\ldots,X_0)

P(X_{t+1}|X_t)
$$

这并不是说历史完全没有作用,而是说历史信息已经被当前状态充分概括。

10.1 马尔可夫链

马尔可夫链(Markov Chain)用于描述状态按照一定概率发生转移的过程。

假设天气只有三个状态:

晴天、阴天、雨天

状态转移概率可以表示为矩阵:

P=[0.70.20.10.30.40.30.20.30.5] P= \begin{bmatrix} 0.7 & 0.2 & 0.1 \\ 0.3 & 0.4 & 0.3 \\ 0.2 & 0.3 & 0.5 \end{bmatrix} P=0.70.30.20.20.40.30.10.30.5

如果行和列的顺序都是“晴天、阴天、雨天”,那么第一行表示:

今天是晴天时:
明天是晴天的概率为 0.7;
明天是阴天的概率为 0.2;
明天是雨天的概率为 0.1。

每一行概率之和必须为 1。

10.2 状态概率预测

假设今天的状态分布为:

π0=[100] \pi_0= \begin{bmatrix} 1 & 0 & 0 \end{bmatrix} π0=[100]

表示今天确定是晴天。

明天的状态分布为:

π1=π0P \pi_1=\pi_0P π1=π0P

经过 nnn 步后的状态分布为:

πn=π0Pn \pi_n=\pi_0P^n πn=π0Pn

10.3 Python 示例

import numpy as np

states = ["晴天", "阴天", "雨天"]

transition_matrix = np.array([
    [0.7, 0.2, 0.1],
    [0.3, 0.4, 0.3],
    [0.2, 0.3, 0.5]
])

# 今天确定为晴天
current_distribution = np.array([1.0, 0.0, 0.0])

for day in range(1, 6):
    current_distribution = current_distribution @ transition_matrix

    result = {
        state: round(probability, 4)
        for state, probability in zip(
            states,
            current_distribution
        )
    }

    print(f"第 {day} 天:{result}")

10.4 平稳分布

经过大量状态转移后,部分马尔可夫链会逐渐趋于稳定。

如果一个概率分布 π\piπ 满足:

π=πP \pi=\pi P π=πP

那么 π\piπ 被称为平稳分布。

可以通过不断迭代近似求解:

import numpy as np

transition_matrix = np.array([
    [0.7, 0.2, 0.1],
    [0.3, 0.4, 0.3],
    [0.2, 0.3, 0.5]
])

distribution = np.array([1 / 3, 1 / 3, 1 / 3])

for _ in range(1000):
    distribution = distribution @ transition_matrix

print("平稳分布:", distribution)
print("分布之和:", distribution.sum())

并非所有转移矩阵都会以同样方式收敛。是否存在唯一平稳分布以及能否从任意初始状态收敛,需要结合马尔可夫链的不可约性、非周期性等性质判断。

10.5 隐马尔可夫模型

隐马尔可夫模型(Hidden Markov Model,HMM)进一步假设:

  • 系统存在无法直接观察的隐藏状态;
  • 每个隐藏状态会按照一定概率产生可观察结果;
  • 隐藏状态之间满足马尔可夫性质。

例如,在语音识别中:

  • 隐藏状态可以是音素;
  • 观察结果可以是声学特征。

HMM 通常包含以下参数:

  • 初始状态概率;
  • 状态转移概率;
  • 观测概率。

HMM 中的经典问题包括:

  • 评估问题:计算某个观测序列出现的概率;
  • 解码问题:推断最可能的隐藏状态序列;
  • 学习问题:从数据中估计模型参数。

对应的经典算法包括:

问题常用算法
序列概率计算前向算法
最可能状态序列Viterbi 算法
参数学习Baum-Welch 算法

10.6 马尔可夫决策过程

马尔可夫决策过程(Markov Decision Process,MDP)在状态转移的基础上加入动作和奖励,是强化学习的重要数学基础。

一个 MDP 通常表示为:

(S,A,P,R,γ) (S,A,P,R,\gamma) (S,A,P,R,γ)

其中:

  • SSS:状态集合;
  • AAA:动作集合;
  • PPP:状态转移概率;
  • RRR:奖励函数;
  • γ\gammaγ:折扣因子。

智能体在状态 sss 下执行动作 aaa,环境转移到新状态并给出奖励。智能体的目标是学习一个策略,使长期累计奖励最大。

10.7 应用场景

马尔可夫相关模型常用于:

  • 天气状态预测;
  • 用户行为路径分析;
  • 搜索排序;
  • 语音识别;
  • 词性标注;
  • 金融状态建模;
  • 设备状态监控;
  • 强化学习;
  • 推荐系统中的序列行为分析。

10.8 优缺点

优点:

  • 适合描述序列和状态转移;
  • 具有清晰的概率解释;
  • 可以处理具有时间依赖关系的数据;
  • 是 HMM、MDP 和强化学习的重要基础。

缺点:

  • 一阶马尔可夫假设可能过于简单;
  • 状态空间较大时计算成本可能迅速增加;
  • 转移概率需要足够数据才能可靠估计;
  • 状态定义不合理时,模型很难表达真实过程。

算法对比与选型

算法学习类型主要任务是否通常需要标准化可解释性主要特点
决策树监督学习分类、回归规则清晰,容易过拟合
随机森林监督学习分类、回归稳定、通用、抗过拟合
逻辑回归监督学习分类速度快,可输出概率
SVM监督学习分类、回归中低适合中小型高维数据
朴素贝叶斯监督学习分类视模型而定适合文本和稀疏数据
KNN监督学习分类、回归原理简单,预测成本较高
K-Means无监督学习聚类快速划分相似样本
AdaBoost监督学习分类、回归通常不需要持续关注错误样本
神经网络监督或无监督学习分类、回归、生成通常需要表达能力强,训练成本高
马尔可夫模型概率序列模型状态转移、序列建模不适用中高描述时间状态转移

模型评估方法

选择算法之后,还需要使用正确的指标评估模型。

分类指标

准确率

Accuracy=TP+TNTP+TN+FP+FN Accuracy= \frac{TP+TN} {TP+TN+FP+FN} Accuracy=TP+TN+FP+FNTP+TN

适合类别相对均衡的场景。

精确率

Precision=TPTP+FP Precision= \frac{TP}{TP+FP} Precision=TP+FPTP

关注“被预测为正类的样本中,有多少是真的正类”。

召回率

Recall=TPTP+FN Recall= \frac{TP}{TP+FN} Recall=TP+FNTP

关注“所有真实正类中,有多少被成功识别”。

F1 分数

F1=2×Precision×RecallPrecision+Recall F1= 2\times \frac{Precision\times Recall} {Precision+Recall} F1=2×Precision+RecallPrecision×Recall

用于综合衡量精确率和召回率。

ROC-AUC

ROC-AUC 衡量模型在不同分类阈值下区分正负样本的能力,适合评价排序效果。但在正类极少的场景中,也应该关注 PR-AUC、精确率和召回率。

回归指标

平均绝对误差

MAE=1n∑i=1n∣yi−y^i∣ MAE= \frac{1}{n} \sum_{i=1}^{n}|y_i-\hat{y}_i| MAE=n1i=1nyiy^i

均方误差

MSE=1n∑i=1n(yi−y^i)2 MSE= \frac{1}{n} \sum_{i=1}^{n}(y_i-\hat{y}_i)^2 MSE=n1i=1n(yiy^i)2

决定系数

R2=1−∑i=1n(yi−y^i)2∑i=1n(yi−yˉ)2 R^2= 1- \frac{ \sum_{i=1}^{n}(y_i-\hat{y}_i)^2 }{ \sum_{i=1}^{n}(y_i-\bar{y})^2 } R2=1i=1n(yiyˉ)2i=1n(yiy^i)2

机器学习中的常见问题

过拟合

模型在训练集上表现很好,但在测试集或真实数据上表现较差。

常见解决方法:

  • 增加训练数据;
  • 降低模型复杂度;
  • 使用正则化;
  • 进行剪枝;
  • 使用交叉验证;
  • 使用早停;
  • 删除噪声特征;
  • 防止数据泄漏。

欠拟合

模型过于简单,无法学习数据中的主要规律,导致训练集和测试集表现都不好。

常见解决方法:

  • 使用更复杂的模型;
  • 增加有效特征;
  • 减弱正则化;
  • 延长训练时间;
  • 调整模型结构。

数据泄漏

如果训练过程中使用了预测时不可能获得的信息,模型评估结果就会虚高。

常见的数据泄漏包括:

  • 在划分训练集前对全部数据计算标准化参数;
  • 将目标变量的衍生字段作为输入特征;
  • 时间序列任务中使用未来数据训练过去;
  • 同一个用户的高度相似样本同时出现在训练集和测试集;
  • 根据整个数据集填充缺失值后再划分数据。

使用 Pipeline 可以降低部分预处理泄漏风险。

类别不平衡

在欺诈检测、故障检测和疾病筛查中,正类样本可能非常少。

可使用以下方法:

  • 设置类别权重;
  • 对少数类过采样;
  • 对多数类欠采样;
  • 调整分类阈值;
  • 使用分层抽样;
  • 关注召回率、F1 和 PR-AUC;
  • 根据误判成本选择指标。

总结

不同机器学习算法各有自己的假设和适用边界:

  • 决策树适合需要规则解释的非线性任务;
  • 随机森林是稳定、通用的表格数据基线模型;
  • 逻辑回归适合线性分类和概率预测;
  • SVM 适合中小规模的高维数据;
  • 朴素贝叶斯适合文本和高维稀疏特征;
  • KNN 适合规模较小、距离有明确意义的数据;
  • K-Means 用于发现没有标签的数据分组;
  • AdaBoost 通过持续关注错误样本提升弱学习器;
  • 神经网络适合复杂非线性关系和非结构化数据;
  • 马尔可夫模型适合描述状态随时间发生转移的过程。

实际项目中不存在永远最好的算法。更合理的方法是先建立简单基线模型,再结合数据规模、特征类型、业务目标、可解释性要求和计算资源进行比较。

机器学习项目最终能否成功,不仅取决于算法,还取决于:

业务目标是否明确
+ 数据是否可靠
+ 特征是否有效
+ 评估方式是否正确
+ 部署监控是否完善

算法只是机器学习系统的一部分,高质量数据和正确的问题定义往往更加重要。

更多推荐