基于LAS模型的聊天机器人解读

一、概述

先上图,图来自 李宏毅老师的《Deep Learning for Human Language Processing》课程

image

核心包括三个部分 Encoder, Attention, Decoder, 其中Attention的实现方式各种各样,也是大家重点研究的对象。 Encoder 和 Decoder的使用和实现方式就比较通用了。并且也都是用到RNN结构。

二、RNN

RNN 实际上和Conv 的本质上是相同, 拿(None,Width, Height, Channel) 结果来说, 就是将 w,h 空间上的信息提取到channel 维度上。换语之,rnn的操作就是将 t 时间维度上的信息,变成有限容量的信息。

image

每个时刻的输入对应一个 $x_t$, RNN是参数共享, 每一个时刻通过的都是同一个神经元。公式如下

一个segment 结束后, 同时有两个输出 y_t, h_t,下一个时刻 h_t 作为新的输入经过 σh 后输出下一个时刻的 y{t+1}, h_{t+1}, 以此类推。

RNN结构看似能完美解决了时间维度上的体征提取, 但同时存在诸多问题,例如:

无法长时记忆,RNN 对于短的序列可以有效记录相关信息,较长序列时,因为特征是通过简单的累加操作,当序列中存在较多相似的特征时,就容易被覆盖。

超参调教困难,RNN 存在递归相乘的结构 h_t = σh(W_h x_t + U_h y{t-1} + b_h),进行倒数运算的时候,就会发现, 存在 W^{t-1} 项, 意味着什么的,超参选取不合理的时候, 在进行反向梯度传播的时候,即便时较小的变动也会造成网络参数的巨大波动,梯度爆炸 , 或者 梯度拟散。

如何解决呢? 使用RNN相应的变种 lstm 或 gru 都可以很好的解决~

三、Embedding 编码

现实世界存在很多具有相关性的数据,以词汇为例: “哈士奇”,“萨摩耶”, “英短”, “加菲”, 如果以简单的 one_hot 进行处理那么上面的数据将被处理成: 000, 100, 010,001, 不经加大了运算量,同时破坏了数据相关性。

那么通过 Embedding 层后的,数据是什么样子的?接图:

image

数据使用向量表示,并采用欧式距离表示数据相关性, 可视化的结果,就是相关的数据聚合在某个区域。

四、构建模型

上面讲述基础的单元,接下来补充一下, 更大的基础结构,==模型==。

  • 编码器模型

class Encoder(tf.keras.Model):
    def __init__(self, vocab_size, embedding_dim, enc_units, batch_sz):
        super(Encoder, self).__init__()
        self.batch_sz = batch_sz
        self.enc_units = enc_units
        self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)
        self.gru = tf.keras.layers.GRU(self.enc_units, return_sequences=True, return_state=True,
                                       recurrent_initializer='glorot_uniform')

    def call(self, x, hidden):
        x = self.embedding(x)
        output, state = self.gru(x, initial_state=hidden)
        return output, state

    def initialize_hidden_state(self):
        return tf.zeros((self.batch_sz, self.enc_units))

一个批次的数据的 shape 通过, embeding层 和 rnn 层后, 相应的shape 的变换为。

i: embeding 层变换

(None, 20) => (None, 20 , embeding_dim)

ii:RNN 层变

RNN 输出两个Tensor, (None, enc_units) 为最后一个经过RNNCell的状态, 可以认为是整句话的语境, (None, 20, enc_uints) 为每个时刻输出的状态。

(None, 20 , embeding_dim) => (None, enc_units), (None, 20, enc_uints)
  • 解码器模型

class Decoder(tf.keras.Model):
    def __init__(self, vocab_size, embedding_dim, dec_units, batch_sz):
        super(Decoder, self).__init__()
        self.batch_sz = batch_sz
        self.dec_units = dec_units
        self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)
        self.gru = tf.keras.layers.GRU(self.dec_units,
                                       return_sequences=True,
                                       return_state=True,
                                       recurrent_initializer='glorot_uniform')
        self.fc = tf.keras.layers.Dense(vocab_size)

        self.attention = BahdanauAttention(self.dec_units)

    def call(self, x, hidden, enc_output):
        # 解码器 hidden 和 编码器 output 输入dao Attention中,hidden (128, 256),  enc_output (128, 20, 256)
        context_vector, attention_weights = self.attention(hidden, enc_output)

        x = self.embedding(x)

        x = tf.concat([tf.expand_dims(context_vector, 1), x], axis=-1)

        output, state = self.gru(x)

        output = tf.reshape(output, (-1, output.shape[2]))

        x = self.fc(output)

        return x, state, attention_weights

解码器的接口在本质上和编码器相似, 不一样的是,解码器的RNN输入是来自上一个 RNN 输出,并且在首次输入的时候都是一个固定的标志符 "start" . 拥有start 标志符还不够, 还需要结合编码器的输出输出的语境。

当前时刻输入 = start标志符 +  语境 vector

i. embeding 层变换

此部分和编码器一摸一样, 不同的是,输入的数量是一个字符表示启动。
(None, 1) => (None, 1, embeding_dim)

ii. 语境结合

content_vector 来自于Atention Model, 下面会讲到。


(None, 1, embeding) + boradcat(context_vector) ==> (None, 1, embeding_dim + content_vector_dim)
iii. RNN 层变换

经过 attention 加持过的 输出,需要再次经过RNN 层,入下图。

image

以头尾相接的形式,也就是上一个时刻的输出为下一个时刻的输入。变换如下

(None, 1, embeding_dim + content_vector_dim) => (None, 1)

进过N轮后,或者超过最大限制长度,循环结束。输出的shape 经过 concat处理得到 (None, N), 通过查表可得到对应的字符

  • Attention

该层是变化最多的层, 经典的有: Luong Attention 和 Bahdanau Attention, 这里我只讲解 Bahdanau Attention

结构图

image

解码器的 t-1 时刻输出 和 编码器全部时刻输出, 经过一个 a_t 变换后,得到 attention_weight 权重, attention_weight 和 编码器全部输出再次经过 一个 c_t变换 等到 t时刻的 content_vector。shape的变化如下注释所示。

class BahdanauAttention(tf.keras.Model):
    def __init__(self, units):
        super(BahdanauAttention, self).__init__()
        self.W1 = tf.keras.layers.Dense(units)
        self.W2 = tf.keras.layers.Dense(units)
        self.V = tf.keras.layers.Dense(1)

    def call(self, query, values):
        # hidden shape == (batch_size, hidden size)
        # hidden_with_time_axis shape == (batch_size, 1, hidden size)
        # we are doing this to perform addition to calculate the score
        # (128, 256) => (128, 1,  256)
        hidden_with_time_axis = tf.expand_dims(query, 1)

        # (128, 20, 256) x (256, 256)  + (128, 1, 256) x (256, 256) => (128, 20, 256) + (128, 20, 256) => (128, 20, 256)
        # fc 层处理 (128, 20, 256) x (256, 1) => (128, 20, 1)
        # score shape == (batch_size, max_length, hidden_size)
        score = self.V(tf.nn.tanh(
            self.W1(values) + self.W2(hidden_with_time_axis)))

        # softmax 层处理到 0 ~ 1
        # attention_weights shape == (batch_size, max_length, 1)
        # we get 1 at the last axis because we are applying score to self.V
        attention_weights = tf.nn.softmax(score, axis=1)

        # (128, 20, 1) x (128, 20, 256) => (128, 20, 256)
        # context_vector shape after sum == (batch_size, hidden_size)
        context_vector = attention_weights * values
        context_vector = tf.reduce_sum(context_vector, axis=1)

        return context_vector, attention_weights

注意上面的变化是某个时刻的变换, 而不是群全部时刻的, 所以如果想的全部时刻的变化, 将生成多个时刻的 content_vector。

四、loss 计算

使用categorical_crossentropy 即可,如果出现梯度拟散, 计算loss的时候,使用batch的loss即可。

️五、注意事项

  1. 训练文本需要标记 start end 符号, 使得模型知道何时输入开始和输出结束。
  2. 训练时间比较长,使用小数据确认loss下降后,再去训练。

六、附加

请右键下载后使用

视频及其代码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容