Keras基本用法

  • 2019 年 12 月 20 日
  • 筆記

Keras是目前使用最为广泛的深度学习工具之一,它的底层可以支持TensorFlow、MXNet、CNTK和Theano。如今,Keras更是被直接引入了TensorFlow的核心代码库,成为TensorFlow官网提供的高层封装之一。下面首先介绍最基本的Keras API,斌哥给出一个简单的样例,然后介绍如何使用Keras定义更加复杂的模型以及如何将Keras和原生态TensorFlow结合起来。

1、Keras基本用法

和TFLearn API类似,Keras API也对模型定义、损失函数、训练过程等进行了封装,而且封装之后的整个训练过程和TFLearn是基本一致的,可以分为数据处理、模型定义和模型训练三个部分。使用原生态的Keras API需要先安装Keras包,安装的方法如下:

pip install keras

以下代码展示了如何使用原生态Keras在MNIST数据集上实现LeNet-5模型。

# -*- coding: utf-8 -*-    import keras  from keras.datasets import mnist  from keras.models import Sequential  from keras.layers import Dense, Flatten, Conv2D, MaxPooling2D    num_class = 10    im_rows, im_cols = 28, 28    # 通过Keras封装好的API加载MNIST数据。其中trainX就是一个60000x28x28的数组,trainY是每一张图片对应的数字。  (trainX, trainY), (testX, testY) = mnist.load_data()    # 因为不同的底层(TensorFlow或者MXNet)对输入的要求基本不一样,所以这里需要根据图像编码的格式要求来设置输入层的格式。  if K.image_data_format() == 'channels_first':     trainX = trainX.reshape(trainX.shape[0], 1, img_rows, img_cols)     testX = testX.reshape(testX.shape[0], 1, img_rows, img_cols)     # 因为MNIST中的图片是黑白的,所以第一维的取值为1,     input_shape = (1, img_rows, img_cols)  else:     trainX = trainX.reshape(trainX.shape[0], img_rows, img_cols, 1)     testX = testX.reshape(testX.shape[0], img_rows, img_cols, 1)     input_shapes = (img_rows, img_cols, 1)  # 将图像像素转化为0到1之间的实数。  trainX = trainX.astype('float32')  testX = testX.astype('float32')  trainX /= 255.0  testX  /= 255.0    # 将标准答案转化为需要的格式(one-hot编码)。  trainY = keras.utils.to_categorical(trainY, num_classes)  testY = keras.utils.to_categories(testY, num_classes)    # 使用Keras API定义模型。  model = Sequential()    # 一层深度为32,滤波器大小为5x5的卷积层。  model.add(MaxPooling2D(pool_size=(2,2)))  # 一层深度为64,滤波器大小为5x5的卷积层。  model.add(Conv2D(64, (5,5), activation='relu'))  # 一层滤波器大小为2x2的最大池化层。  model.add(MaxPooling2D(pool_size=(2,2)))  # 将卷积层的输出拉直后作为下面全连接层的输入。  model.add(Flatten())  # 全连接层,有500个节点。  model.add(Flatten())  # 全连接层,有500个节点。  model.add(Dense(500, activate='relu'))  # 全连接层,得到最后的输出  model.add(Dense(num_classes, activate='softmax'))    # 定义损失函数、优化函数和测评方法。  model.compile(loss=losses.categorical_crossentropy,                optimazer = keras.optimizer.SGD()                metric = ['accuracy'])  # 类似TFLearn中的训练过程,给出训练数据、Batch大小、训练论事和验证数据,Keras可以自动完成模型训练过程。  model.fit(trainX, trainY, batch_size=128, epochs=20, validation_data=(testX, testY))  #在测试数据上计算准确率。  score = model.evaluate(testX, testY)  print('Test loss:',score[0])  print('Test accuracy', score[1])

从以上代码中可以看出使用Keras API训练模型可以先定义一个Sequential类,然后在Sequential实例中通过add函数添加网络层。Keras把卷积层、池化层、RNN结构(LSTM、GRU),全连接层等常用的神经网络结构都做了封装,可以很方便地实现深层神经网络。在神经网络结构定义好之后,Sequential实例可以通过compile函数,指定优化函数、损失函数以及训练过程中需要监控等指标。Keras对优化函数、损失函数以及监控指标都有封装,同时也支持使用自定义的方式,在Keras的API文档中有详细的介绍,这里不再赘述。最后在网络结构、损失函数和优化函数都定义好之后,Sequential实例可以通过fit函数来训练模型。类似TFLearn中的fit函数,Keras的fit函数只需给出训练数据,batch大小和训练轮数,Keras就可以自动完成模型训练的整个过程。

除了能够很方便地处理图像问题,Keras对训练神经网络的支持也是非常出色的。有了Keras APA,循环神经网络的训练体系也可以通过简单的一句命令完成。以下代码给出了如何通过Keras实现自然语言感情分类问题。使用循环网络判断语言的感情(比如在以下例子中需要判断一个评价是好评还是差评)和自然语言建模问题类似,唯一的区别在于除了最后一个时间点的输出是有意义的,其他时间点的输出都可以忽略,下图展示了使用循环网络处理感情分析问题的模型结构。

# -*- coding: utf-8 -*-    from keras.preprocessing import sequence  from keras.models import Sequential  from keras.layers import Dense, Embedding  from keras.layers import LSTM  from keras.datasets import imdb    # 最多使用的单词数。  max_features = 20000  # 循环神经网络的截断长度。  maxlen = 80  batch_size = 32  # 加载数据并将单词转化为ID, max_features给出了最多使用的单词数。和自然语言模型类似,会将出现频率 # 较低的单词替换为统一的ID,通过Keras封装的API生成25000条训练数据和25000条测试数据,每一条数据可以  # 摆看成一段话,并且每段话都有一个好评或者差评的标签。  (trainX, trainY), (testX, testY) = imdb.load_data(num_words=max_features)  print(len(trainX), 'train sequences')  print(len(trainY), 'test_sequences')    # 在自然语言处理中,每一段话的长度都是不一样的,但循环神经网络的循环长度是固定的,所以这里需要首先  # 将所有段落统一成固定长度。对于长度不够的段落,要使用默认值0来填充,对于超过长度  # 的段落则直接忽略掉超过的部分。  trainX = sequence.pad_sequences(trainX, maxlen = maxlen)  testX  = sequence.pad_sequences(testX, maxlen=mexlen)    # 输出统一长度之后的数据维度:  # ('x_train shape:', (25000, 80))  # ('x_test shape:', (25000, 80))  print('trainX shape:', trainX.shape)  print('testX shape:', trainX.shape)    # 再完成数据预处理之后的模型结构  model = Sequential()  # 构建embedding层。128代表了embedding层的向量维度。  model.add(Embedding(max_features, 128))  # 构建LSTM层。  model.add(LSTM(128,dropout=0.2))  # 构建最后的全连接层。注意在上面构建LSTM层时只会得到最后一个节点输出,  # 如果需要输出每个时间点的结果,那么可以将return_sequence参数设置为true。  model.add(Dense(1, activation='sigmoid'))    # 与MNIST样例类似地指定损失函数,优化函数和评测指标。  model.compile(loss='binary_crossentropy',optimizer='adam',metrics=['accuracy'])  # 与MNIST样例类似地指定训练数据,训练轮数,batch大小以及验证数据。  model.fitt(trainX, trainY, batch_size=batch_size, epochs=15, validation_data=(testX, testY))    # 在测试数据上评测模型  score = model.evaluate(testX, testY, batch_size=batch_size)  print('Test loss:', score[0])  print('Test accuracy:',score[1])

以上两个样例针对Keras的基本用法做了详细的介绍。虽然Keras的封装,很多经典的神经网络结构能很快地被实现,不过要实现一些更加灵活的网络结构、损失函数或者数据输入方法,就需要对Keras的高级用法有更多的了解。

2、Keras高级用法

上面样例中最重要的封装就是Sequential类,所有的神经网络定义和训练都是通过Sequential实例来实现的。然而从这个类的名称可以看出,它只支持顺序模型的定义。类似Inception这样的模型结构,通过Sequential类就不容易直接实现了。为了支持更加灵活的模型定义方法,Keras支持以返回值的形式定义网络层结构。以下代码展示了如何使用这种方式定义模型。

# -*- coding:utf-8 -*-    import keras  from keras.datasets import mnist  from keras.layers import Input, Dense  from keras.models import Model    # 使用1中介绍的类似方法生成trainingX、trainingY、testX、testY,唯一的  # 不同是这里只使用了全连接层,所以不需要将输入整理成三维矩阵。    ...    # 定义输入,这里指定的维度不用考虑batch大小。  inputs = Input(shape=(784,))  # 定义一层全连接,该层有500隐藏节点,使用ReLU激活函数,这一层的输入为inputs。  x = Dense(500, activate='relu')(inputs)  # 定义输出层。注意因为keras封装需要指定softmax作为激活函数。  predictions = Dense(10, activate='softmax')(x)    # 通过Model类创建模型,和Sequential类不同的是Model类在初始化的时候需要指定模型的输入和输出。  model = Model(inputs=inputs, outputs=predictions)    # 与1中类似的方法定义损失函数、优化函数和评测方法。  model.complie(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.SGD(),metrics=['accuracy'])    # 与1中类似的方法训练模型。  model.fit(trainX, trainY, batch_size=128, epoches=20, validation_data=(testX, testY))

通过这样的方式,Keras就可以实现类似Inception这样大的模型结构。以下代码展示了如何通过Keras实现Inception结构。

from keras.layers import Conv2D, MaxPooling2D, Input    # 定义输入图像尺寸  input_img = Input(shape=(256,256,3))    # 定义第一个分支。  tower_1 = Conv2D(64,(1,1),padding='same',activation='relu')(input_img)  tower_1 = COnv2D(64,(3,3),padding='same',activation='relu')(tower_1)    # 定义第二个分支。与顺序模型不同,第二个分支的输入使用的是input_img,而不是第一个分支的输出。  tower_2 = Conv2D(64,(1,1),padding='same',activation='relu')(input_img)  tower_2 = Conv2D(65,(5,5),padding='same',activation='relu')(tower_2)    # 定义第三个分支。类似地,第三个分支的输入也是input_img,  tower_3 = MaxPooling2D((3,3),strides=(1,1),padding='same')(input_img)  tower_3 = Conv2D(64, (1,1), padding='same', activation='relu')(tower_3)    # 将第三个分支通过concatenate的方式拼接在一起  output = keras.layers.concatenate([tower_1, tower_2, tower_3], axis = 1)

除了可以支持顺序模型,Keras也可以支持有多个输入或者输出的模型。以下代码实现了下图所示的网络结构。

