神经网络与深度学习笔记(2)——机器学习

目录

1 实现一个简单的线性回归模型

1.1 数据集构建

1.2 模型构建

1.3 损失函数

1.4 模型优化

1.5 模型训练

1.6 模型评估

2 多项式回归

2.1 数据集构建

2.2 模型构建

2.3 模型训练

2.4 模型评估


这里,不知道为什么我的内核经常挂掉,所以在开头附上此代码:

import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" #防止内核挂掉重启

1 实现一个简单的线性回归模型

要通过机器学习来解决一个特定的任务时,我们需要准备5个方面的要素:

  1. 数据集:收集任务相关的数据集用来进行模型训练和测试,可分为训练集、验证集和测试集;
  2. 模型:实现输入到输出的映射,通常为可学习的函数;
  3. 学习准则:模型优化的目标,通常为损失函数和正则化项的加权组合;
  4. 优化算法:根据学习准则优化机器学习模型的参数;
  5. 评价指标:用来评价学习到的机器学习模型的性能.

1.1 数据集构建

首先,我们构造一个小的回归数据集。

def linear_func(x,w=1.2,b=0.5):
    y=w*x+b
    return y

使用paddle.rand()函数来进行随机采样输入特征x,并代入上面函数得到输出标签。

import paddle

def create_toy_data(func, interval, sample_num, noise = 0.0, add_outlier = False, outlier_ratio = 0.001):
    """
    根据给定的函数,生成样本
    输入:
       - func:函数
       - interval: x的取值范围
       - sample_num: 样本数目
       - noise: 噪声均方差
       - add_outlier:是否生成异常值
       - outlier_ratio:异常值占比
    输出:
       - X: 特征数据,shape=[n_samples,1]
       - y: 标签数据,shape=[n_samples,1]
    """

    # 均匀采样
    # 使用paddle.rand生成sample_num个随机数
    X = paddle.rand(shape = [sample_num]) * (interval[1]-interval[0]) + interval[0]
    y = func(X)

    # 生成高斯分布的标签噪声
    # 使用paddle.normal生成0均值,noise标准差的数据
    epsilon = paddle.normal(0,noise,paddle.to_tensor(y.shape[0]))#生成均值为0,标准差为noise,个数为sample_num的数据
    y = y + epsilon
    if add_outlier:     # 生成额外的异常点
        outlier_num = int(len(y)*outlier_ratio)
        if outlier_num != 0:
            # 使用paddle.randint生成服从均匀分布的、范围在[0, len(y))的随机Tensor
            outlier_idx = paddle.randint(len(y),shape = [outlier_num])
            y[outlier_idx] = y[outlier_idx] * 5
    return X, y

下面生成 150 个带噪音的样本,其中 100 个训练样本,50 个测试样本,并打印出训练数据的可视化分布。

from matplotlib import pyplot as plt # matplotlib 是 Python 的绘图库

func = linear_func
interval = (-10,10)
train_num = 100 # 训练样本数目
test_num = 50 # 测试样本数目
noise = 2 #标准差
#给定函数下生成样本数据
X_train, y_train = create_toy_data(func=func, interval=interval, sample_num=train_num, noise = noise, add_outlier = False)
X_test, y_test = create_toy_data(func=func, interval=interval, sample_num=test_num, noise = noise, add_outlier = False)

X_train_large, y_train_large = create_toy_data(func=func, interval=interval, sample_num=5000, noise = noise, add_outlier = False)

# paddle.linspace返回一个Tensor,Tensor的值为在区间start和stop上均匀间隔的num个值,输出Tensor的长度为num
X_underlying = paddle.linspace(interval[0],interval[1],train_num) 
y_underlying = linear_func(X_underlying)

# 绘制数据
plt.scatter(X_train, y_train, marker='*', facecolor="none", edgecolor='#e4007f', s=50, label="train data")
plt.scatter(X_test, y_test, facecolor="none", edgecolor='#f19ec2', s=50, label="test data")
plt.plot(X_underlying, y_underlying, c='#000000', label=r"underlying distribution")#绘制我们的线性函数
plt.legend(fontsize='x-large') # 给图像加图例
# plt.savefig('ml-vis.pdf') # 保存图像到PDF文件中
plt.show()

运行结果如下:

神经网络与深度学习笔记(2)——机器学习_第1张图片

数据已经准备好了。

1.2 模型构建

神经网络与深度学习笔记(2)——机器学习_第2张图片

shape属性是指每个维度上元素的数量,其实无非就是矩阵的运算。

import paddle
from nndl.op import Op

