keras入门系列(2)

Keras之函数式(Functional)模型

我们起初将Functional一词译作泛型,想要表达该类模型能够表达任意张量映射的含义,但表达的不是很精确,在Keras2里我们将这个词改移为“函数式”,函数式模型称作Functional,但它的类名是Model,因此有时候也用Model来代表函数式模型。

Keras函数式模型接口是用户定义多输出模型、非循环有向模型或具有共享层的模型等复杂模型的途径。一句话,只要你的模型不是类似VGG一样一条路走到黑的模型,或者你的模型需要多于一个的输出,那么你总应该选择函数式模型。函数式模型是最广泛的一类模型,序贯(Sequential)模型只是它的一种特殊情况。

第一个模型:全连接网络

Sequential当然是实现全连接网络的最好方式,但我们从简单的全连接网络开始,有助于我们学习这部分的内容。在开始前,有几个概念需要澄清:

  1. 层对象接受张量作为参数,返回一个张量。
  2. 输入是张量,输出也是张量的一个框架就是一个模型,通过Model定义。
  3. 这样的模型可以向Keras的Sequential一样被训练
from keras.layer import Input,Dense
from keras.models import Model

#This returns a tensor
inputs = Input(shape=(784,))

#a Layer instance is callable on a tensor , and returns a tensor
x = Dense(64 , activation='relu')(inputs)
x = Dense(64,activation='relu')(x)
predictions = Dense(10,activation='softmax')(x)

#This creates a model that includes
#the Input layer and three Dense layers
model = Model(inputs=inputs,outputs=predictions)
model.compile(optimizer='rmsprop',loss='categorical_crossentropy',metrics=['accuracy'])
model.fit(data,labels) #starts training

所有的模型都是可调用的,就像层一样

利用函数式模型的接口,我们可以很容易的重用已经训练好的模型:你可以把模型当做一个层一样,通过提供一个tensor来调用它。注意当你调用一个模型时,你不仅仅重用了它的结构,也重用了它的权重。

x = Input(shape=(784,))
#This works, and returns the 10-way softmax we defined above
y = model(x)

这种方式可以允许你快速的创建能处理序列信号的模型,你可以很快将一个图像分类的模型变为一个对视频分类的模型,只需要一行代码:

from keras.layers import TimeDistributed

#Input tensor for sequences of 20 timesteps
#each containing a 784-dimensional vector

input_sequences = Input(shape=(20,784))

#This applies our previous model to every timestep in the input sequences.
#the output of the previous model was a 10-way softmax
#so the output of the layer below will be a sequence of 20 vectors of size 10.

processed_sequences = TimeDistributed(model)(input_sequences)

多输入和多输出模型

使用函数式模型的一个典型场景是搭建多输入、多输出的模型。

考虑这样一个模型。我们希望预测Twitter上一条新闻会被转发和点赞多少次。模型的主要输入是新闻本身,也就是一个词语的序列。但我们还可以拥有额外的输入,如新闻发布的日期等。这个模型的损失函数将有两部分组成,辅助的损失函数评估仅仅基于新闻本身做出预测的情况,主损失函数评估基于新闻的额外信息的预测的情况,即使来自主损失函数的梯度发生弥散,来自辅导损失函数的信息也能够训练Embedding和LSTM层。在模型中早点使用主要的损失函数是对于深度网络的一个良好的正则方法。总而言之,该模型的框图如下:
在这里插入图片描述
让我们用函数式模型来实现这个框图

主要的输入接收新闻本身,即一个整数的序列(每个整数编码了一个词)。这些整数位于1到10,000之间(即我们的字典有10,000)个词。


from keras.layers import Input, Embedding, LSTM, Dense
from keras.models import Model
 
# 标题输入:接收一个含有 100 个整数的序列,每个整数在 1 到 10000 之间。
# 注意我们可以通过传递一个 "name" 参数来命名任何层。name表示该层的名字。
main_input = Input(shape=(100,), dtype='int32', name='main_input')
 
# Embedding 层将输入序列编码为一个稠密向量的序列,
# 每个向量维度为 512。
x = Embedding(output_dim=512, input_dim=10000, input_length=100)(main_input)
 
# LSTM 层把向量序列转换成单个向量,
# 它包含整个序列的上下文信息
lstm_out = LSTM(32)(x)

Embedding层的知识:
嵌入层将正整数(下标)转换为具有固定大小的向量,它只能作为模型的第一层。

在这里,我们插入辅助损失,即使在模型主损失很高的情况下,LSTM层和Embedding层都能被平稳地训练

auxiliary_output = Dense(1, activation='sigmoid', name='aux_output')(lstm_out)

此时,我们将辅助输入数据与 LSTM 层的输出连接起来,输入到模型中:

