- import numpy as np
- import matplotlib.pyplot as plt
- import pandas as pd
- import mglearn
- from sklearn.model_selection import train_test_split
1. 用于回归的线性模型
对于回归问题, 线性模型预测的一般公式如下:
y =w[0]x[0]+w[1]x[1]+...+w[p]*x[p]+b
这里 x[0]到 x[p]表示单个数据点的特征(本例中特征个数为 p+1),w 和 b 是学习模型的参数, y是模型的预测结果. 对于单一特征的数据集, 公式如下:
y=w[0]*x[0]+b
这就是高中数学里的直线方程. 这里 w[0]是斜率, b 是 y 轴偏移. 对于有更多特征的数据集, w 包含沿沿每个特征坐标轴的斜率. 也可以将预测的响应值看作输入特征的加权求和, 权重由 w 的元素给出(可以取负值).
下列代码可以在一维 wave 数据集上学习参数 w[0]和 b
- mglearn.plots.plot_linear_regression_wave()
- w[0]: 0.393906 b: -0.031804
用于回归的线性模型可以表示为这样的回归模型: 对单一特征的预测结果是一条直线, 两个特征时是一个平面, 或者在更高维度 (即更多特征) 时是一个超平面
如果将直线的预测结果与
的预测结果相比较, 会发现直线的预测能力非常受限. 似乎数据的所有细节都丢失了. 从某种意义上讲, 这种说法是正确的. 假设目标 y 是特征的线性组合, 这是一个非常强(也有点不现实的) 假设. 但观察一维数据得出的观点有些片面. 对于有多个特征的数据集而言, 线性模型可以非常强大. 特别地, 如何特征数量大于训练数据点的数量, 任何目标 y 都可以在 (训练集上) 用线性函数完美拟合.
2. 线性回归(普通最小二乘法)
线性回归, 或者普通最小二乘法 (ordinary least squares,OLS), 是回归问题最简单也最经典的线性方法. 线性回归寻找参数 w 和 b, 使得对训练集的预测值与真实的目标值 y 之间的均方误差最小. 均方误差(mean squared error) 是预测值与真实值之差的平方和除以样本数. 线性回归没有参数, 这是一个优点, 但也因此无法控制模型的复杂度.
下列代码构建一个线性模型
- from sklearn.linear_model import LinearRegression
- 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_))
- lr_coef_: [0.39390555]
- lr_intercept_: -0.031804343026759746
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)))
- Training set score: 0.67
- Test set score: 0.66
R^2 约为 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)
比较一下训练集和测试集的分数可以发现, 在训练集上的预测结果非常准确, 但测试集上的 R^2 要低很多:
- print("Training set score: {:.2f}".format(lr.score(X_train, y_train)))
- print("Test set score: {:.2f}".format(lr.score(X_test, y_test)))
- Training set score: 0.95
- Test set score: 0.61
训练集和测试集之间的性能差异是过拟合的明显标志, 因此我们应该试图找到一个可以控制复杂度的模型. 标准线性回归最常用的替代方法之一就是岭回归(ridge regression)
3. 岭回归
岭回归也是一种用于回归的线性模型, 因此它的预测公式与普通最小二乘法相同. 但在岭回归中, 对系数 (w) 的选择不仅要在训练数据上得到好的预测结果, 而且还要拟合附加约束. 我们还希望系数尽量小, 换句话说, w 的所有元素都应该接近于 0. 直观上来看, 这意味着每个特征对输出的影响尽可能小 (即斜率很小), 同时仍给出很好的预测结果, 这种约束是所谓正则化(regularization) 的一个例子. 正则化是指对模型做显式约束, 以避免过拟合. 岭回归用到的这种被称为 L2 正则化
从数学的观点来看, Ridge 惩罚了系数的 L2 范数或 w 的欧式长度.
岭回归在 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)))
- Training set score: 0.89
- Test set score: 0.75
可以看出, 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)))
- Training set score: 0.79
- Test set score: 0.64
减小 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)))
- Training set score: 0.93
- Test set score: 0.77
可以查看 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()
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()
如上图所示, 无论是岭回归还是线性回归, 所有数据集大小对应的训练分数都要高于测试分数. 由于岭回归是正则化的, 因此它的训练分数要整体低于线性回归的训练分数. 但岭回归的测试分数要更高, 特别是对较小的子数据集. 如果少于 400 个数据点, 线性模型学不到任何内容. 随着模型可用的数据越来越多, 两个模型的测试集性能开始提升, 最终线性模型的性能追上了岭回归. 这里要注意的是, 如果有足够多的训练数据, 正则化变得不那么重要, 并且岭回归和线性回归具有相同的测试集性能. 从图中还可以看出线性回归的训练性能在下降. 如果添加更多数据, 模型将更加难以过拟合或记住所有的数据.
4. lasso
除了 Ridge, 还有一种正则化的线性回归是 Lasso. 与岭回归相同, 使用 Lasso 也是约束系数使其接近于 0, 但用到的方法不同, 叫作 L1 正则化. L1 正则化的结果是, 使用 Lasso 时某些系数刚好为 0. 这说明某些特征被模型完全忽略. 这可以看作是一种自动化的特征选择. 某些系数刚好为 0, 这样模型更容易解释, 也可以呈现模型最重要的特征.
Lasso 惩罚系数向量的 L1 范数, 换句话说, 系数的绝对值之和
将 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("Numbor of feayures used: {}".format(np.count_nonzero(lasso.coef_)))
- Training set score: 0.29
- Test set score: 0.21
- Numbor of feayures used: 4
可以看出, Lasso 在训练集与测试集上的表现都很差. 这表示存在欠拟合, 我们发现模型只用到了 105 个特征中的 4 个. 与 Ridge 类似, Lasso 也有一个正则化参数 alpha, 可以控制系数趋向于 0 的强度. Lasso()类中默认值 alpha=1. 为了降低欠拟合, 我们尝试减小 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("Numbor of feayures used: {}".format(np.count_nonzero(lasso001.coef_)))
- Training set score: 0.90
- Test set score: 0.77
- Numbor of feayures used: 33
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("Numbor of feayures used: {}".format(np.count_nonzero(lasso00001.coef_)))
- Training set score: 0.95
- Test set score: 0.64
- Numbor of feayures used: 94
对不同系数的模型进行作图
- 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')
- Text(0,0.5,'Coefficient magnitude')
在 alpha=1 时, Lasso 回归不仅大部分系数为 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 正则化.
- 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()
- 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 socre: {:.3f}".format(logreg.score(X_train, y_train)))
- print("Test set socre: {:.3f}".format(logreg.score(X_test, y_test)))
- Training set socre: 0.955
- Test set socre: 0.958
- logreg100 = LogisticRegression(C=100).fit(X_train, y_train)
- print("Training set socre: {:.3f}".format(logreg100.score(X_train, y_train)))
- print("Test set socre: {:.3f}".format(logreg100.score(X_test, y_test)))
- Training set socre: 0.972
- Test set socre: 0.965
- logreg001 = LogisticRegression(C=0.01).fit(X_train, y_train)
- print("Training set socre: {:.3f}".format(logreg001.score(X_train, y_train)))
- print("Test set socre: {:.3f}".format(logreg001.score(X_test, y_test)))
- Training set socre: 0.934
- Test set socre: 0.930
- 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.01")
- 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()
- for C, marker in zip([0.01, 1, 100], ['o', '^', 'v']):
- logreg_l1 = LogisticRegression(C=C, penalty="l1").fit(X_train, y_train)
- print("Training accuracy of l1 logreg with C={:.3f}: {:.2f}".format(C, logreg_l1.score(X_train, y_train)))
- print("Test accuracy of l1 logreg with C={:.3f}: {:.2f}".format(C, logreg_l1.score(X_test, y_test)))
- plt.plot(logreg_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)
- Training accuracy of l1 logreg with C=0.010: 0.92
- Test accuracy of l1 logreg with C=0.010: 0.93
- 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
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", "class2"])
linear_svm = LinearSVC().fit(X, y) print("Training set score: {:.2f}".format(linear_svm.score(X, y))) print("Coefficient shape:", linear_svm.coef_.shape) print("Intercept shape:", linear_svm.intercept_.shape) Training set score: 1.00 Coefficient shape: (3, 2) Intercept shape: (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])
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.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])
来源: https://www.cnblogs.com/wsilj/p/12831198.html