实验题目
编写程序:模拟仿真多项式回归 参见 textbook p4-12(PRML)。完成以下任务:
1) 生成正弦序列 s(n);
2) 使用噪声函数对正弦序列加噪 x(n)=s(n)+w(n);
3) 使用多项式回归模型对 x(n)进行拟合,并分析过拟合和欠拟合情况
注:参考误差函数式 1-2,带正则项的修正误差函数式 1-4,实验仿真生成图 1- 6、图 1-7,给出模型系数表。
实验过程及代码
实验过程按照题目要求可以拆解为4步完成。首先进行数据采样,添加噪声;接着定义损失函数,
以及正则项;
之后采用梯度下降寻找局部最优解;再通过画图可视化实验结果。以下为具体描述:
数据生成
利用numpy
在sin(2*pi*x)
的基础上生成点,并添加高斯噪声(其中均值设为0,方差设为0.3)。
为了方便后序的实验,我们在生成数据x, y
的同时添加阶数, 每个x
调整为形如
x = [1, x, x^2, ..., x^M]
的形式。最前面的1对应目标函数的b。
数据生成代码如下:
# define gaussion noise function
def noise(mu=0, sigma=0.3):
'''
dtype:
mu: default 0
sigma: default 0.3
rtype:
noise: float
'''
return np.random.normal(mu, sigma)
def genTargetWithNoise(x, noise=noise):
''' target = sin(2*pi*x) + noise
dtype:
x: float
noise: noise function
rtype:
t: float
'''
# assert 0 <= x <= 1
t = np.sin(2*np.pi*x)
if noise:
t += noise()
return t
def regenX(random, poly_degree):
xs = [[x ** i for i in range(1, poly_degree+1)] for x in random]
for x in xs:
x.insert(0, 1.)
return xs
def genData(numPoints, noise, poly_degree):
'''Generate data.
'''
# random sample floats in the half-open interval [0.0, 1.0).
random = np.random.random_sample((numPoints,))
# generate targets for random
targets = list(map(genTargetWithNoise, random))
# remake data to ractangle form and add 1. to all x to correspond with b
xs = regenX(random, poly_degree)
return np.array(xs), np.array(targets)
损失函数
这里我们按照《PRML》中式1-2, 1-3
的定义,使用平方误差与均方误差。同时添加正则项。
如式1-4
。
代码如下:
# define a polynomial
def f(xs, theta):
xs = np.asarray(xs)
# print(xs.shape)
if xs.shape[0] > 1:
return [x.T.dot(theta) for x in xs]
return xs.T.dot(theta)
# define squares error
def LSE_loss(y_true, y_hat, theta, penalization=0.1):
y_true = np.asarray(y_true)
y_hat = np.asarray(y_hat)
# set the regularizer
regularizer = (penalization / 2) * (np.dot(theta.T, theta))
return (1/2) * np.square(y_hat - y_true).sum() + regularizer
# define mean squares error loss
def MSE_loss(y_true, y_hat, theta, penalization=0.1):
y_true = np.asarray(y_true)
y_hat = np.asarray(y_hat)
# set the regularizer
regularizer = (penalization / 2) * (np.dot(theta.T, theta))
# compute mse
mse = np.sqrt(np.square(y_hat - y_true).sum()/len(y_hat)) + regularizer
return mse
梯度下降
上面两步我们分别准备好了数据以及损失函数,这一步我们设置一些常用的参数,利用梯度下降法寻找
局部最优解。定义的多项式函数如下:
这里我们的theta0
设置为1,由于我们在准备数据的时候已经处理好了输入x
,因此我们
的bias
项可以直接并入到w
中,构成新的theta
。
对参数theta求导:
更新theta:
代码如下:
# gd
def gradientDescent(x, y, x_val, y_val, theta, lr, sample_num, numIterations, loss_name='LSE', penalization=0):
xTrans = x.transpose()
record = []
freq = 100 if numIterations < 10000 else 1000
for i in range(0, numIterations+1):
y_hat = f(x, theta)
if loss_name == 'LSE':
loss = LSE_loss(y, y_hat, theta, penalization)
elif loss_name == 'MSE':
loss = MSE_loss(y, y_hat, theta, penalization)
if i % freq == 0:
print("Iteration %d | %s loss: %f" % (i, loss_name, loss))
y_val_hat = f(x_val, theta)
if loss_name == 'LSE':
val_loss = LSE_loss(y_val, y_val_hat, theta)
elif loss_name == 'MSE':
val_loss = MSE_loss(y_val, y_val_hat, theta)
record.append([i, loss, val_loss])
# avg gradient per example
gradient = (x.T.dot(y_hat - y) / sample_num) + penalization*theta
# update
theta = theta - lr * gradient
return theta, record
结果绘图
这一步我们通过在训练结果以及在训练中保存的结果分别绘制train loss, val loss
对比图,
与拟合结果图。
代码如下:
def plotLoss(record):
x = [it[0] for it in record]
train_loss = [it[1] for it in record]
val_loss = [it[2] for it in record]
plt.figure(figsize=(8, 4))
plt.plot(x, train_loss, label='$train-loss$', color='green', linewidth=0.5)
plt.plot(x, val_loss, label='$val-loss$', color='red', linewidth=0.5)
plt.plot(x, train_loss, 'go', markerfacecolor='none')
plt.plot(x, val_loss, 'ro', markerfacecolor='none')
plt.xlabel('Iterations')
plt.ylabel('Loss')
plt.title('Train vs Val')
plt.legend()
plt.show()
def plotNow(x, y, target_func, cur_func, theta, poly_degree):
xrange = np.arange(0, 1, 0.01)
targetfunc = target_func(2*np.pi*xrange)
re_xrange = regenX(xrange, poly_degree)
curfunc = cur_func(re_xrange, theta)
plt.figure(figsize=(8, 4))
plt.plot(xrange, targetfunc, label='$sin(2πx)$', color='green', linewidth=0.5)
plt.plot(xrange, curfunc, label='$Polynomial$', color='red', linewidth=0.5)
plt.xlabel('x')
plt.ylabel('y')
plt.title('Polynomial')
# plt.xlim(0,1)
plt.ylim(-2,2)
plt.legend()
plt.plot(x, y, 'bo', markerfacecolor='none')
plt.show()
效果图如下:
实验结果及分析
实验中,我们分别对了3大组主要的对比实验,分别是:1)对同一阶数的模型用不同的参数作对比实 验;2)对不同阶数(模型复杂度)的模型作对比实验;3)对模型添加L2正则项。
同模型-不同参数
这里我们选取M=4
的模型,4阶模型复杂度适中。
迭代次数学习率
学习率保持不变。迭代次数过少,有可能没有达到局部(全局)最小点的时候终止,导致模型本身欠拟
合,需要增加迭代次数;迭代次数过多,模型可能对训练数据过拟合,在测试数据上的误差变大,泛化能力变差。
下面三图中,学习率设为0.1,迭代次数分别为1000,10000,20000。
迭代次数固定为10000,学习率分别设为0.1,0.01。观察到,迭代相同的次数时,lr=0.1
时,测
试集上的误差要小一些。
不同评价指标
保持其他条件一致,分别使用两种Loss fucntion
做实验。
对比发现,有时候单一的评价指标并不能反应模型的准确性能,换了MSE,过拟合了。
不同数据量
训练测试集数量分别采用(50,50),(100, 50)
。分别用50、100组输入作为训练,均用50组输入
作为测试。为了方便对比,我们都将其训练至过拟合。
对比观察可知,当训练数据增多时,模型的泛化能力相应增强。
不同模型-同参数
通过4.1
的实验,我们将参数固定为:
(训练, 测试) = (100, 50), learning_rate = 0.1, iterations=5000
。
M=1, 2, 3
M=4, 5, 6
M=7, 8, 9
这里我们没有训练到过拟合。观察输出的Val loss
,当模型的复杂度上升的时候,模型的表达能力
相应提高,因此泛化能力变好,val loss呈现出下降的趋势。同时我发现M=1
时,模型训练到2000
轮,就不再开始训练了,而更高阶的模型依然可以继续训练。
一次实验中的模型系数表如下:
添加正则项(过拟合、欠拟合分析)
当我们的迭代次数设置少,或者学习率过低,或者模型过于简单的时候,都可能会出现欠拟合。比如只
迭代1000次,学习率设为0.0001,只用M=1
的模型。
我们选取M=4
的模型,先将其训练至过拟合。
设置lamda分别为0.1, 0.25, 0.5, 1, 5
。
实验总结
通过本次实验,在概念上,对多项式拟合有了更加全面深入的理解,能正确分析过拟合、欠拟合的 情况,运用L2范数增加模型的鲁棒性;在实验呢实践上,给了一个以后做实验的参照物,学习了如 何做对比实验、参数如何调整,更熟悉代码实现。