多项式回归是线性回归法的改进,针对一些不是线性关系的数据
进而引出模型泛化--机器学习中非常重要的概念
一,什么是多项式回归
程序实现:
import numpy as np
importmatplotlib.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, 100)

使用线性回归拟合:
fromsklearn.linear_model import LinearRegression
lin_reg =LinearRegression()
lin_reg.fit(X, y)
y_predict =lin_reg.predict(X)

解决方案,添加一个特征
X2 =np.hstack([X, X**2]) #X^2是新构建的特征
lin_reg2 =LinearRegression()
lin_reg2.fit(X2, y)
y_predict2 =lin_reg2.predict(X2)
plt.scatter(x, y)
plt.plot(np.sort(x),y_predict2[np.argsort(x)], color='r')
#注意这里生成的y_predict是无序的。因此要对这俩个参数进行排列



多项式回归没有用到新模型,而是是继续使用线性回归的思路,不同的是多项式回归在线性回归的基础上增加了特征,并对这些新特征进行线性组合。
二,scikit-learn中的多项式回归与Pipline
数据同上
fromsklearn.preprocessing import PolynomialFeatures
poly =PolynomialFeatures(degree=2) #最多添加几次幂
poly.fit(X)
X2 =poly.transform(X) #针对现有的X进行变形,很多算法都有

从这里也可以看出数据是无序的,并且第一列对应的是零次幂,第三列数据是第二列数据的平方
fromsklearn.linear_model import LinearRegression
lin_reg2 =LinearRegression()
lin_reg2.fit(X2, y)
y_predict2 =lin_reg2.predict(X2)
利用线性回归进行预测
fromsklearn.linear_model import LinearRegression
lin_reg2 =LinearRegression()
lin_reg2.fit(X2, y)
y_predict2 =lin_reg2.predict(X2)


关于PolynomialFeatures类
X =np.arange(1, 11).reshape(-1, 2) #相当于有俩个特征,五行俩列

poly =PolynomialFeatures(degree=2)
poly.fit(X)
X2 = poly.transform(X)

观察X2和X的关系,分别生成了原来X的X(1),X(2)和X(1)*X(2),以及平方
也就是生成了不同特征对应的各种排列组合的项

pipline用法
进行线性回归过程中进行的三步:多项式的特征,数据的归一化和线性回归。pipline可以将这三步合在一起。
数据生成:
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, 100)
使用pipline:
from sklearn.pipelineimport Pipeline
fromsklearn.preprocessing import StandardScaler
poly_reg = Pipeline([
("poly",PolynomialFeatures(degree=2)), #引号里是起的名称
("std_scaler", StandardScaler()), #数据的归一化
("lin_reg", LinearRegression()) #线性回归
])
创建了这个管道,就可以使数据沿着这个管道进行处理
poly_reg.fit(X, y)
y_predict =poly_reg.predict(X)

三,过拟合与欠拟合
np.random.seed(666)
x =np.random.uniform(-3.0, 3.0, size=100)
X = x.reshape(-1, 1)
y = 0.5 * x**2 + x + 2+ np.random.normal(0, 1, size=100)
使用线性回归:
fromsklearn.linear_model import LinearRegression
lin_reg =LinearRegression()
lin_reg.fit(X, y)
lin_reg.score(X, y)

y_predict =lin_reg.predict(X)
plt.scatter(x, y)
plt.plot(np.sort(x),y_predict[np.argsort(x)], color='r')
plt.show()

拟合效果很差
对于线性回归,可以使用R^2来判定线性关系,但是对于多项式回归要使用其他的判断标准,为了统一,使用均方误差计算效果。
from sklearn.metricsimport mean_squared_error
y_predict =lin_reg.predict(X)
mean_squared_error(y,y_predict)
使用多项式回归:
同上一节,建立一个pipline
from sklearn.pipelineimport Pipeline
fromsklearn.preprocessing import PolynomialFeatures
fromsklearn.preprocessing import StandardScaler
defPolynomialRegression(degree):
return Pipeline([
("poly",PolynomialFeatures(degree=degree)),
("std_scaler",StandardScaler()),
("lin_reg",LinearRegression())
])
poly2_reg =PolynomialRegression(degree=2)
poly2_reg.fit(X, y)
y2_predict =poly2_reg.predict(X)
mean_squared_error(y,y2_predict)

