Keras Basic Usage

Keras Basic Usage

Keras是一个基于Python的深度学习库,是一个高级的神经网络API,可以运行在Tensorflow, CNTK, Theano等深度学习框架的顶层。对于研究者来说,使用Keras可以快速开发和构建深度学习模型。

tf.keras是Google根据Keras API进行的Tensorflow版本的实现。官网上给出了一个非常简短的例子来介绍Keras的使用方法,我们来看一下:

首先check一下tensorflow和keras的版本

Python 3.6.8 |Anaconda, Inc.| (default, Dec 29 2018, 19:04:46)
[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import tensorflow as tf
>>> tf.__version__
'1.12.0'
>>> tf.keras.__version__
'2.1.6-tf'

接下来我们运行一下TF官网上给出的Demo

from __future__ import absolute_import, division, print_function, unicode_literals
import tensorflow as tf
# Load mnist dataset
mnist = tf.keras.datasets.mnist 
# 获取训练集和测试集
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# 数据预处理,此处利用到了归一化
x_train, x_test = x_train / 255.0, x_test / 255.0
# 搭建神经网络
model = tf.keras.models.Sequential([
  tf.keras.layers.Flatten(input_shape=(28, 28)),
  tf.keras.layers.Dense(128, activation='relu'),
  tf.keras.layers.Dropout(0.2),
  tf.keras.layers.Dense(10, activation='softmax')
])
# 选择优化器,损失函数和模型度量方法
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])
# 模型拟合
model.fit(x_train, y_train, epochs=5)
# 模型评价
model.evaluate(x_test, y_test)

如果读者之前没有运行过这段代码,那么首先这段代码会下载mnist,之后便是模型训练的结果。

Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 152s 13us/step
Epoch 1/5
2019-07-13 21:38:59.927952: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
60000/60000 [==============================] - 5s 77us/step - loss: 0.3011 - acc: 0.9134
Epoch 2/5
60000/60000 [==============================] - 4s 60us/step - loss: 0.1430 - acc: 0.9581
Epoch 3/5
60000/60000 [==============================] - 4s 61us/step - loss: 0.1068 - acc: 0.9673
Epoch 4/5
60000/60000 [==============================] - 4s 61us/step - loss: 0.0868 - acc: 0.9739
Epoch 5/5
60000/60000 [==============================] - 4s 63us/step - loss: 0.0749 - acc: 0.9769

10000/10000 [==============================] - 0s 29us/step
[0.07364166865674779, 0.9761]

可以看到,模型在训练了5个epoch之后,就达到了0.9769的精度,在验证集上的精度也达到了0.9761的精度。我们搭建了一个简易的带有Dropout功能的全连接网络,就能在mnist数据集上达到不俗的精度。而且,上面这段代码并不难理解。

显然地,对于演示Demo来讲,这段代码确实简单。但是在实际项目生产中,有很多需要注意的点,本文将从以下几个部分来说明Keras的使用方法与需要注意的点。主要是:1. 版本问题;2. 数据加载;3. 模型构建;4. 损失函数,优化器,度量方法选择;5. 模型拟合。

版本问题

TF是公认的向下兼容性比较差,这也就意味着高版本的代码大多数情况下无法在低版本的TF框架下运行,或多或少地都需要改变一些代码的细节,这个问题一直被TF的使用者所诟病。所以,在使用TF前,读者应该要仔细Check框架版本,以免到时候出现一些莫名其妙的问题。

如果需要使用GPU,除了CheckTF的版本之外,还需要注意计算机上所安装的CUDA版本与cuDNN的版本。以下是tf-gpu的版本所对应的CUDA和cuDNN版本。更多细节可查看https://www.tensorflow.org/install/source#linux

[外链图片转存失败(img-pmja0hOe-1568211756503)(./images/cuda_version_check.png)]

查看TF和Keras版本的方法我们在上面已经给出了,下面给出查看本机CUDA和cuDNN版本的方法。如果没有安装CUDA,则下面的命令是无效的。

# 一般来说,CUDA会安装在/usr/local文件夹下面
$ cat /usr/local/cuda/version.txt
# CUDA Version 9.0.176
# CUDA Patch Version 9.0.176.1
# CUDA Patch Version 9.0.176.2
# CUDA Patch Version 9.0.176.3
# CUDA Patch Version 9.0.176.4
# 查看cuDNN版本
$ cat /usr/local/cuda/include/cudnn.h | grep CUDNN_MAJOR -A 2
# #define CUDNN_MAJOR 7
# #define CUDNN_MINOR 5
# #define CUDNN_PATCHLEVEL 0
# --
# define CUDNN_VERSION (CUDNN_MAJOR * 1000 + CUDNN_MINOR * 100 + CUDNN_PATCHLEVEL)

