Python 您所在的位置:网站首页 辟是指什么 Python

Python

2023-04-17 02:51| 来源: 网络整理| 查看: 265

Python — 中文自然語言處理Jieba斷詞Taipei, Taiwan, photo by Sean Yeh

當我們從網路上擷取(爬取)各種文章或內容後,還無法馬上對這些內容進行分析。必須先對這些內容進行清洗與整理,讓它們變成可以用的資料。例如可以將爬回來的文章或內容進行斷詞處理,藉此來進行詞量的初步統計。

中文斷詞

中文與英語或其他歐洲拼音文字不同,它本身不會標記詞的邊界,它沒有空格。所以,兩個靠在一起的字符可能是同一個詞,也可能不是。

特性

對比中文的漢字,英語與其他拼音文字,與它有一個很明顯的區別,那就是文字與文字之間是否有空格來標記詞的界線,前者沒有,而後者則有空格。

這對於如你我常用中文的人來說,問題並不大,但對於以中文為第二語言的學習者來說,學習起來就會比較辛苦。

一個中文字本身可以是詞,但在中文裡有更多藉由兩個以上的漢字組成的複合詞。這樣的複合詞在現代的中文裡,約佔88%。以「橡樹」這個詞為例,其第一個字「橡」本身就是個有意義的詞,意思為「橡樹」。而「樹」也是一個詞,代表植物。兩個漢字合併起來成為複合詞「橡樹」,它也是一個詞,意思為「橡樹」。對於讀過書的你我來說或許問題不大,可以從句子中看出「橡」與「橡樹」指的是同一個意思。但這對於電腦來說,就像是個例外。

如何斷詞

基於此,電腦在進行「自然語言處理」的斷詞處理時,中文斷詞就不能像處理英文一樣,只要用空白(space)即可,而要處理中文斷詞卻複雜的多。不透過特殊的方式處理,無法得到想要的結果。

Jieba是一個 MIT 授權的開源分詞詞庫套件,放在 GitHub 上供大家使用。套件本身支援中文繁體與簡體的分詞。Jieba有三種模式(精確、全引擎與搜尋引擎),若對分詞結果不滿意時,可以自己動手增加特定詞彙或者是自行定義字典,調整分詞的結果。

日文也有類似的特性,它借用了不少漢字,但處理起來與中文還是有點不一樣,我們曾經在這一篇裡面介紹過日文自然語言處理的套件。

Python -JANOME快速拆解日文的方法當我們聽到「すもももももももものうち」的一句話時,能分出意思是什麼嗎?我想,除非你不懂日文,不然對於這樣一句話應該不會太難。人類對於自然語言本身,就有能力可以理解與分辨出中間的差異性。但是,對電腦來說,這個繞口令般的句子,看起來就像是一堆「…

medium.com

安裝Jieba

使用Jieba模組需要先透過pip安裝在自己的環境中。

pip install jiebaJieba pipy使用Jieba

以下先介紹Jieba基本的使用方式。在使用Jieba時,需要在檔案中匯入Jieba 套件。

匯入Jieba套件

首先,透過import將Jieba 套件匯入專案。

import Jieba三種斷詞模式

Jieba模組的斷詞函式有二:

cut()cut_for_search()

斷詞的方式則可以分為,精確模式、全模式與搜尋引擎模式等三種模式。以下分別就每一個模式加以說明。

全模式

在Jieba模組裡,只要將cut_all 參數設定為True,即為全模式。

全模式可以非常快的把句子中所有可以成為詞的詞語都掃描出來。但缺點是不能解決歧義。

如下程式碼所示,使用時全模式,要在cut函式裡面加上cut_all=True 的設定:

jieba.cut(sentence, cut_all=True)

程式碼中的sentence變數代表我們要斷詞的句子。平常您可能會這樣的寫:

sentence = "今天是一個晴朗的星期天"精確模式

若將cut_all 參數設定為False的話,即為精確模式 cut(cut_all=False)。精確模式,試圖將句子做最精確地切割斷詞,適合於文本分析。

