机器学习基本理论


目录

机器学习基本理论

机器学习三要素

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

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

机器学习方法分类

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

  • 通常分类:按照有无监督,机器学习可以分为 有监督学习、无监督学习 和 半监督学习,除此之外还有 强化学习。
  • 按模型分类:根据模型性质,可以分为概率模型/非概率模型,线性/非线性模型等。
  • 按学习技巧分类:根据算法基于的技巧,可以分为贝叶斯学习、核方法等。

各种类型的机器学习方法可以用下图汇总展示:

建模流程

以监督学习为例,考察一下机器学习的具体过程。

可以看到,机器学习是由数据驱动的,核心是利用数据来“训练模型”;模型训练的结果需要用一定的方法来进行评估、优化,最终得到一个成熟的学习模型;最后就可以用这个模型来进行预测和解决问题了。

监督学习建模的整体流程如下:

特征工程

什么是特征工程

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

特征工程有什么

特征选择

从原始特征中挑选出与目标变量关系最密切的特征,剔除冗余、无关或噪声特征。这样可以减少模型的复杂度、加速训练过程、并减少过拟合的风险。

特征选择不会创建新特征,也不会改变数据结构。

(1)过滤法(Filter Method)

基于统计测试(如卡方检验、相关系数、信息增益等)来评估特征与目标变量之间的关系,选择最相关的特征。

(2)包裹法(Wrapper Method)

使用模型(如递归特征消除 RFE)来评估特征的重要性,并根据模型的表现进行特征选择。

(3)嵌入法(Embedded Method)

使用模型本身的特征选择机制(如决策树的特征重要性,L1正则化的特征选择)来选择最重要的特征。

特征转换

对数据进行数学或统计处理,使其变得更加适合模型的输入要求。

(1)归一化(Normalization)

将特征缩放到特定的范围(通常是0到1之间)。适用于对尺度敏感的模型(如KNN、SVM)。

(2)标准化(Standardization)

通过减去均值并除以标准差,使特征的分布具有均值0,标准差1。

(3)对数变换

对于有偏态的分布(如收入、价格等),对数变换可以将其转化为更接近正态分布的形式。

(4)类别变量的编码

独热编码(One-Hot Encoding):将类别型变量转换为二进制列,常用于无序类别特征。

标签编码(Label Encoding):将类别型变量映射为整数,常用于有序类别特征。

目标编码(Target Encoding):将类别变量的每个类别替换为其对应目标变量的平均值或其他统计量。
频率编码(Frequency Encoding):将类别变量的每个类别替换为该类别在数据集中的出现频率。

特征构造

特征构造是基于现有的特征创造出新的、更有代表性的特征。通过组合、转换、或者聚合现有的特征,形成能够更好反映数据规律的特征。

(1)交互特征

将两个特征组合起来,形成新的特征。例如,两个特征的乘积、和或差等。

例如,将年龄与收入结合创建新的特征,可能能更好地反映某些模式。

(2)统计特征

从原始特征中提取统计值,例如求某个时间窗口的平均值、最大值、最小值、标准差等。

例如,在时间序列数据中,你可以从原始数据中提取每个小时、每日的平均值。

(3)日期和时间特征

从日期时间数据中提取如星期几、月份、年份、季度等特征。

例如,将“2000-01-01”转换为“星期几”、“是否节假日”、“月初或月末”等特征。

特征降维

当数据集的特征数量非常大时,特征降维可以帮助减少计算复杂度并避免过拟合。通过降维方法,可以在保持数据本质的情况下减少特征的数量。

(1)主成分分析(PCA)

通过线性变换将原始特征映射到一个新的空间,使得新的特征(主成分)尽可能地保留数据的方差。

(2)线性判别分析(LDA)

一种监督学习的降维方法,通过最大化类间距离与类内距离的比率来降维。

(3)t-SNE(t-Distributed Stochastic Neighbor Embedding,t分布随机近邻嵌入)

一种非线性的降维技术,特别适合可视化高维数据。

(4)自编码器(Auto Encoder)

一种神经网络模型,通过压缩编码器来实现数据的降维。

常用方法

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

例如:

某个特征取值较接近,变化很小,可能与结果无关。

某几个特征相关性较高,可能包含冗余信息。

因此,特征选择 在特征工程中是最基本、也最常见的操作。

另外,在训练模型时有时也会遇到维度灾难,即特征数量过多。我们希望能在确保不丢失重要特征的前提下减少维度的数量,来降低训练模型的难度。所以在特征工程中,也经常会用到 特征降维 方法。

