Paddle模型搭建-从keras转换为Paddle

瞎扯淡的部分

keras其实是高度封装的一个神经网络模块,优点就是可以很方便的进行开发,缺点就是很多情况下只能用现成的Layer去构建模型,比如我需要用神经网络去进行控制,那么在控制量和输出量两层中间,使用keras是很难实现误差的反向传播(Back Propagation)的。
由于作业要求,开始使用Paddle进行神经网络搭建,从一头雾水到现在还是一头雾水,总得写点东西(其实是作业要求) 去记录一下,万一以后开发会用到呢(狗头)。

Keras模型文件

数据生成

这一部分使用numpy生成数据,可直接搬到Paddle中。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

#================================
#准备数据
N = 100  #每个类别100个样本
D = 2  #维度
K = 3  #3类
X = np.zeros((N*K,D))  #一个300乘2的矩阵
y = np.zeros((N*K),dtype='uint8')

for j in range(K):
    ix = list(range(N*j, N*(j+1)))
    r = np.linspace(0.0,1,N)
    t = np.linspace(j*4,(j+1)*4,N) + np.random.randn(N)*0.2

    X[ix] = np.c_[r*np.sin(t), r*np.cos(t)]  #对矩阵每个元素取正弦
    y[ix] = j

# 展示数据
x1 = X
x2 = x1[:100]
x2 = x2.T
y2 = y[:100]
x3 = x1[100:200]
x3 = x3.T
y3 = y[100:200]
x4= x1[200:]
x4 = x4.T
y4 = y[200:]
plt.figure(figsize=(10, 10))
plt.plot(x2[0],x2[1],'o')
plt.plot(x3[0],x3[1],'o')
plt.plot(x4[0],x4[1],'o')
plt.show()

定义模型

这一部分开始定义模型,主要是使用Keras的两种定义模型的方式:

from keras.layers import *
from keras.models import Model

from sklearn.model_selection import train_test_split
#sklearn用来划分训练集和交叉验证集
# 使用sequential定义两层模型
model_in = Input(shape=(2,))
model_out = Dense(10,input_dim=(2),activation='relu')(model_in)
model_out = Dense(3,activation='softmax')(model_out)
model = Model(model_in,model_out)

# model = Sequential()
# model.add(Dense(10, input_shape=(2, )))
# model.add(Activation('relu'))
# model.add(Dense(3))
# model.add(Activation('softmax'))
  1. sequential序列模型。这种方式可构建简单的神经网络模型,只需要加入层、加入激活函数,一层一层按顺序叠加即可完成,但是无法实现多输入/多输出/残差网络。
  2. 使用Model类进行创建。这种方式一般是使用函数来进行封装,返回一个Model对象,要实例一个Model类,需要指定Input和Output层。这种方式可用进行残差块的构建、多输入多输出神经网络的搭建。
    搭建多输入/多输出网络可以参考这篇博文
    下面是残差块的实现,主要是使用Add()方法,这里是截取了一部分Xception网络的代码。
    注意这里是用的Keras的Add(),相类似的还有一个concatenate,这两个并不相同,可以参考这篇博文
    residual = Conv2D(256, (1, 1), strides=(2, 2),padding='same', use_bias=False)(x)
    residual = BatchNormalization(axis=channel_axis)(residual)
    #构建短跳连接,因为原始X和主线路输出X的大小不同,因此需要进行卷积 
    x = Activation('relu', name='block3_sepconv1_act')(x)
    x = SeparableConv2D(256, (3, 3),padding='same',use_bias=False,name='block3_sepconv1')(x)
    x = BatchNormalization(axis=channel_axis, name='block3_sepconv1_bn')(x)
    x = Activation('relu', name='block3_sepconv2_act')(x)
    x = SeparableConv2D(256, (3, 3),padding='same', use_bias=False,name='block3_sepconv2')(x)
    x = BatchNormalization(axis=channel_axis, name='block3_sepconv2_bn')(x)
    x = MaxPooling2D((3, 3), strides=(2, 2),padding='same',name='block3_pool')(x)
    #构建主连接
    x = Add()([x, residual])
    #将其相加
  1. 有一个需要注意的点就是keras的全连接层Dense是默认使用偏置神经元bias的,即参数use_bias=true,如果要在其他的平台实现需要注意这一点。

数据集划分

数据集划分主要是为了防止过拟合(overfit),一般来说会划分为训练集(60%)、交叉验证集(20%)、测试集(20%),训练集用于训练模型参数,交叉验证集主要是用来检测过拟合还是欠拟合,主要通过看高方差还是高偏差的方式来判断。
这里用sklearn进行简单的划分,20%的数据用来进行交叉验证:

x_train, x_valid, y_train, y_valid = train_test_split(X, y, test_size=0.2, random_state=0)

如果使用keras中的model.fit方法,可不用手动划分,只需要指定validation_split,即划分比例即可:

history = model.fit(X,Y,validation_split=0.33, epochs=10,callbacks=[tensorboard_callback])

不用读部分:
如果使用决策融合算法,比如stacking,那么数据集划分需要进行一定的调整,常用的方法是K折交叉验证,可以使用sklearn的StratifiedKFold实现。

配置模型

我是这么叫的,其实就是model.compile(即编译)方法。
这个步骤需要指定优化器、Loss function、评估标准,然后才可以进行梯度下降和反向传播,因此在其他平台实现时主要完成这几个步骤。
这里使用RMSprop优化器,并指定其学习率为0.01(学习率的影响后面会谈到),使用多分类的交叉熵函数,评估标准为准确率。
关于评估标准可以参考keras中文文档,分类主要就是top_k准确率,也可以自定义评估函数。