paddle.seed(10) #设置随机种子

# 线性算子
class Linear(Op):
    def __init__(self, input_size):
        """
        输入:
           - input_size:模型要处理的数据特征向量长度
        """

        self.input_size = input_size

        # 模型参数
        self.params = {}
        self.params['w'] = paddle.randn(shape=[self.input_size,1],dtype='float32') 
        self.params['b'] = paddle.zeros(shape=[1],dtype='float32')

    def __call__(self, X):
        return self.forward(X)

    # 前向函数
    def forward(self, X):
        """
        输入:
           - X: tensor, shape=[N,D]
           注意这里的X矩阵是由N个x向量的转置拼接成的,与原教材行向量表示方式不一致
        输出:
           - y_pred: tensor, shape=[N]
        """

        N,D = X.shape

        if self.input_size==0:
            return paddle.full(shape=[N,1], fill_value=self.params['b'])
        
        assert D==self.input_size # 输入数据维度合法性验证

        # 使用paddle.matmul计算两个tensor的乘积
        y_pred = paddle.matmul(X,self.params['w'])+self.params['b']
        
        return y_pred

# 注意这里我们为了和后面章节统一,这里的X矩阵是由N个x向量的转置拼接成的,与原教材行向量表示方式不一致
input_size = 3
N = 2
X = paddle.randn(shape=[N, input_size],dtype='float32') # 生成2个维度为3的数据
model = Linear(input_size)
y_pred = model(X)
print("y_pred:",y_pred) #输出结果的个数也是2个

运行结果如下:

y_pred: Tensor(shape=[2, 1], dtype=float32, place=Place(cpu), stop_gradient=True,
       [[ 0.50950956],
        [-2.26490903]])

 先说一下,这里需要注意,我这篇博文的程序代码是在下面创建的(内置nndl模块):

神经网络与深度学习笔记(2)——机器学习_第3张图片

如果不是在该文件夹下创建,需要将nndl文件移动到我们创建的文件夹下,否则会出现:

神经网络与深度学习笔记(2)——机器学习_第4张图片

其实 nndl 文件中的op文件也就是上面代码里中间一大段的讲解和实现(就是线性算子)。

在第一节就纳闷怎么突然讲算子,这里就用上了。

算子是构建复杂机器学习模型的基础组件,包含一个函数()的前向函数和反向函数。

算子的接口是这样定义的:

class Op(object):
    def __init__(self):
        pass

    def __call__(self, inputs):
        return self.forward(inputs)

    # 前向函数
    # 输入:张量inputs
    # 输出:张量outputs
    def forward(self, inputs):
        # return outputs
        raise NotImplementedError

    # 反向函数
    # 输入:最终输出对outputs的梯度outputs_grads
    # 输出:最终输出对inputs的梯度inputs_grads
    def backward(self, outputs_grads):
        # return inputs_grads
        raise NotImplementedError

这个要对python中的类有一定的基础知识,否则看不懂。 

1.3 损失函数

def mean_squared_error(y_true, y_pred):
    """
    输入:
       - y_true: tensor,样本真实标签
       - y_pred: tensor, 样本预测标签
    输出:
       - error: float,误差值
    """

    assert y_true.shape[0] == y_pred.shape[0]
    
    # paddle.square计算输入的平方值
    # paddle.mean沿 axis 计算 x 的平均值,默认axis是None,则对输入的全部元素计算平均值。
    error = paddle.mean(paddle.square(y_true - y_pred))

    return error


# 构造一个简单的样例进行测试:[N,1], N=2
y_true= paddle.to_tensor([[-0.2],[4.9]],dtype='float32')
y_pred = paddle.to_tensor([[1.3],[2.5]],dtype='float32')

error = mean_squared_error(y_true=y_true, y_pred=y_pred).item()#items() 函数以列表返回可遍历的(键, 值) 元组。
print("error:",error)

运行结果如下: 

error: 4.005000114440918

1.4 模型优化