1)低方差过滤法
对于特征的选择,可以直接基于方差来判断,这是最简单的。低方差的特征意味着该特征的所有样本值几乎相同,对预测影响极小,可以将其去掉。

示例代码:https://gitee.com/kohler19/machine_-learn/blob/master/ch02_base/feature/1-variance_filter.ipynb

from sklearn.feature_selection import VarianceThreshold

# 低方差过滤:删除方差低于 0.01 的特征
var_thresh = VarianceThreshold(threshold=0.01)
X_filtered = var_thresh.fit_transform(X)

2)相关系数法

通过计算特征与目标变量或特征之间的相关性,筛选出高相关性特征(与目标相关)或剔除冗余特征(特征间高度相关)。

  • 皮尔逊相关系数

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

\(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}}\)

  • 正相关:值接近1,说明特征随目标变量增加而增加。
  • 负相关:值接近-1,说明特征随目标变量增加而减少。
  • 无关:值接近0,说明特征和目标变量无明显关系。

例如,现有一数据集包括不同渠道广告投放金额与销售额。

示例代码:https://gitee.com/kohler19/machine_-learn/blob/master/ch02_base/feature/2_pearson.ipynb

使用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 – \frac{6 \sum d_i2}{n(n2 – 1)}\)

其中:

  • $ d_i$ 是两个变量的等级之差
  • \(n\) 是样本数

斯皮尔曼相关系数的取值范围为 \([-1, 1]\)

  • \(\rho = 1\):完全正相关(一个变量增加,另一个变量也总是增加)。
  • \(\rho = -1\):完全负相关(一个变量增加,另一个变量总是减少)。
  • \(\rho = 0\):无相关性。

例如,现有一组每周学习时长与数学考试成绩的数据:

示例代码:https://gitee.com/kohler19/machine_-learn/blob/master/ch02_base/feature/3_spearman.ipynb

\(X\) \(y\)
5 55
8 65
10 70
12 75
15 85
3 50
7 60
9 72
14 80
6 58

按数值由小到大排出X、y的等级,并计算等级差:

\(X\) \(R_X\) \(y\) \(R_y\) \(d = R_X – R_y\) \(d^2\)
5 2 55 2 0 0
8 5 65 5 0 0
10 7 70 6 1 1
12 8 75 8 0 0
15 10 85 10 0 0
3 1 50 1 0 0
7 4 60 4 0 0
9 6 72 7 \(-1\) 1
14 9 80 9 0 0
6 3 58 3 0 0

\[\rho = 1 – \frac{6\sum d_i^2}{n(n^2 – 1)} = 1 – \frac{6\times 2}{10(10^2 – 1)} = 1 – 0.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
  • 主成分分析(PCA)

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

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

示例代码:https://gitee.com/kohler19/machine_-learn/blob/master/ch02_base/feature/4_pca.ipynb

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

模型评估和模型选择

损失函数

对于模型一次预测结果的好坏,需要有一个度量标准。

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

损失函数用来衡量模型预测误差的大小;损失函数值越小,模型就越好;

损失函数是f(X)Y的非负实值函数;

常见损失函数

  1. 0-1 损失

    \[L(Y, f(X)) = \begin{cases} 1, & Y \neq f(X) \\[4pt] 0, & Y = f(X) \end{cases} \]

  2. 平方损失

    \[L(Y, f(X)) = (Y – f(X))^2 \]

  3. 绝对损失

    \[L(Y, f(X)) = |Y – f(X)| \]

  4. 对数似然损失

    \[L(Y, P(Y|X)) = -\log P(Y|X) \]

经验误差

给定一个训练数据集,数据个数为 \(n\)

\[T = \{(x_1, y_1), (x_2, y_2), \dots, (x_n, y_n)\} \]

根据选取的损失函数,就可以计算出模型 \(f(X)\) 在训练集上的平均误差,称为训练误差,也被称作 经验误差(empirical error)经验风险(empirical risk)

\[R_{\text{emp}}(f) = \frac{1}{n}\sum_{i=1}^{n} L(y_i, f(x_i)) \]

类似地,在测试数据集上平均误差,被称为测试误差或者 泛化误差(generalization error)

一般情况下对模型评估的策略,就是考察经验误差;当经验风险最小时,就认为取到了最优的模型。这种策略被称为 经验风险最小化(empirical risk minimization,ERM)

欠拟合和过拟合

拟合(Fitting)是指机器学习模型在训练数据上学习到规律并生成预测结果的过程。理想情况下,模型能够准确地捕捉训练数据的模式,并且在未见过的新数据(测试数据)上也有良好的表现;即模型具有良好的 泛化能力。

欠拟合(Underfitting):是指模型在训练数据上表现不佳,无法很好地捕捉数据中的规律。这样的模型不仅在训练集上表现不好,在测试集上也同样表现差。

过拟合(Overfitting):是指模型在训练数据上表现得很好,但在测试数据或新数据上表现较差的情况。过拟合的模型对训练数据中的噪声或细节过度敏感,把训练样本自身的一些特点当作了所有潜在样本都会具有的一般性质,从而失去了泛化能力。

产生欠拟合和过拟合的根本原因,是模型的复杂度过低或过高,从而导致测试误差(泛化误差)偏大。

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

1)产生原因与解决办法
(1)欠拟合
产生原因:

  • 模型复杂度不足:模型过于简单,无法捕捉数据中的复杂关系。
  • 特征不足:输入特征不充分,或者特征选择不恰当,导致模型无法充分学习数据的模式。
  • 训练不充分:训练过程中迭代次数太少,模型没有足够的时间学习数据的规律。
  • 过强的正则化:正则化项设置过大,强制模型过于简单,导致模型无法充分拟合数据。