# -*- coding: utf-8 -*-    import keras  from tflearn,layers.core import fully_connected  from keras.datasets import mnist  from keras.layers import Input, Dense  from keras.models import Model    # 类似1的方式生成trainX, trainY, testX, testY  # 定义两个输入,一个输入为原始的图片信息,另一个输入为正确答案。  input1 = Input(shape=(784,), name="input1")  input2 = Input(shape=(10,), name="input2")    # 定义一个只有一个隐藏节点的全连接网络。  x = Dense(1, activation='relu')(input1)  # 定义只使用了一个隐藏节点的网络结构的输出层。  output1 = Desnse(10, activation='softmax',name="output1")(x)    # 将一个隐藏节点的输出和正确答案拼接在一起,这个将作为第二个输出层的输入。  y = keras.layers.concatenate([x, input2])  # 定义第二个输出层。  input3 = Dense(10, activation='softmax', name = "output2")(y)    # 定义一个有多个输入和多个输出的模型。这里只需要将所有的输入和输出给出即可。  model = Model(inputs=[inputs1, inputs2], outputs = [output1, output2])    # 定义损失函数、优化函数和测评方法。若多个输出的损失函数相同,可以只指定一个损失函数。  # 如果多个输出的损失函数不同,则可以通过一个列表或一个字典来指定每一个输出的损失函数。  # 比如可以使用:  # loss = {'output1':binary_crossentropy,'output2':binary_crossentropy}  # 求为不同的输出指定不同的损失函数。类似地,Keras也支持为不同输出产生的损失指定权重,  # 这可以通过loss_weights参数来完成。在下面的定义中,输出output1的权重为1,output2的  # 权重为0.1,所以这个模型会更加偏向于优化的第一个输出。    model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.SGD(), loss_weights = [1,0.1], metrics = ['accuracy'])    # 模型训练过程。因为有两个输入和输出,所以这里提供的数据也需要有两个输入和两个期待的正确答案输出。  # 通过列表的方式提供数据时,Keras会假设数据给出的顺序和定义Model类时输入会给出的顺序是对应的。为 # 了避免顺序不一致导致的问题,推荐使用字典的形式给出:  #  model.fit(  #          {'input1':trainX, 'input2':trainY}  #          {'output1':trainX, 'output2':trainY}  #          ...)    model.fit([trainX, trainY], [trainY, trainY], batch_size=128, epochs=20, validation_data=([testX, testY], [testX, testY]))

从以上输出可以看出Keras在训练过程中会展示每个输出层的loss和accuracy。因为输出层output1只使用了一个维度为1的隐藏点,所以正确率只有29.85%。虽然输出层output2使用了正确答案作为输入,但是因为在损失函数中权重较低(只有0.1),所以它的收敛速度较慢,在20个epoch时准确率也只有92.1%。如果将两个输出层的损失权重设为一样,那么输出层output1在20个epoch时的准确率将只有27%,而输出层output2的准确率可以达到99.9%。虽然通过返回值的方式已经可以实现大部分的神经网络模型,然而Keras API还存在两大问题。第一,原生态Keras API对训练数据的处理流程支持得不太好,基本上需要一次性将数据全部全部加载到内存。第二,原生态Keras API无法支持分布式训练。为了解决这两个问题,Keras提供了一种与原生态TensorFlow结合地更加紧密的方式。以下代码显示了如何将Keras和原生态TensorFlow API联合起来解决MNIST问题。

# -*- coding: utf-8 -*-    import tensorflow as tf  from tensorflow.example.tutorials.mnist import input_data    mnist_data=input_data.read_data_sets('/path/to/MNIST_data', one_hot=True)    # 通过TensorFlow中的placeholder定义输入。类似的,Keras封装数据的网络层结构也可以支持队列输入。  # 这样可以有效避免一次性加载所有数据的问题。    x = tf.placeholder(tf.float32, shape=(None, 764))  y_ = tf.placeholder(tf.float32, shape=(None, 10))      # 直接使用TensorFlow中提供的Keras API定义网络层结构。  net = tf.keras.layers.Dense(500, activate='relu')(x)  y = tf.keras.layers.Dense(10, activation='softmax')(net)    # 定义损失函数和优化方法。注意这里可以混用Keras的API和原生态TensorFlow的API。  loss = tf.reduce_mean(tf.keras.losses.categorical_crossentropy(y_, y))  train_step = tf.train.GradientDescentOptimizer(0.5).minimize(loss)    # 定义正确的预测率作为指标。  acc_value = tf.reduce_mean(tf.keras.metric.categorical_accuracy(y_, y))    # 使用原生态TensorFlow的方式训练模型。这样就可以有效的实现分布式。  with tf.Session() as sess:     tf.global_variables_initializer().run()     for i in range(10000):         xs, ys = mnist_data.train.next_batch(100)         _, loss_value = sess.run([train_step, loss], feed_dict={x:xs, y_:ys})           if i % 1000 == 0:            print("After %d training step(s), loss on training batch is " "%g." % (i,  loss_value))     print acc_value.eval(feed_dict={x:mnist_data.test.images, y_: mnist_data.test.labels})