概念:
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
加载数据与分割数据:
def loadData(filename):
data = sci.loadmat(filename)
return data
def reshapeData(data):
x = data["X"]
y = data["y"].flatten() # 一维化(12,)而使用.T倒置的话,则是(1,12)还是二维化的一种
x_t = data["Xtest"]
y_t = data["ytest"].flatten()
x_v = data["Xval"]
y_v = data["yval"].flatten()
return x, y, x_t, y_t, x_v, y_v
绘制曲线:
def plotData(x, y):
plt.scatter(x, y, c="red", marker="x")
plt.xlabel("change in water level (x)")
plt.ylabel("water flowing out of the dam(y)")
plt.show()
损失函数:
def costFunction(x, y, theta, Lambda):
m = y.size
x = np.c_[np.ones(m), x]
cost = np.dot(x, theta) - y
J_1 = np.sum(cost * cost) / (2 * m)
J_2 = np.sum(np.multiply(theta[1:], theta[1:])) * Lambda / (2 * m)
J = J_1 + J_2
G1 = np.dot(cost, x)/m
G2 = Lambda / m * theta
G2[0] = 0
grad = G1 + G2
return J, grad
测试:
theta = np.ones(2)
J, grad = costFunction(x, y, theta, 1)
# 输出结果应该大致为:J = 303.9931922202643 grad = [-15.30301567 598.25074417]
训练数据:
def train(x, y, Lambda):
initial = np.ones(x.shape[1] + 1)
def costFun(theta):
return costFunction(x, y, theta, Lambda)[0]
def gradFun(theta):
return costFunction(x, y, theta, Lambda)[1]
res = fmin_cg(f=costFun, x0=initial, fprime=gradFun, maxiter=200, disp=False) #方法一定不要带括号
return res
测试:
theta = train(x, y, 0)
plt.scatter(x, y, c="red", marker="x")
plt.xlabel("change in water level (x)")
plt.ylabel("water flowing out of the dam(y)")
z = np.c_[np.ones(x.shape[0]), x].dot(theta)
plt.plot(x, z)
plt.show()
可以看到是一阶函数,拟合效果并不是很好。
学习曲线:
def learningCurve(x, y, xval, yval, Lambda):
m = x.shape[0]
error_train = np.zeros(m)
error_val = np.zeros(m)
for i in range(m):
x_i = x[:i+1]
y_i = y[:i+1]
theta = train(x_i, y_i, Lambda)
error_train[i] = costFunction(x_i, y_i, theta, Lambda)[0]
error_val[i] = costFunction(x_v, y_v, theta, Lambda)[0]
return error_train, error_val
使用训练集训练出来的 θ \theta θ(从少到多),得到一组误差集,绘制学习曲线查看变化:
def plotData3(x, error_train, error_val):
plt.plot(np.arange(x.shape[0]), error_train)
plt.plot(np.arange(x.shape[0]), error_val)
plt.title("learing curve for linear regression")
plt.legend(["Train", "Cross Validation"])
plt.xlabel("Number of training examples")
plt.ylabel("Error")
plt.axis([0, 13, 0, 150])
plt.show()
结果如下:
高阶参数匹配:
def polyFeatures(x, Degree):
x_poly = np.zeros((x.shape[0], Degree))
for i in range(Degree):
if i == 0:
cur_x = x
else:
cur_x = cur_x * x # 阶次依次递增
x_poly[:, i] = cur_x.flatten()
return x_poly
特征归一化:
def featureNormalize(x):
mu = np.mean(x, 0) # 每一行的平均值
sigma = np.std(x, 0, ddof=1) # 标准差
x_norm = (x-mu)/sigma # 特征缩放
return mu, sigma, x_norm
测试验证与学习曲线绘制:
p = 8
x_poly = polyFeatures(x, p)
mu, sigma, x_poly = featureNormalize(x_poly)
x_poly_test = polyFeatures(x_t, p)
x_poly_test = x_poly_test - mu
x_poly_test = x_poly_test/sigma
x_poly_val = polyFeatures(x_v, p)
x_poly_val = x_poly_val-mu
x_poly_val = x_poly_val/sigma
def plotData4(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
x_poly = x_poly/sigma
x_poly = np.c_[np.ones(x.shape[0]), x_poly]
y = np.dot(x_poly, theta)
plt.plot(x, y)
theta = train(x_poly, y, 1)
plt.figure()
plt.scatter(x, y, c='red', marker='x')
plotData4(min(x), max(x), mu, sigma, theta, p)
plt.xlabel("change in water level(x)")
plt.ylabel("water flowing out of the dam(y)")
plt.ylim([0, 60])
plt.title("polynomial regression fit (lambda = {})".format(1))
plt.show()
error_train, error_val = learningCurve(x_poly, y, x_poly_val, y_v, 1)
plt.figure()
plt.plot(np.arange(m), error_train, np.arange(m), error_val)
plt.title("polynomial regression learning curve(lambda = {})".format(1))
plt.xlabel("Number of training examples")
plt.ylabel("Error")
plt.axis([0, 13, 0, 150])
plt.legend(["Train", "Cross Validation"])
plt.show()
结果如下:
可以看到数据被较好的吻合了。
根据学习曲线判断,在8阶的时候可以保证训练误差和验证误差都处在一个较小的范围之内,让我们尝试改变 λ \lambda λ
选取Lambda:
根据上述情况可以发现$\lambda 会 很 大 程 度 的 影 响 到 整 个 学 习 曲 线 的 变 化 , 那 么 我 们 如 何 求 取 最 合 适 的 会很大程度的影响到整个学习曲线的变化,那么我们如何求取最合适的 会很大程度的影响到整个学习曲线的变化,那么我们如何求取最合适的\lambda$?
def validationCurve(x, y, xval, yval):
lambda_vec = np.array([0, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1, 3, 10]) # 随机初始化
error_train = np.zeros(lambda_vec.size)
error_val = np.zeros(lambda_vec.size)
for i in range(lambda_vec.size):
Lambda = lambda_vec[i]
theta = train(x, y, Lambda)
error_train[i] = costFunction(x, y, theta, Lambda)[0]
error_val[i] = costFunction(xval, yval, theta, Lambda)[0] # 错误集
return lambda_vec, error_train, error_val
绘制:
lambda_vec, error_train, error_val = validationCurve(x_poly, y, x_poly_val, y_v)
plt.plot(lambda_vec, error_train, lambda_vec, error_val)
plt.legend(["Train", "Cross Validation"])
plt.xlabel("Label")
plt.ylabel("Error")
plt.show()
结果如下:
可见在一左右训练和验证集的错误都比较小,可以作为合适的 λ \lambda λ值