auxiliary_input = Input(shape=(5,), name='aux_input')
x = keras.layers.concatenate([lstm_out, auxiliary_input])
# 该层接收一个列表的同shape张量,并返回它们的按照给定轴相接构成的向量。
# 堆叠多个全连接网络层
x = Dense(64, activation='relu')(x)
x = Dense(64, activation='relu')(x)
x = Dense(64, activation='relu')(x)
 
# 最后添加主要的逻辑回归层
main_output = Dense(1, activation='sigmoid', name='main_output')(x)

然后定义一个具有两个输入和两个输出的模型:

model = Model(inputs=[main_input, auxiliary_input], outputs=[main_output, auxiliary_output])

现在编译模型,并给辅助损失分配一个 0.2 的权重。如果要为不同的输出指定不同的 loss_weights 或 loss,可以使用列表或字典。 在这里,我们给 loss 参数传递单个损失函数,这个损失将用于所有的输出

model.compile(optimizer='rmsprop', loss='binary_crossentropy',
              loss_weights=[1., 0.2])

我们可以通过输入数组和目标数组的列表来训练模型:

model.fit([headline_data, additional_data], [labels, labels],
          epochs=50, batch_size=32)

#由于输入和输出均被命名了(在定义时传递了一个 name 参数),我们也可以通过以下方式编译模型:
model.compile(optimizer='rmsprop',
              loss={'main_output': 'binary_crossentropy', 'aux_output': 'binary_crossentropy'},
              loss_weights={'main_output': 1., 'aux_output': 0.2})
 
# 然后使用以下方式训练:
model.fit({'main_input': headline_data, 'aux_input': additional_data},
          {'main_output': labels, 'aux_output': labels},
          epochs=50, batch_size=32)

共享网络层

另一个使用函数式模型的场合是使用共享层的时候。

考虑微博数据,我们希望建立模型来判别两条微博是否是来自同一个用户,这个需求同样可以用来判断一个用户的两条微博的相似性。

一种实现方式是,我们建立一个模型,它分别将两条微博的数据映射到两个特征向量上,然后将特征向量串联并加一个logistic回归层,输出它们来自同一个用户的概率。这种模型的训练数据是一对对的微博。

因为这个问题是对称的,所以处理第一条微博的模型当然也能重用于处理第二条微博。所以这里我们使用一个共享的LSTM层来进行映射。

首先,我们将微博的数据转为(140,256)的矩阵,即每条微博有140个字符,每个单词的特征由一个256维的词向量表示,向量的每个元素为1表示某个字符出现,为0表示不出现,这是一个one-hot编码。

之所以是(140,256)是因为一条微博最多有140个字符,而扩展的ASCII码表编码了常见的256个字符。原文中此处为Tweet,所以对外国人而言这是合理的。如果考虑中文字符,那一个单词的词向量就不止256了。

import keras
from keras.layers import Input, LSTM, Dense
from keras.models import Model

tweet_a = Input(shape=(140, 256))
tweet_b = Input(shape=(140, 256))

若要对不同的输入共享同一层,就初始化该层一次,然后多次调用它

# This layer can take as input a matrix
# and will return a vector of size 64
shared_lstm = LSTM(64)
#输出维度为64维
# When we reuse the same layer instance
# multiple times, the weights of the layer
# are also being reused
# (it is effectively *the same* layer)
encoded_a = shared_lstm(tweet_a)
encoded_b = shared_lstm(tweet_b)


assert shared_lstm .get_output_at(0) == encoded_a
assert shared_lstm .get_output_at(1) == encoded_b

##如果没有以上两行,会报错
# We can then concatenate the two vectors:
merged_vector = keras.layers.concatenate([encoded_a, encoded_b], axis=-1)
#按照给定的轴连接向量
# And add a logistic regression on top
predictions = Dense(1, activation='sigmoid')(merged_vector)

# We define a trainable model linking the
# tweet inputs to the predictions
model = Model(inputs=[tweet_a, tweet_b], outputs=predictions)

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['accuracy'])
model.fit([data_a, data_b], labels, epochs=10)

补充知识:关于LSTM层的说明

keras.layers.recurrent.LSTM(units, activation='tanh', recurrent_activation='hard_sigmoid', use_bias=True, kernel_initializer='glorot_uniform', recurrent_initializer='orthogonal', bias_initializer='zeros', unit_forget_bias=True, kernel_regularizer=None, recurrent_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, recurrent_constraint=None, bias_constraint=None, dropout=0.0, recurrent_dropout=0.0)

units:输出维度
input_dim:输入维度,当使用该层为模型首层时,应指定该值(或等价的指定input_shape)
return_sequences:布尔值,默认False,控制返回类型。若为True则返回整个序列,否则仅返回输出序列的最后一个输出
input_length:当输入序列的长度固定时,该参数为输入序列的长度。当需要在该层后连接Flatten层,然后又要连接Dense层时,需要指定该参数,否则全连接的输出无法计算出来。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值