相比较线性回归所拟合的直线,多线性回归可以拟合的曲线更具有表达能力,能够有效提高模型的精度。区别如下图所示。
多项式回归在回归分析中很重要,因为任意一个函数至少在一个较小的范围内都可以用多项式任意逼近,因此在比较复杂的实际问题中,有时不问y与诸元素的确切关系如何,而用回归分析进行分析运算。
比如,对房屋成交信息建立多项式回归方程,并依据回归方程对房屋价格进行预测 。
多项式回归在机器学习算法上并没有什么新的地方,完全是使用线性回归的思路,它的关键在于:
为原来的数据样本添加合理的新的特征
这些新的特征是源于原有特征的
比如原有特征为X,则通过将 X 2 X^2 X2 作为新的特征
在PCA中是想办法将数据降维,而多项式回归则相反,是想办法将数据维度提升。在升维后,原始数据集具有了一下新的特征,可以使得线性回归算法可以更好的拟合这个高维度的数据。
其实要理解多项式回归的思路还是得通过例子来。
首先我们模拟一个数据集
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)
plt.scatter(X,y)
plt.title("original data")
plt.show()
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(X,y)
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)
y_predict = lin_reg.predict(X)
plt.scatter(x,y)
plt.plot(X,y_predict,'r')
plt.show()
显然想过是不够好的,我们采用多项式回归的思路,人为的为该样本添加一个特征
(X**2).shape # 一个新的特征
(100, 1)
X_new = np.hstack([X,X**2]) # 将原来的样本横向展开,添加新的特征X**2
X_new.shape
(100, 2)
使用拓展了特征空间的新的数据集进行线性回归
lin_reg2 = LinearRegression()
lin_reg2.fit(X_new,y)
y_predict_new = lin_reg2.predict(X_new)
plt.scatter(x,y)
plt.plot(X,y_predict_new,'r')
plt.show()
可以看到生成了一大堆直线,很乱,这是因为我们的x不是顺序递增的,可能是3,2,6,1之类的顺序
我们可以通过对x进行排序来平滑曲线
plt.scatter(x,y)
plt.plot(np.sort(x),y_predict_new[np.argsort(x)],'r')
plt.show()
查看模型训练出的系数
lin_reg2.coef_
array([1.08034107, 0.4707525 ])
与之前生成的多项式中系数基本相似
lin_reg2.intercept_
2.1819245672677283
与之前生成的多项式截距基本相似
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)
scikit-leran将数据升维的过程封装到了preprocessing
包中的PolynomialFeatures
类中
from sklearn.preprocessing import PolynomialFeatures
在其构造函数中有一个重要参数degree
degree次幂
相应的特征poly = PolynomialFeatures(degree=2)
poly.fit(X)
X2 = poly.transform(X)
X2.shape
(100, 3)
X2[:5]
array([[ 1. , 1.52415023, 2.32303391],
[ 1. , 2.60525966, 6.7873779 ],
[ 1. , -2.58263766, 6.67001727],
[ 1. , -0.09536093, 0.00909371],
[ 1. , 2.20542548, 4.86390153]])
X[:5]
array([[ 1.52415023],
[ 2.60525966],
[-2.58263766],
[-0.09536093],
[ 2.20542548]])
可以看到X2的第一列表示X的特征的0次幂,第二列表示1次幂,第三列表示2次幂(degree=2)
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(X2,y)
y_predict = lin_reg.predict(X2)
plt.scatter(x,y)
plt.plot(np.sort(x),y_predict[np.argsort(x)],'r')
plt.show()
我们尝试设置X具有两个特征
X = np.arange(1,11).reshape(-1,2)
X.shape
(5, 2)
现在我们尝试使用scikit-leran中的PolynomialFeatures类来处理X
poly = PolynomialFeatures(degree=2)
poly.fit(X)
X2 = poly.transform(X)
X2.shape
(5, 6)
本身是5行2列的数据集被升维到了5行6列,增加了4个维度,共6个维度分别是:
X
array([[ 1, 2],
[ 3, 4],
[ 5, 6],
[ 7, 8],
[ 9, 10]])
X2
array([[ 1., 1., 2., 1., 2., 4.],
[ 1., 3., 4., 9., 12., 16.],
[ 1., 5., 6., 25., 30., 36.],
[ 1., 7., 8., 49., 56., 64.],
[ 1., 9., 10., 81., 90., 100.]])
如果我们使用多项式回归,需要经过以下步骤
而pipline可以帮助我们将这三个步骤合在一起,减少重复性的代码
from sklearn.pipeline import Pipeline
在Pipeline的构造中我们需要传入一个列表,表示这个管道中需要的每一个步骤所对应的类,想象一个工厂里的流水线生产过程
from sklearn.preprocessing import StandardScaler
poly_reg = Pipeline([
("poly",PolynomialFeatures(degree=2)),
("std_scaler",StandardScaler()),
("lin_reg",LinearRegression())
])
我们通过如上代码所示的方式创建了一个poly_reg的这样一个管道,我们送给这个poly_reg对象的数据就会沿着这个管道内部的三个类(三个步骤)依次的进行下去
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)
poly_reg.fit(X,y)
y_predict = poly_reg.predict(X)
plt.scatter(x,y)
plt.plot(np.sort(x),y_predict[np.argsort(x)],'r')
plt.show()
可以看到,pipline(管道)还是非常好用的,可以很好的帮助我们快速完成一个预测或回归任务。
在这里可以使用均分误差来衡量线性回归拟合和多项式回归拟合的准确度。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
np.random.seed(666)
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)
def plot_regression(X,y,y_predict,x=x):
plt.scatter(X,y)
plt.plot(np.sort(x),y_predict[np.argsort(x)],'r')
plt.show()
使用线性回归拟合的误差
使用均分误差进行衡量
from sklearn.metrics import mean_squared_error
lin_reg = LinearRegression()
lin_reg.fit(X,y)
y_predict = lin_reg.predict(X)
plot_regression(X,y,y_predict)
mean_squared_error(y,y_predict)
3.0750025765636577
使用多项式回归拟合的误差
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import PolynomialFeatures
def PolynomialRegression(degree):
return Pipeline([
("poly",PolynomialFeatures(degree=degree)),
("std_scaler",StandardScaler()),
("lin_reg",LinearRegression())
])
poly2_reg = PolynomialRegression(degree=2)
poly2_reg.fit(X,y)
Pipeline(memory=None,
steps=[('poly',
PolynomialFeatures(degree=2, include_bias=True,
interaction_only=False, order='C')),
('std_scaler',
StandardScaler(copy=True, with_mean=True, with_std=True)),
('lin_reg',
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None,
normalize=False))],
verbose=False)
y2_predict = poly2_reg.predict(X)
plot_regression(X,y,y2_predict)
mean_squared_error(y,y2_predict)
1.0987392142417856
显然使用多项式回归产生的误差要比线性回归的要小
我们不妨试试让degree=10,看看生成的多项式回归模型的预测均分误差
poly10_reg = PolynomialRegression(degree=10)
poly10_reg.fit(X,y)
y10_predict = poly10_reg.predict(X)
plot_regression(X,y,y10_predict)
mean_squared_error(y,y10_predict)
1.0508466763764202
可以看到虽然误差没有减少多少,但是拟合出来的曲线有些歪歪曲曲了
我们继续把degree调到100,再看看误差与拟合曲线
poly100_reg = PolynomialRegression(degree=100)
poly100_reg.fit(X,y)
y100_predict = poly100_reg.predict(X)
plot_regression(X,y,y100_predict)
mean_squared_error(y,y100_predict)
0.6870911922673567
可以看到,拟合的曲线非常的"活跃"
# 使用新的样本作为测试集进行预测
X_plot = np.linspace(-3, 3, 100).reshape(100, 1)
y_plot = poly100_reg.predict(X_plot)
plt.scatter(x, y)
plt.plot(X_plot[:,0], y_plot, color='r')
plt.axis([-3, 3, 0, 10])
plt.show()
可以看到虽然使用当时样本所得到的误差变小了,但是一旦使用新的样本,就会产生极大的误差。
而且误差比之前还要大很多,甚至不如线性回归,这就是传说中的过拟合
而回到最开始通过线性回归拟合的直线也无法准确表达样本的分布,称之为欠拟合
对于这样的模型,我们称之泛化能力很弱
为了能及早地发现模型过拟合或欠拟合的问题,所以才有了测试数据集的意义
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X,y,random_state=666)
lin_reg = LinearRegression()
lin_reg.fit(X_train,y_train)
y_predict = lin_reg.predict(X_test)
mean_squared_error(y_test,y_predict)
2.2199965269396573
poly2_reg = PolynomialRegression(degree=2)
poly2_reg.fit(X_train,y_train)
y2_predict = poly2_reg.predict(X_test)
mean_squared_error(y_test,y2_predict)
0.8035641056297901
poly10_reg = PolynomialRegression(degree=10)
poly10_reg.fit(X_train,y_train)
y10_predict = poly10_reg.predict(X_test)
mean_squared_error(y_test,y10_predict)
0.9212930722150697
poly100_reg = PolynomialRegression(degree=100)
poly100_reg.fit(X_train,y_train)
y100_predict = poly100_reg.predict(X_test)
mean_squared_error(y_test,y100_predict)
14075780347.739939
学习曲线可以表征随着样本的逐渐增多,算法训练出的模型的表现能力。
根据学习曲线,我们还可以判别当前模型是否欠拟合,是否过拟合。
接下来,我们通过代码的形式来绘制学习曲线。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
np.random.seed(666)
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)
plt.scatter(X,y)
plt.show()
首先分割数据集
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X,y,random_state=10)
X_train.shape
(75, 1)
然后根据训练集和测试集样本的数量递增地训练模型,记录各个拥有不同数量训练样本的模型分别在当前训练集以及所有测试集上的表现
先考虑使用线性回归算法
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
train_score = []
test_score = []
for i in range(1,76):
lin_reg = LinearRegression()
lin_reg.fit(X_train[:i],y_train[:i]) # 取出训练集中前i个样本进行训练,i从1递增至75
# 对于不同样本数训练的模型,获得其预测结果并将模型精度记录
y_train_predict = lin_reg.predict(X_train[:i])
train_score.append(mean_squared_error(y_train[:i],y_train_predict))
y_test_predict = lin_reg.predict(X_test)
test_score.append(mean_squared_error(y_test,y_test_predict))
plt.plot([i for i in range(1,76)],np.sqrt(train_score),label="train")
plt.plot([i for i in range(1,76)],np.sqrt(test_score),label="test")
plt.legend()
plt.title("learning curve")
plt.show()
为方便对比其他算法的学习曲线,将上面的代码封装为一个函数
def plot_learning_curve(algo,X_train,X_test,y_train,y_test):
train_score=[]
test_score=[]
for i in range(1,len(X_train)+1):
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))
plt.plot([i for i in range(1,len(X_train)+1)],np.sqrt(train_score),label="train")
plt.plot([i for i in range(1,len(X_train)+1)],np.sqrt(test_score),label="test")
plt.legend()
plt.axis([0,len(X_train)+1,0,4]) # 限定显示范围
plt.title("learning curve")
plt.show()
plot_learning_curve(LinearRegression(),X_train,X_test,y_train,y_test)
绘制多项式回归的学习曲线
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
def PolynomialRegression(degree):
return Pipeline([
('poly',PolynomialFeatures(degree=degree)),
('std_scaler',StandardScaler()),
('lin_reg',LinearRegression())
])
poly2_reg = PolynomialRegression(degree=2)
plot_learning_curve(poly2_reg,X_train,X_test,y_train,y_test)
poly20_reg = PolynomialRegression(degree=20)
plot_learning_curve(poly20_reg,X_train,X_test,y_train,y_test)
可以看到,随着degree的增加,训练集和测试集的间隔越来越大,当出现如上图所示两条学习曲线间隔过大且没有要重合的趋势时,往往出现了过拟合
最后把三张图放一块做一个比较
def plot_learning_curve(algo,X_train,X_test,y_train,y_test,subplot,title):
train_score=[]
test_score=[]
for i in range(1,len(X_train)+1):
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))
subplot1,subplot2,subplot3 =subplot
plt.subplot(subplot1,subplot2,subplot3)
plt.plot([i for i in range(1,len(X_train)+1)],np.sqrt(train_score),label="train")
plt.plot([i for i in range(1,len(X_train)+1)],np.sqrt(test_score),label="test")
plt.axis([0,len(X_train)+1,0,4]) # 限定显示范围
# plt.legend()
plt.title(title)
# plt.show()
plt.figure(figsize=(20,8))
plot_learning_curve(LinearRegression(),X_train,X_test,y_train,y_test,(1,3,1),"under-fiting")
poly2_reg = PolynomialRegression(degree=2)
plot_learning_curve(poly2_reg,X_train,X_test,y_train,y_test,(1,3,2),"best-fiting")
poly20_reg = PolynomialRegression(degree=20)
plot_learning_curve(poly20_reg,X_train,X_test,y_train,y_test,(1,3,3),"over-fiting")
plt.legend()
plt.show()
可以看到