【Keras】Keras使用进阶

2019-08-12 15:22:48 浏览数 (1)

参考资料:

  • https://github.com/keras-team/keras/blob/eb97bc385599dec8182963fe263bd958b9ab0057/keras/models.py
  • https://github.com/xingkongliang/Keras-Tutorials
  • Keras学习资料大全,这是fchollet的一个仓库
  • Keras官方扩展库,能找到许多没写进Keras但是会用得着的Layer,Model,Objectives
  • keras进行图像预处理源码
  • UCF课程:高级计算机视觉(Keras) by Mubarak Shah

用keras训练多标签数据

  • Multi_Label_Classification_Keras

通常用keras做分类任务的时候,一张图像往往只对应着一种类别,但是在实际的问题中,可能你需要预测出一张图像的多种属性。例如在pyimagesearch的《multi-label-classification-with-keras》这篇文章中提出了一个衣服数据集,整个数据集有两种属性,一种是颜色(blue, red, black),另一种是衣服的类型(dress, jeans, shirt) 。如假设one-hot-vector编码顺序是(blue, red, black, dress, jeans, shirt)则black jeans的 label就是[0,0,1,0,1,0]。

keras multi label dataset

那么面对这样的多标签任务如何使用keras进行CNN模型的搭建与训练呢?

首先我们搭建一个单输入(一张图像)多输出(图像的多个属性,比如衣服的颜色,类型)的CNN。

代码语言:javascript复制
def GenModel(img_width = 512, img_height = 512 , model_name = 'AlexNet'):
    # 指定输入图像大小
    image_input_shape = (img_width, img_height, 3)
    # 定义网络(AlexNet)
    if model_name == 'AlexNet':
        print('n--------Start build model ', model_name, '--------n')
        
        # 定义网络输入
        image_input = Input(shape=image_input_shape, name='image_input')

        # 定义主卷积(图像)网络卷积->激活->池化
        conv_image = Conv2D(96, (11, 11), strides = (4, 4), padding = 'valid', activation = 'relu')(image_input)
        conv_image = MaxPooling2D(pool_size = (3, 3), strides = (2, 2))(conv_image)

        conv_image = Conv2D(256, (5, 5), strides = (1, 1), padding = 'same', activation = 'relu')(conv_image)
        conv_image = MaxPooling2D(pool_size = (3, 3), strides = (2, 2))(conv_image)

        conv_image = Conv2D(384, (3,3), strides = (1, 1), padding = 'same', activation = 'relu')(conv_image)
        
        conv_image = Conv2D(384, (3,3), strides = (1, 1), padding = 'same', activation = 'relu')(conv_image)

        conv_image = Conv2D(384, (3,3), strides = (1, 1), padding = 'same', activation = 'relu')(conv_image)
        
        conv_image = MaxPooling2D(pool_size = (3, 3), strides = (2, 2))(conv_image)

        conv_image = Flatten()(conv_image)

        # 定义2个输出,分别用于预测衣服的颜色和类型
        out_color = Dense(4096, activation='relu',)(conv_image)        
        out_color = Dense(512, activation='relu',)(out_color)
        out_color = Dense(3, activation='sigmoid', name='out_age')(out_color) 
                                                           
        out_type = Dense(4096, activation='relu',)(conv_image)
        out_type = Dense(512, activation='relu',)(out_sex)
        out_type = Dense(3, activation='sigmoid', name='out_sex')(out_sex)

        # 定义整个单输入,2输出的模型
        model=Model(inputs = image_input, outputs = [out_color, out_type]) 
        return model

然后对模型进行编译

