课程地址

第三章 机器学习基本理论

3.1 机器学习三要素

机器学习的方法一般主要由三部分构成:模型、策略和算法,可以认为:
机器学习方法 = 模型 + 策略 + 算法 机器学习方法 = 模型 + 策略 + 算法 机器学习方法=模型+策略+算法

  • 模型(model):总结数据的内在规律,用数学语言描述的参数系统
  • 策略(strategy):选取最优模型的评价准则
  • 算法(algorithm):选取最优模型的具体方法

3.2 机器学习方法分类

机器学习的方法种类繁多,并不存在一个统一的理论体系能够涵盖所有内容。从不同的角度,可以将机器学习的方法进行不同的分类:

  • 通常分类:按照有无监督,机器学习可以分为 有监督学习无监督学习 半监督学习,除此之外还有 强化学习
  • 按模型分类:根据模型性质,可以分为概率模型/非概率模型,线性/非线性模型等。
  • 按学习技巧分类:根据算法基于的技巧,可以分为贝叶斯学习、核方法等。
    在这里插入图片描述
    • 有监督学习:提供数据并提供数据对应结果的机器学习过程
    • 无监督学习:提供数据并且不提供数据对应结果的机器学习过程
    • 强化学习:通过与环境交互并获取延迟返回进而改进行为的学习过程

各种类型的机器学习方法可以用下图汇总展示:
在这里插入图片描述

3.3 建模流程

我们可以以监督学习为例,考察一下机器学习的具体过程。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
总结监督学习建模的整体流程如下:
在这里插入图片描述

3.4 特征工程

3.4.1 什么是特征工程

特征工程(Feature Engineering)是机器学习过程中非常重要的一步,指的是通过对原始数据的处理、转换和构造,生成新的特征或选择有效的特征,从而提高模型的性能。简单来说,特征工程是将原始数据转换为可以更好地表示问题的特征形式,帮助模型更好地理解和学习数据中的规律。优秀的特征工程可以显著提高模型的表现;反之,忽视特征工程可能导致模型性能欠佳。
实际上,特征工程是一个迭代过程。特征工程取决于具体情境。它需要大量的数据分析和领域知识。其中的原因在于,特征的有效编码可由所用的模型类型、预测变量与输出之间的关系以及模型要解决的问题来确定。在此基础上,辅以不同类型的数据集(如文本与图像)则可能更适合不同的特征工程技术。因此,要具体说明如何在给定的机器学习算法中最好地实施特征工程可能并非易事。

3.4.2 特征工程的内容

  • 特征选择
    从原始特征中挑选出与目标变量关系最密切的特征,剔除冗余、无关或噪声特征。这样可以减少模型的复杂度、加速训练过程、并减少过拟合的风险。
    特征选择不会创建新特征,也不会改变数据结构。
    • 过滤法(Filter Method)
      基于统计测试(如卡方检验、相关系数、信息增益等)来评估特征与目标变量之间的关系,选择最相关的特征。
    • 包裹法(Wrapper Method)
      使用模型(如递归特征消除 RFE)来评估特征的重要性,并根据模型的表现进行特征选择。
    • 嵌入法(Embedded Method)
      使用模型本身的特征选择机制(如决策树的特征重要性,L1正则化的特征选择)来选择最重要的特征。
  • 特征转换
    对数据进行数学或统计处理,使其变得更加适合模型的输入要求。
    • 归一化(Normalization)
      将特征缩放到特定的范围(通常是0到1之间)。适用于对尺度敏感的模型(如KNN、SVM)。
    • 标准化(Standardization)
      通过减去均值并除以标准差,使特征的分布具有均值0,标准差1。
    • 对数变换
      对于有偏态的分布(如收入、价格等),对数变换可以将其转化为更接近正态分布的形式。
    • 类别变量的编码
      • 独热编码(One-Hot Encoding):将类别型变量转换为二进制列,常用于无序类别特征。
        在这里插入图片描述

      • 标签编码(Label Encoding):将类别型变量映射为整数,常用于有序类别特征。
        在这里插入图片描述

      • 目标编码(Target Encoding):将类别变量的每个类别替换为其对应目标变量的平均值或其他统计量。

      • 频率编码(Frequency Encoding):将类别变量的每个类别替换为该类别在数据集中的出现频率。

  • 特征构造
    特征构造是基于现有的特征创造出新的、更有代表性的特征。通过组合、转换、或者聚合现有的特征,形成能够更好反映数据规律的特征。
    • 交互特征
      将两个特征组合起来,形成新的特征。例如,两个特征的乘积、和或差等。
      例如,将年龄与收入结合创建新的特征,可能能更好地反映某些模式。
    • 统计特征
      从原始特征中提取统计值,例如求某个时间窗口的平均值、最大值、最小值、标准差等。
      例如,在时间序列数据中,你可以从原始数据中提取每个小时、每日的平均值。
    • 日期和时间特征
      从日期时间数据中提取如星期几、月份、年份、季度等特征。
      例如,将“2000-01-01”转换为“星期几”、“是否节假日”、“月初或月末”等特征。
  • 特征降维
    当数据集的特征数量非常大时,特征降维可以帮助减少计算复杂度并避免过拟合。通过降维方法,可以在保持数据本质的情况下减少特征的数量。
    • 主成分分析(PCA)
      通过线性变换将原始特征映射到一个新的空间,使得新的特征(主成分)尽可能地保留数据的方差。
    • 线性判别分析(LDA)
      一种监督学习的降维方法,通过最大化类间距离与类内距离的比率来降维。
    • t-SNE(t-Distributed Stochastic Neighbor Embedding,t分布随机近邻嵌入)
      一种非线性的降维技术,特别适合可视化高维数据。
    • 自编码器(Auto Encoder)
      一种神经网络模型,通过压缩编码器来实现数据的降维。

3.4.3 常用方法

对于一个模型来说,有些特征可能很关键,而有些特征可能用处不大。
例如:

  • 某个特征取值较接近,变化很小,可能与结果无关。
  • 某几个特征相关性较高,可能包含冗余信息。
    因此,特征选择 在特征工程中是最基本、也最常见的操作。
    另外,在训练模型时有时也会遇到维度灾难,即特征数量过多。我们希望能在确保不丢失重要特征的前提下减少维度的数量,来降低训练模型的难度。所以在特征工程中,也经常会用到 特征降维 方法。
  1. 低方差过滤法
    对于特征的选择,可以直接基于方差来判断,这是最简单的。低方差的特征意味着该特征的所有样本值几乎相同,对预测影响极小,可以将其去掉。
from sklearn.feature_selection import VarianceThreshold

# 低方差过滤:删除方差低于 0.01 的特征
var_thresh = VarianceThreshold(threshold=0.01)
X_filtered = var_thresh.fit_transform(X)
  1. 相关系数法
    通过计算特征与目标变量或特征之间的相关性,筛选出高相关性特征(与目标相关)或剔除冗余特征(特征间高度相关)。
  • 皮尔逊相关系数

皮尔逊相关系数(Pearson Correlation)用于衡量两个变量的线性相关性,取值范围[−1,1]。

r = ∑ i = 1 n ( x i − x ˉ ) ( y i − y ˉ ) ∑ i = 1 n ( x i − x ˉ ) 2 ∑ i = 1 n ( y i − y ˉ ) 2 r=\frac{\sum_{i=1}^n(x_i−\bar{x})(y_i−\bar{y})}{\sqrt{\sum_{i=1}^n(x_i−\bar{x})^2}\sqrt{\sum_{i=1}^n(y_i−\bar{y})^2}} r=i=1n(xixˉ)2 i=1n(yiyˉ)2 i=1n(xixˉ)(yiyˉ)

  • 正相关:值接近1,说明特征随目标变量增加而增加。
  • 负相关:值接近-1,说明特征随目标变量增加而减少。
  • 无关:值接近0,说明特征和目标变量无明显关系。
    例如,现有一数据集包括不同渠道广告投放金额与销售额。
    使用pandas.DataFrame.corrwith(method=“pearson”)计算各个特征与标签间的皮尔逊相关系数。
import pandas as pd

advertising = pd.read_csv("data/advertising.csv")
advertising.drop(advertising.columns[0], axis=1, inplace=True)
advertising.dropna(inplace=True)
X = advertising.drop("Sales", axis=1)
y = advertising["Sales"]
# 计算皮尔逊相关系数
print(X.corrwith(y, method="pearson"))
# TV           0.782224
# Radio        0.576223
# Newspaper    0.228299
# dtype: float64

使用pandas.DataFrame.corr(method=“pearson”)计算皮尔逊相关系数矩阵。

import seaborn as sns
import matplotlib.pyplot as plt

# 计算皮尔逊相关系数矩阵
corr_matrix = advertising.corr(method="pearson")
# 可视化热力图
sns.heatmap(corr_matrix, annot=True, cmap="coolwarm", fmt=".2f")
plt.title("Feature Correlation Matrix")
plt.show()

