中文短文本分類項目實踐


一、WordCloud 制作詞雲

在網上摘取了一些文本(自己線下可以繼續添加語料),下面來制作一個中美貿易戰相關的詞雲。

1. jieba 分詞安裝

jieba 俗稱中文分詞利器,作用是來對文本語料進行分詞。

  • 全自動安裝:easy_install jieba 或者 pip install jieba / pip3 install jieba
  • 半自動安裝:先下載 https://pypi.python.org/pypi/jieba/ ,解壓后運行python setup.py install
  • 手動安裝:將 jieba 目錄放置於當前目錄或者 site-packages 目錄。
  • 安裝完通過 import jieba 驗證安裝成功與否。

2. WordCloud 安裝

WordCloud 作用是用來繪制詞雲。

3. 開始編碼實現

整個過程分為幾個步驟:

  • 文件加載
  • 分詞
  • 統計詞頻
  • 去停用詞
  • 構建詞雲

下面詳細看代碼:

#引入所需要的包
import jieba
import pandas as pd 
import numpy as np
from scipy.misc import imread 
from wordcloud import WordCloud,ImageColorGenerator
import matplotlib.pyplot as plt
#定義文件路徑
dir =  "D://ProgramData//PythonWorkSpace//study//"
#定義語料文件路徑
file = "".join([dir,"z_m.csv"])
#定義停用詞文件路徑
stop_words = "".join([dir,"stopwords.txt"])
#定義wordcloud中字體文件的路徑
simhei = "".join([dir,"simhei.ttf"])
#讀取語料
df = pd.read_csv(file, encoding='utf-8')
df.head()
#如果存在nan,刪除
df.dropna(inplace=True)
#將content一列轉為list
content=df.content.values.tolist()
#用jieba進行分詞操作
segment=[]
for line in content:
    try:
        segs=jieba.cut_for_search(line)
        segs = [v for v in segs if not str(v).isdigit()]#去數字
        segs = list(filter(lambda x:x.strip(), segs))   #去左右空格
        #segs = list(filter(lambda x:len(x)>1, segs)) #長度為1的字符
        for seg in segs:
            if len(seg)>1 and seg!='\r\n':
                segment.append(seg)
    except:
        print(line)
        continue
#分詞后加入一個新的DataFrame
words_df=pd.DataFrame({'segment':segment})
#加載停用詞
stopwords=pd.read_csv(stop_words,index_col=False,quoting=3,sep="\t",names=['stopword'], encoding='utf-8')               
#安裝關鍵字groupby分組統計詞頻,並按照計數降序排序
words_stat=words_df.groupby(by=['segment'])['segment'].agg({"計數":np.size})
words_stat=words_stat.reset_index().sort_values(by=["計數"],ascending=False)
#分組之后去掉停用詞
words_stat=words_stat[~words_stat.segment.isin(stopwords.stopword)]
#下面是重點,繪制wordcloud詞雲,這一提供2種方式
#第一種是默認的樣式
wordcloud=WordCloud(font_path=simhei,background_color="white",max_font_size=80)
word_frequence = {x[0]:x[1] for x in words_stat.head(1000).values}
wordcloud=wordcloud.fit_words(word_frequence)
plt.imshow(wordcloud)
wordcloud.to_file(r'wordcloud_1.jpg')  #保存結果
#第二種是自定義圖片
text = " ".join(words_stat['segment'].head(100).astype(str))
abel_mask = imread(r"china.jpg")  #這里設置了一張中國地圖
wordcloud2 = WordCloud(background_color='white',  # 設置背景顏色 
                     mask = abel_mask,  # 設置背景圖片
                     max_words = 3000,  # 設置最大現實的字數
                     font_path = simhei,  # 設置字體格式
                     width=2048,
                     height=1024,
                     scale=4.0,
                     max_font_size= 300,  # 字體最大值
                     random_state=42).generate(text)

