Python 机器学习 基础 之 监督学习 [线性模型] 算法 的简单说明
目录
Python 机器学习 基础 之 监督学习 [线性模型] 算法 的简单说明
一、简单介绍
二、监督学习 算法 说明前的 数据集 说明
三、监督学习 之 线性模型 算法
1、用于回归的线性模型
2、线性回归(又名普通最小二乘法)
3、岭回归(Ridge Regression)
4、Lasso 回归
5、用于分类的线性模型
6、用于多分类的线性模型
7、优点、缺点和参数
8、方法链
附录
一、简单介绍
Python是一种跨平台的计算机程序设计语言。是一种面向对象的动态类型语言,最初被设计用于编写自动化脚本(shell),随着版本的不断更新和语言新功能的添加,越多被用于独立的、大型项目的开发。Python是一种解释型脚本语言,可以应用于以下领域: Web 和 Internet开发、科学计算和统计、人工智能、教育、桌面界面开发、软件开发、后端开发、网络爬虫。
Python 机器学习是利用 Python 编程语言中的各种工具和库来实现机器学习算法和技术的过程。Python 是一种功能强大且易于学习和使用的编程语言,因此成为了机器学习领域的首选语言之一。Python 提供了丰富的机器学习库,如Scikit-learn、TensorFlow、Keras、PyTorch等,这些库包含了许多常用的机器学习算法和深度学习框架,使得开发者能够快速实现、测试和部署各种机器学习模型。
Python 机器学习涵盖了许多任务和技术,包括但不限于:
- 监督学习:包括分类、回归等任务。
- 无监督学习:如聚类、降维等。
- 半监督学习:结合了有监督和无监督学习的技术。
- 强化学习:通过与环境的交互学习来优化决策策略。
- 深度学习:利用深度神经网络进行学习和预测。
通过 Python 进行机器学习,开发者可以利用其丰富的工具和库来处理数据、构建模型、评估模型性能,并将模型部署到实际应用中。Python 的易用性和庞大的社区支持使得机器学习在各个领域都得到了广泛的应用和发展。
二、监督学习 算法 说明前的 数据集 说明
相关说明可见如下链接的 < 二、监督学习 算法 说明前的 数据集 说明 >,这里不在赘述:
数据集 说明:Python 机器学习 基础 之 监督学习 K-NN (K-邻近)算法 的简单说明-CSDN博客
三、监督学习 之 线性模型 算法
线性模型是在实践中广泛使用的一类模型,几十年来被广泛研究,它可以追溯到一百多年前。线性模型利用输入特征的线性函数 (linear function)进行预测。
在监督学习中,线性模型是一类常见的算法,它们试图通过将输入特征的线性组合与目标变量建立关系来建立模型。线性模型的核心思想是假设输入特征和目标变量之间存在线性关系,并通过学习线性参数来拟合数据。
以下是一些常见的线性模型算法:
线性回归(Linear Regression): 线性回归是一种用于建立输入特征与连续目标变量之间的线性关系的算法。它通过最小化预测值与实际值之间的平方误差来找到最佳拟合直线。线性回归适用于解决回归问题。
逻辑回归(Logistic Regression): 逻辑回归是一种用于建立输入特征与二元分类目标变量之间的关系的算法。尽管名称中含有"回归"一词,但逻辑回归实际上是一种分类算法,它使用 logistic 函数将线性模型的输出映射到[0, 1]区间,以表示分类概率。逻辑回归适用于解决二分类问题。
岭回归(Ridge Regression): 岭回归是线性回归的一种正则化形式,它通过在损失函数中加入 L2 正则项(权重的平方和)来控制模型的复杂度,并防止模型过拟合。岭回归适用于解决回归问题。
Lasso 回归: Lasso 回归是线性回归的另一种正则化形式,它通过在损失函数中加入 L1 正则项(权重的绝对值之和)来控制模型的复杂度。Lasso 回归不仅可以用于特征选择,还可以防止模型过拟合。
这些线性模型算法在实践中被广泛应用于各种领域的问题,它们具有简单、易于理解、高效等优点,并且通常表现良好,尤其在数据集具有线性关系或数据集维度较高的情况下。
1、用于回归的线性模型
对于回归问题,线性模型预测的一般公式如下:
这里 x [0] 到 x [p ] 表示单个数据点的特征(本例中特征个数为 p +1),w 和 b 是学习模型的参数 是模型的预测结果。对于单一特征的数据集,公式如下:
你可能还记得,这就是高中数学里的直线方程。这里 w [0] 是斜率,b 是 y 轴偏移。对于有更多特征的数据集,w 包含沿每个特征坐标轴的斜率。或者,你也可以将预测的响应值看作输入特征的加权求和,权重由 w 的元素给出(可以取负值)。
下列代码可以在一维 wave
数据集上学习参数 w [0] 和 b :
import pandas as pd
import matplotlib.pyplot as plt
from pandas.plotting import scatter_matrix
import mglearn
import numpy as np
mglearn.plots.plot_linear_regression_wave()
plt.savefig('02LinearModel-01.png')
w[0]: 0.393906 b: -0.031804
线性模型对 wave
数据集的预测结果
我们在图中添加了坐标网格,便于理解直线的含义。从 w[0]
可以看出,斜率应该在 0.4 左右,在图像中也可以直观地确认这一点。截距是指预测直线与 y 轴的交点:比 0 略小,也可以在图像中确认。
用于回归的线性模型可以表示为这样的回归模型:对单一特征的预测结果是一条直线,两个特征时是一个平面,或者在更高维度(即更多特征)时是一个超平面。
如果将直线的预测结果与 KNeighborsRegressor
的预测结果进行比较,你会发现直线的预测能力非常受限。似乎数据的所有细节都丢失了。从某种意义上来说,这种说法是正确的。假设目标 y 是特征的线性组合,这是一个非常强的(也有点不现实的)假设。但观察一维数据得出的观点有些片面。对于有多个特征的数据集而言,线性模型可以非常强大。特别地,如果特征数量大于训练数据点的数量,任何目标 y 都可以(在训练集上)用线性函数完美拟合 。
有许多不同的线性回归模型。这些模型之间的区别在于如何从训练数据中学习参数 w 和 b ,以及如何控制模型复杂度。下面介绍最常见的线性回归模型。
2、线性回归(又名普通最小二乘法)
线性回归,或者普通最小二乘法 (ordinary least squares,OLS),是回归问题最简单也最经典的线性方法。线性回归寻找参数 w 和 b ,使得对训练集的预测值与真实的回归目标值 y 之间的均方误差 最小。均方误差(mean squared error)是预测值与真实值之差的平方和除以样本数。线性回归没有参数,这是一个优点,但也因此无法控制模型的复杂度。
线性回归是一种用于建立输入特征与连续目标变量之间线性关系的监督学习算法。它是一种简单而强大的模型,通常用于预测数值型输出。线性回归的核心思想是假设输入特征与目标变量之间存在线性关系,并通过学习线性参数来拟合数据。
线性回归的数学模型
线性回归模型可以表示为:
其中, 是模型的预测值,𝑥1,𝑥2,…,𝑥𝑛 是输入特征,𝑤0,𝑤1,𝑤2,…,𝑤𝑛 是模型的参数(也称为权重),𝑛 是特征的数量。模型的预测值 是输入特征的线性组合,加上一个常数项 𝑤0,也称为截距。
模型训练
线性回归模型的训练目标是找到一组最优的参数 𝑤0,𝑤1,𝑤2,…,𝑤𝑛,使得模型的预测值 与实际值 𝑦y 之间的损失函数最小化。常用的损失函数是均方误差(Mean Squared Error,MSE),定义为预测值与实际值之间差值的平方和的均值:
其中,𝑚 是样本数量, 𝑖是第 𝑖 个样本的预测值,𝑦𝑖 是第 𝑖 个样本的实际值。
通过最小化均方误差,可以得到最优的参数值,使得模型能够最好地拟合训练数据。
模型评估
在训练完成后,我们需要评估模型的性能。常见的评估指标包括均方误差(MSE)、平均绝对误差(Mean Absolute Error,MAE)、决定系数(Coefficient of Determination,R²)等。这些指标用于衡量模型对训练数据的拟合程度和泛化能力。
示例
假设我们有一组房屋的特征数据,包括房屋的面积、卧室数量等,以及对应的房价。我们可以使用线性回归模型来建立输入特征(如面积、卧室数量)与房价之间的线性关系,从而预测未来房屋的价格。通过训练线性回归模型,并使用交叉验证等方法评估模型的性能,我们可以得出模型的预测能力和泛化能力,并进行房价预测。
下列代码可以生成上图中的模型:
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
import mglearn
X, y = mglearn.datasets.make_wave(n_samples=60)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
lr = LinearRegression().fit(X_train, y_train)
“斜率”参数(w ,也叫作权重或系数 )被保存在 coef_
属性中,而偏移或截距 (b )被保存在 intercept_
属性中:
print("lr.coef_: {}".format(lr.coef_))
print("lr.intercept_: {}".format(lr.intercept_))
可能注意到了
coef_
和intercept_
结尾处奇怪的下划线。scikit-learn
总是将从训练数据中得出的值保存在以下划线结尾的属性中。这是为了将其与用户设置的参数区分开。
intercept_
属性是一个浮点数,而 coef_
属性是一个 NumPy 数组,每个元素对应一个输入特征。由于 wave
数据集中只有一个输入特征,所以 lr.coef_
中只有一个元素。
来看一下训练集和测试集的性能:
print("Training set score: {:.2f}".format(lr.score(X_train, y_train)))
print("Test set score: {:.2f}".format(lr.score(X_test, y_test)))
约为 0.66,这个结果不是很好,但我们可以看到,训练集和测试集上的分数非常接近。这说明可能存在欠拟合,而不是过拟合。对于这个一维数据集来说,过拟合的风险很小,因为模型非常简单(或受限)。然而,对于更高维的数据集(即有大量特征的数据集),线性模型将变得更加强大,过拟合的可能性也会变大。我们来看一下 LinearRegression
在更复杂的数据集上的表现,比如波士顿房价数据集。记住,这个数据集有 506 个样本和 105 个导出特征。首先,加载数据集并将其分为训练集和测试集。然后像前面一样构建线性回归模型:
X, y = mglearn.datasets.load_extended_boston()
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
lr = LinearRegression().fit(X_train, y_train)
比较一下训练集和测试集的分数就可以发现,我们在训练集上的预测非常准确,但测试集上的 R2 要低很多:
print("Training set score: {:.2f}".format(lr.score(X_train, y_train)))
print("Test set score: {:.2f}".format(lr.score(X_test, y_test)))
训练集和测试集之间的性能差异是过拟合的明显标志,因此我们应该试图找到一个可以控制复杂度的模型。标准线性回归最常用的替代方法之一就是岭回归 (ridge regression),
3、岭回归(Ridge Regression)
岭回归也是一种用于回归的线性模型,因此它的预测公式与普通最小二乘法相同。但在岭回归中,对系数(w )的选择不仅要在训练数据上得到好的预测结果,而且还要拟合附加约束。我们还希望系数尽量小。换句话说,w 的所有元素都应接近于 0。直观上来看,这意味着每个特征对输出的影响应尽可能小(即斜率很小),同时仍给出很好的预测结果。这种约束是所谓 正则化 (regularization)的一个例子。正则化是指对模型做显式约束,以避免过拟合。岭回归用到的这种被称为 L2 正则化。
岭回归(Ridge Regression)是线性回归的一种正则化形式,旨在解决线性回归中存在的过拟合问题。与普通线性回归不同的是,岭回归在损失函数中添加了一个正则化项,以限制模型参数的大小,从而降低模型复杂度,防止过拟合。
岭回归的数学模型
岭回归的数学模型可以表示为:
其中, 是模型的预测值,𝑥1,𝑥2,…,𝑥𝑛 是输入特征,𝑤0,𝑤1,𝑤2,…,𝑤𝑛 是模型的参数,𝛼 是正则化参数(也称为惩罚项的强度),MSEMSE 是均方误差(Mean Squared Error)损失函数。正则化项 是模型参数的平方和,它限制了模型参数的大小。
正则化参数 𝛼 的影响
正则化参数 𝛼 控制了正则化项的重要性。较大的 𝛼 值会导致模型参数的惩罚更加严厉,从而限制了模型的复杂度,防止过拟合。较小的 𝛼 值则允许模型更加灵活,但可能会增加过拟合的风险。
模型训练
与普通线性回归类似,岭回归的训练目标是最小化损失函数。在实际训练过程中,通过梯度下降等优化算法来更新模型参数,使损失函数达到最小值。岭回归通过添加正则化项来惩罚较大的参数值,从而使得模型更加稳定。
示例
假设我们有一个具有高度相关特征的数据集,线性回归可能会受到多重共线性(multicollinearity)的影响,导致参数估计不稳定,而岭回归则可以通过正则化来解决这个问题。在实际应用中,岭回归通常用于处理高维数据和共线性问题,提高模型的泛化能力和稳定性。
from sklearn.linear_model import Ridge ridge = Ridge(alpha=1.0) # 设置正则化参数 alpha ridge.fit(X_train, y_train) # 训练岭回归模型
以上是一个在 Python 中使用 Scikit-Learn 进行岭回归训练的示例。在实际应用中,我们可以通过交叉验证等技术来选择最优的正则化参数 𝛼。
岭回归在 linear_model.Ridge
中实现。来看一下它对扩展的波士顿房价数据集的效果如何:
from sklearn.linear_model import Ridge
ridge = Ridge().fit(X_train, y_train)
print("Training set score: {:.2f}".format(ridge.score(X_train, y_train)))
print("Test set score: {:.2f}".format(ridge.score(X_test, y_test)))
可以看出,Ridge
在训练集上的分数要低 于 LinearRegression
,但在测试集上的分数更高 。这和我们的预期一致。线性回归对数据存在过拟合。Ridge
是一种约束更强的模型,所以更不容易过拟合。复杂度更小的模型意味着在训练集上的性能更差,但泛化性能更好。由于我们只对泛化性能感兴趣,所以应该选择 Ridge
模型而不是 LinearRegression
模型。
Ridge
模型在模型的简单性(系数都接近于 0)与训练集性能之间做出权衡。简单性和训练集性能二者对于模型的重要程度可以由用户通过设置 alpha
参数来指定。在前面的例子中,我们用的是默认参数 alpha=1.0
。但没有理由认为这会给出最佳权衡。alpha
的最佳设定值取决于用到的具体数据集。增大 alpha
会使得系数更加趋向于 0,从而降低训练集性能,但可能会提高泛化性能。例如:
ridge10 = Ridge(alpha=10).fit(X_train, y_train)
print("Training set score: {:.2f}".format(ridge10.score(X_train, y_train)))
print("Test set score: {:.2f}".format(ridge10.score(X_test, y_test)))
减小 alpha 可以让系数受到的限制更小,对于非常小的 alpha
值,系数几乎没有受到限制,我们得到一个与 LinearRegression
类似的模型:
ridge01 = Ridge(alpha=0.1).fit(X_train, y_train)
print("Training set score: {:.2f}".format(ridge01.score(X_train, y_train)))
print("Test set score: {:.2f}".format(ridge01.score(X_test, y_test)))
这里 alpha=0.1
似乎效果不错。我们可以尝试进一步减小 alpha
以提高泛化性能。现在,注意参数 alpha
与模型复杂度的对应关系。
我们还可以查看 alpha
取不同值时模型的 coef_
属性,从而更加定性地理解 alpha
参数是如何改变模型的。更大的 alpha
表示约束更强的模型,所以我们预计大 alpha
对应的 coef_
元素比小 alpha
对应的 coef_
元素要小:
plt.plot(ridge.coef_, 's', label="Ridge alpha=1")
plt.plot(ridge10.coef_, '^', label="Ridge alpha=10")
plt.plot(ridge01.coef_, 'v', label="Ridge alpha=0.1")
plt.plot(lr.coef_, 'o', label="LinearRegression")
plt.xlabel("Coefficient index")
plt.ylabel("Coefficient magnitude")
plt.hlines(0, 0, len(lr.coef_))
plt.ylim(-25, 25)
plt.legend()
plt.savefig('02LinearModel-02.png')
不同 alpha
值的岭回归与线性回归的系数比较
这里 x 轴对应 coef_
的元素:x=0
对应第一个特征的系数,x=1
对应第二个特征的系数,以此类推,一直到 x=100
。y 轴表示该系数的具体数值。这里需要记住的是,对于 alpha=10
,系数大多在 -3 和 3 之间。对于 alpha=1
的 Ridge
模型,系数要稍大一点。对于 alpha=0.1
,点的范围更大。对于没有做正则化的线性回归(即 alpha=0
),点的范围很大,许多点都超出了图像的范围。
还有一种方法可以用来理解正则化的影响,就是固定 alpha
值,但改变训练数据量。对于下图 来说,我们对波士顿房价数据集做二次抽样,并在数据量逐渐增加的子数据集上分别对 LinearRegression
和 Ridge(alpha=1)
两个模型进行评估(将模型性能作为数据集大小的函数进行绘图,这样的图像叫作学习曲线 ):
mglearn.plots.plot_ridge_n_samples()
plt.savefig('02LinearModel-03.png')
岭回归和线性回归在波士顿房价数据集上的学习曲线
正如所预计的那样,无论是岭回归还是线性回归,所有数据集大小对应的训练分数都要高于测试分数。由于岭回归是正则化的,因此它的训练分数要整体低于线性回归的训练分数。但岭回归的测试分数要更高,特别是对较小的子数据集。如果少于 400 个数据点,线性回归学不到任何内容。随着模型可用的数据越来越多,两个模型的性能都在提升,最终线性回归的性能追上了岭回归。这里要记住的是,如果有足够多的训练数据,正则化变得不那么重要,并且岭回归和线性回归将具有相同的性能(在这个例子中,二者相同恰好发生在整个数据集的情况下,这只是一个巧合)。上图中还有一个有趣之处,就是线性回归的训练性能在下降。如果添加更多数据,模型将更加难以过拟合或记住所有的数据。
4、Lasso 回归
除了 Ridge
,还有一种正则化的线性回归是 Lasso
。与岭回归相同,使用 lasso 也是约束系数使其接近于 0,但用到的方法不同,叫作 L1 正则化。 L1 正则化的结果是,使用 lasso 时某些系数刚好为 0 。这说明某些特征被模型完全忽略。这可以看作是一种自动化的特征选择。某些系数刚好为 0,这样模型更容易解释,也可以呈现模型最重要的特征。
Lasso 回归(Least Absolute Shrinkage and Selection Operator Regression)是线性回归的一种正则化形式,类似于岭回归,旨在解决线性回归中存在的过拟合问题。与岭回归不同的是,Lasso 回归在损失函数中添加了 L1 正则化项,以限制模型参数的大小,并且具有特征选择的作用。
Lasso 回归的数学模型
Lasso 回归的数学模型可以表示为:
其中, 是模型的预测值,𝑥1,𝑥2,…,𝑥𝑛是输入特征,𝑤0,𝑤1,𝑤2,…,𝑤𝑛 是模型的参数,𝛼 是正则化参数(也称为惩罚项的强度),MSE 是均方误差(Mean Squared Error)损失函数。正则化项 是模型参数的绝对值之和,它限制了模型参数的大小。
正则化参数 𝛼 的影响
与岭回归类似,正则化参数 𝛼 控制了正则化项的重要性。较大的 𝛼 值会导致模型参数的惩罚更加严厉,从而限制了模型的复杂度,防止过拟合。较小的 𝛼 值则允许模型更加灵活,但可能会增加过拟合的风险。
模型训练
Lasso 回归的训练目标与普通线性回归相似,是最小化损失函数。在实际训练过程中,通过梯度下降等优化算法来更新模型参数,使损失函数达到最小值。Lasso 回归通过添加 L1 正则化项来限制模型参数的大小,并且具有特征选择的功能,即能够将某些不重要的特征的系数缩小为零,从而实现特征选择。
示例
与岭回归类似,Lasso 回归也可以用于处理高维数据和共线性问题,提高模型的泛化能力和稳定性。以下是一个在 Python 中使用 Scikit-Learn 进行 Lasso 回归训练的示例:
from sklearn.linear_model import Lasso lasso = Lasso(alpha=1.0) # 设置正则化参数 alpha lasso.fit(X_train, y_train) # 训练 Lasso 回归模型
在实际应用中,我们可以通过交叉验证等技术来选择最优的正则化参数 𝛼。
我们将 lasso 应用在扩展的波士顿房价数据集上:
from sklearn.linear_model import Lasso
lasso = Lasso().fit(X_train, y_train)
print("Training set score: {:.2f}".format(lasso.score(X_train, y_train)))
print("Test set score: {:.2f}".format(lasso.score(X_test, y_test)))
print("Number of features used: {}".format(np.sum(lasso.coef_ != 0)))
如你所见,Lasso
在训练集与测试集上的表现都很差。这表示存在欠拟合,我们发现模型只用到了 105 个特征中的 4 个。与 Ridge
类似,Lasso
也有一个正则化参数 alpha
,可以控制系数趋向于 0 的强度。在上一个例子中,我们用的是默认值 alpha=1.0
。为了降低欠拟合,我们尝试减小 alpha
。这么做的同时,我们还需要增加 max_iter
的值(运行迭代的最大次数):
# 我们增大max_iter的值,否则模型会警告我们,说应该增大max_iter
lasso001 = Lasso(alpha=0.01, max_iter=100000).fit(X_train, y_train)
print("Training set score: {:.2f}".format(lasso001.score(X_train, y_train)))
print("Test set score: {:.2f}".format(lasso001.score(X_test, y_test)))
print("Number of features used: {}".format(np.sum(lasso001.coef_ != 0)))
alpha
值变小,我们可以拟合一个更复杂的模型,在训练集和测试集上的表现也更好。模型性能比使用 Ridge
时略好一点,而且我们只用到了 105 个特征中的 33 个。这样模型可能更容易理解。
但如果把 alpha
设得太小,那么就会消除正则化的效果,并出现过拟合,得到与 LinearRegression
类似的结果:
lasso00001 = Lasso(alpha=0.0001, max_iter=100000).fit(X_train, y_train)
print("Training set score: {:.2f}".format(lasso00001.score(X_train, y_train)))
print("Test set score: {:.2f}".format(lasso00001.score(X_test, y_test)))
print("Number of features used: {}".format(np.sum(lasso00001.coef_ != 0)))
再次像之前那样对不同模型的系数进行作图,见下图
plt.plot(lasso.coef_, 's', label="Lasso alpha=1")
plt.plot(lasso001.coef_, '^', label="Lasso alpha=0.01")
plt.plot(lasso00001.coef_, 'v', label="Lasso alpha=0.0001")
plt.plot(ridge01.coef_, 'o', label="Ridge alpha=0.1")
plt.legend(ncol=2, loc=(0, 1.05))
plt.ylim(-25, 25)
plt.xlabel("Coefficient index")
plt.ylabel("Coefficient magnitude")
plt.savefig('02LinearModel-04.png')
不同 alpha
值的 lasso 回归与岭回归的系数比较
在 alpha=1
时,我们发现不仅大部分系数都是 0(我们已经知道这一点),而且其他系数也都很小。将 alpha
减小至 0.01
,我们得到图中向上的三角形,大部分特征等于 0。alpha=0.0001
时,我们得到正则化很弱的模型,大部分系数都不为 0,并且还很大。为了便于比较,图中用圆形表示 Ridge
的最佳结果。alpha=0.1
的 Ridge
模型的预测性能与 alpha=0.01
的 Lasso
模型类似,但 Ridge
模型的所有系数都不为 0。
在实践中,在两个模型中一般首选岭回归。但如果特征很多,你认为只有其中几个是重要的,那么选择 Lasso
可能更好。同样,如果你想要一个容易解释的模型,Lasso
可以给出更容易理解的模型,因为它只选择了一部分输入特征。scikit-learn
还提供了 ElasticNet
类,结合了 Lasso
和 Ridge
的惩罚项。在实践中,这种结合的效果最好,不过代价是要调节两个参数:一个用于 L1 正则化,一个用于 L2 正则化。
5、用于分类的线性模型
线性模型也广泛应用于分类问题。我们首先来看二分类。这时可以利用下面的公式进行预测:
这个公式看起来与线性回归的公式非常相似,但我们没有返回特征的加权求和,而是为预测设置了阈值(0)。如果函数值小于 0,我们就预测类别 -1;如果函数值大于 0,我们就预测类别 +1。对于所有用于分类的线性模型,这个预测规则都是通用的。同样,有很多种不同的方法来找出系数(w )和截距(b )。
对于用于回归的线性模型,输出 是特征的线性函数,是直线、平面或超平面(对于更高维的数据集)。对于用于分类的线性模型,决策边界 是输入的线性函数。换句话说,(二元)线性分类器是利用直线、平面或超平面来分开两个类别的分类器。本节我们将看到这方面的例子。
学习线性模型有很多种算法。这些算法的区别在于以下两点:
- 系数和截距的特定组合对训练数据拟合好坏的度量方法;
- 是否使用正则化,以及使用哪种正则化方法。
不同的算法使用不同的方法来度量“对训练集拟合好坏”。由于数学上的技术原因,不可能调节 w 和 b 使得算法产生的误分类数量最少。对于我们的目的,以及对于许多应用而言,上面第一点(称为损失函数 )的选择并不重要。
最常见的两种线性分类算法是 Logistic 回归 (logistic regression)和线性支持向量机 (linear support vector machine,线性 SVM),前者在
linear_model.LogisticRegression
中实现,后者在svm.LinearSVC
(SVC 代表支持向量分类器)中实现。虽然LogisticRegression
的名字中含有回归(regression),但它是一种分类算法,并不是回归算法,不应与LinearRegression
混淆。
我们可以将 LogisticRegression
和 LinearSVC
模型应用到 forge
数据集上,并将线性模型找到的决策边界可视化
from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC
X, y = mglearn.datasets.make_forge()
fig, axes = plt.subplots(1, 2, figsize=(10, 3))
for model, ax in zip([LinearSVC(), LogisticRegression()], axes):
clf = model.fit(X, y)
mglearn.plots.plot_2d_separator(clf, X, fill=False, eps=0.5,
ax=ax, alpha=.7)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y, ax=ax)
ax.set_title("{}".format(clf.__class__.__name__))
ax.set_xlabel("Feature 0")
ax.set_ylabel("Feature 1")
axes[0].legend()
plt.savefig('02LinearModel-05.png')
线性 SVM 和 Logistic 回归在 forge
数据集上的决策边界(均为默认参数)
在这张图中,forge
数据集的第一个特征位于 x 轴,第二个特征位于 y 轴,与前面相同。图中分别展示了 LinearSVC
和 LogisticRegression
得到的决策边界,都是直线,将顶部归为类别 1 的区域和底部归为类别 0 的区域分开了。换句话说,对于每个分类器而言,位于黑线上方的新数据点都会被划为类别 1,而在黑线下方的点都会被划为类别 0。
两个模型得到了相似的决策边界。注意,两个模型中都有两个点的分类是错误的。两个模型都默认使用 L2 正则化,就像 Ridge
对回归所做的那样。
对于 LogisticRegression
和 LinearSVC
,决定正则化强度的权衡参数叫作 C
。C
值越大,对应的正则化越弱 。换句话说,如果参数 C
值较大,那么 LogisticRegression
和 LinearSVC
将尽可能将训练集拟合到最好,而如果 C
值较小,那么模型更强调使系数向量(w )接近于 0。
参数 C
的作用还有另一个有趣之处。较小的 C
值可以让算法尽量适应“大多数”数据点,而较大的 C
值更强调每个数据点都分类正确的重要性。下面是使用 LinearSVC
的图示
mglearn.plots.plot_linear_svc_regularization()
plt.savefig('02LinearModel-06.png')
不同 C
值的线性 SVM 在 forge
数据集上的决策边界
在左侧的图中,C
值很小,对应强正则化。大部分属于类别 0 的点都位于底部,大部分属于类别 1 的点都位于顶部。强正则化的模型会选择一条相对水平的线,有两个点分类错误。在中间的图中,C
值稍大,模型更关注两个分类错误的样本,使决策边界的斜率变大。最后,在右侧的图中,模型的 C
值非常大,使得决策边界的斜率也很大,现在模型对类别 0 中所有点的分类都是正确的。类别 1 中仍有一个点分类错误,这是因为对这个数据集来说,不可能用一条直线将所有点都分类正确。右侧图中的模型尽量使所有点的分类都正确,但可能无法掌握类别的整体分布。换句话说,这个模型很可能过拟合。
与回归的情况类似,用于分类的线性模型在低维空间中看起来可能非常受限,决策边界只能是直线或平面。同样,在高维空间中,用于分类的线性模型变得非常强大,当考虑更多特征时,避免过拟合变得越来越重要。
我们在乳腺癌数据集上详细分析 LogisticRegression
:
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
cancer.data, cancer.target, stratify=cancer.target, random_state=42)
logreg = LogisticRegression().fit(X_train, y_train)
print("Training set score: {:.3f}".format(logreg.score(X_train, y_train)))
print("Test set score: {:.3f}".format(logreg.score(X_test, y_test)))
C=1
的默认值给出了相当好的性能,在训练集和测试集上都达到 95% 的精度。但由于训练集和测试集的性能非常接近,所以模型很可能是欠拟合的。我们尝试增大 C
来拟合一个更灵活的模型:
logreg100 = LogisticRegression(C=100).fit(X_train, y_train)
print("Training set score: {:.3f}".format(logreg100.score(X_train, y_train)))
print("Test set score: {:.3f}".format(logreg100.score(X_test, y_test)))
使用 C=100
可以得到更高的训练集精度,也得到了稍高的测试集精度,这也证实了我们的直觉,即更复杂的模型应该性能更好。
我们还可以研究使用正则化更强的模型时会发生什么。设置 C=0.01
:
logreg001 = LogisticRegression(C=0.01).fit(X_train, y_train)
print("Training set score: {:.3f}".format(logreg001.score(X_train, y_train)))
print("Test set score: {:.3f}".format(logreg001.score(X_test, y_test)))
正如我们所料,将已经欠拟合的模型继续向左移动,训练集和测试集的精度都比采用默认参数时更小。
最后,来看一下正则化参数 C
取三个不同的值时模型学到的系数
plt.plot(logreg.coef_.T, 'o', label="C=1")
plt.plot(logreg100.coef_.T, '^', label="C=100")
plt.plot(logreg001.coef_.T, 'v', label="C=0.001")
plt.xticks(range(cancer.data.shape[1]), cancer.feature_names, rotation=90)
plt.hlines(0, 0, cancer.data.shape[1])
plt.ylim(-5, 5)
plt.xlabel("Coefficient index")
plt.ylabel("Coefficient magnitude")
plt.legend()
plt.savefig('02LinearModel-07.png', bbox_inches='tight')
不同 C
值的 Logistic 回归在乳腺癌数据集上学到的系数
由于
LogisticRegression
默认应用 L2 正则化,所以其结果与图 2-12 中Ridge
的结果类似。更强的正则化使得系数更趋向于 0,但系数永远不会正好等于 0。进一步观察图像,还可以在第 3 个系数那里发现有趣之处,这个系数是“平均周长”(mean perimeter)。C=100
和C=1
时,这个系数为负,而C=0.001
时这个系数为正,其绝对值比C=1
时还要大。在解释这样的模型时,人们可能会认为,系数可以告诉我们某个特征与哪个类别有关。例如,人们可能会认为高“纹理错误”(texture error)特征与“恶性”样本有关。但“平均周长”系数的正负号发生变化,说明较大的“平均周长”可以被当作“良性”的指标或“恶性”的指标,具体取决于我们考虑的是哪个模型。这也说明,对线性模型系数的解释应该始终持保留态度。
如果想要一个可解释性更强的模型,使用 L1 正则化可能更好,因为它约束模型只使用少数几个特征。
for C, marker in zip([0.001, 1, 100], ['o', '^', 'v']):
lr_l1 = LogisticRegression(C=C, penalty="l1", solver='liblinear').fit(X_train, y_train)
print("Training accuracy of l1 logreg with C={:.3f}: {:.2f}".format(
C, lr_l1.score(X_train, y_train)))
print("Test accuracy of l1 logreg with C={:.3f}: {:.2f}".format(
C, lr_l1.score(X_test, y_test)))
plt.plot(lr_l1.coef_.T, marker, label="C={:.3f}".format(C))
plt.xticks(range(cancer.data.shape[1]), cancer.feature_names, rotation=90)
plt.hlines(0, 0, cancer.data.shape[1])
plt.xlabel("Coefficient index")
plt.ylabel("Coefficient magnitude")
plt.ylim(-5, 5)
plt.legend(loc=3)
plt.savefig('02LinearModel-08.png', bbox_inches='tight')
plt.show()
Training accuracy of l1 logreg with C=0.001: 0.91 Test accuracy of l1 logreg with C=0.001: 0.92 Training accuracy of l1 logreg with C=1.000: 0.96 Test accuracy of l1 logreg with C=1.000: 0.96 Training accuracy of l1 logreg with C=100.000: 0.99 Test accuracy of l1 logreg with C=100.000: 0.98
如你所见,用于二分类的线性模型与用于回归的线性模型有许多相似之处。与用于回归的线性模型一样,模型的主要差别在于 penalty
参数,这个参数会影响正则化,也会影响模型是使用所有可用特征还是只选择特征的一个子集。
6、用于多分类的线性模型
许多线性分类模型只适用于二分类问题,不能轻易推广到多类别问题(除了 Logistic 回归)。将二分类算法推广到多分类算法的一种常见方法是“一对其余 ”(one-vs.-rest)方法。在“一对其余”方法中,对每个类别都学习一个二分类模型,将这个类别与所有其他类别尽量分开,这样就生成了与类别个数一样多的二分类模型。在测试点上运行所有二类分类器来进行预测。在对应类别上分数最高的分类器“胜出”,将这个类别标签返回作为预测结果。
每个类别都对应一个二类分类器,这样每个类别也都有一个系数(w )向量和一个截距(b )。下面给出的是分类置信方程,其结果中最大值对应的类别即为预测的类别标签:
多分类 Logistic 回归背后的数学与“一对其余”方法稍有不同,但它也是对每个类别都有一个系数向量和一个截距,也使用了相同的预测方法。
我们将“一对其余”方法应用在一个简单的三分类数据集上。我们用到了一个二维数据集,每个类别的数据都是从一个高斯分布中采样得出的
from sklearn.datasets import make_blobs
X, y = make_blobs(random_state=42)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
plt.legend(["Class 0", "Class 1", "Class 2"])
plt.savefig('02LinearModel-09.png', bbox_inches='tight')
plt.show()
包含 3 个类别的二维玩具数据集
现在,在这个数据集上训练一个 LinearSVC
分类器:
linear_svm = LinearSVC().fit(X, y)
print("Coefficient shape: ", linear_svm.coef_.shape)
print("Intercept shape: ", linear_svm.intercept_.shape)
我们看到,coef_
的形状是 (3, 2)
,说明 coef_
每行包含三个类别之一的系数向量,每列包含某个特征(这个数据集有 2 个特征)对应的系数值。现在 intercept_
是一维数组,保存每个类别的截距。
我们将这 3 个二类分类器给出的直线可视化
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
line = np.linspace(-15, 15)
for coef, intercept, color in zip(linear_svm.coef_, linear_svm.intercept_,
['b', 'r', 'g']):
plt.plot(line, -(line * coef[0] + intercept) / coef[1], c=color)
plt.ylim(-10, 15)
plt.xlim(-10, 8)
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
plt.legend(['Class 0', 'Class 1', 'Class 2', 'Line class 0', 'Line class 1',
'Line class 2'], loc=(1.01, 0.3))
plt.savefig('02LinearModel-10.png', bbox_inches='tight')
plt.show()
三个“一对其余”分类器学到的决策边界
你可以看到,训练集中所有属于类别 0 的点都在与类别 0 对应的直线上方,这说明它们位于这个二类分类器属于“类别 0”的那一侧。属于类别 0 的点位于与类别 2 对应的直线上方,这说明它们被类别 2 的二类分类器划为“其余”。属于类别 0 的点位于与类别 1 对应的直线左侧,这说明类别 1 的二元分类器将它们划为“其余”。因此,这一区域的所有点都会被最终分类器划为类别 0(类别 0 的分类器的分类置信方程的结果大于 0,其他两个类别对应的结果都小于 0)。
但图像中间的三角形区域属于哪一个类别呢,3 个二类分类器都将这一区域内的点划为“其余”。这里的点应该划归到哪一个类别呢?答案是分类方程结果最大的那个类别,即最接近的那条线对应的类别。
下面的例子给出了二维空间中所有区域的预测结果:
mglearn.plots.plot_2d_classification(linear_svm, X, fill=True, alpha=.7)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
line = np.linspace(-15, 15)
for coef, intercept, color in zip(linear_svm.coef_, linear_svm.intercept_,
['b', 'r', 'g']):
plt.plot(line, -(line * coef[0] + intercept) / coef[1], c=color)
plt.legend(['Class 0', 'Class 1', 'Class 2', 'Line class 0', 'Line class 1',
'Line class 2'], loc=(1.01, 0.3))
plt.xlabel("Feature 0")
plt.ylabel("Feature 1")
plt.savefig('02LinearModel-11.png', bbox_inches='tight')
plt.show()
三个“一对其余”分类器得到的多分类决策边界
7、优点、缺点和参数
线性模型的主要参数是正则化参数,在回归模型中叫作 alpha
,在 LinearSVC
和 Logistic-Regression
中叫作 C
。alpha
值较大或 C
值较小,说明模型比较简单。特别是对于回归模型而言,调节这些参数非常重要。通常在对数尺度上对 C
和 alpha
进行搜索。你还需要确定的是用 L1 正则化还是 L2 正则化。如果你假定只有几个特征是真正重要的,那么你应该用 L1 正则化,否则应默认使用 L2 正则化。如果模型的可解释性很重要的话,使用 L1 也会有帮助。由于 L1 只用到几个特征,所以更容易解释哪些特征对模型是重要的,以及这些特征的作用。
线性模型的训练速度非常快,预测速度也很快。这种模型可以推广到非常大的数据集,对稀疏数据也很有效。如果你的数据包含数十万甚至上百万个样本,你可能需要研究如何使用 LogisticRegression
和 Ridge
模型的 solver='sag'
选项,在处理大型数据时,这一选项比默认值要更快。其他选项还有 SGDClassifier
类和 SGDRegressor
类,它们对本节介绍的线性模型实现了可扩展性更强的版本。
线性模型的另一个优点在于,利用我们之间见过的用于回归和分类的公式,理解如何进行预测是相对比较容易的。不幸的是,往往并不完全清楚系数为什么是这样的。如果你的数据集中包含高度相关的特征,这一问题尤为突出。在这种情况下,可能很难对系数做出解释。
如果特征数量大于样本数量,线性模型的表现通常都很好。它也常用于非常大的数据集,只是因为训练其他模型并不可行。但在更低维的空间中,其他模型的泛化性能可能更好。
线性模型是机器学习中常用的模型之一,具有一些优点和缺点,并且有一些参数可以调节以改进模型性能。
优点:
- 简单易于理解:线性模型的基本原理简单易懂,不需要复杂的数学知识就可以理解。
- 计算效率高:线性模型的训练和预测速度通常很快,尤其是在大规模数据集上。
- 可解释性强:线性模型的参数具有直观的解释,可以帮助理解特征对目标变量的影响程度。
- 适用性广泛:线性模型可以应用于各种类型的问题,包括分类和回归问题。
缺点:
- 局限性:线性模型只能拟合线性关系,无法处理非线性关系的数据。
- 对异常值敏感:线性模型对异常值比较敏感,异常值可能会影响模型的性能。
- 高度依赖特征工程:线性模型的性能很大程度上取决于特征工程的质量,需要选择合适的特征和预处理方法。
- 容易出现欠拟合:当数据复杂度较高或特征与目标变量之间的关系不明显时,线性模型可能会出现欠拟合现象。
参数:
- 正则化参数(Regularization Parameter):用于控制模型的复杂度,防止过拟合。常见的正则化参数包括岭回归和 Lasso 回归中的 𝛼α 参数。
- 损失函数(Loss Function):用于衡量模型预测值与真实值之间的差异。常见的损失函数包括平方损失函数(用于线性回归)和对数损失函数(用于逻辑回归)。
- 优化算法(Optimization Algorithm):用于训练模型的优化算法,例如梯度下降、随机梯度下降等。
- 正则化类型(Regularization Type):用于指定正则化类型,例如岭回归(L2 正则化)和 Lasso 回归(L1 正则化)等。
调节这些参数可以影响模型的性能和泛化能力,使其更好地适应数据并提高预测准确性。
8、方法链
scikit-learn
中所有模型的 fit
方法返回的都是 self
。这允许你像下面这样编写代码
# 用一行代码初始化模型并拟合
logreg = LogisticRegression().fit(X_train, y_train)
这里我们利用 fit
的返回值(即 self
)将训练后的模型赋值给变量 logreg
。这种方法调用的拼接(先调用 __init__
,然后调用 fit
)被称为方法链 (method chaining)。 scikit-learn
中方法链的另一个常见用法是在一行代码中同时 fit
和 predict
:
logreg = LogisticRegression()
y_pred = logreg.fit(X_train, y_train).predict(X_test)
最后,你甚至可以在一行代码中完成模型初始化、拟合和预测:
y_pred = LogisticRegression().fit(X_train, y_train).predict(X_test)
不过这种非常简短的写法并不完美。一行代码中发生了很多事情,可能会使代码变得难以阅读。此外,拟合后的回归模型也没有保存在任何变量中,所以我们既不能查看它也不能用它来预测其他数据。
附录
参考文献:[德] Andreas C. Müller [美] Sarah Guido 《Python Machine Learning Basics Tutorial》