zoukankan      html  css  js  c++  java
  • 机器学习(5)——多项式回归与模型泛化

    什么是多项式回归

    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.show()
    

    1570114741603

    用线性回归试试:

    from sklearn.linear_model import LinearRegression
    lin_reg=LinearRegression()
    lin_reg.fit(X,y)
    
    y_predict=lin_reg.predict(X)
    
    plt.scatter(x,y)
    plt.plot(x,y_predict,color='r')
    plt.show()
    

    发现根本没有拟合,我们可以把x**2这一项设为另一个特征,和原来的特征合并后一起训练,这就是多项式回归:

    X2=np.hstack([X,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') #要对x排序,否则是混乱的折线图
    plt.show()
    
    
    

    看看预测的特征值和截距:

    lin_reg2.coef_
    
    lin_reg2.intercept_
    

    发现都很接近我们开始设置的。

    scikit-learn中的多项式回归和Pipeline

    scikit-learn中的多项式回归

    from sklearn.preprocessing import PolynomialFeatures
    
    poly=PolynomialFeatures(degree=2)
    poly.fit(X)
    X2=poly.transform(X)
    
    X2.shape
    
    X2[:5,:] #分别是x的0次方,x的1次方,x的二次方
    

    from sklearn.linear_model import LinearRegression
    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')
    plt.show()
    
    

    x^0的系数是0,这是正确的,因为常数是截距。

    X=np.arange(1,11).reshape(-1,2)
    poly=PolynomialFeatures(degree=2)
    poly.fit(X)
    X2=poly.transform(X)
    
    X2.shape
    X2#如果原来有两个特征a、b,那么平方后是三项:a^2,a*b,b^2
    

    那么degree=3时应该有10项。

    Pipeline

    Pipeline是scikit-learn提供的一大利器,可以顺序处理一些流程:

    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)
    
    from sklearn.pipeline import Pipeline
    from sklearn.preprocessing import StandardScaler
    poly_reg=Pipeline([
        ("poly",PolynomialFeatures(degree=2)),#最大为2次方
        ("std_scaler",StandardScaler()),#归一化
        ("lin_reg",LinearRegression()) #线性回归
    ])
    
    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)],color='r')
    plt.show()
    

    过拟合与欠拟合

    import numpy as np
    import matplotlib.pyplot as plt
    
    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)
    
    plt.scatter(x,y)
    plt.show()
    
    from sklearn.linear_model import LinearRegression
    lin_reg=LinearRegression()
    lin_reg.fit(X,y)
    lin_reg.score(X,y) #R^2值
    
    

    下面我们使用均方误差:

    直线:

    from sklearn.metrics import mean_squared_error
    
    y_predict=lin_reg.predict(X)
    mean_squared_error(y,y_predict)
    
    
    
    

    多项式:

    from sklearn.pipeline import Pipeline
    from sklearn.preprocessing import PolynomialFeatures
    from sklearn.preprocessing import StandardScaler
    
    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)
    
    y2_predict=poly2_reg.predict(X)
    mean_squared_error(y,y2_predict)
    
    

    测试一下扩展到100次方:

    poly100_reg=PolynomialRegression(degree=100)
    poly100_reg.fit(X,y)
    
    y100_predict=poly100_reg.predict(X)
    mean_squared_error(y,y100_predict)
    
    plt.scatter(x,y)
    plt.plot(np.sort(x),y100_predict[np.argsort(x)],'r')
    plt.show()
    
    

    用-3~3的等差数列来测试:

    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,-1,10])
    plt.show()
    
    

    曲线变得很复杂,这就是过拟合;而直线太过简单,是欠拟合。

    train_test_split的意义

    可以发现这条过拟合的曲线面对新数据预测是非常弱的,我们称模型的泛化能力弱。

    所以测试数据集的意义就是把数据拆分成训练数据和测试数据,如果训练得到的模型对测试数据准确率也比较高,那么泛化能力强。

    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)
    
    
    

    对于线性回归,拆分成训练和测试数据集后:

    对于多项式回归:

    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)
    
    

    传参为10的时候:

    发现刚才把整个数据当训练数据的时候误差要比传参为2的时候还小,但拆分数据集后误差是比2次多项式要大的。

    学习曲线

    import numpy as np
    import matplotlib.pyplot as plt
    
    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)
    
    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
    
    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])
        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.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.show()
    
    

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

    plot_learning_curve(LinearRegression(),X_train,X_test,y_train,y_test)
    
    

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

    from sklearn.pipeline import Pipeline
    from sklearn.preprocessing import PolynomialFeatures
    from sklearn.preprocessing import StandardScaler
    
    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)
    
    

    20次多项式回归的学习曲线:

    poly20_reg=PolynomialRegression(degree=20)
    
    plot_learning_curve(poly20_reg,X_train,X_test,y_train,y_test)
    
    

    可以发现,过拟合和欠拟合最终趋近于的高度(误差)是比最佳曲线要高的。

    验证数据集与交叉验证

    我们采用train_test_split的方法每次根据测试数据集的准确率来修改参数重新拟合,那么最后可能会对测试数据集过拟合了,可以再拆分出一块验证数据集来承担原来测试数据集的任务,而测试数据集是最终评判模型的标准:

    交叉验证:

    将训练数据拆成k个模型,K个模型的均值作为结果调参。

    import numpy as np
    from sklearn import datasets
    
    digits=datasets.load_digits()
    X=digits.data
    y=digits.target
    
    from sklearn.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.neighbors import KNeighborsClassifier
    best_score,best_p,best_k=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_score,best_p,best_k=score,p,k
            
    print("Best k=",best_k)
    print("Best p=",best_p)
    print("Best Score=",best_score)
    
    

    调用交叉验证:

    from sklearn.model_selection import cross_val_score
    knn_clf=KNeighborsClassifier()
    cross_val_score(knn_clf,X_train,y_train)
    
    

    默认是把训练数据拆成三分,如上图所示,交叉验证三次。

    #使用交叉验证寻找最优超参数
    best_score,best_p,best_k=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)
            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 k=",best_k)
    print("Best p=",best_p)
    print("Best Score=",best_score)
    
    

    best_knn_clf=KNeighborsClassifier(weights="distance",n_neighbors=2,p=2)
    
    best_knn_clf.fit(X_train,y_train)
    best_knn_clf.score(X_test,y_test)
    
    

    虽然正确率比上面用一般方法(train_test_split)低了一点,但是上面的方法可能对测试数据集过拟合了,交叉验证的更可信一些。

    回顾网格搜索:

    from sklearn.model_selection import GridSearchCV
    param_grid=[
        {
            'weights':['distance'],
            'n_neighbors':[i for i in range(2,11)],
            'p':[i for i in range(1,6)]
        }
    ]
    
    grid_search=GridSearchCV(knn_clf,param_grid,verbose=1)
    grid_search.fit(X_train,y_train)
    
    

    其中的CV就是交叉验证,返回的结果意思是把训练数据集分成三块,共有45中模型,一共要交叉验证3*45次。

    grid_search.best_score_
    grid_search.best_params_
    best_knn_clf=grid_search.best_estimator_
    best_knn_clf.score(X_test,y_test)
    
    

    可以看出和我们上面用交叉验证得到的结果一样。

    把数据集分成5份:

    cross_val_score(knn_clf,X_train,y_train,cv=5)
    
    GridSearchCV(knn_clf,param_grid,verbose=1,cv=5)
    
    

    k-folds交叉验证:把训练数据集分成k份,称为k-fold cross validation。缺点是每次训练k个模型,相当于整体性能慢了k倍。

    留一法(LOO-CV):把训练数据集分成m分,称为留一法(Leave-One-Out Cross Validation)。完全不受随机的影响,最接近模型真正的性能指标。缺点是计算量巨大。

    偏差方差平衡

    偏差(bias)、方差(variance)

    模型误差=偏差+方差+不可避免的误差

    导致偏差的主要原因:对问题本身的假设不正确,如非线性数据使用线性回归,欠拟合

    方差:数据的一点点扰动都会较大影响模型,通常原因是使用的模型太复杂,如高阶多项式回归,过拟合。

    有一些算法天生是高方差的算法,如KNN。

    非参数学习通常都是高方差,因为不对数据进行任何假设。

    有一些算法天生是高偏差算法,如线性回归。

    参数学习通常都是高偏差算法,因为对数据具有极强的假设。

    大多数算法具有相应的参数,可以调整偏差和方差。如KNN的K,线性回归中使用多项式回归。

    偏差和方差通常是矛盾的,降低偏差会提高方差,降低方差会提高偏差。

    机器学习的主要挑战,来自于方差!

    解决高方差的通常手段:

    1. 降低模型复杂度
    2. 减少数据维度、降噪
    3. 增加样本数
    4. 使用验证集
    5. 模型正则化

    模型泛化与岭回归

    模型正则化(Regularization):限制参数的大小

    过拟合的曲线非常陡峭,这是因为求得的参数大小规模相差较大

    使用线性数据加噪音来测试一下:

    import numpy as np
    import matplotlib.pyplot as plt
    
    np.random.seed(42)
    x=np.random.uniform(-3.0,3.0,size=100)
    X=x.reshape(-1,1)
    y=0.5* x + 3 +np.random.normal(0,1,size=100)
    
    plt.scatter(x,y)
    plt.show()
    
    

    多项式回归:

    from sklearn.pipeline import Pipeline
    from sklearn.preprocessing import PolynomialFeatures
    from sklearn.preprocessing import StandardScaler
    from sklearn.linear_model import LinearRegression
    
    def PolynomialRegression(degree):
        return Pipeline([
            ("poly",PolynomialFeatures(degree=degree)),
            ("std_scaler",StandardScaler()),
            ("lin_reg",LinearRegression())
        ])
    
    from sklearn.model_selection import train_test_split
    np.random.seed(666)
    X_train,X_test,y_train,y_test=train_test_split(X,y)
    
    from sklearn.metrics import mean_squared_error
    poly_reg=PolynomialRegression(degree=20)
    poly_reg.fit(X_train,y_train)
    y_poly_predict=poly_reg.predict(X_test)
    mean_squared_error(y_test,y_poly_predict)
        
    
    

    可见MSE是非常大的。

    把模型的拟合曲线绘制封装成函数:

    def plot_model(model):
        X_plot=np.linspace(-3,3,100).reshape(100,1)
        y_plot=model.predict(X_plot)
        plt.scatter(x,y)
        plt.plot(X_plot[:,0],y_plot,color="r")
        plt.axis([-3,3,0,6])
        plt.show()
        
    plot_model(poly_reg)
    
    

    很明显,20阶的多项式对训练数据过拟合了。

    我们用岭回归来一发:

    from sklearn.linear_model import Ridge
    def RidgeRegression(degree,alpha):#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)
    
    

    可见MSE降了非常多!!

    plot_model(ridge1_reg)
    
    

    如果alpha=1:

    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)
    
    
    plot_model(ridge2_reg)
    
    

    alpha=100:

    ridge3_reg=RidgeRegression(20,100)
    ridge3_reg.fit(X_train,y_train)
    y3_predict=ridge3_reg.predict(X_test)
    mean_squared_error(y_test,y3_predict)
    
    plot_model(ridge3_reg)
    
    

    可以发现,随着alpha越来越大,损失函数中系数占比越来越大,那么重心就会转到降低每个系数的大小,最后系数趋近于0,就是直线了。

    LASSO回归

    继续使用上面岭回归的数据:

    from sklearn.linear_model import Lasso
    
    def LassoRegression(degree,alpha):
        return Pipeline([
            ("poly",PolynomialFeatures(degree=degree)),
            ("std_scaler",StandardScaler()),
            ("lasso_reg",Lasso(alpha=alpha))
        ])
        
    lasso1_reg=LassoRegression(20,0.01)
    lasso1_reg.fit(X_train,y_train)
    
    y1_predict=lasso1_reg.predict(X_test)
    mean_squared_error(y_test,y1_predict)
    
    plot_model(lasso1_reg)
    
    

    传入alpha=0.1:

    lasso2_reg=LassoRegression(20,0.1)
    lasso2_reg.fit(X_train,y_train)
    
    y2_predict=lasso2_reg.predict(X_test)
    mean_squared_error(y_test,y2_predict)
    
    plot_model(lasso2_reg)
    
    

    可见已经近似直线了,而岭回归是曲线。

    比较Ridge和LASSO:

    L1、L2正则项和弹性网络

    L0正则就是让theta的数量尽量少。

    弹性网综合了岭回归(计算相对是精准的,但特征比较多的时候因为不会特征选择所以计算量比较大)和lasso回归(特征选择,因为急于把特征化成0,所以可能会产生错误)的优点。

  • 相关阅读:
    软件测试初探
    weiPHPOneThink1.0开发手册
    关于.aspx与.aspx.cs的关系
    佩服的牛人
    当客户说“没钱”,我该怎么应对?
    weiphp 简介笔记
    FlashFXP、LeapFTP、CuteFTP 等FTP软件二进制上传或下载方法
    集体智慧编程(一)
    Sargur Srihari 的两个课程 ML和DM
    一个神人Hoifung Poon
  • 原文地址:https://www.cnblogs.com/mcq1999/p/11654372.html
Copyright © 2011-2022 走看看