吴恩达机器学习第五次编程作业-正则线性回归和偏差/方差

课时82编程作业:正则线性回归和偏差/方差

工具:Pycharm2019.1,Python3.5

参考资料:

  1. 吴恩达机器学习第五次作业
  2. 正则化
  3. 应用机器学习建议
  4. 机器学习作业Python实现(五)
  5. 吴恩达机器学习第六周编程作业
  6. scikit-learn的四种特征缩放方式
  7. Python 进行特征缩放的方法
  8. 特征缩放
  9. numpy计算方差/标准方差/样本标准方差/协方差

正则化线性回归完整代码
Polynomial regression完整代码


Regularized Linear Regression

目的:根据水库水位预测水流量

Visualizing the dataset

tips: 数据是.mat存储的,所以使用loadmat取出后如何查看里面的数据构造呢?要知道这里的数据都是以字典dict存储的,所以查看键即可,而对应值是我们需要的ndarray数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import numpy as np
import scipy.io as sio
import scipy.optimize as opt
import matplotlib.pyplot as plt

def Get_data():
data=sio.loadmat('./ex5/ex5data1.mat')
   # for key in data: # 查看键
   #     print(key)
return data['X'],data['y'],data['y'].size

def Plot_data(X,y):
plt.plot(X,y,'rx')
plt.xlim(-50,40)
plt.ylim(0,40)
plt.xlabel('Change in water level (x)',fontsize=8)
plt.ylabel('Water flowing out of the dam (y)', fontsize=8)
plt.title('Figure 1: Data',fontsize=12)
plt.show()

X,y,m=Get_data()
Plot_data(X,y)

Figure_12.png

Regularized linear regression cost function

2019-07-30 10-46-34 的屏幕截图.png
2019-07-30 10-46-40 的屏幕截图.png

注意不要惩罚θ0
加入惩罚项是为了防止过拟合现象。

代价函数与梯度下降

1
2
3
4
5
6
7
8
9
def Cost_func(X,y,theta,m): # 原来的线形回归代价函数
   hypothesis=X.dot(theta)
return 1/(2*m)*(((hypothesis-y)**2).sum())

def Cost_reg(X,y,theta,lamda,m): # 加入正则化项的线形回归代价函数
   _theta=theta[1:] # 不惩罚theta0
   return Cost_func(X,y,theta,m)+lamda/(2*m)*((_theta**2).sum())

# except output : 303.993

1
2
3
4
5
6
7
8
9
10
def Gradient_Linear(X,y,theta,m): # 原来的梯度
   hypothesis=X.dot(theta)
return 1/m*np.dot(X.T,hypothesis-y)

def Gradient_reg(X,y,theta,lamda,m): # 加入惩罚项的梯度
   _theta=theta.copy() # 复制创建新的对象,改变数据不影响原数据
_theta[0]=0 # 不惩罚theta0
   return Gradient_Linear(X,y,theta,m)+lamda/m*_theta

# except output: [ [-15.30301567] [598.25074417] ]
Fitting linear regression

这里使用的是scipy.optimize.minimize()方法,但效果并不好。
终于知道如何把梯度作为参数了,使用jac=partial_derivative即可
传入参数要和函数参数一一对应

1
2
3
4
5
6
7
8
9
def Train_Linear_Reg(X,y,init_theta,lamda):
   result=opt.minimize(fun=Cost_reg,x0=init_theta,args=(X,y,lamda,m),method='TNC',jac=Gradient_reg) # x0是以一维传入的
   return result.x

def Fitting_Linear_reg(X,y,theta):
plt=Plot_data(X.T[1],y)
plt.plot(X.T[1],X.dot(theta))
plt.ylim(-5, 40)
plt.show()

Figure_12.png
完整代码见顶部。

总结:

  1. 要特别注意进行运算的数组或矩阵之间的关系,一维和二维运算等,这里传入的x0=init_theta就是(2,)一维的,特别容易出错,故把y以y.ravel()传入
  2. 高级优化方法参数列表应该和函数参数列表对应
  3. 训练的目的得出参数,最终用参数和样本输入进行运算作为输出再拟合
  4. 向量与矩阵进行运算分几种情况:如果想实现矩阵乘法,则向量作为行还是作为列进行运算取决于与它进行运算的那一维的情况;如果是普通加减乘除,则会各自用相同的向量扩展成相同的维数,再对应元素进行运算。

Bias-variance

机器学习重要概念:权衡偏差-方差。高偏差会导致拟合不足,高方差会导致过拟合。

学习曲线

学习曲线是偏差和方差关于训练样本数m的变化情况
大致步骤:

  1. 理论:什么是学习曲线
  2. 什么是训练误差、交叉验证误差与测试误差,实际就是不同模型的代价函数
  3. 用训练样本得出参数
  4. 用参数和交叉验证数据集算出交叉验证误差
  5. 作图

