diagnostic -- 修正

这篇博客探讨了机器学习诊断的过程,包括评估假设、模型选择、过拟合与欠拟合的判断以及学习曲线的绘制。作者通过实例展示了线性回归在不同情况下的表现,解释了如何通过学习曲线来识别模型的偏差与方差问题,并提出了相应的改进措施,如添加特征项、调整正则化参数等。此外,还展示了如何通过验证曲线选择合适的正则化参数λ。

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

Maching learning diagnostic

概念:

​ A test that you can run to gain insight whta is/isn’t working with a learning algorithm,and gain guidance as to how best to improve its performance


评估假设:

将一组数据分为训练集和测试集,一般以7:3 当数据是有规则时,我们最好使用随机函数,没有规则时,只需要按顺序分割即可。接下来我们使用前百分之七十的数据训练我们的模型, 并用接下来的百分之三十的数据来验证我们的模型,并计算它的误差。


模型选择:

​ 假设我们有如下的模型:
θ 0 + θ 1 x + θ 2 x θ 0 + θ 1 x + θ 2 x 2 + θ 3 x 3 … … \theta_0+\theta_1x+\theta_2x \\ \theta_0+\theta_1x+\theta_2x^2+\theta_3x^3 \\ …… θ0+θ1x+θ2xθ0+θ1x+θ2x2+θ3x3
在选取模型时,我们可以依靠上方的评估假设来选择哪一个模型最为合适,但是这样并不能公平的比较各个模型对新样本的泛化能力,因为我们使用测试集的数据,选择了这个模型,那么,我们就不能继续使用测试集来进行评估各个模型对新样本的泛化能力。为了解决这个问题,我们将测试集再进行划分,划分一半的数据用来选择模型,划分一半的数据用来作为验证集。即(6:2:2)


过拟合与欠拟合的判断:

​ 对于一组数据和一个模型,不可避免的会产生一种误差,结合上面的知识,我们来总结一下如何判断模型是欠拟合和过拟合的问题,当模型欠拟合时,我们可以预见到它的验证集误差必然是大的,同时它的测试误差也是大的,这就是欠拟合的情况,而对于过拟合的情况,对应于验证误差是越来越小的(因为是用验证误差来选择的),但是测试误差依然是大的,这就是欠拟合和过拟合在样本数据误差的不同表现。

在这个问题上我们之前还牵扯到了一个正则化的概念,我们知道,正则化是通过限制 θ \theta θ的大小来剔除部分特征的,当我们通过上述办法选择了较好的模型来进行训练时,我们仍然存在过拟合与欠拟合的问题,此时我们使用正则化对它进行改善,再通过验证集训练确定一个最适用的参数 λ \lambda λ


学习曲线:

​ 这里我们先总结一下整个流程,当我们拿到一组数据时,我们需要先假设出一组模型,通过训练集来求出各自的参数,然后我们通过第二组的验证集来选择出最合适的那个模型。

​ 现在我们回归初始,当我们通过训练集得到较好的参数时,我们会有两个误差值,即验证误差和训练误差,当我们处于一个高偏差的情况时,在测试样本很少时,训练误差显然而然会很小,即和数据吻合的很好(因为训练参数就是基于这些数据的),而验证误差就可能很大,因为验证集的数据可能天差地别,但随着测试样本增多时,训练误差可能就无法很好的去吻合所有的数据了,所以它的误差就会慢慢变大,而验证误差就会有多个吻合的情况,导致误差慢慢变小,但是一直到最后测试样本一定大的时候,两者就不会有很大的改动了。同时二者的数值会逐渐逼近。

​ 而对于高方差的情况大致与上面一致,只不过最后两个误差之间会有较大的不同。

​ 显然通过学习曲线我们可以判断出当前模型处于过拟合还是高方差来选择下一步该如何去做。


下一步做什么?

​ 经过以上的讨论我们可以很好的分辨出过拟合欠拟合,高方差的分辨,那么,分辨出来之后我们应该怎么去做呢?我们有如下的方案抉择:

  1. 获取更多的训练集。(高方差)
  2. 减少特征项 (高方差)
  3. 添加特征项/添加高阶特征项(过拟合)
  4. 增加/减少$\lambda $​(过拟合/高方差)

Linear Regression with bias or variance
加载数据:
print('Loading and Visualizing data')
data = sio.loadmat('ex5data1.mat')	# 必须一维化,不然后续的损失函数运算会报错
X = data['X'][:, 0]
y = data['y'][:, 0]
Xtest = data['Xtest'][:, 0]
ytest = data['ytest'][:, 0]
Xval = data['Xval'][:, 0]
yval = data['yval'][:, 0]
m = X.shape[0]

绘制曲线:
plt.plot(X, y, 'rx', ms=10, lw=1.5)
plt.xlabel('Change in water level(x)')
plt.ylabel('Water flowing out of the dam(y)')
plt.show()

b_data


损失函数:
def linearRegCostFunction(theta, x, y, lamd):
    J = 1/(2*m) * np.sum((x.dot(theta) - y) ** 2) + lamd/2 * (np.sum(theta ** 2) - theta[0] ** 2)
    grad = 1/m * x.T.dot(x.dot(theta) - y) + lamd/m * theta
    grad[0] = grad[0] - lamd/m * theta[0]
    return J, grad

测试:

theta = np.array([1, 1])
J = linearRegCostFunction(theta, np.c_[np.ones(m), X], y, 1)[0]
print('Cost at theta = [1, 1] %f, (this value should be about 303.993192)' % J)

结果:

Cost at theta = [1, 1] 304.451526, (this value should be about 303.993192)

梯度测试:

J, grad = linearRegCostFunction(theta, np.c_[np.ones(m), X], y, 1)
print('Gradient at theta = [1 ; 1]:  [%f; %f] (this value should be about [-15.303016; 598.250744])', grad[0], grad[1])

结果:

Gradient at theta = [1 ; 1]:  [%f; %f] (this value should be about [-15.303016; 598.250744]) -15.303015674201186 598.2507441727035
训练数据:
def costFun(theta, x, y, lamd):
    return linearRegCostFunction(theta, x, y, lamd)[0]


def gradientFun(theta, x, y, lamd):
    return linearRegCostFunction(theta, x, y, lamd)[1]


def trainLinearReg(x, y, lamd):
    initial_theta = np.zeros((x.shape[1],))
    result = sop.fmin_cg(f=costFun, x0=initial_theta, fprime=gradientFun, args=(x, y, lamd), maxiter=200)
    return result


lamd = 0
theta = trainLinearReg(np.c_[np.ones(m), X], y, lamd)
曲线拟合:
plt.plot(X, y, 'rx', ms=10, lw=1.5)
plt.xlabel('Change in water level(x)')
plt.ylabel('Water flowing out of the dam(y)')
plt.plot(X, np.c_[np.ones(m), X].dot(theta), '--', lw=2)
plt.show()

b_data2

可以看到是一阶函数,拟合效果并不是很好。


学习曲线:
def learningCurve(x, y, xval, yval, lamd):
    m = x.shape[0]
    train_error = np.zeros(m)
    val_error = np.zeros(m)
    for i in range(m): # 数据集从少到多,观察损失变化(训练集与验证集)
        theta = trainLinearReg(x[:i+1], y[:i+1], lamd)
        train_error[i] = linearRegCostFunction(theta, x[:i+1], y[:i+1], lamd)[0]
        val_error[i] = linearRegCostFunction(theta, xval, yval, lamd)[0]
    return train_error, val_error

使用训练集训练出来的$\theta $(从少到多),得到一组误差集,绘制学习曲线查看变化:

lamd = 0
mval = Xval.shape[0]
error_train, error_val = learningCurve(np.c_[np.ones(m), X], y, np.c_[np.ones(mval), Xval], yval, lamd)
plt.plot(np.arange(m)+1, error_train, 'b-')
plt.plot(np.arange(m)+1, error_val, 'r-')
plt.legend(['Train', 'Cross Validation'])
plt.axis([0, 13, 0, 150])
plt.title('Learning curve for linear regression')
plt.xlabel('Number of Training examples')
plt.ylabel('Error')
plt.show()
print('Training Examples  Train Error  Cross Validation Error')
for i in range(m):
    print('\t%d\t\t\t\t%f\t\t\t%f' % (i+1, error_train[i], error_val[i]))

结果如下:

learning curve

结果:

Training Examples  Train Error  Cross Validation Error
	1				0.000000			358.961918
	2				0.000000			193.025641
	3				0.821649			78.767905
	4				0.947559			84.645595
	5				5.480854			62.764038
	6				9.721981			59.202433
	7				11.724138			55.949225
	8				12.115239			54.009281
	9				16.957054			54.487996
	10				19.384551			50.638363
	11				22.290812			51.715005
	12				22.373906			51.509182

可以明显的看到训练集误差随着数据集增大而增大,验证集误差随着数据集增大而减小。

上面的曲线是一次函数,我们也看到了匹配效果不是特别好,接下来我们尝试高维的函数

高阶参数匹配:
def polyFeatures(x, p): # 拓展维度
    x_poly = np.zeros((x.shape[0], p))
    for i in range(p):
        x_poly[:, i] = np.power(x, i+1)
    return x_poly


def featureNormalize(x): # 归一化
    mu = np.mean(x, 0)
    sigma = np.std(x, 0, ddof=1) # 标准差
    x_norm = (x-mu)/sigma
    return x_norm, mu, sigma


p = 8 # 
x_poly = polyFeatures(X, p)
x_poly, mu, sigma = featureNormalize(x_poly)# 训练集得出均值与标准差
x_poly = np.c_[np.ones(m), x_poly]

x_poly_test = polyFeatures(Xtest, p)
x_poly_test = (x_poly_test - mu) / sigma
x_poly_test = np.c_[np.ones(x_poly_test.shape[0]), x_poly_test] # 基于训练集的测试集归一化

x_poly_val = polyFeatures(Xval, p)
x_poly_val = (x_poly_val - mu) / sigma
x_poly_val = np.c_[np.ones(x_poly_val.shape[0]), x_poly_val] # 基于训练集的验证集的归一化

print('Normalized training example')
print('%f', x_poly[0])
高阶学习曲线绘制:


def plotFit(min_x, max_x, mu, sigma, theta, p):
    x = np.arange(min_x-15, max_x+25, 0.05)
    x_poly = polyFeatures(x, p)
    x_poly = (x_poly-mu)/sigma
    x_poly = np.c_[np.ones(x_poly.shape[0]), x_poly]
    return x, x_poly.dot(theta)

# 曲线拟合
lamd = 0
theta = trainLinearReg(x_poly, y, lamd) # 高阶训练集的拟合参数
plt.figure(1)
plt.subplot(111)
plt.plot(X, y, 'rx', ms=10, lw=1.5)
x_f, y_f = plotFit(np.min(X), np.max(X), mu, sigma, theta, p) # 拟合曲线
plt.plot(x_f, y_f, '--', lw=2)
plt.xlabel('Change in water level(x)')
plt.ylabel('Water flowing out of the dam (y)')
plt.title('Polynomial Regression Fit (lambda = %f)' % lamd)

# 学习曲线
error_train, error_val = learningCurve(x_poly, y, x_poly_val, yval, lamd)
plt.figure(2)
plt.subplot(111)
plt.plot(np.arange(m)+1, error_train, 'b-')
plt.plot(np.arange(m)+1, error_val, 'r-')
plt.xlabel('Number of Training examples')
plt.ylabel('Error')
plt.axis([0, 13, 0, 100])
plt.legend(['Train', 'Cross Validation'])
plt.show()
print('Polynomial Regression (lambda = %f)' % lamd)
print('Training Examples\tTrain Error\tCross Validation Error')
for i in range(m):
    print('  \t%d\t\t%f\t%f' % (i+1, error_train[i], error_val[i]))

_ = input('Press [Enter] to continue.')

结果如下:

lc-data-poly

lc-error

Training Examples	Train Error	Cross Validation Error
  	1		0.000000	281.263324
  	2		0.000000	280.212717
  	3		0.000000	108.070943
  	4		0.000000	108.375567
  	5		0.000000	11.546261
  	6		0.000001	18.557971
  	7		0.000003	49.468571
  	8		0.006163	32.876010
  	9		0.040100	20.701537
  	10		0.046528	19.438397
  	11		0.035271	40.227690
  	12		0.118377	21.452384

此时训练集误差接近于0,而验证集误差较一次函数更小。

根据学习曲线判断,在8阶的时候可以保证训练误差和验证误差都处在一个较小的范围之内,让我们尝试改变 λ \lambda λ


选取Lambda:

​ 根据上述情况可以发现$\lambda ​ 会 很 大 程 度 的 影 响 到 整 个 学 习 曲 线 的 变 化 , 那 么 我 们 如 何 求 取 最 合 适 的 ​会很大程度的影响到整个学习曲线的变化,那么我们如何求取最合适的 线\lambda$?

def validationCurve(x, y, xval, yval):
    lambda_vec = [0, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1, 3, 10]
    error_train = np.zeros(len(lambda_vec))
    error_val = np.zeros(len(lambda_vec))
    for i in range(len(lambda_vec)):
        lamd = lambda_vec[i]
        theta = trainLinearReg(x, y, lamd)
        error_train[i] = linearRegCostFunction(theta, x, y, lamd)[0]
        error_val[i] = linearRegCostFunction(theta, xval, yval, lamd)[0]
    return lambda_vec, error_train, error_val


Lambda_vec, error_train, error_val = validationCurve(x_poly, y, x_poly_val, yval)
plt.plot(Lambda_vec, error_train, 'b-')
plt.plot(Lambda_vec, error_val, 'r-')
plt.legend(["Train", "Cross Validation"])
plt.xlabel("Label")
plt.ylabel("Error")
plt.show()
print('lambda\t\tTrain Error\tValidation Error')
for i in range(len(Lambda_vec)):
    print(' %f\t%f\t%f' % (Lambda_vec[i], error_train[i], error_val[i]))

_ = input('Press [Enter] to continue.')

结果如下:

lc-reset-lambda

可见在一左右训练和验证集的错误都比较小,可以作为合适的 λ \lambda λ

上面与老师给的答案有些出入,自己检查代码又检查不出错误,总而言之,绘制学习曲线,可以更直观的观察出模型的优劣,并且分析出该模型处于一种什么样的状态。而关于正则化,我们可以选择出一组合适的 λ \lambda λ来绘制对应的学习曲线,这样就可以找到一个合适的 λ \lambda λ了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值