解决办法:

  • 增加模型复杂度:选择更复杂的模型。
  • 增加特征或改进特征工程:添加更多的特征或通过特征工程来创造更有信息量的特征。
  • 增加训练时间:增加训练的迭代次数,让模型有更多机会去学习。
  • 减少正则化强度:如果使用了正则化,尝试减小正则化的权重,以让模型更灵活。

(2)过拟合
产生原因:

  • 模型复杂度过高:模型过于复杂,参数太多。
  • 训练数据不足:数据集太小,模型能记住训练数据的细节,但无法泛化到新数据。
  • 特征过多:特征太多,模型可能会“记住”数据中的噪声,而不是学到真正的规律。
  • 训练过长:训练时间过长,导致模型学习到训练数据中的噪声,而非数据的真正规律。

解决办法:

  • 减少模型复杂度:降低模型的参数数量、使用简化的模型或降维来减小模型复杂度。
  • 增加训练数据:收集更多数据,或通过数据增强来增加训练数据的多样性。
  • 使用正则化:引入L1、L2正则化,避免过度拟合训练数据。
  • 交叉验证:使用交叉验证技术评估模型在不同数据集上的表现,以减少过拟合的风险。
  • 早停:训练时,当模型的验证损失不再下降时,提前停止训练,避免过度拟合训练集。

2)示例代码

使用多项式在 $ x \in [-3,3] $ 上拟合 \(sin(x)\)

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression #现行回归模型
from sklearn.preprocessing import PolynomialFeatures #构建多项式特征
from sklearn.model_selection import train_test_split #花粉训练集和测试集
from sklearn.metrics import mean_squared_error #均方误差损失函数

"""
1.生成数据
2.划分训练接和测试集
3.定义模型(线性回归模型)
4.训练模型
5.预算结果,计算误差
"""
plt.rcParams['font.sans-serif']=['KaiTi']
plt.rcParams['axes.unicode_minus']=False

#1.生成数据
X = np.linspace(-3,3,300).reshape(-1,1)
Y = np.sin(X) + np.random.uniform(low=-0.5,high=0.5,size=300).reshape(-1,1)
print(X.shape)
print(Y.shape)
#画出散点图(三个子图)
fig,ax = plt.subplots(1,3,figsize = (15,4))
ax[0].scatter(X,Y,c='y')
ax[1].scatter(X,Y,c='y')
ax[2].scatter(X,Y,c='y')


#2.划分训练集和测试集
trainX,testX,trainY,testY=train_test_split(X,Y,test_size=0.2,random_state=42)

#3.定义模型(现行回归模型)
model1 = LinearRegression()
model2 = LinearRegression()
model3 = LinearRegression()
#一、欠拟合(直线)
x_train1 = trainX
x_test1 = testX
#恰好拟合(5次多项式)
poly5 = PolynomialFeatures(degree=5)
x_train2 = poly5.fit_transform(trainX)
x_test2 = poly5.fit_transform(testX)
#过拟合(20次多项式)
poly20 = PolynomialFeatures(degree=20)
x_train3 = poly20.fit_transform(trainX)
x_test3 = poly20.fit_transform(testX)
#4.训练模型
model1.fit(x_train1,trainY)
model2.fit(x_train2,trainY)
model3.fit(x_train3,trainY)

#打印查看模型参数
print(model1.coef_)
print(model1.intercept_)

