keras 自定义层input_NLP实战篇之tf2自定义Layer与Model

本文是基于tensorflow2.2.0版本,介绍了自定义Layer和自定义Model。Layer是tf.keras中的核心抽象之一,是状态(权重)与前向传递(输入到输出的计算转换过程)的封装。Model与Layer结构有些类似,但也有明显区别,tf.keras中构建模型主要有三种形式:sequential、subclass、functional,前面文章已经介绍过前两种,本文则介绍后面两种方式。

实战系列篇章中主要会分享,解决实际问题时的过程、遇到的问题或者使用的工具等等。如问题分解、bug排查、模型部署等等。相关代码实现开源在:https://github.com/wellinxu/nlp_store ,更多内容关注知乎专栏(或微信公众号):NLP杂货铺。4edfe80ef0191df820834914207ff177.png

  • Layer
    • 权重与计算
    • 延迟创建权重
    • add_loss与add_metric
    • 序列化
    • 递归组合的Layer
  • Model
    • subclass
    • functional

Layer

权重与计算

Layer是tf.keras中的核心抽象之一,是状态(权重)与前向传递(输入到输出的计算转换过程)的封装。以简单的全连接层为例,它具有两个权重:w、b,如下面代码所示,可以根据输入维度,在init函数中创建它们,可以使用tf.Variable的方式,也可以使用self.add_weight的方式,后面一种更加简洁。层的计算过程则写在call函数中,call函数需要一个默认的inputs参数,全连接层的计算过程也如下面代码所示。

class Linear(tf.keras.layers.Layer):
    def __init__(self, units=32, input_dim=32):
        super(Linear, self).__init__()
        # add_weight与下面注释部分是等价的,只是更方便
        self.w = self.add_weight(shape=(input_dim, units),
                                 initializer=tf.random_normal_initializer,
                                 trainable=True)
        self.b = self.add_weight(shape=(units, ),
                                 initializer=tf.zeros_initializer,
                                 trainable=True)
        # w_init = tf.random_normal_initializer()
        # self.w = tf.Variable(initial_value=w_init(shape=(input_dim, units),
        #                                           dtype=tf.float32),
        #                      trainable=True)
        # b_init = tf.zeros_initializer()
        # self.b = tf.Variable(initial_value=b_init(shape=(units,),
        #                                           dtype=tf.float32),
        #                      trainable=True)

    def call(self, inputs, training=None, mask=None):
        #if training:
        #    inputs = tf.nn.dropout(inputs, rate=0.9)
        return tf.matmul(inputs, self.w) + self.b

如上面代码所示,构建权重时可以设置其他参数,比如trainable参数,可以控制该权重是否要被学习(如果为False,则训练过程中不会更新此权重的值),add_weight的其他参数如下所示:

  1. name: 变量名称
  2. shape: 变量形状
  3. dtype: 变量数据类型,默认为self.dtypefloat32
  4. initializer: 可调用的初始化实例
  5. regularizer: 可调用的正则化实例
  6. trainable: Boolean, 是否是可训练参数
  7. constraint: 可调用的约束实例
  8. partitioner:
  9. use_resource:
  10. synchronization:
  11. aggregation:
  12. **kwargs: 额外的关键字参数

类似的,call函数也可以有其他参数,比如training,可以区分训练过程还是预测过程,从而来判断是否要进行dropout等处理。当然也可以传递mask或其他关键字参数。

延迟创建权重

在上面实现中,Linear在init函数中就传入了input_dim参数,但在很多情况下,可能实现不知道输入的维度大小,需要在知道输入维度之后的时间点上进行延迟权重创建。tf2中可以使用build函数来处理这种情况,如下面代码所示:

# 动态创建权重,在知道输入形状之后再创建权重
class Linear2(tf.keras.layers.Layer):
    def __init__(self, units=32):
        super(Linear2, self).__init__()
        self.units = units

    def build(self, input_shape):
        self.w = self.add_weight(shape=(input_shape[-1], self.units),
                                 initializer=tf.random_normal_initializer,
                                 trainable=True)
        self.b = self.add_weight(shape=(self.units, ),
                                 initializer=tf.zeros_initializer,
                                 trainable=True)

    def call(self, inputs, training=None, mask=None):
        self.add_loss(0.5 * tf.reduce_sum(tf.square(self.w)))    # L2正则
        # self.add_metric()
        return tf.matmul(inputs, self.w) + self.b

    def get_config(self):
        return {"units": self.units}
add_loss与add_metric

如上面代码所示,在call函数中可以使用add_loss函数,来添加自定义的loss,不过普通参数的正则化操作可以通过add_weight的regularizer来实现。同样的也可以使用add_metric来添加一些计算过程中需要的度量结果。add_loss与add_metric都可以被模型的fit()函数跟踪,如果是自定义训练过程,则需要手动添加,自定义训练相关内容参考后续文章。

序列化

通过实现get_config方法,可以对层进行序列化,然后使用from_config方法来反序列化,如果反序列化比较复杂,也可以重写from_config方法。Layer的序列化与反序列化简单示例如下:

layer = Linear2(64)
config = layer.get_config()
print(config)
new_layer = Linear2.from_config(config)
递归组合的Layer

Layer对应于文献中的“块”(卷积层、全连接层等)或者“块”(编码块、解码块等)。如下面代码所示,可以将多个Layer组合成一个新的Layer:

# 层之间的递归组合
class MLPBlock(tf.keras.layers.Layer):
    def __init__(self):
        super(MLPBlock, self).__init__()
        self.linear_1 =  Linear2(2)
        self.linear_2 =  Linear2(2)
        self.linear_3 =  Linear2(1)

    def call(self, inputs, **kwargs):
        x = self.linear_1(inputs)
        x = tf.nn.relu(x)
        x = self.linear_2(x)
        x = tf.nn.relu(x)
        return self.linear_3(x)

Model

tf.keras构建模型主要有三种形式:sequential、subclass、functional。前面两种在【NLP实战篇之tensorflow2.0快速入门】上已经做过简单介绍。

subclass

通常我们会用Layer来定义内部计算块,用Model来定义外部模型,Model与Layer具有非常类似的api,但也有着明显的区别:

  1. Model内置了训练(fit())、评估(evaluate())和预测(predict())循环。
  2. Model通过layers属性获取其内部层的列表。
  3. Model具有保存和序列化api(save()、save_weights()...)。

跟自定义Layer类似,我们可以自定义Model,如下面代码所示,init函数中包含了模型的各个子结构(层、块),call函数中则体现了整个模型从输入到输出的计算流程。

# 使用模型子类化构建模型
class MyModel(tf.keras.models.Model):
    def __init__(self):
        super(MyModel, self).__init__()
        self.flatten = tf.keras.layers.Flatten()
        self.d1 = tf.keras.layers.Dense(128, activation="relu")
        self.dropout = tf.keras.layers.Dropout(0.2)
        self.d2 = tf.keras.layers.Dense(10, activation="softmax")

    def call(self, inputs, training=None, mask=None):
        # inputs: [batch_size, 28, 28]
        x = self.flatten(inputs)    # [batch_size, 28 * 28]
        x = self.d1(x)    # [batch_size, 128]
        x = self.dropout(x)    # [batch_size, 128]
        x = self.d2(x)    # [batch_size, 10]]
        return x
functional

上面我们都是使用的面相对象的方式进行开发的,其实也可以用函数式api来进行构建模型,更棒的是,选择哪种方式其实并不影响,可以将两种方式混合使用,这样可以更加灵活地构建模型。下面代码展示了如何用函数式api进行构建模型,更具一般性的,inputs与outputs可以是多个值(多个输入、输出)。

# 使用函数式api构建模型
inputs = tf.keras.Input(shape=(256,))
emb = tf.keras.layers.Embedding(vocab_size, 16)(inputs)
avg_pool = tf.keras.layers.GlobalAveragePooling1D()(emb)
d1 = tf.keras.layers.Dense(16, activation="relu")(avg_pool)
outputs = tf.keras.layers.Dense(1, activation="sigmoid")(d1)
model = tf.keras.Model(inputs=inputs, outputs=outputs)

参考

