学习曲线的绘制与参数选择

博客介绍了学习曲线,它能反映训练集样本数与训练误差、验证集误差的关系,可判断模型是高偏差(欠拟合)还是高方差(过拟合)。还阐述了学习曲线的应用,通过绘制学习曲线选择模型参数,对比线性和多项式拟合,最终选定正则化常数为0.3。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、学习曲线与高偏差、高方差
学习曲线描述了训练集样本数与训练误差、在验证集误差的关系。通过学习曲线,我们能够清楚地知道我们的模型是高偏差还是高方差,即欠拟合还是过拟合。
下图为高偏差情况:
在这里插入图片描述
在高偏差情况下,可以看出训练误差JtrainJ_{train}Jtrain以及验证集误差JcvJ_{cv}Jcv都很大,且当我们横坐标的训练集样本数增大到一定程度,训练误差和验证误差都不在增加和减少。这很好理解,因为高误差意味着我们的模型不能很好描述所给训练集样本的情况。但我们的训练集样本增多到一定程度,由于模型的限制,训练样本的增加并不能再改善我们模型的拟合情况,所以最终JtrainJ_{train}JtrainJcvJ_{cv}Jcv都会趋于稳定。
高方差情况如下图:
在这里插入图片描述
在高方差情况下,JtrainJ_{train}Jtrain会小,JcvJ_{cv}Jcv很大,且当训练样本数增加到很大的程度,JtrainJ_{train}JtrainJcvJ_{cv}Jcv才会趋于稳定。这也很好理解:高方差情况对应着过拟合,过拟合情况下训练模型往往在训练集上拟合情况很好,但是模型把训练集的样本的特征当做所有样本的特征,所以往往过拟合情况在验证集上误差较大。当样本数mmm很大时,模型把个别样本特征当做所有样本特征情况就会有所缓解。所以随着mmm的增大,JtrainJ_{train}Jtrain增大,JcvJ_{cv}Jcv减小。

二、学习曲线的应用
下面我们应用绘制学习曲线来帮助我们选择模型参数
1、先导入需要使用的模块:

import numpy as np
from scipy.io import loadmat
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
from scipy.optimize import minimize

2、读取并查看处理数据:

data = loadmat('F:\\MachineLearning\data\ex5data1.mat')
print(data)

我们可以发现data中包含训练集X,训练集标记y,测试集Xtest,测试集标记ytest,验证集Xval以及验证集标记yval
我们分别取出x,y,Xtest,ytest,Xval以及yval。并为了之后的计算方便,把标记全部由矩阵形式改为数组形式:

X=data['X']
y=data['y']
Xval = data['Xval']
yval = data['yval']
Xtest = data['Xtest']
ytest = data['ytest']
X1=np.ravel(X)
y1=np.ravel(y)
m_Xval,d_Xval = Xval.shape
Xval1 = np.ravel(Xval)
yval1 = np.ravel(yval)
Xtest1 = np.ravel(Xtest)
ytest1 = np.ravel(ytest)
m,d = X.shape
m_test,d_test=Xtest.shape

下面我们查看训练集样本样本分布:

#查看训练集
df = pd.DataFrame({'water_level':X1,'flow':y1})
sns.lmplot('water_level','flow',data=df,fit_reg=False)
plt.show()

得到图:
在这里插入图片描述
由于常数项的存在,我们需要对X,Xtest,Xval添加全为1的列:

X1=X
Xval1=Xval
Xtest1=Xtest
X=np.insert(X,0,values=np.ones(m),axis=1)
Xval=np.insert(Xval,0,values=np.ones(m_Xval),axis=1)
Xtest=np.insert(Xtest,0,values=np.ones(m_test),axis=1)

3、用线性拟合数据
首先我们是正则化的代价函数和不带正则化的代价函数,这里之所以要有一个带正则化和不带正则化,是因为在训练参数是我们需要使用的是带正则化的代价函数和梯度函数,然而在绘制学习曲线时,我们需要使用不带正则化的代价函数:

def regularized_cost(theta,X,y,regularized_param):
    m = X.shape[0]
    delta= X @ theta - y
    cost_term = (delta.T @ delta) / (2*m)
    regularized_term = (theta[1:].T @ theta[1:]) * (regularized_param/(2*m))
    cost = regularized_term + cost_term
    return cost

def cost(theta,X,y):
    m = X.shape[0]
    delta= X @ theta - y
    total_cost = (delta.T @ delta) / (2*m)
    return total_cost

然后是带正则化的梯度函数:

def regularized_gradient(theta,X,y1,regularized_param):
    m = X.shape[0]
    delta = X @ theta - y1
    gradient_term = (X.T @ delta) / m
    theta1 = np.insert(theta[1:],0,values=0)
    regularized_term = (regularized_param / m) * theta1
    gradient=gradient_term + regularized_term
    return gradient

scipy中的minimize函数去求参数,因为这里我们的数据只有一个属性,如果加上常数项对应的参数,我们应该得到的是二维数组:

#拟合数据
theta=np.ones(2)
res = minimize(fun=regularized_cost,x0=theta,args=(X,y1,1),method='Newton-CG',\
               jac=regularized_gradient,options={'disp':True})
x=res.x

我们绘制曲线图查看模型与数据的拟合情况:

#对比线性拟合情况
plt.scatter(X1,y1,label='Trainning Set',color='blue')
plt.plot(X1,x[1]*X1+x[0],label='Prediction',color='red')
plt.legend()
plt.show()

得到图:
在这里插入图片描述
从图上可以看出我们的模型过于简单不能很好的描述训练集数据的特点。下面我们看看学习曲线的情况:

#画出学习曲线
Jtraining = []
Jcv = []
for i in range(m):
    res=minimize(fun=regularized_cost,x0=theta,args=(X[:i+1,:],y1[:i+1],1),method='Newton-CG',\
                jac=regularized_gradient,options={'disp':True})
    x=res.x
    jtraining = cost(x,X[:i+1,:],y1[:i+1])
    Jtraining.append(jtraining)
    jcv = cost(x,Xval,yval1)
    Jcv.append(jcv)
plt.plot(np.arange(1,m+1),Jcv,label='Jcv',color='red')
plt.plot(np.arange(1,m+1),Jtraining,label='Jtraining',color='blue')
plt.legend()
plt.show()

这里使用迭代,从i=0逐渐扩大i,取X[:i+1,:],这就相当与训练集样本数从1逐渐扩大,然后把不同样本集样本数下的训练集代价和验证集代价分别存于列表中。这样我们可以到学习曲线图:
在这里插入图片描述
我们可以看到JtrainJ_{train}JtrainJcvJ_{cv}Jcv都比较大,而且最终趋于稳定,所以对应与高偏差情况,即模型太简单不能很好描述我们的数据分布特点。下面我们考虑用更为复杂的多项式取拟合数据。

4、用多项式拟合数据
由于我们用多项式拟合数据时,会把我们原有数据进行高次计算形成新的属性,这就导致了我们的不同的属性之间的范围不同,从未导致各属性贡献的不平衡,所以我们要对我们的数据进行归一化,归一化的公式为:
xij,Normal=xij−μjδjx_{ij,Normal}=\frac{x_{ij}-μ_{j}}{δ_{j}}xij,Normal=δjxijμj
其中xNormal,ijx_{Normal,ij}xNormal,ij代表i个样本第j个属性归一化后的取值,xijx_{ij}xij代表i个样本第j个属性原始取值,μjμ_{j}μj代表所有样本在第j个属性上的平均值,δjδ_{j}δj代表所有样本在第j个属性上的标准差。这样我们就有归一化函数:

def normalize(x):
    x1=(x-np.mean(x)) / (np.std(x))
    return x1

接下来我们需要把我们原来一维的数据通过高次幂处理,拓展成八维数据,然后归一化,最后添加全为1的列,这样数据处理函数为:

def prepare_data(X,n):
    m = X.shape[0]
    prepared_data = np.zeros((m,n))
    for i in range(n):
        x = X[:,0]**(i+1)
        prepared_data[:,i] = (x-np.mean(x)) / (np.std(x))
    prepared_data = np.insert(prepared_data,0,values=np.ones(m),axis=1)
    return prepared_data

将我们的原始数据利用此函数处理:

prepared_data = prepare_data(X1,8)
prepared_xval = prepare_data(Xval1,8)

接着是绘制学习曲线的函数,其原理和一维情况相同:

def plot_learning_curve(prepared_data,y1,prepared_xval,yval1,regularized_param):
    m,d = prepared_data.shape
    theta8=np.ones(d)
    Jtraining=[]
    Jcv=[]
    m = prepared_data.shape[0]
    for i in range(m):
        theta8=np.ones(9)
        res=minimize(fun=regularized_cost,x0=theta8,args=(prepared_data[:i+1,:],y1[:i+1],regularized_param),\
                     method='TNC',jac=regularized_gradient,options={'disp':True})
        x1 = res.x
        jtraining = cost(x1,prepared_data[:i+1,:],y1[:i+1])
        jcv = cost(x1,prepared_xval,yval1)
        Jtraining.append(jtraining)
        Jcv.append(jcv)
    plt.plot(np.arange(1,m+1),Jtraining,label='Jtraing',color='blue')
    plt.plot(np.arange(1,m+1),Jcv,label='Jcv',color='red')
    plt.legend()

接着我们从0到100调整不同的正则化常数,查看学习曲线:

L=[0,0.001,0.003,0.01,0.03,0.1,0.3,1,3,10,30,100]
for l in L:
    plot_learning_curve(prepared_data,y1,prepared_xval,yval1,l)
    plt.show()

得到:

l=0
在这里插入图片描述
l=0.001
在这里插入图片描述
l=0.003
在这里插入图片描述
l=0.01
在这里插入图片描述
l=0.03
在这里插入图片描述
l=0.1
在这里插入图片描述
l=0.3
在这里插入图片描述
l=1
在这里插入图片描述
l=3
在这里插入图片描述
l=10
在这里插入图片描述
l=30
在这里插入图片描述
l=100
在这里插入图片描述
我们从图上可以知道如果选择l正则化常数为0.3或者1的话,JtrainingJ_{training}Jtraining不会太小而过拟合,JcvJ_{cv}Jcv不会太大表明在新样本上依然拥有较好的泛化能力。
我们还可以计算在测试集上的误差来选择正则化常数:

#得到测试集上的代价值
prepared_test = prepare_data(Xtest1,8)
theta8=np.ones(9)
for l in L:
    test_fmin=minimize(fun=regularized_cost,x0=theta8,args=(prepared_data[:i+1,:],y1[:i+1],l),\
                     method='TNC',jac=regularized_gradient,options={'disp':True})
    test_cost = cost(test_fmin.x,prepared_test,ytest1)
    print(str(l)+'  :  '+str(test_cost))

得到:

0  :  9.873941227352864
0.001  :  11.008145910102536
0.003  :  11.301862220853764
0.01  :  10.988460477608943
0.03  :  10.218881836544037
0.1  :  8.953968731131386
0.3  :  7.745110198919966
1  :  7.851586049767016
3  :  11.770306579735657
10  :  26.894076292237678
30  :  52.76679321644176
100  :  79.07925641027286

我们发现在测试集上正则化常数为0.3时,测试集上的代价最小,所以我们选择正则化常数为0.3。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值