#5.预测结果,计算误差
y_pred1 = model1.predict(x_test1)
test_loss1 = mean_squared_error(testY,y_pred1)
train_loss1 = mean_squared_error(trainY,model1.predict(x_train1))

y_pred2 = model2.predict(x_test2)
test_loss2 = mean_squared_error(testY,y_pred2)
train_loss2 = mean_squared_error(trainY,model2.predict(x_train2))

y_pred3 = model3.predict(x_test3)
test_loss3 = mean_squared_error(testY,y_pred3)
train_loss3 = mean_squared_error(trainY,model3.predict(x_train3))

#画出拟合曲线并写出训练误差和测试误差
ax[0].plot(X,model1.predict(X),c='r')
ax[0].text(-3,1,f"测试误差:{test_loss1:.4f}")
ax[0].text(-3,1.3,f"训练误差{train_loss1:.4f}")

ax[1].plot(X,model2.predict(poly5.fit_transform(X)),c='r')
ax[1].text(-3,1,f"测试误差:{test_loss2:.4f}")
ax[1].text(-3,1.3,f"训练误差{train_loss2:.4f}")

ax[2].plot(X,model3.predict(poly20.fit_transform(X)),c='r')
ax[2].text(-3,1,f"测试误差:{test_loss3:.4f}")
ax[2].text(-3,1.3,f"训练误差{train_loss3:.4f}")
plt.show()

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

正则化

正则化(Regularization)是一种在训练机器学习模型时,在损失函数中添加额外项,来惩罚过大的参数,进而限制模型复杂度、避免过拟合,提高模型泛化能力的技术。

如在平方损失函数中加入正则化项\(\lambda \sum_{i=1}^{k}\omega_{i}^{2}\)

\[Loss = \frac{1}{n}\left( \sum_{i = 1}^{n}(f(x_{i}) – y_{i})^{2}+\lambda\sum_{i = 1}^{k}\omega_{i}^{2} \right) \]

  • 原损失函数\(\sum_{i = 1}^{n}(f(x_{i}) – y_{i})^{2}\)的目的:更好的拟合数据集。
  • 正则化项\(\lambda \sum_{i = 1}^{k}\omega_{i}^{2}\)的目的:减小参数的大小,从而降低模型的复杂度。

这里的\(\lambda\)是 正则化系数,用来表示惩罚项的权重。正则化系数不属于模型的参数,无法通过训练学习得到,需要在模型训练开始之前手动设置,这种参数被称为“超参数”。
两者相互平衡,在模型的拟合能力(偏差)和复杂度之间找到最佳折中。
常见的正则化技术有L1正则化和L2正则化。

1)L1正则化(Lasso回归)
L1正则化在损失函数中加入参数的绝对值之和:

$ Loss_{L1} = 原Loss + \lambda\sum_{i = 1}^{k}|\omega_{i}| $

L1 正则化通过惩罚模型参数的绝对值,使得部分权重趋近 0 甚至变为 0。这会导致特征选择,即模型会自动“丢弃”一些不重要的特征。L1 正则化有助于创建稀疏模型(即许多参数为 0)。在解决回归问题时,使用 L1 正则化也被称为 “Lasso 回归”。

\(\lambda\) 超参数控制着正则化的强度。较大的 \(\lambda\) 值意味着强烈的正则化,会使模型更简单,可能导致欠拟合。而较小的 \(\lambda\) 值则会使模型更复杂,可能导致过拟合。

2)L2 正则化(Ridge 回归,岭回归)

L2 正则化在损失函数中加入参数的平方之和:

\[Loss_{L2} = 原Loss + \lambda\sum_{i = 1}^{k}\omega_{i}^{2} \]

L2 正则化通过惩罚模型参数的平方,使得所有参数都变得更小,但不会将参数强行压缩为 0。它会使得模型尽量平滑,从而防止过拟合。

在解决回归问题时,使用 L2 正则化也被称为“岭回归”。

3)ElasticNet 正则化(弹性网络回归)

ElasticNet 正则化结合了 L1 和 L2 正则化,通过调整两个正则化项的比例来取得平衡,从而同时具备稀疏性和稳定性的优点。

\[Loss_{ElasticNet} = 原Loss + \lambda\left( \alpha\sum_{i = 1}^{n}|\omega_{i}| + \frac{1 – \alpha}{2}\sum_{i = 1}^{n}\omega_{i}^{2} \right) \]

\(\alpha \in [0,1]\),决定 L1 和 L2 的权重。

4)正则化案例

