概念:
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 λ。
学习曲线:
这里我们先总结一下整个流程,当我们拿到一组数据时,我们需要先假设出一组模型,通过训练集来求出各自的参数,然后我们通过第二组的验证集来选择出最合适的那个模型。
现在我们回归初始,当我们通过训练集得到较好的参数时,我们会有两个误差值,即验证误差和训练误差,当我们处于一个高偏差的情况时,在测试样本很少时,训练误差显然而然会很小,即和数据吻合的很好(因为训练参数就是基于这些数据的),而验证误差就可能很大,因为验证集的数据可能天差地别,但随着测试样本增多时,训练误差可能就无法很好的去吻合所有的数据了,所以它的误差就会慢慢变大,而验证误差就会有多个吻合的情况,导致误差慢慢变小,但是一直到最后测试样本一定大的时候,两者就不会有很大的改动了。同时二者的数值会逐渐逼近。
而对于高方差的情况大致与上面一致,只不过最后两个误差之间会有较大的不同。
显然通过学习曲线我们可以判断出当前模型处于过拟合还是高方差来选择下一步该如何去做。
下一步做什么?
经过以上的讨论我们可以很好的分辨出过拟合欠拟合,高方差的分辨,那么,分辨出来之后我们应该怎么去做呢?我们有如下的方案抉择:
- 获取更多的训练集。(高方差)
- 减少特征项 (高方差)
- 添加特征项/添加高阶特征项(过拟合)
- 增加/减少$\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()
损失函数:
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()
可以看到是一阶函数,拟合效果并不是很好。
学习曲线:
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]))
结果如下:
结果:
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.')
结果如下:
。
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.')
结果如下:
可见在一左右训练和验证集的错误都比较小,可以作为合适的 λ \lambda λ值
上面与老师给的答案有些出入,自己检查代码又检查不出错误,总而言之,绘制学习曲线,可以更直观的观察出模型的优劣,并且分析出该模型处于一种什么样的状态。而关于正则化,我们可以选择出一组合适的 λ \lambda λ来绘制对应的学习曲线,这样就可以找到一个合适的 λ \lambda λ了。