此外,cut_all這個參數若沒有設定的話,得到的結果會跟精確模式一樣,這種狀況又被稱之為「預設模式」。

jieba.cut(sentence, cut_all=False)搜尋引擎模式

搜尋引擎模式,係在精確模式的基礎上,對長詞進行再次切分,目的是要提高召回率。這種模式適合用於搜索引擎分詞。

透過jieba中的cut_for_search()函式,可以使用搜尋引擎模式:

jieba.cut_for_search()實作三種模式

以下程式碼係依據上面的說明,進行的實作。

# 匯入套件import jieba

# 宣告字串sentence = '今天是一個晴朗的星期天'

# 斷詞

## 全模式s1_list = jieba.cut(sentence, cut_all =True)print('模式- 全 : ', ' | '.join(s1_list))

## 精確模式s2_list = jieba.cut(sentence, cut_all =False)print('模式- 精確: ', ' | '.join(s2_list))

## 預設模式s3_list = jieba.cut(sentence)print('模式- 預設: ', ' | '.join(s3_list))

## 搜尋引擎模式s4_list = jieba.cut_for_search(sentence)print('模式- 搜尋: ', ' | '.join(s4_list))

其中我們要斷詞的句子,與上面例子的內容一樣,指定給變數sentence。並且使用了另外四個變數s1_list、s2_list、s3_list、s4_list,分別代表「全模式」、「精確模式」、「預設模式」與「搜尋引擎模式」。並且透過print將結果顯示在螢幕上。

程式執行的結果如上圖。可以發現整體上來說,上斷詞的結果還不錯,而且「精確模式」與「預設模式」的結果是一樣的。

加入字詞 add_word

隨著時間的演進,語言會產生新的詞彙。當我們透過Jieba模組進行斷詞的時候,可能會因為某個詞彙太新而無法正確的將該詞彙分析出來。又或是為了處理特殊領域(經濟、法律、醫學等特殊領域)的專有名詞,對於哪些字應該斷在一起,有自行定義的需求時。

碰到以上的狀況時,可以透過Jieba模組裡面的add_word函式,加入新的詞彙。如下:

jieba.add_word(word, freq=None, tag=None)自定義詞典

語言產生新的詞彙雖然可以透過上面的方式加入,但是若一次需要加入的字太多,採用上面的逐一加入時,非常的麻煩。可以透過載入外部自己定義的辭典檔案來處理。自定義詞典的方法如下:

編輯字典檔

首先,使用一般的文字編輯器撰寫字典檔案,字典檔案為副檔名txt的文字檔。

在編輯時,一個詞佔用一行,也就是說字典檔案中每一個row只能有一個詞。字典檔案中的詞可以依照每個人的需要添加。您可以在沒有字典的情況下,先執行一次jieba斷詞,然後再根據斷詞的結果來增加詞典的內容。

載入字典檔

完成字典檔案的編輯後,即可透過load_userdict函式將這個編輯好的字典檔案載入程式中使用,函式的括弧裡面要放入字典的路徑。

jieba.load_userdict(file_path)

如上程式碼,load_userdict函式的括弧中所放入的file_path變數即是我們先前編好的字典檔案所在的路徑。假設我們將字典檔案custom_dict.txt放在專案的目錄裡面,使用的時候就可以這樣引入:

file_path = 'custom_dict.txt'jieba.load_userdict(file_path)實作自定義詞典Before狀態

上面的sentence例子,斷詞的結果沒有什麼問題。在這裡,我們要改用另外一個句子來進行斷詞。

sentence2 = '今天是一個晴朗的星期天。我要去西門町看電影,騎完之後要去東吳大學後面爬山'

並且只針對精確模式進行斷詞。程式碼如下:

# 匯入套件import jieba

# 宣告字串sentence2 = '今天是一個晴朗的星期天。我要去西門町看電影,騎完之後要去東吳大學後面爬山'# 斷詞精確模式s_list = jieba.cut(sentence2, cut_all =False)print('模式- 精確: ', ' | '.join(s_list))

執行的斷詞的結果如下:

上面的結果出乎意料的怪。從上圖看來:

「西門町」被切割為「西門」與「町」兩個詞;「騎完之後要去」被切割為「騎」、「完之後要」、「去」;「東吳大學後面」也被分割出來,也變成「東」與「吳大學後面」

等等這些奇怪的斷詞方式。

After狀態

基於此,我們可以建立一個自定義詞典custom_dict.txt。並將想要的詞加進這個檔案中。參考上面執行的結果,我們在字典中加入四個詞如下:

完成編輯並將字典檔案存檔後,即可依照前面的說明,透過load_userdict函式將檔案載入程式中。程式碼如下:

# 匯入套件import jieba

# 載入自定義詞典file_path = 'custom_dict.txt'jieba.load_userdict(file_path)# 宣告字串sentence2 = '今天是一個晴朗的星期天。我要去西門町看電影,騎完之後要去東吳大學後面爬山'# 斷詞## 精確模式s_list = jieba.cut(sentence2, cut_all =False)print('模式- 精確: ', ' | '.join(s_list))

執行後,斷詞的結果如下:

可以從上圖執行的結果看到,經過自定義的詞典,這次產生的斷詞,比較接近我們的需要了。

案例實作

在此利用上面說明的概念與方法,嘗試計算文章詞彙出現的次數。我們要使用jieba套件將事先從PPT爬下來的文章檔案(txt)進行分詞,並且計算每個詞語出現的次數。

# 註:從PPT爬取文章檔案的方式,涉及到網路爬蟲相關知識與概念,在此不加贅述。

1. 引入套件

首先,要引入Jieba、os等必要的套件。Jieba套件用來分詞,而os套件則用來開啟txt文字檔案。

import jiebaimport os2.列出所有檔案

先列出所有需要斷詞的檔案。我們將放置檔案的路徑指定給變數src_file_path。在此我們將檔案全數放在file資料夾裡面。資料夾與專案檔的關係如下:

file資料夾裡面的內容如下圖:

並利用os套件中的listdir函式,將src_file_path路徑中的所有檔案名稱都搜集起來,指定給file_list。

src_file_path = r'./files'file_list = os.listdir(src_file_path)print(file_list)

如果將file_list顯示在螢幕中,會看到一個存放著所有檔案名稱的list串列。

在Spyder執行的結果3.將所有文章存成一個字串

接下來,要將所有文章裡面的文字取出來,存在一個字串all_article裡面(如下)。

all_article = ''

還記得在前一個步驟中完成的file_list變數,它搜集了所有的檔案名稱。透過for迴圈可以對file_list進行操作。

#打開檔案

我們將使用for迴圈配合open函式,將每個檔案都打開來並取文字。為了達到這個目的,除了已經知到檔名外,還需要知道file_list中每個檔案的路徑(article_path)。取得這個路徑很簡單,只要將檔名與前面指定用來放置檔案的src_file_path組合起來就可以了。

article_path = f'{}/{}'.format(src_file_path,article)

有了每個檔案的路徑後,就可以使用open函式打開檔案:

# 每個檔案的路徑article_path = src_file_path + '/' + article# 打開article_path檔案with open(article_path, 'r', encoding='utf-8') as f: #讀取文章內容 f.read()

為了方便使用,在這裡我們採用with open語法來打開檔案,並且使用唯獨模式( ’r’ )。開啟文章後,使用 read() 來讀取檔案中的內容。

# 分割文章

觀察每一篇個別檔案的文章(如下圖),可以發現符號 --split-— 剛好將文章分為上下兩個部分,上半部為文章的主要內容,而下半部則是該文章作者、時間、推、噓等的相關資訊。

f.read().split('---split---')[0]

我們可以利用這個特質,以split函式將文章分割成兩個部分,split之後會將結果呈現在list串列中。在此需要的是「上半段」的文章內容,因此要用[0]取出第0個項目。

# 再次分割

取出上半段的內容後,你會發現整個內容中有一的部分也不是我們需要的,那就是第一排的作者、標題、時間。為了去掉這個部分的內容,我們可以再對這個字串進行第二次的分割,並且只保留需要的部分。

這次分割的基準點為換行符號『 \\n 』,由於要保留的是換行後地一行以外的內容,因此要用 [1:]取第一個以外的全部資料。第二次分割可以直接接在第一次分割的後面。

f.read().split('---split---')[0].**split('\\n')[1:]**

並將分割後的結果指定給變數tmp_article。

tmp_article = f.read().split('---split---')[0].split('\\n')[1:]

# 逐行取出

前面取得的內容tmp_article,透過for迴圈逐ㄧ的將一行行的內容加到字串all_article中。

for article_line in tmp_article: all_article = all_article + article_line + '\\n'

最後,透過for迴圈將上面的程式碼包裹起來,for迴圈將巡迴資料夾中的所有爬回來的檔案,透過逐一的處理添加,形成一個大字串all_article。並且印出結果。易言之,這個程式碼裡面有兩個for迴圈,外層迴圈會逐一的讀取資料夾中的個別檔案,而內層的迴圈則負責搜集被讀取的單ㄧ檔案中需要的文字內容。

all_article = ''for article in file_list: ...略...(article處理)

print(all_article)

# 步驟3之完整程式碼

將所有文章存成一個字串all_article的這個部分,完整程式碼如下:

all_article = ''for article in file_list: article_path = src_file_path + '/' + article with open(article_path, 'r', encoding='utf-8') as f: tmp_article = f.read().split('---split---')[0].split('\\n')[1:] for article_line in tmp_article: all_article = all_article + article_line + '\\n' print(all_article)

執行到這裏為止的程式碼,我們會得到類似下面的結果:

執行結果部分截圖

執行結果會依照您放入檔案的多寡而定。由於筆者在這個資料夾裡面放了很多爬來的txt文字檔案,因此執行的結果非常的長,上圖只是結果顯示在螢幕中的部分截圖。

4.斷詞

在此使用預設模式對前面的all_article大字串進行分詞。由於使用的是預設模式,在cut函式中不需要引入cut_all參數。

str_list = jieba.cut(all_article)

並且將斷詞結果指定給變數str_list。

5.計算詞出現頻次

斷詞完畢後,就可以計算每一個詞出現的次數。首先我們要宣告一個dict字典word_count,來記錄每個詞出現的次數。

word_count = {}

透過一個for迴圈,巡迴斷詞結果str_list。如果找到的詞已經存在字典word_count中,就在字典中以該字為鍵(key)的後面加1( word_count[i] +=1 ),反之如果在字典中沒有找到該詞,則以該詞創設一個新的鍵(key)( word_count[i] = 1 )。

for i in str_list: if i in word_count: word_count[i] +=1 else: word_count[i] = 1print(word_count)

最後將字典word_count搜集的結果顯示在螢幕上( print(word_count) )。

# 步驟4與5之完整程式碼

這一部分的完整程式碼如下:

str_list = jieba.cut(all_article)word_count = {}for i in str_list: if i in word_count: word_count[i] +=1 else: word_count[i] = 1print(word_count)

步驟1到5執行結果:

從上面的結果,你會發現斷詞不甚完美,並不是很精確,例如上面有個「李辭鴻海當」一詞,就不知道所謂為何?

會出現這樣的結果,是因為對於jieba來說有一些詞可能「太新」了,或者是那些詞太具備台灣的「地方特色」了。jieba字典裡面的詞不夠完善(因為jieba是大陸人開發的套件),無法辨別出來。因此需要透過前面介紹的自訂義詞典與停用字典來改善斷詞不夠精確的問題。

6.套用自定義詞典

為了改善斷詞不夠精確的問題,我們要建立一組自定義的詞典。

#加入自定義詞典

依照前面的說明,我們要先建立一個自定義詞典mydict.txt,並且透過load_userdict將它載入程式中。自定義詞典mydict.txt如下:

將字典指定給變數dict_path,並透過load_userdict將它載入程式中。

dict_path=r'./mydict.txt'jieba.load_userdict(dict_path)