同样以使用多项式在$ x \in [-3,3] \(上拟合\)\sin(x)$为例,分别不使用正则化、使用 L1 正则化、使用 L2 正则化进行拟合。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression,Lasso,Ridge #现行回归模型
from sklearn.preprocessing import PolynomialFeatures #构建多项式特征
from sklearn.model_selection import train_test_split #花粉训练集和测试集
from sklearn.metrics import mean_squared_error #均方误差损失函数

"""
1.生成数据
2.划分训练接和测试集
3.定义模型(线性回归模型)
4.训练模型
5.预算结果,计算误差
"""
plt.rcParams['font.sans-serif']=['KaiTi']
plt.rcParams['axes.unicode_minus']=False

#1.生成数据
X = np.linspace(-3,3,300).reshape(-1,1)
Y = np.sin(X) + np.random.uniform(low=-0.5,high=0.5,size=300).reshape(-1,1)

#画出散点图(2x3 子图)
fig,ax = plt.subplots(2,3,figsize = (15,4))
ax[0,0].scatter(X,Y,c='y')
ax[0,1].scatter(X,Y,c='y')
ax[0,2].scatter(X,Y,c='y')

#2.划分训练集和测试集
trainX,testX,trainY,testY=train_test_split(X,Y,test_size=0.2,random_state=42)

#过拟合(20次多项式)
poly20 = PolynomialFeatures(degree=20)
x_train = poly20.fit_transform(trainX)
x_test = poly20.fit_transform(testX)

#一、不加正则项

# 定义模型
model = LinearRegression()

#4.训练模型
model.fit(x_train,trainY)

#5.预测结果,计算误差

y_pred1 = model.predict(x_test)
test_loss1 = mean_squared_error(testY,y_pred1)
train_loss1 = mean_squared_error(trainY,model.predict(x_train))

#画出拟合曲线并写出训练误差和测试误差

ax[0,0].plot(X,model.predict(poly20.fit_transform(X)),c='r')
ax[0,0].text(-3,1,f"测试误差:{test_loss1:.4f}")

#画出所有系数的直方图
ax[1,0].bar(np.arange(21),model.coef_.reshape(-1))

#二、加L1正则化项(Lasso回归)
# 定义模型
lasso = Lasso(alpha=0.01)

#4.训练模型
lasso.fit(x_train,trainY)

#5.预测结果,计算误差

y_pred2 = lasso.predict(x_test)
test_loss2 = mean_squared_error(testY,y_pred2)
train_loss2 = mean_squared_error(trainY,lasso.predict(x_train))

#画出拟合曲线并写出训练误差和测试误差

ax[0,1].plot(X,lasso.predict(poly20.fit_transform(X)),c='r')
ax[0,1].text(-3,1,f"测试误差:{test_loss2:.4f}")

#画出所有系数的直方图
ax[1,1].bar(np.arange(21),lasso.coef_.reshape(-1))
#三、加L2正则化项(Ridge(岭)回归)
# 定义模型
ridge = Ridge(alpha=1)

#4.训练模型
ridge.fit(x_train,trainY)

#5.预测结果,计算误差

y_pred3 = ridge.predict(x_test)
test_loss3 = mean_squared_error(testY,y_pred3)
train_loss3 = mean_squared_error(trainY,ridge.predict(x_train))

#画出拟合曲线并写出训练误差和测试误差

ax[0,2].plot(X,ridge.predict(poly20.fit_transform(X)),c='r')
ax[0,2].text(-3,1,f"测试误差:{test_loss3:.4f}")

#画出所有系数的直方图
ax[1,2].bar(np.arange(21),ridge.coef_.reshape(-1))

plt.show()

交叉验证

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

1)简单交叉验证(Hold-Out Validation)

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

2)k折交叉验证(k-Fold Cross-Validation)

将数据均匀分为k个子集(称为“折”),每次用k−1折训练,剩余1折验证,重复k次后取平均性能。充分利用数据,结果更稳定。

3)留一交叉验证(Leave-One-Out,LOO)

每次仅留一个样本作为验证集,其余全部用于训练,重复直到所有样本都被验证一次。适用于小数据集,计算成本极高。

模型求解算法

正则化可以有效防止过拟合,增强模型的泛化能力。这时模型的评估策略,就是让结构化的经验风险最小,即增加了正则化项的损失函数最小,称为结构风险最小化(Structural Risk Minimization,SRM)。

$\min \frac{1}{n}\left( \sum_{i=1}^{n} L(y_i, f(x_i)) + \lambda J(\theta) \right) $

