雙向循環神經網絡+條件隨機場進行分詞

前言

目前 NLP 領域的不少任務基本都會朝深度學習、注意力模型、半監督等方向發展,並且確實也取得了更好的效果,而有些也會把深度學習和傳統機器學習結合起來,都能有不錯的性能提高。這裏講一個用深度學習和機器學習結合來作分詞。git

關於分詞

分詞就是將一句話按照最合理的單詞分開,英語通常就沒有這個麻煩,由於英語詞語都是空格隔開的,而中文就須要作額外處理。分詞任務通常是nlp其餘任務的基礎,分詞分得好很差將直接對後面的其餘任務產生很大的影響。github

傳統作法

在此以前,先了解分詞的通常作法:算法

  • 基於詞典正向最大匹配法,很簡單的從左往右的規則匹配,相似的還有逆向最大匹配法。
  • 基於詞典最小切分法,通用是規則匹配,它使句子儘量少單詞數量。
  • 基於n元文法的分詞法,主要是經過大量語料統計單詞或字的轉換機率,並經過動態歸劃的算法算出最後最優的分詞序列。
  • 隱馬爾科夫模型分詞法,主要經過大量語料的觀測序列和狀態學習到參數,而後對觀測序列進行隱含狀態推測,也是須要解碼的過程,解碼完成及分詞完成。
  • 條件隨機場分詞法,經過大量語料學習到參數,這裏須要設計不少特徵函數和轉移函數,條件隨機場分詞準確率很高,它比隱馬爾可夫的精度高不少,由於條件隨機場考慮了上下文。

關於LSTM

LSTM 是循環神經網絡的一種變種,是處理序列的小能手,具體能夠看前面的文章《LSTM神經網絡》,而雙向 LSTM 能夠看前面文章《雙向循環神經網絡及TensorFlow實現》。json

關於CRF

CRF是一種機率無向圖模型,也是處理序列的小能手,具體能夠看前面的文章《機器學習之條件隨機場(CRF)》。bash

LSTM+CRF

LSTM 和 CRF 咱們都瞭解了,單獨使用它們都挺好理解,但如何將它們結合起來是咱們更關注的。網絡

其實若是沒有 CRF 參與其實也是能夠完成任務的,咱們說單向 LSTM 網絡由於沒考慮上下文,因此引入了雙向 LSTM 網絡,此時每一個詞通過詞嵌入層再進入前向和後向循環神經網絡,這時輸出能獲得標籤的機率。以下圖,併發

這裏寫圖片描述

在沒有 CRF 參與的時候可能會存在一個小缺陷,它沒辦法約束標籤的特徵,好比某標籤到另一標籤的轉換機率。若是有標籤的特徵就能進一步提升學習能力。app

這裏寫圖片描述

因此最終的網絡結構圖以下,第一層爲詞嵌入層,第二層爲雙向循環神經網絡層,正向網絡的輸出和反向網絡的輸出分別做爲輸入輸到一個隱含層,最後再輸入到 CRF 層。dom

這裏寫圖片描述

分詞標籤

咱們能夠設定狀態值集合S爲(B, M, E,S),分別表明每一個狀態表明的是該字在詞語中的位置,B表明該字是詞語中的起始字,M表明是詞語中的中間字,E表明是詞語中的結束字,S則表明是單字成詞。機器學習

核心代碼

https://github.com/sea-boat/nlp_lab/tree/master/bilstm_crf_seg

建立詞彙

def create_vocab(text):
    unique_chars = ['<NUM>', '<UNK>', '<ENG>'] + list(set(text))
    print(unique_chars)
    vocab_size = len(unique_chars)
    vocab_index_dict = {}
    index_vocab_dict = {}
    for i, char in enumerate(unique_chars):
        vocab_index_dict[char] = i
        index_vocab_dict[i] = char
    return vocab_index_dict, index_vocab_dict, vocab_size
複製代碼

處理字符首先就是須要建立包含語料中全部的詞的詞彙,須要一個從字符到詞彙位置索引的詞典,也須要一個從位置索引到字符的詞典。

詞彙保存及讀取

def load_vocab(vocab_file):
    with codecs.open(vocab_file, 'r', encoding='utf-8') as f:
        vocab_index_dict = json.load(f)
    index_vocab_dict = {}
    vocab_size = 0
    for char, index in iteritems(vocab_index_dict):
        index_vocab_dict[index] = char
        vocab_size += 1
    return vocab_index_dict, index_vocab_dict, vocab_size


def save_vocab(vocab_index_dict, vocab_file):
    with codecs.open(vocab_file, 'w', encoding='utf-8') as f:
        json.dump(vocab_index_dict, f, indent=2, sort_keys=True)