# 根據圖片生成詞雲顏色
image_colors = ImageColorGenerator(abel_mask)
wordcloud2.recolor(color_func=image_colors)
# 以下代碼顯示圖片
plt.imshow(wordcloud2)
plt.axis("off")
plt.show()
wordcloud2.to_file(r'wordcloud_2.jpg') #保存結果

這里只給出默認生產的圖,自定義的圖保存在 wordcloud_2.jpg,回頭自己看。

enter image description here

二、LDA 提取關鍵字

接下來完成 LDA 提取關鍵字的實戰,如果有人對 LDA 的理論感興趣,推薦閱讀馬晨《LDA 漫游指南》這本書。

1. Gensim 安裝

Gensim 除了具備基本的語料處理功能外,Gensim 還提供了 LSI、LDA、HDP、DTM、DIM 等主題模型、TF-IDF 計算以及當前流行的深度神經網絡語言模型 word2vec、paragraph2vec 等算法,可謂是方便之至。

  • Gensim 安裝:pip install gensim
  • 安裝完通過 import gensim 驗證安裝成功與否。

2. LDA 提取關鍵字編碼實現

語料是一個關於汽車的短文本,傳統的關鍵字提取有基於 TF-IDF 算法的關鍵詞抽取;基於 TextRank 算法的關鍵詞抽取等方式。下面通過 Gensim 庫完成基於 LDA 的關鍵字提取。

整個過程步驟:

  • 文件加載
  • 分詞
  • 去停用詞
  • 構建詞袋模型
  • LDA 模型訓練
  • 結果可視化

下面詳細看代碼:

#引入庫文件
import jieba.analyse as analyse
import jieba
import pandas as pd
from gensim import corpora, models, similarities
import gensim
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
#設置文件路徑
dir = "D://ProgramData//PythonWorkSpace//study//"
file_desc = "".join([dir,'car.csv'])
stop_words = "".join([dir,'stopwords.txt'])
#定義停用詞
stopwords=pd.read_csv(stop_words,index_col=False,quoting=3,sep="\t",names=['stopword'], encoding='utf-8')
stopwords=stopwords['stopword'].values
#加載語料
df = pd.read_csv(file_desc, encoding='gbk')
#刪除nan行
df.dropna(inplace=True)
lines=df.content.values.tolist()
#開始分詞
sentences=[]
for line in lines:
    try:
        segs=jieba.lcut(line)
        segs = [v for v in segs if not str(v).isdigit()]#去數字
        segs = list(filter(lambda x:x.strip(), segs))   #去左右空格
        segs = list(filter(lambda x:x not in stopwords, segs)) #去掉停用詞
        sentences.append(segs)
    except Exception:
        print(line)
        continue
#構建詞袋模型
dictionary = corpora.Dictionary(sentences)
corpus = [dictionary.doc2bow(sentence) for sentence in sentences]
#lda模型,num_topics是主題的個數,這里定義了5個
lda = gensim.models.ldamodel.LdaModel(corpus=corpus, id2word=dictionary, num_topics=10)
#我們查一下第1號分類,其中最常出現的5個詞是:
print(lda.print_topic(1, topn=5))
#我們打印所有5個主題,每個主題顯示8個詞
for topic in lda.print_topics(num_topics=10, num_words=8):
    print(topic[1])

enter image description here

#顯示中文matplotlib
plt.rcParams['font.sans-serif'] = [u'SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 在可視化部分,我們首先畫出了九個主題的7個詞的概率分布圖
num_show_term = 8 # 每個主題下顯示幾個詞
num_topics  = 10  
for i, k in enumerate(range(num_topics)):
    ax = plt.subplot(2, 5, i+1)
    item_dis_all = lda.get_topic_terms(topicid=k)
    item_dis = np.array(item_dis_all[:num_show_term])
    ax.plot(range(num_show_term), item_dis[:, 1], 'b*')
    item_word_id = item_dis[:, 0].astype(np.int)
    word = [dictionary.id2token[i] for i in item_word_id]
    ax.set_ylabel(u"概率")
    for j in range(num_show_term):
        ax.text(j, item_dis[j, 1], word[j], bbox=dict(facecolor='green',alpha=0.1))
plt.suptitle(u'9個主題及其7個主要詞的概率', fontsize=18)
plt.show()

enter image description here

三、朴素貝葉斯和 SVM 文本分類

最后一部分,我們通過帶標簽的數據:

數據是一份司法數據,其中需求是對每一條輸入數據,判斷事情的主體是誰,比如報警人被老公打,報警人被老婆打,報警人被兒子打,報警人被女兒打等來進行文本有監督的分類操作。

本次主要選這 4 類標簽,基本操作過程步驟:

  • 文件加載
  • 分詞
  • 去停用詞
  • 抽取詞向量特征
  • 分別進行朴素貝葉斯和 SVM 分類算法建模
  • 評估打分

下面詳細看代碼:

#引入包
import random
import jieba
import pandas as pd
#指定文件目錄
dir = "D://ProgramData//PythonWorkSpace//chat//chat1//NB_SVM//"
#指定語料
stop_words = "".join([dir,'stopwords.txt'])
laogong = "".join([dir,'beilaogongda.csv'])  #被老公打
laopo = "".join([dir,'beilaopoda.csv'])  #被老婆打
erzi = "".join([dir,'beierzida.csv'])   #被兒子打
nver = "".join([dir,'beinverda.csv'])    #被女兒打
#加載停用詞
stopwords=pd.read_csv(stop_words,index_col=False,quoting=3,sep="\t",names=['stopword'], encoding='utf-8')
stopwords=stopwords['stopword'].values
#加載語料
laogong_df = pd.read_csv(laogong, encoding='utf-8', sep=',')
laopo_df = pd.read_csv(laopo, encoding='utf-8', sep=',')
erzi_df = pd.read_csv(erzi, encoding='utf-8', sep=',')
nver_df = pd.read_csv(nver, encoding='utf-8', sep=',')
#刪除語料的nan行
laogong_df.dropna(inplace=True)
laopo_df.dropna(inplace=True)
erzi_df.dropna(inplace=True)
nver_df.dropna(inplace=True)
#轉換
laogong = laogong_df.segment.values.tolist()
laopo = laopo_df.segment.values.tolist()
erzi = erzi_df.segment.values.tolist()
nver = nver_df.segment.values.tolist()
#定義分詞和打標簽函數preprocess_text
#參數content_lines即為上面轉換的list
#參數sentences是定義的空list,用來儲存打標簽之后的數據
#參數category 是類型標簽
def preprocess_text(content_lines, sentences, category):
    for line in content_lines:
        try:
            segs=jieba.lcut(line)
            segs = [v for v in segs if not str(v).isdigit()]#去數字
            segs = list(filter(lambda x:x.strip(), segs))   #去左右空格
            segs = list(filter(lambda x:len(x)>1, segs)) #長度為1的字符
            segs = list(filter(lambda x:x not in stopwords, segs)) #去掉停用詞
            sentences.append((" ".join(segs), category))# 打標簽
        except Exception:
            print(line)
            continue 
#調用函數、生成訓練數據
sentences = []
preprocess_text(laogong, sentences, 'laogong')
preprocess_text(laopo, sentences, 'laopo')
preprocess_text(erzi, sentences, 'erzi')
preprocess_text(nver, sentences, 'nver')

#打散數據,生成更可靠的訓練集
random.shuffle(sentences)

#控制台輸出前10條數據,觀察一下
for sentence in sentences[:10]:
    print(sentence[0], sentence[1])
#用sk-learn對數據切分,分成訓練集和測試集
from sklearn.model_selection import train_test_split
x, y = zip(*sentences)
x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=1234)

#抽取特征,我們對文本抽取詞袋模型特征
from sklearn.feature_extraction.text import CountVectorizer
vec = CountVectorizer(
    analyzer='word', # tokenise by character ngrams
    max_features=4000,  # keep the most common 1000 ngrams
)
vec.fit(x_train)
#用朴素貝葉斯算法進行模型訓練
from sklearn.naive_bayes import MultinomialNB
classifier = MultinomialNB()
classifier.fit(vec.transform(x_train), y_train)
#對結果進行評分
print(classifier.score(vec.transform(x_test), y_test))

這時輸出結果為:0.99284009546539376。

我們看到,這個時候的結果評分已經很高了,當然我們的訓練數據集中,噪聲都已經提前處理完了,使得數據集在很少數據量下,模型得分就可以很高。

下面我們繼續進行優化:

#可以把特征做得更棒一點,試試加入抽取2-gram和3-gram的統計特征,比如可以把詞庫的量放大一點。
from sklearn.feature_extraction.text import CountVectorizer
vec = CountVectorizer(
    analyzer='word', # tokenise by character ngrams
    ngram_range=(1,4),  # use ngrams of size 1 and 2
    max_features=20000,  # keep the most common 1000 ngrams
)
vec.fit(x_train)
#用朴素貝葉斯算法進行模型訓練
from sklearn.naive_bayes import MultinomialNB
classifier = MultinomialNB()
classifier.fit(vec.transform(x_train), y_train)
#對結果進行評分
print(classifier.score(vec.transform(x_test), y_test))

這時輸出結果為:0.97852028639618138。

我們看到,這個時候的結果稍微有點下降,這與我們語料本身有關系,但是如果隨着數據量更多,語料文本長點,我認為或許第二種方式應該比第一種方式效果更好。

可見使用 scikit-learn 包,算法模型開發非常簡單,下面看看 SVM 的應用:

from sklearn.svm import SVC
svm = SVC(kernel='linear')
svm.fit(vec.transform(x_train), y_train)
print(svm.score(vec.transform(x_test), y_test))

這時輸出結果為:0.997613365155,比朴素貝葉斯效果更好。

語料數據下載地址:https://github.com/sujeek/chat_list

四、數據科學比賽大殺器 XGBoost 實戰文本分類

在說 XGBoost 之前,我們先簡單從樹模型說起,典型的決策樹模型。決策樹的學習過程主要包括:

  • 特征選擇: 從訓練數據的特征中選擇一個特征作為當前節點的分裂標准(特征選擇的標准不同產生了不同的特征決策樹算法,如根據信息增益、信息增益率和gini等)。

  • 決策樹生成: 根據所選特征評估標准,從上至下遞歸地生成子節點,直到數據集不可分則停止決策樹生長。

  • 剪枝: 決策樹容易過擬合,需通過剪枝來預防過擬合(包括預剪枝和后剪枝)。

常見的決策樹算法有 ID3、C4.5、CART 等。

在 sklearn 中決策樹分類模型如下,可以看到默認通過 gini 計算實現。

sklearn.tree.DecisionTreeClassifier(criterion=’gini’, splitter=’best’, max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None, random_state=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, class_weight=None, presort=False)
盡管決策樹分類算法模型在應用中能夠得到很好的結果,並通過剪枝操作提高模型泛化能力,但一棵樹的生成肯定不如多棵樹,因此就有了隨機森林,並成功解決了決策樹泛化能力弱的缺點,隨機森林就是集成學習的一種方式。

在西瓜書中對集成學習的描述:集成學習通過將多個學習器進行結合,可獲得比單一學習器顯著優越的泛化性能,對“弱學習器” 尤為明顯。弱學習器常指泛化性能略優於隨機猜測的學習器。集成學習的結果通過投票法產生,即“少數服從多數”。個體學習不能太壞,並且要有“多樣性”,即學習器間具有差異,即集成個體應“好而不同”。

假設基分類器的錯誤率相互獨立,則由 Hoeffding 不等式可知,隨着集成中個體分類器數目 T 的增大,集成的錯誤率將指數級下降,最終趨向於零。

但是這里有一個關鍵假設:基學習器的誤差相互獨立,而現實中個體學習器是為解決同一個問題訓練出來的,所以不可能相互獨立。因此,如何產生並結合“好而不同”的個體學習器是集成學習研究的核心。

集成學習大致分為兩大類:

  • Boosting:個體學習器間存在強依賴關系,必須串行生成的序列化方法。代表:AdaBoost、GBDT、XGBoost

  • Bagging:個體學習器間不存在強依賴關系,可同時生成的並行化方法。代表:隨機森林(Random Forest)

在 sklearn 中,對於 Random Forest、AdaBoost、GBDT 都有實現,下面我們重點說說在 kaggle、阿里天池等數據科學比賽經常會用到的大殺器 XGBoost,來實戰文本分類 。

關於分類數據,還是延用《NLP 中文短文本分類項目實踐(上)》中朴素貝葉斯算法的數據,這里對數據的標簽做個修改,標簽由 str 換成 int 類型,並從 0 開始,0、1、2、3 代表四類,所以是一個多分類問題:

preprocess_text(laogong, sentences,0)       #laogong   分類0
preprocess_text(laopo, sentences, 1)        #laopo   分類1
preprocess_text(erzi, sentences, 2)          #erzi   分類2
preprocess_text(nver, sentences,3)          #nver   分類3

接着我們引入 XGBoost 的庫(默認你已經安裝好 XGBoost),整個代碼加了注釋,可以當做模板來用,每次使用只需微調即可使用。

import xgboost as xgb  
from sklearn.model_selection import StratifiedKFold  
import numpy as np
# xgb矩陣賦值  
xgb_train = xgb.DMatrix(vec.transform(x_train), label=y_train)  
xgb_test = xgb.DMatrix(vec.transform(x_test))  

上面在引入庫和構建完 DMatrix 矩陣之后,下面主要是調參指標,可以根據參數進行調參:

params = {
    'booster': 'gbtree',  # 使用gbtree
    'objective': 'multi:softmax',  # 多分類的問題、  
    # 'objective': 'multi:softprob',   # 多分類概率  
    # 'objective': 'binary:logistic',  #二分類
    'eval_metric': 'merror',  # logloss
    'num_class': 4,  # 類別數,與 multisoftmax 並用  
    'gamma': 0.1,  # 用於控制是否后剪枝的參數,越大越保守,一般0.1、0.2這樣子。  
    'max_depth': 8,  # 構建樹的深度,越大越容易過擬合  
    'alpha': 0,  # L1正則化系數  
    'lambda': 10,  # 控制模型復雜度的權重值的L2正則化項參數,參數越大,模型越不容易過擬合。  
    'subsample': 0.7,  # 隨機采樣訓練樣本  
    'colsample_bytree': 0.5,  # 生成樹時進行的列采樣  
    'min_child_weight': 3,
    # 這個參數默認是 1,是每個葉子里面 h 的和至少是多少,對正負樣本不均衡時的 0-1 分類而言  
    # 假設 h 在 0.01 附近,min_child_weight 為 1 意味着葉子節點中最少需要包含 100 個樣本。  
    # 這個參數非常影響結果,控制葉子節點中二階導的和的最小值,該參數值越小,越容易 overfitting。  
    'silent': 0,  # 設置成1則沒有運行信息輸出,最好是設置為0.  
    'eta': 0.03,  # 如同學習率  
    'seed': 1000,
    'nthread': -1,  # cpu 線程數  
    'missing': 1
    # 'scale_pos_weight': (np.sum(y==0)/np.sum(y==1))  # 用來處理正負樣本不均衡的問題,通常取:sum(negative cases) / sum(positive cases)  
}  

這里進行迭代次數設置和 k 折交叉驗證,訓練模型,並進行模型保存和預測結果。

plst = list(params.items())
num_rounds = 200  # 迭代次數  
watchlist = [(xgb_train, 'train')]
# 交叉驗證  
result = xgb.cv(plst, xgb_train, num_boost_round=200, nfold=4, early_stopping_rounds=200, verbose_eval=True,
                folds=StratifiedKFold(n_splits=4).split(vec.transform(x_train), y_train))
# 訓練模型並保存  
# early_stopping_rounds 當設置的迭代次數較大時,early_stopping_rounds 可在一定的迭代次數內准確率沒有提升就停止訓練  
model = xgb.train(plst, xgb_train, num_rounds, watchlist, early_stopping_rounds=200)
# model.save_model('../data/model/xgb.model')  # 用於存儲訓練出的模型    
# predicts = model.predict(xgb_test)   #預測

五、詞向量 Word2Vec 和 FastText 實戰

深度學習帶給自然語言處理最令人興奮的突破是詞向量(Word Embedding)技術。詞向量技術是將詞語轉化成為稠密向量。在自然語言處理應用中,詞向量作為機器學習、深度學習模型的特征進行輸入。因此,最終模型的效果很大程度上取決於詞向量的效果。

1. Word2Vec 詞向量

在 Word2Vec 出現之前,自然語言處理經常把字詞進行獨熱編碼,也就是 One-Hot Encoder。

    大數據 [0,0,0,0,0,0,0,1,0,……,0,0,0,0,0,0,0] 雲計算[0,0,0,0,1,0,0,0,0,……,0,0,0,0,0,0,0] 機器學習[0,0,0,1,0,0,0,0,0,……,0,0,0,0,0,0,0] 人工智能[0,0,0,0,0,0,0,0,0,……,1,0,0,0,0,0,0] 

比如上面的例子中,大數據 、雲計算、機器學習和人工智能各對應一個向量,向量中只有一個值為 1,其余都為 0。所以使用 One-Hot Encoder 有以下問題:第一,詞語編碼是隨機的,向量之間相互獨立,看不出詞語之間可能存在的關聯關系。第二,向量維度的大小取決於語料庫中詞語的多少,如果語料包含的所有詞語對應的向量合為一個矩陣的話,那這個矩陣過於稀疏,並且會造成維度災難。

而解決這個問題的手段,就是使用向量表示(Vector Representations)。

Word2Vec 是 Google 團隊 2013 年推出的,自提出后被廣泛應用在自然語言處理任務中,並且受到它的啟發,后續出現了更多形式的詞向量模型。Word2Vec 主要包含兩種模型:Skip-Gram 和 CBOW,值得一提的是,Word2Vec 詞向量可以較好地表達不同詞之間的相似和類比關系。

下面我們通過代碼實戰來體驗一下 Word2Vec,pip install gensim 安裝好庫后,即可導入使用:

訓練模型定義

from gensim.models import Word2Vec  
model = Word2Vec(sentences, sg=1, size=100,  window=5,  min_count=5,  negative=3, sample=0.001, hs=1, workers=4)  

參數解釋:

  • sg=1 是 skip-gram 算法,對低頻詞敏感;默認 sg=0 為 CBOW 算法。
  • size 是輸出詞向量的維數,值太小會導致詞映射因為沖突而影響結果,值太大則會耗內存並使算法計算變慢,一般值取為 100 到 200 之間。
  • window 是句子中當前詞與目標詞之間的最大距離,3 表示在目標詞前看 3-b 個詞,后面看 b 個詞(b 在 0-3 之間隨機)。
  • min_count 是對詞進行過濾,頻率小於 min-count 的單詞則會被忽視,默認值為 5。
  • negative 和 sample 可根據訓練結果進行微調,sample 表示更高頻率的詞被隨機下采樣到所設置的閾值,默認值為 1e-3。
  • hs=1 表示層級 softmax 將會被使用,默認 hs=0 且 negative 不為 0,則負采樣將會被選擇使用。
  • 詳細參數說明可查看 Word2Vec 源代碼。

訓練后的模型保存與加載,可以用來計算相似性,最大匹配程度等。

model.save(model)  # 保存模型
model = Word2Vec.load(model)  # 加載模型
model.most_similar(positive=['女人', '女王'], negative=['男人'])
# 輸出[('女王', 0.50882536), ...]  
model.similarity('女人', '男人')
# 輸出0.73723527 

2.FastText詞向量

FastText 是 facebook 開源的一個詞向量與文本分類工具,模型簡單,訓練速度非常快。FastText 做的事情,就是把文檔中所有詞通過 lookup table 變成向量,取平均后直接用線性分類器得到分類結果。

FastText python 包的安裝:

pip install fasttext

FastText 做文本分類要求文本是如下的存儲形式:

__label__1 ,內容。 __label__2,內容。 __label__3 ,內容。 __label__4,內容。 

其中前面的 __label__ 是前綴,也可以自己定義,__label__ 后接的為類別,之后接的就是我是我們的文本內容。

調用 fastText 訓練生成模型,對模型效果進行評估。

import fasttext

classifier = fasttext.supervised('train_data.txt', 'classifier.model', label_prefix='__label__')
result = classifier.test('train_data.txt')
print(result.precision)
print(result.recall)

實際預測過程:

    label_to_cate = {1:'科技', 2:'財經', 3:'體育', 4:'生活', 5:'美食'} texts = ['現如今 機器 學習 和 深度 學習 帶動 人工智能 飛速 的 發展 並 在 圖片 處理 語音 識別 領域 取得 巨大成功'] labels = classifier.predict(texts) print(labels) print(label_to_cate[int(labels[0][0])]) [[u'1']] 科技 

六、文本分類之神經網絡 CNN 和 LSTM 實戰

1. CNN 做文本分類實戰

CNN 在圖像上的巨大成功,使得大家都有在文本上試一把的沖動。CNN 的原理這里就不贅述了,關鍵看看怎樣用於文本分類的,下圖是一個 TextCNN 的結構:

enter image description here

具體結構介紹:

(1)輸入層

可以把輸入層理解成把一句話轉化成了一個二維的圖像:每一排是一個詞的 Word2Vec 向量,縱向是這句話的每個詞按序排列。輸入數據的 size,也就是圖像的 size,n×k,n 代表訓練數據中最長的句子的詞個數,k 是 embbeding 的維度。從輸入層還可以看出 kernel 的 size。很明顯 kernel 的高 (h) 會有不同的值,有的是 2,有的是 3。這很容易理解,不同的 kernel 想獲取不同范圍內詞的關系;和圖像不同的是,NLP 中的 CNN 的 kernel 的寬 (w) 一般都是圖像的寬,也就是 Word2Vec 的維度,這也可以理解,因為我們需要獲得的是縱向的差異信息,也就是不同范圍的詞出現會帶來什么信息。

(2)卷積層

由於 kernel 的特殊形狀,因此卷積后的 feature map 是一個寬度是 1 的長條。

(3)池化層

這里使用 MaxPooling,並且一個 feature map 只選一個最大值留下。這被認為是按照這個 kernel 卷積后的最重要的特征。

(4)全連接層

這里的全連接層是帶 dropout 的全連接層和 softmax。

下面我們看看自己用 Tensorflow 如何實現一個文本分類器:

超參數定義:

# 文檔最長長度
MAX_DOCUMENT_LENGTH = 100
# 最小詞頻數
MIN_WORD_FREQUENCE = 2
# 詞嵌入的維度
EMBEDDING_SIZE = 20
# filter個數
N_FILTERS = 10
# 感知野大小
WINDOW_SIZE = 20
# filter的形狀
FILTER_SHAPE1 = [WINDOW_SIZE, EMBEDDING_SIZE]
FILTER_SHAPE2 = [WINDOW_SIZE, N_FILTERS]
# 池化
POOLING_WINDOW = 4
POOLING_STRIDE = 2
n_words = 0