这其实就是求解一个最优化问题。代入训练集所有数据 $ (x_i, y_i) $ ,要求最小值的目标函数就是模型中参数$ \theta $ 的函数。 具体求解的算法,可以利用数学公式直接计算解析解,也可以使用迭代算法。

解析法

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

  • 适用条件:目标函数必须可导,且导数方程有解析解。
  • 优点:直接且精确;计算高效;
  • 缺点:适用条件较为苛刻;特征维度较大时,矩阵求逆计算复杂度极高。

2)应用示例

  • 线性回归问题:可以采用“最小二乘法”求得解析解。

\(Loss_{MSE} = \frac{1}{n}(\boldsymbol{X}\boldsymbol{\beta} – \boldsymbol{y})^T(\boldsymbol{X}\boldsymbol{\beta} – \boldsymbol{y}) \\\)

$ \nabla Loss_{MSE} = \frac{2}{n}\boldsymbol{X}^T(\boldsymbol{X}\boldsymbol{\beta} – \boldsymbol{y}) = 0 $

\[\boldsymbol{\beta} = (\boldsymbol{X}^T\boldsymbol{X})^{-1}\boldsymbol{X}^T\boldsymbol{y} \]

  • 线性回归L2正则化(Ridge回归,岭回归)

可以得到解析解如下:

$ Loss_{L2} = \frac{1}{n}(\boldsymbol{X}\boldsymbol{\beta} – \boldsymbol{y})^T(\boldsymbol{X}\boldsymbol{\beta} – \boldsymbol{y}) + \frac{1}{n}\lambda\boldsymbol{\beta}^T\boldsymbol{\beta} $

$ \nabla Loss_{L2} = \frac{2}{n}\boldsymbol{X}^T(\boldsymbol{X}\boldsymbol{\beta} – \boldsymbol{y}) + \frac{2}{n}\lambda\boldsymbol{\beta} = 0 $

\[\boldsymbol{\beta} = (\boldsymbol{X}^T\boldsymbol{X} + \lambda\boldsymbol{I})^{-1}\boldsymbol{X}^T\boldsymbol{y} \]

由于加入的对角矩阵\(\lambda\boldsymbol{I}\)就像一条“山岭”,因此L2正则化也称作“岭回归”。

梯度下降法

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

  • 梯度方向:函数变化增长最快的方向(变量沿此方向变化时函数增长最快)
  • 负梯度方向:函数变化减少最快的方向(变量沿此方向变化时函数减少最快)

因为损失函数是系数的函数,那么如果系数沿着损失函数的负梯度方向变化,此时损失函数减少最快,能够以最快速度下降到极小值。

$\theta_{k+1} = \theta_k – \alpha \cdot \nabla L(\theta_k) $

这里的\(\nabla L(\theta_k)\)是参数取值为\(\theta_k\)时损失函数( L )的梯度;\(\alpha\)是每次迭代的“步长”,被称为“学习率”。学习率也是一个常见的超参数,需要手动设置,选择不当会导致收敛失败。

1)特点 – 梯度下降不一定能够找到全局的最优解,有可能是一个局部最优解。

  • 优点:适用性广;计算简单;
  • 缺点:收敛速度慢;可能陷入局部最优。

2)分类

①批量梯度下降(Batch Gradient Descent,BGD) 每次迭代使用全部训练数据计算梯度。

  • 优点:稳定收敛。

  • 缺点:计算开销大。

②随机梯度下降(Stochastic Gradient Descent,SGD) 每次迭代随机选取一个样本计算梯度。

  • 优点:速度快,适合大规模数据。
  • 缺点:梯度更新方向不稳定,优化过程震荡较大,可能难以收敛。

③小批量梯度下降(Mini-batch Gradient Descent,MBGD) 每次迭代使用一小批样本(如 32、64 个)计算梯度。 平衡了 BGD 的稳定性和 SGD 的速度,是最常用的方法。

3)梯度下降法计算步骤
(1)初始化参数:随机选择初始参数
(2)计算梯度:在当前参数下,计算损失函数的梯度
(3)更新参数:沿负梯度方向调整参数
(4)重复迭代:直到满足停止条件(如梯度接近零、达到最大迭代次数等)

4)代码实现

以一个单变量函数为例,介绍梯度下降法的代码实现。

设 $ f(x) = x^2 $ ,求$ x$为何值时, $ f(x) = 2 $ 。 目标函数 $ J(x) = (f(x) – 2)^2 = (x^2 – 2)^2 $ ,原问题等价于求 $x $ 为何值时目标函数取得最小值。

