Machine Learning Exercise 1

实验题目

编写程序:模拟仿真多项式回归 参见 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步完成。首先进行数据采样,添加噪声;接着定义损失函数, 以及正则项;
之后采用梯度下降寻找局部最优解;再通过画图可视化实验结果。以下为具体描述:

数据生成

利用numpysin(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

LSE
RMS
loss_with_reg

代码如下:

# 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

梯度下降

上面两步我们分别准备好了数据以及损失函数,这一步我们设置一些常用的参数,利用梯度下降法寻找
局部最优解。定义的多项式函数如下:

Poly

这里我们的theta0设置为1,由于我们在准备数据的时候已经处理好了输入x,因此我们 的bias项可以直接并入到w中,构成新的theta

对参数theta求导:

theta_grad

更新theta:
theta_update

代码如下:

# 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()

效果图如下:

example

实验结果及分析

实验中,我们分别对了3大组主要的对比实验,分别是:1)对同一阶数的模型用不同的参数作对比实 验;2)对不同阶数(模型复杂度)的模型作对比实验;3)对模型添加L2正则项。

同模型-不同参数

这里我们选取M=4的模型,4阶模型复杂度适中。

迭代次数学习率

学习率保持不变。迭代次数过少,有可能没有达到局部(全局)最小点的时候终止,导致模型本身欠拟 合,需要增加迭代次数;迭代次数过多,模型可能对训练数据过拟合,在测试数据上的误差变大,泛化能力变差。
下面三图中,学习率设为0.1,迭代次数分别为1000,10000,20000。

iter:1000
iter:10000
iter:10000

迭代次数固定为10000,学习率分别设为0.1,0.01。观察到,迭代相同的次数时,lr=0.1时,测 试集上的误差要小一些。

lr:0.1

lr:0.01
不同评价指标

保持其他条件一致,分别使用两种Loss fucntion做实验。

LSE
MSE

对比发现,有时候单一的评价指标并不能反应模型的准确性能,换了MSE,过拟合了。

不同数据量

训练测试集数量分别采用(50,50),(100, 50)。分别用50、100组输入作为训练,均用50组输入 作为测试。为了方便对比,我们都将其训练至过拟合。

train:50 val:50
train:100 val:50

对比观察可知,当训练数据增多时,模型的泛化能力相应增强。

不同模型-同参数

通过4.1的实验,我们将参数固定为: (训练, 测试) = (100, 50), learning_rate = 0.1, iterations=5000

M=1, 2, 3

M=1
M=2
M=3

M=4, 5, 6

M=4

M=5
M=6

M=7, 8, 9

M=7

M=8
M=9

这里我们没有训练到过拟合。观察输出的Val loss,当模型的复杂度上升的时候,模型的表达能力 相应提高,因此泛化能力变好,val loss呈现出下降的趋势。同时我发现M=1时,模型训练到2000 轮,就不再开始训练了,而更高阶的模型依然可以继续训练。

一次实验中的模型系数表如下:

模型系数表

添加正则项(过拟合、欠拟合分析)

当我们的迭代次数设置少,或者学习率过低,或者模型过于简单的时候,都可能会出现欠拟合。比如只 迭代1000次,学习率设为0.0001,只用M=1的模型。

我们选取M=4的模型,先将其训练至过拟合。

过拟合

设置lamda分别为0.1, 0.25, 0.5, 1, 5

lambda=0.1
lambda=0.25
lambda=0.5
lambda=1

lambda=5
观察可知,lambda越大,惩罚越大,模型泛化能力越好。

实验总结

通过本次实验,在概念上,对多项式拟合有了更加全面深入的理解,能正确分析过拟合、欠拟合的 情况,运用L2范数增加模型的鲁棒性;在实验呢实践上,给了一个以后做实验的参照物,学习了如 何做对比实验、参数如何调整,更熟悉代码实现。