2019119_文本文件處理方式

這是在接單過程中得到的一個好東西,感覺這個包含了所有文本處理的問題和方式,主要通過分析文本進行轉換,學到了好多東西,我覺得現在接單不算是只爲了掙錢而是多練手,多掌握數據分析過程以及多任務處理,我需要的是平臺而不是工作。

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

pandas官網

pandas中文

正則表達式

python正則表達

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(條件隨機場)
    • 這類分詞算法能很好的處理歧義和未登錄詞問題,效果比前一類要好,但是需要大量的人工標註數據,分詞速度也較慢

注意:分詞算法本身在中文文本挖掘裏就是一個“巨坑”

基於字符串匹配的分詞算法原理

  • 以現有的詞典爲基礎進行

  • 最大匹配法:以設定的最大詞長度爲框架,取出其中最長的匹配詞

    • “中華人民共和國”會被完整取出,而不會進一步被分詞
    • 最佳匹配法:按照詞典中的頻率高低,優先取高頻詞
  • 最大概率法:對句子整體進行分詞,找到最佳的詞彙排列組合規律

    • 例:早上好->早上/好
  • 最短路徑分詞:尋找單詞數最少的分詞方式

分詞的難點

  • 分詞歧義
    • 我個人沒有意見
    • 三個人沒有意見
  • 未登錄詞識別:蔡國慶
    • 數字
    • 實體名稱/專業術語
    • 成語
    • 虛詞、語氣詞

常見的分詞工具

結巴分詞的基本用法

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. 對語料進行清理,並完成分詞
    • 大魚/吃/小魚/也/吃/蝦米,小魚吃蝦米。
    1. 對每個詞進行編號,形成字典(順序無關的流水號即可)
    • {“大魚”:1,“吃”:2,“小魚”:3,“也”:4,“蝦米”:5}
    1. 用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等更先進的模型,提供文本分類功能

樸素貝葉斯的算法原理

  • 乘法公式:P(AB)=P(BA)P(A)=P(AB)P(B)P(AB) = P(B|A) * P(A) = P(A|B) * P(B)

    • P(AB)P(AB):聯合概率
    • P(A)P(A)P(B)P(B):先驗概率
    • P(BA)P(B|A)P(AB)P(A|B):後驗概率
  • 貝葉斯公式:P(BA)=P(AB)P(B)P(A)P(B|A) = \frac{P(A|B) * P(B)}{P(A)}

    • 擁有某特徵的案例,屬於某類的概率 = 該類出現的概率 * 該類有某特徵的概率 / 該特徵出現的概率
  • 該公式可以很容易的擴展至多條件的情況

    • 具體公式大家可以自己嘗試寫出來
  • 應用案例

    • 判斷一下郵件正文中含有句子:“我司可辦理正規發票/增值稅發票,點數優惠!”的是垃圾郵件的概率有多大?
  • 句子的變化形式很多,但是核心信息就包含在“辦理”、“發票”、“優惠”等幾個詞條的組合中,因此考慮分詞後建模

    • 發垃圾郵件的人也很敬業的…
  • 樸素貝葉斯(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

精確率Precision=TPTP+FPPrecision = \frac{TP}{TP+FP}

召回率Recall=TPTP+FNRecall = \frac{TP}{TP+FN}

理想情況下,精確率和召回率兩者都越高越好。然而事實上這兩者在某些情況下是矛盾的,精確率高時,召回率低;精確率低時,召回率高;關於這個性質通過觀察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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章