keras其实是高度封装的一个神经网络模块,优点就是可以很方便的进行开发,缺点就是很多情况下只能用现成的Layer去构建模型,比如我需要用神经网络去进行控制,那么在控制量和输出量两层中间,使用keras是很难实现误差的反向传播(Back Propagation)的。
由于作业要求,开始使用Paddle进行神经网络搭建,从一头雾水到现在还是一头雾水,总得写点东西(其实是作业要求) 去记录一下,万一以后开发会用到呢(狗头)。
这一部分使用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'))
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])
#将其相加
数据集划分主要是为了防止过拟合(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来说的。
这一部分是代码中没有的,但是也还是说一下(虽然我在paddle中没有实现)。
# 绘制训练 & 验证的损失值
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()
keras中可以使用model.evaluate(X,Y)来对X,Y进行loss和评估标准函数的计算;如果要进行应用,可以使用model.predict(X)方法。
常用的模型评估标准主要就是loss和accuracy,但是有时候会存在过拟合、不对称分类的情况,还会对交叉验证集和训练集的loss进行对比,判断高方差、高偏差的情况去区分过拟合还是欠拟合;使用召回率、查准率、F值对过拟合和不对称分类进行评估。
其实上面基本都是废话,下面才是比较重要的,个人认知存在一定的问题,还请见谅。
下面的记录基本上是按照上面对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的这篇的思路。
模型的构建、配置、训练很多博文/官方例程都是在一起的,因此参考文献直接放到最后了。
然后是搭建自己的模型,在网上搜集到的主要是两种方式:
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
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的错误,可以参考。