在这里插入图片描述

  • 斯皮尔曼相关系数

斯皮尔曼相关系数(Spearman’s Rank Correlation Coefficient)的定义是等级变量之间的皮尔逊相关系数。用于衡量两个变量之间的单调关系,即当一个变量增加时,另一个变量是否总是增加或减少(不要求是线性关系)。适用于非线性关系或数据不符合正态分布的情况。

r s = 1 − 6 ∑ d i 2 n ( n 2 − 1 ) r_s=1−\frac{6∑d_i^2}{n(n^2−1)} rs=1n(n21)6di2
其中:

  • d i d_i di是两个变量的等级之差
  • n是样本数

斯皮尔曼相关系数的取值范围为[−1,1]:

  • ρ=1:完全正相关(一个变量增加,另一个变量也总是增加)。
  • ρ=−1:完全负相关(一个变量增加,另一个变量总是减少)。
  • ρ=0:无相关性。
    例如,现有一组每周学习时长与数学考试成绩的数据:
    在这里插入图片描述

按数值由小到大排出X、y的等级,并计算等级差:
XRXyRyd=RX−Ryd252552008565500107706111287580015108510003150100746040096727−11149809006358300
ρ = 1 − 6 ∑ d i 2 n ( n 2 − 1 ) = 1 − 6 × 2 10 ( 10 2 − 1 ) = 1 − 0.0121 = 0.9879 ρ=1−\frac{6∑d_i^2}{n(n^2−1)}=1−\frac{6×2}{10(10^2−1)}=1−0.0121=0.9879 ρ=1n(n21)6di2=110(1021)6×2=10.0121=0.9879
使用pandas.DataFrame.corrwith(method=“spearman”)计算斯皮尔曼相关系数。

import pandas as pd

# 每周学习时长
X = [[5], [8], [10], [12], [15], [3], [7], [9], [14], [6]]
# 数学考试成绩
y = [55, 65, 70, 75, 85, 50, 60, 72, 80, 58]
# 计算斯皮尔曼相关系数
X = pd.DataFrame(X)
y = pd.Series(y)
print(X.corrwith(y, method="spearman"))
# 0.987879
  1. 主成分分析(PCA)

主成分分析(Principal Component Analysis,PCA)是一种常用的降维技术,通过线性变换将高维数据投影到低维空间,同时保留数据的主要变化模式。

在这里插入图片描述

在这里插入图片描述

使用sklearn.decomposition.PCA进行主成分分析。参数n_components若为小数则表示保留多少比例的信息,为整数则表示保留多少个维度。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

n_samples = 1000
# 第1个主成分方向
component1 = np.random.normal(0, 1, n_samples)
# 第2个主成分方向
component2 = np.random.normal(0, 0.2, n_samples)
# 第3个方向(噪声,方差较小)
noise = np.random.normal(0, 0.1, n_samples)
# 构造3维数据
X = np.vstack([component1 - component2, component1 + component2, component2 + noise]).T

# 标准化
scaler = StandardScaler()
X_standardized = scaler.fit_transform(X)

# 应用PCA,将3维数据降维到2维
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_standardized)

# 可视化
# 转换前的3维数据可视化
fig = plt.figure(figsize=(12, 4))
ax1 = fig.add_subplot(121, projection="3d")
ax1.scatter(X[:, 0], X[:, 1], X[:, 2], c="g")
ax1.set_title("Before PCA (3D)")
ax1.set_xlabel("Feature 1")
ax1.set_ylabel("Feature 2")
ax1.set_zlabel("Feature 3")
# 转换后的2维数据可视化
ax2 = fig.add_subplot(122)
ax2.scatter(X_pca[:, 0], X_pca[:, 1], c="g")
ax2.set_title("After PCA (2D)")
ax2.set_xlabel("Principal Component 1")
ax2.set_ylabel("Principal Component 2")
plt.show()

3.5 模型评估和模型选择(重点)

3.5.1 损失函数

对于模型一次预测结果的好坏,需要有一个度量标准。
对于监督学习而言,给定一个输入X,选取的模型就相当于一个“决策函数”f,它可以输出一个预测结果f(X),而真实的结果(标签)记为Y。f(X) 和Y之间可能会有偏差,我们就用一个损失函数(loss function)来度量预测偏差的程度,记作 L(Y,f(X))。

  • 损失函数用来衡量模型预测误差的大小;损失函数值越小,模型就越好;
  • 损失函数是f(X)和Y的非负实值函数;

常见的损失函数有:

  1. 0-1损失函数
    L ( Y , f ( X ) ) = { 1 , Y ≠ f ( X ) 0 , Y = f ( X ) L(Y,f(X))=\begin{cases} 1, Y≠f(X) \\ 0, Y=f(X) \end{cases} L(Y,f(X))={1,Y=f(X)0,Y=f(X)
  2. 平方损失函数
    L ( Y , f ( X ) ) = ( Y − f ( X ) ) 2 L(Y,f(X))=(Y−f(X))^2 L(Y,f(X))=(Yf(X))2
  3. 绝对损失函数
    L ( Y , f ( X ) ) = ∣ Y − f ( X ) ∣ L(Y,f(X))=|Y−f(X)| L(Y,f(X))=Yf(X)
  4. 对数似然损失函数
    L ( Y , P ( Y ∣ X ) ) = − l o g P ( Y ∣ X ) L(Y,P(Y|X))=−logP(Y|X) L(Y,P(YX))=logP(YX)

3.5.2 经验误差

给定一个训练数据集,数据个数为n:
T = ( x 1 , y 1 ) , ( x 2 , y 2 ) , … , ( x n , y n ) T={(x_1, y_1),(x_2, y_2),…,(x_n, y_n)} T=(x1,y1),(x2,y2),,(xn,yn)
根据选取的损失函数,就可以计算出模型f(X)在训练集上的平均误差,称为训练误差,也被称作 经验误差(empirical error) 或 经验风险(empirical risk)。
R e m p ( f ) = 1 n ∑ i = 1 n L ( y i , f ( x i ) ) R_{emp}(f)=\frac{1}{n}\sum_{i=1}^nL(y_i,f(x_i)) Remp(f)=n1i=1nL(yi,f(xi))
类似地,在测试数据集上平均误差,被称为测试误差或者 泛化误差(generalization error)。
一般情况下对模型评估的策略,就是考察经验误差;当经验风险最小时,就认为取到了最优的模型。这种策略被称为 经验风险最小化(empirical risk minimization,ERM)。

3.5.3 欠拟合和过拟合

拟合(Fitting)是指机器学习模型在训练数据上学习到规律并生成预测结果的过程。理想情况下,模型能够准确地捕捉训练数据的模式,并且在未见过的新数据(测试数据)上也有良好的表现;即模型具有良好的 泛化能力
在这里插入图片描述
欠拟合(Underfitting):是指模型在训练数据上表现不佳,无法很好地捕捉数据中的规律。这样的模型不仅在训练集上表现不好,在测试集上也同样表现差。
在这里插入图片描述
过拟合(Overfitting):是指模型在训练数据上表现得很好,但在测试数据或新数据上表现较差的情况。过拟合的模型对训练数据中的噪声或细节过度敏感,把训练样本自身的一些特点当作了所有潜在样本都会具有的一般性质,从而失去了泛化能力。
在这里插入图片描述
产生欠拟合和过拟合的根本原因,是模型的复杂度过低或过高,从而导致测试误差(泛化误差)偏大。

  • 欠拟合:模型在训练集和测试集上误差都比较大。模型过于简单,高偏差。
  • 过拟合:模型在训练集上误差较小,但在测试集上误差较大。模型过于复杂,高方差。

在这里插入图片描述

  1. 产生原因与解决办法
  • 欠拟合
    产生原因:
    • 模型复杂度不足:模型过于简单,无法捕捉数据中的复杂关系。
    • 特征不足:输入特征不充分,或者特征选择不恰当,导致模型无法充分学习数据的模式。
    • 训练不充分:训练过程中迭代次数太少,模型没有足够的时间学习数据的规律。
    • 过强的正则化:正则化项设置过大,强制模型过于简单,导致模型无法充分拟合数据。
      解决办法:
      • 增加模型复杂度:选择更复杂的模型。
      • 增加特征或改进特征工程:添加更多的特征或通过特征工程来创造更有信息量的特征。
      • 增加训练时间:增加训练的迭代次数,让模型有更多机会去学习。
      • 减少正则化强度:如果使用了正则化,尝试减小正则化的权重,以让模型更灵活。
  • 过拟合
    产生原因:
    • 模型复杂度过高:模型过于复杂,参数太多。
    • 训练数据不足:数据集太小,模型能记住训练数据的细节,但无法泛化到新数据。
    • 特征过多:特征太多,模型可能会“记住”数据中的噪声,而不是学到真正的规律。
    • 训练过长:训练时间过长,导致模型学习到训练数据中的噪声,而非数据的真正规律。
      解决办法:
      • 减少模型复杂度:降低模型的参数数量、使用简化的模型或降维来减小模型复杂度。
      • 增加训练数据:收集更多数据,或通过数据增强来增加训练数据的多样性。
      • 使用正则化:引入L1、L2正则化,避免过度拟合训练数据。
      • 交叉验证:使用交叉验证技术评估模型在不同数据集上的表现,以减少过拟合的风险。
      • 早停:训练时,当模型的验证损失不再下降时,提前停止训练,避免过度拟合训练集。
  1. 使用多项式在x∈[−3,3]上拟合sin(x):
函数泰勒级数(麦克劳林展开)关键特点
sinx x − x 3 3 ! + x 5 5 ! − x 7 7 ! + . . . x-\frac{x^3}{3!}+\frac{x^5}{5!}-\frac{x^7}{7!}+... x3!x3+5!x57!x7+...只有奇数次项,正负交替
cosx 1 − x 2 2 ! + x 4 4 ! − x 6 6 ! + . . . 1-\frac{x^2}{2!}+\frac{x^4}{4!}-\frac{x^6}{6!}+... 12!x2+4!x46!x6+...只有偶数次项,正负交替
e x e^x ex(指数函数) 1 + x + x 2 2 ! + x 3 3 ! + x 4 4 ! + . . . 1+x+\frac{x^2}{2!}+\frac{x^3}{3!}+\frac{x^4}{4!}+... 1+x+2!x2+3!x3+4!x4+...所有次项都有,系数全为正
ln(1+x) x − x 2 2 ! + x 3 3 ! − x 4 4 ! + . . . x-\frac{x^2}{2!}+\frac{x^3}{3!}-\frac{x^4}{4!}+... x2!x2+3!x34!x4+...仅在 −1<x≤1 有效
( 1 + x ) k (1+x)^k (1+x)k(二项式) 1 + k x + k ( k − 1 ) 2 ! x 2 + k ( k − 1 ) ( k − 2 ) 3 ! x 3 1+kx+\frac{k(k-1)}{2!}x^2+\frac{k(k-1)(k-2)}{3!}x^3 1+kx+2!k(k1)x2+3!k(k1)(k2)x3特殊情况:k=2 就是 ( 1 + x ) 2 = 1 + 2 x + x 2 (1+x)^2 =1+2x+x^2 (1+x)2=1+2x+x2(有限项)
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error

plt.rcParams["font.sans-serif"] = ["KaiTi"]
plt.rcParams["axes.unicode_minus"] = False

def polynomial(x, degree):
    """构成多项式,返回 [x^1,x^2,x^3,...,x^n]"""
    return np.hstack([x**i for i in range(1, degree + 1)])

# 生成随机数据
X = np.linspace(-3, 3, 300).reshape(-1, 1)
y = np.sin(X) + np.random.uniform(-0.5, 0.5, 300).reshape(-1, 1)
fig, ax = plt.subplots(1, 3, figsize=(15, 4))
ax[0].plot(X, y, "yo")
ax[1].plot(X, y, "yo")
ax[2].plot(X, y, "yo")

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

# 创建线性回归模型
model = LinearRegression()

# 欠拟合
x_train1 = x_train
x_test1 = x_test
model.fit(x_train1, y_train)  # 模型训练
y_pred1 = model.predict(x_test1)  # 预测
ax[0].plot(np.array([[-3], [3]]), model.predict(np.array([[-3], [3]])), "c")  # 绘制曲线
ax[0].text(-3, 1, f"测试集均方误差:{mean_squared_error(y_test, y_pred1):.4f}")
ax[0].text(-3, 1.3, f"训练集均方误差:{mean_squared_error(y_train, model.predict(x_train1)):.4f}")

# 恰好拟合
x_train2 = polynomial(x_train, 5)
x_test2 = polynomial(x_test, 5)
model.fit(x_train2, y_train)  # 模型训练
y_pred2 = model.predict(x_test2)  # 预测
ax[1].plot(X, model.predict(polynomial(X, 5)), "k")  # 绘制曲线
ax[1].text(-3, 1, f"测试集均方误差:{mean_squared_error(y_test, y_pred2):.4f}")
ax[1].text(-3, 1.3, f"训练集均方误差:{mean_squared_error(y_train, model.predict(x_train2)):.4f}")

# 过拟合
x_train3 = polynomial(x_train, 20)
x_test3 = polynomial(x_test, 20)
model.fit(x_train3, y_train)  # 模型训练
y_pred3 = model.predict(x_test3)  # 预测
ax[2].plot(X, model.predict(polynomial(X, 20)), "r")  # 绘制曲线
ax[2].text(-3, 1, f"测试集均方误差:{mean_squared_error(y_test, y_pred3):.4f}")
ax[2].text(-3, 1.3, f"训练集均方误差:{mean_squared_error(y_train, model.predict(x_train3)):.4f}")
plt.show()

在这里插入图片描述
当多项式次数较低时,模型过于简单,拟合效果较差。
当多项式次数增加后,模型复杂度适中,拟合效果较好,训练误差和测试误差均较低。
当多项式次数继续增加,模型变得过于复杂,过度学习了噪声,导致训练误差较低而测试误差较高。

3.5.4 正则化

正则化(Regularization)是一种在训练机器学习模型时,在损失函数中添加额外项,来惩罚过大的参数,进而限制模型复杂度、避免过拟合,提高模型泛化能力的技术。
符合“奥卡姆剃刀原则”(如无必要,勿增实体)

如在平方损失函数中加入正则化项 λ ∑ i = 1 k ω i 2 λ\sum_{i=1}^kω_i^2 λi=1kωi2
L o s s = 1 n ( ∑ i = 1 n ( f ( x i ) − y i ) 2 + λ ∑ i = 1 k ω i 2 Loss=\frac{1}{n}(\sum_{i=1}^n(f(x_i)−y_i)^2+λ\sum_{i=1}^kω_i^2 Loss=n1(i=1n(f(xi)yi)2+λi=1kωi2

  • 原损失函数 ∑ i = 1 n ( f ( x i ) − y i ) 2 \sum_{i=1}^n(f(x_i)−y_i)^2 i=1n(f(xi)yi)2的目的:更好的拟合训练数据集。
  • 正则化项 λ ∑ i = 1 k ω i 2 λ\sum_{i=1}^kω_i^2 λi=1kωi2 的目的:减小参数的大小,从而降低模型的复杂度。
    这里的 λ 是 正则化系数,用来表示惩罚项的权重。正则化系数不属于模型的参数,无法通过训练学习得到,需要在模型训练开始之前手动设置,这种参数被称为“超参数”。
    两者相互平衡,在模型的拟合能力(偏差)和复杂度之间找到最佳折中。
    常见的正则化技术有L1正则化和L2正则化。
  1. L1正则化(Lasso 回归)
    L1正则化在损失函数中加入参数的绝对值之和:
    L o s s L 1 = 原 L o s s + λ ∑ i = 1 k ∣ ω i ∣ Loss_{L1}=原Loss+λ\sum_{i=1}^k|ω_i| LossL1=Loss+λi=1kωi
    L1正则化通过惩罚模型参数的绝对值,使得部分权重趋近0甚至变为0。这会导致特征选择,即模型会自动“丢弃”一些不重要的特征。L1正则化有助于创建稀疏模型(即许多参数为0)。在解决回归问题时,使用L1正则化也被称为“Lasso回归”。
    λ 超参数控制着正则化的强度。较大的 λ 值意味着强烈的正则化,会使模型更简单,可能导致欠拟合。而较小的 λ 值则会使模型更复杂,可能导致过拟合。
  2. L2正则化(Ridge回归,岭回归)
    L2正则化在损失函数中加入参数的平方之和:
    L o s s L 2 = 原 L o s s + λ ∑ i = 1 k ω i 2 Loss_{L2}=原Loss+λ\sum_{i=1}^kω_i^2 LossL2=Loss+λi=1kωi2
    L2正则化通过惩罚模型参数的平方,使得所有参数都变得更小,但不会将参数强行压缩为0。它会使得模型尽量平滑,从而防止过拟合。
    在解决回归问题时,使用L2正则化也被称为“岭回归”。
  3. ElasticNet正则化(弹性网络回归)
    ElasticNet正则化结合了L1和L2正则化,通过调整两个正则化项的比例来取得平衡,从而同时具备稀疏性和稳定性的优点。
    L o s s E l a s t i c N e t = 原 L o s s + λ ( α ∑ i = 1 n ∣ ω i ∣ + 1 − α 2 ∑ i = 1 n ω i 2 ) Loss_{ElasticNet}=原Loss+λ(α\sum_{i=1}^n|ω_i|+\frac{1−α}{2}\sum_{i=1}^nω_i^2) LossElasticNet=Loss+λ(αi=1nωi+21αi=1nωi2)
    α∈[0,1],决定L1和L2的权重。
  4. 正则化案例
    同样以使用多项式在x∈[−3,3]上拟合sin(x)为例,分别不使用正则化、使用L1正则化、使用L2正则化进行拟合。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression, Lasso, Ridge
from sklearn.metrics import mean_squared_error

plt.rcParams["font.sans-serif"] = ["KaiTi"]
plt.rcParams["axes.unicode_minus"] = False

def polynomial(x, degree):
    """构成多项式,返回 [x^1,x^2,x^3,...,x^n]"""
    return np.hstack([x**i for i in range(1, degree + 1)])

# 生成随机数据
X = np.linspace(-3, 3, 300).reshape(-1, 1)
y = np.sin(X) + np.random.uniform(-0.5, 0.5, X.size).reshape(-1, 1)
fig, ax = plt.subplots(2, 3, figsize=(15, 8))
ax[0, 0].plot(X, y, "yo")
ax[0, 1].plot(X, y, "yo")
ax[0, 2].plot(X, y, "yo")

# 划分训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
x_train1 = polynomial(x_train, 20)
x_test1 = polynomial(x_test, 20)

# 拟合
model = LinearRegression()
model.fit(x_train1, y_train)  # 模型训练
y_pred3 = model.predict(x_test1)  # 预测
ax[0, 0].plot(X, model.predict(polynomial(X, 20)), "r")  # 绘制曲线
ax[0, 0].text(-3, 1, f"测试集均方误差:{mean_squared_error(y_test, y_pred3):.4f}")
ax[1, 0].bar(np.arange(20), model.coef_.reshape(-1))  # 绘制所有系数

# L1正则化-Lasso回归
lasso = Lasso(alpha=0.01)
lasso.fit(x_train1, y_train)  # 模型训练
y_pred3 = lasso.predict(x_test1)  # 预测
ax[0, 1].plot(X, lasso.predict(polynomial(X, 20)), "r")  # 绘制曲线
ax[0, 1].text(-3, 1, f"测试集均方误差:{mean_squared_error(y_test, y_pred3):.4f}")
ax[0, 1].text(-3, 1.2, "Lasso回归")
ax[1, 1].bar(np.arange(20), lasso.coef_)  # 绘制所有系数

# L2正则化-岭回归
ridge = Ridge(alpha=1)
ridge.fit(x_train1, y_train)  # 模型训练
y_pred3 = ridge.predict(x_test1)  # 预测
ax[0, 2].plot(X, ridge.predict(polynomial(X, 20)), "r")  # 绘制曲线
ax[0, 2].text(-3, 1, f"测试集均方误差:{mean_squared_error(y_test, y_pred3):.4f}")
ax[0, 2].text(-3, 1.2, "岭回归")
ax[1, 2].bar(np.arange(20), ridge.coef_)  # 绘制所有系数
plt.show()

在这里插入图片描述

3.5.5 交叉验证

交叉验证(Cross-Validation)是一种评估模型泛化能力的方法,通过将数据集划分为多个子集,反复进行训练和验证,以减少因单次数据划分带来的随机性误差。通过交叉验证能更可靠地估计模型在未知数据上的表现,亦能避免因单次数据划分不合理导致的模型过拟合或欠拟合。

  1. 简单交叉验证(Hold-Out Validation)
    将数据划分为训练集和验证集(如70%训练,30%验证)。结果受单次划分影响较大,可能高估或低估模型性能。

  2. k折交叉验证(k-Fold Cross-Validation)
    将数据均匀分为k个子集(称为“折”),每次用k−1折训练,剩余1折验证,重复k次后取平均性能。充分利用数据,结果更稳定。
    在这里插入图片描述

  3. 留一交叉验证(Leave-One-Out,LOO)
    每次仅留一个样本作为验证集,其余全部用于训练,重复直到所有样本都被验证一次。适用于小数据集,计算成本极高。

3.6 模型求解算法

正则化可以有效防止过拟合,增强模型的泛化能力。这时模型的评估策略,就是让结构化的经验风险最小,即增加了正则化项的损失函数最小,称为 结构风险最小化(Structural Risk Minimization,SRM)。
m i n 1 n ( ∑ i = 1 n L ( y i , f ( x i ) ) + λ J ( θ ) ) min\frac{1}{n}(\sum_{ i=1}^{n}L(y_i,f(x_i))+λJ(θ) ) minn1(i=1nL(yi,f(xi))+λJ(θ))
这其实就是求解一个 最优化问题。代入训练集所有数据 ( x i , y i ) (x_i, y_i) (xi,yi),要求最小值的目标函数就是模型中参数 θ的函数。
具体求解的算法,可以利用数学公式直接计算解析解,也可以使用迭代算法。

3.6.1 解析法

如果模型损失函数的最小值可以通过数学公式进行严格推导,得到一个解析解,那么就直接得到了最优模型的全部参数。这种方法称作解析法。

  1. 特点
  • 适用条件:目标函数必须可导,且导数方程有解析解。
  • 优点:直接且精确;计算高效;
  • 缺点:适用条件较为苛刻;特征维度较大时,矩阵求逆计算复杂度极高。
  1. 应用示例
  • 线性回归问题:可以采用“最小二乘法”求得解析解。
    L o s s M S E = 1 n ( X β − y ) T ( X β − y ) Loss_{MSE}=\frac{1}{n}(Xβ−y)^T(Xβ−y) LossMSE=n1(Xβy)T(Xβy)
    ∇ L o s s M S E = 2 n X T ( X β − y ) = 0 ∇Loss_{MSE}=\frac{2}{n}X^T(Xβ−y)=0 LossMSE=n2XT(Xβy)=0
    β = ( X T X ) − 1 X T y β=(X^TX)^{−1}X^Ty β=(XTX)1XTy
  • 线性回归L2正则化(Ridge回归,岭回归)
    可以得到解析解如下:
    L o s s L 2 = 1 n ( X β − y ) T ( X β − y ) + 1 n λ β T β Loss_{L2}=\frac{1}{n}(Xβ−y)^T(Xβ−y)+\frac{1}{n}λβ^Tβ LossL2=n1(Xβy)T(Xβy)+n1λβTβ
    ∇ L o s s L 2 = 2 n X T ( X β − y ) + 2 n λ β = 0 ∇Loss_{L2}=\frac{2}{n}X^T(Xβ−y)+\frac{2}{n}λβ=0 LossL2=n2XT(Xβy)+n2λβ=0
    β = ( X T X + λ I ) − 1 X T y β=(X^TX+λI)^{−1}X^Ty β=(XTX+λI)1XTy
    由于加入的对角矩阵 λI 就像一条“山岭”,因此L2正则化也称作“岭回归”。

3.6.2 梯度下降法(重点)

梯度下降法(gradient descent)是一种常用的一阶优化方法,是求解无约束优化问题最简单、最经典的方法之一。梯度下降法是迭代算法,基本思路就是先选取一个适当的初始值 θ 0 θ_0 θ0,然后沿着梯度方向或者负梯度方向,不停地更新参数,最终取到极小值。
在这里插入图片描述

  • 梯度方向:函数变化增长最快的方向(变量沿此方向变化时函数增长最快)
  • 负梯度方向:函数变化减少最快的方向(变量沿此方向变化时函数减少最快)
    因为损失函数是系数的函数,那么如果系数沿着损失函数的负梯度方向变化,此时损失函数减少最快,能够以最快速度下降到极小值。
    θ k + 1 = θ k − α ⋅ ▽ L ( θ k ) θ_{k+1}=θ_k− α·▽L(θ_k) θk+1=θkαL(θk)
    这里的 ▽ L ( θ k ) ▽L(θ_k) L(θk)是参数取值为 θ k θ_k θk 时损失函数 L 的梯度; α 是每次迭代的“步长”,被称为“学习率”。学习率也是一个常见的超参数,需要手动设置,选择不当会导致收敛失败。
  1. 特点
  • 梯度下降不一定能够找到全局的最优解,有可能是一个局部最优解。
  • 优点:适用性广;计算简单;
  • 缺点:收敛速度慢;可能陷入局部最优。
  1. 分类
  • 批量梯度下降(Batch Gradient Descent,BGD)
    每次迭代使用全部训练数据计算梯度。
    • 优点:稳定收敛。
    • 缺点:计算开销大。
  • 随机梯度下降(Stochastic Gradient Descent,SGD)
    每次迭代随机选取一个样本计算梯度。
    • 优点:速度快,适合大规模数据。
    • 缺点:梯度更新方向不稳定,优化过程震荡较大,可能难以收敛。
  • 小批量梯度下降(Mini-batch Gradient Descent,MBGD)
    每次迭代使用一小批样本(如32、64个)计算梯度。
    平衡了BGD的稳定性和SGD的速度,是最常用的方法。
  1. 梯度下降法计算步骤
    (1)初始化参数:随机选择初始参数
    (2)计算梯度:在当前参数下,计算损失函数的梯度
    (3)更新参数:沿负梯度方向调整参数
    (4)重复迭代:直到满足停止条件(如梯度接近零、达到最大迭代次数等)
  2. 代码实现
    我们以一个单变量函数为例,介绍梯度下降法的代码实现。
    f ( x ) = x 2 f(x)=x^2 f(x)=x2,求x为何值时,f(x)=2。
    目标函数 J ( x ) = ( f ( x ) − 2 ) 2 = ( x 2 − 2 ) 2 J(x)=(f(x)−2)^2=(x^2−2)^2 J(x)=(f(x)2)2=(x22)2,原问题等价于求x为何值时目标函数取得最小值。
    使用梯度下降法求解。
  • x初始值取1
  • 学习率α取0.1
  • d J ( x ) d x = 2 × ( x 2 − 2 ) × 2 × x = 4 x 3 − 8 x \frac{dJ(x)}{dx}=2×(x^2−2)×2×x=4x^3−8x dxdJ(x)=2×(x22)×2×x=4x38x
    第1步: x 1 = 1 , J ( x 1 ) = 1 , J ( x 1 ) ′ = − 4 , x 2 = x 1 − α J ( x 1 ) ′ = 1.4 x_1=1,J(x_1)=1,J(x_1)'=−4,x_2=x_1−αJ(x_1)'=1.4 x1=1J(x1)=1J(x1)=4x2=x1αJ(x1)=1.4
    第2步: x 2 = 1.4 , J ( x 2 ) = 0.0016 , J ( x 2 ) ′ = − 0.2240 , x 3 = x 2 − α J ( x 2 ) ′ = 1.4224 x_2=1.4,J(x_2)=0.0016,J(x_2)'=−0.2240,x_3=x_2−αJ(x_2)'=1.4224 x2=1.4J(x2)=0.0016J(x2)=0.2240x3=x2αJ(x2)=1.4224

    第n步: x n = 1.414213 , J ( x n ) = 2.53 × 10 − 12 , J ( x n ) ′ = 8.8817 × 10 − 15 x_n=1.414213,J(x_n)=2.53×10^{−12},J(x_n)'=8.8817×10^{−15} xn=1.414213J(xn)=2.53×1012J(xn)=8.8817×1015
    在这里插入图片描述

示例代码:

def J(x):
    """目标函数"""
    return (x**2 - 2) ** 2

def gradient(x):
    """梯度"""
    return 4 * x**3 - 8 * x

x = 1  # x的初始值
alpha = 0.1  # 学习率
while (j := J(x)) > 1e-30:  # 当目标函数的值小于10的-30次幂时停止计算
    print(f"x={x}\tJ={j}")
    grad = gradient(x)  # 求解梯度
    x = x - alpha * grad  # 更新参数
  1. 应用示例
  • L1正则化(Lasso回归)(模型求解方法默认是坐标下降法)
    梯度下降法求解的推导过程如下:
    L o s s L 1 = 1 n ( ∑ i = 1 n ( f ( x i ) − y i ) 2 + λ ∑ j = 1 k ∣ ω j ∣ ) Loss_{L1}=\frac{1}{n}(\sum_{i=1}^n(f(x_i)−y_i)^2+λ\sum_{j=1}^k|ω_j|) LossL1=n1(i=1n(f(xi)yi)2+λj=1kωj)
    ∂ L o s s L 1 ∂ ω j = 1 n ( 2 ∑ i = 1 n x i j ( f ( x i ) − y i ) + λ ∙ s i g n ( ω j ) ) \frac{∂Loss_{L1}}{∂ω_j}=\frac{1}{n}(2\sum_{i=1}^nx_{ij}(f(x_i)−y_i)+λ∙sign(ω_j)) ωjLossL1=n1(2i=1nxij(f(xi)yi)+λsign(ωj))
    其中 s i g n ( ω j ) = { 1 , ω j > 0 0 , ω j = 0 − 1 , ω j < 0 其中sign(ω_j)=\begin{cases} 1,ω_j>0\\ 0,ω_j=0\\ −1,ω_j<0 \end{cases} 其中sign(ωj)= 1ωj>00ωj=01ωj<0
    参数更新: ω j ← ω j − α ( 2 n ∑ i = 1 n x i j ( f ( x i ) − y i ) + λ n ∙ s i g n ( ω j ) ) 参数更新:ω_j←ω_j−α(\frac{2}{n}\sum_{i=1}^{n}x_{ij}(f(x_i)−y_i)+\frac{λ}{n}∙sign(ω_j)) 参数更新:ωjωjα(n2i=1nxij(f(xi)yi)+nλsign(ωj))
    可见L1正则化项的梯度是一个常数 λ n \frac{λ}{n} nλ,当 ω j ω_j ωj很小时会直接变成0,导致稀疏性。
  • L2正则化(Ridge回归,岭回归)
    梯度下降法求解的推导过程如下:
    L o s s L 2 = 1 n ( ∑ i = 1 n ( f ( x i ) − y i ) 2 + λ ∑ j = 1 k ω j 2 Loss_{L2}=\frac{1}{n}(\sum_{i=1}{n}(f(x_i)−y_i)^2+λ\sum_{j=1}^kω_j^2 LossL2=n1(i=1n(f(xi)yi)2+λj=1kωj2
    ∂ L o s s L 2 ∂ ω j = 1 n ( 2 ∑ i = 1 n x i j ( f ( x i ) − y i ) + 2 λ ω j ) \frac{∂Loss_{L2}}{∂ω_j}=\frac{1}{n}(2\sum_{i=1}^nx_{ij}(f(x_i)−y_i)+2λω_j) ωjLossL2=n1(2i=1nxij(f(xi)yi)+2λωj)
    梯度更新: ω j ← ω j − α ( 2 n ∑ i = 1 n x i j ( f ( x i ) − y i ) + 2 λ n ω j ) 梯度更新:ω_j←ω_j−α(\frac{2}{n}\sum_{i=1}^{n}x_{ij}(f(x_i)−y_i)+\frac{2λ}{n}ω_j) 梯度更新:ωjωjα(n2i=1nxij(f(xi)yi)+n2λωj)
    可见L2正则化项的梯度是 2 λ n ω j \frac{2λ}{n}ω_j n2λωj,相当于在每次更新时都对 ω j ω_j ωj 进行缩小,但不会直接变为0。

3.6.3 牛顿法和拟牛顿法(了解)

牛顿法也是求解无约束最优化问题的常用方法,核心思想是利用目标函数的二阶导数信息,通过迭代逐渐逼近极值点。
θ k + 1 = θ k − H − 1 ( θ k ) ⋅ ▽ L ( θ k ) θ_{k+1}=θ_k− H^{−1}(θ_k)·▽L(θ_k) θk+1=θkH1(θk)L(θk)
这里的 H − 1 ( θ k ) H^{−1}(θ_k) H1(θk)表示损失函数 L 黑塞矩阵的逆在点 θ k θ_k θk 的取值。

  • 优点:收敛速度快;精度高;
  • 缺点:计算复杂;可能发散。
    由于牛顿法中需要计算黑塞矩阵的逆 H − 1 ( θ k ) H^{−1}(θ_k) H1(θk),这一步比较复杂;所以可以考虑用一个n阶正定矩阵来近似代替它,这种方法称为“拟牛顿法”。
    牛顿法和拟牛顿法一般用于解决中小规模的凸优化问题。

3.7 模型评估指标(重点)

对学习的泛化性能进行评估,不仅需要有效可行的实验估计方法,还需要有衡量模型泛化能力的评价指标,也叫做性能度量(performance measure)。

3.7.1 回归模型评价指标

模型的评价指标用于衡量模型在训练集或测试集上的性能,评估结果反映了模型预测的准确性和泛化能力。
对于回归问题,最常用的性能度量是“均方误差” (Mean Squared Error,MSE)。

  1. 平均绝对误差(MAE)
    M A E = 1 n ∑ i = 1 n ∣ f ( x i ) − y i ∣ MAE=\frac{1}{n}\sum_{i=1}^n|f(x_i)−y_i| MAE=n1i=1nf(xi)yi
  • MAE对异常值不敏感,解释直观。适用于数据包含异常值的场景。
  1. 均方误差(MSE)
    M S E = 1 n ∑ i = 1 n ( f ( x i ) − y i ) 2 MSE=\frac{1}{n}\sum_{i=1}^{n}(f(x_i)−y_i)^2 MSE=n1i=1n(f(xi)yi)2
  • MSE会放大较大误差,对异常值敏感。适用于需要惩罚大误差的场景。
  1. 均方根误差(RMSE)
    R M S E = 1 n ∑ i = 1 n ( f ( x i ) − y i ) 2 RMSE=\sqrt{\frac{1}{n}\sum_{i=1}^n(f(x_i)−y_i)^2} RMSE=n1i=1n(f(xi)yi)2
  • 与MSE类似,但量纲与目标变量一致。适用于需要直观误差量纲的场景。如果一味地试图降低RMSE,可能会导致模型对异常值也拟合度很高,容易过拟合。
  1. R²(决定系数)
    R 2 = 1 − ∑ i = 1 n ( f ( x i ) − y i ) 2 ∑ i = 1 n ( y i − y ˉ ) 2 R^2=1−\frac{\sum_{i=1}^n(f(x_i)−y_i)^2}{\sum_{i=1}^n(y_i−\bar{y})^2} R2=1i=1n(yiyˉ)2i=1n(f(xi)yi)2
  • 衡量模型对目标变量的解释能力,越接近1越好,对异常值敏感。

3.7.2 分类模型评价指标

对于分类问题,最常用的指标就是“准确率”(Accuracy),它定义为分类器对测试集正确分类的样本数与总样本数之比。此外还有一系列常用的评价指标。

  1. 混淆矩阵
    混淆矩阵(Confusion Matrix)是用于评估分类模型性能的工具,展示了模型预测结果与实际标签的对比情况。
    对于二分类问题,混淆矩阵是一个2×2矩阵:
    在这里插入图片描述
    例如,有10个样本。6个是猫,4个是狗。假设以猫为正例,模型预测对了5个猫,2个狗。
    在这里插入图片描述
    使用sklearn.metrics.confusion_matrix查看混淆矩阵:
import pandas as pd
import seaborn as sns
from sklearn.metrics import confusion_matrix

label = ["猫", "狗"]  # 标签
y_true = ["猫", "猫", "猫", "猫", "猫", "猫", "狗", "狗", "狗", "狗"]  # 真实值
y_pred1 = ["猫", "猫", "狗", "猫", "猫", "猫", "猫", "猫", "狗", "狗"]  # 预测值
matrix1 = confusion_matrix(y_true, y_pred1, labels=label)  # 混淆矩阵
print(pd.DataFrame(matrix1, columns=label, index=label))
sns.heatmap(matrix1, annot=True, fmt='d', cmap='Greens')
  1. 准确率(Accuracy)
    正确预测的比例。
    A c c u r a c y = T P + T N T P + T N + F P + F N Accuracy=\frac{TP+TN}{TP+TN+FP+FN} Accuracy=TP+TN+FP+FNTP+TN
    在这里插入图片描述
    上述案例中,准确率= 5 + 2 10 \frac{5+2}{10} 105+2=0.7。
from sklearn.metrics import accuracy_score

label = ["猫", "狗"]  # 标签
y_true = ["猫", "猫", "猫", "猫", "猫", "猫", "狗", "狗", "狗", "狗"]  # 真实值
y_pred1 = ["猫", "猫", "狗", "猫", "猫", "猫", "猫", "猫", "狗", "狗"]  # 预测值
accuracy = accuracy_score(y_true, y_pred1)
print(accuracy)
  1. 精确率(Precision)
    预测为正例的样本中实际为正例的比例,也叫查准率。
    P r e c i s i o n = T P T P + F P Precision=\frac{TP}{TP+FP} Precision=TP+FPTP
    在这里插入图片描述
    上述案例中,精确率= 5 5 + 2 \frac{5}{5+2} 5+25=0.7143。
from sklearn.metrics import precision_score

label = ["猫", "狗"]  # 标签
y_true = ["猫", "猫", "猫", "猫", "猫", "猫", "狗", "狗", "狗", "狗"]  # 真实值
y_pred1 = ["猫", "猫", "狗", "猫", "猫", "猫", "猫", "猫", "狗", "狗"]  # 预测值
precision = precision_score(y_true, y_pred1, pos_label="猫")  # pos_label指定正例
print(precision)
  1. 召回率(Recall)
    实际为正类的样本中预测为正类的比例,也叫查全率。
    R e c a l l = T P T P + F N Recall=\frac{TP}{TP+FN} Recall=TP+FNTP
    在这里插入图片描述
    上述案例中,召回率= 5 5 + 1 \frac{5}{5+1} 5+15=0.8333。
from sklearn.metrics import recall_score

label = ["猫", "狗"]  # 标签
y_true = ["猫", "猫", "猫", "猫", "猫", "猫", "狗", "狗", "狗", "狗"]  # 真实值
y_pred1 = ["猫", "猫", "狗", "猫", "猫", "猫", "猫", "猫", "狗", "狗"]  # 预测值
recall = recall_score(y_true, y_pred1, pos_label="猫")  # pos_label指定正例
print(recall)
  1. F1分数(F1 Score)
    精确率和召回率的调和平均。
    F 1 S c o r e = 2 × P r e c i s i o n × R e c a l l P r e c i s i o n + R e c a l l F1 Score=\frac{2×Precision×Recall}{Precision+Recall} F1Score=Precision+Recall2×Precision×Recall
    上述案例中,
    F 1 分数 = 2 × 5 5 + 2 × 5 5 + 1 5 5 + 2 + 5 5 + 1 = 0.7692 F1分数=\frac{2×\frac{5}{5+2}×\frac{5}{5+1}}{\frac{5}{5+2}+\frac{5}{5+1}}=0.7692 F1分数=5+25+5+152×5+25×5+15=0.7692
from sklearn.metrics import f1_score

label = ["猫", "狗"]  # 标签
y_true = ["猫", "猫", "猫", "猫", "猫", "猫", "狗", "狗", "狗", "狗"]  # 真实值
y_pred1 = ["猫", "猫", "狗", "猫", "猫", "猫", "猫", "猫", "狗", "狗"]  # 预测值
f1 = f1_score(y_true, y_pred1, pos_label="猫")  # pos_label指定正例
print(f1)

在代码中,我们可通过sklearn.metrics.classification_report生成分类任务的评估报告,包括精确率、召回率、F1分数等。

from sklearn.metrics import classification_report

report = classification_report(y_true, y_pred, labels=[], target_names=None)
# y_true:真实标签
# y_pred:预测的标签
# labels:可选,指定需要计算的类别列表(默认计算所有出现过的类别)
# target_names:可选,类别名称(默认使用 labels 指定的类别号)

例如:

from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report

# 生成一个二分类数据集
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=100)

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

# 训练一个逻辑回归模型
model = LogisticRegression()
model.fit(x_train, y_train)

# 预测
y_pred = model.predict(x_test)

# 生成分类报告
report = classification_report(y_test, y_pred)
print(report)
  1. ROC曲线
  • 真正例率(TPR):实际为正例,被预测为正例的比例,即召回率。
  • 假正例率(FPR):实际为负例,被预测为正例的比例。
  • 阈值(Threshold):根据阈值将概率转换为类别标签。
    T P R = T P 实际正例数 = T P T P + F N TPR=\frac{TP}{实际正例数}=\frac{TP}{TP+FN} TPR=实际正例数TP=TP+FNTP
    F P R = F P 实际负例数 = F P F P + T N FPR=\frac{FP}{实际负例数}=\frac{FP}{FP+TN} FPR=实际负例数FP=FP+TNFP
    ROC曲线(Receiver Operating Characteristic Curve,受试者工作特征)是评估二分类模型性能的工具,以假正例率(FPR)为横轴,以真正例率(TPR)为纵轴,展示不同阈值下模型的表现。绘制ROC曲线时,从高到低调整阈值,计算每个阈值的TPR和FPR并绘制所有阈值的点,形成ROC曲线。
    在这里插入图片描述
  1. 案例:绘制ROC曲线
    假设一个二分类模型的真实标签和模型输出概率如下:
    在这里插入图片描述
    调整阈值,计算TPR和FPR:
    阈值=0.9: T P R = 0 4 = 0 , F P R = 0 4 = 0 TPR=\frac{0}{4}=0,FPR=\frac{0}{4}=0 TPR=40=0FPR=40=0,点坐标0, 0
    阈值=0.8: T P R = 1 4 = 0.25 , F P R = 0 4 = 0 TPR=\frac{1}{4}=0.25,FPR=\frac{0}{4}=0 TPR=41=0.25FPR=40=0,点坐标(0, 0.25)
    阈值=0.7: T P R = 2 4 = 0.5 , F P R = 0 4 = 0 TPR=\frac{2}{4}=0.5,FPR=\frac{0}{4}=0 TPR=42=0.5FPR=40=0,点坐标(0, 0.5)
    阈值=0.6: T P R = 2 4 = 0.5 , F P R = 1 4 = 0.25 TPR=\frac{2}{4}=0.5,FPR=\frac{1}{4}=0.25 TPR=42=0.5FPR=41=0.25,点坐标(0.25, 0.5)
    阈值=0.5: T P R = 3 4 = 0.75 , F P R = 1 4 = 0.25 TPR=\frac{3}{4}=0.75,FPR=\frac{1}{4}=0.25 TPR=43=0.75FPR=41=0.25,点坐标(0.25, 0.75)
    阈值=0.4: T P R = 3 4 = 0.75 , F P R = 2 4 = 0.5 TPR=\frac{3}{4}=0.75,FPR=\frac{2}{4}=0.5 TPR=43=0.75FPR=42=0.5,点坐标(0.5, 0.75)
    阈值=0.3: T P R = 3 4 = 0.75 , F P R = 3 4 = 0.75 TPR=\frac{3}{4}=0.75,FPR=\frac{3}{4}=0.75 TPR=43=0.75FPR=43=0.75,点坐标(0.75, 0.75)
    阈值=0.2: T P R = 4 4 = 1 , F P R = 3 4 = 0.75 TPR=\frac{4}{4}=1,FPR=\frac{3}{4}=0.75 TPR=44=1FPR=43=0.75,点坐标(0.75, 1)
    根据坐标点绘制ROC曲线:
    在这里插入图片描述
  2. AUC值
    AUC值代表ROC曲线下的面积,用于量化模型性能。AUC值越大,模型区分正负类的能力越强,模型性能越好。AUC值=0.5表示模型接近随机猜测,AUC值=1代表完美模型。
    可通过sklearn.metrics.roc_auc_score计算AUC值。
from sklearn.metrics import roc_auc_score

auc_score = roc_auc_score(y_true, y_score)
# y_true: 真实标签(0 或 1)
# y_score: 正例的概率值或置信度

例如:

from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score

# 生成一个二分类数据集
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=100)

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

# 训练一个逻辑回归模型
model = LogisticRegression()
model.fit(x_train, y_train)

# 预测概率值(取正类的概率)
y_pred_proba = model.predict_proba(x_test)[:, 1]

# 计算AUC
auc_score = roc_auc_score(y_test, y_pred_proba)
print(auc_score)

第四章 KNN算法

4.1 KNN算法介绍

K近邻算法(K-Nearest Neighbors,KNN)是一种基本的分类与回归方法,属于监督学习算法。其核心思想是通过计算给定样本与数据集中所有样本的距离,找到距离最近的K个样本,然后根据这K个样本的类别或值来预测当前样本的类别或值。
在这里插入图片描述

4.1.1 工作原理

计算距离:计算待分类样本与训练集中每个样本的距离。
选择K个近邻:根据计算的距离,选择距离最近的K个样本。
投票或平均:

  • 分类任务:统计K个近邻各类别的数量,将待分类样本归为数量最多的类别。
  • 回归任务:取K个近邻的平均值作为预测结果。

4.1.2 关键参数

距离度量方法:选择合适的距离度量方法,常见的有欧氏距离、曼哈顿距离、切比雪夫距离、闵可夫斯基距离等。
K值(超参数):K值的选择对结果影响很大。K值过小容易过拟合,K值过大则可能欠拟合。

4.1.3 优缺点

KNN优点:

  • 简单直观,易于理解和实现。
  • 无需训练过程,直接利用训练数据进行预测。

KNN缺点:

  • 计算量大,尤其是训练集较大时。
  • 对噪声数据较敏感。

4.1.4 API使用

  1. 分类
from sklearn.neighbors import KNeighborsClassifier

knn = KNeighborsClassifier(n_neighbors=2)  # KNN分类模型,K值为2
X = [[2, 1], [3, 1], [1, 4], [2, 6]]  # 特征
y = [0, 0, 1, 1]  # 标签
knn.fit(X, y)  # 模型训练
knn.predict([[4, 9]])  # 预测
  1. 回归
from sklearn.neighbors import KNeighborsRegressor

knn = KNeighborsRegressor(n_neighbors=2)  # KNN回归模型,K值为2
X = [[2, 1], [3, 1], [1, 4], [2, 6]]  # 特征
y = [0.5, 0.33, 4, 3]  # 标签
knn.fit(X, y)  # 模型训练
knn.predict([[4, 9]])  # 预测

4.2 常见距离度量方法(了解)

4.2.1 欧氏距离

欧几里得距离(Euclidean Distance)是指连接两点的线段的长度。
在这里插入图片描述
x ( x 1 , . . . , x n ) x(x_1,...,x_n) x(x1,...,xn) y ( y 1 , . . . , y n ) y(y_1,...,y_n) y(y1,...,yn)之间的欧氏距离 d ( x , y ) = ∑ i = 1 n ( x i − y i ) 2 d(x,y)=\sqrt{\sum_{i=1}^n(x_i−y_i)^2} d(x,y)=i=1n(xiyi)2

4.2.2 曼哈顿距离

曼哈顿距离(Manhattan Distance)是两点在标准坐标系上的绝对轴距之和。
在这里插入图片描述
x ( x 1 , . . . , x n ) x(x_1,...,x_n) x(x1,...,xn) y ( y 1 , . . . , y n ) y(y_1,...,y_n) y(y1,...,yn)之间的曼哈顿距离 d ( x , y ) = ∑ i = 1 n ∣ x i − y i ∣ d(x,y)=\sum_{i=1}^n|x_i−y_i| d(x,y)=i=1nxiyi
曼哈顿距离得名于纽约曼哈顿的街道布局。由于曼哈顿的街道多为规则的网格状,车辆只能沿水平和垂直方向行驶,无法直接斜穿。因此,两点之间的实际行驶距离是沿街道行走的距离,而非直线距离。
在这里插入图片描述

4.2.3 切比雪夫距离

切比雪夫距离(Chebyshev Distance)是两点各坐标数值差的最大值。
在这里插入图片描述
x ( x 1 , . . . , x n ) x(x_1,...,x_n) x(x1,...,xn) y ( y 1 , . . . , y n ) y(y_1,...,y_n) y(y1,...,yn)之间的切比雪夫距离 d ( x , y ) = m a x ⁡ ( ∣ x i − y i ∣ ) d(x,y)=max⁡(|x_i−y_i|) d(x,y)=max(xiyi)
在国际象棋中,国王可以横向、纵向或斜向移动一格。其从起点到终点的最少步数就等于两点之间的切比雪夫距离。
在这里插入图片描述

4.2.4 闵可夫斯基距离

闵可夫斯基距离(Minkowski Distance)是一种用于度量多维空间中两点间距离的通用方法,点 x ( x 1 , . . . , x n ) x(x_1,...,x_n) x(x1,...,xn) y ( y 1 , . . . , y n ) y(y_1,...,y_n) y(y1,...,yn)之间的闵可夫斯基距离 d ( x , y ) = ( ∑ i = 1 n ( ∣ x i − y i ∣ ) p ) 1 p d(x,y)=(\sum_{i=1}^n(|x_i−y_i|)^p)^\frac{1}{p} d(x,y)=(i=1n(xiyi)p)p1。p越小,对多个维度的差异更敏感;p越大,更关注最大维度的差异。
通过调整参数p,闵可夫斯基距离可以退化为以下经典距离:

  • 曼哈顿距离: p = 1 , d ( x , y ) = ∑ i = 1 n ∣ x i − y i ∣ p=1,d(x,y)=\sum_{i=1}^n|x_i−y_i| p=1d(x,y)=i=1nxiyi
  • 欧氏距离: p = 2 , d ( x , y ) = ∑ i = 1 n ( x i − y i ) 2 p=2,d(x,y)=\sqrt{\sum_{i=1}^n(x_i−y_i)^2} p=2d(x,y)=i=1n(xiyi)2
  • 切比雪夫距离: p = ∞ , d ( x , y ) = m a x ⁡ ( x i − y i ) p=∞,d(x,y)=max⁡(x_i−y_i) p=d(x,y)=max(xiyi)

4.3 归一化与标准化

4.3.1 归一化

  1. 定义
    将数据按比例缩放到一个固定范围[xmin,xmax](通常是[0, 1]或[−1, 1])。
    x ’ = x − x m i n x m a x − x m i n x’=\frac{x−x_{min}}{x_{max}−x_{min}} x=xmaxxminxxmin
    x ’ = 2 × x − x m i n x m a x − x m i n − 1 x’=2×\frac{x−x_{min}}{x_{max}−x_{min}}−1 x=2×xmaxxminxxmin1
  2. 目的
  • 消除量纲差异:不同特征的单位或量纲可能差异巨大(例如身高以米为单位,体重以千克为单位),归一化可消除这种差异,避免模型被大范围特征主导。
  • 加速模型收敛:对于梯度下降等优化算法,归一化后特征处于相近的尺度,优化路径更平滑,收敛速度更快。
  • 适配特定模型需求:某些模型(如神经网络、K近邻、SVM)对输入数据的范围敏感,归一化能显著提升其性能。
  1. 场景
    归一化不改变原始分布形状,但对异常值比较敏感。当数据分布有明显边界(如图像像素值、文本词频),或模型对输入范围敏感时可以优先考虑归一化。
  2. API使用
from sklearn.preprocessing import MinMaxScaler

X = [[2, 1], [3, 1], [1, 4], [2, 6]]
# 归一化,区间设置为(-1,1)
X = MinMaxScaler(feature_range=(-1, 1)).fit_transform(X)
print(X)

4.3.2 标准化

  1. 定义
    将数据调整为均值为0、标准差为1的标准分布。
    x ’ = x − μ σ x’=\frac{x−μ}{σ} x=σxμ
    其中 μ = ∑ i = 1 n x i n μ=\frac{\sum_{i=1}^nx_i}{n} μ=ni=1nxi是平均值, σ = ∑ i = 1 n ( x i − μ ) 2 n σ=\sqrt{\frac{\sum_{i=1}^n(x_i−μ)^2}{n}} σ=ni=1n(xiμ)2 是标准差。
  2. 目的
    适应数据分布:将数据转换为均值为0、标准差为1的分布,适合假设数据服从正态分布的模型(如线性回归、逻辑回归)。
    稳定模型训练:标准化后的数据对异常值的敏感度较低(相比归一化),鲁棒性更强。
    统一特征尺度:与归一化类似,标准化也能消除量纲差异,但更关注数据的统计分布而非固定范围。
  3. 场景
    大多数场景下标准化更通用,尤其是数据分布未知或存在轻微异常值时。
  4. API使用
from sklearn.preprocessing import StandardScaler

X = [[2, 1], [3, 1], [1, 4], [2, 6]]
# 标准化
X = StandardScaler().fit_transform(X)
print(X)

4.4 心脏病预测

4.4.1 数据集说明

Heart Disease数据集

  • 年龄:连续值
  • 性别:0-女,1-男
  • 胸痛类型:0-典型心绞痛,1-非典型心绞痛,2-非心绞痛,3-无症状
  • 静息血压:连续值,单位mmHg
  • 胆固醇:连续值,单位mg/dl
  • 空腹血糖:1-大于120mg/dl,0-小于等于120mg/dl
  • 静息心电图结果:0-正常,1-ST-T异常,2-可能左心室肥大
  • 最大心率:连续值
  • 运动性心绞痛:1-有,0-无
  • 运动后的ST下降:连续值
  • 峰值ST段的斜率:0-向上,1-水平,2-向下
  • 主血管数量:0到3
  • 地中海贫血:一种先天性贫血,0-正常,1-固定缺陷,2-可逆缺陷
  • 是否患有心脏病:标签,0-否,1-是

4.4.2 加载数据集

import pandas as pd

# 加载数据集
heart_disease = pd.read_csv("data/heart_disease.csv")
# 处理缺失值
heart_disease.dropna()
heart_disease.info()
heart_disease.head()

在这里插入图片描述

4.4.3 数据集划分

from sklearn.model_selection import train_test_split

# 划分特征和标签
X = heart_disease.drop("是否患有心脏病", axis=1)  # 特征
y = heart_disease["是否患有心脏病"]  # 标签
# 将数据集按7:3划分为训练数据与测试数据
x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=100)

4.4.4 特征工程

  1. 特征转换
    数据集中包含多种类型的特征:
  • 类别型特征(需要特殊处理)
    • 胸痛类型:4种分类(名义变量)
    • 静息心电图结果:3种分类(名义变量)
    • 峰值ST段的斜率:3种分类(有序变量)
    • 地中海贫血:4种分类(名义变量)
  • 数值型特征(可直接标准化):年龄、静息血压、胆固醇、最大心率、运动后的ST下降、主血管数量
  • 二元特征(保持原样):性别、空腹血糖、运动性心绞痛
    对于类别型特征,直接使用整数编码的类别特征会被算法视为有序数值,导致错误的距离计算(例如:会认为 胸痛类型=1 和 胸痛类型=2 之间的差异比 胸痛类型=1和 胸痛类型=3之间差异更小,而实际上它们都是类别)。使用 独热编码(One-Hot Encoding)可将类别特征转换为二元向量,消除虚假的顺序关系。
    在这里插入图片描述
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer

# 数值型特征
numerical_features = ["年龄", "静息血压", "胆固醇", "最大心率", "运动后的ST下降", "主血管数量"]
# 类别型特征
categorical_features = ["胸痛类型", "静息心电图结果", "峰值ST段的斜率", "地中海贫血"]
# 二元特征
binary_features = ["性别", "空腹血糖", "运动性心绞痛"]
# 创建列转换器
preprocessor = ColumnTransformer(
    transformers=[
        # 对数值型特征进行标准化
        ("num", StandardScaler(), numerical_features),
        # 对类别型特征进行独热编码,使用drop="first"避免多重共线性
        ("cat", OneHotEncoder(drop="first"), categorical_features),
        # 二元特征不进行处理
        ("binary", "passthrough", binary_features),
    ]
)
# 执行特征转换
x_train = preprocessor.fit_transform(x_train)  # 计算训练集的统计信息并进行转换
x_test = preprocessor.transform(x_test)  # 使用训练集计算的信息对测试集进行转换
  1. 避免多重共线性
    drop="first"是独热编码中的一个参数,它的核心目的是避免多重共线性(Multicollinearity)。
    多重共线性是指特征之间存在高度线性相关关系的现象。例如特征胸痛类型包含4个类别(0、1、2、3),若直接进行独热编码会生成4个新列(胸痛类型_0、胸痛类型_1、胸痛类型_2、胸痛类型_3),此时这4列满足
    胸痛类型_0+胸痛类型_1+胸痛类型_2+胸痛类型_3=1
    这种完全线性相关关系会导致特征矩阵的列之间存在完美共线性。
    当特征矩阵存在多重共线性时,模型参数估计会变得不稳定(矩阵不可逆或接近奇异),导致系数估计值方差增大、模型可解释性下降、过拟合等问题。
    在独热编码时设置drop=“first”,会删除每个类别特征的第1列,从而打破完全共线性。比如特征胸痛类型会生成3列(胸痛类型_1、胸痛类型_2、胸痛类型_3),此时
    胸痛类型_1=0,胸痛类型_2=0,胸痛类型_3=0 隐含代表 胸痛类型_0=1
    在这里插入图片描述
    虽然KNN不直接受多重共线性影响(不像线性模型),但使用drop="first"也能够减少冗余特征,提升计算效率。

4.4.5 模型训练与评估

from sklearn.neighbors import KNeighborsClassifier

# 使用K近邻分类模型,K=3
knn = KNeighborsClassifier(n_neighbors=3)
# 模型训练
knn.fit(x_train, y_train)
# 模型评估,计算准确率
knn.score(x_test, y_test)

4.4.6 模型的保存

可以使用Python的joblib库保存训练好的模型:

import joblib

joblib.dump(knn, "knn_heart_disease")

加载先前保存的模型:

# 加载模型
knn_loaded = joblib.load("knn_heart_disease")
# 预测
y_pred = knn_loaded.predict(x_test[10:11])
# 打印真实值与预测值
print(y_test.iloc[10], y_pred)

4.5 模型评估与超参数调优

4.5.1 网格搜索

网格搜索(Grid Search)是一种系统化的超参数调优方法,通过遍历预定义的超参数组合,找到使模型性能最优的参数配置。通过自动化调参避免手动试错,提高效率。
网格搜索通常嵌套交叉验证,与交叉验证结合以提高调参的可靠性:

  • 外层循环:遍历参数网格中的每个参数组合。
  • 内层循环:对每个参数组合使用交叉验证评估模型性能(k折)。

4.5.2 对心脏病预测模型进行超参数调优

对模型训练与评估部分进行修改,使用sklearn.model_selection.GridSearchCV进行交叉验证和网格搜索:

from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV

knn = KNeighborsClassifier()
# 网格搜索参数,K值设置为1到10
param_grid = {"n_neighbors": list(range(1, 10))}
# GridSearchCV(estimator=模型, param_grid=网格搜索参数, cv=k折交叉验证)
knn = GridSearchCV(estimator=knn, param_grid=param_grid, cv=10)

# 模型训练
knn.fit(x_train, y_train)
print(pd.DataFrame(knn.cv_results_))  # 所有交叉验证结果
print(knn.best_estimator_)  # 最佳模型
print(knn.best_score_)  # 最佳得分

# 使用最佳模型进行评估
knn = knn.best_estimator_
print(knn.score(x_test, y_test))
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