# #include "driver_types.h"

确保TF-GPU支持CUDA和cuDNN版本之后,我们就可以进行模型编写与训练工作。当然,如果读者不需要使用GPU,那么只需CheckTF的版本即可。

数据加载

在TF官网上给出的Demo里面,我们看到数据加载就只有一行mnist.load_data(),实际上这只是对mnist数据集进行的一个封装。在实际应用中,数据来源可能是多种多样的,这里以Figaro1k发型分类数据为例,说明数据构建和加载的基本流程。在后续,笔者会给出适用于更多形式和更多场景的数据构建和加载方式。

对图片分类问题而言,Keras的数据加载方式比原生TF简单太多。读者只需要两步操作就可以加载图片。
Step 1. 构建图片数据的生成器

from functools import partial
from keras_preprocessing.image import ImageDataGenerator
train_datagen = ImageDataGenerator(preprocessing_function=partial(resnet50.preprocess_input, data_format='channels_last'),
                                   rotation_range=20,
                                   width_shift_range=0.2,
                                   height_shift_range=0.2,
                                   horizontal_flip=True,
                                   vertical_flip=True,
                                   featurewise_center=False,
                                   samplewise_center=False,
                                   featurewise_std_normalization=False,
                                   samplewise_std_normalization=False,
                                   zca_whitening=False,
                                   zca_epsilon=1e-6,
                                   brightness_range=None,
                                   shear_range=0.,
                                   zoom_range=0.,
                                   channel_shift_range=0.,
                                   fill_mode='nearest',
                                   cval=0.,
                                   rescale=None,
                                   data_format='channels_last',
                                   validation_split=0.0,
                                   interpolation_order=1,
                                   dtype='float32'
                                   )

我们看到,该段代码建立了一个ImageDataGenerator对象,这个对象有非常多的参数,大部分的参数是图片在训练前的一些预处理工作,比如图片增强,归一化等等,其中preprocessing_function参数是用户指定的图片预处理方法。

Step 2. 指定数据读取方式

train_generator = train_datagen.flow_from_directory(directory=train_data_path,
                                                    target_size=(224, 224),
                                                    color_mode='rgb',
                                                    classes=None,
                                                    class_mode='categorical',
                                                    batch_size=batch_size,
                                                    shuffle=True,
                                                    seed=None,
                                                    save_to_dir=None,
                                                    save_prefix='',
                                                    save_format='jpg',
                                                    follow_links=False,
                                                    subset=None,
                                                    interpolation='nearest')

上述代码指定数据从哪里获取,flow_from_directory指定数据是从文件夹中获取的,其中directory参数指定文件夹的路径,需要注意的是,在图片分类问题中,这个函数会自动的搜索train_data_path下的子文件夹,并认为不同的子文件夹下的图片属于不同的类。

[外链图片转存失败(img-SIyKNuwt-1568211756512)(/Users/wuyanxue/Documents/技术笔记/Tensorflow/KerasBasicUsage/images/flow_from_directory.png)]

上图表示了图片文件的存储结构,读者在使用这个函数进行图片分类的时候,只需要将图片按照上述方式组织即可。通过上述两行代码,我们就构建好了训练数据的生成器。验证数据的构建过程和上述过程类似,但需要注意的是,验证过程是不需要对图片进行增强的。

所以验证数据一般通过如下手段进行加载。


val_datagen = ImageDataGenerator(preprocessing_function=partial(resnet50.preprocess_input, data_format='channels_last'), featurewise_center=False,
                 samplewise_center=False,
                 featurewise_std_normalization=False,
                 samplewise_std_normalization=False,
                 zca_whitening=False,
                 zca_epsilon=1e-6,
                 rotation_range=0,
                 width_shift_range=0.,
                 height_shift_range=0.,
                 brightness_range=None,
                 shear_range=0.,
                 zoom_range=0.,
                 channel_shift_range=0.,
                 fill_mode='nearest',
                 cval=0.,
                 horizontal_flip=False,
                 vertical_flip=False,
                 rescale=None,
                 validation_split=0.0,
                 interpolation_order=1,
                 dtype='float32')

