目前 NLP 领域的不少任务基本都会朝深度学习、注意力模型、半监督等方向发展,并且确实也取得了更好的效果,而有些也会把深度学习和传统机器学习结合起来,都能有不错的性能提高。这里讲一个用深度学习和机器学习结合来作分词。git
分词就是将一句话按照最合理的单词分开,英语通常就没有这个麻烦,由于英语词语都是空格隔开的,而中文就须要作额外处理。分词任务通常是nlp其余任务的基础,分词分得好很差将直接对后面的其余任务产生很大的影响。github
在此以前,先了解分词的通常作法:算法
LSTM 是循环神经网络的一种变种,是处理序列的小能手,具体能够看前面的文章《LSTM神经网络》,而双向 LSTM 能够看前面文章《双向循环神经网络及TensorFlow实现》。json
CRF是一种几率无向图模型,也是处理序列的小能手,具体能够看前面的文章《机器学习之条件随机场(CRF)》。bash
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)
复制代码
-------------推荐阅读------------
跟我交流,向我提问:
公众号的菜单已分为“读书总结”、“分布式”、“机器学习”、“深度学习”、“NLP”、“Java深度”、“Java并发核心”、“JDK源码”、“Tomcat内核”等,可能有一款适合你的胃口。
欢迎关注: