Skip to content

[Python] wordcloud 設定文字雲中文字顏色

Last Updated on 2021-06-23 by Clay

在 Python 中的 wordcloud 是一個著名於產生文字雲的模組。今天我想要紀錄的是該如何控制文字雲中的顏色。

套用圖片遮罩的筆記,可以參考我之前所寫的:[Python] wordcloud 模組使用 mask 生成特定形狀、顏色。不過,就算不套用遮罩,當然也是可以直接調整文字雲顏色的。

那麼,我們就開始吧!


HSL

HSLHSV都是一種將RGB色彩模型中的點在圓柱坐標系中的表示法。這兩種表示法試圖做到比基於笛卡爾坐標系的幾何結構RGB更加直觀。

HSL即色相飽和度亮度(英語:Hue, Saturation, Lightness)。HSV即色相飽和度明度(英語:Hue, Saturation, Value),又稱HSB,其中B即英語:Brightness。

色相(H)是色彩的基本屬性,就是平常所說的顏色名稱,如紅色黃色等。

飽和度(S)是指色彩的純度,越高色彩越純,低則逐漸變灰,取0-100%的數值。

明度(V),亮度(L),取0-100%。

—— From WIKI

然後若是要調整顏色,然後又對 HSL 不熟的,我滿推薦以下這個網站的:https://www.ginifab.com.tw/tools/colors/rgb_to_hsv_hsl.html

這個網站可以讓我們透過使用常見的 RGB 來調整 HSL 的顏色,相當適合我這種對色彩沒什麼概念的傢伙 XDD

為什麼要突然提到 HSL 呢?這是因為 wordcloud 裡面需要使用 HSL 調整顏色。


程式碼

程式碼有點長,相當抱歉。這是因為我將應該斷詞的完整文本 (我的某三篇 Blog 文章) 都放在程式碼中了。之所以這樣做的目的在於,希望想要嘗試這個工具的人可以直接複製來試試看。

# coding: utf-8
"""
Rewritten from Github
"""
import jieba
import matplotlib.pyplot as plt
import numpy as np
import random
from PIL import Image
from wordcloud import WordCloud
from scipy.ndimage import gaussian_gradient_magnitude


# Load
text = """
在我的筆電鍵盤上,F11 和 F12 一直都是調整螢幕亮度的快捷鍵,就像下面這張圖片一樣:
不過這個圖示好看歸好看,但是在我的筆電上從來都沒辦法用他們來調整螢幕亮度 (正常的 F11、F12 功能還是可以用)。為了保險起見,我還使用 xev 測試看看有沒有抓到 Keycode,不過自然是抓得到的。
今天想要紀錄的就是,我如何將這兩個按鍵設置為調整螢幕亮度的快捷鍵,搞不好會有一樣問題的人可以參考看看。
在我的系統上,我的亮度設定是位於 “/sys/class/backlight/intel_backlight/” 這個路徑底下。你的路徑可能與我不同,不過大致上都會在 “/sys/class/backlight/” 底下。
其中,max_brightness 是我系統上可以設定的亮度最大值,為 120000;brightness 則是我系統當前設定的值,只要更動這個檔案內的數值,我電腦螢幕的亮度也會隨之改變。
所以要做的第一件事情,就是將 brightness 這份檔案設定權限,讓我可以在一般模式時就能寫入它。
若是我要減少亮度,我可以使用以下指令:
若是我要提昇亮度,則就將 “-” 換成 “+” 即可。
確認這兩個指令沒有問題,就可以將其設定為快捷鍵了。
其實設定快捷鍵也是相當簡單,在此我簡單做個示範、不過大部份桌面環境設定快捷鍵的方式都大同小異。
基本上,搜尋 “Shortcut”,應該就能直接找到設定快捷鍵的地方。
設定快捷鍵有三個參數要填入: Name、Command、Shortcut。
Name: 無所謂,填入喜歡的名稱即可
Command: 填入上方調整螢幕亮度的指令
Shortcut: 按下你想要的快捷鍵,以我這裡為例便是 F11、F12
完成設置後,我就能成功假裝我的按鍵是正常可以用的了 XDD
在 Python 中,我們若是想要將一段文本依照『特定字元』來切割,讓文本變成一段段的 List 資料型態儲存著,我們可以簡單地使用 split() 這個函式來完成。
而 splitlines() 這個函式與 split() 非常相像,不過 splitlines() 是專門處理換行符號的,並且在處理時還能選擇是否保留著換行符號,非常好用。
假設我們有著以下這樣的文本:
假設我們想要以空格符號 ” ” 為切割點,我們得到的結果應如下:
for word in text.split():
    print(word)
Output:
除了預設的以『空格符號』切割外,我們也可以自己選擇換行符號 “\n” 來進行切割。
Today is a nice day.
This is a good day.
Hello! How are you?
不過在以換行符號 “\n” 切割的時候,split() 是沒有比 splitlines() 方便的,以下我們就來看看 splitlines() 的範例。
splitlines() 比 split() 優秀的地方在於,我們的文本中很有可能並不僅僅只存在著 “\n” 這樣單純的換行, “\r”、”\r\n”、”\n” 等等也都會被視作換行。
根據我的經驗,同時考慮 “\r”、”\r\n”、”\n” 三種情況的切割點效果比較好,畢竟不會出現應該換行卻沒有被切割的句子。
同時,splitlines() 還可以透過 keepends 參數決定是否在輸出結果中保留『換行符號』,在某些情況是非常有用的。
'\n'
'Today is a nice day.\n'
'This is a good day.\n'
'Hello! How are you?\n'
這裡我使用了 pprint 來顯示換行符號。值得一提的是 keepends 若為 True 表示保留換行符號;反之,則不保留。
使用 FastText 訓練詞向量
FastText 是由 Facebook AI Research Lab (FAIR) 所開發的『詞嵌入』以及『文本分類』,支援 294 種語言,並且使用類神經網路訓練詞嵌入模型。
基本上 FastText 的演算法是基於以下這兩篇論文:
Enriching Word Vectors with Subword Information
Bag of Tricks for Efficient Text Classification
不過介紹了這麼多,今天我主要的目的是要介紹如何在 Python 中透過 Gensim 套件快速地調用 FastText 訓練一個 Word Embedding 的模型。基本上流程都與 Gensim 中本來訓練 Word2Vec 極度相像。
以下就直接來看程式碼。值得一提的是,由於我之前寫過使用 Gensim 訓練 Word2Vec 模型,所以有部份相同的流程我是直接從那邊抄來的 XDD
安裝 Gensim
首先,如果你的環境裡沒有 Gensim,使用以下指令安裝:
pip3 install gensim
Python is the most popular programming language!
前言
FastText 是由 Facebook AI Research Lab (FAIR) 所開發的『詞嵌入』以及『文本分類』,支援 294 種語言,並且使用類神經網路訓練詞嵌入模型。
基本上 FastText 的演算法是基於以下這兩篇論文:
Enriching Word Vectors with Subword Information
Bag of Tricks for Efficient Text Classification
不過介紹了這麼多,今天我主要的目的是要介紹如何在 Python 中透過 Gensim 套件快速地調用 FastText 訓練一個 Word Embedding 的模型。基本上流程都與 Gensim 中本來訓練 Word2Vec 極度相像。
以下就直接來看程式碼。值得一提的是,由於我之前寫過使用 Gensim 訓練 Word2Vec 模型,所以有部份相同的流程我是直接從那邊抄來的 XDD
安裝 Gensim
首先,如果你的環境裡沒有 Gensim,使用以下指令安裝:
pip3 install gensim
下載 WIKI 語料
這裡以 Wiki 上的中文資料為訓練語料,Wiki 上的資料可是相當優秀的。不僅量多、而且詞彙涵蓋範圍廣,可以應用在許多不同的任務上。
可以從這個網址找到下載處:https://zh.wikipedia.org/wiki/Wikipedia:%E6%95%B0%E6%8D%AE%E5%BA%93%E4%B8%8B%E8%BD%BD
進入之後可以直接選擇一個日期,那是不同時間點的資料備份。
比如說我選擇了 20200101 這個時間點的資料,那麼,我要下載的資料則為:zhwiki-20200101-pages-articles-multistream.xml.bz2
要注意的是,你的日期可能與我不同,不過同樣都是這份檔案。
下載之後不用解壓縮,可以使用 Gensim 裡的工具直接提取文章。
WikiCorpus
WikiCorpus 是 Gensim 裡面的一個模組,專門用於清理 Wiki 資料庫中語料不必要的標籤,使其恢復成乾淨的文本。
# coding: utf-8
Extracted the content in the wiki_database
from gensim.corpora import WikiCorpus
# Load data
wiki_corpus = WikiCorpus('../data/zhwiki-latest-pages-articles-multistream.xml.bz2', dictionary={})
# Save data
with open('wiki_text.txt', 'w', encoding='utf-8') as f:
    print('Start to preprocess.')
    for times, text in enumerate(wiki_corpus.get_texts()):
        f.write(' '.join(text)+'\n')
        if (times+1) % 10000 == 0:
            print(times+1)
順帶一提,WikiCorpus() 函式當中的路徑為我下載的語料資料,你的名稱可能與我不同、也需要對應到你下載的路徑。
儲存之後的效果應如下:
斷詞
『斷詞』這項工作指的是文本分成以『詞』為單位的句子。斷詞的工具有很多種,基本上可以參考我之前寫過的:
NLP 繁體中文斷詞的霸主 —— CKIP
NLP 中文斷詞最方便的開源工具之一 —— Jieba
中文自然語言分析的工具包 —— THULAC
多功能的自然語言處理工具 —— HanLP
如果想要對繁體中文斷詞效果最好,首推 CKIP;如果是要斷詞速度的話,那非 Jieba 莫屬。在這裡以 Jieba 示範。
(註:Wiki 的資料當中許多地方是簡繁混雜的,可以考慮使用 OpenCC 這項工具來轉換。詳細的作法可以參閱我之前撰寫過的《中文繁簡轉換的便利工具 —— OpenCC》)
# coding: utf-8
Tokenize
import jieba
from opencc import OpenCC
# Initial
cc = OpenCC('s2t')
# Tokenize
with open('wiki_text_seg.txt', 'w', encoding='utf-8') as new_f:
    with open('wiki_text.txt', 'r', encoding='utf-8') as f:
        for data in f:
            data = cc.convert(data)
            data = jieba.cut(data)
            data = [word for word in data if word != ' ']
            data = ' '.join(data)

            new_f.write(data)
斷詞結果展示如下:
歐幾


西元前
三世
紀的
古希臘
數學家
現在

認為

幾何
之父
此畫
為拉斐爾

作品
雅典
學院
數學

利用

最後,終於來到呼叫 FastText 來訓練模型了。
使用 FastText 訓練模型
# coding: utf-8
from gensim.models import word2vec, fasttext
# Settings
seed = 666
sg = 0
window_size = 10
vector_size = 100
min_count = 1
workers = 8
epochs = 5
batch_words = 10000
# Train
train_data = word2vec.LineSentence('wiki_text_seg.txt')
model = fasttext.FastText(
    train_data,
    min_count=min_count,
    size=vector_size,
    workers=workers,
    iter=epochs,
    window=window_size,
    sg=sg,
    seed=seed,
    batch_words=batch_words,
)
model.save('fasttext.model')
這裡也解釋一下模型中各個參數的意義:
seed: 亂數種子
sg: Word2Vec 有兩種算法,CBOW 以及 Skip-gram,這裡選擇了訓練比較快的 CBOW
window_size: 周圍詞彙要看多少範圍
size: 轉成向量的維度
min_count: 詞頻少於 min_count 之詞彙不會參與訓練
workers: 訓練的並行數量
iter: 訓練的迭代次數
batch_words:每次給予多少詞彙量訓練
訓練結束以後,我將模型儲存為 “fasttext.model”。
測試模型效果
# coding: utf-8
Test the w2v model
from gensim.models import word2vec
# Load the model
model = word2vec.Word2Vec.load('models/fasttext.model')
# Test
print(model['生物'].shape)
for item in model.most_similar('生物'):
print(item)
model.save('fasttext.model')
Output:
(100,)
('若好', 0.9359757900238037)
('身半', 0.9298930764198303)
('過金', 0.9281899929046631)
('低等生物', 0.9252755641937256)
('腐生物', 0.9232699275016785)
('過金魅', 0.9228056073188782)
('生物能', 0.9225305914878845)
('捉酸蟲', 0.9221773743629456)
('非生物', 0.9185526371002197)
('過金貝', 0.9147799015045166)
不知道是不是我的錯覺,我覺得 Gensim 訓練 Word2Vec 模型感覺效果比較好。
"""


# Tokenize
text = ' '.join(jieba.cut(text))


# Mask image
mask_color = np.array(Image.open('pic/parrot-by-jose-mari-gimenez2.jpg'))
mask_color = mask_color[::3, ::3]
mask_image = mask_color.copy()
mask_image[mask_image.sum(axis=2) == 0] = 255


# Edge detection
edges = np.mean([gaussian_gradient_magnitude(mask_color[:, :, i]/255., 2) for i in range(3)], axis=0)
mask_image[edges > .08] = 255


# WordCloud
wc = WordCloud(max_words=2000,
               mask=mask_image,
               font_path='fonts/DFXingKaiStd-W5.otf',
               max_font_size=40,
               random_state=42,
               relative_scaling=0)

wc.generate(text)


# Create coloring from image
def color_func(word, font_size, position, orientation, random_state=None, **kwargs):
    return 'hsl(51, 100%%, %d%%)' % random.randint(40, 60)


wc.recolor(color_func=color_func)


# Plot
plt.figure()
plt.axis('off')
plt.imshow(wc)
plt.show()


# Save
plt.figure()
plt.axis('off')
fig = plt.imshow(wc, interpolation='nearest')
fig.axes.get_xaxis().set_visible(False)
fig.axes.get_yaxis().set_visible(False)
plt.savefig('test.png',
            bbox_inches='tight',
            pad_inches=0,
            format='png',
            dpi=300)



Output:

其中最大的重點在於這幾行:

# Create coloring from image
def color_func(word, font_size, position, orientation, random_state=None, **kwargs):
    return 'hsl(51, 100%%, %d%%)' % random.randint(40, 60)


wc.recolor(color_func=color_func)



這裡我們定義了一個函式,函式中會返回 HSL 的值來顯示顏色。然後我們要將定義好的 wc 變數使用 recolor() 來賦予新的顏色函式。

熟練了如何設定 HSL 之後,就可以設定各式各樣的顏色啦。


References


Read More

Leave a Reply