# 二分类提问使用二元交叉熵(binary crossentropy)
# 多元分类,使用分类交叉熵(categorical crossentropy)
# 回归问题,使用均方差(meansquared error)
from keras import optimizers
model.compile(optimizer=optimizers.RMSprop(lr=0.01),
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

训练模型

训练模型是使用
这里使用model.fit方法,keras还提供了fit_generator方法,但是在最新的tensorflow2.x中提供的keras模块里,将fit_generator方法整合到了fit方法中。
关于fit方法可以也可以参考keras中文文档中的Model,如果使用tensorflow进行开发,则可以参考tensorflow的文档。
这里说明一下几个常用的相关参数(其实官方文档就很详细了):

fit(x=None, y=None, batch_size=None, epochs=1, verbose=1,callbacks=None, validation_split=0.0, validation_data=None,shuffle=True)

x是输入张量,y是x对应的标签/值,batch_size指定批量的大小,epochs是训练次数,verbose是训练过程的显示模式,callbaks是回调函数,可以用来配合tensorboard实现可视化,validation_spilt是交叉验证集的划分比例,validation_data则可以手动输入交叉验证集;shuffle为是否打乱数据顺序的布尔值,默认为true(打乱)。
对于作业,其x和y分别为x_train和y_train,交叉验证集为x_valid和y_valid,批量数据个数为50,训练次数为100次。

model.fit(x_train,y_train, validation_data=(x_valid, y_valid),batch_size=50,epochs=100)

ps:对于参数,其实还有一些需要注意的点,下面都是对tensorflow的keras来说的。

  1. 首先,x和y的问题。如果x是一个字典、生成器、tf的dataset(比如tfrecord)或者keras的utils.Sequence,那么y、batch_size和交叉验证集划分比例不能指定。
  2. 其次,model.fit有一个返回参数history,用于存储训练过程中训练集和交叉验证集的loss、评估函数值,可以用来进行训练过程的可视化

训练过程可视化

这一部分是代码中没有的,但是也还是说一下(虽然我在paddle中没有实现)。

  1. 使用matplotlib进行可视化。
    这种方式非常容易实现,即使不使用返回的history也能手动完成,只需要记录每个epoch的loss、accuracy,将其放入list最后显示即可。
    使用history的代码如下:loss是训练集的损失函数值,val_loss是交叉验证集的损失函数值。
# 绘制训练 & 验证的损失值
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Model loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Train', 'Test'], loc='upper left')
plt.show()
# 绘制训练 & 验证的准确率值
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('Model accuracy')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Train', 'Test'], loc='upper left')
plt.show()

  1. 使用tensorboard进行可视化。
    这种方式需要在fit时指定callback,具体可以参考https://www.jianshu.com/p/321eb9d195cc

模型评估

keras中可以使用model.evaluate(X,Y)来对X,Y进行loss和评估标准函数的计算;如果要进行应用,可以使用model.predict(X)方法。
常用的模型评估标准主要就是loss和accuracy,但是有时候会存在过拟合、不对称分类的情况,还会对交叉验证集和训练集的loss进行对比,判断高方差、高偏差的情况去区分过拟合还是欠拟合;使用召回率、查准率、F值对过拟合和不对称分类进行评估。

转换为Paddle模型的过程记录

其实上面基本都是废话,下面才是比较重要的,个人认知存在一定的问题,还请见谅。
下面的记录基本上是按照上面对keras的划分进行的,使用paddle实现的部分都附上了参考文章。

数据生成与划分

实在是不好分开,因此就划到一个部分来了,这部分也可以叫数据预处理。
首先先把完整数据集弄出来,这里基本就没改动:

#生成数据X,Y
N = 100  #每个类别100个样本
D = 2  #维度
K = 3  #3类
X = np.zeros((N*K,D),dtype = 'float32')  #一个300乘2的矩阵
y = np.zeros((N*K),dtype='int64')#paddle要求形式为float32和int64,因此需要修改

for j in range(K):
    ix = list(range(N*j, N*(j+1)))
    r = np.linspace(0.0,1,N)
    t = np.linspace(j*4,(j+1)*4,N) + np.random.randn(N)*0.2

    X[ix] = np.c_[r*np.sin(t), r*np.cos(t)]  #对矩阵每个元素取正弦
    y[ix] = j


#划分数据集为训练集和交叉验证集,其实是不知道怎么用paddle划分
x_train, x_valid, y_train, y_valid = train_test_split(X, y, test_size=0.2, random_state=0)

对于Paddle,类似于Pytorch,我们需要自己手动实现mini-batch,即手动实现一个将数据划分为多个批次的生成器(只有需要下一步时才会产生数据,这样可以节省内存),这里需要简单了解一些python的yield语句和装饰器。
注意: numpy的array在paddle中无法直接用于训练,所以这个reader其实并不完整,但是我懒。

#创建一个数据读取函数,返回一个generator,用于minibatch的训练
def read_creator(data:np.array,label:np.array,batch_size = 50):
    #默认传入的data和label为array,其行为数据个数
    length = data.shape[0]
    #产生索引列表,用于后续的打乱
    index_list = list(range(length))

    # 定义数据生成器
    def data_generator():
        #打乱数据
        random.shuffle(index_list)
        x_list = []
        y_list = []
        # 按照索引读取数据
        for i in index_list:
            # 列表添加数据
            x_list.append(data[i]) 
            y_list.append(label[i])
            # 如果当前数据缓存达到了batch_size,就返回一个批次数据
            if len(x_list) == batch_size:
                yield np.array(x_list), np.array(y_list)
                # 清空数据缓存列表
                x_list = []
                y_list = []

        # 如果剩余数据的数目小于batchsize,
        # 则剩余数据一起构成一个大小为len(imgs_list)的mini-batch
        if len(x_list) > 0:
            yield np.array(x_list), np.array(y_list)
    return data_generator

关于这部分的参考资料:

官方的简单实现:官方的简单实现使用paddle.batch对生成器进行批量的划分,我们不需要实现批量的部分,只用单独完成返回单个数据的生成器即可。
官方进阶指南中的说明:这部分包括静态图和动态图,说实在的我当时没看这些…reader是一样的,但是最后的操作有些不太一样,一个是DataLoader,另一个是DataReader(静态)。
官方实现线性回归模型中的一部分代码:这部分直接看从txt文件中读取数据。
还有这篇博文,不用官方的DataLoader和DataReader直接使用自己的生成器进行训练,我的训练代码也是copy的这篇的思路。

模型构建

模型的构建、配置、训练很多博文/官方例程都是在一起的,因此参考文献直接放到最后了。
然后是搭建自己的模型,在网上搜集到的主要是两种方式:

  1. 使用类,创建一个fluid.dygraph.Layer的子类,这个类似于Pytorch的实现,应该是动态图方法。
class MyLayer(fluid.dygraph.Layer):
    def __init__(self):
        super(MyLayer, self).__init__()
        #父类初始化
        self.linear = fluid.dygraph.nn.Linear(MNIST_LABLE_SIZE, 10)
		#动态图的Linear层,用来实现FC_Layer
    def forward(self, inputs, label=None):
       #使用这种方式需要手动实现前向传播
        x = self.linear(inputs)
        if label is not None:
        #如果给定y,可以同时获取准确率和loss值
            loss = fluid.layers.cross_entropy(x, label)
            avg_loss = fluid.layers.mean(loss)
            return x, avg_loss
        else:
            return x
  1. 这种实现类似于Keras,应该是静态图方法?
image = fluid.data(name="image", shape=[None, 784], dtype='float32')
label = fluid.data(name="label", shape=[None, 1], dtype='int64')
hidden = fluid.layers.fc(input=image, size=100, act='relu')
prediction = fluid.layers.fc(input=hidden, size=10, act='softmax')

模型配置

模型配置要定义优化器、loss function、评估标准,后面两个部分其实可以在上面模型搭建的前向传播部分直接完成,但是这里还是写一下。
首先是优化器,在fluid的optimizer中,各个优化器可以参考官方文档。需要注意的是,动态图模式下必须要对parameter_list进行设置,静态图下该参数的值默认为None;另外,正则化项是在优化器中指定的,具体通过设定regularization参数实现。

optimizer = fluid.optimizer.AdamOptimizer(learning_rate=0.01,parameter_list=model.parameters())
#optimizer = fluid.optimizer.Adagrad(regularization=fluid.regularizer.L2DecayRegularizer(regularization_coeff=0.1))
#需要说明训练参数,调节学习率η为0.01

然后是评估函数和loss function,这两个常用的函数都在fluid.layer当中,可以直接调用.
首先是损失函数(其实应该叫优化函数?),需要注意的一点就是要对loss取平均才是我们需要进行梯度下降的那个loss:

#计算损失,并取一个批次样本损失的平均值
loss = fluid.layers.cross_entropy(predict,y_data)
avg_loss = fluid.layers.mean(loss)

关于准确率,也是一样的操作,只有一个参数需要注意就是topk,这个是计算topk准确率,对于分类就是如果你的输出向量的前k个最大值有对应的类别,那么就算是正样本。

 acc = fluid.layers.accuracy(input=outputs, label=label,topk=1)

训练模型

训练模型其实就是一个反向传播的过程,对于某个batch,反向传播的代码为:

avg_loss.backward()
optimizer.minimize(avg_loss)
model.clear_gradients() 

整个循环的代码如下:

with fluid.dygraph.guard():
    #实例化搭建的模型
    model = PDModel()
    model.train()
    #父类的train方法,但是必须要自己定义前向传播后才可实现
    optimizer = fluid.optimizer.AdamOptimizer(learning_rate=0.01,parameter_list=model.parameters())

    # 训练100轮
    EPOCH_NUM = 100
    for epoch_id in range(EPOCH_NUM):
        #进行minibatch训练
        for batch_id, data in enumerate(train_loader()):
            #从生成器中读取批量的数据
            x_data, y_data = data
            #转换为paddle中的tensor
            x_data = fluid.dygraph.to_variable(x_data)
            y_data = fluid.dygraph.to_variable(y_data)
            #前向计算的过程,获取模型输出值
            #如果要同时获取acc,那么必须在前向传播中加入y的输入,并利用layer.accuracy实现acc计算
            predict= model(x_data)
            #计算损失,并取一个批次样本损失的平均值
            loss = fluid.layers.cross_entropy(predict,y_data)
            avg_loss = fluid.layers.mean(loss)
            
            #反向传播,更新参数的过程
            avg_loss.backward()
            optimizer.minimize(avg_loss)
            model.clear_gradients() 

因为我们的数据生成器返回的numpy的数组,不能直接扔进去训练,需要进行转换,否则会报错,paddle的张量数据类型主要是flaot32和int64,而numpy经常是int8,所以可能会产生Tensor holds the wrong type的错误,可以参考。

参考资料:

  • 动态图参考资料:
    – 官方的动态图机制说明:不仅构建了模型,还实现配置和训练、保存参数。
    – 官方进阶指南中数据预处理(动态图)部分:也是包含了训练部分,但是训练的方式和上面的动态图有所不同。
    – 上面的百度PaddlePaddle入门-10(数据处理)
  • 静态图参考资料
    – 官方文档的单机训练
    – 官方文档线性回归教程
    – 官方文档数字识别教程:可以详细看线性回归和数字识别的教程,后一个教程较为复杂,除了网络的搭建还有一个就是增加了可视化的过程。

你可能感兴趣的:(CV,python,机器学习)