# Python大數據分析(二)
###### Aaron Ho
###### tags: `python` `bigdata` `wordcloud`
## 第二堂課:大數據分析實務-資料分析
### 前言
自然語言處理(Netural Language Processing簡稱NLP)是資料分析的一種,其中有一個重要環節就是中文斷詞的處理,不管是要分析文章熱門程度、使用者喜好的商品,都會需要去處理中文的斷詞,中文相較於英文,先天上就比較難處理斷詞,例如:「今天天氣很好」,電腦怎麼會知道要斷成「今天/天氣/很好」而不是「今/天天/氣很/好」呢? 如果是英文「Today is a good weather」,直接用空白來斷成「Today / is / a / good / weather」就可以了,由此可見,中文斷詞真的是非常困難的一件事。
中文的斷詞問題已經行之有年,也有發展出一些解決方案,例如中研院的中文斷詞系統,但其實使用起來並不方便,需要透過API來呼叫,而且有使用次數限制(收費就可以提高限制),加上串接複雜,伺服器也常常掛掉,所以用的人應該不多。
一般來說,很多機構和公司都會開發和維護自己的分詞系統,一個優秀的分詞系統取決於足夠的詞庫和完善的模型。
> **補充**
>
> 中研院斷詞系統:http://ckipsvr.iis.sinica.edu.tw/
### 中文分詞的原理
中文分詞的實現主要分成兩大類:「基於規則」和「基於統計」。
#### 基於規則
基於規則是指根據一個已經整理好的詞庫,來進行字詞前向的最大匹配、後向的最大匹配以及雙向最大匹配等等人工設定的規則來進行分詞。
例如:「今天天氣很好」這句話,使用前向最大匹配的方式來分詞(也就是從最前面往後掃描,將分出來的詞存在於詞典中且儘可能使用最長的詞),可以得到「今天 / 天氣 / 很好」這幾個分詞;用這種方法來設計分詞,方法簡單而且容易實做,對於資料量的要求也不會太高。
分詞所使用的規則可以設計得更復雜,讓分詞效果更加完美。只是中文的語法真的是太千變萬化,目前還很難設計出一套可以全面通用的規則,還能根據上下文的意境、每個詞之間的搭配來斷出完美的分詞,因為整個句子的意境有可能會影響分詞的結果。
#### 基於統計
基於統計會從詞庫中人工輸入的分詞來總結詞的概率分佈以及詞之間的常用搭配,使用機器學習的監督學習訓練分詞模型。
例如:「今天天氣很好」在統計裡,會嘗試使用所有可能的分詞方案,因為兩個字之間,不是要斷,不然就是不斷,像「今 / 天 / 今天 / 天天 / 今天天 / 天 / 氣 / 天氣 / 氣很 / 很 / 好 / 很好 / 氣很好」都是有可能的分詞。
這些全部可能的分詞方案,根據詞庫會來統計每個詞出現的概率,然後保留概率最大的詞,最終「今天 / 天氣 / 很好」出現概率會比「今 / 天 / 天天 / 今天天 / 天 / 氣 / 氣很 / 很 / 好 / 氣很好」更高。
### jieba
##### 介紹
在Python的世界,有一套非常好用、免費且完全開放原始碼的中文分詞套件叫jieba,也有人叫成「結巴」,使用簡單,且目前仍在積極維護中。
但因為是中國大陸大開發的套件,所以內建的詞庫對簡體中文分詞會比較準確,但已經有繁體中文的詞庫,jieba有提供詞庫切換的功能,因此可以解決繁體中文分詞較不準確的問題。
> **補充**
>
> 官網:https://github.com/fxsjy/jieba
##### 演算法
jieba分詞結合了基於規則和基於統計兩類方法。
1. 使用前綴詞典實現高效率的詞庫的掃描,以 Trie Tree 結構來產生成句子中所有中文字所有可能成詞情況所構成的有向無環圖 (DAG)。
**例如:**
<img src="https://i.imgur.com/Nb46m3u.png" style="zoom:15%;" />
2. 使用動態規劃(Dynamic programming)算法來找出最大機率的路徑,這個路徑就是基於詞頻的最大斷詞結果。
3. 對於詞庫中不存在的新詞,則是使用了 HMM 模型(Hidden Markov Model)及 Viterbi 算法來做處理,雖然詞庫完全沒有這個詞,但是也可以進行大略的分詞,雖然準確率會降低,但至少可以行大致的分詞。
##### 支援四種分詞模式:
- 精確模式:試圖將句子最精確的切開,適合文字內容分析;
- 全模式:把句子中所有的可以成詞的詞語都掃描出來,速度非常快,但是不能解決歧義。
- 搜索引擎模式:在精確模式的基礎上,對長詞再次切分,提高召回率,適合用於搜索引擎分詞。
- paddle模式:利用PaddlePaddle深度學習框架,訓練序列標註(雙向GRU)網絡模型實現分詞並支援詞性標註。
##### 詞性和專名類別標簽集合
如下表,其中詞性標簽 24 個(小寫字母),專名類別標簽 4 個(大寫字母)。
| 標簽 | 含義 | 標簽 | 含義 | 標簽 | 含義 | 標簽 | 含義 |
| ---- | -------- | ---- | -------- | ---- | -------- | ---- | -------- |
| n | 普通名詞 | f | 方位名詞 | s | 處所名詞 | t | 時間 |
| nr | 人名 | ns | 地名 | nt | 機構名 | nw | 作品名 |
| nz | 其他專名 | v | 普通動詞 | vd | 動副詞 | vn | 名動詞 |
| a | 形容詞 | ad | 副形詞 | an | 名形詞 | d | 副詞 |
| m | 數量詞 | q | 量詞 | r | 代詞 | p | 介詞 |
| c | 連詞 | u | 助詞 | xc | 其他虛詞 | w | 標點符號 |
| PER | 人名 | LOC | 地名 | ORG | 機構名 | TIME | 時間 |
##### 安裝
使用pip安裝 jieba 套件:
```
pip install jieba
```
> **補充**
>
> Google Colab已內建,不需安裝。
### 文字雲
顧名思義,就是一個由文字所構成,整體形狀很像雲朵的圖形,這樣的圖形應該很常出現在你的周遭,尤其是在各社交或新聞網站上。
它目的在於能讓閱讀者在不閱讀所有文章的前提下,快速聚焦在大批文章中的主要內容,得知文章熱門的詞時哪些以及其熱門程度。
其呈現方式是以「字」或「詞」出現的頻率來做視覺化並以不同的顏色以及字型大小來呈現。
例如下圖:
![](https://i.imgur.com/DvJTVnS.jpg)
> **補充**
>
> Python文字雲套件:https://pypi.org/project/wordcloud/
>
> 安裝文字雲模組:`pip install wordcloud`
>
> Google Colab以內建,不需安裝。
### 事前準備
#### 下載繁體詞庫
用來操作繁體中文斷詞的詞庫
位址:https://raw.githubusercontent.com/fxsjy/jieba/master/extra_dict/dict.txt.big
詞庫內每個分詞分為三個欄位,其內容依序為:分詞、詞頻、詞性
- 分詞:及要進行切斷的詞。
- 詞頻:該分詞被使用的頻率,高詞頻的分詞表示被使用率較高。
- 詞性:名詞、動詞、副詞等等。
#### 下載中文字型
讓文字雲輸出的結果可以正常的顯示中文字。
**免費繁體中文字型下載**
1. Google Noto Sans:https://fonts.google.com/?subset=chinese-traditional
2. 沙奈字型:http://sana.s12.xrea.com/2_sanafonmugi.html
> 僅需下載其中一種字型。
### 程式碼說明
##### 所有使用到的模組
```
import os.path # 系統功能模組
import numpy # 分析模組
import requests # 網路模組
from collections import Counter # 次數統計模組
from google.colab import files # colab檔案處理模組
from bs4 import BeautifulSoup # 網頁解析模組
from PIL import Image # 圖片處理模組
import jieba # 分詞模組
import matplotlib.pyplot as plt # 視覺化模組
import wordcloud # 文字雲模組
```
##### 要抓取的Yahoo新聞網址
在Yahoo網站隨便挑選一篇新聞,然後把網址存到變數中。
```
URL = 'https://tw.news.yahoo.com/%E6%98%8E%E5%9B%9E%E6%9A%96%E4%B8%8A%E7%9C%8B28%E5%BA%A6-%E9%80%99%E5%A4%A9-%E6%9D%B1%E5%8C%97%E9%A2%A8%E5%9B%9E%E6%AD%B8%E5%86%8D%E9%99%8D%E6%BA%AB-%E5%8C%97%E9%83%A8%E9%80%A3%E7%BA%8C%E7%82%B8%E9%9B%A84%E5%A4%A9-104228478.html'
```
##### 定義檔名
因為會使用到外部檔案,所以先使用變數來定義檔名。
```
WORDS_PATH = 'dict.txt.big.txt' # 繁體中文詞庫檔名
TC_FONT_PATH = 'NotoSansTC-Regular.otf' # 繁體中文字型檔名
```
##### 上傳詞庫檔
Python的os模組內的path類別內isfile()方法可以偵測檔案是否存在,如果不存在再透過colab的files物件來上傳詞庫檔案。
```
if not os.path.isfile(WORDS_PATH):
print(f'找不到詞庫檔{WORDS_PATH},請上傳詞庫檔:')
uploaded = files.upload()
```
##### 上傳中文字型檔
Python的os模組內的path類別內isfile()方法可以偵測檔案是否存在,如果不存在再透過colab的files物件來上傳中文字型檔案,否則文字雲會無法正確顯示中文字。
```
if not os.path.isfile(TC_FONT_PATH):
print(f'找不到中文字型檔{TC_FONT_PATH},請上傳詞庫檔:')
uploaded = files.upload()
```
##### 下載網頁
`requests`是Python提供的類別,呼叫`get()`方法,傳入網址,就會到該網址下載整個網頁內容。
```
re = requests.get(URL)
```
##### 爬取網頁
網頁下載後,需要使用分析模組來解析網頁內容,並抽取我們需要的資訊,Python用來解析網頁的模組為BeautifulSoup模組。
```
soup = BeautifulSoup(re.text, 'html.parser')
```
##### 抽取新聞內容
找出整個網頁內的`p`標籤,而且該標籤沒有`class`屬性。
```
texts = soup.find_all('p', class_='')
```
##### 過濾一些不是新聞內容的標籤
找出來的`p`標籤,有些裡面還包含了其他標籤並且並不是新聞內容,所以把它們都過濾掉;`findChildren()`方法可以找出該標籤內所有的子標籤,`len()`方法會傳回有多少個子標籤,如果為0,表示沒有子標籤。
```
all_text = ''
for t in texts:
if len(t.findChildren()) != 0: # 有子標籤的內容去掉不要
continue
all_text += t.text
print(all_text)
```
##### 切換繁體中文詞庫
使用jieba模組的`set_dictionary()`來切換詞庫,詞庫是程式一開始就上傳的詞庫檔。
```
jieba.set_dictionary(WORDS_PATH)
```
##### 進行斷詞
呼叫jieba模組的`lcut()`方法來進行斷詞,斷完的詞會放在一個list結構內。
```
seg_list = jieba.lcut(all_text)
print(seg_list) # 把斷完的詞顯示出來看看
```
> **補充**
>
> 1. `jieba.cut()`回傳的是一個產生器(generator),也就是可以使用for-in迴圈來取得每一個詞。
> 2. `jieba.lcut()`則直接回傳一個list。
> 3. `cut_all`參數為True的話為全模式,預設為 False,也就是精確模式。
##### 統計分詞出現次數
使用Python的collections模組內的Counter類別來計算list內每個元素出現的次數,也就是分詞出現的頻率。
```
dictionary = Counter(seg_list)
```
##### 移除停用詞
有些像標點符號並不需要,所以手動將他們從統計後的dictionary結構內移除。
```
STOP_WORDS = [' ', ',', '(', ')', '...', '。', '「', '」', '[', ']']
[dictionary.pop(x, None) for x in STOP_WORDS] # 從字典裡刪除停用詞
print(dictionary) # 把計算完的每個分詞出現次數顯示出來看看
```
##### 文字雲格式設定
準備好了分詞出現的頻率,接下來進行wordcloud文字雲模組的設定:
- background_color:設定文字雲的背景顏色。
- margin:設定文字雲內文字之間的間距。
- font_path:要使用的字型檔檔名及路徑。
- max_words:最多要使用的文字。
- width, height:輸出的圖形解析度。
設定完後會產生一個WordCloud物件,透過指派運算子存在wc變數中。
```
wc = wordcloud.WordCloud(background_color='white',
margin=2, # 文字間距
font_path=TC_FONT_PATH, # 設定字體
max_words=200, # 取多少文字在裡面
width=1280, height=720) # 解析度
```
##### 生成文字雲
使用剛剛算出來的分詞頻率dictionary資料,透過呼叫WordCloud物件的`generate_from_frequencies()`方法來產生文字雲。
```
wc.generate_from_frequencies(dictionary) # 吃入次數字典資料
```
##### 產生圖檔
將產生的文字雲輸出成圖片檔案並存放在硬碟裡。
```
wc.to_file('WordCloud.png')
```
##### 顯示文字雲圖片
使用matplotlib模組來顯示剛剛產生的文字雲。
```
plt.imshow(wc)
```
> **補充**
>
> 在Google Colab上顯示圖片,不管解析度多大,看起來都會一樣大,直接下載產生的圖檔後點開,就會明顯發現解析度大小不同,產生的圖檔大小也會不一樣。
### 完整程式碼
```
import os.path # 系統功能模組
import numpy # 分析模組
import requests # 網路模組
from collections import Counter # 次數統計模組
from google.colab import files # colab檔案處理模組
from bs4 import BeautifulSoup # 網頁解析模組
from PIL import Image # 圖片處理模組
import jieba # 分詞模組
import matplotlib.pyplot as plt # 視覺化模組
import wordcloud # 文字雲模組
# 要抓取的Yahoo新聞網址
URL = 'https://tw.news.yahoo.com/%E6%98%8E%E5%9B%9E%E6%9A%96%E4%B8%8A%E7%9C%8B28%E5%BA%A6-%E9%80%99%E5%A4%A9-%E6%9D%B1%E5%8C%97%E9%A2%A8%E5%9B%9E%E6%AD%B8%E5%86%8D%E9%99%8D%E6%BA%AB-%E5%8C%97%E9%83%A8%E9%80%A3%E7%BA%8C%E7%82%B8%E9%9B%A84%E5%A4%A9-104228478.html'
# 定義檔名
WORDS_PATH = 'dict.txt.big.txt' # 繁體中文詞庫檔名
TC_FONT_PATH = 'NotoSansTC-Regular.otf' # 繁體中文字型檔名
# 上傳詞庫檔
if not os.path.isfile(WORDS_PATH):
print(f'找不到詞庫檔{WORDS_PATH},請上傳詞庫檔:')
uploaded = files.upload()
# 上傳中文字型檔
if not os.path.isfile(TC_FONT_PATH):
print(f'找不到中文字型檔{TC_FONT_PATH},請上傳詞庫檔:')
uploaded = files.upload()
# 下載網頁
re = requests.get(URL)
# 網頁解析
soup = BeautifulSoup(re.text, 'html.parser')
texts = soup.find_all('p', class_='')
all_text = ''
for t in texts:
if len(t.findChildren()) != 0: # 有子標籤的內容去掉不要
continue
all_text += t.text
print(all_text)
# 切換繁體中文詞庫
jieba.set_dictionary(WORDS_PATH)
# 進行斷詞
seg_list = jieba.lcut(all_text)
print(seg_list) # 把斷完的詞顯示出來看看
# 統計分詞出現次數
dictionary = Counter(seg_list)
# 移除停用詞
STOP_WORDS = [' ', ',', '(', ')', '...', '。', '「', '」', '[', ']']
[dictionary.pop(x, None) for x in STOP_WORDS] # 從字典裡刪除停用詞
print(dictionary) # 把計算完的每個分詞出現次數顯示出來看看
# 文字雲格式設定
wc = wordcloud.WordCloud(background_color='white',
margin=2, # 文字間距
font_path=TC_FONT_PATH, # 設定字體
max_words=200, # 取多少文字在裡面
width=1280, height=720) # 解析度
# 生成文字雲
wc.generate_from_frequencies(dictionary) # 吃入次數字典資料
# 產生圖檔
wc.to_file('WordCloud.png')
# 顯示文字雲圖片
plt.imshow(wc)
```
輸出結果:
![](https://i.imgur.com/boQFsCz.jpg)
### 其它
#### 使用自訂的分詞檔案
```
# 設定自訂分詞檔
# jieba.load_userdict('my_dict.txt')
```
#### 程式內新增、刪除分詞
如果臨時想增加分詞或刪除分詞,可以透過jieba的add_word()方法和del_word()方法來做到。
```
# 新增分詞
jieba.add_word('高屏')
# 刪除分詞
jieba.del_word('到')
```
#### 加上遮罩
##### 遮罩圖檔
<img src="https://i.imgur.com/pgHakej.png" alt="heart" style="zoom:20%;" />
使用遮罩可以讓輸出的文字雲變成特定形狀,例如圓形、心形及不規則圖形等等等等,遮罩圖片白色(RGB: 255, 255, 255)的地方將不會有文字雲的輸出,例如:
```
# 建立遮罩
mask = numpy.array(Image.open('mask.png')) # 遮罩
```
並且在建立WordCloud物件時傳入mask參數:
```
# 文字雲格式設定
wc = wordcloud.WordCloud(background_color='white',
margin=2, # 文字間距
mask=mask,
font_path=TC_FONT_PATH, # 設定字體
max_words=200, # 取多少文字在裡面
width=6000, height=2000, # 解析度
relative_scaling=0.5) # 詞頻與詞大小關聯性
```
> **補充**
>
> 圖片上只要有顏色的地方(也就是RGB不為255, 255, 255),就會出現文字雲。
##### 輸出的文字雲
<img src="https://i.imgur.com/LCF8wcM.png" alt="WordCloud_with_mask" style="zoom:20%;" />