val_generator = val_datagen.flow_from_directory(directory=val_data_path,
                                                target_size=(224, 224),
                                                color_mode='rgb',
                                                classes=None,
                                                class_mode='categorical',
                                                batch_size=batch_size,
                                                shuffle=True,
                                                seed=None,
                                                save_to_dir=None,
                                                save_prefix='',
                                                save_format='jpg',
                                                follow_links=False,
                                                subset=None,
                                                interpolation='nearest')

可以看到,验证阶段是不需要对图片进行增强操作的,但仍需注意的是,验证集的图片同样需要进行预处理,以保证和训练集相同的输入形式,比如归一化等操作。验证所用的图片存储结构和训练所用的图片存储结构是一样的。

读者可根据自己的需要,来调整对输入图片的一些额外的操作。

模型构建

我们以Keras预训练的ResNet50为例,Keras的模型构建同样比原生TF要简单许多。如下所示:

# 加载ResNet50预训练模型,注意,我们移除掉了ResNet50的顶层。include_top=False
model = tf.keras.applications.ResNet50(include_top=False,
                                       weights='imagenet',
                                       input_tensor=None,
                                       input_shape=(224, 224, 3),
                                       pooling=None)

# 设置ResNet前面若干层不可被训练
for layer in model.layers[:-5]:
    layer.trainable = False

# Add output layer
new_model = models.Sequential()

new_model.add(model)
# 添加顶层以适应我们的分类问题
new_model.add(layers.Conv2D(filters=1024,
                            kernel_size=(7, 7),
                            strides=(1, 1),
                            padding='valid',
                            data_format=None,
                            dilation_rate=(1, 1),
                            activation=None,
                            use_bias=True,
                            kernel_initializer='glorot_uniform',
                            bias_initializer='zeros',
                            kernel_regularizer=None,
                            bias_regularizer=None,
                            activity_regularizer=None,
                            kernel_constraint=None,
                            bias_constraint=None))

new_model.add(layers.Flatten())

new_model.add(layers.Dense(units=1024,
                           activation=None,
                           use_bias=True,
                           kernel_initializer='glorot_uniform',
                           bias_initializer='zeros',
                           kernel_regularizer=None,
                           bias_regularizer=None,
                           activity_regularizer=None,
                           kernel_constraint=None,
                           bias_constraint=None))

new_model.add(layers.Activation(tf.nn.relu))

new_model.add(layers.Dropout(rate=0.5,
                             noise_shape=None,
                             seed=None))

new_model.add(layers.Dense(units=7,
                           activation=None,
                           use_bias=True,
                           kernel_initializer='glorot_uniform',
                           bias_initializer='zeros',
                           kernel_regularizer=None,
                           bias_regularizer=None,
                           activity_regularizer=None,
                           kernel_constraint=None,
                           bias_constraint=None))

new_model.add(layers.Activation(tf.nn.softmax))

我们看到,首先加载了ResNet50预训练模型,并去除顶层,然后在根据需要添加我们自己的层。由于Figaro1k有7个类别,所以最后模型的输出层有7个输出单元,并进行了softmax操作。

损失函数,优化器,评价方法选择

这一步整合在了模型编译步骤里面,只需下面一行代码。

new_model.compile(loss='categorical_crossentropy',
                  optimizer=optimizers.Adam(lr=1e-4),
                  metrics=['acc'])

其中,loss指定损失函数,optimizer指定优化器,metrics指定模型评价方法。

模型拟合与验证

这一步也只需要一行代码,如下所示:

history = new_model.fit_generator(generator=train_generator,
                                  steps_per_epoch=math.ceil(n_train_samples / batch_size),
                                  epochs=num_epochs,
                                  verbose=1,
                                  callbacks=[tensorboard_callback, lr_callback],
                                  validation_data=val_generator,
                                  validation_steps=math.ceil(n_test_samples / batch_size),
                                  class_weight=None,
                                  max_queue_size=10,
                                  workers=1,
                                  use_multiprocessing=False,
                                  shuffle=True,
                                  initial_epoch=0)

fit_generator方法将启动模型的训练过程与验证过程,其中generator=train_generator将训练集生成器喂给模型,validation_data=val_generator指定验证集生成器,callbacks=[tensorboard_callback, lr_callback]指定回调函数,这里我们指定了两个回调,一个是tensorboard,一个是学习率衰减的回调。学习率衰减的回调函数如下:

def lr_schedule(epoch, learning_rate):
    """
    Returns a custom learning rate that decreases as epochs progress.
    """
    # learning_rate = 1e-4
    if epoch > 5:
        learning_rate = learning_rate * 0.5
    if epoch > 10:
        learning_rate = learning_rate * 0.5
    if epoch > 15:
        learning_rate = learning_rate * 0.5
    if epoch > 20:
        learning_rate = learning_rate * 0.5
    if epoch > 25:
        learning_rate = learning_rate * 0.5
    if epoch > 30:
        learning_rate = learning_rate * 0.5
    if epoch > 35:
        learning_rate = learning_rate * 0.5
    if epoch > 40:
        learning_rate = learning_rate * 0.5

    # tf.summary.scalar('learning rate', learning_rate)
    return learning_rate


lr_callback = keras.callbacks.LearningRateScheduler(schedule=lr_schedule)

应用学习率衰减的回调后,模型在训练过程会按照用户所定义的学习率衰减的schedule进行。

Tensorboard的回调方法如下:

tensorboard_callback = keras.callbacks.TensorBoard(log_dir=logdir,
                                                   histogram_freq=0,
                                                   batch_size=32,
                                                   write_graph=True,
                                                   write_grads=False,
                                                   write_images=False,
                                                   embeddings_freq=0,
                                                   embeddings_layer_names=None,
                                                   embeddings_metadata=None,
                                                   embeddings_data=None)

非常遗憾的是,fit_generator目前对tensorboard的回调仅支持标量,但不支持histogram等向量的写入,相关issues:https://stackoverflow.com/questions/42112260/how-do-i-use-the-tensorboard-callback-of-keras/42112935https://github.com/keras-team/keras/issues/3358#issuecomment-312531958

在代码运行之后,读者可以利用

$ tensorboard --logdir=logdir

来访问Tensorboard的输出结果。

以上是Keras的基本使用过程,后续笔者会给出更多的关于Keras的细节。

完整的代码

以下为完整的代码:


from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import os

# 指定GPU
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"] = '0'

from datetime import datetime

import math

import tensorflow as tf

from tensorflow.python import keras
from tensorflow.python.keras import backend as K
from keras_preprocessing.image import ImageDataGenerator
from keras_applications import resnet50

models = keras.models
layers = keras.layers
optimizers = keras.optimizers

# set GPU memory
if 'tensorflow' == K.backend():
    config = tf.ConfigProto()
    config.gpu_options.allow_growth = True
    sess = tf.Session(config=config)

import config
from functools import partial

logdir = "/home/wuyanxue/PyCharmProjects/hairstyle_classification/logs/scalars/" + datetime.now().strftime("%Y%m%d-%H%M%S")

tensorboard_callback = keras.callbacks.TensorBoard(log_dir=logdir,
                                                   histogram_freq=0,
                                                   batch_size=32,
                                                   write_graph=True,
                                                   write_grads=False,
                                                   write_images=False,
                                                   embeddings_freq=0,
                                                   embeddings_layer_names=None,
                                                   embeddings_metadata=None,
                                                   embeddings_data=None)

# Define learning rate decay
def lr_schedule(epoch, learning_rate):
    """
    Returns a custom learning rate that decreases as epochs progress.
    """
    # learning_rate = 1e-4
    if epoch > 5:
        learning_rate = learning_rate * 0.5
    if epoch > 10:
        learning_rate = learning_rate * 0.5
    if epoch > 15:
        learning_rate = learning_rate * 0.5
    if epoch > 20:
        learning_rate = learning_rate * 0.5
    if epoch > 25:
        learning_rate = learning_rate * 0.5
    if epoch > 30:
        learning_rate = learning_rate * 0.5
    if epoch > 35:
        learning_rate = learning_rate * 0.5
    if epoch > 40:
        learning_rate = learning_rate * 0.5

    # tf.summary.scalar('learning rate', learning_rate)
    return learning_rate


lr_callback = keras.callbacks.LearningRateScheduler(schedule=lr_schedule)

# Param

num_epochs = 50
batch_size = 32
n_train_samples = 840
n_test_samples = 210

# Data

