自然語言處理觀念整理-1
評估.資料量不足作法, 原文提取與清理, 預處理, 原文表示法
標準的NLP流程
深度學習於自然語言處理上的難題
- 雖然DL已於NLP取得非常大的進步, 但並非所有問題都必須使用深度學習來解決, 更多是混合式ML or DL + rule base來解決問題, 以下列出幾點要點來幫助評估
- NLP使用ML如單純貝葉氏可以解釋正負面字眼對情緒分類的影響但LSTM難以被解釋, 而CNN處理影像問題則有很多可以解釋的技術
- 小樣本適合用傳統的ML, DL有更多運算元, 更強的表達能力,容易overfitting
- NLP難以用生成的方式創造樣本, 但影像卻很容易
- 特定領域model不容易在別的領域通用, 要將領域專門化, 或許用簡單的model即可, 不用DL
- DL處理NLP成本很高, 上標籤成本非常昂貴, 有可能花費更多成本但效果只和ML差不多還可能更差
- 原文表示法的好壞影響勝過演算法好壞的影響
- bert gpt 因為看過大量資料, 斷詞變得比較沒必要但可能會因為跑不動, 必須用以前的技術
- 模型小資料小 加上 knowledge graph加強資料超大就可以取代knowledge graph
資料量不足作法
- 同義詞替換:從句子隨機選出K個不是停用詞的字, 使用同義詞替換
- 使用翻譯(google translate):翻成另一個語言再翻回來
- 替換個體 : 地名, 人名替換
- 雙字對調 : 把句子切割成許多雙字, 然後顛倒
- 在資料中加入雜訊:行動手機鍵盤上容易打錯的英文字故意打錯也丟進去training
- 套件:easy data augmentation, NLPAug, Snorkel
原文提取與清理
目的是移除資料中所有非文字的資訊(EX: pdf, html, image ......), 並轉換成所需編碼格式, 會有以下幾個步驟, 不同語言流程可能不一樣:
- HTML解析與清洗: 相關套件如BeautifulSoup
- Unicode正規化: 特殊字元或表情處理
- 拼寫糾正: 相關套件如pyenchant
預處理
詳細再對資料取回來的資料進行預處理, 不同語言需要不同套件協助處理, 如果是情緒分析, 需要進行停用詞, 小寫, 數字移除...等工作, 但如果是email提取行事曆事件, 還要加入parsing, 不要移除停用詞與進行詞幹提取:
- 句子分割:原文變句子, 套件如NLTK
- 斷詞(tokenization): 句子單元化, 套件如NLTK與jieba
- 斷詞能更好的表達意思, 當然每個字都放進去也可以, 但斷詞斷不好會有反效果, 專有領域例如法律金融, 先斷詞會更好, 一般口語領域, 不斷詞也可以
- 過濾停用詞
- 詞幹提取(stemming):cars > car, adjustable > adjust, 可以減少特徵空間
- 詞形還原(lemmatization):was > be, better > good, 可以減少特徵空間
- 原文正規化:例如轉小寫(casing), 簡繁互轉, 數字轉文字(9 > nine), 展開縮寫
- 語言偵測:套件如Polyglot
- 語言混合處理:統一轉成英語拼音
- 詞性標注(pos tagging)
- 共指消解(coreference resolution): 找出關係
- 詞幹提取(stemming):cars > car, adjustable > adjust, 可以減少特徵空間
- 詞形還原(lemmatization):was > be, better > good, 可以減少特徵空間
處理中文文本比較不會處理這一塊,而在非中文的語言上,例如英文語句中,同一個單詞在拼法上可能隨著時態、單複數、主被動等狀況不同,ex: speaking 與 speak,然而其所要表達的語意並沒有太大的不同。
其優點為能夠大幅降低詞數量,在建立 BOW (bag of word,在後面的文章會慢慢介紹到,一個文章或句子的表示型態) 時可以降低表示文本的維度,以降低資料複雜度,加快語言模型訓練速度,ex: speak 與 speaking 想表達的語意相當接近,如果將兩者都轉為 speak。
缺點其實就是喪失的訊息,以上述 speaking / speak 為例,有可能某些預測需要利用到時態的訊息,當我們將兩者都轉為 speak 輸入模型時, speak-ing 中 ing 的訊息也被我們移除。
文字表示法
文字資料逼須先轉換成模型看得懂的表示法, 並能正確地描敘句子含義, 優秀的原文表示法必須提取下列幾點:
- 將句子拆成詞彙單元, 如單字.詞素.子句
- 推倒個詞素單字含義
- 了解句法結構
- 了解句子的上下文
下面提供許多種方式
範例文件 documents = ["Dog bites man.", "Man bites dog.", "Dog eats meat.", "Man eats food."]
基本向量化方法(分布表示法)
- one-hot:
- 缺點向量大小與詞彙量成正比, 大多的文檔過於龐大, 造成向量稀疏, 稀疏容易over fiting
- 每個句子長度也不同
- 單字之間無相似性, 描述單字之間意義能力很差
- 如果訓練詞彙沒有這個字, 就不會有結果
- 已很少人使用它
- bag of word:
假設單字dog=1, bits=2, man=3, meat=4, food=5, eats=6, 句子就會變成 D1=[1 1 1 0 0 0]
- 容易理解與實作
- 稀疏問題
- 長度固定
- 如果訓練詞彙沒有這個字, 就不會有結果
- 捨棄單字出現順序, 我打你跟你打我不一樣分不出來
- I run , I ran, I ate 在bow向量都是等距的,
- 如果不考慮詞頻(例如情緒分析), count_vect = CountVectorizer(binary=True)
from sklearn.feature_extraction.text import CountVectorizer
#look at the documents list
print("Our corpus: ", processed_docs)
count_vect = CountVectorizer()
#Build a BOW representation for the corpus
bow_rep = count_vect.fit_transform(processed_docs)
#Look at the vocabulary mapping
print("Our vocabulary: ", count_vect.vocabulary_)
#see the BOW rep for first 2 documents
print("BoW representation for 'dog bites man': ", bow_rep[0].toarray())
print("BoW representation for 'man bites dog: ",bow_rep[1].toarray())
#Get the representation using this vocabulary, for a new text
temp = count_vect.transform(["dog and dog are friends"])
print("Bow representation for 'dog and dog are friends':", temp.toarray())
- n-garm:
前兩個方法都將單字視為獨立的單位, 他沒有子句或單字順序的概念, bag of n gram, 將原文拆成包含許多n個連續字的段落, 可以描敘上下文, 舉例2-gram = { dog bites, bits man, man bits, bits eats ...... } , D1 = [1,1,0,0,0,0,0,0]
- 有上下文的描敘
- 如果訓練詞彙沒有這個字, 就不會有結果
- 簡單的問題會有好的效果
ngram_range=(1,3) = unigram and bigram and trigram
from sklearn.feature_extraction.text import CountVectorizer
#Ngram vectorization example with count vectorizer and uni, bi, trigrams
count_vect = CountVectorizer(ngram_range=(1,3))
#Build a BOW representation for the corpus
bow_rep = count_vect.fit_transform(processed_docs)
#Look at the vocabulary mapping
print("Our vocabulary: ", count_vect.vocabulary_)
#see the BOW rep for first 2 documents
print("BoW representation for 'dog bites man': ", bow_rep[0].toarray())
print("BoW representation for 'man bites dog: ",bow_rep[1].toarray())
#Get the representation using this vocabulary, for a new text
temp = count_vect.transform(["dog and dog are friends"])
print("Bow representation for 'dog and dog are friends':", temp.toarray())
- TFIDF:
前面的做法都把每個單字視為一樣重要, TFIDF方法可找出關鍵字, 每個單字計算TFIDF後, d1 = [0.136, 0.17, 0.136 0 0 0]
- 有考慮單字重要程度
- 如果訓練詞彙沒有這個字, 就不會有結果
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer()
bow_rep_tfidf = tfidf.fit_transform(processed_docs)
#IDF for all words in the vocabulary
print("IDF for all words in the vocabulary",tfidf.idf_)
print("-"*10)
#All words in the vocabulary.
print("All words in the vocabulary",tfidf.get_feature_names())
print("-"*10)
#TFIDF representation for all documents in our corpus
print("TFIDF representation for all documents in our corpus\n",bow_rep_tfidf.toarray())
print("-"*10)
temp = tfidf.transform(["dog and man are friends"])
print("Tfidf representation for 'dog and man are friends':\n", temp.toarray())
散式表示法
基本向量化方法都有一些缺點(如下)
- 他們都是離散的原子單位, 無法表示各單位關係
- 資料稀疏表示法, 當資料多, 計算效率極低
- 無法處理oov(out of vacabulary)的問題
近年來發展出使用神經網路建立密集低維的單字和原文表示法, 可以產生幾乎沒有0的向量, 其向量空間稱為散式表示法
- word embedding(也稱密集文字向量):
- 將原文語義單元化, 轉換成單字索引向量
- 填補原文序列, 讓所有向量都有相同長度
- 把每個單字索引對映embedding向量
- embedding矩陣可以使用pretrained word embedding, 或使用文集的embedding訓練
- embedding是個龐大的模型, 部屬時必須考慮到將其載入memory的問題
我們可以把任一文字mapping到向量, 但問題再沒有任何結構, 文字之間意思很接近的有可能被嵌到很遠, 深度學習難以理解這種雜亂且非結構化的空間, 我們希望可以理解文字結構, 相同的字應該在空間中很接近
也可以自己訓練word2vec模型, 將字轉成向量, 可以參考這篇
- word2vec:
word2vec會根據單字的上下文學會再一個共同向量空間裡面表示他們, 但要自己訓練非常的昂貴, 以下是使用預訓練好的word2vec找尋同義詞的範例:
import warnings #This module ignores the various types of warnings generated
warnings.filterwarnings("ignore")
import os #This module provides a way of using operating system dependent functionality
import psutil #This module helps in retrieving information on running processes and system resource utilization
process = psutil.Process(os.getpid())
from psutil import virtual_memory
mem = virtual_memory()
import time #This module is used to calculate the time
from gensim.models import Word2Vec, KeyedVectors
pretrainedpath = '/tmp/input/GoogleNews-vectors-negative300.bin.gz'
#Load W2V model. This will take some time, but it is a one time effort!
pre = process.memory_info().rss
print("Memory used in GB before Loading the Model: %0.2f"%float(pre/(10**9))) #Check memory usage before loading the model
print('-'*10)
start_time = time.time() #Start the timer
ttl = mem.total #Toal memory available
w2v_model = KeyedVectors.load_word2vec_format(pretrainedpath, binary=True) #load the model
print("%0.2f seconds taken to load"%float(time.time() - start_time)) #Calculate the total time elapsed since starting the timer
print('-'*10)
print('Finished loading Word2Vec')
print('-'*10)
post = process.memory_info().rss
print("Memory used in GB after Loading the Model: {:.2f}".format(float(post/(10**9)))) #Calculate the memory used after loading the model
print('-'*10)
print("Percentage increase in memory usage: {:.2f}% ".format(float((post/pre)*100))) #Percentage increase in memory after loading the model
print('-'*10)
print("Numver of words in vocablulary: ",len(w2v_model.vocab)) #Number of words in the vocabulary.
#Let us examine the model by knowing what the most similar words are, for a given word!
w2v_model.most_similar('beautiful')
[('gorgeous', 0.8353004455566406),
- word2vec兩種架構版本:
- CBOW: 用上下文單字來預測中心字, CBOW也可以用算出來的向量帶代表整個上下文, 來代表比單字更大的範圍
- SkipGram: 用中心字預測上下字
- OOV(out of vocabulary)問題:
- 用大型文集訓練就可以降低機率
- 專有領域還是必須獨立處理
- fasttext使用類似word2vec結構同時學習單字字元n-gram的embedding, 並將單字的embedding向量視為他成分字元n-gram的聚合體, 可以下載fasttext訓練好的模型, 在gensim的fasttext包裝器中載入使用, 缺點是模型太大會有工程問題
- 單字字元以外的散式表示法:
- word2vec可以學習單字表示法, 整合產生原文表示法
- fasttext可以學習字元表示法,整合產生單字表示法和原文表示法
- doc2vec: 使用gensim實作, 考慮單字上下文, 除了單字向量, 還會學習段落向量, 有DM DBOW兩種, doc2vec為第一種可以產生全文的embedding表示法, 可將任意長度的原編碼成固定.低維度.密集的向量, 這裡有推薦系統使用doc2vec尋找相似原文的範例
- 通用原文表示法:
- 上面原文表示法一個單字都有固定的表示法, 但同樣的單字在不同上下文的意思可能不同
- 可以使用遷移學習,如ELMo, BERT(有transformer, RNN建構而來), 來學習embedding, 再用特定任務專用資料進行微調, 但速度慢
- BERT: ransformer的encoder, GPT太大所以就會再回頭用bert, bert偏向一般性的知識, 要在fine tune成domain的知識, 需要文章級的訓練資料, bert gpt 因為看過大量資料, 斷詞變得比較沒必要, 但可能會因為跑不動, 必須用以前的技術
其他議題
- 所有原文表示法都有偏見, 取決於他們訓練資料可以看到什麼
- 預訓練embedding通常是大型檔案, 工程實現時必須注意
- 每次有新的NLP模型時, 必須考慮如何在生產環境中使用他, 考慮商業需求與基礎建設的限制
- embedding視覺化: t-SNE, tensorboard
Ref:
- 自然語言處理最佳實務, O'Reilly
沒有留言:
張貼留言