以上載入自定義字典的程式碼,要放在斷詞程式的前面。加上自定義字典之後的程式碼如下:

dict_path=r'./mydict.txt'jieba.load_userdict(dict_path)

# 列出所有檔案src_file_path = r'./files'file_list = os.listdir(src_file_path)print(file_list)# 將所有文章存成一個字串all_article = ''for article in file_list: article_path = src_file_path + '/' + article with open(article_path, 'r', encoding='utf-8') as f: tmp_article = f.read().split('---split---')[0].split('\\n')[1:] for article_line in tmp_article: all_article = all_article + article_line + '\\n'print(all_article)# 使用預設模式進行分詞str_list = jieba.cut(all_article)word_count = {}for i in str_list: if i in word_count: word_count[i] +=1 else: word_count[i] = 1print(word_count)

執行上面的程式碼後,可以看到結果與之前沒有使用字典以前有些不同:

7.補充:停用字詞典

到步驟6為止產生的斷詞結果,仍然有些不甚滿意之處。例如下圖紅色方匡中統計了不需要的字串,為了讓結果更精確,我們會想要將這些沒有用的字詞從斷詞結果中移除,那該如何處理?

由於Jieba函式裡面沒有停用詞詞典,我們可以透過程式的撰寫來仿造上面「步驟6」建立自定義詞典的方式,來幫助我們建立停用詞詞典。

#建立詞典

建立停用詞典的方式如下:

建立一個文字檔,並且命名為delete_words.txt編輯delete_words.txt,將不要的字放入,詞典的樣子如下:

#讀入停用字詞典

1.建立一個變數,將delete_words.txt的路徑指定給這個變數。在這裡我們將檔案放在與專案檔案同一層的資料夾裡面。

del_words_path ='delete_words.txt'

2.建立一個串列list,並將停用字放list裡面。

del_words_list = []

3.使用with open語法開啟停用字詞典檔案(del_words_path),並且透過for迴圈逐行讀取檔案中的內容( f.readlines() ),並將讀到的內容添加入上一步建立的串列del_words_list裡面( del_words_list.append() ),在添加的同時把換行符號都去除( line.replace('\\n','') )。

with open(del_words_path, 'r', encoding='utf-8') as f: for line in f.readlines(): del_words_list.append(line.replace('\\n',''))

#檢查woad_count字典

接下來,要來檢查word_count字典,也就是檢查前面文章斷詞次數計算的結果。我們採用刪去法,只取出不屬於停用字的詞。換句話說,只保留長度大於1( len(k) >1 ),以及該詞存不在停用字list中的詞( k not in stopword_list )。將符合上述條件的結果取出鍵與值(Key and value),放在一個tuple裡面( (k,word_count[k]) )。

word_list = []for k in word_count: if len(k) > 1 and k not in del_words_list: word_list.append((k,word_count[k]))

上面程式碼若透過解析式列表表示,則呈現如下:

word_list = [(k,word_count[k]) for k in word_count if len(k) > 1 and k not in del_words_list]

#依照次數排序

最後,還可以依照每一個詞出現的次數,由大到小進行排序。

word_list.sort(key= lambda x: x[1], reverse=True)print(word_list)

執行後,可以得到下面的結果:

所有的詞依照出現的頻次,從大到小排列。

結語

Jieba分詞庫套件,在操作與設定上十分簡單好用。但畢竟這套套件是使用簡體字的大陸人所開發的工具,它對簡體中文以及中國大陸常用的字詞,分詞較為準確。一旦碰到一些具備台灣「地方特色」的專門用語時,就顯的不夠「接地氣」,難以將詞語斷得很好。使用起來,就會有種在Nitfilex上透過日語發音欣賞韓劇的感覺。期待將來有志之士,可以開發出適用於台灣語詞的斷詞套件,造福大眾。

註:雖然目前有採用和原始jieba相同的演算法,只替換其詞庫的台灣繁體版本的Jieba套件。但是在使用上並不是透過pip安裝,而是需要直接下載放入開發程式的資料夾中。因此筆者認為這應該還不算是一個完整的套件。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有