複製代碼

第一次建立詞彙後咱們須要將它保存下來,後面在使用模型預測時須要讀取該詞彙,若是不保存而每次都建立的話則可能致使詞彙順序不一樣。

批量遍歷器

def batch_yield(data, batch_size, vocab, tag2label, shuffle=False):
    if shuffle:
        random.shuffle(data)
    seqs, labels = [], []
    for (sent_, tag_) in data:
        sent_ = sentence2id(sent_, vocab)
        label_ = [tag2label[tag] for tag in tag_]
        if len(seqs) == batch_size:
            yield seqs, labels
            seqs, labels = [], []
        seqs.append(sent_)
        labels.append(label_)

    if len(seqs) != 0:
        yield seqs, labels
複製代碼

構建圖

建立須要的佔位符,分別爲輸入佔位符、標籤佔位符、序列長度佔位符、dropout佔位符和學習率佔位符。

word_ids = tf.placeholder(tf.int32, shape=[None, None], name="word_ids")
labels = tf.placeholder(tf.int32, shape=[None, None], name="labels")
sequence_lengths = tf.placeholder(tf.int32, shape=[None], name="sequence_lengths")
dropout_pl = tf.placeholder(dtype=tf.float32, shape=[], name="dropout")
lr_pl = tf.placeholder(dtype=tf.float32, shape=[], name="lr")
複製代碼

建立嵌入層,

with tf.variable_scope("words"):
    _word_embeddings = tf.Variable(embeddings, dtype=tf.float32, trainable=True, name="_word_embeddings")
    word_embeddings = tf.nn.embedding_lookup(params=_word_embeddings, ids=word_ids, name="word_embeddings")
word_embeddings = tf.nn.dropout(word_embeddings, dropout_pl)
複製代碼

建立向前 LSTM 網絡和向後 LSTM 網絡,

cell_fw = LSTMCell(hidden_dim)
cell_bw = LSTMCell(hidden_dim)
(output_fw_seq, output_bw_seq), _ = tf.nn.bidirectional_dynamic_rnn(cell_fw=cell_fw, cell_bw=cell_bw,
                                                                            inputs=word_embeddings,
                                                                            sequence_length=sequence_lengths,
                                                                            dtype=tf.float32)
複製代碼

將兩個方向的網絡輸出鏈接起來並輸入到一個隱含層,獲得預測結果,

output = tf.concat([output_fw_seq, output_bw_seq], axis=-1)
output = tf.nn.dropout(output, dropout_pl)
W = tf.get_variable(name="W", shape=[2 * hidden_dim, label_num],
                            initializer=tf.contrib.layers.xavier_initializer(),
                            dtype=tf.float32)
b = tf.get_variable(name="b", shape=[label_num], initializer=tf.zeros_initializer(), dtype=tf.float32)
s = tf.shape(output)
output = tf.reshape(output, [-1, 2 * hidden_dim])
pred = tf.matmul(output, W) + b
logits = tf.reshape(pred, [-1, s[1], label_num])
labels_softmax_ = tf.argmax(logits, axis=-1)
labels_softmax_ = tf.cast(labels_softmax_, tf.int32)
複製代碼

最後再添加一個 crf 層,

log_likelihood, transition_params = crf_log_likelihood(inputs=logits, tag_indices=labels,
                                                               sequence_lengths=sequence_lengths)
複製代碼

定義損失函數,

loss = -tf.reduce_mean(log_likelihood)
複製代碼

使用 adam 優化器來優化。

with tf.variable_scope("train_step"):
    global_step = tf.Variable(0, name="global_step", trainable=False)
    optim = tf.train.AdamOptimizer(learning_rate=lr_pl)
    grads_and_vars = optim.compute_gradients(loss)
    grads_and_vars_clip = [[tf.clip_by_value(g, -clip_grad, clip_grad), v] for g, v in grads_and_vars]
    train_op = optim.apply_gradients(grads_and_vars_clip, global_step=global_step)
複製代碼

-------------推薦閱讀------------

個人2017文章彙總——機器學習篇

個人2017文章彙總——Java及中間件

個人2017文章彙總——深度學習篇

個人2017文章彙總——JDK源碼篇

個人2017文章彙總——天然語言處理篇

個人2017文章彙總——Java併發篇


跟我交流,向我提問:

這裏寫圖片描述

公衆號的菜單已分爲「讀書總結」、「分佈式」、「機器學習」、「深度學習」、「NLP」、「Java深度」、「Java併發核心」、「JDK源碼」、「Tomcat內核」等,可能有一款適合你的胃口。

爲何寫《Tomcat內核設計剖析》

歡迎關注:

這裏寫圖片描述
相關文章
相關標籤/搜索