AI人工智能

讲到AI人工智能首先得从图灵测试开始说起:

图灵测试就是:测试者与被测试者(一个人和一台机器)隔开的情况下,遍过一些装置(如键盘)向被测试者随意提问。
多次测试(一般为5min之内),如果有超过30%的测试者不能确定被测试者是人还是机器,那么这台机器就通过了测试,并被认为具有人类智能。

当有人骂你是人机的时候,你不要骂过去,你要说你还没通过图灵测试(这样别人就听不懂了doge)

人工智能的分类

通讯、感知与行动是现代人工智能的三个关键能力

与此对应的三个技术领域分别是

  • 计算机视觉(CV)
    • 计算机视觉(CV)是指机器感知环境的能力。这一技术类别中的经典任务有图像形成、图像处理、图像提取和图像的三维推理。物体检测和人脸识别是其比较成功的研究领域。
  • 自然语言处理(NLP):在NLP领域中,将覆盖文本挖掘/分类、机器翻译和语音识别
    • 语音识别:是指识别语音(说出的语言)并将其转换成对应文本的技术。相反的任务(文本转语音/TTS)也是这一领域内一个类似的研究主题。
    • 文本挖掘:主要是指文本分类,该技术可用于理解、组织和分类结构化或非结构化文本文档。其涵盖的主要任务有句法分析、情绪分析和垃圾信息检测。
    • 机器翻译(MT):是利用机器的力量自动将一种自然语言(源语言)的文本翻译成另一种语言(目标语言)。
  • 机器人
    • 机器人学(Robotics)研究的是机器人的设计、制造、运作和应用,以及控制它们的计算机系统、传感反馈和信息处理。
    • 机器人可以分成两大类:固定机器人和移动机器人。
      • 固定机器人通常被用于工业生产(比如用于装配线)。
      • 常见的移动机器人应用有货运机器人、空中机器人和自动载具。机器人需要不同部件和系统的协作才能实现最优的作业。其中在硬件上包含传感器、反应器和控制器;另外还有能够实现感知能力的软件,比如定位、地图测绘和目标识别。

机器学习基础

机器学习的概念

机器学习是从数据中自动分析获得模型,并利用模型对未知数据进行预测

如下图所示:

image-20250605141520596

机器学习工作流程⭐️

  1. 获取数据

    • 区分样本和特征
    • 根据数据有无特征值和目标值区分第四步机器学习选择的算法:
      • 监督学习、无监督学习、强化学习、半监督学习算法
    学习类型 In Out 目的 案例
    监督学习(Supervised Learning) 有标签 有反馈 预测结果 猫狗分类、房价预测
    无监督学习(Unsupervised Learning) 无标签 无反馈 发现潜在结构 “物以类聚,人以群分”
    半监督学习(Semi-Supervised Learning) 部分有标签,部分无标签 有反馈 降低数据标记难度
    强化学习(Reinforcement Learning) 决策流程及激励系统 一系列行动 长期利益最大化 学下棋
  2. 数据基本处理

  3. 特征工程

    • 特征提取
    • 特征预处理
    • 特征降维
  4. 机器学习(模型训练)

    • 选择合适的算法对模型进行训练
  5. 模型评估

    • 结果达到要求,上线服务
    • 没有达到要求,重新上面步骤

获取数据

image-20250605142251204

对于数据集:

  • 一行数据我们称为一个样本
  • 一列数据我们成为一个特征
  • 有些数据有目标值(标签值),有些数据没有目标值(如上表中,电影类型就是这个数据集的目标值)

数据分割:

  • 数据类型一:特征值+目标值(目标值是连续的和离散的)
  • 数据类型二:只有特征值,没有目标值

针对上述的数据可以分类为有监督学习无监督学习,可以看看之前的Note

09

监督学习典型模型:Linear regression、Logistic regression、SVM、Neural Network等

监督学习:监督学习指的是人们给机器标记好的数据,有特征值和目标值,比如:一大堆照片,人工先标记出哪些是猫的照片,哪些是狗的照片;训练模型;让模型判断其余照片是什么动物

监督学习一般有两个问题(分类问题、回归问题)

  • 分类问题:

img

​ 垃圾邮件识别就是一个分类问题,使用相应的机器学习算法判定邮件属于垃圾邮件还是非垃圾邮件。

  • 回归问题:
    • 数据中会给出大量的自变量和相应的连续因变量(对应输出结果),通过尝试寻找自变量和因变量的关系,就能够预测输出变量。
    • 如房价的预测,根据房屋的面积以及房价的结果来进行后续的预测
    • 还有股票基金的涨跌预测

无监督学习:非监督学习(unsupervised learning)指的是人们给机器一大堆没有分类标记的数据,让机器可以对数据分类、检测异常等。相应的无监督学习只有一个聚类问题

  • 聚类问题:聚类是一种探索性数据分析技术,在没有任何相关先验信息的情况下(相当于不清楚数据的信息),它可以帮助我们将数据划分为有意义的小的组别(也叫簇cluster)。其中每个簇内部成员之间有一定的相似度,簇之间有较大的不同。这也正是聚类作为无监督学习的原因。

img

  • 聚类的应用场景

    • 在对业务不是很熟悉的情况下, 使用聚类可以帮助打开思路
    • 使用聚类算法做聚类分析 是分析过程的开头, 得到聚类结果之后, 再详细的分析每个类别各自的特点

强化学习:实质是make decisions 问题,即自动进行决策,并且可以做连续决策。

  • 举例:小孩想要走路,但在这之前,他需要先站起来,站起来之后还要保持平衡,接下来还要先迈出一条腿,是左腿还是右腿,迈出一步后还要迈出下一步。

  • 小孩就是 agent,他试图通过采取行动(即行走)来操纵环境(行走的表面),并且从一个状态转变到另一个状态(即他走的每一步),当他

  • 完成任务的子任务(即走了几步)时,孩子得到奖励(给巧克力吃),并且当他不能走路时,就不会给巧克力。

半监督学习:训练集同时包含有标记样本数据和未标记样本数据。

机器学习一般的数据集会划分为两个部分:

  • 训练数据:用于训练,构建模型
  • 测试数据:在模型检验时使用,用于评估模型是否有效

划分比例:

  • 训练集:70% 80% 75%
  • 测试集:30% 20% 25%

数据预处理

即对数据进行缺失值、去除异常值等处理

特征工程

  • 特征提取:将任意数据(如文本或图像)转换为可用于机器学习的数字特征

    • 例如将语言、图像等自然语言转为计算机可以识别的字符(base64编码or二进制编码
  • 特征预处理:通过一些转换函数将特征数据转换成更加适合算法模型的特征数据过程;特征的单位或者大小相差较大,或者某特征的方差相比其他的特征要大出几个数量级容易影响(支配)目标结果,使得一些模型(算法)无法学习到其它的特征。

归一化:

最值归一化(Min-Max Scaling):将数据线性映射到[0, 1]区间
$$
X_{\text{norm}} = \frac{X - X_{\text{min}}}{X_{\text{max}} - X_{\text{min}}}
$$

确定归一化区间(mx,mi),如果不指定默认(0,1)
$$
X’’ = X’ \times (mx - mi) + mi
$$

归一化可以提升模型收敛速度

API:sklearn.preprocessing.MinMaxScaler

image-20250614174901792

均值方差归一化(Z-Score标准化):数据分布转为均值为0、标准差为1的正态分布。
$$
X_{\text{std}} = \frac{X - \mu}{\sigma}
$$

$μ$为均值,$σ$为标准差

API:sklearn.preprocessing.StandardScaler()

维度 均值方差归一化(标准化) Min-Max归一化
目标 数据服从标准正态分布(均值为0,标准差为1) 数据缩放到固定区间(如[0,1])
公式 $X=\frac{X−μ}{σ}$ $X = \frac{X-X_{min}}{X_{max}-X_{min}}$
异常值影响 低(依赖整体统计量) 高(依赖极值,异常值会压缩正常数据范围)
输出范围 (−∞,+∞) [0,1] 或 [−1,1]
适用场景 数据分布近似正态、含噪声或异常值 边界明确、分布均匀的数据
  • 特征降维(特征缩放):指在某些限定条件下,降低随机变量(特征)个数,得到一组“不相关”主变量的过程

image-20250605160141881

机器学习(模型训练)

  • 选择合适的算法对模型进行训练

模型评估

分为回归模型评估以及分类模型评估。

其中分类模型评估以混淆矩阵为基础,而回归模型评估中的均方误差比较重要。

  • 分类模型评估

    1. 混淆矩阵(Confusion Matrix)

    基础工具,用于计算后续指标。二分类混淆矩阵结构如下:

    预测为正例 预测为负例
    实际为正例 TP(真正例) FN(假反例)
    实际为负例 FP(假正例) TN(真反例)
    • TP:实际为正,预测为正
    • FN:实际为正,预测为负(漏报)
    • FP:实际为负,预测为正(误报)
    • TN:实际为负,预测为负
    1. 准确率(Accuracy)

    预测正确的样本占总样本比例:

    $$
    \text{Accuracy} = \frac{TP + TN}{TP + TN + FP + FN}
    $$

    • 适用场景:类别平衡的数据(如正负样本比例接近)
    1. 精确率(Precision)

    预测为正的样本中实际为正的比例:

    $$
    \text{Precision} = \frac{TP}{TP + FP}
    $$

    • 侧重:减少误报(如垃圾邮件检测中避免将正常邮件判为垃圾)
    1. 召回率(Recall)

    实际为正的样本中被正确预测的比例:

    $$
    \text{Recall} = \frac{TP}{TP + FN}
    $$

    • 侧重:减少漏报(如疾病诊断中避免漏掉患者)
    1. F1-Score

    精确率与召回率的调和平均,平衡二者:

    $$
    F1 = 2 \times \frac{\text{Precision} \times \text{Recall}}{\text{Precision} + \text{Recall}}
    $$

    • 适用场景:类别不平衡或需权衡误报/漏报代价时
    1. ROC曲线与AUC值

      • ROC曲线:以假阳率(FPR)为横轴、真阳率(TPR)为纵轴绘制,反映不同阈值下的性能。

        $$
        \text{TPR} = \frac{TP}{TP + FN}, \quad \text{FPR} = \frac{FP}{FP + TN}
        $$

      • AUC:ROC曲线下面积,衡量模型区分正负样本的能力:

        • AUC=0.5:随机猜测
        • AUC>0.8:模型效果较好
        • AUC=1:完美分类器
  • 回归模型评估

    • 回归模型评估关注预测值与真实值的偏差,核心指标如下:

      1. 平均绝对误差(MAE)

      预测值与真实值绝对误差的平均值:

      $$
      \text{MAE} = \frac{1}{n} \sum_{i=1}^{n} |y_i - \hat{y}_i|
      $$

      • 特点:对异常值不敏感,单位与原始数据一致(如房价预测误差单位为”万元”)

      2. 均方误差(MSE)

      预测值与真实值误差平方的平均值:

      $$
      \text{MSE} = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2
      $$

      • 特点:放大异常值影响(平方效应),常用于梯度下降优化

      3. 均方根误差(RMSE)

      MSE的平方根,恢复量纲一致性:

      $$
      \text{RMSE} = \sqrt{\text{MSE}}
      $$

      • 解释:RMSE=10表示平均预测误差约为10个单位(如温度预测误差±10℃)

      4. 可决系数(R²)

      模型解释的目标变量方差比例:

      image-20250627160407264

      • 范围:[0, 1],越接近1说明模型拟合越好
      • 意义:若R²=0.8,表示模型能解释80%的数据变异
  • 拟合

    • 过拟合:机器已经基本能区别天鹅和其他动物了。但很不巧已有的天鹅图片全是白天鹅的,于是机器判断黑的天鹅不是天鹅。
    • 欠拟合:模型学习的太过粗糙,连训练集中的样本数据特征关系都没有学出来。比如判断鹦鹉、鸭子也是天鹅。

Sklearn与特征工程⭐️

scikit-learn(简称 sklearn)基于 NumPy、SciPy 和 Matplotlib 构建,提供了简单高效的算法工具,适合从数据预处理到模型训练的全流程。

sklearn 的核心组成

sklearn的核心部分主要有

  • 数据预处理(Preprocessing)
    • StandardScaler / MinMaxScaler:数据标准化/归一化。
    • OneHotEncoder:分类变量独热编码。
    • train_test_split:划分训练集和测试集。
    • SimpleImputer:处理缺失值。
  • 监督学习算法(Supervised Learning)
    • 分类(Classification):
      • LogisticRegression(逻辑回归)
      • SVM(支持向量机)
      • RandomForestClassifier(随机森林)
      • KNeighborsClassifier(K近邻)
    • 回归(Regression):
      • LinearRegression(线性回归)
      • DecisionTreeRegressor(决策树回归)
      • GradientBoostingRegressor(梯度提升回归)
  • 无监督学习算法(Unsupervised Learning)
    • 聚类(Clustering):
      • KMeans(K均值聚类)
      • DBSCAN(基于密度的聚类)
    • 降维(Dimensionality Reduction):
      • PCA(主成分分析)
      • t-SNE(数据可视化降维)
  • 模型评估与选择(Model Evaluation)
    • 指标计算:
      • accuracy_score(分类准确率)
      • mean_squared_error(回归均方误差)
      • confusion_matrix(混淆矩阵)
    • 交叉验证:
      • cross_val_score(K折交叉验证)
  • 特征工程(Feature Engineering)
    • SelectKBest:基于统计检验选择特征。
    • RFE(递归特征消除):自动筛选重要特征。
  • 流水线(Pipeline)
1
2
3
4
5
from sklearn.pipeline import Pipeline
pipeline = Pipeline([
('scaler', StandardScaler()),
('classifier', RandomForestClassifier())
])
特点 适用场景
sklearn 传统机器学习算法(非深度学习) 中小规模结构化数据
TensorFlow/PyTorch 深度学习框架 图像、文本等复杂数据
XGBoost 高性能梯度提升树 表格数据竞赛/高精度需求

Iris 鸢尾花数据集内包含 3 种类别,分别为

image-20250614210851372

  • 山鸢尾(Iris-setosa)
  • 变色鸢尾(Iris-versicolor)
  • 维吉尼亚鸢尾(Iris-virginica)image-20250606102258377

sklearn随机森林-鸢尾花案例的典型代码流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score

# 1. 加载数据
data = load_iris()
X, y = data.data, data.target

# 2. 划分数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# 3. 训练模型
model = RandomForestClassifier()
model.fit(X_train, y_train)

# 4. 预测与评估
y_pred = model.predict(X_test)
print("准确率:", accuracy_score(y_test, y_pred))

sklear典型流程(带注释详解)

1
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
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score

# 1, 加载数据
data = load_iris()
x, y = data.data, data.target

# 查看数据结构
print("特征矩阵 x 的形状:", x.shape) # 输出: (150, 4) → 150个样本,每个样本4个特征
print("标签数组 y 的形状:", y.shape) # 输出: (150,) → 150个标签
print("前5个样本的特征:\n", x[:5])
# [[5.1 3.5 1.4 0.2]
# [4.9 3. 1.4 0.2]
# [4.7 3.2 1.3 0.2]
# [4.6 3.1 1.5 0.2]
# [5. 3.6 1.4 0.2]]
print("前5个样本的标签:", y[:5])
# 前5个样本的标签: [0 0 0 0 0]

# 2,划分数据集
# 划分训练集和测试集。表示将数据的 20% 划分为测试集,剩余的 80% 自动作为训练集。也可指定绝对数量(如 test_size=200 表示 200 个样本作为测试集)。
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)

"""
其余参数:
random_state:控制随机划分的种子(如 random_state=42 确保每次运行结果一致)。
shuffle:是否打乱数据后再划分(默认为 True)。
stratify:按标签分层划分(确保训练集和测试集的类别比例一致,适用于分类任务)。
"""

# x:特征矩阵(输入数据),形状通常为 [样本数, 特征数](如 (1000, 5) 表示 1000 个样本,每个样本 5 个特征)。
# y:标签数组(输出数据),形状为 [样本数,](如 (1000,) 表示 1000 个样本对应的类别或回归值)。

# x_train:训练集特征矩阵(80% 的原始 x)。
# x_test:测试集特征矩阵(20% 的原始 x)。
# y_train:训练集标签(与 x_train 对应)。
# y_test:测试集标签(与 x_test 对应)。

# 3,训练模型
# 创建随机森林模型对象
model = RandomForestClassifier()
# 用训练数据(x_train 和 y_train)训练模型,也就是训练集特征矩阵以及训练集标签进行有监督学习训练
model.fit(x_train, y_train)

# 4,预测与评估
# 让训练好的模型处理新数据(如预测新样本的类别)
y_pred = model.predict(x_test)
print(y_pred)
# [0 2 2 1 0 0 1 0 1 0 2 0 1 1 2 1 2 2 2 1 0 1 1 1 0 1 1 1 1 0]

注意事项:

  • 如果数据未标准化或存在缺失值,可能需要在 fit 之前进行预处理(如使用 StandardScaler)。
  • 训练后可通过 model.score(x_test, y_test) 快速评估模型在测试集上的准确率。

超参数选择与调优

超参数:模型当中有一些人为指定的参数,称为超参数;超参数的选择不同会对模型预测的准确率产生影响。

比如:KNeighborsClassifier(n_neighbors=5)中n_neighbors可以称为超参数。

image-20250707205749031

交叉验证

交叉验证是一种数据集的分割方法,将训练集划分为 n 份,其中一份做验证集、其他n-1份做训练集

交叉验证法原理:将数据集划分为 cv=10 份:

  1. 第一次:把第一份数据做验证集,其他数据做训练
  2. 第二次:把第二份数据做验证集,其他数据做训练
  3. …. 以此类推,总共训练10次,评估10次。
  4. 使用训练集+验证集多次评估模型,取平均值做交叉验证为模型得分
  5. 若k=5模型得分最好,再使用全部训练集(训练集+验证集) 对k=5模型再训练一边,再使用测试集对k=5模型做评估

网格搜索

简单来说就是寻找最优超参

交叉验证+网格搜索(模型选择和调优)

  • 交叉验证解决模型的数据输入问题(数据集划分)得到更可靠的模型
  • 网格搜索解决超参数的组合
  • 两个组合再一起形成一个模型参数调优的解决方案

鸢尾花案例(数据预处理+K值调优)

fit_transform(训练+转化),适用于训练集

transform(转化),适用于测试集

1
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
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score

iris_data = load_iris()

# 这里只是加载样本和标签
x, y = iris_data.data, iris_data.target

x_train, x_test, y_train, y_test =train_test_split(x, y, test_size=0.2, random_state=12)

# 进行特征工程
transfer = StandardScaler()

# fit_transform(训练+转化),适用于训练集 transform(转化),适用于测试集
x_train = transfer.fit_transform(x_train)
x_test = transfer.transform(x_test)

# 进行训练
es_model = KNeighborsClassifier()
param_dict = {'n_neighbors': [i for i in range(1, 11)]}
es = GridSearchCV(es_model, param_dict, cv=4)

# 在获取最优评分之前 需要先训练
# 4-4:打印最优的超参组合
print(f"最优评分{es.best_score_}") # 最优组合平均分
print(f"最优超参组合:{es.best_params_}") # 最终超参 (仅供参考)
print(f"最优的估计器对象:{es.best_estimator_}") # 最优组合模型的对象
print(f"具体的交叉验证结果{es.cv_results_}") # 所有组合的 评分结果(过程)

# 再次评估
# 获取最优超参模型对象
es = KNeighborsClassifier(n_neighbors=6)
es.fit(x_train, y_train)
y_pre = es.predict(x_test)
print(f"准确率:{accuracy_score(y_test, y_pre)}")

数字(图像)识别案例

严格来讲不是CV,而是一个基于KNN算法的多分类小案例。

  • 忽略告警:
    • import warnings
    • warnings.filterwarnings(“ignore”,module=”sklearn”)

参考链接:KNN算法实现手写数字识别

自己做的

1
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
57
58
59
60
61
# 图像识别案例
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler, MinMaxScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import *
from collections import Counter
import joblib # 用于保存模型

# 第一步,从csv文件中还原出图像
df = pd.read_csv('/Users/lifuyao/PycharmProjects/pythonProject/Files/手写数字识别.csv', encoding='utf-8')
x = df.iloc[:, 1:]
y = df.iloc[:, :1]


def get_pics(idx):
if idx < 0 or idx > len(df) - 1:
return
# 使用matplotlib 还原图像
pic_idx = x.iloc[idx]
pic_idx = pic_idx.values.reshape(28, 28)
plt.imshow(pic_idx, cmap='gray')
plt.axis('off') # 不显示坐标轴
plt.show()


# 训练模型
def train_model():
df = pd.read_csv('/Users/lifuyao/PycharmProjects/pythonProject/Files/手写数字识别.csv', encoding='utf-8')
x = df.iloc[:, 1:]
# y = df.iloc[:, :1] # 虽然选取0列,但是这个会返回一个DataFrame
y = df.iloc[:, 0] # 返回Series
# 数据归一化(AI 说这里应该放在切分train_test_split后,这样会导致准确率虚高)
x = x / 255
print(x.shape)
print(Counter(y))
x_train,x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=12)
# 开始训练
es_model = KNeighborsClassifier(n_neighbors=5)
es_model.fit(x_train, y_train)
print("准确率为:", es_model.score(x_test, y_test))
# 保存模型
joblib.dump(es_model, './knn.pth')


def use_model():
# 加载图片()中的数据并用模型识别
img = plt.imread('/Users/lifuyao/PycharmProjects/pythonProject/Files/demo.png')
# print(img) #这里好像进行了缩放 不能使用plt.show()展示 0.7137255 0.8901961
# 加载模型
es_model = joblib.load('./knn.pth')
# 这里-1代表全部取,相当于784
print("识别到图片中的数字为:", es_model.predict(img.reshape(1, -1)))
plt.imshow(img, cmap='gray')
plt.axis('off')
plt.show()

if __name__ == '__main__':
# get_pics(5)
# train_model()
use_model()

补充代码

1
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
"""
案例:演示 KNN算法 识别图片 ,即:手写数字识别哪里

介绍:
每个图片都是有28*28像素组成,即我们csv文件每一行有784个像素点,表示图片(每个像素点)的颜色(0~255)
最终构成图像
"""

import matplotlib.pyplot as plt
import pandas as pd
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
import joblib
from collections import Counter

#拓展:忽略警告信息
import warnings
warnings.filterwarnings("ignore",module="sklearn")

#定义一个函数 ,接收用户传入的索引,展示该索引对应的图片
#记住:excel表格中的行号从1开始,索引从0开始,找到对应的正确的数字,应该行号-1
def show_digit(idx):
#1、读取数据集,获取源数据
df = pd.read_csv("../data/手写数字识别.csv")
# print(df) #[42000 行 x 785 列]
#2、判断传入的索引是否越界:
#根据excel表格来看,索引的最大值是4w2000 不是4w2001 因为第一个标签是lable
if idx<0 or idx>len(df)-1:
print("索引越界")
return
#iloc 根据索引来获取
# 格式:df.iloc[行号,列索引]
# print(df.iloc[0:5, 0:2])
#3、走到这里,说明索引没有越界,就是可以正常获取数据
x=df.iloc[:,1:] #1:获取到所有的像素点
y=df.iloc[:,0] #获取到所有的手写数字,excel表格中标签
# print(f"像素的形状:{x.shape}")
# 4、根据传入的索引值,获取该行的数据 (excel表的第一行是lable pixel0...所以这行要去掉,然后索引数字要从0开始)
#如果我想取表格中5(label)个值,(excel显示10行 ,实际传入的是8)
print(f"您传入的索引,对应的数字是:{y[idx]}")

#5、绘制图片(784,) 转换成(28,28)
# print(x.iloc[idx].values) 784个像素点
x=x.iloc[idx].values.reshape(28,28)
# print(x)
#6、绘制具体的灰度图
plt.imshow(x,cmap='gray')
plt.axis('off') #不显示坐标轴
plt.show()

print(f"查看所有标签的分布情况:{Counter(y)}")
#{1: 4684, 7: 4401, 3: 4351, 9: 4188, 2: 4177, 6: 4137, 0: 4132, 4: 4072, 8: 4063, 5: 3795}
# 1: 4684 表示里面有4684种形态的1 笔迹


# 模型训练
# 核心需求 :给一个图片,解析像素点,
# 核心代码
# 1:读取csv 获取数据
# 2: 数据预处理
# 3:特征工程
# 4;模型训练
# 5:模型评估
# 6:模型保存


# 2、定义函数 ,训练模型,并且保存好训练好的模型
def train_model():
# 1:读取csv 获取数据
df=pd.read_csv("../data/手写数字识别.csv")
# 2: 数据预处理
# 2-1 拆分特征列
x=df.iloc[:,1:]
# 2-2 拆分标签列
y=df.iloc[:,0]
# 3:特征工程
# x是像素点的值 [0,255]
# y是标签值 [0,9]
x=x/255

# 4;模型训练
# 参5: 参考y之进行抽取 ,保持标签比例的均衡
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2,random_state=21,stratify=y)
es=KNeighborsClassifier(n_neighbors=3)
es.fit(x_train,y_train)
# 5:模型评估
print(f"准确率{accuracy_score(y_test,es.predict(x_test))}")
# 6:模型保存
joblib.dump(es,"../model/手写数字识别.pkl") # pkl 文件 pickle文件 ,python独有文件类型
print("模型保存成功")

def use_model():
#1、读取图片,
img=plt.imread("../data/demo_0.png") # 28*28像素
# print(img) # [ [像素1,像素2,...像素28],[像素1,像素2,...像素28],.... ] 28*28
#2、读取模型,加载模型对象
es=joblib.load("../model/手写数字识别.pkl")
# print(knn)
#3、模型预测
#写法一:
y_predict=es.predict(img.reshape(1,784))
#写法二:
# y_predict = es.predict(img.reshape(1, -1))
print(f"预测结果为{y_predict}")


if __name__ == '__main__':
# show_digit(6)
# train_model()
use_model()

KNN算法

K Nearest Neighbor算法又叫KNN算法,这个算法是机器学习里面一个比较经典的算法。

KNN算法流程

KNN既可以处理分类问题,又可以处理回归问题,不同的问题最后一步的处理方式不同。

对于分类问题:取前n个标签个数最多的,作为最终结果。

对于回归问题:取前n个标签的均值。

  • 计算测试样本和训练样本中每个样本点的距离(常见的距离度量有欧式距离,马氏距离等);
  • 对上面所有的距离值进行排序;
  • 选前 k 个最小距离的样本;
  • 根据这 k 个样本的标签进行投票,得到最后的分类类别

K值大小对于训练的影响

  • K值过小:
    • 容易受到异常点的影响
    • 容易过拟合
  • k值过大:
    • 受到样本均衡的问题
    • 容易欠拟合

欧式距离

其实就是两点之间的距离计算方式:

二维空间

点 $a(x_{1},y_{1})$ 与 $b(x_{2},y_{2})$ 间的距离:
$$
d_{12} = \sqrt{(x_{1}-x_{2})^{2} + (y_{1}-y_{2})^{2}}
$$

三维空间

点 $a(x_{1},y_{1},z_{1})$ 与 $b(x_{2},y_{2},z_{2})$ 间的距离:
$$
d_{12} = \sqrt{(x_{1}-x_{2})^{2} + (y_{1}-y_{2})^{2} + (z_{1}-z_{2})^{2}}
$$

n维空间

点 $a(x_{11},x_{12},\dots,x_{1n})$ 与 $b(x_{21},x_{22},\dots,x_{2n})$ 间的距离:
$$
d_{12} = \sqrt{\sum_{k=1}^{n}(x_{1k} - x_{2k})^{2}}
$$

image-20250606110103367

计算唐探与各个电影的欧式距离,取前5个,统计各个电影类型出现的频率,根据频率判断唐探的目标值为喜剧片

sklearn-KNN算法

分类问题

1
2
3
4
5
6
7
8
9
10
11
12
13
from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor

es = KNeighborsClassifier(n_neighbors=3)
x_train = [[1], [2], [3], [4]]
# 注意这里是分类问题,如果属于二分类 只有0/1
y_train = [0, 0, 0, 1]

# 如果k近邻距离(2个样本)举例相等,则会选择标签值较小的值。剃刀
x_test = [[5]]

es.fit(x_train, y_train)

print(es.predict(x_test))

回归问题

1
2
3
4
5
6
7
8
9
10
from sklearn.neighbors import KNeighborsRegressor

es = KNeighborsRegressor(n_neighbors=4)
x_train = [[0, 0, 1], [1, 1, 0], [3, 10, 10], [4, 11, 12]]
y_train = [0.1, 0.2, 0.3, 0.4]

x_test = [[3, 11, 10]]

es.fit(x_train, y_train)
print(es.predict(x_test))

各种距离计算方式

image-20250606114135017

欧式距离(Euclidean Distance)

这里

曼哈顿距离(Manhattan Distance)

二维空间

点 $a(x_{1},y_{1})$ 与 $b(x_{2},y_{2})$ 间的距离:
$$
d_{12} = |x_{1} - x_{2}| + |y_{1} - y_{2}|
$$

n维空间

点 $a(x_{11},x_{12},\ldots,x_{1n})$ 与 $b(x_{21},x_{22},\ldots,x_{2n})$ 间的距离:
$$
d_{12} = \sum_{k=1}^{n} |x_{1k} - x_{2k}|
$$

切比雪夫距离 (Chebyshev Distance)

二维空间

点 $a(x_{1},y_{1})$ 与 $b(x_{2},y_{2})$ 间的距离:
$$
d_{12} = \max\left(|x_{1}-x_{2}|,\ |y_{1}-y_{2}|\right)
$$

n维空间

点 $a(x_{11},x_{12},\ldots,x_{1n})$ 与 $b(x_{21},x_{22},\ldots,x_{2n})$ 间的距离:
$$
d_{12} = \max_{1 \leq i \leq n} \left( |x_{1i} - x_{2i}| \right)
$$

标准化欧氏距离 (Standardized EuclideanDistance)

既然数据各维分量的分布不一样,那先将各个分量都”标准化”到均值、方差相等,$$S_k$$表示各个维度的标准差,如果将方差的倒数看成一个权重,也可称之为加权欧氏距离Weiahted Euclidean distance)
$$
d_{12} = \sqrt{\sum_{k=1}^{n} \left( \frac{x_{1k} - x_{2k}}{s_{k}} \right)^2 }
$$

余弦距离(Cosine Distance)

几何中,夹角余弦可用来衡量两个向量方向的差异;机器学习中,借用这一概念来衡量样本向量之间的差异。

二维空间向量

向量 $\vec{a}(x_1,y_1)$ 与 $\vec{b}(x_2,y_2)$ 的夹角余弦:
$$
\cos \theta = \frac{x_{1}x_{2} + y_{1}y_{2}}{\sqrt{x_{1}^{2} + y_{1}^{2}} \sqrt{x_{2}^{2} + y_{2}^{2}}}
$$

n维空间向量

对于 $n$ 维样本点:$a(x_{11},x_{12},\ldots,x_{1n})$、$b(x_{21},x_{22},\ldots,x_{2n})$

夹角余弦存在两种表达形式:

点积与模长形式:
$$
\cos (\theta) = \frac{a \cdot b}{|a| \ |b|}
$$

展开形式:
$$
\cos (\theta) = \frac{\sum\limits_{k=1}^{n} x_{1k} x_{2k}}{\sqrt{\sum\limits_{k=1}^{n} x_{1k}^{2}} \sqrt{\sum\limits_{k=1}^{n} x_{2k}^{2}}}
$$

汉明距离(Hamming Distance)

汉明距离(Hamming Distance)是用于衡量两个等长字符串在相同位置上不同字符的个数

对于两个长度为 $n$ 的字符串 $S$ 和 $T$(或二进制向量),汉明距离 $D_H$ 定义为:
$$
D_H(S, T) = \sum_{i=1}^{n} \mathbb{I}(S_i \neq T_i)
$$

其中:

  • $S_i$ 和 $T_i$ 分别表示字符串 $S$ 和 $T$ 的第 $i$ 个字符(或二进制位)
  • $\mathbb{I}(x)$ 是指示函数(若 $x$ 为真则返回 1,否则返回 0)

杰卡德距离(Jaccard Distance)

杰卡德相似系数(Jaccard slmilarity coeficient): 两个集合A和B的交集元素在A,B的并集中所占的比例,称为两个集合的杰卡德相似系数,用符号J(A,B)表示:
$$
J(A,B) = \frac{|A \cap B|}{|A \cup B|}
$$

闵可夫斯基距离(Minkowski Distance)

也叫闵式距离:
$$
d_{12} = \sqrt[p]{\sum_{k=1}^{n} \left| x_{1k} - x_{2k} \right|^{p}}
$$
当$p=1$时,为曼哈顿距离

当$p=2$时,为欧氏距离

KD树

根据KNN每次需要预测一个点时,我们都需要计算训练数据集里每个点到这个点的距离,然后选出距离最近的k个点进行投票。当数据集很大时,这个计算成本非常高

kd树:为了避免每次都重新计算一遍距离,算法会把距离信息保存在一棵树里,这样在计算之前从树里查询距离信息,尽量避免重新计算。其基本原理是,如果A和B距离很远,B和C距离很近,那么A和C的距离也很远。有了这个信息,就可以在合适的时候跳过距离远的点。

KD树的构建(二维平面)

  1. 确定split域:按照x/y轴进行分割,根据x轴以及y轴数据上的方差,方差大的为split域。
  2. 确定Node-Data域:按照split值排序,取中间的点作为Node-Data点
  3. 确定左子空间和右子空间:按照Node-Data的x/y坐标进行点的分割

详细步骤:

给定一个二维空间数据集:T={(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)},构造一个平衡kd树。

image-20250606154437192

  1. 确定:split域=x。具体是:6个数据点在x,y维度上的数据方差分别为39,28.63,在x轴上方差更大,故split域值为x;
  2. 确定:Node-data = (7,2)。具体是:根据x维上的值将数据排序,6个数据的中值(所谓中值,即中间大小的值)为5,7的平均値,但是没有6,所以往后进一到7(暂时没搞懂)!!所以Node-data域位数据点(7,2)。
  3. 确定:左子空间和右子空间。具体是:分割超平面x=7将整个空间分为两部分:x<=7的部分为左子空间,包含3个节点={(2,3),(5,4),(4,7)};另一部分为右子空间,包含2个节点={(9,6),(8,1)};
  4. 如上算法所述,kd树的构建是一个递归过程,我们对左子空间和右子空间内的数据重复根节点的过程就可以得到一级子节点**(5,4)(9,6)**,同时将空间和数据集进一步细分,如此往复直到空间中只包含一个数据点。

KD树的快速最近邻搜索算法

假设标记为星星的点是 test point, 绿色的点是找到的近似点,在回溯过程中,需要用到一个队列,存储需要回溯的点,在判断其他子节点空间中是否有可能有距离查询点更近的数据点时,做法是以查询点为圆心,以当前的最近距离为半径画圆,这个圆称为候选超球(candidate hypersphere),如果圆与回溯点的轴相交,则需要将轴另一边的节点都放到回溯队列里面来。

image-20250606155816616

样本集{(2,3),(5,4), (9,6), (4,7), (8,1), (7,2)}

image-20250606160159584

  • 查找点(2.1,3.1)

  • 确定Search_Path为<(7,2),(5,4), (2,3)>;从search_path中取出(2,3)作为当前最佳结点nearest,dist为0.141

    • 然后回溯至(5,4),以(2.1,3.1)为圆心,以dist=0.141为半径画一个圆,并不和超平面y=4相交,如上图,所以不必跳到结点(5,4)的右子空间去搜索,因为右子空间中不可能有更近样本点了。
  • 于是再回溯至(7,2),同理,以(2.1,3.1)为圆心,以dist=0.141为半径画一个圆并不和超平面x=7相交,所以也不用跳到结点(7,2)的右子空间去搜索。

    • 至此,search_path为空,结束整个搜索,返回nearest(2,3)作为(2.1,3.1)的最近邻点,最近距离为0.141。
  • 查找点(2,4.5)

    • 在(7,2)处测试到达(5,4),在(5,4)处测试到达(4,7)【优先选择在本域搜索】,然后search_path中的结点为<(7,2),(5,4), (4,7)>,从search_path中取出(4,7)作为当前最佳结点nearest, dist为3.202;
    • 然后回溯至(5,4),以(2,4.5)为圆心,以dist=3.202为半径画一个圆与超平面y=4相交,所以需要跳到(5,4)的左子空间去搜索。所以要将(2,3)加入到search_path中,现在search_path中的结点为<(7,2),(2, 3)>;另外,(5,4)与(2,4.5)的距离为3.04 < dist = 3.202,所以将(5,4)赋给nearest,并且dist=3.04。
    • 回溯至(2,3),(2,3)是叶子节点,直接平判断(2,3)是否离(2,4.5)更近,计算得到距离为1.5,所以nearest更新为(2,3),dist更新为(1.5)
    • 回溯至(7,2),同理,以(2,4.5)为圆心,以dist=1.5为半径画一个圆并不和超平面x=7相交, 所以不用跳到结点(7,2)的右子空间去搜索。
    • 至此,search_path为空,结束整个搜索,返回nearest(2,3)作为(2,4.5)的最近邻点,最近距离为1.5。

KD树的插入

  • 在现有KD树中插入点 (3, 6)):
    • 现有树结构(按 x→y→x... 划分):
1
2
3
4
5
          (5,4)        ← 根节点(x轴分割)
/ \
(2,3) (8,1) ← 第2层(y轴分割)
/ \ / \
(1,7) (4,2) (7,9) (9,5)

插入步骤

  1. 比较根节点 (5,4)(第1层,x轴):
    • 插入点 (3,6) 的x值 3 < 5 → 进入左子树。
  2. 比较 (2,3)(第2层,y轴):
    • 插入点y值 6 > 3 → 进入右子树。
  3. 到达叶子节点 (4,2)
    • 第3层按x轴比较,3 < 4 → 作为 (4,2) 的左子节点插入。

插入后树结构

1
2
3
4
5
6
7
          (5,4)
/ \
(2,3) (8,1)
/ \ / \
(1,7) (4,2) (7,9) (9,5)
/
(3,6) ← 新插入节点

KD树的删除

  • 核心逻辑:找到待删除节点后,按以下规则处理:

    • 情况1:若为叶子节点 → 直接删除。

    • 情况2:若非叶子节点 → 找到子树中同分割轴的最优替代节点(类似二叉搜索树的中序后继)。

删除节点 (5,4)

1
2
3
4
5
6
7
          (5,4)
/ \
(2,3) (8,1)
/ \ / \
(1,7) (4,2) (7,9) (9,5)
/
(3,6)

步骤

  1. 定位节点 (5,4)
    • 根节点,分割轴为x轴。
  2. 寻找替代节点:
    • 在右子树中找x轴最小的点(即中序后继)→ (7,9)
  3. 替换并递归删除:
    • (7,9) 替换 (5,4),再递归删除 (7,9) 的原位置。

删除后树结构

1
2
3
4
5
6
7
          (7,9)        ← 原(5,4)被替换
/ \
(2,3) (8,1)
/ \ / \
(1,7) (4,2) (9,5) ← (7,9)从原位置删除
/
(3,6)

线性回归

线性回归(Linear regression)是利用**回归方程(函数)**对一个或多个自变量(特征值)和因变量(目标值)之间关系进行建模的一种分析方式。

只有一个自变量的情况称为单变量回归,多于一个自变量情况的叫做多元回归。

一元线性回归

目标值只与一个因变量有关系,例如在某些情况下体重只跟身高有关系。

image-20230901102857178

$$
Y = wx + b
$$

回归方程解法(最小二乘法)

image-20250708153912662

由于有很多点,且并不正好落在一条直线上,这么多点每两点都能确定一条直线,这到底要怎么确定选哪条直线呢?

刚好我们知道残差平方和的公式:

可以发现上述自变量有$b$和$w$,利用微积分知识,导数为0时,Q取最小值,因此分别对$b$和$w$求偏导并令其为0:
$$
\frac{\partial Q}{\partial b}=2\sum_{1}^{n}{(y_{i}-\hat{b}-\hat{w}x_{i})}=0
$$

$$
\frac{\partial Q}{\partial w}=2\sum_{1}^{n}{(y_{i}-\hat{b}-\hat{w}x_{i})x_{i}}=0
$$

$x_{i}$,$y_{i}$(i=1,2,…n)都是已知的,全部代入上面两个式子,可求得$w$和$b$的值。这就是最小二乘法,二乘是平方的意思

多元线性回归

目标值只与多个因变量有关系

image-20230901103000204

$$
y = \beta_0 + \beta_1 x_1 + \beta_2 x_2 + \cdots + \beta_p x_p
$$

