這是在接單過程中得到的一個好東西,感覺這個包含了所有文本處理的問題和方式,主要通過分析文本進行轉換,學到了好多東西,我覺得現在接單不算是只爲了掙錢而是多練手,多掌握數據分析過程以及多任務處理,我需要的是平臺而不是工作。
NLTK包的安裝
什麼是NLTK
一個完整的自然語言處理框架
- 自帶語料庫,詞性分類庫
- 自帶分類,分詞,等等功能
- 有強大的社區支持
- 框架設計上沒有考慮中文
NLTK的主要模塊
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-xszk2FH9-1574612235177)(./nltkm.png)]
如何安裝NLTK
Anaconda中已經默認安裝
- pip install nltk
- nltk.download()
import nltk
nltk.download()
import nltk
import ssl
try:
_create_unverified_https_context = ssl._create_unverified_context
except AttributeError:
pass
else:
ssl._create_default_https_context = _create_unverified_https_context
nltk.download()
NLTK替代包
NLTK的使用稍顯複雜,初學者也可以使用各種基於NLTK的簡化包
TextBlob
語料庫的準備
什麼是語料庫
# 布朗語料庫示例
from nltk.corpus import brown
brown.categories()
len(brown.sents())
brown.sents()[0]
len(brown.words())
常見的語料庫格式
外部文件
除直接網絡抓取並加工的情況外,原始文檔由於內容較多,往往會首先以單個/多個文本文件的形式保存在外部,然後讀入程序
list
結構靈活鬆散,有利於對原始語料進行處理,也可以隨時增刪成員
[
‘大魚吃小魚也吃蝦米,小魚吃蝦米。’,
‘我幫你,你也幫我。’
]
list of list
語料完成分詞後的常見形式,每個文檔成爲詞條構成的list,而這些list又是原文檔list的成員
[
[‘大魚’, ‘吃’, ‘小魚’, ‘也’, ‘吃’, ‘蝦米’, ‘,’, ‘小魚’, ‘吃’, ‘蝦米’, ‘。’],
[‘我’, ‘幫’, ‘你’, ‘,’, ‘你’, ‘也’, ‘幫’, ‘我’, ‘。’]
]
DataFrame
使用詞袋模型進行後續數據分析時常見格式,行/列代表語料index,相應的列/行代表詞條,或者需要加以記錄的文檔屬性,如作者,原始超鏈接,發表日期等
詞條/文檔對應時,單元格記錄相應的詞條出現頻率,或者相應的概率/分值
Doc2Term矩陣
Term2Doc矩陣
可以和原始語料的外部文件/list配合使用
對於單個文檔,也可以建立DataFrame,用行/列代表一個句子/段落/章節。
準備《射鵰》語料庫
爲使用Python還不熟練的學員提供一個基於Pandas的通用操作框架。
讀入爲數據框
import pandas as pd
# 有的環境配置下read_table出錯,因此改用read_csv
raw = pd.read_csv("金庸-射鵰英雄傳txt精校版.txt",
names = ['txt'], sep ='aaa', encoding ="utf-8" ,engine='python')
print(len(raw))
raw
加入章節標識
# 章節判斷用變量預處理
def m_head(tmpstr):
return tmpstr[:1]
def m_mid(tmpstr):
return tmpstr.find("回 ")
raw['head'] = raw.txt.apply(m_head)
raw['mid'] = raw.txt.apply(m_mid)
raw['len'] = raw.txt.apply(len)
# raw['chap'] = 0
raw.head(50)
# 章節判斷
chapnum = 0
for i in range(len(raw)):
if raw['head'][i] == "第" and raw['mid'][i] > 0 and raw['len'][i] < 30 :
chapnum += 1
if chapnum >= 40 and raw['txt'][i] == "附錄一:成吉思汗家族" :
chapnum = 0
raw.loc[i, 'chap'] = chapnum
# 刪除臨時變量
del raw['head']
del raw['mid']
del raw['len']
raw.head(50)
提取出所需章節
raw[raw.chap == 7]
tmpchap = raw[raw.chap == 7].copy()
##tmpchap = raw[raw.chap == 7].copy()
#firstIdx = tmpchap.index[0]
#firstIdx
tmpchap.reset_index(drop=True, inplace=True)
tmpchap['paraidx'] = tmpchap.index
tmpchap['paralen'] = tmpchap.txt.apply(len)
tmpchap[tmpchap.paralen == tmpchap.paralen.max()]
#tmpchap.head(10)
tmppara = tmpchap.txt[2]
tmppara
import re
tmppara = tmpchap[tmpchap['paraidx'] == 100].copy()
tmppara
#tmpstr = tmppara.txt[224]
#tmpstr
#sentences = re.findall('(.*?[?。!;:](’|”)?)',tmpstr)
#sentences
from matplotlib import pyplot as plt
%matplotlib inline
raw.txt.agg(len).plot.box()
rawgrp = raw.groupby('chap')
chapter = rawgrp.sum()##.agg(sum) # 只有字符串列的情況下,sum函數自動轉爲合併字符串
chapter = chapter[chapter.index != 0]
chapter.txt[2]
實戰1:準備工具與素材
請自行完成分析用Anaconda環境的安裝和配置。
請自行熟悉Jupyter notebook環境的操作。
自行提取《射鵰》任意一回的文字,並完成如下操作:
- 將其讀入爲按整句分案例行的數據框格式,並用另一個變量標識其所在段落的流水號。
- 將上述數據框轉換爲以整段爲成員的list格式。
說明:
最後一題主要涉及到Pandas的操作,對該模塊不熟悉的學員可直接繼續後續課程的學習,這部分知識的欠缺並不會影響對文本挖掘課程本身的學習。當然,能懂得相應的知識是最好的
分詞
分詞原理簡介
分詞的算法分類
-
基於字符串的匹配
- 即掃描字符串,如果發現字符串的子串和詞相同,就算匹配。
- 通常會加入一些啓發式算法,比如“正向/反向最大匹配”,“長詞優先”等
- 優點是速度快,但對歧義和未登錄詞處理不好
-
基於統計以及機器學習的分詞方式
- 基於人工標註的詞性和統計特徵進行建模,並通過模型計算分詞概率
- 常見的序列標註模型有HMM(隱馬爾可夫)和CRF(條件隨機場)
- 這類分詞算法能很好的處理歧義和未登錄詞問題,效果比前一類要好,但是需要大量的人工標註數據,分詞速度也較慢
注意:分詞算法本身在中文文本挖掘裏就是一個“巨坑”
基於字符串匹配的分詞算法原理
-
以現有的詞典爲基礎進行
-
最大匹配法:以設定的最大詞長度爲框架,取出其中最長的匹配詞
- “中華人民共和國”會被完整取出,而不會進一步被分詞
- 最佳匹配法:按照詞典中的頻率高低,優先取高頻詞
-
最大概率法:對句子整體進行分詞,找到最佳的詞彙排列組合規律
- 例:早上好->早上/好
-
最短路徑分詞:尋找單詞數最少的分詞方式
分詞的難點
- 分詞歧義
- 我個人沒有意見
- 三個人沒有意見
- 未登錄詞識別:蔡國慶
- 數字
- 實體名稱/專業術語
- 成語
- 虛詞、語氣詞
常見的分詞工具
- 中科院計算所NLPIR http://ictclas.nlpir.org/
- 哈工大的LTP https://www.ltp-cloud.com/
- 斯坦福分詞器 http://nlp.stanford.edu/software/segmenter.shtml
- 結巴分詞 https://github.com/fxsjy/jieba
結巴分詞的基本用法
jieba是目前應用最廣,評價也較高的分詞工具包
安裝
https://pypi.python.org/pypi/jieba/
pip install jieba
基本特點
三種分詞模式
精確模式,試圖將句子最精確地切開,適合做文本分析
全模式,把句子中所有的可以成詞的詞語都掃描出來,速度非常快,但是不能解決歧義
搜索引擎模式,在精確模式的基礎上,對長詞再次切分,提高召回率,適合用於搜索引擎分詞
支持繁體分詞
支持自定義詞典
import jieba
tmpstr = "郭靖和哀牢山三十六劍。"
res = jieba.cut(tmpstr) # 精確模式
print(res) # 是一個可迭代的 generator,可以使用 for 循環來遍歷結果,本質上類似list
print(' '.join(res))
res = jieba.cut(tmpstr)
list(word for word in res) # 演示generator的用法
print(jieba.lcut(tmpstr)) # 結果直接輸出爲list
print('/'.join(jieba.cut(tmpstr, cut_all = True))) # 全模式
# 搜索引擎模式,還有jieba.lcut_for_search可用
print('/'.join(jieba.cut_for_search(tmpstr)))
#raw['new'] =jieba.lcut( raw.txt.)
raw.head()
修改詞典
動態增刪新詞
在程序中可以動態根據分詞的結果,對內存中的詞庫進行更新
add_word(word)
word:新詞
freq=None:詞頻
tag=None:具體詞性
del_word(word)
# 動態修改詞典
jieba.add_word("哀牢山三十六劍")
'/'.join(jieba.cut(tmpstr))
jieba.del_word("哀牢山三十六劍")
'/'.join(jieba.cut(tmpstr))
使用自定義詞典
load_userdict(file_name)
file_name:文件類對象或自定義詞典的路徑
詞典基本格式
一個詞佔一行:詞、詞頻(可省略)、詞性(可省略),用空格隔開
詞典文件必須爲 UTF-8 編碼
必要時可以使用Uedit32進行文件編碼轉換
雲計算 5
李小福 2 nr
easy_install 3 eng
臺中
dict = '金庸小說詞庫.txt'
jieba.load_userdict(dict) # dict爲自定義詞典的路徑
'/'.join(jieba.cut(tmpstr))
使用搜狗細胞詞庫
https://pinyin.sogou.com/dict/
按照詞庫分類或者關鍵詞搜索方式,查找並下載所需詞庫
使用轉換工具,將其轉換爲txt格式
深藍詞庫轉換
奧創詞庫轉換
在程序中導入相應詞庫
去除停用詞
常見的停用詞種類
超高頻的常用詞:基本不攜帶有效信息/歧義太多無分析價值
的、地、得
虛詞:如介詞,連詞等
只、條、件
當、從、同
專業領域的高頻詞:基本不攜帶有效信息
視情況而定的停用詞
呵呵
emoj
分詞後去除停用詞
基本步驟
讀入停用詞表文件
正常分詞
在分詞結果中去除停用詞
新列表 = [ word for word in 源列表 if word not in 停用詞列表 ]
該方法存在的問題:停用詞必須要被分詞過程正確拆分出來才行
newlist = [ w for w in jieba.cut(tmpstr) if w not in ['和', '。'] ]
print(newlist)
import pandas as pd
tmpdf = pd.read_csv('停用詞.txt',
names = ['w'], sep = 'aaa', encoding = 'utf-8')
tmpdf.head()
tmpchap = raw[raw.chap == 7].copy()
##tmpchap = raw[raw.chap == 7].copy()
#firstIdx = tmpchap.index[0]
#firstIdx
tmpchap.reset_index(drop=True, inplace=True)
tmpchap['paraidx'] = tmpchap.index
tmpchap['paralen'] = tmpchap.txt.apply(len)
paratxt = tmpchap[tmpchap.paralen == tmpchap.paralen.max()].txt
paratxt.reset_index(drop=True, inplace=True)
# 熟悉Python的可以直接使用 open('stopWord.txt').readlines() 獲取停用詞list,效率更高
[ w for w in jieba.cut(paratxt[0]) if w not in list(tmpdf.w) ]
用extract_tags函數去除停用詞
方法特點:
根據TF-IDF算法將特徵詞提取出來,在提取之前去掉停用詞
可以人工指定停用詞字典
jieba.analyse.set_stop_words()
# 使用預先準備的停用詞表
import jieba.analyse as ana
ana.set_stop_words('停用詞.txt')
jieba.lcut(tmpstr) # 讀入的停用詞列表對分詞結果無效
ana.extract_tags(tmpstr, topK = 20) # 使用TF-IDF算法提取關鍵詞,並同時去掉停用詞
詞性標註
import jieba.posseg
posseg.cut():給出附加詞性的分詞結果
詞性標註採用和 ICTCLAS 兼容的標記法
後續可基於詞性做進一步處理,如只提取出名詞,動詞等
import jieba.posseg as psg
tmpres = psg.cut(tmpstr) # 附加詞性的分詞結果
print(tmpres)
for item in tmpres:
print(item.word, item.flag)
psg.lcut(tmpstr) # 直接輸出爲list,成員爲pair
分詞的NLTK實現
NLTK只能識別用空格作爲詞條分割方式,因此不能直接用於中文文本的分詞。
一般的做法是先用jieba分詞,然後轉換爲空格分隔的連續文本,再轉入NLTK框架使用。
rawtext = '周伯通笑道:“你懂了嗎?…”
txt = ’ '.join(jieba.cut(rawtext)) # “周伯通 笑 道 :…”
toke = nltk.word_tokenize(txt) # [‘周伯通’, ‘笑’, ‘道’, ‘:’…]
實戰2:《射鵰》一書分詞
選取第一回的文字,應用搜狗的細胞詞庫和停用詞表,清理出乾淨的分詞結果。
選取第一回中最長的1個段落,比較不使用詞庫、不使用停用詞表前後的分詞結果。
熟悉搜狗細胞詞庫網站中的資源,思考哪些詞庫可能是自己需要的,下載相應的資源並進行格式轉換。
tmpchap = raw[raw.chap == 1].copy()
##tmpchap = raw[raw.chap == 1].copy()
#firstIdx = tmpchap.index[0]
#firstIdx
tmpchap.reset_index(drop=True, inplace=True)
tmpchap['paraidx'] = tmpchap.index
tmpchap['paralen'] = tmpchap.txt.apply(len)
paratxt = tmpchap[tmpchap.paralen == tmpchap.paralen.max()].txt
paratxt.reset_index(drop=True, inplace=True)
# 熟悉Python的可以直接使用 open('stopWord.txt').readlines() 獲取停用詞list,效率更高
[ w for w in jieba.cut(paratxt[0]) if w not in list(tmpdf.w) ]
詞雲展示
詞頻統計
絕大部分詞頻統計工具都是基於分詞後構建詞條的list進行,因此首先需要完成相應的分詞工作。
import pandas as pd
# 載入語料
raw = pd.read_csv("金庸-射鵰英雄傳txt精校版.txt",
names = ['txt'], sep ='aaa', encoding ="utf-8" ,engine='python')
# 章節判斷用變量預處理
def m_head(tmpstr):
return tmpstr[:1]
def m_mid(tmpstr):
return tmpstr.find("回 ")
raw['head'] = raw.txt.apply(m_head)
raw['mid'] = raw.txt.apply(m_mid)
raw['len'] = raw.txt.apply(len)
# 章節判斷
chapnum = 0
for i in range(len(raw)):
if raw['head'][i] == "第" and raw['mid'][i] > 0 and raw['len'][i] < 30 :
chapnum += 1
if chapnum >= 40 and raw['txt'][i] == "附錄一:成吉思汗家族" :
chapnum = 0
raw.loc[i, 'chap'] = chapnum
# 刪除臨時變量
del raw['head']
del raw['mid']
del raw['len']
#取出特定的某一回
chapidx = 1
raw[raw.chap == chapidx]
tmpchap = raw[raw.chap == chapidx].copy()
##tmpchap = raw[raw.chap == 7].copy()
#firstIdx = tmpchap.index[0]
#firstIdx
tmpchap.reset_index(drop=True, inplace=True)
tmpchap['paraidx'] = tmpchap.index
tmpchap['paralen'] = tmpchap.txt.apply(len)
#tmpchap[tmpchap.paralen == tmpchap.paralen.max()]
alltxt = "".join(tmpchap.txt[1:])
alltxt
import jieba
dict = '金庸小說詞庫.txt'
jieba.load_userdict(dict) # dict爲自定義詞典的路徑
tmpdf = pd.read_csv('停用詞.txt',
names = ['w'], sep = 'aaa', encoding = 'utf-8',engine='python')
#分詞
#word_list = jieba.lcut(chapter.txt[1])
#word_list[:10]
word_list = [ w for w in jieba.cut(alltxt) if w not in list(tmpdf.w) ]
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\CHENYA~1\AppData\Local\Temp\jieba.cache
Loading model cost 0.866 seconds.
Prefix dict has been built succesfully.
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
<ipython-input-1-01dab9076eb7> in <module>
2
3 dict = '金庸小說詞庫.txt'
----> 4 jieba.load_userdict(dict) # dict爲自定義詞典的路徑
5 tmpdf = pd.read_csv('停用詞.txt',
6 names = ['w'], sep = 'aaa', encoding = 'utf-8',engine='python')
E:\Anaconda3_2019.07\lib\site-packages\jieba\__init__.py in load_userdict(self, f)
372 if isinstance(f, string_types):
373 f_name = f
--> 374 f = open(f, 'rb')
375 else:
376 f_name = resolve_filename(f)
FileNotFoundError: [Errno 2] No such file or directory: '金庸小說詞庫.txt'
構建完list之後,也可以自行編寫詞頻統計程序,框架如下:
遍歷整個list,對每個詞條:
if word in d:
d[word] += 1
else:
d[word] = 1
使用Pandas統計
df = pd.DataFrame(word_list, columns = ['word'])
result = df.groupby(['word']).size()
print(type(result))
freqlist = result.sort_values(ascending=False)
freqlist
freqlist[freqlist.index == '道']
freqlist[freqlist.index == '道'][0]
freqlist[freqlist.index == '黃蓉道']
使用NLTK統計
NLTK生成的結果爲頻數字典,在和某些程序包對接時比較有用
import nltk
fdist = nltk.FreqDist(word_list) # 生成完整的詞條頻數字典
fdist
# 帶上某個單詞, 可以看到它在整個文章中出現的次數
fdist['顏烈']
fdist.keys() # 列出詞條列表
fdist.tabulate(10)
fdist.most_common(5)
詞雲概述¶
wordcloud包的安裝
安裝
docker的kaggle下推薦安裝方法:
conda install -c conda-forge wordcloud
常規安裝方法(docker或windows下):
pip install wordcloud
警告:常規方法安裝wordcloud有可能非常順利,也有可能會出各種問題
中文字體支持
.WordCloud(font_path=‘msyh.ttf’)
需要帶路徑寫完整字體文件名
注意Win10的字體文件後綴可能不一樣
繪製詞雲
WordCloud的基本語法
class wordcloud.WordCloud(
font_path,
width,
height,
max_words,
stopwords,
min_font_size,
font_step,
relative_scaling,
prefer_horizontal,
background_color,
mode,
color_func,
mask
)
常用功能:
font_path : 在圖形中使用的字體,默認使用系統字體
width / height = 200 : 圖形的寬度/高度
max_words = 200 : 需要繪製的最多詞條數
stopwords = None : 停用詞列表,不指定時會使用系統默認停用詞列表
字體設定:
min_font_size = 4 / max_font_size = None : 字符大小範圍
font_step = 1 : 字號增加的步長
relative_scaling = .5: 詞條頻數比例和字號大小比例的換算關係,默認爲50%
prefer_horizontal = 0.90 : 圖中詞條水平顯示的比例
顏色設定:
background_color = ”black” : 圖形背景色
mode = ”RGB”: 圖形顏色編碼,如果指定爲"RGBA"且背景色爲None時,背景色爲透明
color_func = None : 生成新顏色的函數,使用matplotlib的colormap
背景掩模:
mask = None : 詞雲使用的背景圖(遮罩)
用原始文本直接分詞並繪製
cloudobj = WordCloud().generate(text)
generate實際上是generate_from_text的別名
文本需要用空格/標點符號分隔單詞,否則不能正確分詞
import wordcloud
myfont = 'msyh.ttf'
text = 'this is shanghai, 郭靖, 和, 哀牢山 三十六劍'
cloudobj = wordcloud.WordCloud(font_path = myfont).generate(text)
print(cloudobj)
顯示詞雲
import matplotlib.pyplot as plt
plt.imshow(cloudobj)
plt.axis(“off”)
plt.show()
import matplotlib.pyplot as plt
plt.imshow(cloudobj)
plt.axis("off")
plt.show()
# 更改詞雲參數設定
cloudobj = wordcloud.WordCloud(font_path = myfont,
width = 360, height = 180,
mode = "RGBA", background_color = None).generate(text)
plt.imshow(cloudobj)
plt.axis("off")
plt.show()
保存詞雲
wordcloud.to_file(保存文件的路徑與名稱) 該命令保存的是高精度圖形
cloudobj.to_file("詞雲.png")
生成射鵰第一章的詞雲
cloudobj = wordcloud.WordCloud(font_path = myfont,
width = 1200, height = 800,
mode = "RGBA", background_color = None).generate(' '.join(word_list))
plt.imshow(cloudobj)
plt.axis("off")
plt.show()
cloudobj.to_file("詞雲2.png")
基於分詞頻數繪製
generate()的實際操作
調用分詞函數process_text()
調用基於頻數的繪製函數fit_words()
fit_words(dict)
實際上是generate_from_frequencies的別名
Dict: 由詞條和頻數構成的字典
#基於分詞頻數繪製詞雲
txt_freq = {'張三':100,'李四':90,'王二麻子':50}
cloudobj = wordcloud.WordCloud(font_path = myfont).fit_words(txt_freq)
plt.imshow(cloudobj)
plt.axis("off")
plt.show()
用頻數生成射鵰第一章的詞雲
cloudobj = wordcloud.WordCloud(font_path = myfont).fit_words(fdist)
plt.imshow(cloudobj)
plt.axis("off")
plt.show()
詞雲的美化
各種詳細的操作和設定可以參考官網的案例:
https://amueller.github.io/word_cloud/
設置背景圖片
Mask / 掩模 / 遮罩
用於控制詞雲的整體形狀
指定mask後,設置的寬高值將被忽略,遮罩形狀被指定圖形的形狀取代。除全白的部分仍然保留外,其餘部分會用於繪製詞雲。因此背景圖片的畫布一定要設置爲白色(#FFFFFF)
字的大小,佈局和顏色也會基於Mask生成
必要時需要調整顏色以增強可視效果
基本調用方式
from scipy.misc import imread
mask = imread(背景圖片名稱)
from scipy.misc import imread
cloudobj = wordcloud.WordCloud(font_path = myfont,
mask = imread("射鵰背景1.png"),
mode = "RGBA", background_color = None
).generate(' '.join(word_list))
plt.imshow(cloudobj)
plt.axis("off")
plt.show()
指定圖片色系
讀取指定圖片的色系設定
imgarray = np.array(imread(imgfilepath))
獲取圖片顏色
bimgColors = wordcloud.ImageColorGenerator(imgarray)
重置詞雲顏色
cloudobj.recolor(color_func=bimgColors)
# 利用已有詞雲對象直接重繪顏色,輸出速度要比全部重繪快的多
import numpy as np
imgobj = imread("射鵰背景2.png")
image_colors = wordcloud.ImageColorGenerator(np.array(imgobj))
cloudobj.recolor(color_func=image_colors)
plt.imshow(cloudobj)
plt.axis("off")
plt.show()
指定單詞組顏色
理想的狀況應該是分組比較詞頻,在兩組中都高頻的詞條在圖形中相互抵消。
Python目前只能實現詞條分組上色。
color_to_words = {
‘#00ff00’: [‘顏烈’, ‘武官’, ‘金兵’, ‘小人’],
‘red’: [‘包惜弱’, ‘郭嘯天’, ‘楊鐵心’, ‘丘處機’]
} '#00ff00’爲綠色的代碼
default_color = ‘grey’ # 其餘單詞的默認顏色
cloudobj.recolor()
# 官網提供的顏色分組類代碼,略有修改
from wordcloud import get_single_color_func
class GroupedColorFunc(object):
def __init__(self, color_to_words, default_color):
self.color_func_to_words = [
(get_single_color_func(color), set(words))
for (color, words) in color_to_words.items()]
self.default_color_func = get_single_color_func(default_color)
def get_color_func(self, word):
"""Returns a single_color_func associated with the word"""
try:
color_func = next(
color_func for (color_func, words) in self.color_func_to_words
if word in words)
except StopIteration:
color_func = self.default_color_func
return color_func
def __call__(self, word, **kwargs):
return self.get_color_func(word)(word, **kwargs)
######
# 指定分組色系
color_to_words = {
'#00ff00': ['顏烈', '武官', '金兵', '官兵'],
'red': ['包惜弱', '郭嘯天', '楊鐵心', '丘處機']
}
default_color = 'grey' # 指定其他詞條的顏色
grouped_color_func = GroupedColorFunc(color_to_words, default_color)
cloudobj.recolor(color_func=grouped_color_func)
plt.imshow(cloudobj)
plt.axis("off")
plt.show()
實戰3:優化射鵰詞雲
嘗試進一步清理分詞結果,並且只保留所有的名稱(人名、地名)。
提示:可以使用詞性標註功能,只保留名詞和未知詞性的詞。
可以考慮對自定義詞典做優化,通過強行調整權重等方法改善分詞效果。
將所有的人名按照藍色系,地名按照紅色系進行詞雲繪製。
自行製作兩個純色圖片,分別爲綠色和藍色,然後將其分別指定爲繪圖所用的色系,觀察圖形效果。
嘗試使用不同的背景圖片作爲掩模,思考怎樣的圖片才能使得繪圖效果最佳。
文檔信息的向量化
所謂文檔信息的向量化,就是將文檔信息***數值化*** ,從而便於進行建模分析。
詞袋模型(One-hot表示方式)
- 幾乎是最早的用於提取文本特徵的方法
- 將文本直接簡化爲一系列詞的集合
- 不考慮其語法和詞序關係,每個詞都是獨立的
- 舉例:
- 對語料進行清理,並完成分詞
- 大魚/吃/小魚/也/吃/蝦米,小魚吃蝦米。
- 對每個詞進行編號,形成字典(順序無關的流水號即可)
- {“大魚”:1,“吃”:2,“小魚”:3,“也”:4,“蝦米”:5}
- 用0/1代表該詞是否在文本中出現,從而將文本記錄爲一個特徵向量
- 大魚吃小魚也吃蝦米 ->[大魚,吃,小魚,也,蝦米]->[1,2,1,1,1]
- 小魚吃蝦米 ->[小魚,吃,蝦米]->[0,1,1,0,1]
- 該方式也被稱爲詞袋模型,Bag of Words,BOW
- 詞和文本的關聯就相當於文本是一個袋子,詞只是直接裝在袋子裏
- 顯然,詞袋模型是比較簡單的模型,對文本中的信息有較多丟失,但已經可以解決很多實際問題
- 詞袋模型的提出最初是爲了解決文檔分類問題,目前主要應用在NLP(Natural Language Process),IR(Information Retrival),CV(Computer Vision)等領域
- 也可以不考慮詞頻,減少模型複雜度
- 詞集模型:Set Of Words,單詞構成的集合,常見於短文本分析
- 大魚吃小魚也吃蝦米 ->[大魚,吃,小魚,也,蝦米]->[1,1,1,1,1]
- 優點:
- 解決了分類器不好處理離散數據的問題
- 在一定程度上也起到了擴充特徵的作用
- 缺點:
- 不考慮詞與詞之間的順序
- 它假設詞與詞之間相互獨立(在大多數情況下,詞與詞是相互有關聯的)
- 老公 vs 老婆,老婆 vs 孩子他媽
- 它得到的特徵是離散稀疏的(維度災難)
- 每個詞都是茫茫"0"海中的一個1:[0 0 0 0 0 1 0 0 0 0 0 0 …]
詞袋模型的gensim實現
gensim的安裝
pip install gensim
或者
conda install gensim
安裝完成後如果使用word2vec時報錯,建議去gensim官網下載MS windows install的exe程序進行安裝:
https://pypi.python.org/pypi/gensim
建立字典
Dictionary類用於建立word<->id映射關係,把所有單詞取一個set(),並對set中每個單詞分配一個Id號的map
class gensim.corpora.dictionary.Dictionary(
documents=None : 若干個被拆成單詞集合的文檔的集合,一般以list in list形式出現
prune_at=2000000 : 字典中的最大詞條容量
)
from gensim.corpora import Dictionary
texts = [['human', 'interface', 'computer']]
dct = Dictionary(texts) # fit dictionary
dct.num_nnz
Dictionary類的屬性
token2id
dict of (str, int) – token -> tokenId.
id2token
dict of (int, str) – Reverse mapping for token2id, initialized in lazy manner to > save memory.
dfs
dict of (int, int) – Document frequencies: token_id -> in how many documents > > > contain this token.
num_docs
int – Number of documents processed.
num_pos
int – Total number of corpus positions (number of processed words).
num_nnz
int – Total number of non-zeroes in the BOW matrix.
# 向字典增加詞條
dct.add_documents([["cat", "say", "meow"], ["dog"]])
dct.token2id
轉換爲BOW稀疏向量
dct.doc2bow( # 轉換爲BOW格式:list of (token_id, token_count)
document : 用於轉換的詞條list
allow_update = False : 是否直接更新所用字典
return_missing = False : 是否返回新出現的(不在字典中的)詞
)
輸出結果
[(0, 2), (1, 2)],表明在文檔中id爲0,1的詞彙各出現了2次,至於其他詞彙則沒有出現
return_missing = True時,輸出list of (int, int), dict of (str, int)
dct.doc2bow(["this", "is", "cat", "not", "a", "dog"])
dct.doc2bow(["this", "is", "cat", "not", "a", "dog"], return_missing = True)
轉換爲BOW長向量
可考慮的思路:
從稀疏格式自行轉換。
直接生成文檔-詞條矩陣。
doc2idx( # 轉換爲list of token_id
document : 用於轉換的詞條list
unknown_word_index = -1 : 爲不在字典中的詞條準備的代碼
輸出結果
按照輸入list的順序列出所出現的各詞條ID
dct.doc2idx(["this", "is", "a", "dog", "not", "cat"])
生成文檔-詞條矩陣
用Pandas庫實現
基本程序框架:
原始文檔分詞並清理
拼接爲同一個dataframe
彙總並轉換爲文檔-詞條矩陣格式
去除低頻詞
import pandas as pd
# 載入語料
raw = pd.read_csv("金庸-射鵰英雄傳txt精校版.txt",
names = ['txt'], sep ='aaa', encoding ="utf-8" ,engine='python')
# 章節判斷用變量預處理
def m_head(tmpstr):
return tmpstr[:1]
def m_mid(tmpstr):
return tmpstr.find("回 ")
raw['head'] = raw.txt.apply(m_head)
raw['mid'] = raw.txt.apply(m_mid)
raw['len'] = raw.txt.apply(len)
# 章節判斷
chapnum = 0
for i in range(len(raw)):
if raw['head'][i] == "第" and raw['mid'][i] > 0 and raw['len'][i] < 30 :
chapnum += 1
if chapnum >= 40 and raw['txt'][i] == "附錄一:成吉思汗家族" :
chapnum = 0
raw.loc[i, 'chap'] = chapnum
# 刪除臨時變量
del raw['head']
del raw['mid']
del raw['len']
rawgrp = raw.groupby('chap')
chapter = rawgrp.agg(sum) # 只有字符串列的情況下,sum函數自動轉爲合併字符串
chapter = chapter[chapter.index != 0]
chapter.head()
# 設定分詞及清理停用詞函數
# 熟悉Python的可以使用 open('stopWord.txt').readlines() 獲取停用詞list,效率更高
stoplist = list(pd.read_csv('停用詞.txt', names = ['w'], sep = 'aaa',
encoding = 'utf-8', engine='python').w)
import jieba
def m_cut(intxt):
return [ w for w in jieba.cut(intxt)
if w not in stoplist and len(w) > 1 ]
# 設定數據框轉換函數
def m_appdf(chapnum):
tmpdf = pd.DataFrame(m_cut(chapter.txt[chapnum + 1]), columns = ['word'])
tmpdf['chap'] = chapter.index[chapnum] # 也可以直接 = chapnum + 1
return tmpdf
# 全部讀入並轉換爲數據框
df0 = pd.DataFrame(columns = ['word', 'chap']) # 初始化結果數據框
for chapidx in range(len(chapter)):
df0 = df0.append(m_appdf(chapidx))
df0.tail(50)
# 輸出爲序列格式
df0.groupby(['word', 'chap']).agg('size').head()
# 直接輸出爲數據框
t2d = pd.crosstab(df0.word, df0.chap)
len(t2d)
t2d.head(100)
# 計算各詞條的總出現頻次,準備進行低頻詞刪減,axis=1表示按行統計
totnum = t2d.agg(func = 'sum', axis=1)
totnum
#按行選取
t2dclean = t2d.iloc[list(totnum >= 10),:]
#求轉置
t2dclean.T
用sklearn庫實現
CountVectorizer類的基本用法
文本信息在向量化之前很難直接納入建模分析,考慮到這一問題,專門用於數據挖掘的sklearn庫提供了一個從文本信息到數據挖掘模型之間的橋樑,即CountVectorizer類,通過這一類中的功能,可以很容易地實現文檔信息的向量化。
class sklearn.feature_extraction.text.CountVectorizer(
input = ‘content’ : {‘filename’, ‘file’, ‘content’}
#filename爲所需讀入的文件列表, file則爲具體的文件名稱。
encoding=‘utf-8’ #文檔編碼
stop_words = None #停用詞列表,當analyzer == 'word’時才生效
min_df / max_df : float in range [0.0, 1.0] or int, default = 1 / 1.0
#詞頻絕對值/比例的閾值,在此範圍之外的將被剔除
#小數格式說明提供的是百分比,如0.05指的就是5%的閾值)
CountVectorizer.build_analyzer()
#返回文本預處理和分詞的可調用函數
from sklearn.feature_extraction.text import CountVectorizer
countvec = CountVectorizer(min_df = 2) # 在兩個以上文檔中出現的才保留
analyze = countvec.build_analyzer()
analyze('郭靖 和 哀牢山 三十六 劍 。')
countvec.fit(['郭靖 和 黃蓉 哀牢山 三十六 劍 。', '黃蓉 和 郭靖 郭靖'])
countvec.get_feature_names() # 詞彙列表,實際上就是獲取每個列對應的詞條
countvec.vocabulary_ # 詞條字典
x = countvec.transform(['郭靖 和 黃蓉 哀牢山 三十六 劍 。', '黃蓉 和 郭靖 郭靖'])
x
x.todense() # 將稀疏矩陣直接轉換爲標準格式矩陣
countvec.fit_transform(['郭靖 和 哀牢山 三十六 劍 。', '黃蓉 和 郭靖 郭靖']) # 一次搞定
使用sklearn生成射鵰的章節d2m矩陣
將章節文檔數據框處理爲空格分隔詞條的文本格式
使用fit_transform函數生成bow稀疏矩陣
轉換爲標準格式的d2m矩陣
rawchap = [ " ".join(m_cut(w)) for w in chapter.txt.iloc[:5]]
rawchap[0]
from sklearn.feature_extraction.text import CountVectorizer
countvec = CountVectorizer(min_df = 5) # 在5個以上章節中出現的才保留
res = countvec.fit_transform(rawchap)
res
res.todense()
countvec.get_feature_names()
從詞袋模型到Bi-gram
-
詞袋模型完全無法利用語序信息
-
我幫你 vs 你幫我
-
P(我幫你) = P(我)*P(幫)*P(你)
-
-
Bi-gram:進一步保留順序信息,兩個詞一起看
-
P(我幫你) = P(我)*P(幫|我)*P(你|幫)
-
{“我幫”:1, “幫你”:2,“你幫”:3,“幫我”:4}
-
我幫你 -> [1,1,0,0]
-
你幫我 -> [0,0,1,1]
-
-
顯然,Bi-gram可以保留更多的文本有效信息。
從Bi-gram到N-gram
-
考慮更多的前後詞
- 可以直接擴展至tri-gram,4-gram直至N-gram
-
優點:考慮了詞的順序,信息量更充分
- 長度達到5之後,效果有明顯提升
-
缺點:
-
詞表迅速膨脹,數據出現大量的稀疏化問題
-
沒增加一個詞,模型參數增加40萬倍
-
離散表示方式所面臨的問題總結
-
無法衡量詞向量之間的關係
-
老公 [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,1,0,0]
-
挨千刀的 [0,0,0,0,0,0,0,0,1,0]
-
各種度量(與或費、距離)都不合適,只能靠字典進行補充
-
-
詞表維度隨着語料庫增長膨脹
-
N-gram詞序列隨語料庫膨脹更快
-
數據稀疏問題(導致分析性能成爲嚴重瓶頸)
文檔信息的分佈式表示
分佈式表示(Distributed representation)
-
如何定位圍棋棋盤上的落子位置?
-
方法一:每個點單獨記憶,共361個記憶單元
-
方法二:行座標+列座標,共19+19=38個記憶單元
-
-
將分佈式表示用於NLP
-
不直接考慮詞與詞在原文中的相對位置、距離、語法結構等,先把每個詞看作一個單獨向量
-
根據一個詞在上下文中的臨近詞的含義,應當可以歸納出詞本身的含義
-
單個詞的詞向量不足以表示整個文本,能表示的僅僅只是這個詞本身
-
事先決定用多少維度的向量來表示這個詞條
-
維度以50維和100維比較常見
-
向量中每個維度的取值由模型訓練決定,且不再是唯一的
- [0.762, 0.107, 0.307, -0.199, 0.521,…]
-
-
所有的詞都在同一個高維空間中構成不同的向量
- 從而詞與詞之間的關係就可以用空間中的距離來加以表述
-
所有訓練方法都是在訓練語言模型的同時,順便得到詞向量的
- 語言模型其實就是看一句話是不是正常人說出來的,具體表現爲詞條先後出現的順序和距離所對應的概率是否最大化
-
共現矩陣 (Cocurrence matrix)
-
例如:語料庫如下:
-
I like deep learning.
-
I like NLP.
-
I enjoy flying.
-
-
確定取詞長度:
- 取詞長度爲1的結果
-
窗口長度越長,則信息量越豐富,但數據量也越大
- 一般設爲5–10
-
共現矩陣的行/列數值自然就表示出各個詞彙的相似度
- 從而可以用作分析向量
則共現矩陣表示如下:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-9xGo4dMr-1574612235181)(./concurrence_matrix.jpg)]
例如:“I like”出現在第1,2句話中,一共出現2次,所以=2。
對稱的窗口指的是,“like I”也是2次
將共現矩陣行(列)作爲詞向量表示後,可以知道like,enjoy都是在I附近且統計數目大約相等,他們意思相近
仍然存在的問題
-
如果將共現矩陣(列)直接作爲詞向量
-
向量維數隨着詞典大小線性增長
-
存儲整個詞典的空間消耗非常大
-
一些模型如文本分類模型會面臨稀疏性問題
-
高度的稀疏性導致模型會欠穩定
-
實戰4:生成詞向量
嘗試編制以下程序:
以段爲單位依次讀入射鵰第一回的內容。
爲每一段分別生成bow稀疏向量。
生成稀疏向量的同時動態更新字典。
請自行編制bow稀疏向量和標準長向量互相轉換的程序。
在文檔詞條矩陣中可以看到許多類似“黃蓉道”、“黃蓉說”之類的詞條,請思考對此有哪些處理辦法。
關鍵詞提取
用途
-
用核心信息代表原始文檔
-
在文本聚類、分類、自動摘要等領域中有着重要應用
需求:針對一篇文章,在不加人工干預的情況下提取關鍵詞
當然,首先要進行分詞
關鍵詞分配: 事先給定關鍵詞庫,然後在文檔中進行關鍵詞檢索
關鍵詞提取:根據某種規則,從文檔中抽取最重要的詞作爲關鍵詞
-
有監督:抽取出候選詞並標記是否爲關鍵詞,然後訓練相應的模型
-
無監督:給詞條打分,並基於最高分值抽取
無監督方式的分析思路——基於詞頻
分析思路1:按照詞頻高低進行提取
-
大量的高頻詞並無多少意義(停用詞)
-
即使出現頻率相同,常見詞價值也明顯低於不常見詞
分析思路2:按照詞條在文檔中的重要性進行提取
- 如何確定詞條在該文檔中的重要性?
常見的方法:TF-IDF、網絡圖
TF-IDF算法
信息檢索(IR)中最常用的一種文本關鍵信息表示法
基本思想:
- 如果某個詞在一篇文檔中出現頻率較高,並且在語料庫中其他文本中出現頻率較低,甚至不出現,則認爲這個詞具有很好的類別區分能力
詞頻TF:Term Frequency,衡量一個詞在文檔中出現的頻率
- 平均而言出現越頻繁的詞,其重要性可能就越高
考慮到文章長度的差異,需要對詞頻做標準化
-
TF(w) = (w出現在文檔中的次數)/(文檔中的詞的總數)
-
TF(w) = (w出現在文檔中的次數)/(文檔中出現最多的詞的次數)
逆文檔頻率IDF:Inverse Document Frequency,用於模擬在該語料庫中,某一個詞有多重要
-
有些詞到處出現,但是明顯是沒有用的。比如各種停用詞,過渡句用詞等。
-
因此把罕見的詞的重要性(weight)調高,把常見詞的重要性調低
IDF的具體算法
- IDF(w) = log(語料庫中的文檔總數/(含有該w的文檔總數+1))
TF-IDF = TF * IDF
-
TF-IDF與一個詞在文檔中的出現次數成正比
-
與該詞在整個語料中的出現次數成反比
優點
-
簡單快速
-
結果也比較符合實際情況
缺點
-
單純以“詞頻”橫量一個詞的重要性,不夠全面,有時重要的詞可能出現的次數並不多
-
無法考慮詞與詞之間的相互關係
-
這種算法無法體現詞的位置信息,出現位置靠前的詞與出現位置靠後的詞,都被視爲重要性相同,這是不正確的
- 一種解決方式是,對全文的第一段和每一段的第一句話,給予較大的權重
TF-IDF的具體實現
jieba, NLTK, sklearn, gensim等程序包都可以實現TF-IDF的計算。除算法細節上會有差異外,更多的是數據輸入/輸出格式上的不同。
jieba
輸出結果會自動按照TF-IDF值降序排列,並且直接給出的是詞條而不是字典ID,便於閱讀使用。
可在計算TF-IDF時直接完成分詞,並使用停用詞表和自定義詞庫,非常方便。
有默認的IDF語料庫,可以不訓練模型,直接進行計算。
以單個文本爲單位進行分析。
jieba.analyse.extract_tags(
sentence 爲待提取的文本
topK = 20 : 返回幾個 TF/IDF 權重最大的關鍵詞
withWeight = False : 是否一併返回關鍵詞權重值
allowPOS = () : 僅包括指定詞性的詞,默認值爲空,即不篩選
)
jieba.analyse.set_idf_path(file_name)
關鍵詞提取時使用自定義逆向文件頻率(IDF)語料庫
勞動防護 13.900677652
生化學 13.900677652
奧薩貝爾 13.900677652
奧薩貝爾 13.900677652
考察隊員 13.900677652
jieba.analyse.set_stop_words(file_name)
關鍵詞提取時使用自定義停止詞(Stop Words)語料庫
jieba.analyse.TFIDF(idf_path = None)
新建 TFIDF模型實例
idf_path : 讀取已有的TFIDF頻率文件(即已有模型)
使用該實例提取關鍵詞:TFIDF實例.extract_tags()
import pandas as pd
# 載入語料
raw = pd.read_csv("金庸-射鵰英雄傳txt精校版.txt",
names = ['txt'], sep ='aaa', encoding ="utf-8" ,engine='python')
# 章節判斷用變量預處理
def m_head(tmpstr):
return tmpstr[:1]
def m_mid(tmpstr):
return tmpstr.find("回 ")
raw['head'] = raw.txt.apply(m_head)
raw['mid'] = raw.txt.apply(m_mid)
raw['len'] = raw.txt.apply(len)
# 章節判斷
chapnum = 0
for i in range(len(raw)):
if raw['head'][i] == "第" and raw['mid'][i] > 0 and raw['len'][i] < 30 :
chapnum += 1
if chapnum >= 40 and raw['txt'][i] == "附錄一:成吉思汗家族" :
chapnum = 0
raw.loc[i, 'chap'] = chapnum
# 刪除臨時變量
del raw['head']
del raw['mid']
del raw['len']
rawgrp = raw.groupby('chap')
chapter = rawgrp.agg(sum) # 只有字符串列的情況下,sum函數自動轉爲合併字符串
#chapter = chapter[chapter.index != 0]
chapter
txt | |
---|---|
chap | |
0.0 | 全本全集精校小說盡在:http://www.yimuhe.com/u/anglewing26... |
1.0 | 第一回 風雪驚變錢塘江浩浩江水,日日夜夜無窮無休的從兩浙西路臨安府牛家村邊繞過,東流入海。江... |
2.0 | 第二回 江南七怪顏烈跨出房門,過道中一箇中年士人拖着鞋皮,踢躂踢躂的直響,一路打着哈欠迎面過... |
3.0 | 第三回 黃沙莽莽寺裏僧衆見焦木圓寂,盡皆悲哭。有的便爲傷者包紮傷處,擡入客舍。忽聽得巨鐘下的... |
4.0 | 第四回 黑風雙煞完顏洪熙笑道:“好,再打他個痛快。”蒙古兵前哨報來:“王罕親自前來迎接大金國... |
5.0 | 第五回 彎弓射鵰一行人下得山來,走不多時,忽聽前面猛獸大吼聲一陣陣傳來。韓寶駒一提繮,胯下黃... |
6.0 | 第六回 崖頂疑陣午飯以後,郭靖來到師父帳中。全金髮道:“靖兒,我試試你的開山掌練得怎樣了。”... |
7.0 | 第七回 比武招親江南六怪與郭靖曉行夜宿,向東南進發,在路非止一日,過了大漠草原。這天離張家口... |
8.0 | 第八回 各顯神通王處一腳步好快,不多時便帶同郭靖到了城外,再行數裏,到了一個山峯背後。他不住... |
9.0 | 第九回 鐵槍破犁郭黃二人來到趙王府後院,越牆而進,黃蓉柔聲道:“你輕身功夫好得很啊!”郭靖伏... |
10.0 | 第十回 往事如煙完顏康陡然見到楊鐵心,驚詫之下,便即認出,大叫:“啊,是你!”提起鐵槍,“行... |
11.0 | 第十一回 長春服輸沙通天見師弟危殆,躍起急格,擋開了梅超風這一抓,兩人手腕相交,都感臂酸心驚... |
12.0 | 第十二回 亢龍有悔黃蓉正要將雞撕開,身後忽然有人說道:“撕作三份,雞屁股給我。”兩人都吃了一... |
13.0 | 第十三回 五湖廢人黃蓉回到客店安睡,自覺做了一件好事,大爲得意,一宵甜睡,次晨對郭靖說了。郭... |
14.0 | 第十四回 桃花島主五男一女,走進廳來,卻是江南六怪。他們自北南來,離故鄉日近,這天經過太湖,... |
15.0 | 第十五回 神龍擺尾陸冠英扶起完顏康,見他給點中了穴道,動彈不得,只兩顆眼珠光溜溜地轉動。陸乘... |
16.0 | 第十六回 《九陰真經》郭黃二人自程府出來,累了半夜,正想回客店安歇,忽聽馬蹄聲響,一騎馬自南... |
17.0 | 第十七回 雙手互搏周伯通道:“你道是我師哥死後顯靈?還是還魂復生?都不是,他是假死。”郭靖“... |
18.0 | 第十八回 三道試題郭靖循着蛇聲走去,走出數十步,月光下果見數千條青蛇排成長隊蜿蜒而前。十多名... |
19.0 | 第十九回 洪濤羣鯊洪七公萬想不到這場背書比賽竟會如此收場,較之郭靖將歐陽克連摔十七八個筋斗都... |
20.0 | 第二十回 九陰假經洪七公與郭靖見歐陽鋒叔侄領周伯通走入後艙,徑行到前艙換衣。四名白衣少女過來... |
21.0 | 第二十一回 千鈞巨巖歐陽鋒只感身上炙熱,腳下船板震動甚劇,知道這截船身轉眼就要沉沒,但洪七公... |
22.0 | 第二十二回 騎鯊遨遊黃蓉見歐陽鋒拖泥帶水地將侄兒抱上岸來,他向來陰鷙的臉上竟也笑逐顏開,可是... |
23.0 | 第二十三回 大鬧禁宮黃藥師滿腔悲憤,指天罵地,咒鬼斥神,痛責命運對他不公,命舟子將船駛往大陸... |
24.0 | 第二十四回 密室療傷黃蓉向外走了兩步,回過頭來,見郭靖眼光中露出懷疑神色,料想是自己臉上的殺... |
25.0 | 第二十五回 荒村野店黃藥師仰天一笑,說道:“冠英和這位姑娘留着。”陸冠英早知是祖師爺到了,但... |
26.0 | 第二十六回 新盟舊約黃藥師心想不明不白地跟全真七子大戰一場,更不明不白地結下了深仇,真是好沒... |
27.0 | 第二十七回 軒轅臺前兩人正鬧間,樓梯聲響,適才隨楊康下去的丐幫三長老又回了上來,走到郭黃二人... |
28.0 | 第二十八回 鐵掌峯頂此時魯有腳已經醒轉,四長老聚在一起商議。魯有腳道:“現下真相未明,咱們須... |
29.0 | 第二十九回 黑沼隱女郭靖在雕背連聲呼叫,召喚小紅馬在地下跟來。轉眼之間,雙鵰已飛出老遠。雌雄... |
30.0 | 第三十回 一燈大師兩人順着山路向前走去,行不多時,山路就到了盡頭,前面是條寬約尺許的石樑,橫... |
31.0 | 第三十一回 鴛鴦錦帕一燈大師低低嘆了口氣道:“其實真正的禍根,還在我自己。我乃大理國小君,雖... |
32.0 | 第三十二回 湍江險灘穆念慈右手讓黃蓉握着,望着水面的落花,說道:“我見他殺了歐陽克,只道他從... |
33.0 | 第三十三回 來日大難郭靖與黃蓉此刻心意歡暢,原不想理會閒事,但聽到“老頑童”三字,心中一凜,... |
34.0 | 第三十四回 島上鉅變郭靖低聲道:“蓉兒,你還要什麼?”黃蓉道:“我還要什麼?什麼也不要啦!”... |
35.0 | 第三十五回 鐵槍廟中船靠岸邊,走上二三十人來,彭連虎、沙通天等人均在其內。最後上岸的一高一矮... |
36.0 | 第三十六回 大軍西征黃蓉幽幽地道:“歐陽伯伯贊得我可太好了。現下郭靖中你之計,和我爹爹勢不兩... |
37.0 | 第三十七回 從天而降這一日郭靖駐軍那密河畔,晚間正在帳中研讀兵書,忽聽帳外喀的一聲輕響。帳門... |
38.0 | 第三十八回 錦囊密令郭靖陪了丘處機與他門下十八名弟子李志常、尹志平、夏志誠、於志可,張志素、... |
39.0 | 第三十九回 是非善惡郭靖縱馬急馳數日,已離險地。緩緩南歸,天時日暖,青草日長,沿途兵革之餘,... |
40.0 | 第四十回 華山論劍歐陽鋒冷冷地道:“早到早比,遲到遲比。老叫化,你今日跟我是比武決勝呢,還是... |
import jieba
import jieba.analyse
# 注意:函數是在使用默認的TFIDF模型進行分析!
jieba.analyse.extract_tags(chapter.txt[1])
Building prefix dict from the default dictionary ...
Loading model from cache /tmp/jieba.cache
Loading model cost 0.908 seconds.
Prefix dict has been built succesfully.
['楊鐵心',
'包惜弱',
'郭嘯天',
'顏烈',
'丘處機',
'武官',
'楊二人',
'官兵',
'曲三',
'金兵',
'那道人',
'道長',
'娘子',
'段天德',
'咱們',
'臨安',
'說道',
'丈夫',
'楊家槍',
'兩人']
jieba.analyse.extract_tags(chapter.txt[1], withWeight = True) # 要求返回權重值
[('楊鐵心', 0.21886511509515091),
('包惜弱', 0.1685852913570757),
('郭嘯天', 0.09908082913091291),
('顏烈', 0.05471627877378773),
('丘處機', 0.049556061537506184),
('武官', 0.04608486747703612),
('楊二人', 0.044305304110440376),
('官兵', 0.040144546232276104),
('曲三', 0.03439059290450272),
('金兵', 0.0336976598949901),
('那道人', 0.03117114380098961),
('道長', 0.02912588670625928),
('娘子', 0.026796070076125684),
('段天德', 0.025139911869037603),
('咱們', 0.023296768210644483),
('臨安', 0.022991990912831523),
('說道', 0.022350916333591046),
('丈夫', 0.02221595763081643),
('楊家槍', 0.019765724469755074),
('兩人', 0.0192267944114003)]
# 應用自定義詞典改善分詞效果
jieba.load_userdict('金庸小說詞庫.txt') # dict爲自定義詞典的路徑
# 在TFIDF計算中直接應用停用詞表
jieba.analyse.set_stop_words('停用詞.txt')
TFres = jieba.analyse.extract_tags(chapter.txt[1], withWeight = True)
TFres[:10]
[('楊鐵心', 0.24787133516800222),
('包惜弱', 0.1909279203321098),
('郭嘯天', 0.11221202335308209),
('曲三', 0.06426483083720931),
('顏烈', 0.061967833792000555),
('丘處機', 0.056123732343681704),
('武官', 0.052192500516161394),
('楊二人', 0.050177091402185486),
('官兵', 0.04546490778113197),
('金兵', 0.038163614820832165)]
# 使用自定義TF-IDF頻率文件
jieba.analyse.set_idf_path("idf.txt.big")
TFres1 = jieba.analyse.extract_tags(chapter.txt[1], withWeight = True)
TFres1[:10]
[('楊鐵心', 0.24787133516800222),
('包惜弱', 0.1909279203321098),
('郭嘯天', 0.11221202335308209),
('武官', 0.07034186538551414),
('顏烈', 0.061967833792000555),
('說道', 0.05861822115459512),
('丘處機', 0.056123732343681704),
('曲三', 0.055268608517189684),
('一個', 0.053593802198486966),
('楊二人', 0.053593802198486966)]
sklearn
輸出格式爲矩陣,直接爲後續的sklearn建模服務。
需要先使用背景語料庫進行模型訓練。
結果中給出的是字典ID而不是具體詞條,直接閱讀結果比較困難。
class sklearn.feature_extraction.text.TfidfTransformer()
發現參數基本上都不用動,所以這裏就不介紹了…
from sklearn.feature_extraction.text import TfidfTransformer
txtlist = [ " ".join(m_cut(w)) for w in chapter.txt.iloc[:5]]
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(txtlist) # 將文本中的詞語轉換爲詞頻矩陣
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(X) #基於詞頻矩陣X計算TF-IDF值
tfidf
tfidf.toarray() # 轉換爲數組
tfidf.todense() # 轉換爲矩陣
tfidf.todense().shape
print("字典長度:", len(vectorizer.vocabulary_))
vectorizer.vocabulary_
gensim
輸出格式爲list,目的也是爲後續的建模分析服務。
需要先使用背景語料庫進行模型訓練。
結果中給出的是字典ID而不是具體詞條,直接閱讀結果比較困難。
gensim也提供了sklearn的API接口:sklearn_api.tfidf,可以在sklearn中直接使用。
# 文檔分詞及預處理
chaplist = [m_cut(w) for w in chapter.txt.iloc[:5]]
chaplist
from gensim import corpora, models
# 生成文檔對應的字典和bow稀疏向量
dictionary = corpora.Dictionary(chaplist)
corpus = [dictionary.doc2bow(text) for text in chaplist] # 仍爲list in list
corpus
tfidf_model = models.TfidfModel(corpus) # 建立TF-IDF模型
corpus_tfidf = tfidf_model[corpus] # 對所需文檔計算TF-IDF結果
corpus_tfidf
corpus_tfidf[3] # 列出所需文檔的TF-IDF計算結果
dictionary.token2id # 列出字典內容
TextRank算法
TextRank算法的jieba實現
jieba.analyse.textrank(
sentence, topK=20, withWeight=False,
allowPOS=(‘ns’, ‘n’, ‘vn’, ‘v’)
) # 注意默認過濾詞性
實戰練習
請使用《射鵰》全文計算出jieba分詞的IDF語料庫,然後使用該語料庫重新對第一章計算關鍵詞。比較這樣的分析結果和以前有何不同。
請自行編制將jieba分詞的TF-IDF結果轉換爲文檔-詞條矩陣格式的程序。
請自行思考本章提供的三種TF-IDF實現方式的使用場景是什麼。
抽取文檔主題
什麼是主題模型
LDA,Latent Dirichlet Allocation
-
Q: 有這麼一篇文章,裏面提到了詹姆斯、湖人隊、季後賽,請問這篇文章最可能的主題是什麼?
- 軍事
- 體育
- 養生
- 教育
LDA由Blei於2003年提出,其基本思想是把文檔看成各種隱含主題的混合,而每個主題則表現爲跟該主題相關的詞項的概率分佈
- 該方法不需要任何關於文本的背景知識
- 隱含主題的引入使得分析者可以對“一詞多義”和“一義多詞”的語言現象進行建模,更接近人類語言交互的特徵
LDA基於詞袋模型構建,認爲文檔和單詞都是可交換的,忽略單詞在文檔中的順序和文檔在語料庫中的順序,從而將文本信息轉化爲易於建模的數字信息
- 主題就是一個桶,裏面裝了出現概率較高的單詞,這些單詞與這個主題有很強的的相關性
LDA模型包含詞項、主題和文檔三層結構
本質上,LDA簡單粗暴的認爲:文章中的每個詞都是通過“以一定概率選擇某個主題,再從該主題中以一定概率選擇某個詞”得到的
一個詞可能會關聯很多主題,因此需要計算各種情況下的概率分佈,來確定最可能出現的主題是哪種
- 體育:{姚明:0.3,籃球:0.5,拳擊:0.2,李現:0.03,王寶強:0.03,楊紫:0.04}
- 娛樂:{姚明:0.03,籃球:0.03,足球:0.04,李現:0.6,王寶強:0.7,楊紫:0.8}
一篇文章可能會涉及到幾個主題,因此也需要計算多個主題的概率
- 體育新聞:[廢話,體育,體育,體育,…,娛樂,娛樂]
- 八卦消息:[廢話,廢話,廢話,廢話,…,娛樂,娛樂]
LDA中涉及到的數學知識
多項式分佈:主題和詞彙的概率分佈服從多項式分佈
- 如果1個詞彙主題,就是大家熟悉的二項分佈
Dirichlet分佈:上述多項式分佈的參數爲隨機變量,均服從Dirichlet分佈
Gibbs抽樣:直接求LDA的精確參數分佈計算量太大,實際上不可行,因此通過Gibbs抽菸減小計算量,得到逼近的結果
- 通過現有文章(已有主題,或者需要提取主題)訓練處LDA模型
- 用模型預測新的文章所屬主題分類
主題模型對於 短文本 效果不好
主題模型的sklearn實現
在scikit-learn中,LDA主題模型的類被放置在sklearn.decomposition.LatentDirichletAllocation類中,其算法實現主要基於變分推斷EM算法,而沒有使用基於Gibbs採樣的MCMC算法實現。
注意由於LDA是基於詞頻統計的,因此理論上一般不宜用TF-IDF來做文檔特徵,但並非不能嘗試。實際分析中也確實會見到此類操作。
class sklearn.decomposition.LatentDirichletAllocation(
n_components = None : 隱含主題數K,需要設置的最重要參數。
K的設定範圍和具體的研究背景有關。
K越大,需要的文檔樣本越多。doc_topic_prior = None : 文檔主題先驗Dirichlet分佈的參數α,未設定則用1/K。
topic_word_prior = None : 主題詞先驗Dirichlet分佈的參數η,未設定則用1/K。
learning_method = ‘online’ : 即LDA的求解算法。‘batch’ | ‘online’
batch: 變分推斷EM算法,會將將訓練樣本分批用於更新主題詞分佈,新版默認算法。
樣本量不大隻是用來學習的話用batch比較好,這樣可以少很多參數要調。
需注意n_components(K), doc_topic_prior(α), topic_word_prior(η)
online: 在線變分推斷EM算法,大樣本時首選。
需進一步注意learning_decay, learning_offset,
total_samples和batch_size等參數。僅在online算法時需要設定的參數
learning_decay = 0.7 :控制"online"算法的學習率,一般不用修改。
取值最好在(0.5, 1.0],以保證"online"算法漸進的收斂。learning_offset = 10. :用來減小前面訓練樣本批次對最終模型的影響。
取值要大於1。total_samples = 1e6 : 分步訓練時每一批文檔樣本的數量。
使用partial_fit進行模型擬合時才需要此參數。batch_size = 128 : 每次EM算法迭代時使用的文檔樣本的數量。
)
將語料庫轉換爲所需矩陣
除直接使用分詞清理後文本進行轉換外,也可以先計算關鍵詞的TF-IDF值,然後使用關鍵詞矩陣進行後續分析。
# 設定分詞及清理停用詞函數
# 熟悉Python的可以使用 open('stopWord.txt').readlines() 獲取停用詞list,效率更高
stoplist = list(pd.read_csv('停用詞.txt', names = ['w'], sep = 'aaa',
encoding = 'utf-8', engine='python').w)
import jieba
def m_cut(intxt):
return [ w for w in jieba.cut(intxt)
if w not in stoplist and len(w) > 1 ]
# 生成分詞清理後章節文本
cleanchap = [ " ".join(m_cut(w)) for w in chapter.txt]
# 將文本中的詞語轉換爲詞頻矩陣
from sklearn.feature_extraction.text import CountVectorizer
countvec = CountVectorizer(min_df = 5)
wordmtx = countvec.fit_transform(cleanchap)
wordmtx
#基於詞頻矩陣X計算TF-IDF值
from sklearn.feature_extraction.text import TfidfTransformer
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(wordmtx)
tfidf
# 設定LDA模型
from sklearn.decomposition import LatentDirichletAllocation
n_topics = 10
ldamodel = LatentDirichletAllocation(n_components = n_topics)
# 擬合LDA模型
ldamodel.fit(wordmtx)
# 擬合後模型的實質
print(ldamodel.components_.shape)
ldamodel.components_[:2]
# 主題詞打印函數
def print_top_words(model, feature_names, n_top_words):
for topic_idx, topic in enumerate(model.components_):
print("Topic #%d:" % topic_idx)
print(" ".join([feature_names[i]
for i in topic.argsort()[:-n_top_words - 1:-1]]))
print()
n_top_words = 12
tf_feature_names = countvec.get_feature_names()
print_top_words(ldamodel, tf_feature_names, n_top_words)
gensim實現
class gensim.models.ldamodel.LdaModel(
corpus = None : 用於訓練模型的語料
num_topics = 100 : 準備提取的主題數量
id2word = None : 所使用的詞條字典,便於結果閱讀
passes = 1 :模型遍歷語料庫的次數,次數越多模型越精確,但是也更花時間
)
用新出現的語料更新模型
ldamodel.update(other_corpus)
gensim也提供了sklearn的API接口:sklearn_api.ldamodel,可以在sklearn中直接使用。
# 設定分詞及清理停用詞函數
# 熟悉Python的可以使用 open('stopWord.txt').readlines() 獲取停用詞list,效率更高
stoplist = list(pd.read_csv('停用詞.txt', names = ['w'], sep = 'aaa',
encoding = 'utf-8', engine='python').w)
import jieba
def m_cut(intxt):
return [ w for w in jieba.cut(intxt)
if w not in stoplist and len(w) > 1 ]
# 文檔預處理,提取主題詞
chaplist = [m_cut(w) for w in chapter.txt]
# 生成文檔對應的字典和bow稀疏向量
from gensim import corpora, models
dictionary = corpora.Dictionary(chaplist)
corpus = [dictionary.doc2bow(text) for text in chaplist] # 仍爲list in list
tfidf_model = models.TfidfModel(corpus) # 建立TF-IDF模型
corpus_tfidf = tfidf_model[corpus] # 對所需文檔計算TF-IDF結果
corpus_tfidf
from gensim.models.ldamodel import LdaModel
# 列出所消耗的時間備查
%time ldamodel = LdaModel(corpus, id2word = dictionary, \
num_topics = 10, passes = 2)
列出最重要的前若干個主題
print_topics(num_topics=20, num_words=10)
ldamodel.print_topics()
# 計算各語料的LDA模型值
corpus_lda = ldamodel[corpus_tfidf] # 此處應當使用和模型訓練時相同類型的矩陣
for doc in corpus_lda:
print(doc)
ldamodel.get_topics()
# 檢索和文本內容最接近的主題
query = chapter.txt[1] # 檢索和第1章最接近的主題
query_bow = dictionary.doc2bow(m_cut(query)) # 頻數向量
query_tfidf = tfidf_model[query_bow] # TF-IDF向量
print("轉換後:", query_tfidf[:10])
ldamodel.get_document_topics(query_bow) # 需要輸入和文檔對應的bow向量
# 檢索和文本內容最接近的主題
ldamodel[query_tfidf]
結果的圖形化呈現
pyLDAvis包引入自R,可以用交互式圖形的方式呈現主題模型的分析結果。
同時支持sklearn和gensim包。
在許多系統配置下都會出現兼容問題。
# 對sklearn的LDA結果作呈現
import pyLDAvis
import pyLDAvis.sklearn
pyLDAvis.enable_notebook()
pyLDAvis.sklearn.prepare(ldamodel, tfidf, countvec)
pyLDAvis.gensim.prepare(ldamodel, corpus, dictionary)
pyLDAvis.disable_notebook() # 關閉notebook支持後,可以看到背後所生成的數據
實戰練習
在其餘參數全部固定不變的情況下,嘗試分別用清理前矩陣、清理後原始矩陣、TF-IDF矩陣進行LDA模型擬合,比較分析結果。
在gensim擬合LDA時,分別將passes參數設置爲1、5、10、50、100等,觀察結果變化的情況,思考如何對該參數做最優設定。
請嘗試對模型進行優化,得到對本案例較好的分析結果。
提示:使用gensim進行擬合更容易一些。
文檔相似度
用途
- 搜索引擎的類似文章推薦
- 購物網站的類似商品推薦
- 點評網站/微博微信平臺上的類似內容推薦
基於詞袋模型的基本思路
- 如果兩個文檔/兩句話的用詞越相似,他們的內容就應該越相似。因此,可以從詞頻入手,計算他們的相似程度
- 文檔向量化之後,相似度的考察就可以直接轉化爲計算空間中的距離問題
- 缺陷: 不能考慮否定詞的巨大作用,不能考慮詞序的差異
在本質上,向量空間中文本相似度的計算和任何聚類方法所考慮的問題***沒有區別***
餘弦相似度
兩個向量間的夾角能夠很好的反映其相似度
- 但夾角大小使用不便,因此用夾角的餘弦值作爲相似度衡量指標
- 思考:爲什麼只考慮夾角,不考慮相對距離?
餘弦值越接近1,夾角越接近0度,兩個向量也就越相似
可以證明餘弦值的計算公式可以直接擴展到n維空間
因此在由n維向量所構成的空間中,可以利用餘弦值來計算文檔的相似度
相似度計算:基本分析思路
語料分詞、清理
- 原始語料分詞
- 語料清理
語料向量化
- 將語料轉換爲詞頻向量
- 爲了避免文章長度的差異,長度懸殊時可以考慮使用相對詞頻
計算相似度
- 計算兩個向量的餘弦相似度,值越大表示越相似
仍然存在的問題
- 高頻詞不一定具有文檔代表性,導致相似度計算結果變差
相似度計算:基本分析思路
語料分詞、清理
- 原始語料分詞
- 語料清理
語料向量化
- 將語料轉換爲基於關鍵詞的詞頻向量
- 爲了避免文章長度的差異,長度懸殊時可以考慮使用相對詞頻
使用TF-IDF算法,找出兩篇文章的關鍵詞
- 例如取前20個,或者前50個
計算相似度
- 計算兩個向量的餘弦相似度,值越大表示越相似
當向量表示概率分佈式,其他相似度測量方法比餘弦相似度更好
詞條相似度:word2vec
詞袋模型不考慮詞條之間的相關性,因此無法用於計算詞條相似度。
分佈式表達會考慮詞條的上下文關聯,因此能夠提取出詞條上下文中的相關性信息,而詞條之間的相似度就可以直接利用此類信息加以計算。
目前主要使用gensim實現相應的算法。
gensim也提供了sklearn的API接口:sklearn_api.w2vmodel,可以在sklearn中直接使用。
設置word2vec模型
class gensim.models.word2vec.Word2Vec(
sentences = None : 類似list of list的格式,對於特別大的文本,儘量考慮流式處理
size = 100 : 詞條向量的維度,數據量充足時,300/500的效果會更好
window = 5 : 上下文窗口大小
workers = 3 : 同時運行的線程數,多核系統可明顯加速計算
其餘細節參數設定:
min_count = 5 : 低頻詞過濾閾值,低於該詞頻的不納入模型
max_vocab_size = None : 每1千萬詞條需要1G內存,必要時設定該參數以節約內存
sample=0.001 : 負例採樣的比例設定
negative=5 : 一般爲5-20,設爲0時不進行負例採樣
iter = 5 : 模型在語料庫上的迭代次數,該參數將被取消
與神經網絡模型有關的參數設定:
seed=1, alpha=0.025, min_alpha=0.0001, sg=0, hs=0
)
chapter.head()
# 分詞和預處理,生成list of list格式
import jieba
chapter['cut'] = chapter.txt.apply(jieba.lcut)
chapter.head()
# 初始化word2vec模型和詞表
from gensim.models.word2vec import Word2Vec
n_dim = 300 # 指定向量維度,大樣本量時300~500較好
w2vmodel = Word2Vec(size = n_dim, min_count = 10)
w2vmodel.build_vocab(chapter.cut) # 生成詞表
w2vmodel
對word2vec模型進行訓練
word2vecmodel.train(
sentences : iterable of iterables格式,對於特別大量的文本,儘量考慮流式處理
total_examples = None : 句子總數,int,可直接使用model.corpus_count指定
total_words = None : 句中詞條總數,int,該參數和total_examples至少要指定一個
epochs = None : 模型迭代次數,需要指定
其他帶默認值的參數設定:
start_alpha=None, end_alpha=None, word_count=0, queue_factor=2,
report_delay=1.0, compute_loss=False, callbacks=()
)
# 在評論訓練集上建模(大數據集時可能會花費幾分鐘)
# 本例消耗內存較少
#time
w2vmodel.train(chapter.cut, \
total_examples = w2vmodel.corpus_count, epochs = 10)
# 訓練完畢的模型實質
print(w2vmodel.wv["郭靖"].shape)
w2vmodel.wv["郭靖"]
w2v模型的保存和複用
w2vmodel.save(存盤路徑及文件名稱)
w2vmodel.load(存盤路徑及文件名稱)
詞向量間的相似度
w2vmodel.wv.most_similar(詞條)
w2vmodel.wv.most_similar("郭靖")
w2vmodel.wv.most_similar("黃蓉", topn = 20)
w2vmodel.wv.most_similar("黃蓉道")
# 尋找對應關係
w2vmodel.wv.most_similar(['郭靖', '小紅馬'], ['黃藥師'], topn = 5)
w2vmodel.wv.most_similar(positive=['郭靖', '黃蓉'], negative=['楊康'], topn=10)
# 計算兩個詞的相似度/相關程度
print(w2vmodel.wv.similarity("郭靖", "黃蓉"))
print(w2vmodel.wv.similarity("郭靖", "楊康"))
print(w2vmodel.wv.similarity("郭靖", "楊鐵心"))
# 尋找不合羣的詞
w2vmodel.wv.doesnt_match("小紅馬 黃藥師 魯有腳".split())
w2vmodel.wv.doesnt_match("楊鐵心 黃藥師 黃蓉 洪七公".split())
文檔相似度
基於詞袋模型計算
sklearn實現
sklearn.metrics.pairwise.pairwise_distances(
X : 用於計算距離的數組
[n_samples_a, n_samples_a] if metric == ‘precomputed’
[n_samples_a, n_features] otherwise
Y = None : 用於計算距離的第二數組,當metric != 'precomputed’時可用
metric = ‘euclidean’ : 空間距離計算方式
scikit-learn原生支持 : [‘cityblock’, ‘cosine’, ‘euclidean’,
'l1', 'l2', 'manhattan'\],可直接使用稀疏矩陣格式
來自scipy.spatial.distance : [‘braycurtis’, ‘canberra’,
‘chebyshev’, ‘correlation’, ‘dice’, ‘hamming’, ‘jaccard’,
'kulsinski', 'mahalanobis', 'matching', 'minkowski', 'rogerstanimoto', 'russellrao', 'seuclidean', 'sokalmichener', 'sokalsneath', 'sqeuclidean', 'yule'\] 不支持稀疏矩陣格式
n_jobs = 1 : 用於計算的線程數,爲-1時,所有CPU內核都用於計算
)
cleanchap = [ " ".join(m_cut(w)) for w in chapter.txt.iloc[:5]]
from sklearn.feature_extraction.text import CountVectorizer
countvec = CountVectorizer()
resmtx = countvec.fit_transform(cleanchap)
resmtx
from sklearn.metrics.pairwise import pairwise_distances
pairwise_distances(resmtx, metric = 'cosine')
pairwise_distances(resmtx) # 默認值爲euclidean
# 使用TF-IDF矩陣進行相似度計算
pairwise_distances(tfidf[:5], metric = 'cosine')
gensim實現
基於LDA計算餘弦相似度
需要使用的信息:
擬合完畢的lda模型
按照擬合模型時矩陣種類轉換的需檢索文本
需檢索的文本
建模時使用的字典
from gensim import similarities
simmtx = similarities.MatrixSimilarity(corpus)
simmtx
# 檢索和第1章內容最相似(所屬主題相同)的章節
simmtx = similarities.MatrixSimilarity(corpus) # 使用的矩陣種類需要和擬合模型時相同
simmtx
simmtx.index[:2]
# 使用gensim的LDA擬合結果進行演示
query = chapter.txt[1]
query_bow = dictionary.doc2bow(m_cut(query))
lda_vec = ldamodel[query_bow] # 轉換爲lda模型下的向量
sims = simmtx[lda_vec] # 進行矩陣內向量和所提供向量的餘弦相似度查詢
sims = sorted(enumerate(sims), key=lambda item: -item[1])
sims
doc2vec
word2vec用來計算詞條相似度非常合適。
較短的文檔如果希望計算文本相似度,可以將各自內部的word2vec向量分別進行平均,用平均後的向量作爲文本向量,從而用於計算相似度。
但是對於長文檔,這種平均的方式顯然過於粗糙。
doc2vec是word2vec的拓展,它可以直接獲得sentences/paragraphs/documents的向量表達,從而可以進一步通過計算距離來得到sentences/paragraphs/documents之間的相似性。
模型概況
分析目的:獲得文檔的一個固定長度的向量表達。
數據:多個文檔,以及它們的標籤,一般可以用標題作爲標籤。
影響模型準確率的因素:語料的大小,文檔的數量,越多越高;文檔的相似性,越相似越好。
import jieba
import gensim
from gensim.models import doc2vec
def m_doc(doclist):
reslist = []
for i, doc in enumerate(doclist):
reslist.append(doc2vec.TaggedDocument(jieba.lcut(doc), [i]))
return reslist
corp = m_doc(chapter.txt)
corp[:2]
d2vmodel = gensim.models.Doc2Vec(vector_size = 300,
window = 20, min_count = 5)
d2vmodel.build_vocab(corp)
d2vmodel.wv.vocab
# 將新文本轉換爲相應維度空間下的向量
newvec = d2vmodel.infer_vector(jieba.lcut(chapter.txt[1]))
d2vmodel.docvecs.most_similar([newvec], topn = 10)
文檔聚類
在得到文檔相似度的計算結果後,文檔聚類問題在本質上已經和普通的聚類分析沒有區別。
注意:最常用的Kmeans使用的是平方歐氏距離,這在文本聚類中很可能無法得到最佳結果。
算法的速度和效果同樣重要。
# 爲章節增加名稱標籤
chapter.index = [raw.txt[raw.chap == i].iloc[0] for i in chapter.index]
chapter.head()
import jieba
cuttxt = lambda x: " ".join(m_cut(x))
cleanchap = chapter.txt.apply(cuttxt)
cleanchap[:2]
# 計算TF-IDF矩陣
from sklearn.feature_extraction.text import TfidfTransformer
vectorizer = CountVectorizer()
wordmtx = vectorizer.fit_transform(cleanchap) # 將文本中的詞語轉換爲詞頻矩陣
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(wordmtx) #基於詞頻矩陣計算TF-IDF值
tfidf
# 進行聚類分析
from sklearn.cluster import KMeans
clf = KMeans(n_clusters = 5)
s = clf.fit(tfidf)
print(s)
clf.cluster_centers_
clf.cluster_centers_.shape
clf.labels_
chapter['clsres'] = clf.labels_
chapter.head()
chapter.sort_values('clsres').clsres
chapgrp = chapter.groupby('clsres')
chapcls = chapgrp.agg(sum) # 只有字符串列的情況下,sum函數自動轉爲合併字符串
cuttxt = lambda x: " ".join(m_cut(x))
chapclsres = chapcls.txt.apply(cuttxt)
chapclsres
# 列出關鍵詞以刻畫類別特徵
import jieba.analyse as ana
ana.set_stop_words('停用詞.txt')
for item in chapclsres:
print(ana.extract_tags(item, topK = 10))
文檔分類
什麼是文本分類
-
通過程序對文本按照一定的分類體系或標準自動分類標記
-
應用場景
- 對抓取到的新聞進行自動歸類
- 郵件服務器對收到的郵件進行垃圾郵件甄別
- 監測系統對採集到的文本信息進行優先級評估,將高優先級的信息優先發送至人工處理流程
文本分類的基本步驟
-
文本有效信息的提取
- 文本預處理:分詞、清理等工作
- 特徵抽取:從文檔中抽取出反映文檔主題的特徵
-
分類器的選擇與訓練
-
分類結果的評價與反饋
- 該步驟與普通的模型完全相同
-
基於詞袋模型時可考慮的特徵抽取方法:
- 詞頻(基於詞袋模型的文檔-詞條矩陣)
- 關鍵詞(TF-IDF)
- 文檔主題(LDA模型)
基於詞袋模型的文本分類算法選擇
-
邏輯上所有用於分類因變量預測的模型都可以用於文本分類
- 判別分析、Logistics迴歸、樹模型、神經網絡、SVM、KNN、樸素貝葉斯、遺傳算法、Bagging、Boosting
-
文本分類的數據特徵
- 自變量(詞條)數量極多
- 各自變量(詞條)之間不可能完全獨立
- 大部分自變量(詞條)只是混雜項而已,對分類無貢獻
-
選擇算法的幾個考慮方向
- 速度 變量篩選能力 容錯性 共線性容忍度
-
算法簡單的貝葉斯公式(樸素貝葉斯)往往會成爲優先考慮的算法
-
模型中,改進後的隨機森林,以及SVM相對應用較多
-
有的算法會在特定數據集下表現較好,不能一概而論
-
語料的事先清理至關重要,甚至可以考慮只使用關鍵詞來分類
Python下的文本分類工具包選擇
-
sklearn:
- 基於D2M矩陣結構,將文本分類問題看做標準的樣本分類預測問題來處理
- 非常完善的模型擬合、診斷功能
- 同時也提供了樸素貝葉斯算法
-
NLTK:
- 主要基於樸素貝葉斯算法進行文本分類
- 可以直接使用稀疏向量格式進行分析,使用上很方便
-
gensim:
- 基於LDA等更先進的模型,提供文本分類功能
樸素貝葉斯的算法原理
-
乘法公式:
- :聯合概率
- 或:先驗概率
- 或:後驗概率
-
貝葉斯公式:
- 擁有某特徵的案例,屬於某類的概率 = 該類出現的概率 * 該類有某特徵的概率 / 該特徵出現的概率
-
該公式可以很容易的擴展至多條件的情況
- 具體公式大家可以自己嘗試寫出來
-
應用案例
- 判斷一下郵件正文中含有句子:“我司可辦理正規發票/增值稅發票,點數優惠!”的是垃圾郵件的概率有多大?
-
句子的變化形式很多,但是核心信息就包含在“辦理”、“發票”、“優惠”等幾個詞條的組合中,因此考慮分詞後建模
- 發垃圾郵件的人也很敬業的…
-
樸素貝葉斯(Naive Bayes)=貝葉斯公式+條件獨立假設
- 拋棄詞條建的關聯,假設各個詞條完全獨立,完全基於詞袋模型進行計算
- 如此***蠢萌***的樸素貝葉斯方法,實踐證明至少在垃圾郵件的識別中的應用效果是非常好的
sklearn實現
sklearn是標準的數據挖掘建模工具包,在語料轉換爲d2m矩陣結構之後,就可以使用所有標準的DM建模手段在sklearn中進行分析。
在sklearn中也實現了樸素貝葉斯算法,使用方式上也和其他模型非常相似。
生成D2M矩陣
# 從原始語料df中提取出所需的前兩章段落
raw12 = raw[raw.chap.isin([1,2])]
raw12ana = raw12.iloc[list(raw12.txt.apply(len) > 50), :] # 只使用超過50字的段落
raw12ana.reset_index(drop = True, inplace = True)
print(len(raw12ana))
raw12ana.head()
# 分詞和預處理
import jieba
cuttxt = lambda x: " ".join(jieba.lcut(x)) # 這裏不做任何清理工作,以保留情感詞
raw12ana["cleantxt"] = raw12ana.txt.apply(cuttxt)
raw12ana.head()
from sklearn.feature_extraction.text import CountVectorizer
countvec = CountVectorizer()
wordmtx = countvec.fit_transform(raw12ana.cleantxt)
wordmtx
劃分訓練集和測試集
# 作用:將數據集劃分爲 訓練集和測試集
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(wordmtx, raw12ana.chap,
test_size = 0.3, random_state = 111)
擬合樸素貝葉斯模型
from sklearn import naive_bayes
NBmodel = naive_bayes.MultinomialNB()
# 擬合模型
NBmodel.fit(x_train, y_train)
# 進行驗證集預測
x_test
NBmodel.predict(x_test)
模型評估
# 預測準確率(給模型打分)
print('訓練集:', NBmodel.score(x_train, y_train),
',驗證集:', NBmodel.score(x_test, y_test))
from sklearn.metrics import classification_report
print(classification_report(y_test, NBmodel.predict(x_test)))
使用Logistic迴歸模型進行分類
from sklearn.linear_model import LogisticRegression
logitmodel = LogisticRegression() # 定義Logistic迴歸模型
# 擬合模型
logitmodel.fit(x_train, y_train)
print(classification_report(y_test, logitmodel.predict(x_test)))
模型預測
將需要預測的文本轉換爲和建模時格式完全對應的d2m矩陣格式,隨後即可進行預測。
countvec.vocabulary_
string = "楊鐵心和包惜弱收養穆念慈"
words = " ".join(jieba.lcut(string))
words_vecs = countvec.transform([words]) # 數據需要轉換爲可迭代的list格式
words_vecs
NBmodel.predict(words_vecs)
NLTK實現
NLTK中內置了樸素貝葉斯算法,可直接實現文檔分類。
數據集中語料的格式
用於訓練的語料必須是分詞完畢的字典形式,詞條爲鍵名,鍵值則可以是數值、字符、或者T/F
{‘張三’ : True, ‘李四’ : True, ‘王五’ : False}
{‘張三’ : 1, ‘李四’ : 1, ‘王五’ : 0}
{‘張三’ : ‘有’, ‘李四’ : ‘有’, ‘王五’ : ‘無’}
# 使用Pandas的命令進行轉換
freqlist.to_dict()
df0.groupby(['word']).agg('size').tail(10).to_dict()
訓練用數據集的格式
訓練用數據集爲list of list格式,每個成員爲list[語料字典, 結果變量]
[
[{‘張三’ : 1, ‘李四’ : 1, ‘王五’ : 0}, ‘合格’],
[{‘張三’ : 0, ‘李四’ : 1, ‘王五’ : 0}, ‘不合格’]
]
構建模型
考慮到過擬合問題,此處需要先拆分好訓練集和測試集
model = NaiveBayesClassifier.train(training_data)
# 這裏直接以章節爲一個單元進行分析,以簡化程序結構
import nltk
from nltk import FreqDist
# 生成完整的詞條頻數字典,這部分也可以用遍歷方式實現
fdist1 = FreqDist(m_cut(chapter.txt[1]))
fdist2 = FreqDist(m_cut(chapter.txt[2]))
fdist3 = FreqDist(m_cut(chapter.txt[3]))
fdist1
from nltk.classify import NaiveBayesClassifier
training_data = [ [fdist1, 'chap1'], [fdist2, 'chap2'], [fdist3, 'chap3'] ]
# 訓練分類模型
NLTKmodel = NaiveBayesClassifier.train(training_data)
print(NLTKmodel.classify(FreqDist(m_cut("楊鐵心收養穆念慈"))))
print(NLTKmodel.classify(FreqDist(m_cut("錢塘江 日日夜夜 包惜弱 顏烈 使出楊家槍"))))
模型擬合效果的考察
nltk.classify.accuracy(NLTKmodel, training_data) # 準確度評價
NLTKmodel.show_most_informative_features(5)#得到似然比,檢測對於哪些特徵有用
分類結果評估
精確率和召回率
精確率和召回率主要用於二分類問題(從其公式推導也可看出),結合混淆矩陣有:
分類1 | 分類2 | |
---|---|---|
預測分類1 | TP | FP |
預測分類2 | FN | TN |
精確率
召回率
理想情況下,精確率和召回率兩者都越高越好。然而事實上這兩者在某些情況下是矛盾的,精確率高時,召回率低;精確率低時,召回率高;關於這個性質通過觀察PR曲線不難觀察出來。比如在搜索網頁時,如果只返回最相關的一個網頁,那精確率就是100%,而召回率就很低;如果返回全部網頁,那召回率爲100%,精確率就很低。因此在不同場合需要根據實際需求判斷哪個指標跟重要。
# 分類報告:precision/recall/fi-score/均值/分類個數
from sklearn.metrics import classification_report
class_true = [1, 2, 3, 3, 1] #正確的分類結果
class_pred = [1, 1, 3, 3, 1] #實際的分類結果
target_names = ['class 1', 'class 2', 'class 3']
print(classification_report(class_true, class_pred, target_names=target_names))
precision recall f1-score support
class 1 0.67 1.00 0.80 2
class 2 0.00 0.00 0.00 1
class 3 1.00 1.00 1.00 2
avg / total 0.67 0.80 0.72 5