数据集的划分
训练集和测试集
在机器学习中,数据集通常会被划分为训练集(Training Set)和测试集(Test Set),有时还会包括一个验证集(Validation Set)。这样的划分是为了能够更好地评估模型的性能,并防止过拟合。
训练集:
- 训练集用于构建模型。这是模型学习数据特征和模式的主要来源。模型通过最小化训练集上的损失函数来学习参数。
测试集:
- 测试集用于评估模型的泛化能力,即模型在未见过的新数据上的表现。这是非常重要的,因为我们的目标通常是使模型能够在未知数据上做出准确预测,而不仅仅是记忆训练数据。
- 测试集应该独立于训练集,模型在训练过程中不应该接触到测试集的数据。
验证集(可选):
- 验证集用于在训练过程中调整模型的超参数,比如学习率、正则化参数等。它帮助我们选择最佳模型版本,但不应用于最终评估模型性能,因为模型已经“看到”了这部分数据。
留出法
最常用的划分方法,将数据分成两部分:一个训练集,一个测试集,数据集的划分比例没有固定标准,常见的是70%训练集、15%验证集和15%测试集,或者是80%训练集和20%测试集,具体取决于数据量大小和项目需求。
我们之前的例子中都是使用这种方法划分训练集和测试集,在sklearn中,使用model_selection中的train_test_split就可以非常快速地实现数据集的划分,在使用这个方法时注意,传入的x和y都应该是ndarray(nump数组)类型,如果是使用pandas进行读取和处理数据,记得转换格式。
k折交叉验证
将数据集划分为k个等大小子集,并重复k次保持验证。每次使用其中一个k子集作为测试集,将其他k-1子集放在一起形成训练集。然后计算所有k次试验的平均误差。这种方法的优点是,如何划分数据无关紧要。每一个数据点在一个测试集中精确地出现一次,并且在一个训练集中有k-1次。结果估计的方差随着k的增加而减小。该方法的缺点是训练算法必须从头开始重新运行k次,这意味着需要k倍的计算量来进行评估。
同样在sklean中,已经为我们提供了k折交叉验证的实现函数,是model_selection 的KFold方法
【示例】使用sklearn实现对鸢尾花数据集逻辑回归模型的k折交叉验证
import numpy as np
from sklearn.model_selection import KFold
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score
# 加载数据集
data = load_iris()
X = data.data
y = data.target
# 创建K折交叉验证对象
kf = KFold(n_splits=5)
# 进行K折交叉验证
accuracies = []
for train_index, test_index in kf.split(X):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
model = LogisticRegression()
model.fit(X_train, y_train)
predictions = model.predict(X_test)
accuracy = accuracy_score(y_test, predictions)
accuracies.append(accuracy)
# 输出平均准确性
mean_accuracy = np.mean(accuracies)
print(f"Mean accuracy: {mean_accuracy}")
输出结果:Mean accuracy: 0.9266666666666665
留一法交叉验证:k折交叉验证的特殊情况,其中k=n,
n为样本个数。使用一个样本作为验证集,其余样本作为训练集。重复此步骤,以便将样本中的每个观察结果作为验证数据使用一次。如果数据较多计算量会很大
数据集划分方法的选择:大数据集用留出法,小数据集用交叉验证
过拟合
-
训练误差:指模型在其训练数据上的表现情况。具体来说,它是模型预测输出与训练数据的真实输出之间的差异度量。通常情况下,训练误差较低意味着模型能够很好地拟合训练数据中的模式。
-
泛化误差:指模型在未见过的数据上的表现情况。这是模型实际应用时最重要的性能指标,因为它衡量了模型对新数据的预测能力。理想情况下,泛化误差应该尽可能低,这意味着模型不仅能够很好地拟合训练数据,而且还能很好地处理未知数据。
泛化是指一个模型能够很好地处理新的未知数据,而不仅仅是训练数据。如果模型训练得太好,它可以很好地拟合训练数据中的随机波动或噪声,但在新的数据上无法准确地预测。
机器学习中更经常遇到过拟合问题,让我们来详细看一下如何解决过拟合问题
- 选择合适的模型复杂度:使用更复杂的模型可以减少训练误差,但同时也增加了过拟合的风险。使用较简单的模型可以降低过拟合的风险,但也可能导致欠拟合。因此,需要选择一个合适的模型复杂度。
- 正则化技术:如L1、L2正则化可以帮助控制模型复杂度,防止过拟合。
- 交叉验证:使用交叉验证等技术来评估模型的泛化能力,帮助调整模型的参数。
- 数据增强:通过增加训练数据的多样性来提高模型的泛化能力。
- 早停法(Early Stopping):在训练过程中,如果验证集上的性能不再改善,则停止训练,以避免过拟合。
在选择完合适的模型之后,我们最常用的方法就是利用正则化来消除过拟合。
偏差和方差
偏差和方差的概念和过拟合欠拟合息息相关。
偏差(Bias):用所有可能的训练集训练出所有的模型输出的平均值与真实模型输出值之间的差异
方差(Variance):不同的训练集训练出的模型输出值之间的差异
确定模型是否有偏差或方差:
如果你的模型在训练集上表现得很好,但在测试集上表现得差得多,那么它就面临高方差问题。
另一方面,如果你的模型在训练数据集和测试数据集上都表现不佳,那么它就存在高偏差。
一般而言,偏差和方差存在冲突:训练不足时,学习器拟合能力不强,偏差主导;随着训练程度加深,学习器拟合能力增强,方差主导;训练充足后,拟合能力强,方差主导
正则化
正则化是一种常用的防止过拟合的技术,它通过在损失函数中加入一个额外的项来限制模型参数的大小,从而避免模型变得过于复杂。在scikit-learn
(简称sklearn
)中,可以通过设置正则化参数来轻松地应用正则化。下面是如何使用正则化来解决过拟合问题的具体步骤:
主要有两种类型的正则化:
-
L1正则化(Lasso Regression):L1正则化倾向于产生稀疏解,使得一些特征的系数变为0,从而实现特征选择。L1是指所有参数的绝对值之和
L o s s = Original Loss + λ ∑ i = 1 n ∣ w i ∣ \mathrm{Loss}=\text{Original Loss}+\lambda\sum_{i=1}^n|w_i| Loss=Original Loss+λi=1∑n∣wi∣
-
L2正则化(Ridge Regression):L2正则化使得所有特征都有非零系数,但这些系数的大小受到限制,从而防止过拟合。L2是指所有参数的平方和
L o s s = Original Loss + λ ∑ i = 1 n w i 2 \mathrm{Loss}=\text{Original Loss}+\lambda\sum_{i=1}^nw_i^2 Loss=Original Loss+λi=1∑nwi2
正则化强度由一个超参数控制,通常标记为alpha
(对于线性模型)或C
(对于支持向量机等其他模型)。较大的alpha
值意味着更强的正则化,较小的alpha
值意味着较弱的正则化。
下面的两个模型套索回归和岭回归分别使用L1和L2正则化
套索回归
Lasso回归(Least Absolute Shrinkage and Selection Operator,最小绝对收缩和选择算子)是一种正则化
线性回归方法,由Robert Tibshirani于1996年提出,旨在解决多重共线性问题、变量选择以及防止过拟合。
在Lasso回归中,目标函数在线性回归的基础上加上一个L1正则项(所有参数的绝对值之和),变为:
min β 1 2 n ∑ i = 1 n ( y i − β 0 − ∑ j = 1 p β j x i j ) 2 + λ ∑ j = 1 p ∣ β j ∣ \begin{aligned}\min_\beta\frac{1}{2n}\sum_{i=1}^n(y_i-\beta_0-\sum_{j=1}^p\beta_jx_{ij})^2+\lambda\sum_{j=1}^p|\beta_j|\end{aligned} βmin2n1i=1∑n(yi−β0−j=1∑pβjxij)2+λj=1∑p∣βj∣
Lasso回归的特点:
- 特征选择:由于L1惩罚项的作用,Lasso回归会将一些不重要的特征的系数压缩至零,从而实现特征选择。
- 稀疏解:Lasso回归产生的解是稀疏的,即大多数特征的权重会被设为零,这有助于解释和理解模型。
- 正则化:通过调节 λ 参数,可以控制模型的复杂度。当 λ=0时,回归就退化为普通的线性回归。当 λ 很大时,所有参数都会被压缩至零。
- 防止过拟合:由于包含了正则化项,Lasso回归在面对多重共线性或数据维度很高的情况下,比普通的线性回归更不容易过拟合。
- 计算复杂性:虽然Lasso回归的优化问题是非光滑的(因为L1惩罚项是非光滑的),但可以通过坐标下降、最小角度回归(LARS)等算法高效地求解。
【代码示例】使用sklearn实现Lasso回归
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_regression
from sklearn.linear_model import Lasso
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
# 设置随机数种子以便结果可复现
np.random.seed(0)
# 使用make_regression生成数据集
X, y = make_regression(n_samples=100, n_features=1, noise=10)
# 将数据分为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
# 创建Lasso回归模型实例
lasso = Lasso(alpha=0.1)
# 拟合模型
lasso.fit(X_train, y_train)
# 预测测试集
y_pred = lasso.predict(X_test)
# 计算均方误差
mse = mean_squared_error(y_test, y_pred)
print(f"Mean Squared Error: {mse}")
# 可视化
plt.scatter(X_test, y_test, color='blue', label='Actual data')
plt.plot(X_test, y_pred, color='red', linewidth=2, label='Lasso regression')
plt.legend()
plt.title('Lasso Regression')
plt.xlabel('Feature')
plt.ylabel('Target')
plt.show()
运行结果:
岭回归
岭回归(Ridge Regression)是一种用于解决多重共线性问题以及防止过拟合的线性模型技术。它通过对最小二乘估计加上一个正则化项来修正模型参数的估计,从而减小了某些极端情况下的系数估计值,提高了模型的稳定性和泛化能力。岭回归的求解步骤如下:
-
模型形式: 岭回归的目标函数是在最小二乘损失函数的基础上加入L2范数惩罚项(所有参数的平方和):
L ( a ) = 1 2 N ∑ i = 1 N ( y i − ( a 0 + a 1 x i 1 + a 2 x i 2 + ⋯ + a p x i p ) ) 2 + λ ∑ j = 1 p a j 2 L(a)=\frac1{2N}\sum_{i=1}^N(y_i-(a_0+a_1x_{i1}+a_2x_{i2}+\cdots+a_px_{ip}))^2+\lambda\sum_{j=1}^pa_j^2 L(a)=2N1i=1∑N(yi−(a0+a1xi1+a2xi2+⋯+apxip))2+λj=1∑paj2
也可写为为 Minimize β ∣ ∣ y − X β ∣ ∣ 2 2 + λ ∣ ∣ β ∣ ∣ 2 2 \text{Minimize}_\beta||y-X\beta||_2^2+\lambda||\beta||_2^2 Minimizeβ∣∣y−Xβ∣∣22+λ∣∣β∣∣22
其中,y 是因变量向量,X 是包含所有特征(包括截距项)的设计矩阵,β 是回归系数向量,而 λ 是正则化强度参数,控制着模型对复杂度的惩罚程度。
-
选择正则化参数 λ:
- 交叉验证(多次实验):通过分割训练数据集为若干子集并计算不同 λ 下的交叉验证误差,选择使得验证误差最小的那个 λ。
- 通过轮廓线(L-curve)、网格搜索或其他启发式方法寻找最优的 λ 值。
通过梯度下降法或其变种(如随机梯度下降、小批量梯度下降等)来计算参数。对于每个参数计算梯度,然后进行参数更新,达到满意的状态。
这里用的是批量梯度下降法,如果使用随机梯度下降或小批量梯度下降,只需将全体样本N替换为随机选取的一个或一小批样本即可。
岭回归主要适用于过拟合严重或各变量之间存在多重共线性的情况,它可以解决特征数量比样本量多的问题,另外,岭回归作为一种缩减算法可以判断哪些特征重要或者不重要,有点类似于降维,缩减算法可以看作是对一个模型增加偏差的同时减少方差。但是岭回归方程的R2会稍低于普通回归分析,但回归系数的显著性往往明显高于普通回归。
【代码示例】python实现岭回归
# 岭回归
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import Ridge
# 数据
data=[
[0.07,3.12],[0.41,3.82],[0.99,4.55],[0.73,4.25],[0.98,4.56],
[0.55,3.92],[0.34,3.53],[0.03,3.15],[0.13,3.11],[0.13,3.15],
[0.31,3.47],[0.65,4.12],[0.73,4.28],[0.23,3.48],[0.96,4.65],
[0.62,3.95],[0.36,3.51],[0.15,3.12],[0.63,4.09],[0.23,3.46],
[0.08,3.22],[0.06,3.19],[0.92,4.63],[0.71,4.29],[0.01,3.08],
[0.34,3.45],[0.04,3.16],[0.21,3.36],[0.61,3.99],[0.54,3.89] ]
dataMat = np.array(data) # 将列表转换为二维矩阵
# 生成x和y
x = dataMat[:,0:1]
y = dataMat[:,1]
model = Ridge(alpha=0.5) # alpha为参数更新时的学习率
model.fit(x,y)
print("系数矩阵:",model.coef_)
print("线性回归模型:\n",model)
predicted = model.predict(x)
plt.scatter(x,y,marker='o')
plt.plot(x,predicted,color = 'r')
plt.xlabel('x')
plt.ylabel('y')
plt.show
运行结果:
系数矩阵: [1.40203058]
线性回归模型:
Ridge(alpha=0.5)
【代码示例】使用交叉验证得到最佳的学习率alpha
# 岭回归交叉验证
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import RidgeCV
# 数据
data=[
[0.07,3.12],[0.41,3.82],[0.99,4.55],[0.73,4.25],[0.98,4.56],
[0.55,3.92],[0.34,3.53],[0.03,3.15],[0.13,3.11],[0.13,3.15],
[0.31,3.47],[0.65,4.12],[0.73,4.28],[0.23,3.48],[0.96,4.65],
[0.62,3.95],[0.36,3.51],[0.15,3.12],[0.63,4.09],[0.23,3.46],
[0.08,3.22],[0.06,3.19],[0.92,4.63],[0.71,4.29],[0.01,3.08],
[0.34,3.45],[0.04,3.16],[0.21,3.36],[0.61,3.99],[0.54,3.89] ]
dataMat = np.array(data) # 将列表转换为二维矩阵
# 生成x和y
x = dataMat[:,0:1]
y = dataMat[:,1]
model = RidgeCV([0.1,0.5,1.0,10.0]) # 多个alpha交叉验证
model.fit(x,y)
print("效果最佳的alpha = ",model.alpha_)
运行结果:
效果最佳的alpha = 0.1
套索回归 vs 岭回归
岭回归无法剔除变量,而Lasso(Least Absolute Shrinkage and Selection Operator)回归模型,将惩罚项由L2范数变为L1范数,可以将一些不重要的回归系数缩减为0,达到剔除变量的目的。
在实践中,岭回归(Ridge)和lasso我们首选岭回归。不过如果特征数过多,但是其中只有几个特征是重要的,则选择lasso效果会更好。同时,lasso由于其模型更加便于理解(因为它只选择一部分输入特征),所以有时候用lasso回归效果也不理想。
当然,如果我们能够将两者进行优势互补,则会达到更佳的效果,在scikit-earn中提过了ElasticNet类,就是结合了这两种回归的惩罚项。在实践中效果会更好,不过需要同时调节L1和L2正则化参数。