这里是验证误差关于训练样本数m的变化情况,故依次增大训练样本:X[:i,] , y[:i],用高级优化方法得出参数。
再用参数与交叉验证数据集进行运算得出第i次即训练样本数为i的交叉验证误差。
得出训练误差和交叉验证误差向量作图。
这里下标可能稍微不对称,Pycharm运行结果如此

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def Learning_Curve(x, y, xval, yval,m, lamda):
train_error=np.zeros(m)
val_error=np.zeros(m)
for i in range(1,m+1):
       train_X=x[:i,] # 训练样本
       theta=Train_linear_reg(train_X,y[:i],m,lamda) # 高级优化得出参数
       train_error[i-1]=Cost_reg(theta,train_X,y[:i],m,lamda) # 训练样本误差
       val_error[i-1]=Cost_reg(theta,xval,yval,yval.size,lamda) # 交叉验证误差
   return train_error,val_error # 返回两个误差向量

def Plot_learning_curve(train_error,val_error,m):
plt.xlim(0, 13)
plt.yticks(np.linspace(0,150,4))
plt.plot(np.arange(m),train_error,label='Train')
plt.plot(np.arange(m), val_error, label='Cross Validation')
plt.xlabel('Number of training examples',fontsize=8)
plt.ylabel('Error',fontsize=8)
plt.title('Learning curve for linear regression',fontsize=10)
   plt.grid(True) # 显示网格
   plt.legend(loc='best')
plt.show()

Figure_13.png

Polynomial regression

Learning Polynomial Regression

绘制多项式回归曲线
步骤:

  1. 利用已有特征[12,1]扩展次数至[12,6]
  2. 获取每列均值与样本标准偏差
  3. 标准化特征缩放
  4. 用训练样本训练参数
  5. 绘制原数据曲线
  6. 随机生成[-80,60]以内60个数x1=np.linspace(-80,60,60).reshape(-1,1),这里只需指定一维另一维自动确定,故-1无所谓
  7. 同样进行扩展,再用训练样本均值和样本标准偏差进行标准化特征缩放
  8. 绘制多项式回归曲线
  9. 扩展交叉验证集,用训练集均值和标准偏差进行归一化即特征缩放
  10. 计算训练误差与交叉验证误差
  11. 绘制多项式学习曲线

关于样本标准偏差:

  1. 第一个疑点在于为什么所有需要归一化即缩放的地方都用训练样本的均值和标准偏差:在实际应用中,我们是无法知道除训练样本意外的样本数据,即预测的数据是模型未曾见过的,故用训练样本所训练出来的模型处理其他的数据
  2. 特征缩放为什么除以标准偏差:我的理解是,和标准差不同的是标准偏差是样本方差的算术平方根,这里使用训练样本训练模型,也是样本数据,不能代表总体数据,故设置ddof=1,在方差的基础上底数减一
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    def Ploy_features(x,p): # x,x^2,...,x^p
       temp_x=np.zeros((x.shape[0],p))
    for i in range(1,p+1):
    temp_x[:,i-1]=x[:,0]**i
    return temp_x

    def Get_mean_std(x):
       means=np.mean(x,axis=0) # 取每列平均值
       std=np.std(x,axis=0,ddof=1) # 取每列标准偏差
       return means,std

    def Feature_Normalize(x,means,std): # 第一列不用特征缩放
    return np.hstack((np.ones((x.shape[0],1)),(x-means)/std))

    def Plot_fit(x,y,theta,mean,std):
    plt.plot(x,y,'rx')
    x1=np.linspace(-80,60,60).reshape(-1,1)
    x_ploy=Ploy_features(x1,6)
    x_ploy=Feature_Normalize(x_ploy,mean,std)
    plt.plot(x1,x_ploy@theta,'--')
    plt.title('Polynomial Regression Fit (lambda = 0.000000)',fontsize=12)
    plt.xlabel('Change in water level (x)',fontsize=8)
    plt.ylabel('Water flowing out of the dam (y)',fontsize=8)
    plt.show()

    def Learning_Polynomial_Reg(x,y,xval,yval,lamda=0):
    X_ploy = Ploy_features(X, 6) # 原文档使用octave优化算法不同,这里为了达到同样效果设为6
    means, std = Get_mean_std(X_ploy)
    X_ploy = Feature_Normalize(X_ploy, means, std)
    train_theta = Train_linear_reg(X_ploy, y.ravel(), m, lamda)
       Plot_fit(x,y,train_theta,means,std) # 绘制多项式回归曲线
       xval_ploy=Ploy_features(xval,6)
    xval_ploy=Feature_Normalize(xval_ploy,means,std)
    error_train,error_val=Learning_Curve(X_ploy,y.ravel(),xval_ploy,yval.ravel(),X_ploy.shape[0],lamda)
    plt=Plot_learning_curve(error_train,error_val,X_ploy.shape[0])
       plt.show() # 绘制多项式学习曲线

    Learning_Polynomial_Reg(X,y,Xval,yval)

Figure_14.png
Figure_15.png

Adjusting the regularization parameter

调整λ的值为1,100。
在上面代码中传参的时候更改λ的值,然后在绘制图片的标题部分手动设置输出值即可。
Figure_16.png
Figure_17.png
Figure_18.png