train_datagen = ImageDataGenerator(preprocessing_function=partial(resnet50.preprocess_input, data_format='channels_last'),
                                   rotation_range=20,
                                   width_shift_range=0.2,
                                   height_shift_range=0.2,
                                   horizontal_flip=True,
                                   vertical_flip=True,
                                   featurewise_center=False,
                                   samplewise_center=False,
                                   featurewise_std_normalization=False,
                                   samplewise_std_normalization=False,
                                   zca_whitening=False,
                                   zca_epsilon=1e-6,
                                   brightness_range=None,
                                   shear_range=0.,
                                   zoom_range=0.,
                                   channel_shift_range=0.,
                                   fill_mode='nearest',
                                   cval=0.,
                                   rescale=None,
                                   data_format='channels_last',
                                   validation_split=0.0,
                                   interpolation_order=1,
                                   dtype='float32'
                                   )


train_generator = train_datagen.flow_from_directory(directory=config.figaro1k_train_path,
                                                    target_size=(224, 224),
                                                    color_mode='rgb',
                                                    classes=None,
                                                    class_mode='categorical',
                                                    batch_size=batch_size,
                                                    shuffle=True,
                                                    seed=None,
                                                    save_to_dir=None,
                                                    save_prefix='',
                                                    save_format='jpg',
                                                    follow_links=False,
                                                    subset=None,
                                                    interpolation='nearest')


val_datagen = ImageDataGenerator(preprocessing_function=partial(resnet50.preprocess_input, data_format='channels_last'))

val_generator = val_datagen.flow_from_directory(directory=config.figaro1k_test_path,
                                                target_size=(224, 224),
                                                color_mode='rgb',
                                                classes=None,
                                                class_mode='categorical',
                                                batch_size=batch_size,
                                                shuffle=True,
                                                seed=None,
                                                save_to_dir=None,
                                                save_prefix='',
                                                save_format='jpg',
                                                follow_links=False,
                                                subset=None,
                                                interpolation='nearest')

# Model

model = tf.keras.applications.ResNet50(include_top=False,
                                       weights='imagenet',
                                       input_tensor=None,
                                       input_shape=(224, 224, 3),
                                       pooling=None)

for layer in model.layers[:-5]:
    layer.trainable = False

# Add output layer

new_model = models.Sequential()

new_model.add(model)

new_model.add(layers.Conv2D(filters=1024,
                            kernel_size=(7, 7),
                            strides=(1, 1),
                            padding='valid',
                            data_format=None,
                            dilation_rate=(1, 1),
                            activation=None,
                            use_bias=True,
                            kernel_initializer='glorot_uniform',
                            bias_initializer='zeros',
                            kernel_regularizer=None,
                            bias_regularizer=None,
                            activity_regularizer=None,
                            kernel_constraint=None,
                            bias_constraint=None))

new_model.add(layers.Flatten())

new_model.add(layers.Dense(units=1024,
                           activation=None,
                           use_bias=True,
                           kernel_initializer='glorot_uniform',
                           bias_initializer='zeros',
                           kernel_regularizer=None,
                           bias_regularizer=None,
                           activity_regularizer=None,
                           kernel_constraint=None,
                           bias_constraint=None))

new_model.add(layers.Activation(tf.nn.relu))

new_model.add(layers.Dropout(rate=0.5,
                             noise_shape=None,
                             seed=None))

new_model.add(layers.Dense(units=7,
                           activation=None,
                           use_bias=True,
                           kernel_initializer='glorot_uniform',
                           bias_initializer='zeros',
                           kernel_regularizer=None,
                           bias_regularizer=None,
                           activity_regularizer=None,
                           kernel_constraint=None,
                           bias_constraint=None))

new_model.add(layers.Activation(tf.nn.softmax))

# new_model.summary(line_length=None,
#                   positions=None,
#                   print_fn=None)

# Compile

new_model.compile(loss='categorical_crossentropy',
                  optimizer=optimizers.Adam(lr=1e-4),
                  metrics=['acc'])

history = new_model.fit_generator(generator=train_generator,
                                  steps_per_epoch=math.ceil(n_train_samples / batch_size),
                                  epochs=num_epochs,
                                  verbose=1,
                                  callbacks=[tensorboard_callback, lr_callback],
                                  validation_data=val_generator,
                                  validation_steps=math.ceil(n_test_samples / batch_size),
                                  class_weight=None,
                                  max_queue_size=10,
                                  workers=1,
                                  use_multiprocessing=False,
                                  shuffle=True,
                                  initial_epoch=0)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值