degree=2拟合结果:

如果degree=10:
poly10_reg =PolynomialRegression(degree=10)
poly10_reg.fit(X, y)
y10_predict =poly10_reg.predict(X)
mean_squared_error(y,y10_predict)

拟合结果:

如果degree=100:
poly100_reg =PolynomialRegression(degree=100)
poly100_reg.fit(X, y)
y100_predict =poly100_reg.predict(X)
mean_squared_error(y,y100_predict)

拟合结果:

再看拟合的结果:
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()

degree越高,均方误差越低,但是不能更好的反应数据样本的变化曲线,也就是多项式拟合的过程中将数据的函数曲线便复杂了。这就是过拟合。
而对于把函数曲线弄的太简单的情况就是欠拟合。
train test split的意义
模型的泛化能力:新加入数据后,看看拟合的曲线是否满足新的样本点
使用训练数据集与测试数据集来测试预测模型的泛化能力
fromsklearn.model_selection import train_test_split
X_train, X_test,y_train, y_test = train_test_split(X, y, random_state=666)
分别使用不同degree的多项式回归来拟合训练数据集,并使用测试数据集进行测试分数:


四,学习曲线
随着训练样本的逐渐增多,算法训练出的模型的表现能力
生成的数据同上
fromsklearn.model_selection import train_test_split
X_train, X_test,y_train, y_test = train_test_split(X, y, random_state=10)
fromsklearn.linear_model import LinearRegression
from sklearn.metricsimport mean_squared_error
train_score = []
test_score = []
for i in range(1, 76):#从一个数据到76个数据
lin_reg = LinearRegression()
lin_reg.fit(X_train[:i], y_train[:i])
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 inrange(1, 76)], np.sqrt(train_score), label="train")
plt.plot([i for i inrange(1, 76)], np.sqrt(test_score), label="test")
plt.legend()
plt.show()

封装成一个函数:

线性回归学习曲线:(欠拟合)

利用pipline训练模型:
二阶多项式:

二阶多项式回归学习曲线:(最佳)

拟合结果比较好
degree=20:(过拟合)
poly20_reg =PolynomialRegression(degree=20)
plot_learning_curve(poly20_reg,X_train, X_test, y_train, y_test)

泛化能力很差,过拟合
五,验证数据集与交叉验证
验证数据集:
测试数据集的意义:发生过拟合却不自知
问题:针对特定测试数据集过拟合

训练数据集获得模型;验证数据集用于调整超参数,仿真过拟合;最终的测试数据集作为衡量最终模型性能的数据集。
交叉验证:

交叉验证程序实现:
导入数据集:
import numpy as np
from sklearn importdatasets
digits =datasets.load_digits()
X = digits.data
y = digits.target
测试train_test_split:
fromsklearn.model_selection import train_test_split
X_train, X_test,y_train, y_test = train_test_split(X, y, test_size=0.4, random_state=666)
from sklearn.neighborsimport KNeighborsClassifier
best_k, best_p,best_score = 0, 0, 0
for k in range(2, 11):
for p in range(1, 6):
knn_clf =KNeighborsClassifier(weights="distance", n_neighbors=k, p=p)
knn_clf.fit(X_train, y_train)
score = knn_clf.score(X_test, y_test)
if score > best_score:
best_k, best_p, best_score = k, p,score
print("Best K=", best_k)
print("Best P=", best_p)
print("Best Score=", best_score)

使用交叉验证:
fromsklearn.model_selection import cross_val_score
knn_clf =KNeighborsClassifier()
cross_val_score(knn_clf,X_train, y_train)

默认分成了五份进行交叉验证

得到和上边不同的参数

交叉验证的准确度低于普通验证方式,是因为交叉验证并没有过拟合
并且交叉验证的目的是为了得到最好的超参数,而不是得到最好的准确率。要得到最好的准确率就需要重新利用得到的超参数进行fit,再计算准确率。

回顾网格搜索:
用到了交叉验证的方式GridSearchCV

