NJUNMT學(xué)習(xí)

一、模型定義

NJUNMT中的模型分為Sequence2Sequence和Transformer兩種。兩種模型總體上都可以看做是Encoder-Decoder模型,但使用的具體encoder和decoder的類型存在不同。Seq2Seq模型使用RNN,而Transformer不使用RNN,依賴于attention機制。

模型.jpg

1. Sequence2Sequence

這一部分介紹NJUNMT中seq2seq模型的主要構(gòu)成部件。
seq2seq模型配置示例:

#njunmt/example_configs/toy_seq2seq.yml
model: njunmt.models.SequenceToSequence
model_params:
  '''是否對所有fflayer進行歸一化'''
  fflayers.layer_norm: true 
  '''源語言embedding維度'''
  embedding.dim.source: 8
  '''目標語言embedding維度'''
  embedding.dim.target: 8
  modality.params:
    multiply_embedding_mode:
    '''各層是否共享embedding和softmax權(quán)重?'''
    share_embedding_and_softmax_weights: false
     '''logits的保留率 1.0為不進行dropout'''
    dropout_logit_keep_prob: 1.0
    '''損失函數(shù)'''
    loss: crossentropy
    timing:
'''初始激活值'''
  initializer: random_uniform
  ...

1.1 Embedding

Embedding(詞嵌入)到底是什么
神經(jīng)網(wǎng)絡(luò)中的embedding層的參數(shù)是一個矩陣,跟整個模型一起訓(xùn)練得來。矩陣乘以某個詞的獨熱向量得到這個詞的詞向量表示。

NJUNMT中用的是經(jīng)過n次封裝后的tf.nn.embedding_lookup()
詳解TF中的Embedding操作!

1.1 Encoder

seq2seq模型的encoder配置示例:

model_params:
  ...
  """指定encoder類型"""
  encoder.class: njunmt.encoders.BiUnidirectionalRNNEncoder
  encoder.params:
    rnn_cell:
      """指定rnn cell類型,可以是LSTMCell/GRUCell"""
      cell_class: GRUCell
      cell_params:
      """每個cell中神經(jīng)元的數(shù)量"""
      num_units: 9
      """input 的保留率 1.0為不進行dropout"""
      dropout_input_keep_prob: 0.9
      """input 的保留率 1"""
      dropout_state_keep_prob: 1.0
      """RNN層數(shù)"""
      num_layers: 2
    ...

seq2seq模型使用RNN encoder。njunmt/encoders/rnn_encoder.py中定義了三種encoder類型:
UnidirectionalRNNEncoder:單向多層RNN
StackBidirectionalRNNEncoder:雙向多層RNN
BiUnidirectionalRNNEncoder:底層為雙向RNN,上層為雙向多層RNN
*疑問:為什么要使用這樣的RNN架構(gòu)?

Encoder類均包含encode()方法:

def encode(inputs, sequence_length, **kwargs):
    outputs, states = tf.nn.dynamic_rnn(...)
    return (outputs, final_states)

tf.nn.dynamic_rnn()方法運行已創(chuàng)建RNN cell,dynamic在于不同batch傳入的sequence_length可以不同,每一個batch內(nèi)動態(tài)進行padding.

Sequence2Sequence類中的_encode()方法:
對input進行embedding,然后調(diào)用Encoder的encode()方法

    def _encode(self, input_fields):
        features = self._input_to_embedding_fn(...)
        encoder_output = self._encoder.encode(features, feature_length)
        return encoder_output

1.2 Bridge

銜接encoder final state和decoder initial state的東西。
njunmt/utils/bridges.py中定義了4種Bridge:
ZeroBridge:decoder initial state 為全0
PassThroughBridge:直接將encoder final state傳給decoder
InitialStateBridge:用一層全連接網(wǎng)絡(luò)對encoder final state進行映射再傳給decoder
Variable Bridge:通過學(xué)習(xí)決定decoder initial state

1.3 Decoder

seq2seq模型使用RNN decoder。njunmt/decoders/rnn_decoder.py中定義了三種decoder類型:
SimpleDecoder:多層RNN,無Attention機制
AttentionDecoder:多層RNN,使用concat(decoder_input, attention_context)作為輸入
CondAttentionDecoder:底層是使用decoder_input作為輸入的單層RNN,上層是使用attention_context作為輸入的多層RNN

Decoder類的decode()方法主要是調(diào)用了dynamic_decode()方法:

def dynamic_decode(decoder,
                   encoder_output,
                   bridge,
                   helper,
                   target_to_embedding_fn,
                   outputs_to_logits_fn,
                   parallel_iterations=32,
                   swap_memory=False,
                   **kwargs):
     ...
    '''一些初始化,包括把target input 變成embedding'''

    with tf.variable_scope(decoder.name):
        '''調(diào)用decoder.prepare()方法:
        1. 用bridge初始化decoder的states,
        2. 從encoder_output獲得attention(如果需要)'''
        initial_cache = decoder.prepare(encoder_output, bridge, helper)  
        if decoder.mode == ModeKeys.INFER:
            assert "beam_size" in kwargs
            beam_size = kwargs["beam_size"]
            initial_cache = stack_beam_size(initial_cache, beam_size)

    """while_loop中的循環(huán)體"""
    def body_traininfer(time, inputs, cache, outputs_ta,
                        finished, *args):

        with tf.variable_scope(decoder.name):
            '''調(diào)用decoder.step()方法'''
            outputs, next_cache = decoder.step(inputs, cache)
        outputs_ta = nest.map_structure(lambda ta, out: ta.write(time, out),
                                        outputs_ta, decoder_output_remover.apply(outputs))
        inner_loop_vars = [time + 1, None, None, outputs_ta, None]
        sample_ids = None
        if decoder.mode == ModeKeys.INFER:
            log_probs, lengths = args[0], args[1]
            bs_stat_ta = args[2]
            predicted_ids = args[3]
            with tf.variable_scope(self.name):
                decoder_top_features = self.merge_top_features(ret_val)
            logits = outputs_to_logits_fn(decoder_top_features)
            # sample next symbols
            sample_ids, beam_ids, next_log_probs, next_lengths \
                = helper.sample_symbols(logits, log_probs, finished, lengths, time=time)
            predicted_ids = gather_states(tf.reshape(predicted_ids, [-1, time + 1]), beam_ids)

            next_cache["decoding_states"] = gather_states(next_cache["decoding_states"], beam_ids)
            bs_stat = BeamSearchStateSpec(
                log_probs=next_log_probs,
                beam_ids=beam_ids)
            bs_stat_ta = nest.map_structure(lambda ta, out: ta.write(time, out),
                                            bs_stat_ta, bs_stat)
            next_predicted_ids = tf.concat([predicted_ids, tf.expand_dims(sample_ids, axis=1)], axis=1)
            next_predicted_ids = tf.reshape(next_predicted_ids, [-1])
            next_predicted_ids.set_shape([None])
            inner_loop_vars.extend([next_log_probs, next_lengths, bs_stat_ta, next_predicted_ids])

        next_finished, next_input_symbols = helper.next_symbols(time=time, sample_ids=sample_ids)
        next_inputs = target_to_embedding_fn(next_input_symbols, time + 1)

        next_finished = tf.logical_or(next_finished, finished)
        inner_loop_vars[1] = next_inputs
        inner_loop_vars[2] = next_cache
        inner_loop_vars[4] = next_finished
        return inner_loop_vars

    loop_vars = [initial_time, initial_inputs, initial_cache,
                 initial_outputs_ta, initial_finished]

    if decoder.mode == ModeKeys.INFER:  
        ...'''增加一些infer需要的參數(shù)'''

    '''while(cond(args)) loop(body(args))
    cond: tf.logical_not(tf.reduce_all(initial_finished))
    tf.logical_not:布爾值(組成的張量)not運算
    tf.reduce_all:計算張量在維度上的邏輯和(按某軸向全部取and)
    finished為布爾張量 記錄什么東西結(jié)束了
    循環(huán)到所有finished標記為真為止'''
    conf = lambda *args: tf.logical_not(tf.reduce_all(args[4]))
    res = tf.while_loop(
        conf,
        body_traininfer,
        loop_vars=loop_vars,
        parallel_iterations=parallel_iterations,
        swap_memory=swap_memory)

    ...'''從res中取出final_outputs'''

    if decoder.mode == ModeKeys.INFER:
        ...'''return final_outputs和一些state'''

    return final_outputs

decoder output-(merge)->logits-(softmax)->
*這部分比較復(fù)雜,還有很多沒看懂

Sequence2Sequence類中的_decode()方法:

    def _decode(self, encoder_output, input_fields):

        if '''mode == TRAIN or EVAL''':
            ...'''準備label和helper'''
        else:  ''' mode == INFER'''
            ...'''準備helper'''

        '''調(diào)用Decoder的decode()方法'''
        decoder_output, decoding_res = self._decoder.decode(
            encoder_output, bridge, helper,
            self._target_to_embedding_fn,
            self._outputs_to_logits_fn,
            beam_size=...)
        return decoder_output, decoding_res

1.3 Attention

如果不使用注意力機制,那么decoder在每一個時間片使用的都是同一個上下文向量(Context),即encoder output。
在有注意力機制的seq2seq模型中,針對decoder的每個時間片,有不同的Context,每個Context分配給encoder隱藏層狀態(tài)(h)的各時間片的權(quán)重不同。


context計算公式.png

注意力機制使得decoder第i個時間片的輸出不僅僅關(guān)注相對應(yīng)的encoder第j個時間片的隱藏層狀態(tài),還可以將前后的隱藏層狀態(tài)以一定權(quán)重輸入進來。

上圖中的α即attention weight,根據(jù)encoder的隱藏層狀態(tài)(h)和decoder的隱藏層狀態(tài)(H)得到。在不同種類的注意力機制中,attention weight有不同的計算方法。

1.3.1 加法注意力

BahnandauAttention中使用的是加法注意力:用一個全連接層得到attention score(logits,即下圖中的e),然后softmax得到attention weight(下圖中的α)

image.png

另一種常見的字母表達方式:把上式中decoder隱藏層狀態(tài)H替換為Q(query),encoder隱藏層狀態(tài)h替換為K(keys),即為:

attention_weight = softmax(tanh(W1*Q+W2*K))

1.3.2 乘法注意力

其他注意力模型中用到的是乘法注意力:乘法注意力不用使用一個全連接層,所以空間復(fù)雜度占優(yōu);另外由于乘法可以使用優(yōu)化的矩陣乘法運算,所以計算上也一般占優(yōu)。


乘法注意力.png

把上式中decoder隱藏層狀態(tài)H替換為Q(query),encoder隱藏層狀態(tài)h替換為K(keys),即為:

attention_weight = softmax(dot_product(Q, K))

Attention的看這里不要看上面
[深度概念]·Attention機制實踐解讀

#in encoder
EO, _ = encoder(...)
attention_values = EO
attention_keys = FC(EO)

#in decoder
attention_query = cell_output
#in attention.build()
query = FC(attention_query)
keys = attention_keys
memory = attention_values
score = att_fn(query, keys)#att_fn可以是加法乘法等
attention_weight = softmax(score)
context = attention_weight * memory
#then use context to infer
image.png

關(guān)于注意力的計算詳細:
提出Transformer的《Attention is all you need》論文原文
《attention is all you need》解讀

2. Transformer

Transformer模型.png

Transformer模型簡介

*以下來自:Transformer for NMT
總的來說,模型由encoder&decoder兩部分組成,左側(cè)的Stage 2,3構(gòu)成一層的encoder,右側(cè)的Stage 2,3,4構(gòu)成一層decoder, 在每一層中的每個Stage稱為一個子層(sublayer). 在transformer中,encoder和decoder同樣可以堆疊N個來構(gòu)建deeper model,論文中使用了6個encoders和6個decoders. 除此之外再加入輸入embedding,位置編碼(positional encoding)和輸出的dense layer,就是完整的transformer. 下面我們具體地分析各部分結(jié)構(gòu)。

*Transformer很復(fù)雜,還沒仔細看

二、運行過程

2.1 一些TF基礎(chǔ)知識

tensorflow中的Graph(圖)和Session(會話)的關(guān)系(大盤雞與紅燒肉)

TensorFlow 中的幾個關(guān)鍵概念:Tensor,Operation,Graph,Session

tensorflow中的Session()和run()
Hook? tf.train.SessionRunHook()介紹

*一個相似完整流程參考
[tf]使用attention機制進行NMT

NJUNMT
bin/train.py中實例化了一個TrainingExperiment對象,帶入模型配置運行訓(xùn)練。

    training_runner = TrainingExperiment(
        model_configs=model_configs)

    training_runner.run()

TrainingExperiment的run()方法定義:

#njunmt/nmt_experiment.py
 def run(self):
        """ 建立源語言與目標語言詞匯表"""
        vocab_source = Vocab(...)
        vocab_target = Vocab(...)
        eval_dataset = {
            "vocab_source": vocab_source,
            "vocab_target": vocab_target,
            "features_file": self._model_configs["data"]["eval_features_file"],
            "labels_file": self._model_configs["data"]["eval_labels_file"]}

        config = tf.ConfigProto()
        config.gpu_options.allow_growth = True
        config.allow_soft_placement = True

        """model_fn()建立模型"""
        estimator_spec = model_fn(...)
        train_ops = estimator_spec.train_ops
        hooks = estimator_spec.training_hooks

       """build training session"""
        sess = tf.train.MonitoredSession(
            session_creator=tf.train.ChiefSessionCreator(
                scaffold=tf.train.Scaffold(),
                checkpoint_dir=None,
                master="",
                config=config),
            hooks=tuple(hooks) + tuple(build_eval_metrics
                                       (self._model_configs, eval_dataset,
                                        model_name=estimator_spec.name)))

        ...
        """somehow 得到處理好的train data"""
        train_data = ...

        eidx = [0, 0]
        update_cycle = [self._model_configs["train"]["update_cycle"], 1]#[目標cycle數(shù),當(dāng)前cycle數(shù)]

        def step_fn(step_context):
            step_context.session.run(train_ops["zeros_op"])
            try:
                while update_cycle[0] != update_cycle[1]:
                    data = train_data.next()
                    step_context.session.run(
                        train_ops["collect_op"], feed_dict=data["feed_dict"])
                    update_cycle[1] += 1
                data = train_data.next()
                update_cycle[1] = 1
                return step_context.run_with_hooks(
                    train_ops["train_op"], feed_dict=data["feed_dict"])
            except StopIteration:
                eidx[1] += 1

        while not sess.should_stop():
            if eidx[0] != eidx[1]:
                tf.logging.info("STARTUP Epoch {}".format(eidx[1]))
                eidx[0] = eidx[1]
            sess.run_step_fn(step_fn)

*疑問:這里的sess.run_step_fn(step_fn)是怎么運行的?tf.train.MonitoredSession()類中沒有查到run_step_fn()方法

關(guān)于tf.train.MonitoredSession()
官方文檔給的定義是:
Session-like object that handles initialization, recovery and hooks.
是一個處理初始化,模型恢復(fù),和處理Hooks的類似于Session的類。

關(guān)于這里的Experiment,training_hooks都還缺乏了解

關(guān)于tf.estimator
Estimator對象包裝由model_fn指定的模型,其中,給定輸入和其他一些參數(shù),返回需要進行訓(xùn)練、計算,或預(yù)測的操作.
EstimatorSpec是定義model_fn返回的類(繼承自namedtuple),用于初始化Estimator

#njunmt/model/model_builder.py
class EstimatorSpec(
    namedtuple('EstimatorSpec', ['name', 'input_fields',
                                 'predictions', 'loss', 'train_ops',
                                 'training_chief_hooks', 'training_hooks'])):

"""創(chuàng)建NMT模型,根據(jù)train,evaluation,inference三種mode具體有不同的返回內(nèi)容"""
def model_fn(...):
    ...
    """創(chuàng)建模型實例"""
    model = eval(model_str)(
        params=model_configs["model_params"],...)
    ...
    """調(diào)用各Model類的build()方法"""
    def _build_model():
        ...
        if mode == ModeKeys.INFER:
            # model_output is prediction
            return _input_fields, _model_output
        elif mode == ModeKeys.EVAL:
            # model_output = (loss_sum, weight_sum), attention
            return _input_fields, _model_output[0], _model_output[1]
        elif mode == ModeKeys.TRAIN:  # mode == TRAIN
            ..."""something for train"""
            return _input_fields, _loss, grads
        ...

    model_returns = parallelism(_build_model)
    input_fields = model_returns[0]
    if mode == ModeKeys.INFER:
        ..."""something for inference"""
        return EstimatorSpec(...)

    if mode == ModeKeys.EVAL:
        ... """something for evaluation"""
        return EstimatorSpec(...)

    assert mode == ModeKeys.TRAIN
    ..."""something for train"""
    return EstimatorSpec(...)

三、調(diào)參基礎(chǔ)

機器學(xué)習(xí)中的正則化(Regularization)
Normalization(歸一化)
《理解Dropout》分享
Seq2Seq中的beam search算法
Tensorflow中優(yōu)化器--AdamOptimizer詳解

激活函數(shù).png

TensorFlow之RNN:堆疊RNN、LSTM、GRU及雙向LSTM
[深度概念]·Attention機制實踐解讀
變形金剛”為何強大:從模型到代碼全面解析Google Tensor2Tensor系統(tǒng)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容