双向循环神经网络+条件随机场进行分词

前言

目前 NLP 领域的很多任务基本都会朝深度学习、注意力模型、半监督等方向发展,而且确实也取得了更好的效果,而有些也会把深度学习和传统机器学习结合起来,都能有不错的性能提升。这里讲一个用深度学习和机器学习结合来做分词。

关于分词

分词就是将一句话按照最合理的单词分开,英语一般就没有这个麻烦,因为英语词语都是空格隔开的,而中文就需要做额外处理。分词任务一般是nlp其他任务的基础,分词分得好不好将直接对后面的其他任务产生很大的影响。

传统做法

在此之前,先了解分词的一般做法: 
* 基于词典正向最大匹配法,很简单的从左往右的规则匹配,类似的还有逆向最大匹配法。 
* 基于词典最小切分法,通用是规则匹配,它使句子尽可能少单词数量。 
* 基于n元文法的分词法,主要是通过大量语料统计单词或字的转换概率,并通过动态归划的算法算出最后最优的分词序列。 
* 隐马尔科夫模型分词法,主要通过大量语料的观测序列和状态学习到参数,然后对观测序列进行隐含状态推测,也是需要解码的过程,解码完成及分词完成。 
* 条件随机场分词法,通过大量语料学习到参数,这里需要设计很多特征函数和转移函数,条件随机场分词准确率很高,它比隐马尔可夫的精度高很多,因为条件随机场考虑了上下文。

关于LSTM

LSTM 是循环神经网络的一种变种,是处理序列的小能手,具体可以看前面的文章《LSTM神经网络》,而双向 LSTM 可以看前面文章《双向循环神经网络及TensorFlow实现》。

关于CRF

CRF是一种概率无向图模型,也是处理序列的小能手,具体可以看前面的文章《机器学习之条件随机场(CRF)》。

LSTM+CRF

LSTM 和 CRF 我们都了解了,单独使用它们都挺好理解,但如何将它们结合起来是我们更关注的。

其实如果没有 CRF 参与其实也是可以完成任务的,我们说单向 LSTM 网络因为没考虑上下文,所以引入了双向 LSTM 网络,此时每个词经过词嵌入层再进入前向和后向循环神经网络,这时输出能得到标签的概率。如下图,

这里写图片描述

在没有 CRF 参与的时候可能会存在一个小缺陷,它没办法约束标签的特征,比如某标签到另外一标签的转换概率。如果有标签的特征就能进一步提高学习能力。

这里写图片描述

所以最终的网络结构图如下,第一层为词嵌入层,第二层为双向循环神经网络层,正向网络的输出和反向网络的输出分别作为输入输到一个隐含层,最后再输入到 CRF 层。

这里写图片描述

分词标签

我们可以设定状态值集合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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

处理字符首先就是需要创建包含语料中所有的词的词汇,需要一个从字符到词汇位置索引的词典,也需要一个从位置索引到字符的词典。

词汇保存及读取

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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

第一次创建词汇后我们需要将它保存下来,后面在使用模型预测时需要读取该词汇,如果不保存而每次都创建的话则可能导致词汇顺序不同。

批量遍历器

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

构建图

创建需要的占位符,分别为输入占位符、标签占位符、序列长度占位符、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")
  • 1
  • 2
  • 3
  • 4
  • 5

创建嵌入层,

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)
  • 1
  • 2
  • 3
  • 4

创建向前 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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

将两个方向的网络输出连接起来并输入到一个隐含层,得到预测结果,

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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

最后再添加一个 crf 层,

log_likelihood, transition_params = crf_log_likelihood(inputs=logits, tag_indices=labels,
                                                               sequence_lengths=sequence_lengths)
  • 1
  • 2

定义损失函数,

loss = -tf.reduce_mean(log_likelihood)
  • 1

使用 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)
阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kwame211/article/details/80347560
个人分类: 算法
上一篇混沌神经网络学习笔记四
下一篇蚁群算法原理及Matlab实现
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