代码语言:javascript复制
# 编译模型,定义损失函数
    opt=Adadelta()
    # opt=SGD()
    print('n-------- optimizer: %s --------n'%(opt.__class__.__name__) )
    model.compile(  optimizer = opt, 
                    loss = {'out_color': 'categorical_crossentropy', 'out_type': 'categorical_crossentropy'}, 
                    loss_weights = {'out_color': out_color_weight, 'out_type': out_type_weight, metrics = ['accuracy']) # 这里loss_weights需要自己手动设定下

最后将数据集载入模型进行训练和预测

代码语言:javascript复制
# grab the image paths and randomly shuffle them
print("[INFO] loading images...")
imagePaths = sorted(list(paths.list_images(args["dataset"])))
random.seed(42)
random.shuffle(imagePaths)

# initialize the data and labels
data = []
labels = []

# loop over the input images
for imagePath in imagePaths:
    # load the image, pre-process it, and store it in the data list
    image = cv2.imread(imagePath)
    image = cv2.resize(image, (IMAGE_DIMS[1], IMAGE_DIMS[0]))
    image = img_to_array(image)
    data.append(image)

    # extract set of class labels from the image path and update the
    # labels list
    l = label = imagePath.split(os.path.sep)[-2].split("_")
    labels.append(l)

# scale the raw pixel intensities to the range [0, 1]
data = np.array(data, dtype="float") / 255.0
labels = np.array(labels)
print("[INFO] data matrix: {} images ({:.2f}MB)".format(
    len(imagePaths), data.nbytes / (1024 * 1000.0)))
print(labels)
# binarize the labels using scikit-learn's special multi-label
# binarizer implementation
print("[INFO] class labels:")
mlb = MultiLabelBinarizer()
labels = mlb.fit_transform(labels)
print(labels)
# loop over each of the possible class labels and show them
for (i, label) in enumerate(mlb.classes_):
    print("{}. {}".format(i   1, label))

# partition the data into training and testing splits using 80% of
# the data for training and the remaining 20% for testing
(trainX, testX, trainY, testY) = train_test_split(data,
    labels, test_size=0.2, random_state=42)

# construct the image generator for data augmentation
aug = ImageDataGenerator(rotation_range=25, width_shift_range=0.1,
    height_shift_range=0.1, shear_range=0.2, zoom_range=0.2,
    horizontal_flip=True, fill_mode="nearest")

# train the network
print("[INFO] training network...")
H = model.fit_generator(
    aug.flow(trainX, trainY, batch_size=BS),
    validation_data=(testX, testY),
    steps_per_epoch=len(trainX) // BS,
    epochs=EPOCHS, verbose=1)

输出优化器的名字

代码语言:javascript复制
from keras.optimizers import RMSprop, Adam, Adadelta

opt=Adam()
opt_name=opt.__class__.__name__
print(opt_name)

使用回调函数

  • https://keras.io/callbacks/
  • http://blog.csdn.net/tsyccnh/article/details/78865167
  • https://www.spaces.ac.cn/archives/5765

model的.fit方法有一个参数是callbacks,这个参数可以传入一些其他待执行的函数,在训练过程中,每一个epoch会调用一次列表中的callbacks 在下面这个例子中设置monitor='val_acc'来保存训练过程中验证集准确率最高的模型

代码语言:javascript复制
checkpoint = ModelCheckpoint(filepath='./best_model.weights',
                             monitor='val_acc',
                             verbose=1,
                             save_best_only=True)

model.fit(x_train,
          y_train,
          epochs=10,
          validation_data=(x_test, y_test),
          callbacks=[checkpoint])

训练过程中保存文件 深度学习有可能需要跑很长时间,如果中间断了(特别是在竞价式实例上跑的时候)就要亲命了。本章关于在训练时中途保存模型。

  • https://yq.aliyun.com/articles/599528
  • https://keras.io/zh/callbacks/#_4
代码语言:javascript复制
# checkpoint
# https://keras.io/zh/callbacks/
# 如果验证损失下降, 那么在每个训练轮之后保存模型
ArgName=',epo=' str(epoch) ',bsize=' str(batch_size) ',lr=' str(LearningRate) ',DropRate=' str(DropoutRate)
FileNamePy=os.path.basename(__file__).split('.')[-2]
checkpoint_filepath = FileNamePy ArgName


checkpointer_val_best = ModelCheckpoint(filepath=checkpoint_filepath, monitor='val_acc',
    verbose=1, save_best_only=True, mode='max', save_weights_only=True)

callbacks_list = [checkpointer_val_best]

hist=model.fit_generator(
    train_generator,
    steps_per_epoch=nb_train_samples,
    epochs=epoch,
    validation_data=validation_generator,
    validation_steps=nb_validation_samples,
    callbacks = callbacks_list)

这种方法虽然简单,但是有一个明显的缺点,就是里边的指标是由compile的metrics来确定的,而Keres中自定义一个metric,需要写成张量运算才行,也就是说如果你期望的指标并不能写成张量运算(比如bleu等指标),那么就没法写成一个metric函数了,也就不能用这个方案了。 by:苏剑林

苏神提供了一个方案:自己写回调器

代码语言:javascript复制
from keras.callbacks import Callback

def evaluate(): # 评测函数
    pred = model.predict(x_test)
    return np.mean(pred.argmax(axis=1) == y_test) # 爱算啥就算啥


# 定义Callback器,计算验证集的acc,并保存最优模型
class Evaluate(Callback):

    def __init__(self):
        self.accs = []
        self.highest = 0.

    def on_epoch_end(self, epoch, logs=None):
        acc = evaluate()
        self.accs.append(acc)
        if acc >= self.highest: # 保存最优模型权重
            self.highest = acc
            model.save_weights('best_model.weights')

        # 爱运行什么就运行什么
        print 'acc: %s, highest: %s' % (acc, self.highest)


evaluator = Evaluate()
model.fit(x_train,
          y_train,
          epochs=10,
          callbacks=[evaluator])

训练过程中还有可能对超参数进行微调,比如最常见的一个需求是根据epoch来调整学习率,这可以简单地通过LearningRateScheduler来实现,它也属于回调器之一。这个方案也是苏神的~

代码语言:javascript复制
from keras.callbacks import LearningRateScheduler

def lr_schedule(epoch):
    # 根据epoch返回不同的学习率
    if epoch < 50:
        lr = 1e-2
    elif epoch < 80:
        lr = 1e-3
    else:
        lr = 1e-4
    return lr

lr_scheduler = LearningRateScheduler(lr_schedule)

model.fit(x_train,
          y_train,
          epochs=10,
          callbacks=[evaluator, lr_scheduler])

更多例子可以看苏神博客:https://www.spaces.ac.cn/

在keras中多种数据读取的方法

  • FancyKeras-数据的输入(传统)
  • FancyKeras-数据的输入(花式)

自定义loss函数

Keras中自定义复杂的loss函数

使用Lambda层让你的keras网络更加灵活

  • https://keras.io/layers/writing-your-own-keras-layers/
  • https://keras-cn.readthedocs.io/en/latest/layers/core_layer/#lambda
  • Lambda层
  • Keras 自定义层

keras的Lambda层的导入和函数原型: from keras.layers.core import Lambda keras.layers.core.Lambda(function, output_shape=None, mask=None, arguments=None)

参数的含义: function: 要实现的函数,该函数仅接受一个变量,即上一层的输出 output_shape: 函数应该返回值的shape,可以是一个tuple,也可以是一个根据输入shape mask: 掩膜 arguments: 可选参数,字典,用来记录向函数中传递的其他关键字参数

例子:

代码语言:javascript复制
# add a x -> x^2 layer
model.add(Lambda(lambda x: x ** 2))
代码语言:javascript复制
# add a layer that returns the concatenation
# of the positive part of the input and
# the opposite of the negative part

def antirectifier(x):
    x -= K.mean(x, axis=1, keepdims=True)
    x = K.l2_normalize(x, axis=1)
    pos = K.relu(x)
    neg = K.relu(-x)
    return K.concatenate([pos, neg], axis=1)

def antirectifier_output_shape(input_shape):
    shape = list(input_shape)
    assert len(shape) == 2  # only valid for 2D tensors
    shape[-1] *= 2
    return tuple(shape)

model.add(Lambda(antirectifier,
         output_shape=antirectifier_output_shape))
代码语言:javascript复制
# 对于简单的定制操作,可以通过使用layers.core.Lambda层来完成。
# 该方法的适用情况:仅对流经该层的数据做个变换,而这个变换本身没有需要学习的参数

# 切片后再分别进行embedding和average pooling
import numpy as np  
from keras.models import Sequential  
from keras.layers import Dense, Activation,Reshape  
from keras.layers import merge  
from keras.utils import plot_model
from keras.layers import *
from keras.models import Model  

def get_slice(x, index):
    return x[:, index]

keep_num = 3 
field_lens = 90
input_field = Input(shape=(keep_num, field_lens))
avg_pools = []
for n in range(keep_num):
    block = Lambda(get_slice,output_shape=(1,field_lens),arguments={'index':n})(input_field)
    x_emb = Embedding(input_dim=100, output_dim=200, input_length=field_lens)(block)
    x_avg = GlobalAveragePooling1D()(x_emb)
    avg_pools.append(x_avg)  
output = concatenate([p for p in avg_pools])
model = Model(input_field, output) 
plot_model(model, to_file='model/lambda.png',show_shapes=True)  

plt.figure(figsize=(21, 12))
im = plt.imread('model/lambda.png')
plt.imshow(im)
代码语言:javascript复制
# 对于具有可训练权重的定制层,需要自己来实现。 

from keras import backend as K
from keras.engine.topology import Layer
import numpy as np

class MyLayer(Layer):

    def __init__(self, output_dim, **kwargs):
        self.output_dim = output_dim
        super(MyLayer, self).__init__(**kwargs)

    def build(self, input_shape):
        # Create a trainable weight variable for this layer.
        self.kernel = self.add_weight(name='kernel', 
                                      shape=(input_shape[1], self.output_dim),
                                      initializer='uniform',
                                      trainable=True)
        super(MyLayer, self).build(input_shape)  # Be sure to call this somewhere!

    def call(self, x):
        return K.dot(x, self.kernel)

    def compute_output_shape(self, input_shape):
        return (input_shape[0], self.output_dim)

总结一下,keras的Lambda层就是一个层,允许用户自定义对上层输入数据的操作,自定义操作通过keras.layers.core.Lambda的function进行

0 人点赞