自從開始使用Python做深度學習的相關項目時,大部分時候或者說基本都是在研究圖像處理與分析方面,但是找工作反而碰到了很多關於自然語言處理(natural language processing: NLP)的問題,所以決定花點時間學習並且寫下來,希望那些跟我一樣同時在學習NLP的朋友能有一些幫助,學習過程中以英文為文本標准,后期會嘗試用中文,並且將相關的信息補進來博客文章中。剛開始學習哪里講得不好,多海涵並且歡迎指出。
NLP的首要任務就是將文本內容做Tokenization(標識化)處理,也就是說我們將文本分割成一個小塊一個小塊的例如以一個英文單詞為單位或者一個漢字為單位,這樣子的操作主要是方便我們可以更集中的去分析文本信息的內容和文本想表達的含義。當然分割是一個大范圍,不僅僅是文本分成不同的詞,也可以將整個文本分成段落,進而分成句子,句子在細分到詞。當然,我們一般所說的標識化就是將整句分割為單個標識符(tokens)。
文本標識化主要是將沒有結構的數據(unstructured data)或者自然語言文本分割成不同的分散的(discrete)塊信息,從而可以被進行統計計算,這樣子做的原因是我們可以通過計算每個標識符(tokens)在文件中出現的頻次從而形成一個頻次向量(vector)組來表示文件,因為計算機是無法直接處理我們人類所能辨識的文本信息,所以我們用統計的方式將純文本信息轉化為數字信息。完成了轉換之后,我們就可以直接將這些頻次向量作為特征輸入到機器學習或者深度學習相關的算法模型中去進行訓練。
當我們實際處理的時候一個比較簡單的標記化處理就是利用空格作為分隔符將字符串分割,下面的代碼是簡單的將英文句子進行分割:
1 sentence_example = """I love deep learning, I love natural language processing.""" 2 token_seq1 = str.split(sentence_example) 3 token_seq2 = sentence_example.split(' ') 4 print("token_seq1: ", token_seq1) 5 print("token_seq2: ", token_seq2)
第二和第三行采用不同形式對句子進切分割,輸出的結果是一樣的:
token_seq1: ['I', 'love', 'deep', 'learning,', 'I', 'love', 'natural', 'language', 'processing.'] token_seq2: ['I', 'love', 'deep', 'learning,', 'I', 'love', 'natural', 'language', 'processing.']
但是我們可以看出上面的做的標識化的結果是有問題的,processing后面跟了句號,正常情況下,我們在一個句子的末尾是不需要跟着句號的在做自然語言處理分析的時候,當然這個情況分的比較多種,倘若我們句子的最后是一個數字13,那么‘13.’是表示13呢還是標識一個13之后有小數呢?這點需要我們進行一定的的解析。這里我們先不對句號等標點符號進行處理,隨着學習的深入,我們也會遇到,到時再以相應的技巧進行處理。
在完成句子詞結構分割之后,我們需要做的就是將其轉化成計算機系統能識別和處理的數字,這個過程我們叫做one-hot-encoding:即我們可以創建一個數組(矩陣),數組由多個向量組成,每個向量中有一個數字為1,其余數字為0,1在向量所在的位置代表的是該英文單詞出現的位置,one-hot也就因此而來,下面代碼演示如何進行one-hot-encoding:
1 import numpy as np 2 3 token_seq = str.split(sentence_example) # 使用str.plit()函數分割句子 4 vocab = sorted(set(token_seq)) # set函數找出句子中所有獨特唯一的單詞,sorted函數則按照數字字母(先大寫后小寫)方式排序 5 ','.join(vocab) # 按照分割的單詞組成新的語句 6 num_tokens = len(token_seq) # 讓我們知道句子是由多少個標識符組成 7 print("The total tokens are: ", num_tokens) 8 vocab_size = len(vocab) # 句子中包含多少個單詞(這里統計的是非重復的,區別於標識符) 9 print("the vocabulary size is: ", vocab_size) 10 one_hot_vector = np.zeros((num_tokens, vocab_size), int) # 創建一個空矩陣來初始化one-hot-vectors 11 for i, word in enumerate(token_seq): 12 one_hot_vector[i, vocab.index(word)] = 1 13 14 print(one_hot_vector)
輸出結果為:
The total tokens are: 9 the vocabulary size is: 7 [[1 0 0 0 0 0 0] [0 0 0 0 1 0 0] [0 1 0 0 0 0 0] [0 0 0 1 0 0 0] [1 0 0 0 0 0 0] [0 0 0 0 1 0 0] [0 0 0 0 0 1 0] [0 0 1 0 0 0 0] [0 0 0 0 0 0 1]]
這是比較原始的方法,其實如果用這個直接作為特征輸入到機器學習算法模型中的話,是非常不好的,主要有以下幾點需要考慮:
- 我們可以看到它是由於我們目前句子較短,向量構成的矩陣是一個9 x 7的大小,但是我們平時處理的文本或者文集是非常龐大的,那么如果是這樣子的話,也就意味着我們需要創建一個非常非常大的矩陣來表示左右出現過的單詞,而這樣子的一個結果是計算效率非常低,且計算資源開銷(需要非常大的儲存空間)很大;
- 這個樣子的向量組是極度稀疏的,因為它只包含了一個非零數字,這樣的形式對於計算及尋找特征是沒有太大意義的。
一個比較簡單的方法解決上述的問題就是我們用Python的字典來標識已有的字符,如下面的代碼所示:
1 sent_bow = {} 2 for token in sentence_example.split(): 3 sent_bow[token] = 1 4 print(sorted(sent_bow.items()))
通過運行上述的代碼,我們可以得到下列的結果,並且可以看出這樣子要比矩陣的形式的要簡單的多,對於計算的開銷也就沒那么大,導致這樣子的原因是字典形式只需要儲存1就行了
[('I', 1), ('deep', 1), ('language', 1), ('learning,', 1), ('love', 1), ('natural', 1), ('processing.', 1)]
綜上,NLP的第一步就是tokenization然后將其轉換成計算機可以處理的數字,因為我們主要是讓計算機從數字中去尋找文字當中隱含的模式和對應的標記關系,這在NLP中是非常重要的一步,也將影像往后的每一步NLP流程。