天然語言處理,簡稱:NLP,是指對人們平時平常使用的交流語言進行處理的一項技術。NLP 通過多年的發展,現今能夠劃分爲兩部份內容,即:天然語言的理解和天然語言的生成。html
本文將以文本分類爲目標,介紹天然語言處理相關的基礎操做和應用。git
(來自https://www.shiyanlou.com/courses/1208)正則表達式
作一箇中文文本分類任務,首先要作的是文本的預處理,對文本進行分詞和去停用詞操做,來把字符串分割成詞與詞組合而成的字符串集合並去掉其中的一些非關鍵詞彙(像是:的、地、得等)。再就是對預處理事後的文本進行特徵提取。最後將提取到的特徵送進分類器進行訓練。算法
術語解釋:app
分詞:詞是 NLP 中可以獨立活動的有意義的語言成分。即便某個中文單字也有活動的意義,但其實這些單字也是詞,屬於單字成詞。dom
詞性標註:給每一個詞語的詞性進行標註,好比 跑/動詞、美麗的/形容詞等等。函數
命名實體識別:從文本中識別出具備特定類別的實體。像是識別文本中的日期,地名等等。工具
詞義消歧:多義詞判斷最合理的詞義。學習
句法分析:解析句子中各個成分的依賴關係。測試
指代消解:消除和解釋代詞「這個,他,你」等的指代問題。
變量名.count("A"):返回子串A在字符串中出現的次數
.strip()
方法能夠去除字符串首尾的指定符號。無指定時,默認去除空格符 ' '
和換行符 '\n'
只去除字符串開頭的某個字符串使用 .lstrip()
方法
一樣,可使用.rstrip()
方法來單獨去除末尾的字符
字符串拼接:直接用「+」,須要將字符串用特定的符號拼接起來的字符的時候,能夠用 .join()
方法來進行拼接。
seq = ['2018', '10', '31'] seq = '-'.join(seq) # 用 '-' 拼接
比較大小:直接用運算符,也可用 operator
.upper()
和 .lower()轉換英文字符大小寫
爲了查找到某段字符串當中某個子串的位置信息,有兩種方法。一種是.index
,一種是 .find
。 兩種方法均可實現這個功能,不一樣的是 index
若是未找到的話,會報錯,而 find
未找到的則會返回 -1
值。
注意第一個位置是0
有的時候,須要把一個字符串按照某個字符切分開處理。好比‘今每天氣很好,咱們出去玩’,要把兩句話以 ','
切開成兩句話。split()
函數能夠完成這個操做,函數返回一個由切分好的字符串組成的列表。
翻轉字符串:
seq = '12345' seq = seq[::-1]
in
關鍵字能夠用在任何容器對象上,判斷一個子對象是否存在於容器當中,並不侷限於判斷字符串是否存在某子串,還能夠用在其餘容器對象例如 list
,tuple
,set
等類型。可配合if 進行後續操做。
.replace(a,b)替換操做,把a替換成b
當遇到須要判斷字符串是否以某段字符開頭的時候。好比想要判斷‘abcdefg’是否以 'a'開頭。能夠用 .startswish()
方法。一樣的方法,咱們能夠用 .endswith()
來肯定字符串是否以某段字符串結尾。
.isdigit() 判斷字符串是否由純數字組成。
例子:找出 '2018/01/01', '01/01/2019', '01.2017.01' 這幾種不一樣日期表達中的年份。
規則:連續4個字符
都是 0-9的數字
import re pattern = re.compile(r'[0-9]{4}') times = ('2018/01/01', '01/01/2019', '01.2017.01') for time in times: match = pattern.search(time) if match: print('年份有:', match.group())
輸出爲:
re 模塊的更多使用:
下面咱們經過幾個例子來體會一下 re
模塊對於正則表達式的其餘用法。
.findall()
:
這個方法能夠找到符合正則表達式的全部匹配結果。這裏咱們使用了 \d
規則的正則表達式,這個正則表達式能夠替咱們識別數字。
一樣的方法,咱們編寫一個 \D
正則表達式,這個能夠匹配一個非數字字符。
.match()
方法與 .search()
方法相似,只匹配一次,而且只從字符串的開頭開始匹配。一樣,match 結果也是存在 group()
當中。
以前介紹了正則表達式和詞的切分,如今介紹中英文分詞方法。英文分詞比較容易,由於英文詞與詞之間有空格,可是中文須要按照語義進行切分。
直接按照空格進行分詞
按空格指定切分次數:
按照關鍵詞進行切分
若是須要切分的句子中混入一些其餘符號,須要編寫函數將其去除,以下:
1 def tokenize_english_text(text): 2 # 首先,咱們按照標點來分句 3 # 先創建一個空集用來,用來將分好的詞添加到裏面做爲函數的返回值 4 tokenized_text = [] 5 # 一個text中可能不止一個內容,咱們對每一個文本單獨處理並存放在各自的分詞結果中。 6 for data in text: 7 # 創建一個空集來存儲每個文本本身的分詞結果,每對data一次操做咱們都歸零這個集合 8 tokenized_data = [] 9 # 以 '.'分割整個句子,對分割後的每一小快s: 10 for s in data.split('.'): 11 # 將's'以 '?'分割,分割後的每一小快s2: 12 for s1 in s.split('?'): 13 # 一樣的道理分割s2, 14 for s2 in s1.split('!'): 15 # 同理 16 for s3 in s2.split(','): 17 # 將s3以空格分割,而後將結果添加到tokenized_data當中 18 tokenized_data.extend( 19 s4 for s4 in s3.split(' ') if s4 != '') 20 # 括號內的部分拆開理解 21 # for s4 in s3.split(' '): 22 # if s4!='': 這一步是去除空字符''。注意與' ' 的區別。 23 # 將每一個tokenized_data分別添加到tokenized_text當中 24 tokenized_text.append(tokenized_data) 25 return tokenized_text
注意的是 必須判斷是否爲空字符,再將其加入列表。運行結果以下:
中文分詞這個概念自提出以來,通過多年的發展,主要能夠分爲三個方法:機械分詞方法,統計分詞方法,以及兩種結合起來的分詞。
機械分詞方法又叫作基於規則的分詞方法,這種分詞方法按照必定的規則將待處理的字符串與一個詞表詞典中的詞進行逐一匹配,若在詞典中找到某個字符串,則切分,不然不切分。機械分詞方法按照匹配規則的方式,又能夠分爲:正向最大匹配法,逆向最大匹配法和雙向匹配法三種。
如下展現的是正向最大匹配法,其計算步驟以下:
dic
,待分詞文本 text
,建立空集 words
。max_len_word
。max_len=max_len_word
個字符做爲待匹配字符串 word
。word
與詞典 dic
匹配max_len = max_len - 1
,而後word
添加進 words
當中。max_len
個字符max_len
值爲 max_len_word
words
如下是一個實例:
1 text = "我在實驗樓學習呢" 2 dic = ("我","正在","在","實驗樓","學習","呢") 3 words = [] 4 #獲得最大詞長 5 max_len = 0 6 for key in dic: 7 if len(key)>max_len: 8 max_len = len(key) 9 10 # 判斷text的長度是否大於0,若是大於0則進行下面的循環 11 while len(text) > 0: 12 # 初始化想要取的字符串長度 13 # 按照最長詞長度初始化 14 word_len = max_len 15 # 對每一個字符串可能會有(max_len_word)次循環 16 for i in range(0, max_len): 17 # 令word 等於text的前word_len個字符 18 word = text[0:word_len] 19 # 爲了便於觀察過程,咱們打印一下當前分割結果 20 print('用', word, '進行匹配') 21 # 判斷word是否在詞典dic當中 22 # 若是不在詞典當中 23 if word not in dic: 24 #則以word_len - 1 25 word_len -= 1 26 # 清空word 27 word = [] 28 # 若是word 在詞典當中 29 else: 30 # 更新text串起始位置 31 text = text[word_len:] 32 # 爲了方便觀察過程,咱們打印一下當前結果 33 print('{} 匹配成功,添加進words當中'.format(word)) 34 # 把匹配成功的word添加進上面建立好的words當中 35 words.append(word) 36 # 清空word 37 word = [] 38 break 39 40 print(words)
運行結果爲
上述方法雖然簡單易行,可是很是依賴於原始詞典的構成,因此不具備普適性。接下來介紹一個更爲成熟的方法——基於統計規則的分詞方法。
簡單的來講,就是經過分析字與字之間在一塊兒的頻率,判斷它們是否是一個詞。
基於統計的分詞,通常狀況下有兩個步驟:
使用的工具:結巴分詞
import jieba
cut_all:False表示顯示精確模式分詞結果,True表示全模式
如下是搜索引擎模式,與全模式匹配結果相同。全模式和搜索引擎模式,jieba 會把所有可能組成的詞都打印出來。在通常的任務當中,咱們使用默認的精確模式就好了,在模糊匹配時,則須要用到全模式或者搜索引擎模式。
修改詞典:
1. 使用 suggest_freq(segment, tune=True)
可調節單個詞語的詞頻,使其能(或不能)被分出來。
「今每天氣不錯」,若是直接使用jieba,會分解成爲:今每天氣和不錯兩個詞,因此須要調整。
也能夠從詞典直接刪除該詞語:
還有一種狀況,就是有一些字,他們的組詞頻率很低,好比「臺」和「中」,「臺中」出現的機率低於這兩個字單獨成詞的機率,因此此時能夠添加詞典,強制調高詞頻。
1 jieba.add_word('臺中') 2 seg_list = jieba.cut(string, cut_all=False) 3 '/'.join(seg_list)
如下是一個利用jieba作的簡單過濾器,在實際應用中很常見,由於像「的」,「得」,「地」這樣的助詞須要被過濾掉。
方法是創建一個停用詞列表,若是分的詞不在列表中,就保留。
1 stopwords = ["的","地","得"] 2 new_list = [] 3 s_list = jieba.cut(s,cut_all=False) 4 for word in s_list: 5 if word not in stopwords: 6 new_list.append(word)
特徵提取是將文本應用於分詞前的重要步驟。作文本分類等問題的時,須要從大量語料中提取特徵,並將這些文本特徵變換爲數值特徵。通常而言,特徵提取有下面的兩種經典方法。
詞袋模型是最原始的一類特徵集,忽略掉了文本的語法和語序,用一組無序的單詞序列來表達一段文字或者一個文檔。能夠這樣理解,把整個文檔集的全部出現的詞都丟進袋子裏面,而後無序的排出來(去掉重複的)。對每個文檔,按照詞語出現的次數來表示文檔。
這種模型主要是用詞彙的統計特徵來做爲特徵集。TF-IDF 由兩部分組成:TF(Term frequency,詞頻),IDF(Inverse document frequency,逆文檔頻率)兩部分組成。
在Python中可使用sklearn來實現上述兩種模型。這裏要用到 CountVectorizer()
類以及TfidfVectorizer()
類。
下面介紹這兩個類:
1 #詞袋 2 from sklearn.feature_extraction.text import CountVectorizer 3 #調整參數 4 vectorizer = CountVectorizer(min_df=1, ngram_range=(1, 1)) 5 corpus = ['This is the first document.', 6 'This is the second second document.', 7 'And the third one.', 8 'Is this the first document?'] 9 a = vectorizer.fit_transform(corpus) 10 vectorizer.get_feature_names() 11 a.toarray()
特徵名詞:
['and', 'document', 'first', 'is', 'one', 'second', 'the', 'third', 'this']
1 #TF-IDF 2 from sklearn.feature_extraction.text import TfidfVectorizer 3 vectorizer = TfidfVectorizer( 4 min_df=1, norm='l2', smooth_idf=True, use_idf=True, ngram_range=(1, 1)) 5 b = vectorizer.fit_transform(corpus) 6 vectorizer.get_feature_names() 7 b.toarray()
郵件樣本分類器項目,二分類。分析工具:SVM(支持向量機),步驟:預處理、特徵提取、分類
from sklearn.model_selection import train_test_split train_d, test_d, train_l, test_l = train_test_split( datas, labels, test_size=0.25, random_state=5)
實例代碼以下:
1 #加載數據集,包括正常郵件、垃圾郵件、停用詞 2 from urllib import request 3 request.urlretrieve('http://labfile.oss.aliyuncs.com/courses/1208/ham_data.txt','ham_data.txt') 4 request.urlretrieve('http://labfile.oss.aliyuncs.com/courses/1208/spam_data.txt','spam_data.txt') 5 request.urlretrieve('http://labfile.oss.aliyuncs.com/courses/1208/stop_word.txt','stop_word.txt') 6 7 #數據集處理 8 import numpy as np 9 from sklearn.model_selection import train_test_split 10 path1 = 'ham_data.txt' # 正常郵件存放地址 11 path2 = 'spam_data.txt' # 垃圾郵件地址 12 h = open(path1, encoding='utf-8') 13 h_data = h.readlines() 14 s = open(path2, encoding='utf-8') 15 s_data = s.readlines() 16 #生成標籤 17 h_labels = np.ones(len(h_data)).tolist() #正常郵件標籤爲1 18 s_labels = np.zeros(len(s_data)).tolist() #垃圾郵件標籤爲0 19 #整合數據集和標籤 20 datas = h_data + s_data # 將正常樣本和垃圾樣本整合到datas當中 21 labels = h_labels + s_labels 22 #分類,隨機劃出 25% 個樣本和標籤來做爲咱們的測試集,剩下的 75% 做爲樣本集來進行咱們的分類。 23 train_d, test_d, train_l, test_l = train_test_split( 24 datas, labels, test_size=0.25, random_state=5) 25 26 import jieba 27 #分詞 28 def tokenize_words(corpus): 29 tokenized_words = jieba.cut(corpus) 30 tokenized_words = [token.strip() for token in tokenized_words]#去掉詞的空格 31 return tokenized_words 32 33 #去除停用詞 34 def remove_stopwords(corpus): # 函數輸入爲樣本集 35 sw = open('stop_word.txt', encoding='utf-8') # stopwords 停詞表 36 sw_list = [l.strip() for l in sw] # 去掉文本中的回車符,而後存放到 sw_list 當中 37 # 調用前面定義好的分詞函數返回到 tokenized_data 當中 38 tokenized_data = tokenize_words(corpus) 39 # 過濾停用詞,對每一個在 tokenized_data 中的詞 data 進行判斷,若是 data 不在 sw_list 則添加到 filtered_data 當中 40 filtered_data = [data for data in tokenized_data if data not in sw_list] 41 # 用''將 filtered_data 串起來賦值給 filtered_datas 42 filtered_datas = ''.join(filtered_data) 43 return filtered_datas # 返回去停用詞以後的 datas 44 45 from tqdm import tqdm_notebook #顯示進度 46 #預處理數據集 47 def preprocessing_datas(datas): 48 preprocessed_datas = [] 49 # 對 datas 當中的每個 data 進行去停用詞操做,並添加到上面剛剛創建的 preprocessed_datas 當中 50 for data in tqdm_notebook(datas): 51 data = remove_stopwords(data) 52 preprocessed_datas.append(data) 53 return preprocessed_datas # 返回去停用詞以後的新的樣本集 54 55 pred_train_d = preprocessing_datas(train_d)#預處理樣本集 56 pred_test_d = preprocessing_datas(test_d)#預處理測試集 57 ''' 58 據此,獲得了分詞事後而且去除停用詞 59 的樣本集 pred_train_d 和 測試集 pred_test_d 60 ''' 61 62 #特徵提取,用 TF-IDF 模型 63 from sklearn.feature_extraction.text import TfidfVectorizer 64 from sklearn.linear_model import SGDClassifier 65 from sklearn import metrics 66 #設置 TF-IDF 模型訓練器 vectorizer 67 vectorizer = TfidfVectorizer( 68 min_df=1, norm='l2', smooth_idf=True, use_idf=True, ngram_range=(1, 1)) 69 #進行特徵詞提取,.get_feature_names()獲得特徵詞列表 70 tfidf_train_features = vectorizer.fit_transform(pred_train_d) 71 ''' 72 用訓練集訓練好特徵後的 vectorizer 來提取測試集的特徵.。 73 注意這裏不能用 vectorizer.fit_transform() 要用 vectorizer.transform(), 74 不然,將會對測試集單獨訓練 TF-IDF 模型,而不是在訓練集的詞數量基礎上作訓練。 75 這樣詞總量跟訓練集不同多,排序也不同,將會致使維數不一樣,最終沒法完成測試。 76 ''' 77 tfidf_test_features = vectorizer.transform(pred_test_d) 78 79 #調用 SGDClassifier() 類來訓練 SVM 分類器 80 svm = SGDClassifier(loss='hinge') 81 #SGDClassifier 是一個多個分類器的組合,當參數 loss='hinge' 時是一個支持向量機分類器 82 svm.fit(tfidf_train_features, train_l)#訓練 83 predictions = svm.predict(tfidf_test_features)#測試 84 85 #用 scikit-learn 庫中的 accuracy_score 函數來計算一下分類器的準確率 86 accuracy_score = np.round(metrics.accuracy_score(test_l, predictions), 2) 87 #np.round(X,2) 的做用是 X 四捨五入後保留小數點後2位數字 88 89 print(accuracy_score)#打印訓練結果——預測準確率 90 91 #抽樣測試 92 print('郵件類型:', test_l[21]) 93 print('預測郵件類型:', predictions[21]) 94 print('文本:', test_d[21])