说明:
主要参考Francois Chollet《Deep Learning with Python》;
代码运行环境为kaggle中的kernels;
# This Python 3 environment comes with many helpful analytics libraries installed
# It is defined by the kaggle/python docker image: https://github.com/kaggle/docker-python
# For example, here's several helpful packages to load in
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import keras
# Input data files are available in the "../input/" directory.
# For example, running this (by clicking run or pressing Shift+Enter) will list the files in the input directory
import os
print(os.listdir("../input"))
# Any results you write to the current directory are saved as output.
一、Keras中的非Sequential模型
在keras中最常见的模型是Sequential类型的模型,其特点是只有一个输入和输出,而且网络是层的线性堆叠。其结构如下图:
但是有些网络需要多个独立的输入,有些网络则需要多个输出,而有些网络在层与层之间具有内部分支,这些都不是层的堆叠
二、函数式API
使用Keras函数式API,可以直接操作张量。也可以将层当做函数用来接收张量并返回张量。
1.函数式API简单介绍
from keras import Input,layers
input_tensor = Input(shape=(32,)) # 一个输入张量
dense = layers.Dense(32,activation='relu') # 一个全连接层,可以看做一个函数
output_tensor = dense(input_tensor) # 使用函数dense将input_tensor转换成output_tensor
2.使用Sequential实现全连接模型
from keras.models import Sequential,Model
from keras import layers
from keras import Input
seq_model = Sequential()
seq_model.add(layers.Dense(32,activation='relu',input_shape=(64,)))
seq_model.add(layers.Dense(32,activation='relu'))
seq_model.add(layers.Dense(10,activation='softmax'))
3.使用函数式API实现全连接模型
input_tensor = Input(shape=(64,))
x = layers.Dense(32,activation='relu')(input_tensor)
x = layers.Dense(32,activation='relu')(x)
output_tensor = layers.Dense(10,activation='softmax')(x)
model = Model(input_tensor,output_tensor)
model.summary()
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_10 (InputLayer) (None, 64) 0
_________________________________________________________________
dense_15 (Dense) (None, 32) 2080
_________________________________________________________________
dense_16 (Dense) (None, 32) 1056
_________________________________________________________________
dense_17 (Dense) (None, 10) 330
=================================================================
Total params: 3,466
Trainable params: 3,466
Non-trainable params: 0
_________________________________________________________________
三、多输入模型
1.多输入模型的应用场景
假设需要一个深度学习模型用于预测二手衣服最有可能的市场价格,可用的数据有:用户提供的元数据、用户提供的文本描述与商品照片。如果只有元数据,那么可以使用全连接神经网络来预测价格;如果只有文本描述,那么可以使用RNN或者1维CNN;如果只有图片,那么可以使用2维的卷积神经网络。
但是如果结合三种数据呢?一种思想是分别训练三个模型,然后对三者做加权平均。另一种思路就是同时考虑这三种输入。
2.多输入模型通常在某一时刻用一个可以组合多个张量的层将不同的输入分支合并,张量组合的方式可以是相加、连接等。这些操作通常可以使用keras.layers.add、keras.layers.concatenate实现。
3.问答系统
问答系统是一种较简单的多输入模型。典型的问题系统有两个输入:一个问题描述和一个文本片段。然后模型要生成一个回答,在最简单的情况下,这个回答只包含一个词,可以通过对某个预定义的词表做softmax得到。
4.使用函数式API实现双输入问答模型
from keras.models import Model
from keras import layers
from keras import Input
text_vocabulary_size = 10000
question_vocabulary_size = 10000
answer_vocabulary_size = 500
# 参考文本的层
text_input = Input(shape=(None,),dtype='int32',name='text') #由于文本序列的长度可变,因此shape=(None,)
embedded_text = layers.Embedding(text_vocabulary_size,64)(text_input)
encoded_text = layers.LSTM(32)(embedded_text)
# 问题的层
question_input = Input(shape=(None,),dtype='int32',name='question')
embedded_question = layers.Embedding(question_vocabulary_size,32)(question_input)
encoded_question = layers.LSTM(16)(embedded_question)
# 将两个单独的层合并在一起
concatenated = layers.concatenate([encoded_text,encoded_question],axis=-1)
# 将合并的结果输入到全连接层
answer = layers.Dense(answer_vocabulary_size,activation='softmax')(concatenated)
# 模型实例化
model = Model([text_input,question_input],answer)
# 训练模型
model.compile(optimizer='rmsprop',loss='categorical_crossentropy',metrics=['acc'])
5.输入数据并训练
num_samples = 1000
maxlen = 100
# 生成随机数据
text = np.random.randint(1,text_vocabulary_size,size=(num_samples,maxlen))
question = np.random.randint(1,question_vocabulary_size,size=(num_samples,maxlen))
answers = np.random.randint(answer_vocabulary_size,size=(num_samples))
answers = keras.utils.to_categorical(answers,answer_vocabulary_size) # one-hot
# 使用list进行fit
history = model.fit([text,question],answers,epochs=10,batch_size=128)
# 使用dict进行fit(只有在构建model时使用了name属性进行命名才可以使用这种方法)
# model.fit({'text':text,'question':question},answers,epochs=10,batch_size=128)
Epoch 1/10
1000/1000 [==============================] - 11s 11ms/step - loss: 6.2146 - acc: 1.0000e-03
...
Epoch 10/10
1000/1000 [==============================] - 3s 3ms/step - loss: 5.6163 - acc: 0.0180
四、多输出模型
1.多输出模型的应用场景
有些任务需要预测输入数据的多个目标属性。例如给定一部小说的文本,那么你可以希望将它按类别进行自动分类,同时还希望预测其大致的写作时间。最直接的想法就是训练两个独立的模型,一个用于分类,另一个用于预测日期。但是更好的做法是构建一个联合模型将有两个输出。这样可以利用写作时间帮助分类,也可以利用类别预测写作时间。
2.构建多输出模型
设计一个网络,输入某个匿名人士的一系列帖子,预测其年龄、性别和收入水平。模型结构如下:
from keras import layers
from keras import Input
from keras.models import Model
vocabulary_size = 50000
num_income_groups = 10
# 构建1维卷积网络
posts_input = Input(shape=(None,),dtype='int32',name='posts')
embedded_posts = layers.Embedding(256,vocabulary_size)(posts_input)
x = layers.Conv1D(128,5,activation='relu')(embedded_posts)
x = layers.MaxPooling1D(5)(x)
x = layers.Conv1D(256,5,activation='relu')(x)
x = layers.Conv1D(256,5,activation='relu')(x)
x = layers.MaxPooling1D(5)(x)
x = layers.Conv1D(256,5,activation='relu')(x)
x = layers.Conv1D(256,5,activation='relu')(x)
x = layers.GlobalMaxPooling1D()(x)
x = layers.Dense(128,activation='relu')(x)
# 构建三个输出层
age_prediction = layers.Dense(1,name='age')(x)
income_prediction = layers.Dense(num_income_groups,activation='softmax',name='income')(x)
gender_prediction = layers.Dense(1,activation='sigmoid',name='gender')(x)
# 构建模型
model = Model(posts_input,[age_prediction,income_prediction,gender_prediction])
3.多输出模型损失函数定义的注意事项
预测年龄是回归问题,预测收入是多分类问题,预测性别是二分类问题。对于不同的问题需要指定不同的损失函数。
model.compile(optimizer='rmsprop',loss=['mse','categorical_crossentropy','binary_crossentropy'])
# 等效写法
# model.compile(optimizer='rmsprop',loss={'age':'mse',
# 'income':'categorical_crossentropy',
# 'gender':'binary_crossentropy'})
当各个损失的度量严重不平衡会导致模型只针对单个损失最大的任务优先进行优化,而忽略其他任务。因此需要为这些损失分配不同的权重,保证其具有相同的重要性。例如mse的损失值通常在3~5之间,二分类的交叉熵损失可能低至0.1。因此二分类交叉熵的损失的重要性可以是mse的20倍。因此,如果设二分类交叉熵的权重为10,那么mse的权重为0.5。
model.compile(optimizer='rmsprop',
loss=['mse','categorical_crossentropy','binary_crossentropy'],
loss_weights=[0.25,1.,10.])
假设posts输入数据,age_targets、income_targets、gender_targets是样本标签,那么向模型输入数据的代码如下:
model.fit(posts,[age_targets,income_targets,gender_targets],epochs=10,batch_size=64)
五、复杂的有向无环图网络
除了构建多输入和多输出的模型,函数式API还可以实现复杂的内部拓扑结构的网络。一些常见的组件都是以图的形式实现的,两个著名的组件Inception连接和残差连接。
1.Inception
Inception模块最基本的形式包含3~4个分支,首先是
1
×
1
1\times1
1×1的卷积,然后是一个
3
×
3
3\times3
3×3的卷积,最后将所得到的特征连接在一起。这种设置有助于网络分别学习空间特征和逐通道特征。Inception还可以有更加复杂的形式。
使用函数式API实现Inception:
from keras import layers
branch_a = layers.Conv2D(128,1,activation='relu',strides=2)(x)
branch_b = layers.Conv2D(128,1,activation='relu')(x)
branch_b = layers.Conv2D(128,3,activation='relu',strides=2)(branch_b)
branch_c = layers.AveragePooling2D(3,strides=2)(x)
branch_c = layers.Conv2D(128,3,activation='relu')(branch_c)
branch_d = layers.Conv2D(128,1,activation='relu')(x)
branch_d = layers.Conv2D(128,3,activation='relu')(branch_d)
branch_d = layers.Conv2D(128,3,activation='relu',strides=2)(branch_d)
output = layers.concatenate([branch_a,branch_b,branch_c,branch_d],axis=-1)
2.残差连接
残差连接是让前面某层的输出作为后面某层的输入,从而在序列网络中创造一条捷径。前面层的输出没有与后面层的激活函数连接在一起,而是与后面层的激活函数的输出(activation)相加。如果前面层的输出与后面层的输出形状不同,可以进行线性变换(加入一个没激活函数的Dense)为相同的形状。
from keras import layers
x = ...
y = layers.Conv2D(128,3,activation='relu',padding='same')(x)
y = layers.Conv2D(128,3,activation='relu',padding='same')(y)
y = layers.Conv2D(128,3,activation='relu',padding='same')(y)
y = layers.add([y,x]) # 将x加入到输出特征上
六、共享权重
函数式API的重要特性就是同一个实例可以多次重复使用,这样该实例的权重就可以被共享。下面是一个用于计算句子相似度的shared LSTM。
from keras import layers
from keras import Input
from keras.models import Model
lstm = layers.LSTM(32) # 实例化LSTM
# 左分支
left_input = Input(shape=(None,128))
left_output = lstm(left_input)
# 右分支
right_input = Input(shape=(None,128))
right_output = lstm(right_input)
# 合并两个分支
merged = layers.concatenate([left_input,right_input],axis=-1)
predictions = layers.Dense(1,activation='sigmoid')(merged)
model = Model([left_input,right_input],predictions)
七、将模型作为层使用
模型也可以作为层一样使用。下面的例子中将模型xception_base作为层使用,并在共享xception_base的权重。
from keras import layers
from keras import Input
from keras import applications
xception_base = applications.Xception(weights=None,include_top=False) #实例化一个xception模型
left_input = Input(shape=(250,250,3))
right_input = Input(shape=(250,250,3))
# 共享模型xception_base的参数
left_features = xception_base(left_input)
right_input = xception_base(right_input)
merged_features = layers.concatenate([left_features,right_input],axis=-1)