可以简写为矩阵形式(一般加粗表示矩阵或向量): $\boldsymbol{Y}=\boldsymbol{X}\boldsymbol{\beta}$

回归方程解法(正规方程法)

一元线性回归的损失函数可以用残差平方和:
$$
Q=\sum_{i=1}^{n}\left(y_{i}-\hat{y}_{i}\right)^{2}
$$

代入多元线性回归方程:
$$
Q=\sum_{i=1}^{n}\left(y_{i}-\hat\beta_{0}-\hat\beta_{1} x_{i 1}-\ldots-\hat\beta_{p} x_{i p}\right)^{2}
$$

用矩阵形式表示($AA^{T}$= A的L2范数):
$$
Q = (\boldsymbol{X}\boldsymbol{\beta}-\boldsymbol{Y})^{T}(\boldsymbol{X}\boldsymbol{\beta}-\boldsymbol{Y})
$$

由矩阵的运算规律 $(A B)^{T}=B^{T} A^{T}$得:
$$
Q = \left(\boldsymbol{\beta}^{T}\boldsymbol{X}^{T}-\boldsymbol{Y}^{T}\right)(\boldsymbol{X}\boldsymbol{\beta}-\boldsymbol{Y})
$$
由矩阵乘法分配律得:
$$
Q={\beta}^{T}{X}^{T}{X}{\beta}-{\beta}^{T}{X}^{T}{Y}-{Y}^{T}{X}{\beta}+ {Y}^{T}{Y}
$$
简化得:
$$
Q={\beta}^{T}{X}^{T}{X}{\beta}-2{\beta}^{T}{X}^{T}{Y}+{Y}^{T}{Y}
$$
按照一元线性回归求解析解的思路,现在要对Q求导并令导数为0:

根据公理:$\frac{\partial}{\partial \beta}(A\beta)^T(B\beta) = A^TB + B^TA$得:
$$
\frac{\partial Q}{\partial \beta} = 2X^{T}X\beta - X^{T}Y - X^{T}Y = 0
$$

合并同类项化简得到对称形式:
$$
2X^{T}X\beta - 2X^{T}Y = 0
$$

移项整理:
$$
X^{T}X\beta = X^{T}Y
$$

最终解得
$$
\beta = \left(X^{T}X\right)^{-1}X^{T}Y
$$

正规方程应用

通过多元线性回归方程求解:

公式$w=(X^{T}X)^{-1}X^{T}Y$

  • X为特征值矩阵,Y为目标值向量,w为模型参数的向量形式

例如根据房子的空间、卧室数量、层数以及房子年龄预测房子的价格:

image-20250708153515253

损失函数

一元线性回归、多元线性回归的损失函数可以用如下公式进行判断

image-20250708143849186

计算每个点的残差 $e_i=y_i-\hat{y_i}$ 的值,再将其平方(消除负号),再将所有的 $e_{i}^{2}$相加,量化出预测值和真实值的误差。

得到残差平方和SSE(Sum of Squares for Error):

Latex传送门:时刻注意下划线_以及尖角号^导致公式解析异常。

image-20250710143356486

也就是回归问题的损失函数。

绝对值误差函数(Mean Absolute Error, MAE):

$$
\text{MAE} = \frac{1}{n} \sum_{i=1}^{n} \left| Y_i - \hat{Y}_i \right|
$$

  • 上面的公式中:n 为样本数量, y 为实际值, $\hat{y}$ 为预测值

  • MAE 越小模型预测约准确

Sklearn 中MAE的API

1
2
from sklearn.metrics import mean_absolute_error
mean_absolute_error(y_test,y_predict)

均方误差函数(Mean Squared Error, MSE):

$$
\text{MSE} = \frac{1}{n} \sum_{i=1}^{n} \left(Y_i - \hat{Y}_i\right)^2
$$

  • 上面的公式中:n 为样本数量, y 为真实值, $\hat{y}$ 为预测值
  • MSE 越小模型预测约准确

Sklearn 中MSE的API

1
2
from sklearn.metrics import mean_squared_error
mean_squared_error(y_test,y_predict)

均方根误差函数(Root Mean Squared Error ,RMSE)

$$
RMSE = \sqrt{\frac{1}{n}\sum_{i=1}^{n}\left(y_{i}-\hat{y}_{i}\right)^{2}}
$$

  • 上面的公式中:n 为样本数量, y 为真实值, $\hat{y}$ 为预测值
  • RMSE 越小模型预测约准确

梯度下降算法⭐️⭐️⭐️

算法思想:沿着梯度下降的方向求解极小值,也就是通过一步步迭代,让所有偏导函数都下降到最低

梯度就是导数+方向

单变量函数中,梯度就是某一点切线斜率(某一点的导数)

image-20250608163506950

多变量函数中,梯度就是某一个点的偏导数

$L(a,b,c)$的梯度为
$$
\left(\frac{\partial L}{\partial a}, \frac{\partial L}{\partial b}, \frac{\partial L}{\partial c}\right)
$$

image-20250608163451996

梯度下降公式:
$$
\theta_{i+1} = \theta_{i} - \alpha \frac{\partial}{\partial \theta_{i}} J(\theta)
$$

  • $\alpha$(或$\eta$):学习率(步长)
    • 机器学习常用范围:$0.001 \sim 0.01$
    • 深度学习常用范围:$10^{-6}$量级
    • 学习率太小,下降的速度会变慢;学习率太大,容易错过最低点,产生下降过程中的震荡,甚至梯度爆炸
  • 负号的意义:梯度是上升最快的方向(想象斜率为正,函数呈上升趋势),我们需要下降最快的方向,因此需要加负号
  • $\alpha$后面的是损失函数对某个特征求偏导

单变量梯度下降示例

image-20250708174010262

求函数 $J(\theta) = \theta^{2}$ 的最小值,即确定 $\theta$ 为何值时 $J(\theta)$ 最小。

  • 函数导数:$\frac{\partial J(\theta)}{\partial \theta} = 2\theta$ ,于是

    • 梯度下降更新公式:$\theta_{new} = \theta - \alpha \cdot 2\theta$
  • 令:初始值:$\theta_0 = 1$、学习率:$\alpha = 0.4$

  • 经过5次迭代后,$\theta$ 值从1收敛到0.0016

迭代过程

步骤 计算公式 结果
第一步 $\theta = 1$ 1.0
第二步 $1 - 0.4 \times (2 \times 1)$ 0.2
第三步 $0.2 - 0.4 \times (2 \times 0.2)$ 0.04
第四步 $0.04 - 0.4 \times (2 \times 0.04)$ 0.008
第五步 $0.008 - 0.4 \times (2 \times 0.008)$ 0.0016

多元线性回归梯度算法

回到前面的多元线性回归,用梯度下降算法求损失函数的最小值。

首先,求梯度,前面已经给出求偏导的公式:
$$
\frac{\partial Q}{\partial \beta}=2 X^{T} X \beta-2X^{T} Y=2 X^{T} (X \beta-Y)
$$

将梯度代入随机梯度下降公式:
$$
\Theta_{k+1}=\Theta_{k}-\alpha \cdot 2 X^{T} (X \beta-Y)
$$

这个式子中,X矩阵和Y向量都是已知的,步长是人为设定的一个值,只有参数 $\beta$ 是未知的,而每一步的 $\Theta$ 是由 $\beta$ 决定的,也就是每一步的点坐标。

算法过程:

  1. 初始化 $\beta$ 向量的值,即 $\Theta_{0}$ ,将其代入 $\frac{\partial Q}{\partial \beta}$ 得到当前位置的梯度;
  2. 用步长 $\alpha$ 乘以当前梯度,得到从当前位置下降的距离;
  3. 更新 $\Theta_1$ ,其更新表达式为 $\Theta_1=\Theta_0-\alpha \cdot 2 X^{T} (X \Theta_0-Y)$ ;
  4. 重复以上步骤,直到更新到某个 $\Theta_k$ ,达到停止条件,这个 $\Theta_k$ 就是我们求解的参数向量。

梯度下降算法调优

image-20250708174829651

梯度下降公式:
$$
\theta_{i+1} = \theta_{i} - \alpha \frac{\partial}{\partial \theta_{i}} J(\theta)
$$

其中,步长:$\theta_0$,学习率:$\alpha$,$\frac{\partial}{\partial \theta_{i}} J(\theta)$为回归函数的对变量的导数。

在使用梯度下降时,需要进行调优

  • 算法的步长选择。步长太大,会导致迭代过快,有可能错过最优解。步长太小,收敛速度过慢。需要多次运行后才能得到一个较为优的值。

  • 算法参数的初始值选择。由于有局部最优解的风险,需要多次用不同初始值运行算法,选择损失函数最小化的初值。

  • 归一化。由于样本不同特征的取值范围不一样,可能导致迭代很慢,为了减少特征取值的影响,可以对特征数据归一化。新期望为0,新方差为1,迭代速度可以大大加快。

$$
\frac{x - \overline{x}}{\operatorname{std}(x)}
$$

改进的梯度算法

  • 批量梯度下降法BGD(Batch Gradient Descent)

$$
\theta_{j} = \theta_{j} - \alpha \left(
\frac{1}{N} \sum_{i=1}^{N} \left(
h_{\theta}\left(x_{0}^{(i)}, x_{1}^{(i)}, \cdots, x_{n}^{(i)}\right) - y^{(i)}
\right) x_{j}^{(i)}
\right)
$$

批量梯度下降法,是梯度下降法最常用的形式

就是在更新参数时使用所有的样本来进行更新,这个方法就是之前的线性回归的梯度下降算法。

  • 随机梯度下降法SGD(Stochastic Gradient Descent)

$$
\theta_{j} = \theta_{j} - \alpha \left(
h_{\theta}\left(x_{0}^{(i)}, x_{1}^{(i)}, \cdots, x_{n}^{(i)}\right) - y^{(i)}
\right) x_{j}^{(i)}
$$

就是在与求梯度时没有用所有的N个样本的数据,而是仅仅选取一个样本j来求梯度。

随机梯度下降法,和批量梯度下降法是两个极端,一个采用所有数据来梯度下降,一个用一个样本来梯度下降。

自然各自的优缺点都非常突出。

训练速度,随机梯度下降法仅采用一个样本来迭代,训练速度很快,而批量梯度下降法在样本量很大的时候,训练速度很慢。

准确度,随机梯度下降法用于仅仅用一个样本决定梯度方向,导致解很有可能不是最优。

收敛速度,由于随机梯度下降法一次迭代一个样本,导致迭代方向变化很大,不能很快的收敛到局部最优解。
那么,有没有一个中庸的办法能够结合两种方法的优点呢?有!这小批量梯度下降法MBGD。

  • 小批量梯度下降法MBGD(Mini-batch Gradient Descent)

$$
\theta_{j}= \theta_{j} - \alpha \left(
\frac{1}{x} \sum_{i=t}^{t+x-1} \left(
h_{\theta}\left(x_{0}^{(i)}, x_{1}^{(i)}, \cdots, x_{n}^{(i)}\right) - y^{(i)}
\right) x_{j}^{(i)}
\right)
$$

小批量梯度下降法是批量梯度下降法和随机梯度下降法的折中

也就是对于N个样本,采用x个样本来迭代,1<x<N。一般可以取x=10,当然根据样本的数据,可以调整这个x的值。

若batch_size=1则变成了SGD,若batch_size=n则变成了FGD

  • 随机平均梯度下降算法 SAG

$$
\theta_{i+1} = \theta_{i} - \frac{\alpha}{n} \sum_{j=1}^{n} \left( h_{\theta}\left(x_{0}^{(j)}, x_{1}^{(j)},\ldots,x_{n}^{(j)}\right) - y_{j} \right) x_{i}^{(j)}
$$

每次迭代时,随机选择一个样本的梯度值和以往样本的梯度值的均值进行参数更新。

正则化

L1正则化:将不重要的特征的参数为0
L2正则化:将不重要的特征参数趋向于0

参考链接:

过拟合如何解决

image-20250708204038413

使用正则化项,也就是给loss function加上一个参数项,正则化项有L1正则化、L2正则化、ElasticNet。加入这个正则化项好处:

  • 控制参数幅度,不让模型“无法无天”。
  • 限制参数搜索空间
  • 解决欠拟合与过拟合的问题。

L2正则化(Ridge岭回归)

  • 数学定义:在原始损失函数中添加权重的平方和作为惩罚项:

$$
J(\mathbf{w}) = \text{Loss}(\mathbf{y}, \hat{\mathbf{y}}) + \lambda \sum_{i=1}^n w_i^2
$$

$$
J = J_{0} + \lambda \sum_{w} w^{2}
$$

  • $J$:加入正则化后的总损失函数

  • $J_0$:原始损失函数(如MSE/交叉熵)

  • $\lambda$:正则化强度超参数($\lambda > 0$)

  • $w$:模型权重参数

损失函数
$$
J_0=\sum_{i=1}^{n}\left(y_{i}-\hat{y}_{i}\right)^{2}
$$

$J_0$表示上面的 loss function ,在loss function的基础上加入$w$参数的平方和乘以$\lambda$,假设:
$$
L = \lambda(w_{1}^{2} + w_{2}^{2})
$$
回忆以前学过的单位元的方程:

$$
x^{2} + y^{2} = 1
$$

和L2正则化项一样,此时我们的任务变成在L约束下求出J取最小值的解。求解J0的过程可以画出等值线。同时L2正则化的函数L也可以在w1w2的二维平面上画出来。如下图:

image-20250616200929180

什么场景下用L2正则化?

只要数据线性相关,用LinearRegression拟合的不是很好,需要正则化,可以考虑使用岭回归(L2), 如何输入特征的维度很高,而且是稀疏线性关系的话, 岭回归就不太合适,考虑使用Lasso回归。

1
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
'''
L2正则化
'''
# 0.导入工具包
import numpy as np
from sklearn.linear_model import LinearRegression,Lasso,Ridge
import matplotlib.pyplot as plt
from sklearn.metrics import mean_squared_error

# 1.准备数据
np.random.seed(22)
x = np.random.uniform(-3, 3, size=100)
#print(x)
y = 0.5 * x ** 2 + x + 2 + np.random.normal(0, 1, size=100)
# print(y)
# 2.模型训练
model = Ridge(alpha=0.1)
X = x.reshape(-1,1)
X3 = np.hstack([X,X**2,X**3,X**4,X**5,X**6,X**7,X**9,X**10])
model.fit(X3,y)
print(model.coef_)

# 3.模型预测
y_predict = model.predict(X3)
print(mean_squared_error(y_true=y,y_pred=y_predict))
# 4.展示效果
plt.scatter(x,y)
plt.plot(np.sort(x),y_predict[np.argsort(x)],color='r')
plt.show()

"""
[ 1.41372496e+00 1.24305713e+00 -9.90023788e-01 -3.35689918e-01
4.82122518e-01 4.60105075e-02 -8.41885140e-02 4.75191054e-03
-2.35177926e-04]

0.8227587479762861

"""

L1正则化(Lasso回归)

Lasso(Least Absolute Shrinkage and Selection Operator)

L1正则化与L2正则化的区别在于惩罚项的不同:

$$
J(\mathbf{w}) = \text{Loss}(\mathbf{y}, \hat{\mathbf{y}}) + \lambda \sum_{i=1}^n |w_i|
$$

$$
J = J_{0} + \lambda \left( |w_{1}| + |w_{2}| \right)
$$

求解J0的过程可以画出等值线。同时L1正则化的函数也可以在w1w2的二维平面上画出来。如下图:

image-20250616201532812

惩罚项表示为图中的黑色棱形,随着梯度下降法的不断逼近,与棱形第一次产生交点,而这个交点很容易出现在坐标轴上。这就说明了L1正则化容易得到稀疏矩阵。

L1正则化使用场景

L1正则化(Lasso回归)可以使得一些特征的系数变小,甚至还使一些绝对值较小的系数直接变为0,从而增强模型的泛化能力 。对于高的特征数据,尤其是线性关系是稀疏的,就采用L1正则化(Lasso回归),或者是要在一堆特征里面找出主要的特征,那么L1正则化(Lasso回归)更是首选了。

1
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
'''
L1正则化
'''
# 0.导入工具包
import numpy as np
from sklearn.linear_model import LinearRegression,Lasso
import matplotlib.pyplot as plt
from sklearn.metrics import mean_squared_error

# 1.准备数据
np.random.seed(22)
x = np.random.uniform(-3, 3, size=100)
#print(x)
y = 0.5 * x ** 2 + x + 2 + np.random.normal(0, 1, size=100)
# print(y)
# 2.模型训练
model = Lasso(alpha=0.1)
X = x.reshape(-1,1)
X3 = np.hstack([X,X**2,X**3,X**4,X**5,X**6,X**7,X**9,X**10])
model.fit(X3,y)
print(model.coef_)

# 3.模型预测
y_predict = model.predict(X3)
print(mean_squared_error(y_true=y,y_pred=y_predict))
# 4.展示效果
plt.scatter(x,y)
plt.plot(np.sort(x),y_predict[np.argsort(x)],color="r")
plt.show()

"""
[ 2.68230387e-01 2.17826117e-01 3.65595593e-01 7.28745107e-02
-4.14761830e-02 -1.80435345e-03 -5.28230167e-03 7.29378060e-04
-5.27239512e-05]

0.94157770743677

"""

ElasticNet回归

ElasticNet综合了L1正则化项和L2正则化项,以下是它的公式:
$$
J(\mathbf{w}) = \text{Loss}(\mathbf{y}, \hat{\mathbf{y}}) + \lambda_1 \sum_{i=1}^n |w_i| + \lambda_2 \sum_{i=1}^n w_i^2
$$

$$
\min \left(
\frac{1}{2m} \left[ \sum_{i=1}^{m}(y_i’ - y_i)^2 \right]
+ \lambda \sum_{j=1}^{n} \theta_j^2
+ \lambda \sum_{j=1}^{n} |\theta_j|
\right)
$$

ElasticNet回归的使用场景

ElasticNet在我们发现用Lasso回归太过(太多特征被稀疏为0),而岭回归也正则化的不够(回归系数衰减太慢)的时候,可以考虑使用ElasticNet回归来综合,得到比较好的结果。

线性回归要求因变量服从正态分布?

我们假设线性回归的噪声服从均值为0的正态分布。 当噪声符合正态分布$N(0,delta^2)$时,因变量则符合正态分布$N(ax(i)+b,delta^2)$,其中预测函数$y=ax^{(i)}+b$。这个结论可以由正态分布的概率密度函数得到。也就是说当噪声符合正态分布时,其因变量必然也符合正态分布。

在用线性回归模型拟合数据之前,首先要求数据应符合或近似符合正态分布,否则得到的拟合函数不正确。

回归模型评估方法⭐️

特性 MAE MSE RMSE
误差敏感性 对异常值不敏感 对异常值敏感 对异常值敏感
量纲 与原数据一致 原数据平方量纲 与原数据一致
数学性质 不可导 处处可导 处处可导
优化方向 中位数 平均值 平均值
计算复杂度 中等 中等(需开方)
  1. MAE适用场景
    • 需要解释性强的场景
    • 数据存在少量异常值时
    • 例如:房价预测、零售销量预测
  2. MSE/RMSE适用场景
    • 需要惩罚大误差的场景
    • 数据质量较高且分布均匀时
    • 例如:金融风险评估、科学实验分析
  3. 特殊注意事项
    • MSE值通常比MAE大1-2个数量级
    • RMSE在量纲上与MAE可比,但数值通常更大
    • 当误差分布呈高斯分布时,MSE是最优

波士顿房价案例(线性回归及API)

波士顿房价数据集是机器学习中经典的回归问题数据集,包含506条样本数据,每条数据有13个特征变量和1个目标变量(房价中位数)。数据来源于1978年美国人口普查局收集的波士顿地区房价信息。

数据集属性

数据集包含以下特征:

  • CRIM:城镇人均犯罪率
  • ZN:占地面积超过2.5万平方英尺的住宅用地比例
  • INDUS:城镇非零售业务地区的比例
  • CHAS:查尔斯河虚拟变量(1表示靠近河流,0表示不靠近)
  • NOX:氮氧化物浓度(每千万分之一)
  • RM:平均每户住房房间数
  • AGE:1940年以前建造的自住房屋比例
  • DIS:到波士顿五个就业中心的加权距离
  • RAD:径向公路可达性指数
  • TAX:每10,000美元的全额财产税税率
  • PTRATIO:城镇师生比例
  • B:黑人比例
  • LSTAT:人口中低收入阶层百分比
  • MEDV:自住房屋房价中位数(目标变量)

案例分析步骤

  1. 数据获取与分割
  2. 特征标准化处理
  3. 模型训练(正规方程和梯度下降两种方法)
  4. 模型评估

梯度下降法

1
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
# 0.导包
# from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression,SGDRegressor
from sklearn.metrics import mean_squared_error,mean_absolute_error

# 1.加载数据
# boston = load_boston()
# print(boston)
import pandas as pd
import numpy as np


data_url = "http://lib.stat.cmu.edu/datasets/boston"
raw_df = pd.read_csv(data_url, sep="\s+", skiprows=22, header=None)
data = np.hstack([raw_df.values[::2, :], raw_df.values[1::2, :2]])
target = raw_df.values[1::2, 2]

# 2.数据集划分
x_train,x_test,y_train,y_test =train_test_split(data,target,test_size=0.2,random_state=22)

# 3.标准化
process=StandardScaler()
x_train=process.fit_transform(x_train)
x_test=process.transform(x_test)

# 4.模型训练
# 4.1 实例化(正规方程)
# 随机梯度下降法
model = SGDRegressor(learning_rate='constant',eta0=0.01)
# 4.2 fit
model.fit(x_train,y_train)

# print(model.coef_)
# print(model.intercept_)
# 5.预测
y_predict=model.predict(x_test)

print(y_predict)

# 6.模型评估
"""
MSE: 23.39259636923585
RMSE: 4.836589332291491
MAE: 3.562918092849904
"""
print("MSE:",mean_squared_error(y_test,y_predict))
print("RMSE:",mean_squared_error(y_test,y_predict) ** 0.5)
print("MAE:",mean_absolute_error(y_test,y_predict))

正规方程法

1
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
57
58
59
# 0.导包
# from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression,SGDRegressor
from sklearn.metrics import mean_squared_error,mean_absolute_error

# 1.加载数据
# boston = load_boston()
# print(boston)
import pandas as pd
import numpy as np

# 导入数据
data_url = "http://lib.stat.cmu.edu/datasets/boston"
raw_df = pd.read_csv(data_url, sep="\s+", skiprows=22, header=None)
data = np.hstack([raw_df.values[::2, :], raw_df.values[1::2, :2]])
target = raw_df.values[1::2, 2]

# 2.数据集划分
x_train,x_test,y_train,y_test =train_test_split(data,target,test_size=0.2,random_state=22)

# 3.标准化
process=StandardScaler()
x_train=process.fit_transform(x_train)
x_test=process.transform(x_test)

# 4.模型训练 - 采用线性回归模型
# 4.1 实例化(正规方程)
# 默认采用的就是正规方程法
model =LinearRegression(fit_intercept=True)
# 4.2 fit
model.fit(x_train,y_train)

"""
coef:
[-0.73088157 1.13214851 -0.14177415 0.86273811 -2.02555721 2.72118285
-0.1604136 -3.36678479 2.5618082 -1.68047903 -1.67613468 0.91214657
-3.79458347]
intercept:
22.57970297029704
"""
print(model.coef_)
print(model.intercept_)
# 5.预测
y_predict=model.predict(x_test)

print(y_predict)

# 6.模型评估
# 使用MSE进行评估
"""
MSE: 20.77068478427006
RMSE: 4.557486674063903
MAE: 3.425181871853366
"""
print("MSE:",mean_squared_error(y_test,y_predict))
print("RMSE:",mean_squared_error(y_test,y_predict) ** 0.5)
print("MAE:",mean_absolute_error(y_test,y_predict))

逻辑回归

逻辑回归是用来做分类算法的,比如一元线性回归,一般形式是$Y=aX+b$,$Y$的取值范围是$[-∞, +∞]$,有这么多取值,怎么进行分类呢?不用担心,伟大的数学家已经为我们找到了一个方法。

也就是把Y的结果带入一个非线性变换的Sigmoid函数中,即可得到[0,1]之间取值范围的数S,S可以把它看成是一个概率值,如果我们设置概率阈值为0.5,那么S大于0.5可以看成是正样本,小于0.5看成是负样本,就可以进行分类了。业界经常用它来预测:客户是否会购买某个商品,借款人是否会违约等等。

Logistic Regression 虽然被称为回归,但其实际上是分类模型,并常用于二分类。其本质是:假设数据服从这个分布,然后使用极大似然估计做参数的估计。

参考链接:NLP-LOVE

Sigmoid 函数

函数公式如下:

image-20250616143820238

函数中t无论取什么值,其结果都在[0,-1]的区间内,回想一下,一个分类问题就有两种答案,一种是“是”,一种是“否”,那0对应着“否”,1对应着“是”,那又有人问了,你这不是[0,1]的区间吗,怎么会只有0和1呢?这个问题问得好,我们假设分类的阈值是0.5,那么超过0.5的归为1分类,低于0.5的归为0分类,阈值是可以自己设定的。

好了,接下来我们把$aX+b$带入t中就得到了逻辑回归的一般模型方程:

$$
H(a,b) = \frac{1}{1 + e^{(aX + b)}}
$$

结果P也可以理解为概率,换句话说概率大于0.5的属于1分类,概率小于0.5的属于0分类,这就达到了分类的目的。

其分布函数和密度函数分别为:

$$
F(x) = P(X\leq{x})=\frac{1}{1 + e^{-(x-u)/\gamma}}
$$

$$
f(x) = F’(X\leq{x})=\frac{e^{-(x-u)/\gamma}}{\gamma(1 + e^{-(x-u)/\gamma})^{2}}
$$

其中, $\mu$ 表示位置参数, $\gamma>0$ 为形状参数,可以看下其图像特征:

image-20250609160632664

Logistic 分布是由其位置和尺度参数定义的连续分布。Logistic 分布的形状与正态分布的形状相似,但是 Logistic 分布的尾部更长,所以我们可以使用 Logistic 分布来建模比正态分布具有更长尾部和更高波峰的数据分布。

在深度学习中常用到的 Sigmoid 函数就是 Logistic 的分布函数在 $\mu=0, \gamma=1$ 的特殊形式。

损失函数

在机器学习领域,总是避免不了谈论损失函数这一概念。

损失函数是用于衡量预测值与实际值的偏离程度,即模型预测的错误程度。

也就是说,这个值越小,认为模型效果越好,举个极端例子,如果预测完全精确,则损失函数值为0。

假设:有 0、1 两个类别,某个样本被分为 1 类的概率为 p,则分为 0 类的概率为 1-p,则每一个样本分类正确的概率为:

image-20250711204140565

对上述公式进行合式:
$$
p^y(1-p)^{1-y}
$$
咱们对损失函数的期望是:

当样本是1类别,模型预测的p越大越好;

当样本是0类别,模型预测的p越小越好;
$$
P(x_2)…P(y_n|x_n) = \prod_{i=1}^{n} p^{y_i}(1-p)^{1-y_i}
$$

取对数转换连乘为连加
$$
\log L = \log\left(\prod_{i=1}^{m}\left[p_{i}^{y_i}\left(1-p_{i}\right)^{1-y_i}\right]\right)
$$

$$
= \sum_{i=1}^{m} \log \left(p_{i}^{y_i}\left(1-p_{i}\right)^{1-y_i}\right)
$$

$$
= \sum_{i=1}^{m}\left[\log\left(p_{i}^{y_i}\right)+\log\left(\left(1-p_{i}\right)^{1-y_i}\right)\right]
$$

(对数性质:$\log(CD) = \log C + \log D$)
$$
= \sum_{i=1}^{m}\left[y_i \log \left(p_{i}\right) + \left(1-y_i\right) \log \left(1-p_{i}\right)\right]
$$
(对数性质:$\log(A^B) = B \log A$)
$$
\log L = \sum_{i=1}^{m} \left[ y_i \log(p_i) + (1-y_i)\log(1-p_i) \right]
$$

于是得到公式:

$$
Loss(L) = -\sum_{i=1}^{m} \left( y_{i}\log(p_{i}) + (1 - y_{i})\log(1 - p_{i}) \right)
$$

其中$$ p_{i} = \operatorname{sigmoid}(w^{T}x + b) $$是逻辑回归的输出结果

  • 损失函数的工作原理:每个样本预测值有A、B两个类别,真实类别对应的位置,概率值域越大越好

image-20250609185456031

逻辑回归API

1
sklearn.linear_model.LogisticRegression(solver='liblinear', penalty=‘l2’, C = 1.0)
  • solver损失函数优化方法:

    • 训练速度:liblinear 对小数据集场景训练速度更快,sag 和 saga 对大数据集更快一些。

    • 正则化:

      • newton-cg、lbfgs、sag、saga 支持 L2 正则化或者没有正则化
      • 2liblinear 和 saga 支持 L1 正则化
  • penalty:正则化的种类

    • L1 正则化或者 L2 正则化
  • C:正则化力度

    • 默认将类别数量少的当做正例
1
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
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

# 1.获取数据
names = ['Sample code number', 'Clump Thickness', 'Uniformity of Cell Size',
'Uniformity of Cell Shape', 'Marginal Adhesion', 'Single Epithelial Cell Size',
'Bare Nuclei', 'Bland Chromatin', 'Normal Nucleoli', 'Mitoses', 'Class']

# 2.基本数据处理
# 2.1 加载数据并指定缺失值标记
data = pd.read_csv(
"https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/breast-cancer-wisconsin.data",
names=names,
na_values='?'
)

# 2.2 缺失值处理 - 删除包含缺失值的行
data = data.dropna()

# 2.3 确定特征值和目标值
x = data.iloc[:, 1:10] # 选取第2到10列作为特征
y = data["Class"] # 选取Class列作为目标值

# 2.4 分割数据集
x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=22)

# 3.特征工程(标准化)
transfer = StandardScaler()
x_train = transfer.fit_transform(x_train)
x_test = transfer.transform(x_test)

# 4.机器学习(逻辑回归)
estimator = LogisticRegression(solver='liblinear', penalty='l2', C=1.0)
estimator.fit(x_train, y_train)

# 5.模型评估
# 5.1 预测测试集
y_predict = estimator.predict(x_test)

# 5.2 计算准确率
accuracy = estimator.score(x_test, y_test)
print("模型准确率:", accuracy)

# 可选:输出预测结果和真实值的对比
print("预测结果示例:", y_predict[:10])
print("真实值示例:", y_test.values[:10])

可以进行多分类吗?

可以的,其实我们可以从二分类问题过度到多分类问题(one vs rest),思路步骤如下:

1.将类型class1看作正样本,其他类型全部看作负样本,然后我们就可以得到样本标记类型为该类型的概率$p_1$。

2.然后再将另外类型class2看作正样本,其他类型全部看作负样本,同理得到$p_2$。

3.以此循环,我们可以得到该待预测样本的标记类型分别为类型class i时的概率$p_i$,最后我们取$p_i$中最大的那个概率对应的样本标记类型作为我们的待预测样本类型。

image-20250616174858019

分类评估方法⭐️

混淆矩阵及其构建

简单根据象限来记忆:TP、FN、FP、TN(从上到下、从左到右都是正、假)

image-20250609202123066

混淆矩阵作用在测试集样本集中:

  1. 真实值是 正例 的样本中,被分类为 正例 的样本数量有多少,这部分样本叫做真正例(TP,True Positive)
  2. 真实值是 正例 的样本中,被分类为 假例 的样本数量有多少,这部分样本叫做伪反例(FN,False Negative)
  3. 真实值是 假例 的样本中,被分类为 正例 的样本数量有多少,这部分样本叫做伪正例(FP,False Positive)
  4. 真实值是 假例 的样本中,被分类为 假例 的样本数量有多少,这部分样本叫做真反例(TN,True Negative)

模型预测正例假例举例:

样本集中有 6 个恶性肿瘤样本,4 个良性肿瘤样本,假设恶性肿瘤为正例,则:

模型 A: 预测对了 3 个恶性肿瘤样本,4 个良性肿瘤样本

  1. 真正例 TP 为:3
  2. 伪反例 FN 为:3
  3. 假正例 FP 为:0
  4. 真反例 TN:4
  5. 精准率:3/(3+0) = 100%
  6. 召回率:3/(3+3)=50%
  7. F1-score:(2*3)/(2*3+3+0)=67%

模型 B: 预测对了 6 个恶性肿瘤样本,1个良性肿瘤样本

  1. 真正例 TP 为:6
  2. 伪反例 FN 为:0
  3. 假正例 FP 为:3
  4. 真反例 TN:1
  5. 精准率:6/(6+3) = 67%
  6. 召回率:6/(6+0)= 100%
  7. F1-score:(2*6)/(2*6+0+3)=80%

TP+FN+FP+TN = 总样本数量

Precision(精确率)

精确率也叫做查准率,指的是对正例样本的预测准确率。

比如:我们把恶性肿瘤当做正例样本,则我们就需要知道$模型对恶性肿瘤的预测准确率。

公式:
$$
P = \frac{TP}{TP + FP}
$$

Recall(召回率)

召回率也叫做查全率,指的是预测为真正例样本占所有真实正例样本的比重。

例如:我们把恶性肿瘤当做正例样本,则我们想知道模型是否能把所有的恶性肿瘤患者都预测出来

公式:
$$
P = \frac{TP}{TP + FN}
$$

F1-score

如果我们对模型的精度、召回率都有要求,希望知道模型在这两个评估方向的综合预测能力如何?则可以使用 F1-score 指标。

$$
F1 = \frac{2TP}{2TP + FN + FP} = \frac{2 \cdot \text{Precision} \cdot \text{Recall}}{\text{Precision} + \text{Recall}}
$$

代码

1
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
# 构建混淆矩阵
from sklearn.metrics import confusion_matrix, precision_score, recall_score, f1_score
import pandas as pd

# 构建数据:真实值,预测值
y_true = ['恶性', '恶性', '恶性', '恶性', '恶性', '恶性', '良性', '良性', '良性', '良性']
# 模型A的预测值
y_pre_A = ['恶性', '恶性', '恶性', '良性', '良性', '良性', '良性', '良性', '良性', '良性']
# 模型B的预测值
y_pre_B = ['恶性', '恶性', '恶性', '恶性', '恶性', '恶性', '恶性', '恶性', '恶性', '良性']

# 混淆矩阵
# 传入真实值和预测值以及标签(正负样本)
print(confusion_matrix(y_true, y_pre_A, labels=['恶性', '良性']))

print(confusion_matrix(y_true, y_pre_B, labels=['恶性', '良性']))

# 精确率
print("-----------------精确率-------------------------------")
print(precision_score(y_true, y_pre_A, pos_label='恶性'))
print(precision_score(y_true, y_pre_B, pos_label='恶性'))

# # 召回率
print("-----------------召回率-------------------------------")
print(recall_score(y_true, y_pre_A, pos_label='恶性'))
print(recall_score(y_true, y_pre_B, pos_label='恶性'))

print("-----------------f1-score-------------------------------")
print(f1_score(y_true, y_pre_A, pos_label='恶性'))
print(f1_score(y_true, y_pre_B, pos_label='恶性'))

ROC曲线和AUC指标

TPR (True Positive Rate):正样本中被预测为正样本的概率 (召回率)

FPR (False Positive Rate):负样本中被预测为正样本的概率

AUC 是 ROC 曲线下面的面积,该值越大,则模型的辨别能力就越强

AUC 值主要评估模型对正例样本、负例样本的辨别能力

image-20250711213215389

TPR越大越好;FPR越小越好

ROC取下越往左上,越好,线下面积越大。

电信客户流失预测

1
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# 导入必要的库
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split, GridSearchCV, StratifiedKFold
from sklearn import linear_model
import sklearn.metrics as metrics
from sklearn.metrics import roc_auc_score

# 设置绘图风格
%matplotlib inline
sns.set(style="whitegrid")

# 修复中文显示异常Mac
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
# 如果是Mac本, 不支持SimHei的时候, 可以修改为 'Microsoft YaHei' 或者 'Arial Unicode MS'\n
plt.rcParams['axes.unicode_minus'] = False

# 1. 数据加载与初步探索
churn = pd.read_csv('data/churn.csv')
print("数据形状:", churn.shape)
print("\n数据信息:")
churn.info()
print("\n前5行数据:")
print(churn.head())

# 2. 数据预处理
# 将类别型变量进行one-hot编码
churn = pd.get_dummies(churn)

# 数据整理
churn.drop(['Churn_No', 'gender_Male'], axis=1, inplace=True)

# 统一列名为小写
churn.columns = churn.columns.str.lower()

# 重命名目标变量
churn = churn.rename(columns={'churn_yes': 'flag'})

# 查看目标变量分布
print("\n目标变量分布:")
print(churn.flag.value_counts())
print("\n目标变量比例:")
print(churn.flag.value_counts(normalize=True))

# 3. 数据分析
# 按标签分组查看均值
summary = churn.groupby('flag').mean()
print("\n按流失分组后的均值:")
print(summary)

# 可视化分析
plt.figure(figsize=(10, 6))
sns.countplot(y='contract_month', hue='flag', data=churn)
plt.title('按月合约客户的流失情况')
plt.show()

# 4. 特征选择与建模
# 选择特征和目标变量
y = churn['flag']
x = churn[['contract_month', 'internet_other', 'streamingtv']]

# 划分训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(
x, y, test_size=0.3, random_state=100)

# 4.1 基础逻辑回归模型
lr = linear_model.LogisticRegression()
lr.fit(x_train, y_train)

# 模型评估
y_pred_train = lr.predict(x_train)
y_pred_test = lr.predict(x_test)

print("\n基础模型训练集准确率:", metrics.accuracy_score(y_train, y_pred_train))
print("基础模型测试集准确率:", metrics.accuracy_score(y_test, y_pred_test))
print("基础模型AUC分数:", roc_auc_score(y_test, y_pred_test))

# 4.2 考虑类别不平衡的模型
lr_balanced = linear_model.LogisticRegression(class_weight='balanced')
lr_balanced.fit(x_train, y_train)

y_pred_balanced = lr_balanced.predict(x_test)
print("\n平衡模型测试集准确率:", metrics.accuracy_score(y_test, y_pred_balanced))
print("平衡模型AUC分数:", roc_auc_score(y_test, y_pred_balanced))

# 4.3 网格搜索优化参数
kfold = StratifiedKFold(n_splits=5, shuffle=True)
lr = linear_model.LogisticRegression()

param_grid = {
'solver': ['newton-cg', 'lbfgs', 'liblinear'],
'C': [0.001, 0.01, 1, 10, 100],
'class_weight': ['balanced']
}

search = GridSearchCV(lr, param_grid, cv=kfold)
best_lr = search.fit(x_train, y_train)

print("\n最佳参数组合:", best_lr.best_params_)

# 使用最佳模型
y_pred_best = best_lr.predict(x_test)
print("\n优化模型测试集准确率:", metrics.accuracy_score(y_test, y_pred_best))
print("优化模型AUC分数:", roc_auc_score(y_test, y_pred_best))

# 查看模型系数
print("\n模型系数:")
print("contract_month:", best_lr.best_estimator_.coef_[0][0])
print("internet_other:", best_lr.best_estimator_.coef_[0][1])
print("streamingtv:", best_lr.best_estimator_.coef_[0][2])

# 5. 模型评估报告
print("\n分类报告:")
print(metrics.classification_report(y_test, y_pred_best))

# 绘制ROC曲线
y_pred_prob = best_lr.predict_proba(x_test)[:, 1]
fpr, tpr, thresholds = metrics.roc_curve(y_test, y_pred_prob)

plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, label='ROC曲线 (AUC = %0.2f)' % roc_auc_score(y_test, y_pred_best))
plt.plot([0, 1], [0, 1], 'k--')
plt.xlabel('假阳性率')
plt.ylabel('真阳性率')
plt.title('ROC曲线')
plt.legend(loc="lower right")
plt.show()

决策树

参考链接:https://liamjohnson-w.github.io/2023/08/09/2023.08.09/

决策树算法是一种监督学习算法,英文是Decision tree。

决策树是一个类似于流程图的树结构:

  • 其中,每个内部结点表示一个特征或属性。
  • 每个树叶结点代表一个标签(分类)
  • 树的最顶层是根结点。使用决策树分类时就是将实例分配到叶节点的类中。该叶节点所属的类就是该节点的标签(分类)

决策树思想的来源非常朴素,试想每个人的大脑都有类似于if-else这样的逻辑判断,这其中的if表示的是条件,if之后的then就是一种选择或决策。程序设计中的条件分支结构就是if-then结构,最早的决策树就是利用这类结构分割数据的一种分类学习方法。

构建决策树

构建决策树包括三个步骤:

  • 特征选择:选取有较强分类能力的特征。
  • 决策树生成:根据选择的特征生成决策树。典型的算法有ID3、C4.5、CART,它们生成决策树过程相似,ID3是采用信息增益作为特征选择度量,而C4.5采用信息增益率、CART基尼指数
  • 决策树剪枝:决策树也易过拟合,采用剪枝的方法缓解过拟合。剪枝原因是决策树生成算法生成的树对训练数据的预测很准确,但是对于未知数据分类很差,这就产生了过拟合的现象。
名称 提出时间 分支方式 特点
ID3 1975 信息增益 1.ID3只能对离散属性的数据集构成决策树 2.倾向于选择取值较多的属性
C4.5 1993 信息增益率 1.缓解了ID3分支过程中总喜欢偏向选择值较多的属性 2.可处理连续数值型属性,也增加了对缺失值的处理方法 3.只适合于能够驻留于内存的数据集,大数据集无能为力
CART 1984 基尼指数 1.可以进行分类和回归,可处理离散属性,也可以处理连续属性 2.采用基尼指数,计算量减小 3.一定是二叉树

Notice

信息增益(ID3)、信息增益率值越大(C4.5),则说明优先选择该特征。

基尼指数值越小(cart),则说明优先选择该特征。

ID3算法构建决策树⭐️

ID3 树是基于信息增益构建的决策树.

使用信息增益最大的特征作为决策树的一个分裂节点。

信息熵

ID3 树是基于信息增益构建的决策树.

定义

  • 熵在信息论中代表随机变量不确定度的度量。
  • 熵越大,数据的不确定性度越高
  • 熵越小,数据的不确定性越低

公式

$$
\large
H = -\sum_{i=1}^{k}p_i\log_{b}(p_i)
$$

参数说明

  • $b=2$(默认):单位为 比特(bits)(信息论常用)。
  • $b=e$(自然对数):单位为 纳特(nats)。
  • $b=10$:单位为 哈特莱(hartleys)。

例子1:假如有三个类别,分别占比为:{1/3,1/3,1/3},信息熵计算结果为:

$H=-\frac{1}{3}\log_{2}(\frac{1}{3})-\frac{1}{3}\log_{2}(\frac{1}{3})-\frac{1}{3}\log_{2}(\frac{1}{3})=1.0986$

例子2:假如有三个类别,分别占比为:{1/10,2/10,7/10},信息熵计算结果为:

$H=-\frac{1}{10}\log_{2}(\frac{1}{10})-\frac{2}{10}\log_{2}(\frac{2}{10})-\frac{7}{10}\log_{2}(\frac{7}{10})=0.8018$

熵越大,表示整个系统不确定性越大,越随机,反之确定性越强。

例子3:假如有三个类别,分别占比为:{1,0,0},信息熵计算结果为:

$H=-1\log_{2}(1)=0$

信息增益

特征$A$对 数据集D的信息增益$g(D,A)$,定义为集合$D$的熵$H(D)$与特征A给定条件下D的熵$H(D|A)$之差。即

$$
\large
g(D,A)=H(D)-H(D|A)
$$

根据信息增益选择特征方法是:对训练数据集D,计算其每个特征的信息增益,并比较它们的大小,并选择信息增益最大的特征进行划分。表示由于特征$A$而使得对数据D的分类不确定性减少的程度。

算法:

设训练数据集为D,$\mid D\mid$表示其样本个数。设有$K$个类$C_k$,$k=1,2,\cdots,K$,$\mid C_k\mid$为属于类$C_k$的样本个数,$\sum\limits_{k=1}^{K}=\mid{D}\mid$。设特征A有$n$个不同取值${a_1, a_2, \cdots,a_n}$,根据特征A的取值将D划分为$n$个子集$D_1, D_2, \cdots,D_n$,$\mid D_i\mid$为$D_i$样本个数,$\sum\limits_{i=1}^n\mid D_i\mid=\mid D\mid$。子集中属于类$C_k$的样本集合为$D_{ik}$,即$D_{ik}=D_i\bigcap C_k$,$\mid D_{ik}\mid$为$D_{ik}$的样本个数。信息增益算法如下:

  • 输入:训练数据集$D$和特征$A$;

  • 输出:特征$A$对训练数据集$D$的信息增益$g(D,A)$

  • (1) 计算数据集$D$的经验熵$H(D)$

    $H(D)=-\sum\limits_{k=1}^{K}\frac{\mid C_k\mid}{\mid D\mid}\log_2\frac{\mid C_k\mid}{\mid D\mid}$

  • (2) 计算特征$A$对数据集$D$的经验条件熵$H(D\mid A)$

    $H(D\mid A)=\sum\limits_{i=1}^{n}\frac{\mid D_i\mid}{\mid D\mid}H(D_i)=-\sum\limits_{i=1}^{n}\frac{\mid D_i\mid}{\mid D\mid}\sum_\limits{k=1}^{K}\frac{\mid D_{ik}\mid}{\mid D_i\mid}\log_2\frac{\mid D_{ik}\mid}{\mid D_i\mid}$

  • (3) 计算信息增益

    $g(D,A)=H(D)-H(D|A)$

信息增益计算案例

例子:已知6个样本,根据特征a:

特征a 目标值
α A
α A
β B
α A
β B
α B

$α$ 部分对应的目标值为: $AAAB$

$β $部分对应的目标值为:$BB$

  • 条件为$α$熵:$-\frac{3}{4} * log_{2}(\frac{3}{4}) - \frac{1}{4} * log_{2}(\frac{1}{4}) = 0.81 $
  • 条件为$β$熵:$ -(\frac{2}{2} * log_{2}(\frac{2}{2})) = 0$
  • 条件熵:$α $部分占了 $\frac{4}{6}$,$β$ 部分 占了 $\frac{2}{6}$
    • $(\frac{4}{6}) * 0.81 + (\frac{2}{6})* 0 = 0.54$
  • 熵:$-\frac{3}{6} * log_{2}(\frac{3}{6}) – \frac{3}{6} * log_{2}(\frac{3}{6}) = 1$
  • 信息增益:熵 – 条件熵: $1.0 – 0.54 = 0.46$

ID3树构建流程

构建流程:

  1. 计算每个特征的信息增益
  2. 使用信息增益最大的特征将数据集 $S $拆分为子集
  3. 使用该特征(信息增益最大的特征)作为决策树的一个节点
  4. 使用剩余特征对子集重复上述过程

案例:

已知:某一个论坛客户流失率数据

需求:考察性别、活跃度特征哪一个特征对流失率的影响更大

image-20250610200825130

分析:

15条样本:5正样本、10个负样本

  • 计算熵

$$ H(D) = \left(-\frac{5}{15}\log_{2}\frac{5}{15}\right) + \left(-\frac{10}{15}\log_{2}\frac{10}{15}\right) = 0.9812 $$

  • 计算性别条件熵(a=”性别”):

$$
\begin{aligned}
H(D, \text{性别}) &= \sum_{v=1}^{n} \frac{D^{v}}{D} H(D^{v})
\end{aligned}
$$

$$= \left(\frac{8}{15}\right)\left(-\frac{3}{8} \log_{2} \frac{3}{8} - \frac{5}{8} \log_{2} \frac{5}{8}\right) + \left(\frac{7}{15}\right)\left(-\frac{2}{7} \log_{2} \frac{2}{7} - \frac{5}{7} \log_{2} \frac{5}{7}\right)$$

  • 计算性别信息增益(a=”性别”)

$$
\begin{aligned}
g(D, \alpha) &= H(D) - H(D \mid \alpha) \
&= 0.9812 - \left[
\left(\frac{8}{15}\right)\left(-\frac{3}{8}\log_{2}\frac{3}{8} - \frac{5}{8}\log_{2}\frac{5}{8}\right)
+ \left(\frac{7}{15}\right)\left(-\frac{2}{7}\log_{2}\frac{2}{7} - \frac{5}{7}\log_{2}\frac{5}{7}\right)
\right] \
&= 0.0064
\end{aligned}
$$

  • 计算活跃度条件熵(a=“活跃度”)

$$
\begin{aligned}
H(D, \text{活跃度}) &= \sum_{v=1}^{n} \frac{D^{v}}{D} H(D^{v}) \
&= \left(\frac{6}{15}\right)(0) + \left(\frac{5}{15}\right)\left(-\frac{1}{5}\log_{2}\frac{1}{5} - \frac{4}{5}\log_{2}\frac{4}{5}\right) + \left(\frac{4}{15}\right)(0)
\end{aligned}
$$

  • 计算活跃度信息增益(a=活跃度”)

$$
\begin{aligned}
g(D, \alpha) &= H(D) - H(D \mid \alpha) \
&= 0.9812 - \left[
\left(\frac{6}{15}\right)(0) +
\left(\frac{5}{15}\right)\left(-\frac{1}{5}\log_{2}\frac{1}{5} - \frac{4}{5}\log_{2}\frac{4}{5}\right) +
\left(\frac{4}{15}\right)(0)
\right] \
&= 0.6776
\end{aligned}
$$

结论:活跃度的信息增益比性别的信息增益大,对用户流失的影响比性别大。

image-20250713083502968

C4.5算法构建决策树

信息增益率

根据信息增益率,选择特征的信息增益率大作为分裂特征。

$$
\begin{aligned}
\text{Gain_Ratio}(D, a) &= \frac{\text{Gain}(D, a)}{IV(a)} \
\end{aligned}
$$

$$
\begin{aligned} IV(a) &= -\sum_{v=1}^{V} \frac{D^{v}}{D} \log \frac{D^{v}}{D} \end{aligned}
$$

  1. Gain_Ratio 表示信息增益率
  2. IV 表示分裂信息、特征熵
  3. 信息增益率=特征的信息增益 ➗ 特征熵

信息增益比本质: 是在信息增益的基础之上乘上一个惩罚参数。

特征个数较多时,惩罚参数较小;特征个数较少时,惩罚参数较大

惩罚参数:数据集D以特征A作为随机变量的熵的倒数。

C4.5决策树构建流程

image-20250610202930336

特征a的信息增益率:

  1. 信息增益

$$
\begin{aligned}
&\left(-\frac{3}{6}\log_{2}\frac{3}{6} - \frac{3}{6}\log_{2}\frac{3}{6}\right) \
&- \left[ \frac{4}{6}\left(-\frac{3}{4}\log_{2}\frac{3}{4} - \frac{1}{4}\log_{2}\frac{1}{4}\right) + \frac{2}{6}(0) \right] \
&= 1 - 0.54 = 0.46
\end{aligned}
$$

  1. IV信息熵

$$
-\frac{4}{6}\log_{2}\frac{4}{6} - \frac{2}{6}\log_{2}\frac{2}{6} = 0.92
$$

  1. 信息增益率

$$
\frac{0.46}{0.92} = 0.5
$$

特征b的信息增益率:

  1. 信息增益

$$
-\frac{3}{6}\log_{2}\frac{3}{6} - \frac{3}{6}\log_{2}\frac{3}{6} - 6 \times 0 = 1
$$

  1. IV信息熵

$$
-\frac{1}{6}\log_{2}\frac{1}{6} \times 6 = 2.58
$$

  1. 信息增益率

$$
\frac{1}{2.58} = 0.39
$$

结论:特征a的信息增益率大于特征b的信息增益率,根据信息增益率,应该选择特征a作为分裂特征

image-20250713150757361

CART算法构建决策树⭐️

Cart树简介

Cart模型是一种决策树模型,它即可以用于分类,也可以用于回归。

分类和回归树模型采用不同的最优化策略。Cart回归树使用平方误差最小化策略,Cart分类生成树采用的基尼指数最小化策略。

基尼指数计算案例

image-20250610203514367

是否有房

计算过程如下:根据是否有房将目标值划分为两部分:

  1. 计算有房子的基尼值: 有房子有 1、4、7 共计三个样本,对应:3个no、0个yes

    $G i n i(\text {是否有房,yes })=1-\left(\frac{0}{3}\right)^{2}-\left(\frac{3}{3}\right)^{2}=0$

  2. 计算无房子的基尼值:无房子有 2、3、5、6、8、9、10 共七个样本,对应:4个no、3个yes

    $\operatorname{Gini}(\text {是否有房,no })=1-\left(\frac{3}{7}\right)^{2}-\left(\frac{4}{7}\right)^{2}=0.4898$

  3. 计算基尼指数:第一部分样本数量占了总样本的 3/10、第二部分样本数量占了总样本的 7/10:

    $\operatorname{Gini_{-}} i n \operatorname{dex}(D, \text { 是否有房 })=\frac{7}{10} * 0.4898+\frac{3}{10} * 0=0.343$

image-20250713083702157

婚姻状况

  1. 计算 {married} 和 {single,divorced} 情况下的基尼指数:

    结婚的基尼值,有 2、4、6、9 共 4 个样本,并且对应目标值全部为 no:

    $\operatorname{Gini_index}(D,\text)=0$

    不结婚的基尼值,有 1、3、5、7、8、10 共 6 个样本,并且对应 3 个 no,3 个 yes:

    $\operatorname{Gini_index}(D, \text { {single,divorced} })=1-\left(\frac{3}{6}\right)^{2}-\left(\frac{3}{6}\right)^{2}=0.5$

    以 married 作为分裂点的基尼指数:

    $\operatorname{Gini_index}(D, \text { married })=\frac{4}{10} * 0+\frac{6}{10} *\left[1-\left(\frac{3}{6}\right)^{2}-\left(\frac{3}{6}\right)^{2}\right]=0.3$

  2. 计算 {single} | {married,divorced} 情况下的基尼指数

    $\operatorname{Gini_index}(D,\text{婚姻状况})=\frac{4}{10} * 0.5+\frac{6}{10} *\left[1-\left(\frac{1}{6}\right)^{2}-\left(\frac{5}{6}\right)^{2}\right]=0.367$

  3. 计算 {divorced} | {single,married} 情况下基尼指数

    $\operatorname{Gini_index}(D, \text { 婚姻状况 })=\frac{2}{10} * 0.5+\frac{8}{10} *\left[1-\left(\frac{2}{8}\right)^{2}-\left(\frac{6}{8}\right)^{2}\right]=0.4$

  4. 最终:该特征的基尼值为 0.3,并且预选分裂点为:{married} 和 {single,divorced}

image-20250713083647358

年收入

先将数值型属性升序排列,以相邻中间值作为待确定分裂点:

image-20230905153041427

以年收入 65 将样本分为两部分,计算基尼指数:

$节点为65时:{年收入}=\frac{1}{10} * 0 + \frac{9}{10} *\left[1-\left(\frac{6}{9}\right)^{2}-\left(\frac{3}{9}\right)^{2}\right]=0.4$

以此类推计算所有分割点的基尼指数,我们发现最小的基尼指数为 0.3。

此时,我们发现:

  1. 以是否有房作为分裂点的基尼指数为:0.343
  2. 以婚姻状况为分裂特征、以 married 作为分裂点的基尼指数为:0.3
  3. 以年收入作为分裂特征、以 97.5 作为分裂点的的基尼指数为:0.3

最小基尼指数有两个分裂点,我们随机选择一个即可,假设婚姻状况,则可确定决策树如下:

image-20250610203859367

Cart案例(泰坦尼克号生存案例

API

1
class sklearn.tree.DecisionTreeClassifier(criterion=’gini’,max_depth=None, random_state=None)
  • criterion
    • 特征选择标准
    • “gini”或者”entropy”,前者代表基尼系数,后者代表信息增益。一默认”gini”,即CART算法。
  • min_samples_split
    • 内部节点再划分所需最小样本数
    • 这个值限制了子树继续划分的条件,如果某节点的样本数少于min_samples_split,则不会继续再尝试选择最优特征来进行划分。 默认是2.如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。我之前的一个项目例子,有大概10万样本,建立决策树时,我选择了min_samples_split=10。可以作为参考。
  • min_samples_leaf
    • 叶子节点最少样本数
    • 这个值限制了叶子节点最少的样本数,如果某叶子节点数目小于样本数,则会和兄弟节点一起被剪枝。 默认是1,可以输入最少的样本数的整数,或者最少样本数占样本总数的百分比。如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。之前的10万样本项目使用min_samples_leaf的值为5,仅供参考。
  • max_depth
    • 决策树最大深度
    • 决策树的最大深度,默认可以不输入,如果不输入的话,决策树在建立子树的时候不会限制子树的深度。一般来说,数据少或者特征少的时候可以不管这个值。如果模型样本量多,特征也多的情况下,推荐限制这个最大深度,具体的取值取决于数据的分布。常用的可以取值10-100之间
  • random_state
    • 随机数种子

image-20250713200345623

1
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
import pandas as pd
import numpy as np
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor, BaseDecisionTree, plot_tree
from sklearn.preprocessing import MinMaxScaler, StandardScaler
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from sklearn.metrics import accuracy_score

plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
# 如果是Mac本, 不支持SimHei的时候, 可以修改为 'Microsoft YaHei' 或者 'Arial Unicode MS'\n
plt.rcParams['axes.unicode_minus'] = False

# 读取数据
df = pd.read_csv('./train.csv', encoding='utf-8')
# 查看数据情况,发现有空值 于是需要用平均值填充
df['Age'].fillna(df['Age'].mean(), inplace=True)

x = df[["Pclass", "Age", "Sex"]]
x = pd.get_dummies(x).rename(columns={'Sex_female':'Sex'})[["Pclass", "Age", "Sex"]]
y = df[['Survived']]
print(x.head(5))
print(y.head(5))

# 有了x,y就可以划分训练集和测试集了
x_train, x_test, y_train, y_test= train_test_split(x, y, test_size=0.3, random_state=12)
es_model = DecisionTreeClassifier(criterion='entropy', max_depth=10)
es_model.fit(x_train, y_train)

y_predict = es_model.predict(x_test)

print("准确率:", accuracy_score(y_test, y_predict))

# 决策树可视化
fig, ax = plt.subplots(figsize=(50, 50))
plot_tree(es_model,
ax=ax,
max_depth=3,
filled=True,
feature_names=['Age', 'Pclass_1', 'Pclass_2', 'Pclass_3', 'Sex_female', 'Sex_male'],
class_names=['生存', '不生存'])
plt.savefig('b.png', dpi=100)

Cart回归决策树⭐️

回归决策树构建原理

CART 回归树和 CART 分类树的不同之处在于:

  1. CART 分类树预测输出的是一个离散值,CART 回归树预测输出的是一个连续值
  2. CART 分类树使用基尼指数作为划分、构建树的依据,CART 回归树使用平方损失
  3. 分类树使用叶子节点里出现更多次数的类别作为预测类别,回归树则采用叶子节点里均值作为预测输出

CART 回归树构建:

$$
\operatorname{Loss}(y, f(x))=(f(x)-y)^{2}
$$

例子:

假设:数据集只有 1 个特征 x, 目标值值为 y,如下图所示:

x 1 2 3 4 5 6 7 8 9 10
y 5.56 5.7 5.91 6.4 6.8 7.05 8.9 8.7 9 9.05

由于只有 1 个特征,所以只需要选择该特征的最优划分点,并不需要计算其他特征。

  1. 先将特征 x 的值排序,并取相邻元素均值作为待划分点,如下图所示:

    s 1.5 2.5 3.5 4.5 5.5 6.5 7.5 8.5 9.5
  2. 计算每一个划分点的平方损失,例如:1.5 的平方损失计算过程为:

    R1 为 小于 1.5 的样本个数,样本数量为:1,其输出值为:5.56

    $R_1 =5.56$

    R2 为 大于 1.5 的样本个数,样本数量为:9 ,其输出值为:

    $R_2=(5.7+5.91+6.4+6.8+7.05+8.9+8.7+9+9.05) / 9=7.50$

    该划分点的平方损失:

    $L(1.5)=(5.56-5.56)^{2}+\left[(5.7-7.5)^{2}+(5.91-7.5)^{2}+\ldots+(9.05-7.5)^{2}\right]=0+15.72=15.72$

  3. 以此方式计算 2.5、3.5… 等划分点的平方损失,结果如下所示:

    s 1.5 2.5 3.5 4.5 5.5 6.5 7.5 8.5 9.5
    m(s) 15.72 12.07 8.36 5.78 3.91 1.93 8.01 11.73 15.74
  4. 当划分点 s=6.5 时,m(s) 最小。因此,第一个划分变量:特征为 X, 切分点为 6.5,即:j=x, s=6.5

    image-20220305183857165

  5. 对左子树的 6 个结点计算每个划分点的平方式损失,找出最优划分点:

    x 1 2 3 4 5 6
    y 5.56 5.7 5.91 6.4 6.8 7.05
    s 1.5 2.5 3.5 4.5 5.5
    c1 5.56 5.63 5.72 5.89 6.07
    c2 6.37 6.54 6.75 6.93 7.05
    s 1.5 2.5 3.5 4.5 5.5
    m(s) 1.3087 0.754 0.2771 0.4368 1.0644
  6. s=3.5时,m(s) 最小,所以左子树继续以 3.5 进行分裂:

  7. 假设在生成3个区域 之后停止划分,以上就是回归树。每一个叶子结点的输出为:挂在该结点上的所有样本均值。

x 1 2 3 4 5 6 7 8 9 10
y 5.56 5.7 5.91 6.4 6.8 7.05 8.9 8.7 9 9.05

1号样本真实值 5.56 预测结果:5.72

2号样本真实值是 5.7 预测结果:5.72

3 号样本真实值是 5.91 预测结果 5.72

CART 回归树构建过程如下:

  1. 选择第一个特征,将该特征的值进行排序,取相邻点计算均值作为待划分点
  2. 根据所有划分点,将数据集分成两部分:R1、R2
  3. R1 和 R2 两部分的平方损失相加作为该切分点平方损失
  4. 取最小的平方损失的划分点,作为当前特征的划分点
  5. 以此计算其他特征的最优划分点、以及该划分点对应的损失值
  6. 在所有的特征的划分点中,选择出最小平方损失的划分点,作为当前树的分裂点

决策树剪枝⭐️

什么是剪枝?

剪枝 (pruning)是决策树学习算法对付 过拟合 的主要手段。

在决策树学习中,为了尽可能正确分类训练样本,结点划分过程将不断重复,有时会造成决策树分支过多,这时就可能因训练样本学得”太好”了,以致于把训练集自身的一些特点当作所有数据都具有的一般性质而导致过拟合。因此,可通过主动去掉一些分支来降低过拟合的风险。

剪枝是指将一颗子树的子节点全部删掉,利用叶子节点替换子树(实质上是后剪枝技术),也可以(假定当前对以root为根的子树进行剪枝)只保留根节点本身而删除所有的叶子,以下图为例:

image-20250610170109157

常见减枝方法

决策树剪枝的基本策略有”预剪枝” (pre-pruning)和”后剪枝”(post- pruning) 。

  1. 预剪枝是指在决策树生成过程中,对每个结点在划分前先进行估计,若当前结点的划分不能带来决策树泛化性能提升,则停止划分并将当前结点标记为叶结点;
  2. 后剪枝则是先从训练集生成一棵完整的决策树,然后自底向上地对非叶结点进行考察,若将该结点对应的子树替换为叶结点能带来决策树泛化性能提升,则将该子树替换为叶结点。

剪枝方法对比

预剪枝优点:

  • 预剪枝使决策树的很多分支没有展开,不单降低了过拟合风险,还显著减少了决策树的训练、测试时间开销

预剪枝缺点:

  • 有些分支的当前划分虽不能提升泛化性能,甚至会导致泛化性能降低,但在其基础上进行的后续划分却有可能导致性能的显著提高
  • 预剪枝决策树也带来了欠拟合的风险

后剪枝优点:

  • 比预剪枝保留了更多的分支。一般情况下,后剪枝决策树的欠拟合风险很小,泛化性能往往优于预剪枝

后剪枝缺点:

  • 但后剪枝过程是在生成完全决策树之后进行的,并且要自底向上地对树中所有非叶子节点进行逐一考察,因此在训练时间开销比未剪枝的决策树和预剪枝的决策树都要大得多。

预剪枝 (pre-pruning)举例

  1. 假设: 当前树只有一个结点, 即编号为1的结点. 此时, 所有的样本预测类别为: 其类别标记为训练样例数最多的类别,假设我们将这个叶结点标记为 “好瓜”。此时, 在验证集上所有的样本都会被预测为 “好瓜”, 此时的准确率为: 3/7

  2. 如果进行此次分裂, 则树的深度为 2, 有三个分支. 在用属性”脐部”划分之后,上图中的结点2、3、4分别包含编号为 {1,2,3, 14}、 {6,7, 15, 17}、 {10, 16} 的训练样例,因此这 3 个结点分别被标记为叶结点”好瓜”、 “好瓜”、 “坏瓜”。此时, 在验证集上 4、5、8、11、12 样本预测正确,准确率为: 5/7。很显然, 通过此次分裂准确率有所提升, 值得分裂.

  3. 接下来,对结点2进行划分,基于信息增益准则将挑选出划分属性”色泽”。然而,在使用”色泽”划分后,编号为 {5} 的验证集样本分类结果会由正确转为错误,使得验证集精度下降为 57.1%。于是,预剪枝策略将禁止结点2被划分。

  4. 对结点3,最优划分属性为”根蒂”,划分后验证集精度仍为 5/7. 这个 划分不能提升验证集精度,于是,预剪枝策略禁止结点3被划分。

  5. 对结点4,其所含训练样例己属于同一类,不再进行划分.

于是,基于预剪枝策略从上表数据所生成的决策树如上图所示,其验证集精度为 71.4%. 这是一棵仅有一层划分的决策树。

image-20250610204200236后剪枝(post- pruning)举例

后剪枝先从训练集生成一棵完整决策树,继续使用上面的案例,从前面计算,我们知前面构造的决策树的验证集精度为42.9%。

image-20230905171420720

  1. 首先考察结点6,若将其领衔的分支剪除则相当于把6替换为叶结点。替换后的叶结点包含编号为 {7, 15} 的训练样本,于是该叶结点的类别标记为”好瓜”, 此时决策树的验证集精度提高至 57.1%。

image-20250610204247674

  1. 然后考察结点5,若将其领衔的子树替换为叶结点,则替换后的叶结点包含编号为 {6,7,15}的训练样例,叶结点类别标记为”好瓜’;此时决策树验证集精度仍为 57.1%. 于是,可以不进行剪枝.
  2. 对结点2,若将其领衔的子树替换为叶结点,则替换后的叶结点包含编号 为 {1, 2, 3, 14} 的训练样例,叶结点标记为”好瓜”此时决策树的验证集精度提高至 71.4%. 于是,后剪枝策略决定剪枝.
  3. 对结点3和1,若将其领衔的子树替换为叶结点,则所得决策树的验证集 精度分别为 71.4% 与 42.9%,均未得到提高,于是它们被保留。
  4. 最终, 基于后剪枝策略生成的决策树如上图所示, 其验证集精度为 71.4%。

集成学习

集成学习是什么?

image-20250610171858319

集成学习是机器学习中的一种思想,它通过多个模型的组合形成一个精度更高的模型,参与组合的模型成为弱学习器(基学习器)。训练时,使用训练集依次训练出这些弱学习器,对未知的样本进行预测时,使用这些弱学习器联合进行预测。

image-20250610171937011

传统机器学习算法 (例如:决策树,逻辑回归等) 的目标都是寻找一个最优分类器尽可能的将训练数据分开。集成学习 (Ensemble Learning) 算法的基本思想就是将多个分类器组合,从而实现一个预测效果更好的集成分类器。集成算法可以说从一方面验证了中国的一句老话:三个臭皮匠,赛过诸葛亮.

集成学习通过建立几个模型来解决单一预测问题。它的工作原理是 生成多个分类器/模型,各自独立地学习和作出预测。这些预测最后结合成组合预测,因此优于任何一个单分类的做出预测

集成学习算法一般分为:bagging和boosting。

Bagging集成算法

Baggging 框架通过有放回的抽样产生不同的训练集,从而训练具有差异性的弱学习器,然后通过平权投票、多数表决的方式决定预测结果。

image-20250610172549709

Boosting 集成算法

Boosting 体现了提升思想,每一个训练器重点关注前一个训练器不足的地方进行训练,通过加权投票的方式,得出预测结果。

image-20250610172643438

Boosting是一组可将弱学习器升为强学习器算法。这类算法的工作机制类似:

1.先从初始训练集训练出一个基学习器

2.在根据基学习器的表现对训练样本分布进行调整,使得先前基学习器做错的训练样本在后续得到最大的关注。

3.然后基于调整后的样本分布来训练下一个基学习器;

4.如此重复进行,直至基学习器数目达到实现指定的值T为止。

5.再将这T个基学习器进行加权结合得到集成学习器。

简而言之:每新加入一个弱学习器,整体能力就会得到提升

Bagging 与 Boosting区别

下面提到的四种算法中,只有随机森林算法是基于Bagging,其余都是基于Boosting

区别一:数据方面

  • Bagging:有放回采样
  • Boosting:全部数据集, 重点关注前一个弱学习器不足

区别二:投票方面

  • Bagging:平权投票
  • Boosting:加权投票

区别三:学习顺序

  • Bagging的学习是并行的,每个学习器没有依赖关系
  • Boosting学习是串行,学习有先后顺序

随机森林

随机森林是基于 Bagging 思想实现的一种集成学习算法,它采用决策树模型为每一个基学习器。其构造过程:

  1. 训练:
    1. 从原来的N个训练样本中有放回地随机抽取m个样本(包括可能重复样本)
    2. 随机挑选 n 个特征(n 小于总特征数量)
  2. 预测:平权投票,多数表决输出预测结果

具体来讲就是每次从原来的N个训练样本中有放回地随机抽取m个样本(包括可能重复样本)。

然后,从候选的特征中随机抽取k个特征,作为当前节点下决策的备选特征,从这些特征中选择最好地划分训练样本的特征。用每个样本集作为训练样本构造决策树。单个决策树在产生样本集和确定特征后,使用CART算法计算,不剪枝。

最后,得到所需数目的决策树后,随机森林方法对这些树的输出进行投票,以得票最多的类作为随机森林的决策。

随机森林不会产生过拟合原因:

(1)随机森林的方法即对训练样本进行了采样,又对特征进行了采样,充分保证了所构建的每个树之间的独立性,使得投票结果更准确。

(2)随机森林的随机性体现在每棵树的训练样本是随机的树中每个节点的分裂属性也是随机选择的。有了这2个随机因素,即使每棵决策树没有进行剪枝,随机森林也不会产生过拟合的现象。

随机森林中有两个可控制参数:

  • 森林中树的数量(一般选取值较大)

  • 抽取的属性值m的大小。

随机森林API(泰坦尼克号案例)

image-20250611180801848

1
sklearn.ensemble.RandomForestClassifier()

n_estimators:决策树数量,(default = 10)

Criterion:entropy、或者 gini, (default = gini)

max_depth:指定树的最大深度,(default = None 表示树会尽可能的生长)

max_features=”auto”, 决策树构建时使用的最大特征数量

  • If “auto”, then max_features=sqrt(n_features).
  • If “sqrt”, then max_features=sqrt(n_features)(same as “auto”).
  • If “log2”, then max_features=log2(n_features).
  • If None, then max_features=n_features.

bootstrap:是否采用有放回抽样,如果为 False 将会使用全部训练样本,(default = True)

min_samples_split: 结点分裂所需最小样本数,(default = 2)

  • 如果节点样本数少于min_samples_split,则不会再进行划分.
  • 如果样本量不大,不需要设置这个值.
  • 如果样本量数量级非常大,则推荐增大这个值.

min_samples_leaf: 叶子节点的最小样本数,(default = 1)

  • 如果某叶子节点数目小于样本数,则会和兄弟节点一起被剪枝.
  • 较小的叶子结点样本数量使模型更容易捕捉训练数据中的噪声.

min_impurity_split: 节点划分最小不纯度

  • 如果某节点的不纯度(基尼系数,均方差)小于这个阈值,则该节点不再生成子节点,并变为叶子节点.
  • 一般不推荐改动默认值1e-7。
1
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
57
58
59
60
61
# 0.导入工具包
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import classification_report
from sklearn.tree import plot_tree
import matplotlib.pyplot as plt


# 1.读取数据
data =pd.read_csv('./data/titanic/train.csv')
print(data.head())
print(data.info())

# 2.数据处理
## 2.1 特征选择
x = data[['Pclass','Sex','Age']].copy() # 数据选择
y = data['Survived'].copy() # 目标值选择
# print(x.head(10))

# 2.2 缺失值处理
# 数值型缺失值 - 采用平均值或者是中位数进行填充
# 日期值,可以使用缺失值前后的值进行填充
x['Age'].fillna(x['Age'].mean(),inplace = True)
# print(x.head(10))
x=pd.get_dummies(x)
print(x.head(10))

## 2.3 训练集和测试集划分
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2)

# 3.模型训练
# 3.1 决策树
tree =DecisionTreeClassifier()
tree.fit(x_train,y_train)

# 3.2 随机森林
rf=RandomForestClassifier()
rf.fit(x_train,y_train)

# 3.3 网格搜索交叉验证
params = {'n_estimators':[10,20],'max_depth':[2,3,4,5]}
model =GridSearchCV(estimator=rf,param_grid=params,cv=3)
model.fit(x_train,y_train)
print(model.best_estimator_)

rfs = RandomForestClassifier(max_depth=4, n_estimators=10)
rfs.fit(x_train,y_train)

# 4.模型评估
# 4.1 决策树
print(tree.score(x_test,y_test))

# 4.2 随机森林
print(rf.score(x_test,y_test))

# 4.3 网格搜索交叉验证
print(rfs.score(x_test,y_test))

Adaboost

Adaptive Boosting(自适应提升)基于 Boosting思想实现的一种集成学习算法核心思想是通过逐步提高那些被前一步分类错误的样本的权重来训练一个强分类器。弱分类器的性能比随机猜测强就行,即可构造出一个非常准确的强分类器。其特点是:训练时,样本具有权重,并且在训练过程中动态调整。被分错的样本的样本会加大权重,算法更加关注难分的样本。

image-20250611175112448

AdaBoost推导过程及案例

整个Adaboost 迭代算法就3步:

1.初始化训练数据权值。
如果有N个样本,则每一个训练样本最开始时都被赋予相同的权值:1/N

2.训练弱分类器。
具体训练过程中,如果某个样本点已经被准确地分类,那么在构造下一个训练集中,它的权值就被降低相反,如果某个样本点没有被准确地分类,那么它的权值就得到提高

然后,权值更新过的样本集被用于训练下一个分类器,整个训练过程如此迭代地进行下去。

3.将各个训练得到的多个弱分类器组合成一个强分类器
各个弱分类器的训练过程结束后,加大分类误差率小的弱分类器的权重,使其在最终的分类函数中起着较大的决策作用,而降低分类误差率大的弱分类器的权重,使其在最终的分类函数中起着较小的决策作用。

换言之,误差率低的弱分类器在最终分类器中占的权重较大,否则较小。

参考链接:

Adaboost算法流程

模型权重计算公式:
$$
\alpha_t = \frac{1}{2} \ln \left( \frac{1 - \epsilon_t}{\epsilon_t} \right)
$$
$\alpha_t$为模型权重,$\epsilon_t$表示第m个弱学习器的错误率。

image-20250714201452809

设训练集样本数为 $N$,初始化每个样本权重:$w_i = \frac{1}{N}$(即均匀分布)。

迭代训练弱分类器(共 $M$轮)

训练弱分类器:使用当前样本权重分布训练弱分类器 $G_m(x)$(如决策树桩)。

计算分类错误率 $\epsilon_m$:$\epsilon_m = \sum_{i=1}^N w_i \cdot I(y_i \neq G_m(x_i))$
(即被错误分类样本的权重之和)。

$I$指示函数,预测错误时值为1,否则为0

计算弱分类器权重 $\alpha_m$:$\alpha_m = \frac{1}{2} \ln \left( \frac{1 - \epsilon_m}{\epsilon_m} \right)$

错误率 $\epsilon_m$ 越低,$\alpha_m$ 越大(分类器贡献越大)。

更新样本权重

  • 正确分类样本:权重乘以 $e^{-\alpha_m}$(权重减小)

  • 错误分类样本:权重乘以$e^{\alpha_m}$(权重增大)

  • 标准化权重:$w_i \leftarrow \frac{w_i}{\sum w_i}$
    (下一轮分类器更关注难分样本)。

组合弱分类器

  • 最终强分类器:$H(x) = \text{sign} \left( \sum_{m=1}^M \alpha_m G_m(x) \right)$。

Adaboost算法案例

数据集与参数(5个样本):

  • 弱分类器:单层决策树(决策树桩)
  • 迭代次数:$M = 3$。
样本 特征 $X_1$ 特征$X_2$ 标签 $y$
1 1.0 2.1 +1
2 2.0 1.1 +1
3 1.3 1.0 -1
4 1.0 1.0 -1
5 2.0 1.0 +1

第一轮迭代($m=1$)

  • 初始权重:$w_i = 0.2$(均匀分布)

  • 弱分类器 $G_1(x)$:以 $X_1 \leq 1.5$ 为阈值(分类规则:若 $X_1 \leq 1.5$则预测为 $-1$,否则 $+1$)。

  • 错误样本:样本1(被错分为 $-1$),错误率 $\epsilon_1 = 0.2$

  • 分类器权重:$\alpha_1 = \frac{1}{2} \ln \frac{0.8}{0.2} \approx 0.693$

  • 更新样本权重:

    • 样本1权重更新为$0.2 \times e^{0.693} \approx 0.4$

    • 其他样本权重更新为 $0.2 \times e^{-0.693} \approx 0.1$

    • 标准化后权重:

      样本 新权重 $w_i$
      1 0.4
      2-5 0.15

第二轮迭代($m=2$)

  • 弱分类器 $G_2(x)$:以 $X_2 \leq 1.0$为阈值(规则:$X_2 \leq 1.0$则 $-1$,否则 $+1$)。

  • 错误样本:样本5(被错分为 $-1$),错误率 $\epsilon_2 = 0.15$

  • 分类器权重:$\alpha_2 = \frac{1}{2} \ln \frac{0.85}{0.15} \approx 0.875$

  • 更新样本权重:

    • 样本5权重更新为 $0.15 \times e^{0.875} \approx 0.35$

    • 其他样本权重减小,标准化后:

      样本 新权重 $w_i$
      1 0.28
      5 0.35
      2,3,4 0.11

第三轮迭代($m=3$)

  • 弱分类器 $G_3(x)$:以 $X_1 \leq 2.0$为阈值(规则:$X_1 \leq 2.0$则 $+1$,否则 $-1$)。

  • 错误样本:样本3(被错分为 $+1$),错误率 $\epsilon_3 = 0.11$

  • 分类器权重:$\alpha_3 = \frac{1}{2} \ln \frac{0.89}{0.11} \approx 1.12$

  • 样本权重更新略(原理同上)。

  • 组合强分类器

$$H(x) = \text{sign} \left( 0.693 \cdot G_1(x) + 0.875 \cdot G_2(x) + 1.12 \cdot G_3(x) \right)$$

  • 分类结果:
    • 样本1:$G_1=-1, G_2=+1, G_3=+1$ → 加权和 $>0$ → 预测 $+1$ ✓
    • 样本3:$G_1=-1, G_2=-1, G_3=+1$ → 加权和 $<0$→ 预测 $-1$ ✓
    • 所有样本分类正确(原始单层决策树错误率40%,集成后达100%)。
1
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# 导入必要的库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.metrics import accuracy_score

# 1. 数据准备
# 获取数据
df_wine = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data', header=None)

# 修改列名
df_wine.columns = ['Class label', 'Alcohol', 'Malic acid', 'Ash',
'Alcalinity of ash', 'Magnesium', 'Total phenols',
'Flavanoids', 'Nonflavanoid phenols', 'Proanthocyanins',
'Color intensity', 'Hue', 'OD280/OD315 of diluted wines', 'Proline']

# 去掉一类(1,2,3)只保留两类
df_wine = df_wine[df_wine['Class label'] != 1]

# 获取特征值和目标值
X = df_wine[['Alcohol', 'Hue']].values
y = df_wine['Class label'].values

# 2. 数据预处理
# 类别转化 (2,3)=>(0,1)
le = LabelEncoder()
y = le.fit_transform(y)

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

# 3. 模型训练与评估
# 创建决策树和AdaBoost分类器
tree = DecisionTreeClassifier(criterion='entropy', max_depth=1, random_state=0)
ada = AdaBoostClassifier(base_estimator=tree, n_estimators=500, learning_rate=0.1, random_state=0)

# 决策树性能评估
tree = tree.fit(X_train, y_train)
y_train_pred = tree.predict(X_train)
y_test_pred = tree.predict(X_test)
tree_train = accuracy_score(y_train, y_train_pred)
tree_test = accuracy_score(y_test, y_test_pred)
print('Decision tree train/test accuracies %.3f/%.3f' % (tree_train, tree_test))

# AdaBoost性能评估
ada = ada.fit(X_train, y_train)
y_train_pred = ada.predict(X_train)
y_test_pred = ada.predict(X_test)
ada_train = accuracy_score(y_train, y_train_pred)
ada_test = accuracy_score(y_test, y_test_pred)
print('Adaboost train/test accuracies %.3f/%.3f' % (ada_train, ada_test))

# 4. 可视化决策边界
x_min, x_max = X_train[:, 0].min() - 1, X_train[:, 0].max() + 1
y_min, y_max = X_train[:, 1].min() - 1, X_train[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
np.arange(y_min, y_max, 0.1))

f, axarr = plt.subplots(1, 2, sharex='col', sharey='row', figsize=(8, 3))

for idx, clf, tt in zip([0, 1], [tree, ada], ['Decision Tree', 'AdaBoost']):
clf.fit(X_train, y_train)
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
axarr[idx].contourf(xx, yy, Z, alpha=0.3)
axarr[idx].scatter(X_train[y_train==0, 0], X_train[y_train==0, 1], c='blue', marker='^')
axarr[idx].scatter(X_train[y_train==1, 0], X_train[y_train==1, 1], c='red', marker='o')
axarr[idx].set_title(tt)
axarr[idx].set_xlabel('Alcohol')
axarr[idx].set_ylabel('Hue')

plt.tight_layout()
plt.show()

GBDT

图解GBDT:https://blog.csdn.net/ShowMeAI/article/details/123402422

GBDT(Gradient Boosting Decision Tree,梯度提升决策树)是一种基于集成学习的迭代算法,通过组合多个弱学习器(决策树)提升模型预测能力。梯度提升树(Gradient Boosting Decision Tre)是提升树(Boosting Decision Tree)的一种改进算法。GBDT通过迭代拟合残差(预测误差)逐步优化模型每轮新增的决策树学习前一模型预测值与真实值的残差,最终将所有树的预测结果加权求和作为最终输出

目标函数:最小化损失函数 $L(y, F(x))$,其中 $F(x)$为模型预测值:$F^*(x) = \arg \min_{F} \sum_{i=1}^n L(y_i, F(x_i))$

迭代更新:第 $m$ 轮模型更新公式为:$F_{m}(x) = F_{m-1}(x) + \gamma_m h_m(x)$

  • 其中 $\gamma_m$ 为学习率
  • $h_m(x)$ 为新决策树。

负梯度拟合:每轮计算损失函数的负梯度(伪残差)作为新树的学习目标:

image-20250714202314363

新树 $h_m(x)$ 的任务是拟合 $r_i^{(m)}$

  • 叶子节点权重:对叶子节点区域 $R_{mj}$,计算最优权重 :$c_{mj}$

    $$
    c_{mj} = \arg\min_{c} \sum_{x_i \in R_{mj}} L(y_i, F_{m-1}(x_i) + c)
    $$

    对于平方损失函数,$c_{mj}$ 即节点内样本残差的均值

不同任务使用不同损失函数:

任务类型 损失函数 负梯度形式
回归(如MSE) $\frac{1}{2}(y_i - \hat{y_i})^2$ $y_i - \hat{y_i}$
二分类(如Log Loss) $-\left[ y_i \ln(\hat{y_i}) + (1-y_i)\ln(1-\hat{y_i}) \right]$ $y_i - \hat{y_i}$

算法流程

  1. 初始化:用常量初始化模型(如回归问题取标签均值):

    $$
    F_0(x) = \arg\min_{c} \sum_{i=1}^n L(y_i, c)
    $$

  2. 迭代训练(共 $M$轮):

    • 计算负梯度(残差) $r_i^{(m)}$;
    • 训练新决策树 $h_m(x)$ 拟合 $r_i^{(m)}$,生成叶子节点区域 $R_{mj}$;
    • 计算每个叶子节点的权重 $c_{mj}$;
    • 更新模型: $F_m(x) = F_{m-1}(x) + \gamma \sum_{j} c_{mj} \mathbf{1}(x \in R_{mj})$。
  3. 输出最终模型:

    $$
    F_M(x) = F_0(x) + \sum_{m=1}^M \gamma_m h_m(x)
    $$

GBDT算法案例

  • 年龄预测(回归问题)

    • 数据:4人样本(A:14岁, B:16岁, C:24岁, D:26岁)。

    • 参数:学习率 $\gamma = 0.1$,树深度为2,迭代5轮。

步骤

  1. 初始化:取年龄均值 $F_0(x) = (14+16+24+26)/4 = 20$。

  2. 第一轮迭代:

    • 计算残差:

      样本 真实值 预测值 残差
      A 14 20 -6
      B 16 20 -4
      C 24 20 4
      D 26 20 6
    • 训练树1:按特征划分(如年龄≤20),左节点(A,B)残差均值= -5,右节点(C,D)残差均值=5。

    • 更新模型:$F_1(x) = 20 + 0.1 \times \text{树1输出}$。

  3. 后续迭代:每轮新树拟合上一轮残差,逐步逼近真实值。

    • 最终预测:A的年龄= $20 + 0.1 \times (-5) + 0.1 \times (-0.5) + \cdots \approx 14$。

关键参数

参数 作用
n_estimators 树的数量,过多易过拟合,过少欠拟合
learning_rate 学习率(缩减因子),越小需更多树但模型更稳定
max_depth 单棵树深度,控制模型复杂度
min_samples_split 节点分裂所需最小样本数,防止过拟合

案例代码实现可参考Scikit-learn库:

1
2
3
from sklearn.ensemble import GradientBoostingClassifier  
model = GradientBoostingClassifier(learning_rate=0.1, n_estimators=100, max_depth=3)
model.fit(X_train, y_train)
1
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
#1.数据导入
#1.1导入数据
import pandas as pd
#1.2.利用pandas的read.csv模块从互联网中收集泰坦尼克号数据集
titanic=pd.read_csv("data/titanic.csv")
titanic.info() #查看信息
#2人工选择特征pclass,age,sex
X=titanic[['pclass','age','sex']]
y=titanic['survived']
#3.特征工程
#数据的填补
X['age'].fillna(X['age'].mean(),inplace=True)

#数据的切分
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test =train_test_split(X,y,test_size=0.25,random_state=22)
#将数据转化为特征向量
from sklearn.feature_extraction import DictVectorizer
vec=DictVectorizer(sparse=False)
X_train=vec.fit_transform(X_train.to_dict(orient='records'))
X_test=vec.transform(X_test.to_dict(orient='records'))

#4.使用单一的决策树进行模型的训练及预测分析
from sklearn.tree import DecisionTreeClassifier
dtc=DecisionTreeClassifier()
dtc.fit(X_train,y_train)
dtc_y_pred=dtc.predict(X_test)
print("score",dtc.score(X_test,y_test))
#5.随机森林进行模型的训练和预测分析
from sklearn.ensemble import RandomForestClassifier
rfc=RandomForestClassifier(random_state=9)
rfc.fit(X_train,y_train)
rfc_y_pred=rfc.predict(X_test)
print("score:forest",rfc.score(X_test,y_test))
#6.GBDT进行模型的训练和预测分析
from sklearn.ensemble import GradientBoostingClassifier
gbc=GradientBoostingClassifier()
gbc.fit(X_train,y_train)
gbc_y_pred=gbc.predict(X_test)
print("score:GradientBoosting",gbc.score(X_test,y_test))

#7.性能评估
from sklearn.metrics import classification_report
print("dtc_report:",classification_report(dtc_y_pred,y_test))
print("rfc_report:",classification_report(rfc_y_pred,y_test))
print("gbc_report:",classification_report(gbc_y_pred,y_test))

XGBoost

XGBoost的核心算法思想,基本就是:

  1. 不断地添加树,不断地进行特征分裂来生长一棵树,每次添加一个树,其实是学习一个新函数f(x),去拟合上次预测的残差。
  2. 当我们训练完成得到k棵树,我们要预测一个样本的分数,其实就是根据这个样本的特征,在每棵树中会落到对应的一个叶子节点,每个叶子节点就对应一个分数
  3. 最后只需要将每棵树对应的分数加起来就是该样本的预测值。

参考链接:

XGBoost与GBDT有什么不同

除了算法上与传统的GBDT有一些不同外,XGBoost还在工程实现上做了大量的优化。总的来说,两者之间的区别和联系可以总结成以下几个方面。

  1. GBDT是机器学习算法,XGBoost是该算法的工程实现。
  2. 在使用CART作为基分类器时,XGBoost显式地加入了正则项来控制模型的复杂度有利于防止过拟合,从而提高模型的泛化能力
  3. GBDT在模型训练时只使用了代价函数的一阶导数信息,XGBoost对代价函数进行二阶泰勒展开,可以同时使用一阶和二阶导数。
  4. 传统的GBDT采用CART作为基分类器,XGBoost支持多种类型的基分类器,比如线性分类器。
  5. 传统的GBDT在每轮迭代时使用全部的数据,XGBoost则采用了与随机森林相似的策略,支持对数据进行采样。
  6. 传统的GBDT没有设计对缺失值进行处理,XGBoost能够自动学习出缺失值的处理策略

在XGBoost目标函数泰勒展开

$Obj^{(t)} = \sum_{i=1}^{n} \textcolor{red}{l\left(y_i, \hat{y}_i^{(t-1)} + f_t(x_i)\right)} + \Omega(f_t) + \text{constant} $

  • $y_i$:第 $i$ 个样本的真实标签
  • $\hat{y}_i^{(t-1)}$:前 $t-1$ 轮模型的累积预测值
  • $f_t(x_i)$:第 $t$ 轮新加入的决策树的预测值
  • $l$ 是一个二元函数:
  • $\textcolor{red}{ l(\text{真实值 } y_i, \text{ 预测值 } \hat{y}_i) }$
  • 衡量模型预测值 $\hat{y}_i$ 与真实标签 $y_i$ 之间的差异(误差)

不同任务中的常见损失函数形式

任务类型 损失函数 $l$ 的表达式 场景示例
回归任务 均方误差(MSE): $\frac{1}{2}(y_i - \hat{y}_i)^2$ 房价预测、销量预测
二分类任务 对数损失(Log Loss): $-y_i \ln(\hat{y}_i) - (1-y_i)\ln(1-\hat{y}_i)$ 疾病诊断、点击率预测
多分类任务 交叉熵损失(Cross-Entropy): $-\sum_c y_{i,c} \ln(\hat{y}_{i,c})$ 图像分类、文本分类
  1. 泰勒展开公式:

    $$
    f(x + \Delta x) \simeq f(x) + f’(x) \Delta x + \frac{1}{2} f’’(x) \Delta x^2
    $$

  2. 应用到损失函数:

    • 令 $x = \hat{y}_i^{(t-1)}$, $\Delta x = f_t(x_i)$

    • 定义梯度:

      $$
      g_i = \frac{\partial l}{\partial \hat{y}^{(t-1)}} \quad \text{(一阶导数)}
      $$

    • 定义Hessian:

      $$
      h_i = \frac{\partial^2 l}{\partial \left( \hat{y}^{(t-1)} \right)^2} \quad \text{(二阶导数)}
      $$

  3. 近似目标函数:

    $$
    Obj^{(t)} \simeq \sum_{i=1}^{n} \left[ \textcolor{red}{l(y_i, \hat{y}_i^{(t-1)})} + g_i f_t(x_i) + \frac{1}{2} h_i f_t^2(x_i) \right] + \Omega(f_t)
    $$

    关键点:泰勒展开将复杂的损失函数 $l$ 转换为 常数项 + 一阶梯度项 + 二阶Hessian项,使优化过程可解析求解。

为什么需要二阶展开?

  • 精度提升
    二阶导数(Hessian $h_i$)捕捉损失函数的曲率信息,比一阶方法(如GBDT)收敛更快、精度更高。
  • 正则化控制
    二阶项 $h_i f_t^2(x_i)$ 隐含正则化作用,抑制决策树权重的极端值(防止过拟合)。

XGBoost算法流程

初始化模型

  • 目标:构建初始预测值(常数值)

  • 参数作用:

    • base_score:初始预测值(回 归任务取标签均值,分类任务取先验概率)
  • 迭代训练(共M轮)

步骤1:计算梯度与Hessian

数学形式:

  • 一阶梯度:

$$
g_i = \frac{\partial L(y_i, F_{m-1}(x_i))}{\partial F_{m-1}(x_i)}
$$

  • 二阶Hessian:

$$
h_i = \frac{\partial^2 L(y_i, F_{m-1}(x_i))}{\partial F_{m-1}(x_i)^2}
$$

  • 参数控制:
    • $objective$:定义损失函数(如$reg:squarederror$、$binary:logistic$)

步骤2:构建决策树(关键参数解析)

参数类别 参数名 数学含义 作用
树结构控制 max_depth 树的最大深度 限制复杂度,防止过拟合(典型值:3-10)
min_child_weight 叶节点样本权重和的最小值 控制分裂粒度(值越大越保守)
gamma 分裂所需最小损失减少量 增益阈值($\gamma \uparrow$ → 模型更简单)
随机化控制 subsample 样本采样比例(默认1) 降低方差(典型值:0.5-0.8)
colsample_bytree 特征采样比例(默认1) 减少特征相关性影响
正则化 reg_alpha L1正则项系数($\alpha$) 稀疏特征优化
reg_lambda L2正则项系数($\lambda$) 抑制权重过大
  • 节点分裂增益公式:

    $$
    \text{Gain} = \frac{1}{2} \left[ \frac{G_L^2}{H_L + \lambda} + \frac{G_R^2}{H_R + \lambda} - \frac{(G_L+G_R)^2}{H_L+H_R + \lambda} \right] - \gamma
    $$

    其中 $G$ 为梯度之和,$H$ 为Hessian之和

步骤3:计算叶节点权重

  • 权重公式:

    $$
    w_j = -\frac{\sum_{i \in I_j} g_i}{\sum_{i \in I_j} h_i + \lambda}
    $$

步骤4:更新模型

  • 参数控制:
    • $eta$($learning_rate$):收缩权重(默认0.3),防止过拟合
    • 更新公式:

$$
F_m(x) = F_{m-1}(x) + \eta \cdot w_{q(x)}
$$

XGBoost案例:乳腺癌分类

  • 数据与参数配置
1
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
import xgboost as xgb
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report

# 加载数据
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)

# 转换为DMatrix格式(加速计算)
dtrain = xgb.DMatrix(X_train, label=y_train)
dtest = xgb.DMatrix(X_test, label=y_test)

# 参数配置(关键参数释义)
params = {
'objective': 'binary:logistic', # 二分类逻辑损失
'eval_metric': 'logloss', # 评估指标
'max_depth': 4, # 树深度限制
'eta': 0.1, # 学习率
'subsample': 0.8, # 80%样本采样
'colsample_bytree': 0.7, # 70%特征采样
'reg_lambda': 1.0, # L2正则
'gamma': 0.3, # 分裂最小增益阈值
'min_child_weight': 1 # 叶节点最小样本权重和
}
  • 算法流程验证
  1. 初始化

    • 初始预测值 = 良性样本比例(62.5%)
  2. 第一轮迭代

    • 计算梯度与Hessian(对数损失函数)
    • 按特征分裂(如$worst radius$),选择增益最大分裂点
    • 计算叶节点权重(如左叶权重=0.2,右叶权重=-0.3)
    • 更新模型:$F_1(x) = 0.625 + 0.1 \times \text{树输出}$
  3. 早停机制

    1
    2
    3
    4
    5
    6
    7
    8
    evals = [(dtrain, 'train'), (dtest, 'eval')]
    model = xgb.train(
    params,
    dtrain,
    num_boost_round=100,
    evals=evals,
    early_stopping_rounds=10 # 连续10轮无提升则停止
    )

    输出

    1
    2
    3
    4
    [0] train-logloss:0.68 eval-logloss:0.65
    [1] train-logloss:0.63 eval-logloss:0.61
    ...
    [42] train-logloss:0.11 eval-logloss:0.21 # 停止在42轮

聚类算法(Clustering)

一种典型的无监督学习算法,主要用于将相似的样本自动归到一个类别中。

在聚类算法中根据样本之间的相似性,将样本划分到不同的类别中,对于不同的相似度计算方法,会得到不同的聚类结果,常用的相似度计算方法有欧式距离法。

API

1
sklearn.cluster.KMeans(n_clusters=8)
  • 参数:
    • n_clusters:开始的聚类中心数量
      • 整型,缺省值=8,生成的聚类数,即产生的质心(centroids)数。
  • 方法:
    • estimator.fit(x)
    • estimator.predict(x)
    • estimator.fit_predict(x)
      • 计算聚类中心并预测每个样本属于哪个类别,相当于先调用fit(x),然后再调用predict(x)

聚类算法都是无监督学习吗?

什么是聚类算法?聚类是一种机器学习技术,它涉及到数据点的分组。给定一组数据点,我们可以使用聚类算法将每个数据点划分为一个特定的组。理论上,同一组中的数据点应该具有相似的属性和/或特征,而不同组中的数据点应该具有高度不同的属性和/或特征。聚类是一种无监督学习的方法,是许多领域中常用的统计数据分析技术。

常用的算法包括K-MEANS、高斯混合模型(Gaussian Mixed Model,GMM)、自组织映射神经网络(Self-Organizing Map,SOM)

k-means(k均值)算法

算法过程

K-均值是最普及的聚类算法,算法接受一个未标记的数据集,然后将数据聚类成不同的组。

K-均值是一个迭代算法,假设我们想要将数据聚类成 n 个组,其方法为:

  • 首先选择𝐾个随机的点,称为聚类中心(cluster centroids);
  • 对于数据集中的每一个数据,按照距离𝐾个中心点的距离,将其与距离最近的中心点关联起来,与同一个中心点关联的所有点聚成一类。
  • 计算每一个组的平均值,将该组所关联的中心点移动到平均值的位置。
  • 重复步骤,直至中心点不再变化。

img来表示聚类中心,用𝑐(1),𝑐(2),…,𝑐(𝑚)来存储与第𝑖个实例数据最近的聚类中心的索引,K-均值算法的伪代码如下:

1
2
3
4
5
6
Repeat {
for i = 1 to m
c(i) := index (form 1 to K) of cluster centroid closest to x(i)
for k = 1 to K
μk := average (mean) of points assigned to cluster k
}

算法分为两个步骤,第一个 for 循环是赋值步骤,即:对于每一个样例𝑖,计算其应该属于的类。第二个 for 循环是聚类中心的移动,即:对于每一个类𝐾,重新计算该类的质心。

K-均值算法也可以很便利地用于将数据分为许多不同组,即使在没有非常明显区分的组群的情况下也可以。下图所示的数据集包含身高和体重两项特征构成的,利用 K-均值算法将数据分为三类,用于帮助确定将要生产的 T-恤衫的三种尺寸。

img

损失函数

K-均值最小化问题,是要最小化所有的数据点与其所关联的聚类中心点之间的距离之和,因此 K-均值的代价函数(又称畸变函数 Distortion function)为:

img

其中 img代表与 img最近的聚类中心点。 我们的的优化目标便是找出使得代价函数最小的 imgimg

k值的选择

在运行 K-均值算法的之前,我们首先要随机初始化所有的聚类中心点,下面介绍怎样做:

  1. 我们应该选择𝐾 < 𝑚,即聚类中心点的个数要小于所有训练集实例的数量。
  2. 随机选择𝐾个训练实例,然后令𝐾个聚类中心分别与这𝐾个训练实例相等K-均值的一个问题在于,它有可能会停留在一个局部最小值处,而这取决于初始化的情况。

为了解决这个问题,我们通常需要多次运行 K-均值算法,每一次都重新进行随机初始化,最后再比较多次运行 K-均值的结果,选择代价函数最小的结果。这种方法在𝐾较小的时候(2–10)还是可行的,但是如果𝐾较大,这么做也可能不会有明显地改善。

没有所谓最好的选择聚类数的方法,通常是需要根据不同的问题,人工进行选择的。选择的时候思考我们运用 K-均值算法聚类的动机是什么。有一个可能会谈及的方法叫作**“肘部法则”**。关 于“肘部法则”,我们所需要做的是改变𝐾值,也就是聚类类别数目的总数。我们用一个聚类来运行 K 均值聚类方法。这就意味着,所有的数据都会分到一个聚类里,然后计算成本函数或者计算畸变函数𝐽。𝐾代表聚类数字。

