非线性曲线如图:python
假设曲线表达式为:$y=ax^2+bx+c$,若是将 $x^2$ 看做为 $x_1$,即 $y_1=ax_1+bx+c$,此时就有了两个特征,则能够看做是线性曲线表达式。git
首先生成一组样本数据:github
import numpy as np import matplotlib.pyplot as plt x = np.random.uniform(-3, 3, size=100) X = x.reshape(-1, 1) y = 0.5 * x**2 + x + 2 + np.random.normal(0, 1, size=100)
(x, y) 如图所示(横轴为 x,纵轴为 y):算法
接着在 $X$ 的基础上增长一个新的特征 $x1(x^2)$ 造成一个新的 $X2$:编程
X2 = np.hstack([X, X**2])
再使用线性回归算法:数组
from sklearn.linear_model import LinearRegression lin_reg = LinearRegression() lin_reg.fit(X2, y) y_predict = lin_reg.predict(X2)
此时对 $X2$ 的预测值反映到图中就是第一张图里的曲线。app
对于增长新的特征(如:$x^2$),Scikit Learn 提供了 PolynomialFeatures,使用方式以下:dom
from sklearn.preprocessing import PolynomialFeatures poly = PolynomialFeatures(degree=2) poly.fit(X) X2 = poly.transform(X)
参数 degree
表示最高次幂;获得的新的 $X2$ 前5行数据以下:机器学习
# X2[:5,:] array([[ 1. , 1.16207716, 1.35042333], [ 1. , -2.62969804, 6.91531181], [ 1. , 0.99966958, 0.99933928], [ 1. , 0.35525362, 0.12620514], [ 1. , -2.48933626, 6.19679503]])
第一列为 $x^0$,第二列为 $x$,第三列为 $x^2$。函数
Scikit Learn 还提供了 Pipeline,将多项式特征、数据归一化和线性回归组合在了一块儿,大大方便的编程的过程。使用方式以下:
from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LinearRegression poly_regression = Pipeline([ ("poly", PolynomialFeatures(degree=2)), ("std_scaler", StandardScaler()), ("lin_reg", LinearRegression()) ])
Pipeline()
传入的是一个列表,包含了执行每个步骤的实例,每个步骤又是一个元组类型,其中第二个表示实例,第一个表示给实例取的名称。接着就能够进行 fit()
、predict()
等内容了:
poly_regression.fit(X, y) y_predict = poly_regression.predict(X)
在使用多项式回归的过程当中须要考虑一个问题,即欠拟合和过拟合。
若是对前面的样本单单使用线性回归,获得的模型如图(这里省略的代码实现):
训练出来的模型很简单,但它并不能完整的表述数据之间的关系,这就是欠拟合(underfitting)。
若是使用多项式回归,代码以下:
def PolynomialRegression(degree): return Pipeline([ ("poly", PolynomialFeatures(degree=degree)), ("std_scaler", StandardScaler()), ("lin_reg", LinearRegression()) ]) poly100_reg = PolynomialRegression(degree=100) poly100_reg.fit(X, y) y_predict100 = poly100_reg.predict(X)
将 degree
设置为100,即最高次幂为 100,训练出来的模型对训练数据 X 的预测结果如图:
可见该模型对训练数据解释的很好,可是若是用测试数据来预测一下:
X_plot = np.linspace(-3, 3, 100).reshape(-1, 1) y_plot = poly100_reg.predict(X_plot)
能够看出对预测数据预测的很是糟糕,这是由于训练的模型过多的表达了训练数据中的噪音,从而形成了对预测数据的结果也含有了寻多噪音,这就是过拟合(overfitting)。
上面的过拟合出来的模型对训练数据解释的很好,但对测试数据(新的数据)解释的很是差,也就是模型的泛化能力差。
因此为了防止模型过拟合,一般将数据分为训练数据和测试数据,经过测试数据来检验是否过拟合。通常模型准确率与训练数据和测试数据的关系为:
图形中左边属于欠拟合,右边属于过拟合,而中间对于测试数据模型准确率高的地方就是模型泛化能力好的地方。
除了模型复杂度曲线,还可使用学习曲线来可视化欠拟合和过拟合。
所谓学习曲线,就是随着训练样本的逐渐增多,训练出的模型的能力的变化。
首先将数据分为训练数据和测试数据:
from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y)
learning_curve()
函数中使用均方偏差来表示模型的优劣,而且训练数据(75个)从1个慢慢增大为75个,记录对训练数据和测试数据预测值的均方偏差,用于画学习曲线。
from sklearn.metrics import mean_squared_error # 均方偏差 def learning_curve(algo, X_train, X_test, y_train, y_test): train_score = [] test_score = [] for i in range(1, 76): algo.fit(X_train[:i], y_train[:i]) y_train_predict = algo.predict(X_train[:i]) train_score.append(mean_squared_error(y_train[:i], y_train_predict)) y_test_predict = algo.predict(X_test) test_score.append(mean_squared_error(y_test, y_test_predict))
先来看看使用线性回归的状况(根据前文已经知道是欠拟合):
learning_curve(LinearRegression(), X_train, X_test, y_train, y_test)
做出的学习曲线如图:
能够看出均方偏差最后趋于稳定。
接着来看看使用多项式回归而且设定最高次幂为2的状况:
poly2_reg = PolynomialRegression(degree=2) learning_curve(poly2_reg, X_train, X_test, y_train, y_test)
做出的学习曲线如图:
能够看出均方偏差最后也趋于稳定,可是比过拟合状况下均方偏差的值要小,表示模型更好。
接着来看看使用多项式回归而且设定最高次幂为20的状况:
poly20_reg = PolynomialRegression(degree=20) learning_curve(poly20_reg, X_train, X_test, y_train, y_test)
做出的学习曲线如图:
能够看出对训练数据的军方偏差最后会趋于稳定,但对测试数据则否则,这种状况就是过拟合的表现。
虽然将数据集划分为训练数据集和测试数据集可以为判断模型是否过拟合提供参考,但这样的划分方式并不严谨,由于模型多是针对测试数据集过拟合的。
更好的方式是将数据集划分为训练数据集、验证数据集和测试数据集。验证数据集用于验证模型的效果,方便调整超参数来改善模型;测试数据集用于衡量最终的模型性能。
可是这种方式也有可能对验证数据集过拟合,此时可使用交叉验证(Cross Validation)。
交叉验证即将数据集划分为训练数据集和测试数据集,并将训练数据集分红 k 份,每次将其中一份做为验证数据集,剩下的(k-1)份做为训练数据集,如此能够获得 k 个模型,再将这 k 个模型的均值做为结果调参。以 kNN 手写数字识别算法举例说明:
首先准备数据:
from sklearn import datasets from sklearn.model_selection import train_test_split digits = datasets.load_digits() X = digits.data y = digits.target X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4)
Scikit Learn 中提供了用于交叉验证的 cross_val_score
,模型将数据分红三份用于交叉验证,返回三个模型的估算分数:
from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import cross_val_score knn_clf = KNeighborsClassifier() cross_val_score(knn_clf, X_train, y_train) # 返回 array([0.9640884 , 0.97506925, 0.96901408])
接着进行调参:
best_score, best_p, best_k = 0, 0, 0 for k in range(2, 10): for p in range(1, 5): knn_clf = KNeighborsClassifier(weights="distance", n_neighbors=k, p=p) scores = cross_val_score(knn_clf, X_train, y_train) score = np.mean(scores) if score > best_score: best_score, best_p, best_k = score, p, k print("best score is:", best_score) print("best p is:", best_p) print("best k is:", best_k) # best score is: 0.9795997710242826 # best p is: 3 # best k is: 2
循环中每次比较 cross_val_score
返回数组的均值,最大的对应的 k 和 p 就是最优的超参数。
拿到了想要的超参数就能够进行模型训练了:
best_knn_clf = KNeighborsClassifier(weights="distance", n_neighbors=2, p=3) best_knn_clf.fit(X_train, y_train) best_knn_clf.score(X_test, y_test) # 结果0.9902642559109874
Scikit Learn 提供了网格搜索(GridSearchCV)整合了上面交叉验证和调参的全过程:
from sklearn.model_selection import GridSearchCV param_grid = [ { 'weights': ['distance'], 'n_neighbors': [i for i in range(1, 11)], 'p': [i for i in range(1, 6)] } ] knn_clf = KNeighborsClassifier() grid_search = GridSearchCV(knn_clf, param_grid=param_grid, verbose=1) grid_search.fit(X_train, y_train)
grid_search.best_params_
返回获得的最优超参数;best_knn_clf = grid_search.best_estimator_
返回最优的模型。
在cross_val_score
和GridSearchCV
中能够指定参数cv
来设置将训练数据集分为几份(默认为3)。
对于一个模型而言,模型偏差=误差(Bias)+方差(Variance)+不可避免的偏差。误差和方差表示如图
致使误差的主要缘由是对问题自己的假设不正确,致使方差的主要缘由是使用的模型太复杂。对于欠拟合,就属于高误差;而过拟合,就属于高方差。
在机器学习算法中,主要的挑战来自方差,解决的方法主要有:
接下来主要看看模型正则化。
对于高方差的模型能够用模型正则化(Regularization)处理,限制参数的大小。
以使用梯度降低法的线性回归为例,其目标函数为:使 $J(\theta) = MSE(y, \hat{y};\theta)$ 尽量小;若是模型过拟合,获得的 $\theta$ 就可能很是大,所以须要对 $J(\theta)$ 加入限制是的 $\theta$ 尽量小。
有两种主要方式:岭回归和 LASSO 回归。
岭回归(Ridge Regression)就是在目标函数中加入了 $\alpha\frac{1}{2}\sum_{i=1}^n\theta_i^2$,即便 $J(\theta) = MSE(y, \hat{y};\theta)+\alpha\frac{1}{2}\sum_{i=1}^n\theta_i^2$ 尽量小。
Scikit Learn 中提供了 Ridge
类表示岭回归,参数为 $\alpha$。使用过程以下:
from sklearn.linear_model import Ridge x = np.random.uniform(-3, 3, size=100) X = x.reshape(-1, 1) y = 0.5 * x + 3 + np.random.normal(0, 1, size=100) def PolynomialRegression(degree, alpha): return Pipeline([ ("poly", PolynomialFeatures(degree=degree)), ("std_scaler", StandardScaler()), ("ridge_reg", Ridge(alpha=alpha)) ])
设置 degree
为20,alpha
为0.0001 训练模型:
ridge_reg = PolynomialRegression(20, 0.0001) ridge_reg.fit(X_train, y_train) y_predict = ridge_reg.predict(X_test)
画出来的图形为:
设置 degree
为20,alpha
为1 训练模型:
ridge_reg = PolynomialRegression(20, 1) ridge_reg.fit(X_train, y_train) y_predict = ridge_reg.predict(X_test)
画出来的图形为:
相比之下模型好上了很多。
LASSO 回归(Least Absolute Shrinkage and Selection Operator Regression)与岭回归不一样的是使用 $\alpha\sum_{i=1}^n|\theta_i|$ 对 $\theta$ 进行限制,即便 $J(\theta) = MSE(y, \hat{y};\theta)+\alpha\sum_{i=1}^n|\theta_i|$ 尽量小。
Scikit Learn 中提供了 Lasso
类表示岭回归,参数为 $\alpha$。使用过程以下:
from sklearn.linear_model import Lasso def LassoRegression(degree, alpha): return Pipeline([ ("poly", PolynomialFeatures(degree=degree)), ("std_scaler", StandardScaler()), ("lasso_reg", Lasso(alpha=alpha)) ])
设置 degree
为20,alpha
为0.01 训练模型:
lasso_reg = PolynomialRegression(20, 0.01) lasso_reg.fit(X_train, y_train) y_predict = lasso_reg.predict(X_test)
画出来的图形为:
设置 degree
为20,alpha
为0.1 训练模型:
lasso_reg = PolynomialRegression(20, 0.1) lasso_reg.fit(X_train, y_train) y_predict = lasso_reg.predict(X_test)
画出来的图形为:
相比之下模型也好上了很多。
LASSO 回归相比岭回归趋向于使得一部分 $\theta$ 等于0,所以画出来的曲线也相对更直一些。
弹性网(Elastic Net)则同时使用了岭回归和LASSO 回归,即便用了 $\alpha\frac{1-r}{2}\sum_{i=1}^n\theta_i^2 +r \alpha\sum_{i=1}^n|\theta_i|$ 对 $\theta$ 进行限制。
使用上面的例子就是使 $J(\theta) = MSE(y, \hat{y};\theta)+\alpha\frac{1-r}{2}\sum_{i=1}^n\theta_i^2 +r \alpha\sum_{i=1}^n|\theta_i|$ 尽量小。