本文介紹了如何使用深度學(xué)習(xí)執(zhí)行文本實(shí)體提取。作者嘗試了分別使用深度學(xué)習(xí)和傳統(tǒng)方法來(lái)提取文章信息,結(jié)果深度學(xué)習(xí)的準(zhǔn)確率達(dá)到了 85%,遠(yuǎn)遠(yuǎn)領(lǐng)先于傳統(tǒng)算法的 65%。
引言
文本實(shí)體提取是自然語(yǔ)言處理(NLP)的主要任務(wù)之一。隨著近期深度學(xué)習(xí)領(lǐng)域快速發(fā)展,我們可以將這些算法應(yīng)用到 NLP 任務(wù)中,并得到準(zhǔn)確率遠(yuǎn)超傳統(tǒng)方法的結(jié)果。我嘗試過(guò)分別使用深度學(xué)習(xí)和傳統(tǒng)方法來(lái)提取文章信息,結(jié)果非常驚人:深度學(xué)習(xí)的準(zhǔn)確率達(dá)到了 85%,遠(yuǎn)遠(yuǎn)領(lǐng)先于傳統(tǒng)算法的 65%。
本項(xiàng)目的目標(biāo)是把文章中的每個(gè)單詞標(biāo)注為以下四種類(lèi)別之一:組織、個(gè)人、雜項(xiàng)以及其他;然后找到文中最突出的組織和名稱(chēng)。深度學(xué)習(xí)模型對(duì)每個(gè)單詞完成上述標(biāo)注,隨后,我們使用基于規(guī)則的方法來(lái)過(guò)濾掉我們不想要的標(biāo)注,并確定最突出的名稱(chēng)和組織。
在這里要感謝 Guillaume Genthial 這篇關(guān)于序列標(biāo)注的文章(https://guillaumegenthial.github.io/),本項(xiàng)目建立在這篇文章的基礎(chǔ)之上。
模型的高級(jí)架構(gòu)
架構(gòu)
上圖是對(duì)每個(gè)單詞進(jìn)行分類(lèi)標(biāo)注的模型高級(jí)架構(gòu)。在建模過(guò)程中,最耗時(shí)間的部分是單詞分類(lèi)。我將解釋模型的每個(gè)組成部分,幫助讀者對(duì)模型組件有一個(gè)全面的、更高層次的理解。通常,模型組件可分為三部分:
單詞表征:在建模第一步,我們需要做的是加載一些預(yù)訓(xùn)練詞嵌入(GloVe)。同時(shí),我們需要從字符中提取出一些含義。
語(yǔ)境單詞表征:我們需要利用 LSTM,對(duì)語(yǔ)境中的每一個(gè)單詞得到一個(gè)有意義的表征。
解碼:當(dāng)我們得到表示單詞的向量后,我們就可以用它進(jìn)行預(yù)測(cè)。
hot encoding(用數(shù)值表示單詞)
深度學(xué)習(xí)算法只接受數(shù)值型數(shù)據(jù)作為輸入,而無(wú)法處理文本數(shù)據(jù)。如果想要在大量的非數(shù)值場(chǎng)景下使用深度神經(jīng)網(wǎng)絡(luò),就需要將輸入數(shù)據(jù)轉(zhuǎn)變數(shù)值形式。這個(gè)過(guò)程就是 hot encoding。
下面是一小段實(shí)現(xiàn) hot encoding 的代碼示例:
word_counts = Counter(words)sorted_vocab = sorted(word_counts, key=word_counts.get, reverse=True)int_to_vocab = {ii: word for ii, word in enumerate(sorted_vocab)}vocab_to_int = {word: ii for ii, word in int_to_vocab.items()}
同樣地,我們必須獲取輸入數(shù)據(jù)中的所有字符,然后將其轉(zhuǎn)化為向量,作為字符嵌入。
單詞嵌入 & 字符嵌入
單詞嵌入是處理文本問(wèn)題時(shí)使用的一種通過(guò)學(xué)習(xí)得到的表征方式,其中含義相同的單詞表征相近。通常,我們利用神經(jīng)網(wǎng)絡(luò)來(lái)實(shí)現(xiàn)單詞嵌入,其中使用的單詞或短語(yǔ)來(lái)自于詞庫(kù),并需要轉(zhuǎn)變?yōu)閷?shí)數(shù)構(gòu)成的向量形式。
但是,在數(shù)據(jù)集上生成詞向量計(jì)算成本很高,我們可以使用一些預(yù)訓(xùn)練的單詞嵌入來(lái)避免這個(gè)問(wèn)題:比如使用斯坦福大學(xué)的 NLP 研究者提供的 GloVe 向量。
字符嵌入是字符的向量表征,可用于推導(dǎo)詞向量。之所以會(huì)使用字符嵌入,是因?yàn)樵S多實(shí)體并沒(méi)有對(duì)應(yīng)的預(yù)訓(xùn)練詞向量,所以我們需要用字符向量來(lái)計(jì)算詞向量。
LSTM
傳統(tǒng)神經(jīng)網(wǎng)絡(luò) VS 循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)
循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)是人工神經(jīng)網(wǎng)絡(luò)的一種,用于序列數(shù)據(jù)中的模式識(shí)別,例如文本、基因組、手寫(xiě)筆跡、口語(yǔ)詞匯,或者來(lái)自傳感器、股市和政府機(jī)構(gòu)的數(shù)值型時(shí)間序列數(shù)據(jù)。它可以「理解」文本的語(yǔ)境含義。
RNN 神經(jīng)元
LSTM 是一種特殊的循環(huán)神經(jīng)網(wǎng)絡(luò),相比于簡(jiǎn)單的循環(huán)神經(jīng)網(wǎng)絡(luò),它可以存儲(chǔ)更多的語(yǔ)境信息。簡(jiǎn)單的 RNN 和 LSTM 之間的主要區(qū)別在于它們各自神經(jīng)元的結(jié)構(gòu)不同。
對(duì)于語(yǔ)境中的每一個(gè)單詞,我們都需要利用 LSTM 得到它在所處語(yǔ)境中的有意義表征。
條件隨機(jī)場(chǎng)(CRF)
在預(yù)測(cè)標(biāo)注最后的解碼步驟中,我們可以使用 softmax 函數(shù)。當(dāng)我們使用 softmax 函數(shù)時(shí),它給出單詞屬于每個(gè)分類(lèi)的概率。但這個(gè)方法給出的是局部選擇;換句話(huà)說(shuō),即使我們從文本語(yǔ)境中提取出了一些信息,標(biāo)注決策過(guò)程依然是局部的,我們?cè)谑褂?softmax 激活函數(shù)時(shí),并沒(méi)有使用到鄰近單詞的標(biāo)注決策。例如,在「New York」這個(gè)詞中,我們將「York」標(biāo)注為一個(gè)地方,事實(shí)上,這應(yīng)該可以幫助我們確定『New』對(duì)應(yīng)地方的開(kāi)始。
在 CRF 中,我們的輸入數(shù)據(jù)是序列數(shù)據(jù);同時(shí),我們?cè)谀硞€(gè)數(shù)據(jù)點(diǎn)上進(jìn)行預(yù)測(cè)時(shí),需要考慮先前文本的語(yǔ)境。在本項(xiàng)目中,我們使用的是線(xiàn)性鏈 CRF。在線(xiàn)性鏈 CRF 中,特征只依賴(lài)當(dāng)前標(biāo)注和之前的標(biāo)注,而不是整個(gè)句子中的任意標(biāo)注。
為了對(duì)這個(gè)行為建模,我們將使用特征函數(shù),該函數(shù)包含多個(gè)輸入值:
句子s
單詞在句子中的位置i
當(dāng)前單詞的標(biāo)注 l_i
前一個(gè)單詞的標(biāo)注 l_i?1
接下來(lái),對(duì)每一個(gè)特征函數(shù) f_j 賦予權(quán)重 λ_j。給定一個(gè)句子s,現(xiàn)在我們可以根據(jù)下式計(jì)算s的標(biāo)注l:對(duì)句子中所有單詞的加權(quán)特征求和。
基于詞性標(biāo)注的特征函數(shù)示例
如果 l_i= ADVERB,且第 i 個(gè)單詞以『-ly』結(jié)尾,則 f_1(s,i,l_i,l_i?1)=1,否則取 0。如果對(duì)應(yīng)的權(quán)重 λ1 為正,且非常大,那么這個(gè)特征基本上就表示我們傾向于把以『-ly』結(jié)尾的單詞標(biāo)注為 ADVERB。
如果 i=1,l_i= VERB,且句子以問(wèn)號(hào)結(jié)尾,則 f_2(s,i,l_i,l_i?1)=1,否則取 0。如果對(duì)應(yīng)的權(quán)重 λ2 為正,且非常大,那么這個(gè)特征基本上就表示我們傾向于把疑問(wèn)句的第一個(gè)單詞標(biāo)為 VERB。(例,「Is this a sentence beginning with a verb?」)
如果 l_i?1= ADJECTIVE,且 l_i= NOUN,則 f_3(s,i,l_i,l_i?1)=1,否則為0。對(duì)應(yīng)權(quán)重為正時(shí),表示我們傾向于認(rèn)為名詞跟在形容詞之后。
如果 l_i?1= PREPOSITION,且 l_i= PREPOSITION,則 f_4(s,i,l_i,l_i?1)=1。此函數(shù)對(duì)應(yīng)的權(quán)重 λ4 為負(fù),表示介詞不應(yīng)該跟著另一個(gè)介詞,因此我們應(yīng)該避免這樣的標(biāo)注出現(xiàn)。
最后,我們可以通過(guò)取指數(shù)和歸一化,將這些得分轉(zhuǎn)換為 0~1 之間的概率 p(l|s)。
總之,要建立一個(gè)條件隨機(jī)場(chǎng),你只需要定義一組特征函數(shù)(可以依賴(lài)于整個(gè)句子、單詞的當(dāng)前位置和附近單詞的標(biāo)注)、賦予權(quán)重,然后加起來(lái),最后如果有需要,轉(zhuǎn)化為概率形式。簡(jiǎn)單地說(shuō),需要做兩件事情:
1. 找到得分最高的標(biāo)注序列;
2. 在全體標(biāo)注序列上求出概率分布。
幸運(yùn)的是,TensorFlow 提供了相關(guān)的庫(kù),幫助我們可以很容易地實(shí)現(xiàn) CRF。
log_likelihood, transition_params=tf.contrib.crf.crf_log_likelihood(scores, labels, sequence_lengths)
模型的運(yùn)行原理
對(duì)于每一個(gè)單詞,我們希望建立一個(gè)向量來(lái)捕捉其意義以及和任務(wù)相關(guān)的特征。我們將該向量構(gòu)建為 GloVe 單詞嵌入與包含字符級(jí)特征的向量的級(jí)聯(lián)。我們還可以選擇使用一些特定的神經(jīng)網(wǎng)絡(luò),自動(dòng)提取出這些特征。在本文中,我們將在字符層面上使用雙向 LSTM 算法。
我們將 CONLL 數(shù)據(jù)集中的所有單詞都進(jìn)行 hot-encode,這些單詞都在 GloVe 單詞嵌入中有對(duì)應(yīng)的實(shí)體。如上文所述,神經(jīng)網(wǎng)絡(luò)只接受向量,不接受文本,因此我們需要將單詞轉(zhuǎn)換為向量。CONLL 數(shù)據(jù)集包含單詞及其對(duì)應(yīng)標(biāo)注。在 hot encoding 后,單詞和標(biāo)注都被轉(zhuǎn)換成了向量。
用于 hot encoding 單詞及其對(duì)應(yīng)標(biāo)注的代碼:
with open(self.filename) as f: words, tags = [], [] for line in f: line = line.strip() if (len(line) == 0 or line.startswith("-DOCSTART-")): if len(words) != 0: niter += 1 if self.max_iter is not None and niter > self.max_iter: break yield words, tags words, tags = [], [] else: ls = line.split(' ') word, tag = ls[0],ls[-1] if self.processing_word is not None: word = self.processing_word(word) if self.processing_tag is not None: tag = self.processing_tag(tag) words += [word] tags += [tag]
用于提取單詞、標(biāo)注和字符向量的代碼:
if vocab_chars is not None and chars == True: char_ids = [] for char in word: # ignore chars out of vocabulary if char in vocab_chars: char_ids += [vocab_chars[char]]if lowercase: word = word.lower()if word.isdigit(): word = NUMif vocab_words is not None: if word in vocab_words: word = vocab_words[word] else: if allow_unk: word = vocab_words[UNK] else: print(word) print(vocab_words)if vocab_chars is not None and chars == True: return char_ids, wordelse: return word
現(xiàn)在,我們使用 TensorFlow 內(nèi)置的函數(shù)加載單詞嵌入。假定 embeddings 是一個(gè) GloVe 嵌入的 numpy 數(shù)組,其中 embeddings[i] 表示第 i 個(gè)單詞的向量形式。
L = tf.Variable(embeddings, dtype=tf.float32, trainable=False)pretrained_embeddings = tf.nn.embedding_lookup(L, word_ids)
現(xiàn)在,我們可以構(gòu)建根據(jù)字符得到的單詞嵌入。這里,我們不需要任何預(yù)訓(xùn)練字符嵌入。
_char_embeddings = tf.get_variable( nam, dtype=tf.float32, shape=[self.config.nchars, self.config.dim_char])char_embeddings = tf.nn.embedding_lookup(_char_embeddings, self.char_ids_tensor, nam)s = tf.shape(char_embeddings)char_embeddings = tf.reshape(char_embeddings, shape=[s[0]*s[1], s[-2], self.config.dim_char])word_lengths = tf.reshape(self.word_lengths_tensor, shape=[s[0]*s[1]])cell_fw = tf.contrib.rnn.LSTMCell(self.config.hidden_size_char, state_is_tuple=True)cell_bw = tf.contrib.rnn.LSTMCell(self.config.hidden_size_char, state_is_tuple=True)_output = tf.nn.bidirectional_dynamic_rnn( cell_fw, cell_bw, char_embeddings, sequence_length=word_lengths, dtype=tf.float32)
一旦得到了單詞表征,我們就可以直接在詞向量序列上運(yùn)行 bi-LSTM,得到另一個(gè)向量序列。
cell_fw = tf.contrib.rnn.LSTMCell(self.config.hidden_size_lstm)cell_bw = tf.contrib.rnn.LSTMCell(self.config.hidden_size_lstm)(output_fw, output_bw), _ = tf.nn.bidirectional_dynamic_rnn( cell_fw, cell_bw, self.word_embeddings, sequence_length=self.sequence_lengths_tensor, dtype=tf.float32)output = tf.concat([output_fw, output_bw], axis=-1)output = tf.nn.dropout(output, self.dropout_tensor)
現(xiàn)在,每個(gè)單詞都和一個(gè)向量對(duì)應(yīng),其中向量記錄了這個(gè)單詞的含義、字符和語(yǔ)境。我們使用向量來(lái)做最后的預(yù)測(cè)。我們可以使用全連接神經(jīng)網(wǎng)絡(luò)求出一個(gè)向量,該向量中每個(gè)條目對(duì)應(yīng)每個(gè)標(biāo)注的得分。
W = tf.get_variable("W", dtype=tf.float32, shape=[2*self.config.hidden_size_lstm, self.config.ntags])b = tf.get_variable("b", shape=[self.config.ntags], dtype=tf.float32, initializer=tf.zeros_initializer())nsteps = tf.shape(output)[1]output = tf.reshape(output, [-1, 2*self.config.hidden_size_lstm])pred = tf.matmul(output, W) + bself.logits = tf.reshape(pred, [-1, nsteps, self.config.ntags])
最后,我們使用 CRF 方法來(lái)計(jì)算每個(gè)單詞的標(biāo)注。實(shí)現(xiàn) CRF 只需要一行代碼!下面的代碼計(jì)算出了損失,同時(shí)返回了在預(yù)測(cè)時(shí)很有用的 trans_params。
log_likelihood, _trans_params = tf.contrib.crf.crf_log_likelihood(self.logits, self.labels_tensor, self.sequence_lengths_tensor)self.trans_params = _trans_paramsself.loss = tf.reduce_mean(-log_likelihood)
現(xiàn)在,我們可以定義我們的訓(xùn)練算子:
optimizer = tf.train.AdamOptimizer(self.lr_tensor)self.train_op = optimizer.minimize(self.loss)
一旦我們定義好模型,在數(shù)據(jù)集上完成很少的幾次迭代,就可以得到訓(xùn)練好的模型了。
如何使用訓(xùn)練好的模型
TensorFlow 提供了存儲(chǔ)模型權(quán)重的功能,這樣我們就可以在之后的場(chǎng)景中復(fù)原訓(xùn)練好的模型。無(wú)論什么時(shí)候需要進(jìn)行預(yù)測(cè),我們都可以加載模型權(quán)重,這樣就不需要重新訓(xùn)練了。
def save_session(self): """Saves session = weights""" if not os.path.exists(self.config.dir_model): os.makedirs(self.config.dir_model) self.saver.save(self.sess, self.config.dir_model)def restore_session(self, dir_model): self.saver.restore(self.sess, dir_model)
每篇文章都被分解為單詞再輸入到模型中,然后經(jīng)過(guò)上文所述一系列過(guò)程,得到輸出結(jié)果。模型最終輸出結(jié)果將每個(gè)單詞分為 4 類(lèi):組織、個(gè)人、雜項(xiàng)以及其他。這個(gè)算法通過(guò)基于規(guī)則的方法過(guò)濾結(jié)果,然后進(jìn)一步正確提取出文本中最突出的名稱(chēng)和組織,它并沒(méi)有達(dá)到 100% 的準(zhǔn)確率。
-
神經(jīng)網(wǎng)絡(luò)
+關(guān)注
關(guān)注
42文章
4771瀏覽量
100766 -
智能計(jì)算
+關(guān)注
關(guān)注
0文章
178瀏覽量
16468 -
工業(yè)物聯(lián)網(wǎng)
+關(guān)注
關(guān)注
25文章
2377瀏覽量
64217
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論