img

我们可能会得到一条类似于这样的曲线。像一个人的肘部。这就是“肘部法则”所做的,让我们来看这样一个图,看起来就好像有一个很清楚的肘在那儿。你会发现这种模式,它的畸变值会迅速下降,从 1 到 2,从 2 到 3 之后,你会在 3 的时候达到一个肘点。在此之后,畸变值就下降的非常慢,看起来就像使用 3 个聚类来进行聚类是正确的,**这是因为那个点是曲线的肘点,畸变值下降得很快,𝐾 = 3之后就下降得很慢,那么我们就选𝐾 = 3。**当你应用“肘部法则”的时候,如果你得到了一个像上面这样的图,那么这将是一种用来选择聚类个数的合理方法。

KNN与K-means区别?

K最近邻(k-Nearest Neighbor,KNN)分类算法,是一个理论上比较成熟的方法,也是最简单的机器学习算法之一。

KNN K-Means
1.KNN是分类算法 2.属于监督学习 3.训练数据集是带label的数据 1.K-Means是聚类算法 2.属于非监督学习 3.训练数据集是无label的数据,是杂乱无章的,经过聚类后变得有序,先无序,后有序。
没有明显的前期训练过程,属于memory based learning 有明显的前期训练过程
K的含义:一个样本x,对它进行分类,就从训练数据集中,在x附近找离它最近的K个数据点,这K个数据点,类别c占的个数最多,就把x的label设为c。 K的含义:K是人工固定好的数字,假设数据集合可以分为K个蔟,那么就利用训练数据来训练出这K个分类。

相似点

都包含这样的过程,给定一个点,在数据集中找离它最近的点。即二者都用到了NN(Nears Neighbor)算法思想。

K-Means优缺点及改进

k-means:在大数据的条件下,会耗费大量的时间和内存。 优化k-means的建议:

  1. 减少聚类的数目K。因为,每个样本都要跟类中心计算距离。

  2. 减少样本的特征维度。比如说,通过PCA等进行降维。

  3. 考察其他的聚类算法,通过选取toy数据,去测试不同聚类算法的性能。

  4. hadoop集群,K-means算法是很容易进行并行计算的。

  5. 算法可能找到局部最优的聚类,而不是全局最优的聚类。使用改进的二分k-means算法。

    二分k-means算法:首先将整个数据集看成一个簇,然后进行一次k-means(k=2)算法将该簇一分为二,并计算每个簇的误差平方和,选择平方和最大的簇迭代上述过程再次一分为二,直至簇数达到用户指定的k为止,此时可以达到的全局最优。

高斯混合模型(GMM)

GMM的思想

高斯混合模型(Gaussian Mixed Model,GMM)也是一种常见的聚类算法,与K均值算法类似,同样使用了EM算法进行迭代计算。高斯混合模型假设每个簇的数据都是符合高斯分布(又叫正态分布)的,当前数据呈现的分布就是各个簇的高斯分布叠加在一起的结果。

第一张图是一个数据分布的样例,如果只用一个高斯分布来拟合图中的数据,图 中所示的椭圆即为高斯分布的二倍标准差所对应的椭圆。直观来说,图中的数据 明显分为两簇,因此只用一个高斯分布来拟和是不太合理的,需要推广到用多个 高斯分布的叠加来对数据进行拟合。第二张图是用两个高斯分布的叠加来拟合得到的结果。**这就引出了高斯混合模型,即用多个高斯分布函数的线形组合来对数据分布进行拟合。**理论上,高斯混合模型可以拟合出任意类型的分布。

img

img

高斯混合模型的核心思想是,假设数据可以看作从多个高斯分布中生成出来 的。在该假设下,每个单独的分模型都是标准高斯模型,其均值 $u_i$ 和方差 $\sum_i$ 是待估计的参数。此外,每个分模型都还有一个参数 $\pi_i$,可以理解为权重或生成数据的概 率。高斯混合模型的公式为:

img

通常我们并不能直接得到高斯混合模型的参数,而是观察到了一系列 数据点,给出一个类别的数量K后,希望求得最佳的K个高斯分模型。因此,高斯 混合模型的计算,便成了最佳的均值μ,方差Σ、权重π的寻找,这类问题通常通过 最大似然估计来求解。遗憾的是,此问题中直接使用最大似然估计,得到的是一 个复杂的非凸函数,目标函数是和的对数,难以展开和对其求偏导。

**在这种情况下,可以用EM算法。 **EM算法是在最大化目标函数时,先固定一个变量使整体函数变为凸优化函数,求导得到最值,然后利用最优参数更新被固定的变量,进入下一个循环。具体到高 斯混合模型的求解,EM算法的迭代过程如下。

首先,初始随机选择各参数的值。然后,重复下述两步,直到收敛。

  • E步骤。根据当前的参数,计算每个点由某个分模型生成的概率。
  • M步骤。使用E步骤估计出的概率,来改进每个分模型的均值,方差和权重。

高斯混合模型是一个生成式模型。可以这样理解数据的生成过程,假设一个最简单的情况,即只有两个一维标准高斯分布的分模型N(0,1)和N(5,1),其权重分别为0.7和0.3。那么,在生成第一个数据点时,先按照权重的比例,随机选择一个分布,比如选择第一个高斯分布,接着从N(0,1)中生成一个点,如−0.5,便是第一个数据点。在生成第二个数据点时,随机选择到第二个高斯分布N(5,1),生成了第二个点4.7。如此循环执行,便生成出了所有的数据点。

也就是说,我们并不知道最佳的K个高斯分布的各自3个参数,也不知道每个 数据点究竟是哪个高斯分布生成的。所以每次循环时,先固定当前的高斯分布不 变,获得每个数据点由各个高斯分布生成的概率。然后固定该生成概率不变,根据数据点和生成概率,获得一个组更佳的高斯分布。循环往复,直到参数的不再变化,或者变化非常小时,便得到了比较合理的一组高斯分布。

GMM与K-Means相比

高斯混合模型与K均值算法的相同点是:

  • 它们都是可用于聚类的算法;
  • 都需要 指定K值;
  • 都是使用EM算法来求解;
  • 都往往只能收敛于局部最优。

而它相比于K 均值算法的优点是,可以给出一个样本属于某类的概率是多少;不仅仅可以用于聚类,还可以用于概率密度的估计;并且可以用于生成新的样本点。

聚类算法如何评估

由于数据以及需求的多样性,没有一种算法能够适用于所有的数据类型、数 据簇或应用场景,似乎每种情况都可能需要一种不同的评估方法或度量标准。例 如,K均值聚类可以用误差平方和来评估,但是基于密度的数据簇可能不是球形, 误差平方和则会失效。在许多情况下,判断聚类算法结果的好坏强烈依赖于主观 解释。尽管如此,聚类算法的评估还是必需的,它是聚类分析中十分重要的部分之一。

聚类评估的任务是估计在数据集上进行聚类的可行性,以及聚类方法产生结 果的质量。这一过程又分为三个子任务。

  1. 估计聚类趋势。

    这一步骤是检测数据分布中是否存在非随机的簇结构。如果数据是基本随机 的,那么聚类的结果也是毫无意义的。我们可以观察聚类误差是否随聚类类别数 量的增加而单调变化,如果数据是基本随机的,即不存在非随机簇结构,那么聚 类误差随聚类类别数量增加而变化的幅度应该较不显著,并且也找不到一个合适 的K对应数据的真实簇数。

  2. 判定数据簇数。

    确定聚类趋势之后,我们需要找到与真实数据分布最为吻合的簇数,据此判定聚类结果的质量。数据簇数的判定方法有很多,例如手肘法和Gap Statistic方 法。需要说明的是,用于评估的最佳数据簇数可能与程序输出的簇数是不同的。 例如,有些聚类算法可以自动地确定数据的簇数,但可能与我们通过其他方法确 定的最优数据簇数有所差别。

  3. 测定聚类质量。

    在无监督的情况下,我们可以通过考察簇的分离情况和簇的紧 凑情况来评估聚类的效果。定义评估指标可以展现面试者实际解决和分析问题的 能力。事实上测量指标可以有很多种,以下列出了几种常用的度量指标,更多的 指标可以阅读相关文献。

    轮廓系数、均方根标准偏差、R方(R-Square)、改进的Hubert统计。

附件

机器学习阶段参考链接

公共基础知识

标量(Scalar):

  • 零阶张量,仅包含大小(Magnitude)​ 没有方向(就是单个数值

  • 数学表示:s∈R(单个实数或复数)

    • 例如:质量:5kg、损失函数值:L=0.32

向量(Vector)

  • 一阶张量,是标量的有序组合,具有 ​​大小和方向​​。(就是一个向量,线性代数里的列向量以及行向量
  • 可表示空间中的点或方向,例如:
    • 坐标点:(2,3,5)
    • 速度向量:v=3i+4j

张量(Tensor)

  • 定义:高阶广义数组(标量是0阶,向量是1阶),可表示 ​​多维数据关系​
  • 特性
    • 阶数(Rank):索引的自由度(如矩阵是2阶)
    • 支持张量积(⊗)、收缩(Contraction)等运算
    • 内存占用随维度指数增长
  • 示例
    • 矩阵(2阶张量):$A∈R^{3×3}$(三阶方阵)
    • RGB图像(3阶张量):$I∈R^{H×W×3}$
    • 时间序列视频(4阶张量):$V∈R^{T×H×W×3}$

三者区别

特性 标量 向量 张量
阶数 0阶 1阶 k阶(k≥2)
方向性 多维方向
存储结构 单个数值 一维数组 多维数组
运算 算术运算 线性代数运算 张量分解/收缩
PyTorch表示 torch.tensor(3) torch.tensor([1,2]) torch.rand(2,3,4)

图形展示

graph LR
    A[标量] -->|升维| B[向量]
    B -->|升维| C[矩阵]
    C -->|升维| D[3阶张量]

应用场景

  • 标量:损失值、阈值参数
  • 向量:特征表示、词嵌入(Word2Vec)
  • 张量:
    • 计算机视觉:卷积核(4阶:[out_channels, in_channels, h, w]
    • 自然语言处理:注意力权重矩阵([batch_size, seq_len, seq_len]

Latex数学语法支持

导数&矩阵&向量

常见函数的导数:

公式 例子
$(C)^\prime=0$ $\left(5\right)^\prime=0$ $\left(10\right)^\prime=0$
$\left(x^\alpha\right)^\prime=\alpha x^{\alpha-1}$ $\left(x^3\right)^\prime=3 x^{2}$ $\left(x^5\right)^\prime=5 x^{4}$
$\left(a^x\right)^\prime=a^{x}\ln{a}$ $\left(2^x\right)^\prime=2^x\ln{2}$ $\left(7^x\right)^\prime=7^x\ln{7}$
$\left(e^x\right)^\prime=e^{x}$ $\left(e^x\right)^\prime=e^{x}$
$\left(\log{_a}x\right)^\prime=\frac{1}{x\ln{a}}$ $\left(\log{_{10}}x\right)^\prime=\frac{1}{x\ln{10}}$
$\left(\ln{x}\right)^\prime=\frac{1}{x}$ $\left(\ln{x}\right)^\prime=\frac{1}{x}$
$\left(\sin{x}\right)^\prime=\cos{x}$ $\left(\sin{x}\right)^\prime=\cos{x}$
$\left(\cos{x}\right)^\prime=-\sin{x}$ $\left(\cos{x}\right)^\prime=-\sin{x}$

导数的四则运算:

公式 例子
$\left[u(x)\pm v(x)\right]^\prime=u^\prime(x) \pm v^\prime(x)$ $(e^x+4\ln{x})^\prime=(e^x)^\prime+(4\ln{x})^\prime=e^x+\frac{4}{x}$
$\left[u(x)\cdot v(x)\right]^\prime=u^\prime(x) \cdot v(x) + u(x) \cdot v^\prime(x)$ $(\sin{x}\cdot\ln{x})^\prime=\cos{x}\cdot\ln{x}+\sin{x}\cdot\frac{1}{x}$
$\left[\frac{u(x)}{v(x)}\right]^\prime=\frac{u^\prime(x) \cdot v(x) - u(x) \cdot v^\prime(x)}{v^2(x)}$ $\left(\frac{e^x}{\cos{x}}\right)^\prime=\frac{e^x\cdot\cos{x}-e^x\cdot(-\sin{x})}{cos^2(x)}$
${g[h(x)]}^\prime=g^\prime(h)*h^\prime(x)$ $(\sin{2x})^\prime=\cos{2x}\cdot(2x)^\prime=2\cos(2x)$

复合函数求导:

链式法则:先对外函数求导,再对内函数求导

例如:计算函数 $y = (x^{2} + 2x)^{2}$ 的导函数:
$$
\begin{aligned}
y’ &= 2(x^{2} + 2x)^{(2-1)} \cdot (x^{2} + 2x)’ \
&= 2(x^{2} + 2x)(2x + 2) \
&= 4(x^{3} + 3x^{2} + 2x) \
&= 4x^{3} + 12x^{2} + 8x
\end{aligned}
$$

对数函数

image-20250609111130085

image-20250609110804572

二元一次方程极值问题

image-20250607143836920

概率

image-20250609190507660

条件概率补充

公式:$$ P(B \mid A) = \frac{P(AB)}{P(A)} $$

  • $P(B \mid A)$:在事件A发生的条件下,事件B发生的概率

  • $P(AB)$:事件A和事件B同时发生的联合概率

  • $P(A)$:事件A发生的边际概率

全概率公式

如果事件$B_{1}、B_{2}、B_{3}…B_{i}$构成一个完备事件组,即它们两两互不相容,其和为全集;并且$P(B_{i})$大于0,则对任一事件A有

$$ P(A) = P(A \mid B_{1})P(B_{1}) + P(A \mid B_{2})P(B_{2}) + \cdots + P(A \mid B_{i})P(B_{i}) $$

公式:$$ P(A) = \sum_{i=1}^{n} P(A|B_i) \cdot P(B_i) $$

贝叶斯公式

也就是在已知结果的情况下,倒推某个事件发生的概率

$$ P(A|B) = \frac{P(B|A) \cdot P(A)}{P(B)} $$

使用全概率公式展开

$$ P(A|B) = \frac{P(B|A) \cdot P(A)}{\sum_{i=1}^{n} P(B|A_i) \cdot P(A_i)} $$

极大似然估计

参考链接:

极大释然估计:https://blog.csdn.net/qq_41775769/article/details/113514294

离散型统计模型

$$ L(\theta) = \prod_{i=1}^{n} P_{\theta}(X_{i}=x_{i}) \quad i=1,2,3,\cdots,n $$

  • $L(\theta)$:似然函数
  • $\prod$:连乘符号
  • $P_{\theta}(X_{i}=x_{i})$:在参数$\theta$下随机变量$X_i$取$x_i$的概率

连续型统计模型

$$ L(\theta) = \prod_{i=1}^{n} f(x_{i};\theta) \quad i=1,2,3,\cdots,n $$

参数说明

  • $f(x_{i};\theta)$:概率密度函数
  • 其他符号含义与离散型相同

从一个直观的例子理解极大似然估计,比如:在一个未知的袋子里摸球,现有的认知告诉我们是袋子里面的球要么是红色,要么是蓝色。于是我们可以知道从该袋子中摸球颜色的概率服从二项分布如下:

$X$ 红色 蓝色
$P$ $θ$ $1-θ$

由于不知道袋子中究竟有多少个球以及每个颜色的球有多少个,所以无法对参数θ进行计算,也不能计算出摸到哪种颜色的球的概率是多少?于是,假设有一个测试人员对袋内球进行有放回的抽取,进行了100次随机测验之后,统计得出:有30次摸到的是红球,有70次摸到的是蓝球。

从现有的测试结果出发,我们有理由相信袋子中球的比例大概是红色 : 蓝色=3 : 7(也就是背后的理论支撑)。所以进而求出概率以及参数 θ=0.3 。也就是用抽样时球的颜色出现的频率近似等于概率。
注意:极大似然估计中采样需满足一个重要的假设,就是所有的采样都是独立同分布的

最大释然估计统计方法

进行$n$次独立随机试验,观测到:

  • “状态1”发生 $n_1$ 次
  • “状态2”发生 $n_2$ 次
    (满足 $n_1 + n_2 = n$)

从频率学派角度,可得经验概率:
$$ P_{\text{empirical}}(\text{状态1}) = \frac{n_1}{n} $$

为建立公理化描述,定义似然函数:
$$ L(\theta) = \theta^{n_1}(1-\theta)^{n_2} \quad \text{其中} \quad n_1 + n_2 = n $$

首先对似然函数取自然对数($\ln$)进行化简:

$$ \ln L(\theta) = n_1 \ln \theta + n_2 \ln (1 - \theta) $$

对对数似然函数关于$\theta$求导并令导数为零:

$$ \frac{d \ln L(\theta)}{d \theta} = \frac{n_1}{\theta} + \frac{n_2}{1 - \theta} = 0 $$

求解上述方程得到$\theta$的极大似然估计:

$$ \hat{\theta} = \frac{n_1}{n_1 + n_2} = \frac{n_1}{n} $$

其中:$n = n_1 + n_2$为总试验次数

做道例题

image-20250609201334679

泰勒公式及常见泰勒展开式

带拉格朗日型余项的泰勒公式

$$ f(x)=f(x_{0})+\frac{f^{\prime}(x_{0})}{1 !}(x-x_{0})+\frac{f^{\prime\prime}(x_{0})}{2 !}(x-x_{0})^{2}+\cdots+\frac{f^{(n)}(x_{0})}{n !}(x-x_{0})^{n}+R_{n}(x)$$

在点$x_0$处的泰勒展开式,包含$n$阶导数项和拉格朗日型余项

拉格朗日型余项表达式

$$ R_{n}(x)=o\left[(x-x_{0})^{n}\right] $$

表示余项是$(x-x_0)^n$的高阶无穷小

带佩亚诺型余项的麦克劳林公式

$$ f(x)=f(0)+\frac{f^{\prime}(0)}{1 !}x+\frac{f^{\prime\prime}(0)}{2 !}x^{2}+\cdots+\frac{f^{(n)}(0)}{n !}x^{n}+R_{n}(x)$$

麦克劳林公式($x_0=0$的特殊情况)

佩亚诺型余项表达式

$$ R_{n}(x)=o\left[x^{n}\right]$$

佩亚诺余项表示$x^n$的高阶无穷小

解释

  • $f^{(n)}(x_0)$:函数在$x_0$处的$n$阶导数
  • $n!$:$n$的阶乘
  • $o[\cdot]$:高阶无穷小符号

常见函数的泰勒展开式

  • 指数函数

$$ e^{x}=1+\frac{1}{1 !}x+\frac{1}{2 !}x^{2}+\frac{1}{3 !}x^{3}+o\left(x^{3}\right) $$

  • 对数函数

$$ \ln (1+x)=x-\frac{1}{2} x^{2}+\frac{1}{3} x^{3}+o\left(x^{3}\right) $$

  • 正弦函数

$$ \sin x=x-\frac{1}{3 !}x^{3}+\frac{1}{5 !}x^{5}+o\left(x^{5}\right) $$

  • 余弦函数

$$ \cos x=1-\frac{1}{2 !}x^{2}+\frac{1}{4 !}x^{4}+o\left(x^{4}\right) $$

  • 反正弦函数

$$ \arcsin x=x+\frac{1}{2}\times\frac{x^{3}}{3}+\frac{1\times 3}{2\times 4}\times\frac{x^{5}}{5}+\frac{1\times 3\times 5}{2\times 4\times 6}\times\frac{x^{7}}{7}+o\left(x^{7}\right) $$

  • 分式函数

$$ \frac{1}{1-x}=1+x+x^{2}+x^{3}+o\left(x^{3}\right) $$

$$ \frac{1}{1+x} = 1 - x + x^{2} - \cdots + (-1)^{n}x^{n} $$

  • 幂函数

$$ (1+x)^{a}=1+\frac{a}{1 !}x+\frac{a(a-1)}{2 !}x^{2}+\frac{a(a-1)(a-2)}{3 !}x^{3}+o\left(x^{3}\right) $$

  • 对数函数展开

$$ \ln(1+x) = x - \frac{x^{2}}{2} + \cdots + (-1)^{n-1}\frac{x^{n}}{n} + o(x^{n}) $$