使用梯度下降法求解。

5)应用示例

  • L1正则化(Lasso回归)
    梯度下降法求解的推导过程如下:

\[Loss_{L1} = \frac{1}{n}\left( \sum_{i=1}^{n}(f(\boldsymbol{x}_i) – y_i)^2 + \lambda\sum_{j=1}^{k}|\omega_j| \right) \]

$ \frac{\partial Loss_{L1}}{\partial \omega_j} = \frac{1}{n}\left( 2\sum_{i=1}^{n}x_{ij}(f(\boldsymbol{x}_i) – y_i) + \lambda \cdot \text{sign}(\omega_j) \right) $

其中\(\text{sign}(\omega_j) = \begin{cases} 1, & \omega_j > 0 \\ 0, & \omega_j = 0 \\ -1, & \omega_j < 0 \end{cases}\)

参数更新:\(\omega_j \leftarrow \omega_j – \alpha\left( \frac{2}{n}\sum_{i=1}^{n}x_{ij}(f(\boldsymbol{x}_i) – y_i) + \frac{\lambda}{n} \cdot \text{sign}(\omega_j) \right)\)

可见L1正则化项的梯度是一个常数\(\frac{\lambda}{n}\),当\(\omega_j\)很小时会直接变成0,导致稀疏性。

  • L2正则化(Ridge回归,岭回归)

梯度下降法求解的推导过程如下:

\[Loss_{L2} = \frac{1}{n}\left( \sum_{i=1}^{n}(f(\boldsymbol{x}_i) – y_i)^2 + \lambda\sum_{j=1}^{k}\omega_j^2 \right) \]

$ \frac{\partial Loss_{L2}}{\partial \omega_j} = \frac{1}{n}\left( 2\sum_{i=1}^{n}x_{ij}(f(\boldsymbol{x}_i) – y_i) + 2\lambda\omega_j \right) $

梯度更新:\(\omega_j \leftarrow \omega_j – \alpha\left( \frac{2}{n}\sum_{i=1}^{n}x_{ij}(f(\boldsymbol{x}_i) – y_i) + \frac{2\lambda}{n}\omega_j \right)\)

可见 L2 正则化项的梯度是 \(\frac{2\lambda}{n}\omega_j\) ,相当于在每次更新时都对 \(\omega_j\) 进行缩小,但不会直接变为 0。

牛顿法和拟牛顿法

牛顿法也是求解无约束最优化问题的常用方法,核心思想是利用目标函数的二阶导数信息,通过迭代逐渐逼近极值点。

$ \theta_{k+1} = \theta_k – H^{-1}(\theta_k) \cdot \nabla L(\theta_k) $

这里的$ H^{-1}(\theta_k) $ 表示损失函数$ L $ 黑塞矩阵的逆在点$ \theta_k $的取值。

  • 优点:收敛速度快;精度高;
  • 缺点:计算复杂;可能发散。

由于牛顿法中需要计算黑塞矩阵的逆 $ H^{-1}(\theta_k) $ ,这一步比较复杂;所以可以考虑用一个$ n $ 阶正定矩阵来近似代替它,这种方法称为“拟牛顿法”。

牛顿法和拟牛顿法一般用于解决中小规模的凸优化问题。

模型评价指标

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

回归模型评价指标

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

$ MAE = \frac{1}{n}\sum_{i=1}^{n}|f(\boldsymbol{x}_i) – y_i| $

  • MAE 对异常值不敏感,解释直观。适用于数据包含异常值的场景。

2)均方误差(MSE)

$ MSE = \frac{1}{n}\sum_{i=1}^{n}(f(\boldsymbol{x}_i) – y_i)^2 $

  • MSE 会放大较大误差,对异常值敏感。适用于需要惩罚大误差的场景。

3)均方根误差(RMSE)

\[ RMSE = \sqrt{\frac{1}{n}\sum_{i=1}^{n}(f(\boldsymbol{x}_i) – y_i)^2} \]

  • 与 MSE 类似,但量纲与目标变量一致。适用于需要直观误差量纲的场景。如果一味地试图降低 RMSE,可能会导致模型对异常值也拟合度很高,容易过拟合。

4)$ R^2 $(决定系数)

\[R^2 = 1 – \frac{\sum_{i=1}^{n}(f(\boldsymbol{x}_i) – y_i)^2}{\sum_{i=1}^{n}(y_i – \bar{y})^2} \]

  • 衡量模型对目标变量的解释能力,越接近 1 越好,对异常值敏感。

分类模型评价指标

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

1)混淆矩阵

