資料分類的過程極其耗時費力又無意義,但是似乎又難以將這樣重複單調的任務標準化、程式化,於是只好先硬著頭皮以人工進行資料分類,觀察以人工分類時可以歸納出怎樣的分類邏輯。這些資料為中文的文章,包含年分、標題、關鍵詞、摘要等眾多欄位,起初分類時,會依自己的經驗,閱讀所有欄位後判斷資料性質再進行分類,但是這樣的做法耗時費力,分類準則變得更加主觀,又容易隨著自己「心中的那把尺」改變而影響分類結果,因此在分類的過程中自己也掙扎了許久。
其實若要做到非主觀的分類,最後還是得回到「關鍵字」,例如專題主題為「雲端運算」,分類架構包含「財稅」、「警政」、「教育」、「交通」等類別,我們在閱讀資料文章的時候,只要看到「車載」這個關鍵字,就幾乎可以不加思索地將該筆資料歸類在「交通」類別,而只要看到「治安」這個關鍵字,也幾乎可以直接將該筆資料歸類在「警政」類別,因此我們可以發現,資料分類的任務是有跡可循的,我們能夠將之自動化。
一般來說,文章分類程式會以機器學習、支援向量機(Support Vector Machine,
SVM)等方式來製作,因為諸如「車載」、「治安」這類關鍵字不勝枚舉,難以全盤掌握,直接讓程式以機率統計的方式來處理比較好。
文章分類程式的運作邏輯圖,以下將製作三個步驟的程式原型功能。
以下將整理結巴中文斷詞套件的功能,並將文章分類程式分為三個步驟來製作原型,以便未來實作文章分類程式時,能夠依照實際情形調整程式碼,再將之整併起來使用,若需要在其它沒有安裝Python的電腦使用文章分類程式時,可以先使用pyinstaller將.py檔打包成.exe檔,往後在其它沒有安裝Python的電腦上亦可使用這樣的文章分類程式了。
pyinstaller
pyinstaller
l 若需將.py檔打包成.exe檔,在命令提示字元輸入pip
install pyinstaller,下載並安裝pyinstaller。
l # 在命令提示字元輸入以查看參數
pyinstaller -h
# 在命令提示字元輸入以打包.py檔
pyinstaller -F .\test.py
pyinstaller -h
# 在命令提示字元輸入以打包.py檔
pyinstaller -F .\test.py
jieba
jieba - 結巴中文斷詞台灣繁體版本
l 下載與安裝:
英文可以使用空格執行斷詞,並透過ngram的概念進行切詞分析,然而中文為方塊字,斷詞不易執行,因此建議透過第三方套件協助執行;可在命令提示字元輸入pip install jieba,下載並安裝jieba,然而該套件為中國大陸開發,因此詞庫及HMM機率表皆為適用對岸的版本,加上必須有簡體中文轉換繁體中文的額外步驟,因此推薦使用交大實驗室改良的jieba分支版本,在其GitHub下載後,將jieba資料夾複製到以下路徑即可使用:
Python安裝路徑\Lib\site-packages
英文可以使用空格執行斷詞,並透過ngram的概念進行切詞分析,然而中文為方塊字,斷詞不易執行,因此建議透過第三方套件協助執行;可在命令提示字元輸入pip install jieba,下載並安裝jieba,然而該套件為中國大陸開發,因此詞庫及HMM機率表皆為適用對岸的版本,加上必須有簡體中文轉換繁體中文的額外步驟,因此推薦使用交大實驗室改良的jieba分支版本,在其GitHub下載後,將jieba資料夾複製到以下路徑即可使用:
Python安裝路徑\Lib\site-packages
l 三種斷詞模式:
import jieba
content = open("article.txt", "r").read()
# 精確模式:將句子最精確地切開,為預設模式
words = jieba.lcut(content, cut_all = False)
# 全模式:把句子所有詞語都掃描出來
# words = jieba.lcut(content, cut_all = True)
# 搜索引擎模式:在精確模式基礎上對長詞再次切分
# words = jieba.lcut_for_search(content)
print(words)
import jieba
content = open("article.txt", "r").read()
# 精確模式:將句子最精確地切開,為預設模式
words = jieba.lcut(content, cut_all = False)
# 全模式:把句子所有詞語都掃描出來
# words = jieba.lcut(content, cut_all = True)
# 搜索引擎模式:在精確模式基礎上對長詞再次切分
# words = jieba.lcut_for_search(content)
print(words)
l 三種斷詞模式輸出內容比較:
原始內容:
艾倫.狄波頓一九六九年生於瑞士蘇黎士,從八歲起在英國接受教育,曾求學於頂尖的哈羅學院與劍橋大學。
精確模式輸出內容:
['艾倫', '.', '狄', '波頓', '一九六九年', '生於', '瑞士', '蘇黎士', ',', '從', '八', '歲', '起', '在', '英國', '接受', '教育', ',', '曾', '求學', '於', '頂尖', '的', '哈羅', '學院', '與', '劍橋', '大學', '。']
全模式輸出內容:
['艾倫', '', '', '狄', '波頓', '一九六九年', '六九年', '九年', '生於', '瑞士', '蘇黎', '蘇黎士', '', '', '從', '八', '歲', '起', '在', '英國', '接受', '受教', '教育', '', '', '曾', '求學', '於', '頂尖', '的', '哈', '羅', '學院', '與', '劍橋', '大學', '', '']
搜索引擎模式輸出內容:
['艾倫', '.', '狄', '波頓', '九年', '六九年', '一九六九年', '生於', '瑞士', '蘇黎', '蘇黎士', ',', '從', '八', '歲', '起', '在', '英國', '接受', '教育', ',', '曾', '求學', '於', '頂尖', '的', '哈羅', '學院', '與', '劍橋', '大學', '。']
原始內容:
艾倫.狄波頓一九六九年生於瑞士蘇黎士,從八歲起在英國接受教育,曾求學於頂尖的哈羅學院與劍橋大學。
精確模式輸出內容:
['艾倫', '.', '狄', '波頓', '一九六九年', '生於', '瑞士', '蘇黎士', ',', '從', '八', '歲', '起', '在', '英國', '接受', '教育', ',', '曾', '求學', '於', '頂尖', '的', '哈羅', '學院', '與', '劍橋', '大學', '。']
全模式輸出內容:
['艾倫', '', '', '狄', '波頓', '一九六九年', '六九年', '九年', '生於', '瑞士', '蘇黎', '蘇黎士', '', '', '從', '八', '歲', '起', '在', '英國', '接受', '受教', '教育', '', '', '曾', '求學', '於', '頂尖', '的', '哈', '羅', '學院', '與', '劍橋', '大學', '', '']
搜索引擎模式輸出內容:
['艾倫', '.', '狄', '波頓', '九年', '六九年', '一九六九年', '生於', '瑞士', '蘇黎', '蘇黎士', ',', '從', '八', '歲', '起', '在', '英國', '接受', '教育', ',', '曾', '求學', '於', '頂尖', '的', '哈羅', '學院', '與', '劍橋', '大學', '。']
詞典
l 自定義詞典的格式和預設詞典dict.txt的格式相同,一個詞佔一行,每一行包含用空格隔開的詞語、詞頻(可省略)、詞性(可省略),存檔時編碼必須為UTF-8。
l 修改預設詞典:
import jieba
# https://raw.githubusercontent.com/fxsjy/jieba/master/extra_dict/dict.txt.big
jieba.set_dictionary("dict.txt")
content = open("article.txt", "r").read()
words = jieba.lcut(content, cut_all = False)
print(words)
import jieba
# https://raw.githubusercontent.com/fxsjy/jieba/master/extra_dict/dict.txt.big
jieba.set_dictionary("dict.txt")
content = open("article.txt", "r").read()
words = jieba.lcut(content, cut_all = False)
print(words)
l 修改預設詞典後精確模式輸出內容:
['艾倫', '.', '狄', '波頓', '一九六九年', '生於', '瑞士', '蘇黎士', ',', '從', '八歲', '起', '在', '英國', '接受', '教育', ',', '曾', '求學', '於', '頂尖', '的', '哈羅', '學院', '與', '劍橋大學', '。']
['艾倫', '.', '狄', '波頓', '一九六九年', '生於', '瑞士', '蘇黎士', ',', '從', '八歲', '起', '在', '英國', '接受', '教育', ',', '曾', '求學', '於', '頂尖', '的', '哈羅', '學院', '與', '劍橋大學', '。']
l 加入自訂詞典:
import jieba
# userdict.txt內含"狄波頓"、"哈羅學院"、"劍橋大學"三個詞語
jieba.load_userdict("userdict.txt")
content = open("article.txt", "r").read()
words = jieba.lcut(content, cut_all = False)
print(words)
import jieba
# userdict.txt內含"狄波頓"、"哈羅學院"、"劍橋大學"三個詞語
jieba.load_userdict("userdict.txt")
content = open("article.txt", "r").read()
words = jieba.lcut(content, cut_all = False)
print(words)
l 加入自訂詞典後精確模式輸出內容:
['艾倫', '.', '狄波頓', '一九六九年', '生於', '瑞士', '蘇黎士', ',', '從', '八', '歲', '起', '在', '英國', '接受', '教育', ',', '曾', '求學', '於', '頂尖', '的', '哈羅學院', '與', '劍橋大學', '。']
['艾倫', '.', '狄波頓', '一九六九年', '生於', '瑞士', '蘇黎士', ',', '從', '八', '歲', '起', '在', '英國', '接受', '教育', ',', '曾', '求學', '於', '頂尖', '的', '哈羅學院', '與', '劍橋大學', '。']
l 加入停用詞典:
import jieba
# stopdict.txt內含音界號(.)、逗號(,)、句號(。)三個符號
stopContent = open("stopdict.txt", "r").read()
stopWords = stopContent.split()
content = open("article.txt", "r").read()
words = jieba.lcut(content, cut_all = False)
selectedWords = [word for word in words if word not in stopWords]
print(selectedWords)
import jieba
# stopdict.txt內含音界號(.)、逗號(,)、句號(。)三個符號
stopContent = open("stopdict.txt", "r").read()
stopWords = stopContent.split()
content = open("article.txt", "r").read()
words = jieba.lcut(content, cut_all = False)
selectedWords = [word for word in words if word not in stopWords]
print(selectedWords)
l 加入停用詞典後精確模式輸出內容:
['艾倫', '狄', '波頓', '一九六九年', '生於', '瑞士', '蘇黎士', '從', '八', '歲', '起', '在', '英國', '接受', '教育', '曾', '求學', '於', '頂尖', '的', '哈羅', '學院', '與', '劍橋', '大學']
['艾倫', '狄', '波頓', '一九六九年', '生於', '瑞士', '蘇黎士', '從', '八', '歲', '起', '在', '英國', '接受', '教育', '曾', '求學', '於', '頂尖', '的', '哈羅', '學院', '與', '劍橋', '大學']
關鍵字
l 使用TF-IDF演算法抽取關鍵字:
import jieba.analyse
content = open("article.txt", "r").read()
words = jieba.analyse.extract_tags(content)
print(words)
import jieba.analyse
content = open("article.txt", "r").read()
words = jieba.analyse.extract_tags(content)
print(words)
l 使用TF-IDF演算法抽取關鍵字後輸出內容:
['艾倫', '波頓', '生於', '蘇黎士', '英國', '求學', '頂尖', '哈羅', '學院', '劍橋', '大學', '一九六九年', '瑞士', '教育', '接受']
['艾倫', '波頓', '生於', '蘇黎士', '英國', '求學', '頂尖', '哈羅', '學院', '劍橋', '大學', '一九六九年', '瑞士', '教育', '接受']
l 使用TF-IDF演算法並加入停用詞典以抽取關鍵字:
import jieba.analyse
# stopdict.txt內含"接受"一個詞語
jieba.analyse.set_stop_words("stopdict.txt")
content = open("article.txt", "r").read()
words = jieba.analyse.extract_tags(content)
print(words)
import jieba.analyse
# stopdict.txt內含"接受"一個詞語
jieba.analyse.set_stop_words("stopdict.txt")
content = open("article.txt", "r").read()
words = jieba.analyse.extract_tags(content)
print(words)
l 使用TF-IDF演算法並加入停用詞典以抽取關鍵字後輸出內容:
['艾倫', '波頓', '生於', '蘇黎士', '英國', '求學', '頂尖', '哈羅', '學院', '劍橋', '大學', '一九六九年', '瑞士', '教育']
['艾倫', '波頓', '生於', '蘇黎士', '英國', '求學', '頂尖', '哈羅', '學院', '劍橋', '大學', '一九六九年', '瑞士', '教育']
文章分類程式原型功能-步驟一
# 步驟一-解析相同類別文章的關鍵字
import os, jieba
from collections import Counter
# 如果有已解析的關鍵字存檔,載入以累加記錄關鍵字
oldWords = []
if
os.path.exists("wordsSave.txt"):
savedContent = open("wordsSave.txt", "r")
savedWords = savedContent.read().split()
for i in range(len(savedWords)):
# 僅載入關鍵字
if i % 2 == 0:
# 依照計次載入關鍵字
for j in range(int(savedWords[i + 1])):
oldWords.append(savedWords[i])
savedContent.close()
# 加入自訂詞典
jieba.load_userdict("userdict.txt")
# 載入未解析的相同類別文章以解析關鍵字
content = open("article.txt",
"r")
# 刪除換行字元並進行斷詞
words = jieba.lcut(content.read().replace("\n",
""), cut_all = False)
# 刪除單個字元的關鍵字
words = [word for word in words if
len(word) > 1]
content.close()
# 累加記錄關鍵字
for oldword in oldWords:
words.append(oldword)
# 計次並排序
sortedList = sorted(Counter(words).items(),
key = lambda a : a[1], reverse = True)
# 存檔已解析的關鍵字
wordsSave = open("wordsSave.txt",
"w")
for item in sortedList:
for subItem in item:
# 一行為一關鍵字與其計次
# 一行為一關鍵字與其計次
wordsSave.write(str(subItem) + " ")
wordsSave.write("\n")
wordsSave.close()
文章分類程式原型功能-步驟二
# 步驟二-比較不同類別文章的關鍵字,找出各類別的特色關鍵字
import os
# 不同類別文章的關鍵字存檔結尾皆為wordsSave.txt,此串列儲存檔名開頭
filenameHead = []
# 此串列儲存不同類別文章的關鍵字集合
allTypeWords = []
# 載入不同類別文章的關鍵字存檔
for filename in os.listdir(os.path.abspath(".")):
if filename.endswith("wordsSave.txt"):
# 儲存檔名開頭
filenameHead.append(filename.rstrip("wordsSave.txt"))
# 儲存關鍵字集合
oldWords = []
savedContent = open(filename, "r")
savedWords = savedContent.read().split()
for i in range(len(savedWords)):
# 僅載入關鍵字
if i % 2 == 0:
# 計次若少於指定數值則不載入該關鍵字
if int(savedWords[i + 1]) <
3:
continue
oldWords.append(savedWords[i])
savedContent.close()
allTypeWords.append(set(oldWords))
# 解析並存檔不同類別文章的特色關鍵字
for filenameNum in
range(len(filenameHead)):
#
將目標類別以外的關鍵字做聯集
unitedWords = set()
for allTypeNum in range(len(allTypeWords)):
if filenameNum != allTypeNum:
unitedWords = unitedWords.union(allTypeWords[allTypeNum])
#
將目標類別的關鍵字與其它類別的關鍵字做差集
traitsWords = allTypeWords[filenameNum].difference(unitedWords)
#
存檔已解析的特色關鍵字
traitsSave = open(filenameHead[filenameNum] +
"traitsSave.txt", "w")
for traitsWord in traitsWords:
# 一行為一關鍵字
traitsSave.write(traitsWord + "\n")
traitsSave.close()
文章分類程式原型功能-步驟三
# 步驟三-特色關鍵字搭配正規表示式,嘗試以程式分類新的文章
import re
# 載入特色關鍵字存檔並以管道字元組裝
traitsContent =
open("traitsSave.txt", "r")
traitsWords =
"|".join(traitsContent.read().split())
traitsContent.close()
# 載入新的文章
newContent = open("article.txt",
"r")
newArticle = newContent.read().replace("\n",
"")
newContent.close()
# 以特色關鍵字搜尋新的文章
aRegex = re.compile(r"("+traitsWords+")")
aMatch = aRegex.findall(r"("+newArticle+")")
# 以搜尋結果判斷是否將該文章歸類該類別
if aMatch != []:
print("The Article Would Be Classified")
print(aMatch)
else:
print("The Article Would Not Be Classified")
資料分類實驗的結果
本實驗以筆者承接前人的資料分類專題做為試驗,前人留下共688筆資料,以人工歸類為19項,其後筆者繼續以人工分類381筆新資料。以688筆舊資料當作擷取19項分類的特色關鍵字標準,並以程式分類381筆新資料,對照人工分類與程式分類的差異如下表:
表一、以.xlsx檔不同欄位做為分類標準之結果差異
l 做為分類標準之欄位:資料名稱
l 人工分類與程式分類結果相同的儲存格數:6745
人工分類與程式分類結果不同的儲存格數:494 儲存格分類相同比率:93.18%
l 人工分類與程式分類結果相同的資料筆數:93
人工分類與程式分類結果不同的資料筆數:288 資料分類相同比率:24.41% |
l 做為分類標準之欄位:資料關鍵字
l 人工分類與程式分類結果相同的儲存格數:6729
人工分類與程式分類結果不同的儲存格數:510 儲存格分類相同比率:92.95%
l 人工分類與程式分類結果相同的資料筆數:41
人工分類與程式分類結果不同的資料筆數:340 資料分類相同比率:10.76% |
l 做為分類標準之欄位:資料摘要
l 人工分類與程式分類結果相同的儲存格數:5262
人工分類與程式分類結果不同的儲存格數:1977 儲存格分類相同比率:72.69%
l 人工分類與程式分類結果相同的資料筆數:9
人工分類與程式分類結果不同的資料筆數:372 資料分類相同比率:2.36% |
l 做為分類標準之欄位:資料名稱 + 資料關鍵字
l 人工分類與程式分類結果相同的儲存格數:6638
人工分類與程式分類結果不同的儲存格數:601 儲存格分類相同比率:91.7%
l 人工分類與程式分類結果相同的資料筆數:71
人工分類與程式分類結果不同的資料筆數:310 資料分類相同比率:18.64% |
l 做為分類標準之欄位:資料關鍵字 + 資料摘要
l 人工分類與程式分類結果相同的儲存格數:5194
人工分類與程式分類結果不同的儲存格數:2045 儲存格分類相同比率:71.75%
l 人工分類與程式分類結果相同的資料筆數:10
人工分類與程式分類結果不同的資料筆數:371 資料分類相同比率:2.62% |
l 做為分類標準之欄位:資料摘要 + 資料名稱
l 人工分類與程式分類結果相同的儲存格數:5188
人工分類與程式分類結果不同的儲存格數:2051 儲存格分類相同比率:71.67%
l 人工分類與程式分類結果相同的資料筆數:4
人工分類與程式分類結果不同的資料筆數:377 資料分類相同比率:1.05% |
l 做為分類標準之欄位:資料名稱 + 資料關鍵字 + 資料摘要
l 人工分類與程式分類結果相同的儲存格數:5143
人工分類與程式分類結果不同的儲存格數:2096 儲存格分類相同比率:71.05%
l 人工分類與程式分類結果相同的資料筆數:4
人工分類與程式分類結果不同的資料筆數:377 資料分類相同比率:1.05% |
備註:共有19項分類,因此1筆資料含有19格儲存格,儲存格總數為資料總數的19倍
分類結果令人驚艷,因為這是未使用自訂詞典與停用詞典,以及未檢視特色關鍵字詞典良窳之下進行的分類;筆者在程式中只排除換行字元、單個字元,以及資料摘要中總出現頻率低於3次的詞彙,因此實際上,本分類實驗仍有大量雜訊詞彙在干擾分類結果,即便如此,資料分類相同比率最高也有24.41%,請記得本實驗中1筆資料內含19格儲存格,人工分類的19格儲存格必須與程式分類的19格儲存格完全相同,才會將該筆資料算入資料分類相同比率,這是相當嚴格的判斷標準。
從另一個角度發想,人工分類一定擁有比較好的分類品質嗎?這方面我是懷疑的,因為本實驗是筆者承接前人的資料分類專題來進行的,因此前人的分類準則與筆者的分類準則很可能從一開始就有差異:就筆者觀察本實驗中特色關鍵字分類新資料時的紀錄,已可以發現前人分類資料時「心中的那把尺」,似乎與筆者分類資料時有些微的差異。
文章分類是一件無趣,亦無重要附加價值的工作,這也導致了資料分類專題負責人頻頻更換、缺乏一致性等後果,這些現象對整個專題而言,長遠之下將帶來致命的後果,因為人工分類的標準不一,即便再明確地闡述分類準則,分類的最終決定權仍在分類者當下的判斷,因此筆者才會提倡程式分類,除了可以降低資料分類者的負擔,更可以協助維持整個專題的長遠運作,儘管這可能不是一系列好用的分類程式,但絕對勝過純粹以人工執行這樣的工作任務。
沒有留言:
張貼留言