同样默认将训练集分成5份,每份计算9*5=45种参数的组合,因此一共要进行5*45=225次训练

best_estimator包就是找到最佳参数对应的准确率
CV参数:

总结:
把训练数据集分成k份,称为k-fola crossvalidation,缺点就是速度慢了k倍
极端的情况是把训练数据集分成最多份,只留一个数据进行测试,称为留一法,leave-one-out cross validation,简称LOO-CV。这样就完全不受随机分配的影响,最接近模型真正的性能指标。计算量巨大
六,偏差方差权衡BiasVariance Trade off

模型误差=偏差+方差+不可避免的误差(数据本身的噪音)
导致偏差的主要原因:
对问题本身的假设不正确,比如非线性数据使用线性回归(欠拟合underfitting)
方差:数据的一点点扰动都会较大的影响模型。通常原因是使用的模型太复杂。比如使用高阶多项式(过拟合overfitting)
有些算法天生是高方差的算法(KNN,对数据敏感,有一个数据错误,整体的数据预测就不准确)
非参数学习通常是高方差算法,因为不对数据进行任何假设,只能根据现有的数据进行预测(例子还是KNN,数据本身就是算法,高度依赖数据,还有决策树也是)
有些算法天生是高偏差算法(比如线性回归,因为现实中的很多问题并不是线性关系)
参数学习通常都是高偏差算法,因为对数据有极强的假设
大多数算法具有相应的参数,可以调整偏差和方差(比如KNN中的k,还有线性回归中的多项式回归)
偏差和方差通常是矛盾的,降低偏差会提高方差,反之同理。
机器学习的主要挑战来自方差!(只局限于算法的层面)
降低方差的常见手段有:
降低模型复杂度
减少数据的维数;降噪
增加样本数
使用验证集
模型的正则化
七,模型泛化与岭回归
模型正则化Regularization
限制参数的大小

加入模型正则化后,可以使系数不太大,防止拟合的曲线太过于陡峭(注意没有加入theta0)
1/2也可以不加,α是个超参数,表示正则化因素的影响程度
以上的正则化方式叫做岭回归(Ridge Gegression)
岭回归程序实现:
生成参数

训练拟合


定义一个绘图函数,方便调用:

使用岭回归:
fromsklearn.linear_model import Ridge
defRidgeRegression(degree, alpha):
return Pipeline([
("poly",PolynomialFeatures(degree=degree)),
("std_scaler",StandardScaler()),
("ridge_reg",Ridge(alpha=alpha))
])
ridge1_reg =RidgeRegression(20, 0.0001)
ridge1_reg.fit(X_train,y_train)
y1_predict =ridge1_reg.predict(X_test)
mean_squared_error(y_test,y1_predict)

plot_model(ridge1_reg)

线条缓和了许多
ridge2_reg =RidgeRegression(20, 1)
ridge2_reg.fit(X_train,y_train)
y2_predict =ridge2_reg.predict(X_test)
mean_squared_error(y_test,y2_predict)

结果进一步优化
当α=100

正则化过头了

α=10000000


几乎是一条直线(所有θ=0)
八,LASSO回归
LeastAbsolute Shrinkage and SelectionOperator Regression

程序实现:


lasso2_reg =LassoRegression(20, 0.1)

lasso3_reg =LassoRegression(20, 1)

比较Ridge和LASSO
Ridge得到的拟合曲线相比LASSO更倾向于是弯曲的,因为正则化形式是二次方的性质
LASSO趋向于使一部分的theta变为0,所以可作为特征选择使用,即把一些特征的系数变为0,表示这个特征没有用


从准确性来看Ridge更好,但如果特征太大太多就可以用LASSO回归
九,L1,L2和弹性网络

回顾明可夫斯基距离
Lp范数:
p=1曼哈顿距离,p=2欧拉距离
岭回归的正则项也被称为L2正则项
LASSO正则项也叫L1正则项
L0正则项是让theta的数量尽可能的少,进而使曲线不要太陡,但一般不使用,实际用L1取代
弹性网ElasticNet

引入俩个正则项。一般来说计算力强,数据特征不多,纯使用岭回归更好
不同的数据样本有不同的特征,要具体问题具体分析