https://www.tensorflow.org/guide/keras/custom_layers_and_models

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 以下是使用 Keras 实现 Multi-Head Self-Attention 的代码示例: ```python from tensorflow import keras from tensorflow.keras import layers class MultiHeadSelfAttention(layers.Layer): def __init__(self, embed_dim, num_heads=8): super(MultiHeadSelfAttention, self).__init__() self.embed_dim = embed_dim self.num_heads = num_heads if embed_dim % num_heads != 0: raise ValueError( f"embed_dim ({embed_dim}) must be divisible by num_heads ({num_heads})" ) self.projection_dim = embed_dim // num_heads self.query_dense = layers.Dense(embed_dim) self.key_dense = layers.Dense(embed_dim) self.value_dense = layers.Dense(embed_dim) self.combine_heads = layers.Dense(embed_dim) def attention(self, query, key, value): score = tf.matmul(query, key, transpose_b=True) dim_key = tf.cast(tf.shape(key)[-1], tf.float32) scaled_score = score / tf.math.sqrt(dim_key) weights = tf.nn.softmax(scaled_score, axis=-1) output = tf.matmul(weights, value) return output, weights def separate_heads(self, x, batch_size): x = tf.reshape(x, (batch_size, -1, self.num_heads, self.projection_dim)) return tf.transpose(x, perm=[0, 2, 1, 3]) def call(self, inputs): # x.shape = [batch_size, seq_len, embedding_dim] batch_size = tf.shape(inputs)[0] query = self.query_dense(inputs) # (batch_size, seq_len, embed_dim) key = self.key_dense(inputs) # (batch_size, seq_len, embed_dim) value = self.value_dense(inputs) # (batch_size, seq_len, embed_dim) query = self.separate_heads( query, batch_size ) # (batch_size, num_heads, seq_len, projection_dim) key = self.separate_heads( key, batch_size ) # (batch_size, num_heads, seq_len, projection_dim) value = self.separate_heads( value, batch_size ) # (batch_size, num_heads, seq_len, projection_dim) attention, weights = self.attention(query, key, value) attention = tf.transpose( attention, perm=[0, 2, 1, 3] ) # (batch_size, seq_len, num_heads, projection_dim) concat_attention = tf.reshape( attention, (batch_size, -1, self.embed_dim) ) # (batch_size, seq_len, embed_dim) output = self.combine_heads( concat_attention ) # (batch_size, seq_len, embed_dim) return output ``` 在上面的代码中,我们定义了一个名为 `MultiHeadSelfAttention` 的自定义 Keras 。在 `__init__` 方法中,我们定义了以下变量: - `embed_dim`:嵌入维度。 - `num_heads`:头的数量。 - `projection_dim`:每个头的投影维度。 - `query_dense`、`key_dense` 和 `value_dense`:三个全连接,用于将输入嵌入到 `embed_dim` 维空间中。 - `combine_heads`:全连接,用于将多头注意力的输出组合成一个 `embed_dim` 维张量。 在 `call` 方法中,我们首先使用 `query_dense`、`key_dense` 和 `value_dense` 将输入嵌入到 `embed_dim` 维空间中。然后,我们将查询、键和值分别投影到 `num_heads` 个子空间中,并计算每个子空间的注意力输出。最后,我们将 `num_heads` 个子空间的注意力输出组合成一个 `embed_dim` 维张量,并通过 `combine_heads` 进行组合。 ### 回答2: Keras是一个流行的深度学习库,它提供了方便的API来实现各种神经网络模型。其中,多头自注意力(multi-head self-attention)是一种在自然语言处理中广泛使用的技术,可以用于提取输入序列之间的重要关系。 下面是使用Keras实现多头自注意力的代码示例: ```python import tensorflow.keras as keras from keras.layers import Layer, Dense class MultiHeadSelfAttention(Layer): def __init__(self, n_heads, d_model, **kwargs): super(MultiHeadSelfAttention, self).__init__(**kwargs) self.n_heads = n_heads self.d_model = d_model self.wq = Dense(d_model) self.wk = Dense(d_model) self.wv = Dense(d_model) self.dense = Dense(d_model) def call(self, inputs): q = self.wq(inputs) k = self.wk(inputs) v = self.wv(inputs) q = self.split_heads(q) k = self.split_heads(k) v = self.split_heads(v) attention_weights = keras.layers.dot([q, k], axes=[-1, -1]) attention_weights = keras.layers.Activation('softmax')(attention_weights) output = keras.layers.dot([attention_weights, v], axes=[-1, 1]) output = self.combine_heads(output) output = self.dense(output) return output def split_heads(self, x): batch_size = keras.backend.shape(x)[0] seq_length = keras.backend.shape(x)[1] d_model = self.d_model split_size = d_model // self.n_heads x = keras.backend.reshape(x, (batch_size, seq_length, self.n_heads, split_size)) return keras.backend.permute_dimensions(x, (0, 2, 1, 3)) def combine_heads(self, x): batch_size = keras.backend.shape(x)[0] seq_length = keras.backend.shape(x)[2] d_model = self.d_model x = keras.backend.permute_dimensions(x, (0, 2, 1, 3)) return keras.backend.reshape(x, (batch_size, seq_length, d_model)) ``` 上述代码中,我们创建了一个名为MultiHeadSelfAttention的自定义,它继承自KerasLayer类。在构造函数中,我们指定了注意力头数n_heads和模型维度d_model。在call函数中,我们分别通过全连接将输入序列映射为查询(q)、键(k)和值(v)的表示。然后,我们将这些表示进行头分割,计算注意力权重,并应用这些权重来聚合值。最后,我们通过全连接将聚合后的结果映射回原始维度。 通过使用上述代码示例,我们可以在Keras中轻松实现多头自注意力机制,并将其用于自然语言处理等任务中。 ### 回答3: Keras是一个流行的深度学习框架,可以用于实现各种神经网络模型,包括self-attention模型。Multi-head self-attention是一种扩展的self-attention模型,用于加强模型对输入数据中不同部分的关注能力。 具体实现multi-head self-attention模型的代码如下: 1. 引入所需的Keras库和模块: ```python from tensorflow import keras from tensorflow.keras.layers import Dense, Input, Dropout, LayerNormalization from tensorflow.keras import Model ``` 2. 定义multi-head self-attention的类: ```python class MultiHeadSelfAttention(keras.layers.Layer): def __init__(self, d_model, num_heads): super(MultiHeadSelfAttention, self).__init__() self.num_heads = num_heads self.d_model = d_model self.depth = int(d_model / num_heads) self.query_dense = Dense(d_model) self.key_dense = Dense(d_model) self.value_dense = Dense(d_model) self.dense = Dense(d_model) def split_heads(self, x, batch_size): x = keras.backend.reshape(x, (batch_size, -1, self.num_heads, self.depth)) return keras.backend.transpose(x, perm=[0, 2, 1, 3]) def call(self, inputs): query = inputs key = inputs value = inputs batch_size = keras.backend.shape(query)[0] query = self.query_dense(query) key = self.key_dense(key) value = self.value_dense(value) query = self.split_heads(query, batch_size) key = self.split_heads(key, batch_size) value = self.split_heads(value, batch_size) scaled_attention_outputs, attention_weights = self.compute_attention(query, key, value) scaled_attention = keras.backend.transpose(scaled_attention_outputs, perm=[0, 2, 1, 3]) concat_attention = keras.backend.reshape(scaled_attention, (batch_size, -1, self.d_model)) outputs = self.dense(concat_attention) return outputs, attention_weights def compute_attention(self, query, key, value): matmul_qk = keras.backend.batch_dot(query, key, axes=[-1, -1]) scaled_attention_logits = matmul_qk / keras.backend.sqrt(keras.backend.cast(self.depth, dtype=keras.backend.floatx())) attention_weights = keras.backend.softmax(scaled_attention_logits) attention_outputs = keras.backend.batch_dot(attention_weights, value, axes=[-1, 2]) return attention_outputs, attention_weights ``` 3. 构建完整的模型: ```python def create_model(d_model=256, num_heads=8): inputs = Input(shape=(seq_length, d_model)) attention_layer = MultiHeadSelfAttention(d_model, num_heads) attention_outputs, attention_weights = attention_layer(inputs) dropout = Dropout(0.1)(attention_outputs) normalization = LayerNormalization(epsilon=1e-6)(dropout) dense = Dense(d_model, activation='relu')(normalization) outputs = Dense(num_classes, activation='softmax')(dense) model = Model(inputs=inputs, outputs=outputs) return model ``` 这段代码实现了一个包含multi-head self-attention的完整模型,输入shape为(seq_length, d_model),输出为一个softmax分类器的结果。考虑到不同应用场景下的具体要求,可以根据实际需要自定义模型的数、宽度以及其他配置,来构建一个更适合具体任务的multi-head self-attention模型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值