def optimizer_lsm(model, X, y, reg_lambda=0):
  """
    输入:
       - model: 模型
       - X: tensor, 特征数据,shape=[N,D]
       - y: tensor,标签数据,shape=[N]
       - reg_lambda: float, 正则化系数,默认为0
    输出:
       - model: 优化好的模型
    """

  N, D = X.shape

  # 对输入特征数据所有特征向量求平均后再转置,为后面求b方便
  x_bar_tran = paddle.mean(X,axis=0).T 
  
  # 求标签的均值,shape=[1]
  y_bar = paddle.mean(y)
  
  # paddle.subtract通过广播的方式实现矩阵减向量
  x_sub = paddle.subtract(X,x_bar_tran)

  # 使用paddle.all判断输入tensor是否全0
  if paddle.all(x_sub==0):
    model.params['b'] = y_bar
    model.params['w'] = paddle.zeros(shape=[D])
    return model
  
  # paddle.inverse求方阵的逆
  tmp = paddle.inverse(paddle.matmul(x_sub.T,x_sub)+
          reg_lambda*paddle.eye(num_rows = (D)))

  w = paddle.matmul(paddle.matmul(tmp,x_sub.T),(y-y_bar))
  
  b = y_bar-paddle.matmul(x_bar_tran,w)
  
  model.params['b'] = b
  model.params['w'] = paddle.squeeze(w,axis=-1)

  return model

1.5 模型训练

训练样本数目(100)较少时:

input_size = 1
model = Linear(input_size)
model = optimizer_lsm(model,X_train.reshape([-1,1]),y_train.reshape([-1,1]))
print("w_pred:",model.params['w'].item(), "b_pred: ", model.params['b'].item())

y_train_pred = model(X_train.reshape([-1,1])).squeeze()
train_error = mean_squared_error(y_true=y_train, y_pred=y_train_pred).item()
print("train error: ",train_error)

运行结果如下: 

w_pred: 1.1364599466323853 b_pred:  0.2283352017402649
train error:  3.3990638256073

训练样本数目(5000)较大时:

model_large = Linear(input_size)
model_large = optimizer_lsm(model_large,X_train_large.reshape([-1,1]),y_train_large.reshape([-1,1]))
print("w_pred large:",model_large.params['w'].item(), "b_pred large: ", model_large.params['b'].item())

y_train_pred_large = model_large(X_train_large.reshape([-1,1])).squeeze()
train_error_large = mean_squared_error(y_true=y_train_large, y_pred=y_train_pred_large).item()
print("train error large: ",train_error_large)

运行结果如下:

w_pred large: 1.197813630104065 b_pred large:  0.5165650248527527
train error large:  4.074573516845703

1.6 模型评估

y_test_pred = model(X_test.reshape([-1,1])).squeeze()
test_error = mean_squared_error(y_true=y_test, y_pred=y_test_pred).item()
print("test error: ",test_error)

运行结果如下:

test error:  3.019052743911743
y_test_pred_large = model_large(X_test.reshape([-1,1])).squeeze()
test_error_large = mean_squared_error(y_true=y_test, y_pred=y_test_pred_large).item()
print("test error large: ",test_error_large)

运行结果如下:

test error large:  2.5873899459838867

2 多项式回归

2.1 数据集构建

import math

# sin函数: sin(2 * pi * x)
def sin(x):
    y = paddle.sin(2 * math.pi * x)
    return y

构建训练和测试数据,其中训练数样本 15 个,测试样本 10 个,高斯噪声标准差为 0.1,自变量范围为 (0,1)。

# 生成数据
func = sin
interval = (0,1)
train_num = 15
test_num = 10
noise = 0.5 #0.1 
X_train, y_train = create_toy_data(func=func, interval=interval, sample_num=train_num, noise = noise)
X_test, y_test = create_toy_data(func=func, interval=interval, sample_num=test_num, noise = noise)

#下图中黑线所示
X_underlying = paddle.linspace(interval[0],interval[1],num=100)
y_underlying = sin(X_underlying)

# 绘制图像
plt.rcParams['figure.figsize'] = (8.0, 6.0)
plt.scatter(X_train, y_train, facecolor="none", edgecolor='#e4007f', s=50, label="train data")
#plt.scatter(X_test, y_test, facecolor="none", edgecolor="r", s=50, label="test data")
plt.plot(X_underlying, y_underlying, c='#000000', label=r"$\sin(2\pi x)$")
plt.legend(fontsize='x-large')
# plt.savefig('ml-vis2.pdf')
plt.show()

运行结果如下:

神经网络与深度学习笔记(2)——机器学习_第5张图片

2.2 模型构建

多项式回归和线性回归一样,同样需要学习参数,只不过需要对输入特征()根据多项式阶数进行变换。因此,我们可以套用求解线性回归参数的方法来求解多项式回归参数。

# 多项式转换
def polynomial_basis_function(x, degree = 2):
    """
    输入:
       - x: tensor, 输入的数据,shape=[N,1]
       - degree: int, 多项式的阶数
       example Input: [[2], [3], [4]], degree=2
       example Output: [[2^1, 2^2], [3^1, 3^2], [4^1, 4^2]]
       注意:本案例中,在degree>=1时不生成全为1的一列数据;degree为0时生成形状与输入相同,全1的Tensor
    输出:
       - x_result: tensor
    """
    
    if degree==0:
        return paddle.ones(shape = x.shape,dtype='float32') 

    x_tmp = x
    x_result = x_tmp

    for i in range(2, degree+1):
        x_tmp = paddle.multiply(x_tmp,x) # 逐元素相乘算子,输入 x 与输入 y 逐元素相乘,并将各个位置的输出元素保存到返回结果中。因为x_tmp和x是相同的张量,所以逐元素相乘时对应的元素是相同的,即2*2,3*3,也就是平方
        x_result = paddle.concat((x_result,x_tmp),axis=-1)#对输入沿 axis 轴进行联结(axis=-1,即按列拼接),返回一个新的Tensor。

    return x_result

# 简单测试
data = [[2], [3], [4]]
X = paddle.to_tensor(data = data,dtype='float32')
degree = 3
transformed_X = polynomial_basis_function(X,degree=degree)
print("转换前:",X)
print("阶数为",degree,"转换后:",transformed_X)

运行结果如下:

转换前: Tensor(shape=[3, 1], dtype=float32, place=Place(cpu), stop_gradient=True,
       [[2.],
        [3.],
        [4.]])
阶数为 3 转换后: Tensor(shape=[3, 3], dtype=float32, place=Place(cpu), stop_gradient=True,
       [[2. , 4. , 8. ],
        [3. , 9. , 27.],
        [4. , 16., 64.]])

2.3 模型训练

对于多项式回归,我们可以同样使用前面线性回归中定义的LinearRegression算子、训练函数train、均方误差函数mean_squared_error。拟合训练数据的目标是最小化损失函数,同线性回归一样,也可以通过矩阵运算直接求出的值。

我们设定不同的多项式阶,的取值分别为0、1、3、8,之前构造的训练集上进行训练,观察样本数据对sin曲线的拟合结果。

plt.rcParams['figure.figsize'] = (12.0, 8.0) #图框属性

#enumerate非常好用,有时我们可以更改索引
for i, degree in enumerate([0, 1, 3, 8]): # []中为多项式的阶数,对于这个,i在索引[0,1,2,3]中遍历,degree在[0,1,3,8]中遍历
    model = Linear(degree)
    #reshape([-1,1])是改变为n行1列,reshape([-1])是改变为1行n列
    X_train_transformed = polynomial_basis_function(X_train.reshape([-1,1]), degree)#在保持输入 x 数据不变的情况下,改变 x 的形状。
    
    X_underlying_transformed = polynomial_basis_function(X_underlying.reshape([-1,1]), degree)
    
    model = optimizer_lsm(model,X_train_transformed,y_train.reshape([-1,1])) #拟合得到参数

    y_underlying_pred = model(X_underlying_transformed).squeeze()#squeeze()会删除输入Tensor的Shape中尺寸为1的维度(元素个数为1的轴)。如果指定了axis,则会删除axis中指定的尺寸为1的维度。如果没有指定axis,那么所有等于1的维度都会被删除。

    print(model.params)
    
    # 绘制图像
    plt.subplot(2, 2, i + 1)
    plt.scatter(X_train, y_train, facecolor="none", edgecolor='#e4007f', s=50, label="train data")#图中粉色○
    plt.plot(X_underlying, y_underlying, c='#000000', label=r"$\sin(2\pi x)$")#图中黑色线
    plt.plot(X_underlying, y_underlying_pred, c='#f19ec2', label="predicted function")#图中粉色实线
    plt.ylim(-2, 1.5)
    plt.annotate("M={}".format(degree), xy=(0.95, -1.4))

#plt.legend(bbox_to_anchor=(1.05, 0.64), loc=2, borderaxespad=0.)
plt.legend(loc='lower left', fontsize='x-large')
# plt.savefig('ml-vis3.pdf')
plt.show()

运行结果如下: 

神经网络与深度学习笔记(2)——机器学习_第6张图片

2.4 模型评估

下面通过均方误差来衡量训练误差、测试误差以及在没有噪音的加入下sin函数值与多项式回归值之间的误差,更加真实地反映拟合结果。多项式分布阶数从0到8进行遍历。

# 训练误差和测试误差
training_errors = []
test_errors = []
distribution_errors = []

# 遍历多项式阶数
for i in range(9):
    model = Linear(i)

    X_train_transformed = polynomial_basis_function(X_train.reshape([-1,1]), i) 
    X_test_transformed = polynomial_basis_function(X_test.reshape([-1,1]), i) 
    X_underlying_transformed = polynomial_basis_function(X_underlying.reshape([-1,1]), i)
    
    optimizer_lsm(model,X_train_transformed,y_train.reshape([-1,1]))
    
    y_train_pred = model(X_train_transformed).squeeze()
    y_test_pred = model(X_test_transformed).squeeze()
    y_underlying_pred = model(X_underlying_transformed).squeeze()

    train_mse = mean_squared_error(y_true=y_train, y_pred=y_train_pred).item()
    training_errors.append(train_mse)

    test_mse = mean_squared_error(y_true=y_test, y_pred=y_test_pred).item()
    test_errors.append(test_mse)

    #distribution_mse = mean_squared_error(y_true=y_underlying, y_pred=y_underlying_pred).item()
    #distribution_errors.append(distribution_mse)

print ("train errors: \n",training_errors)
print ("test errors: \n",test_errors)
#print ("distribution errors: \n", distribution_errors)

# 绘制图片
plt.rcParams['figure.figsize'] = (8.0, 6.0)
plt.plot(training_errors, '-.', mfc="none", mec='#e4007f', ms=10, c='#e4007f', label="Training")
plt.plot(test_errors, '--', mfc="none", mec='#f19ec2', ms=10, c='#f19ec2', label="Test")
#plt.plot(distribution_errors, '-', mfc="none", mec="#3D3D3F", ms=10, c="#3D3D3F", label="Distribution")
plt.legend(fontsize='x-large')
plt.xlabel("degree")
plt.ylabel("MSE")
# plt.savefig('ml-mse-error.pdf')
plt.show()

运行结果如下: 

train errors: 
 [0.5963446497917175, 0.2896002531051636, 0.26379576325416565, 0.23111073672771454, 0.17822088301181793, 0.1385764479637146, 0.16013973951339722, 0.3500741422176361, 0.19967743754386902]
test errors: 
 [0.4975295066833496, 0.4701949954032898, 0.38963937759399414, 0.3042631447315216, 0.29470306634902954, 0.4156731069087982, 0.3180873394012451, 0.7467994093894958, 0.2483164519071579]

神经网络与深度学习笔记(2)——机器学习_第7张图片

degree = 8 # 多项式阶数
reg_lambda = 0.0001 # 正则化系数

X_train_transformed = polynomial_basis_function(X_train.reshape([-1,1]), degree)
X_test_transformed = polynomial_basis_function(X_test.reshape([-1,1]), degree)
X_underlying_transformed = polynomial_basis_function(X_underlying.reshape([-1,1]), degree)

model = Linear(degree) 

optimizer_lsm(model,X_train_transformed,y_train.reshape([-1,1]))

y_test_pred=model(X_test_transformed).squeeze()
y_underlying_pred=model(X_underlying_transformed).squeeze()

model_reg = Linear(degree) 

optimizer_lsm(model_reg,X_train_transformed,y_train.reshape([-1,1]),reg_lambda=reg_lambda)#唯一区别

y_test_pred_reg=model_reg(X_test_transformed).squeeze()
y_underlying_pred_reg=model_reg(X_underlying_transformed).squeeze()

mse = mean_squared_error(y_true = y_test, y_pred = y_test_pred).item()
print("mse:",mse)
mes_reg = mean_squared_error(y_true = y_test, y_pred = y_test_pred_reg).item()
print("mse_with_l2_reg:",mes_reg)

# 绘制图像
plt.scatter(X_train, y_train, facecolor="none", edgecolor="#e4007f", s=50, label="train data")
plt.plot(X_underlying, y_underlying, c='#000000', label=r"$\sin(2\pi x)$")
plt.plot(X_underlying, y_underlying_pred, c='#e4007f', linestyle="--", label="$deg. = 8$")
plt.plot(X_underlying, y_underlying_pred_reg, c='#f19ec2', linestyle="-.", label="$deg. = 8, \ell_2 reg$")
plt.ylim(-1.5, 1.5)
plt.annotate("lambda={}".format(reg_lambda), xy=(0.82, -1.4))
plt.legend(fontsize='large')
# plt.savefig('ml-vis4.pdf')
plt.show()

运行结果如下: 

mse: 0.2483164519071579
mse_with_l2_reg: 0.32638686895370483

神经网络与深度学习笔记(2)——机器学习_第8张图片

和之前机器学习实战是有区别的,但是思路是一样的,不过感觉机器学习实战的代码相对简单,因为都是在调用现成的包。 

 

你可能感兴趣的:(神经网络与深度学习,机器学习)