混淆矩阵(Confusion Matrix)是用于评估分类模型性能的工具,展示了模型预测结果与实际标签的对比情况。

对于二分类问题,混淆矩阵是一个2×2矩阵:

例如,有10个样本。6个是猫,4个是狗。假设以猫为正例,模型预测对了5个猫,2个狗。

使用sklearn.metrics.confusion_matrix查看混淆矩阵:

示例代码:https://gitee.com/kohler19/machine_-learn/blob/master/ch02_base/metrics/1_classification_test.ipynb

2)准确率(Accuracy)
正确预测的比例。

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

https://gitee.com/kohler19/machine_-learn/blob/master/ch02_base/metrics/1_classification_test.ipynb

该案例中准确率$ = \frac{5 + 2}{10} = 0.7 $

from sklearn.metrics import accuracy_score

label = ["猫", "狗"]  # 标签
y_true = ["猫", "猫", "猫", "猫", "猫", "猫", "狗", "狗", "狗", "狗"]  # 真实值
y_pred1 = ["猫", "猫", "狗", "猫", "猫", "猫", "猫", "猫", "狗", "狗"]  # 预测值
accuracy = accuracy_score(y_true, y_pred1)
print(accuracy)

3)精确率

预测为正例的样本中实际为正例的比例,也叫查准率。

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

上述案例中,精确率$ = \frac{5}{5 + 2} = 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)

4)召回率(Recall)

实际为正类的样本中预测为正类的比例,也叫查全率。

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

上述案例中,召回率$ = \frac{5}{5 + 1} = 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)

5)F1分数(F1Score)

精确率和召回率的调和平均。

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

上述案例中,F1 分数$ = \frac{2 \times \frac{5}{5 + 2} \times \frac{5}{5 + 1}}{\frac{5}{5 + 2} + \frac{5}{5 + 1}} = 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 指定的类别号)

示例代码:https://gitee.com/kohler19/machine_-learn/blob/master/ch02_base/metrics/2_classification_report.py

6)ROC曲线

  • 真正例率(TPR):实际为正例,被预测为正例的比例,即召回率。

  • 假正例率(FPR):实际为负例,被预测为正例的比例。

  • 阈值(Threshold):根据阈值将概率转换为类别标签。

    $ TPR = \frac{TP}{\text{实际正例数}} = \frac{TP}{TP + FN} $

    $ FPR = \frac{FP}{\text{实际负例数}} = \frac{FP}{FP + TN} $

ROC曲线(Receiver Operating Characteristic Curve,受试者工作特征)是评估二分类模型性能的工具,以假正例率(FPR)为横轴,以真正例率(TPR)为纵轴,展示不同阈值下模型的表现。绘制ROC曲线时,从高到低调整阈值,计算每个阈值的TPR和FPR并绘制所有阈值的点,形成ROC曲线。

7)案例:绘制ROC曲线

假设一个二分类模型的真实标签和模型输出概率如下:

调整阈值,计算 TPR 和 FPR:

阈值 = 0.9: $ TPR = \frac{0}{4} = 0 $ , $ FPR = \frac{0}{4} = 0 $ ,点坐标 \((0,0)\)

阈值 = 0.8: $ TPR = \frac{1}{4} = 0.25 $ , $ FPR = \frac{0}{4} = 0 $ ,点坐标 \((0, 0.25)\)

阈值 = 0.7: $ TPR = \frac{2}{4} = 0.5 $ , $ FPR = \frac{0}{4} = 0 $ ,点坐标 \((0, 0.5)\)

阈值 = 0.6: $ TPR = \frac{2}{4} = 0.5 $ , $ FPR = \frac{1}{4} = 0.25 $ ,点坐标 \((0.25, 0.5)\)

阈值 = 0.5: $ TPR = \frac{3}{4} = 0.75 $ , $ FPR = \frac{1}{4} = 0.25 $ ,点坐标 \((0.25, 0.75)\)

阈值 = 0.4: $ TPR = \frac{3}{4} = 0.75 $ , $ FPR = \frac{2}{4} = 0.5 $ ,点坐标 \((0.5, 0.75)\)

阈值 = 0.3: $ TPR = \frac{3}{4} = 0.75 $ , $ FPR = \frac{3}{4} = 0.75 $ ,点坐标 \((0.75, 0.75)\)

阈值 = 0.2 :$ TPR = \frac{4}{4} = 1 $ , $ FPR = \frac{3}{4} = 0.75 $ ,点坐标 $(0.75, 1) $

根据坐标点绘制ROC曲线:

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