網絡結構定義,2 層的卷積神經網絡,用於短文本分類:

def cnn_model(features, target):
    # 先把詞轉成詞嵌入
    # 我們得到一個形狀為[n_words, EMBEDDING_SIZE]的詞表映射矩陣
    # 接着我們可以把一批文本映射成[batch_size, sequence_length, EMBEDDING_SIZE]的矩陣形式
    target = tf.one_hot(target, 15, 1, 0)
    word_vectors = tf.contrib.layers.embed_sequence(
        features, vocab_size=n_words, embed_dim=EMBEDDING_SIZE, scope='words')
    word_vectors = tf.expand_dims(word_vectors, 3)
    with tf.variable_scope('CNN_Layer1'):
        # 添加卷積層做濾波
        conv1 = tf.contrib.layers.convolution2d(
            word_vectors, N_FILTERS, FILTER_SHAPE1, padding='VALID')
        # 添加RELU非線性
        conv1 = tf.nn.relu(conv1)
        # 最大池化
        pool1 = tf.nn.max_pool(
            conv1,
            ksize=[1, POOLING_WINDOW, 1, 1],
            strides=[1, POOLING_STRIDE, 1, 1],
            padding='SAME')
        # 對矩陣進行轉置,以滿足形狀
        pool1 = tf.transpose(pool1, [0, 1, 3, 2])
    with tf.variable_scope('CNN_Layer2'):
        # 第2個卷積層
        conv2 = tf.contrib.layers.convolution2d(
            pool1, N_FILTERS, FILTER_SHAPE2, padding='VALID')
        # 抽取特征
        pool2 = tf.squeeze(tf.reduce_max(conv2, 1), squeeze_dims=[1])

    # 全連接層
    logits = tf.contrib.layers.fully_connected(pool2, 15, activation_fn=None)
    loss = tf.losses.softmax_cross_entropy(target, logits)

    train_op = tf.contrib.layers.optimize_loss(
        loss,
        tf.contrib.framework.get_global_step(),
        optimizer='Adam',
        learning_rate=0.01)

    return ({
                'class': tf.argmax(logits, 1),
                'prob': tf.nn.softmax(logits)
            }, loss, train_op)

2. LSTM 做文本分類實戰

上面實現了基於 CNN 的文本分類器之后,再來做一個基於 LSTM 分類器,會覺得非常容易,因為變化的部分只偶遇中間把 CNN 換成了 LSTM 模型。關於 RNN 和 LSTM 的理論知識,請自行解決,這里主要給出思路和代碼實現。

具體結構,參照下面這幅圖:

enter image description here

上面我們用的 Tensorflow 實現的,這次我們用 Keras 更簡單方便的實現其核心代碼:

# 超參數定義
MAX_SEQUENCE_LENGTH = 100
EMBEDDING_DIM = 200
VALIDATION_SPLIT = 0.16
TEST_SPLIT = 0.2
epochs = 10
batch_size = 128
# 模型網絡定義
model = Sequential()
model.add(Embedding(len(word_index) + 1, EMBEDDING_DIM, input_length=MAX_SEQUENCE_LENGTH))
model.add(LSTM(200, dropout=0.2, recurrent_dropout=0.2))
model.add(Dropout(0.2))
model.add(Dense(labels.shape[1], activation='softmax'))
model.summary()

可見,基於 Keras 實現神經網絡比 Tensorflow 要簡單和容易很多,Keras 搭建神經網絡俗稱“堆積木”,這里有所體現。所以筆者也推薦,如果想快速實現一個神經網絡,建議先用 Keras 快速搭建驗證,之后再嘗試用 Tensorflow 去實現。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM