自然語言處理和圖像處理不同,作為人類抽象出來的高級表達形式,它和圖像、聲音不同,圖像和聲音十分直覺,比如圖像的像素的顏色表達可以直接量化成數字輸入到神經網絡中,當然如果是經過壓縮的格式jpeg等必須還要經過一個解碼的過程才能變成像素的高階矩陣的形式,而自然語言則不同,自然語言和數字之間沒有那么直接的相關關系,也就不是那么容易作為特征輸入到神經網絡中去了,所以,用神經網絡處理自然語言,不可避免的在數據預處理方面更加繁瑣,也更加細致!自然語言處理的另外一個不同之處在於語言之間的相關關系,舉一個最簡單的例子,在做智能助理機器人的時候,一句“我將在上午十點到達北京”和“我將在上午十點離開北京” 如果你只考慮每個獨立的詞匯的話,那么“北京”到底是作為始發地還是目的地是不得而知的,也就是說你必須得聯系上下文才能夠更好的理解整個句子的意思!這也是自然語言的特別之處!當然針對語言的這種特性,也有相應的用於處理的網絡——RNN(recurrent neural network)循環神經網絡!
作為分類到Tensorflow編程實戰里的一篇博客,當然以解釋代碼為主,具體的理論部分這里不過多解釋,開門見山,本次處理的數據集是PTB數據集,它是目前語言模型學習中使用的最為廣泛的數據集,PTB數據集的下載地址是: http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz
將數據集解壓之后將會得到好幾個文件夾,重點看/data文件夾下的三個文件,ptb.test.txt, ptb.train.txt, ptb.valid.txt 這三個數據文件已經經過了預處理,相鄰的單詞之間用空格隔開,以ptb.train.txt為例,看一下它里面的具體內容:
no it was n't black monday but while the new york stock exchange did n't fall apart friday as the dow jones industrial average plunged N points most of it in the final hour it barely managed to stay this side of chaos
some circuit breakers installed after the october N crash failed their first test traders say unable to cool the selling panic in both stocks and futures
the N stock specialist firms on the big board floor
數據集中包含了9998個詞匯,加上稀有詞語的特殊符號 <unk>和語句的結束標記,一共是10000個! 為了能夠將數據輸入到神經網絡,我們首先需要做的就是這10000個詞匯按照其出現的頻率進行編號,形成詞匯表,然后按照詞匯表的對應關系,將train/test/valid數據集分別轉換成編號的形式!
按照單詞出現的頻率進行編號的代碼如下:
1 import codecs 2 import collections 3 from operator import itemgetter 4 5 RAW_DATA = "C:\\Users\\Yang\\Desktop\\nlp\\ptb.train.txt" #數據的輸入路徑/ 6 VOCAB_OUTPUT ="ptb.vocab" #詞匯表的輸出路徑 7 8 counter = collections.Counter() #counter 顧名思義是一個計數器 9 10 with codecs.open(RAW_DATA,"r","utf-8") as f: 以read的方式,utf-8編碼的形式打開上述路徑 11 for line in f: #讀行 12 for word in line.strip().split(): #以空格作為划分,將文件里面的每一個詞匯都切開! strip()默認用於去掉收尾的空格和換行符 13 counter[word] += 1 #統計單詞出現的次數 14 15 sorted_word_to_cnt = sorted(counter.items(),key=itemgetter(1),reverse=True) 按照itemgetter(1)屬性進行排序,應該就是按照單詞出現的次數排序,例如:and:981 16 sorted_words = [x[0] for x in sorted_word_to_cnt] #之所以取x[0]是因為格式是 the:356 這種形式,x[0]就是為了將單詞取出來 排好序的word 17 18 sorted_words = ["<eos>"] + sorted_words #將句子的結束符添加到排好序的list中 19 #因為PTB數據中已經將低頻詞匯替換成了<"unk">所以下面這步沒必要 <"eos">是句子結束符 <"sos">是句子開始符 <"unk">是句子的低頻詞匯 20 #sorted_words = ["<unk>","<sos>","<eos>"] + sorted_words 21 #if len(sorted_words) >10000: 22 #sorted_words = sorted_words[:10000] 標紅的這三句沒必要出現! 23 24 with codecs.open(VOCAB_OUTPUT,"w",'utf-8') as file_output : 25 26 for word in sorted_words: 27 file_output.write(word + "\n") #將排序好的詞匯再寫回輸出的文件目錄里面,那么就算完成了詞匯對應表的構建
寫完了詞匯表的構建代碼之后,我們還需要一個將ptb.train/test/valid.txt文件轉換成對應的編號的過程! 每一個詞匯對應的編號就是其在詞匯表里面對應的行號! 注意到之前每一個word的輸出后面都跟着"\n"
下面就來實現將單詞轉換成對應的編號的部分的代碼:
1 import codecs 2 import sys 3 4 RAW_DATA = 'C:\\Users\\Yang\\Desktop\\nlp\\data\\ptb.valid.txt' #這里是待轉換的文件的目錄/相應的改成ptb.test.txt/ptb.train.txt可以用於其他的轉換 5 VOCAB = 'ptb.vocab' #詞匯表的目錄 6 OUTPUT_DATA = 'ptb.valid' 用於輸出的目錄 7 8 9 with codecs.open(VOCAB,'r','utf-8') as f_vocab: #首先就是將詞匯表以read和utf-8編碼的方式打開 10 vocab = [w.strip() for w in f_vocab.readlines()] #我怎么感覺這里只是讀到了一行然后進行收尾空格換行符去掉的處理??? 11 #哦,我明白了 因為VOCAB中詞匯的存儲格式就是一個單詞占一行,所以出來的就是一個個單詞而不需要.split() 12 word_to_id = {k:v for (k,v) in zip(vocab,range(len(vocab)))} #轉換成了單詞序號的字典形式 這里是完成了詞匯和其對應編號的對應! 13 14 #將詞匯轉換成了對應的行號序號 15 def get_id(word): 16 return word_to_id[word] if word in word_to_id else word_to_id["<unk>"] #如果是在詞匯表里面的就將它轉換成對應的編號,否則的話就轉換成unk對應的編號 17 18 fin = codecs.open(RAW_DATA,"r","utf-8") #打開待轉換文件 19 fout = codecs.open(OUTPUT_DATA,"w",'utf-8') #輸出文件目錄 20 21 for line in fin: 22 words = line.strip().split() + ["<eos>"] #打開的文件讀取每一行然后首尾去除換行符和空格之后對空格進行切分,在末尾加上eos! 23 out_line = ' '.join([str(get_id(w)) for w in words]) +'\n' 對其的行進行轉換 24 fout.write(out_line) 然后寫入到對應的輸出文件里面 25 26 fin.close() 27 fout.close()
我們來看一下運行完這個代碼之后我們的ptb.train.txt變成了什么樣子:
9994 9996 9974 9999 9972 9978 9981 9993 9977 9973 9985 9998 9992 9971 9997 9990 9995 9970 9989 9987 9988 9975 9980 9986 0
沒錯,全部變成了詞匯表對應的行號編號的形式!
進行完上述兩步處理之后,我們已經完成了數據預處理部分的一半了,接下來我們需要考慮的問題就是,處理后的編碼數據不可能直接輸入到網絡里面去,網絡接受的一般都是許多batch,但是在將數據打包成batch
的時候,又有一個十分嚴峻的問題需要我們考慮,那就是到底以多長的長度來打包數據呢? 每條句子的長度是不一樣的,該如何打包呢? 有兩種比較常見的做法,一種是根據batch_size的大小,選取里面最長的句子為
參考,剩下的padding成統一的長度!長度統一之后就可以用於batch了!
而PTB數據是一整段一整段文本,如果一句一句的進行切分的話,會破壞掉數據之間的上下文的關聯,而語言模型為了利用上下文信息,必須將前面的信息傳遞到后面的句子中去,所以PTB通常采用的是另外一種Batch的方法!
該方法將長序列切割成固定長度的子序列,然后循環神經網絡處理完這個子序列之后將最后一個隱藏層的結果復制到下一個序列中作為初始值,這樣在前向計算的過程中效果就等同於一次性的讀取完了全部的文檔!
但是如果這樣做的話會面臨另外一個問題,整個數據流都是串行流動的,這對於可以並行計算的tensorflow來說簡直太浪費了,所以,兼顧二者的做法是,根據batch_size的大小將整個文檔划分成batch_size部分!然后讓batch_size的每一個位置負責一部分數據,如果處理不完,繼續橫向移動到下一個batch中去,也就是說,對於batch來講數據之間橫向才是連貫的,縱向其實是互不相干的!這樣既能夠保證數據之間的上下文關系,也能夠保證tensorflow可以並行處理數據!
畫一個示意圖的話,大概是下面這樣: