1.文本挖掘的分詞原理
在做文本挖掘的時候,首先要做的預處理就是分詞郎汪。英文單詞天然有空格隔開容易按照空格分詞,但是也有時候需要把多個單詞做為一個分詞耕驰,比如一些名詞如“New York”朦肘,需要做為一個詞看待媒抠。而中文由于沒有空格咏花,分詞就是一個需要專門去解決的問題了阀趴。
分詞的基本原理
現(xiàn)代分詞都是基于統(tǒng)計的分詞刘急,而統(tǒng)計的樣本內(nèi)容來自于一些標準的語料庫叔汁。假如有一個句子:“小明來到荔灣區(qū)”据块,我們期望語料庫統(tǒng)計后分詞的結(jié)果是:"小明/來到/荔灣/區(qū)"折剃,而不是“小明/來到/荔/灣區(qū)”。那么如何做到這一點呢边篮?
從統(tǒng)計的角度因苹,我們期望"小明/來到/荔灣/區(qū)"這個分詞后句子出現(xiàn)的概率要比“小明/來到/荔/灣區(qū)”大扶檐。如果用數(shù)學的語言來說說,如果有一個句子S,它有m種分詞選項如下:
其中表示在語料庫中相鄰一起出現(xiàn)的次數(shù),而其中分別表示在語料庫中出現(xiàn)的統(tǒng)計次數(shù)奈梳。
利用語料庫建立的統(tǒng)計概率,對于一個新的句子漆撞,我們就可以通過計算各種分詞方法對應的聯(lián)合分布概率浮驳,找到最大概率對應的分詞方法捞魁,即為最優(yōu)分詞谱俭。
常用的分詞工具
對于文本挖掘中需要的分詞功能宵蛀,一般我們會用現(xiàn)有的工具术陶。簡單的英文分詞不需要任何工具瞳别,通過空格和標點符號就可以分詞了杭攻,而進一步的英文分詞推薦使用nltk兆解。對于中文分詞锅睛,則推薦用結(jié)巴分詞(jieba)历谍。這些工具使用都很簡單望侈。你的分詞沒有特別的需求直接使用這些分詞工具就可以了。
分詞是文本挖掘的預處理的重要的一步侥猬,分詞完成后捐韩,我們可以繼續(xù)做一些其他的特征工程荤胁,比如向量化(vectorize),TF-IDF以及Hash trick垢油,這些我們后面再講秸苗。
2.向量化與Hash Trick
在做了分詞后运褪,如果我們是做文本分類聚類,則后面關(guān)鍵的特征預處理步驟有向量化或向量化的特例Hash Trick檀咙,本文我們就對向量化和特例Hash Trick預處理方法做一個總結(jié)。
2.1 詞袋模型
在講向量化與Hash Trick之前蔑匣,我們先說說詞袋模型(Bag of Words,簡稱BoW)裁良。詞袋模型假設我們不考慮文本中詞與詞之間的上下文關(guān)系校套,僅僅只考慮所有詞的權(quán)重笛匙。而權(quán)重與詞在文本中出現(xiàn)的頻率有關(guān)。
詞袋模型首先會進行分詞秋柄,在分詞之后蠢正,通過統(tǒng)計每個詞在文本中出現(xiàn)的次數(shù)机隙,我們就可以得到該文本基于詞的特征,如果將各個文本樣本的這些詞與對應的詞頻放在一起旭旭,就是我們常說的向量化持寄。向量化完畢后一般也會使用TF-IDF進行特征的權(quán)重修正娱俺,再將特征進行標準化。 再進行一些其他的特征工程后模庐,就可以將數(shù)據(jù)帶入機器學習算法進行分類聚類了掂碱。
總結(jié)下詞袋模型的三部曲:分詞(tokenizing),統(tǒng)計修訂詞特征值(counting)與標準化(normalizing)沧卢。
與詞袋模型非常類似的一個模型是詞集模型(Set of Words,簡稱SoW)醉者,和詞袋模型唯一的不同是它僅僅考慮詞是否在文本中出現(xiàn),而不考慮詞頻立磁。也就是一個詞在文本在文本中出現(xiàn)1次和多次特征處理是一樣的息罗。在大多數(shù)時候才沧,我們使用詞袋模型温圆,后面的討論也是以詞袋模型為主岁歉。
當然膝蜈,詞袋模型有很大的局限性,因為它僅僅考慮了詞頻非剃,沒有考慮上下文的關(guān)系备绽,因此會丟失一部分文本的語義鬓催。但是大多數(shù)時候宇驾,如果我們的目的是分類聚類,則詞袋模型表現(xiàn)的很好塌西。
2.2 詞袋的向量化
在詞袋模型的統(tǒng)計詞頻這一步,我們會得到該文本中所有詞的詞頻雇盖,有了詞頻栖忠,我們就可以用詞向量表示這個文本。這里我們舉一個例子狸相,例子直接用scikit-learn的CountVectorizer類來完成脓鹃,這個類可以幫我們完成文本的詞頻統(tǒng)計與向量化古沥,代碼如下:
from sklearn.feature_extraction.text import CountVectorizer
corpus=["I come to China to travel",
"This is a car polupar in China",
"I love tea and Apple ",
"The work is to write some papers in science"]
vectorizer = CountVectorizer()
print (vectorizer.fit_transform(corpus))
(0, 16) 1
(0, 3) 1
(0, 15) 2
(0, 4) 1
(1, 5) 1
(1, 9) 1
(1, 2) 1
(1, 6) 1
(1, 14) 1
(1, 3) 1
(2, 1) 1
(2, 0) 1
(2, 12) 1
(2, 7) 1
(3, 10) 1
(3, 8) 1
(3, 11) 1
(3, 18) 1
(3, 17) 1
(3, 13) 1
(3, 5) 1
(3, 6) 1
(3, 15) 1
可以看出4個文本的詞頻已經(jīng)統(tǒng)計出太颤,在輸出中盹沈,左邊的括號中的第一個數(shù)字是文本的序號,第2個數(shù)字是詞的序號做裙,注意詞的序號是基于所有的文檔的锚贱。第三個數(shù)字就是我們的詞頻关串。
我們可以進一步看看每個文本的詞向量特征和各個特征代表的詞,代碼如下:
print vectorizer.fit_transform(corpus).toarray()#轉(zhuǎn)化為一個矩陣
print vectorizer.get_feature_names()
可以看到我們一共有19個詞卦绣,所以4個文本都是19維的特征向量滤港。而每一維的向量依次對應了下面的19個詞溅漾。另外由于詞"I"在英文中是停用詞,不參加詞頻的統(tǒng)計屁倔。
由于大部分的文本都只會使用詞匯表中的很少一部分的詞锐借,因此我們的詞向量中會有大量的0往衷。也就是說詞向量是稀疏的。在實際應用中一般使用稀疏矩陣來存儲布轿。
將文本做了詞頻統(tǒng)計后汰扭,我們一般會通過TF-IDF進行詞特征值修訂
向量化的方法很好用福铅,也很直接,但是在有些場景下很難使用,比如分詞后的詞匯表非常大拷沸,達到100萬+薯演,此時如果我們直接使用向量化的方法,將對應的樣本對應特征矩陣載入內(nèi)存序无,有可能將內(nèi)存撐爆帝嗡,在這種情況下我們怎么辦呢璃氢?第一反應是我們要進行特征的降維一也,說的沒錯喉脖!而Hash Trick就是非常常用的文本特征降維方法树叽。
3. TF-IDF
corpus=["I come to China to travel",
"This is a car polupar in China",
"I love tea and Apple ",
"The work is to write some papers in science"]
[[0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 2 1 0 0]
[0 0 1 1 0 1 1 0 0 1 0 0 0 0 1 0 0 0 0]
[1 1 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0]
[0 0 0 0 0 1 1 0 1 0 1 1 0 1 0 1 0 1 1]]
如果我們直接將統(tǒng)計詞頻后的19維特征做為文本分類的輸入题诵,會發(fā)現(xiàn)有一些問題挨稿。比如第一個文本奶甘,我們發(fā)現(xiàn)"come","China"和“Travel”各出現(xiàn)1次,而“to“出現(xiàn)了兩次疲陕。似乎看起來這個文本與”to“這個特征更關(guān)系緊密钉赁。但是實際上”to“是一個非常普遍的詞你踩,幾乎所有的文本都會用到,因此雖然它的詞頻為2吩谦,但是重要性卻比詞頻為1的"China"和“Travel”要低的多膝藕。如果我們的向量化特征僅僅用詞頻表示就無法反應這一點芭挽。因此我們需要進一步的預處理來反應文本的這個特征,而這個預處理就是TF-IDF蠕趁。
3.2 TF-IDF概述
前面的TF也就是我們前面說到的詞頻妻导,我們之前做的向量化也就是做了文本中各個詞的出現(xiàn)頻率統(tǒng)計,并作為文本特征术浪,這個很好理解寿酌。關(guān)鍵是后面的這個IDF,即“逆文本頻率”如何理解硕并。在上一節(jié)中倔毙,我們講到幾乎所有文本都會出現(xiàn)的"to"其詞頻雖然高乙濒,但是重要性卻應該比詞頻低的"China"和“Travel”要低。我們的IDF就是來幫助我們來反應這個詞的重要性的么库,進而修正僅僅用詞頻表示的詞特征值诉儒。
概括來講亏掀, 反應了一個詞在所有文本中出現(xiàn)的頻率滤愕,如果一個詞在很多的文本中出現(xiàn),那么它的
值應該低,比如上文中的“to”韭畸。而反過來如果一個詞在比較少的文本中出現(xiàn)胰丁,那么它的
值應該高。比如一些專業(yè)的名詞如“Machine Learning”机蔗。這樣的詞IDF值應該高萝嘁。一個極端的情況,如果一個詞在所有的文本中都出現(xiàn)酸钦,那么它的IDF值應該為0咱枉。
上面的公式已經(jīng)可以使用了蚕断,但是在一些特殊的情況會有一些小問題,比如某一個生僻詞在語料庫中沒有硝拧,這樣我們的分母為0,
沒有意義了风皿。所以常用的IDF我們需要做一些平滑桐款,使語料庫中沒有出現(xiàn)的詞也可以得到一個合適的IDF值。平滑的方法有很多種媳维,最常見的IDF平滑后的公式之一為
由此可以計算一個詞的值:
3.3 用sklearn進行處理
- 使用CountVectorizer+TfidfTransformer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.feature_extraction.text import CountVectorizer
corpus=["I come to China to travel",
"This is a car polupar in China",
"I love tea and Apple ",
"The work is to write some papers in science"]
vectorizer=CountVectorizer()
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(vectorizer.fit_transform(corpus))
print (tfidf)
- 使用TfidfVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf2 = TfidfVectorizer()
re = tfidf2.fit_transform(corpus)
print re
(0, 4) 0.442462137895
(0, 15) 0.697684463384
(0, 3) 0.348842231692
(0, 16) 0.442462137895
(1, 3) 0.357455043342
(1, 14) 0.453386397373
(1, 6) 0.357455043342
(1, 2) 0.453386397373
(1, 9) 0.453386397373
(1, 5) 0.357455043342
(2, 7) 0.5
(2, 12) 0.5
(2, 0) 0.5
(2, 1) 0.5
(3, 15) 0.281131628441
(3, 6) 0.281131628441
(3, 5) 0.281131628441
(3, 13) 0.356579823338
(3, 17) 0.356579823338
(3, 18) 0.356579823338
(3, 11) 0.356579823338
(3, 8) 0.356579823338
(3, 10) 0.356579823338
tf_count_dict = {} # tf字典
idf_count_dict = {} # idf中的key字典
tfidf_count_dict = {} # tf*idf的字典
def get_string(line):
for i in line:
str = i[1] + ' ' + i[3]
str_list = str.strip().split(' ')
yield str_list
def load_data(filename):
# 使用關(guān)鍵字yield生成器
with open('/home/kesci/input/bytedance/first-round/' + filename, 'r') as f:
cvs_reader_line = csv.reader(f)
for line in cvs_reader_line:
yield line
filename = 'test.csv'
# 使用關(guān)鍵字yield生成器
with open('/home/kesci/input/bytedance/first-round/' + filename, 'r') as f:
cvs_reader_line = csv.reader(f)
for line in cvs_reader_line:
yield line
def get_tf_idf(str_list1):
for str_list in str_list1:
for word in str_list:
if word not in tf_count_dict.keys():
tf_count_dict[word] = 1
else:
tf_count_dict[word] += 1
for word in set(str_list):
if word not in idf_count_dict.keys():
idf_count_dict[word] = 1
else:
idf_count_dict[word] += 1
return tf_count_dict, idf_count_dict
def gettfidf(tf_count_dict, idf_count_dict):
sum_tf = 0
for key in tf_count_dict:
sum_tf += tf_count_dict[key]
for key in tf_count_dict:
tf_count_dict[key] /= sum_tf
for key in idf_count_dict:
idf_count_dict[key] = np.log(D / (idf_count_dict[key] + 1))
for key in tf_count_dict:
tfidf_count_dict[key] = tf_count_dict[key] * idf_count_dict[key]
return tfidf_count_dict
TF-IDF是非常常用的文本挖掘預處理基本步驟朋凉,可以看作特征提取的一種方法,但是如果預處理中使用了Hash Trick墓毒,則一般就無法使用TF-IDF了亲怠,因為Hash Trick后我們已經(jīng)無法得到哈希后的各特征的IDF的值。使用了IF-IDF并標準化以后主胧,我們就可以使用各個文本的詞特征向量作為文本的特征,進行分類或者聚類分析焙格。
當然TF-IDF不光可以用于文本挖掘己英,在信息檢索等很多領域都有使用。因此值得好好的理解這個方法的思想厢破。
在提取特征后就可以利用傳統(tǒng)的機器學習方法進行文本分類
import pandas as pd
import numpy as np
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn import svm
train = pd.read_csv('train.csv',lineterminator='\n')
test = pd.read_csv('test.csv',lineterminator = '\n')
#提取TF-IDF特征
column = "review"
test_id = test["ID"].copy()
vec = TfidfVectorizer(ngram_range=(1,2),min_df=3, max_df=0.9,use_idf=1,smooth_idf=1, sublinear_tf=1)
trn_term_doc = vec.fit_transform(train[column])
test_term_doc = vec.transform(test[column])
train['label'] = train['label'].replace('Negative', 0).astype(int)
train['label'] = train['label'].replace('Positive', 1).astype(int)
y=(train["label"]).astype(int)
lin_clf = svm.SVC(C = 3,gamma = 1,probability= True)
lin_clf.fit(trn_term_doc,y)
preds = lin_clf.predict(test_term_doc)
lin_clf.score(X_test,y_test)
0.9974
from sklearn.model_selection import GridSearchCV,train_test_split
#把要調(diào)整的參數(shù)以及其候選值 列出來摩泪;
param_grid = {"gamma":[1,3,5,7],
"C":[1,3,5,7]}
print("Parameters:{}".format(param_grid))
grid_search = GridSearchCV(svm.SVC(),param_grid,cv=5) #實例化一個GridSearchCV類
X_train,X_test,y_train,y_test = train_test_split(trn_term_doc,train['label'],random_state=10)
grid_search.fit(X_train,y_train) #訓練见坑,找到最優(yōu)的參數(shù)捏检,同時使用最優(yōu)的參數(shù)實例化一個新的SVC estimator。
def cpu_soft_nms(np.ndarray[float, ndim=2] boxes, float sigma=0.5, float Nt=0.3, float threshold=0.001, unsigned int method=0):
cdef unsigned int N = boxes.shape[0]
cdef float iw, ih, box_area
cdef float ua
cdef int pos = 0
cdef float maxscore = 0
cdef int maxpos = 0
cdef float x1,x2,y1,y2,tx1,tx2,ty1,ty2,ts,area,weight,ov
for i in range(N):
maxscore = boxes[i, 4]
maxpos = i
tx1 = boxes[i,0]
ty1 = boxes[i,1]
tx2 = boxes[i,2]
ty2 = boxes[i,3]
ts = boxes[i,4]
pos = i + 1
# get max box
while pos < N:
if maxscore < boxes[pos, 4]:
maxscore = boxes[pos, 4]
maxpos = pos
pos = pos + 1
# add max box as a detection
boxes[i,0] = boxes[maxpos,0]
boxes[i,1] = boxes[maxpos,1]
boxes[i,2] = boxes[maxpos,2]
boxes[i,3] = boxes[maxpos,3]
boxes[i,4] = boxes[maxpos,4]
# swap ith box with position of max box
boxes[maxpos,0] = tx1
boxes[maxpos,1] = ty1
boxes[maxpos,2] = tx2
boxes[maxpos,3] = ty2
boxes[maxpos,4] = ts
tx1 = boxes[i,0]
ty1 = boxes[i,1]
tx2 = boxes[i,2]
ty2 = boxes[i,3]
ts = boxes[i,4]
pos = i + 1
# NMS iterations, note that N changes if detection boxes fall below threshold
while pos < N:
x1 = boxes[pos, 0]
y1 = boxes[pos, 1]
x2 = boxes[pos, 2]
y2 = boxes[pos, 3]
s = boxes[pos, 4]
area = (x2 - x1 + 1) * (y2 - y1 + 1)
iw = (min(tx2, x2) - max(tx1, x1) + 1)
if iw > 0:
ih = (min(ty2, y2) - max(ty1, y1) + 1)
if ih > 0:
ua = float((tx2 - tx1 + 1) * (ty2 - ty1 + 1) + area - iw * ih)
ov = iw * ih / ua #iou between max box and detection box
if method == 1: # linear
if ov > Nt:
weight = 1 - ov
else:
weight = 1
elif method == 2: # gaussian
weight = np.exp(-(ov * ov)/sigma)
else: # original NMS
if ov > Nt:
weight = 0
else:
weight = 1
boxes[pos, 4] = weight*boxes[pos, 4]
# if box score falls below threshold, discard the box by swapping with last box
# update N
if boxes[pos, 4] < threshold:
boxes[pos,0] = boxes[N-1, 0]
boxes[pos,1] = boxes[N-1, 1]
boxes[pos,2] = boxes[N-1, 2]
boxes[pos,3] = boxes[N-1, 3]
boxes[pos,4] = boxes[N-1, 4]
N = N - 1
pos = pos - 1
pos = pos + 1
keep = [i for i in range(N)]
return keep