顯示具有 計算語言學 標籤的文章。 顯示所有文章
顯示具有 計算語言學 標籤的文章。 顯示所有文章

2024年3月20日 星期三

ACL 2024

三月初內訓 LLM 課程古倫維老師的一堂 Transformer 基礎課我 miss 掉前 30 分鐘, 今天被我找出錄影來複習, 發現我沒聽到的這前 30 分鐘資訊量頗大, 例如世界模型我是第一次聽到. 其次是簡介時古老師提到如果要知道最新的 NLP 發展趨勢可以到 ACL (計算語言學學會) 去看看所提交的論文 (先看摘要即可) :


古老師將擔任 ACL 2024 年會的 Program Chair : 


8/11~8/16 在泰國曼谷ㄟ (呵呵我的泰國記憶). 

2024年1月17日 星期三

匯入中文斷詞套件 pywordseg 出現 IndexError 問題

去年底我測試了左岸開發的中文斷詞模組 jieba, 在支援自訂字典功能的加持下, 繁體中文的斷詞效果倒也還不錯. 今天在 "Python 實戰聖經 (碁峰, 2021)" 這本書讀到 pywordseg 這個專為繁體中文開發的斷詞模組, 今天就來測試看看唄. 關於 Jieba 的用法參考 :   


與 Jieba 不同的是, pywordseg 模組採用深度學習模型 (elmo/w2v) 來斷詞, 雖然準確度較高, 但所需要的算力也較大, 最好是開啟 GPU 來加速. 由於我的筆電沒有 GPU, 故以下測試改在 Colab 上進行. 關於 Colab 用法參考 :


由於 Colab 未預載 pywordseg, 故使用前須先安裝 : 

!pip install pywordseg




安裝完就可以匯入所有模組來使用 : 

from pywordseg import *   

第一次會先下載模型, 包含系統斷句模型與 CharEmb, ELMo 等模型, 總共超過 500MB, 需要花點時間. 但下載完後匯入模組與函式時卻出現 IndexError : 




我以為是 pywordseg 版本問題, 於是改安裝較舊版本 0.0.9, 但還是無法順利匯入, 這回出現的錯誤是 TypeError : 




即使是只匯入 Wordseg 函式也是一樣 :

from pywordseg import Wordseg 

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-bf1f65fc63ab> in <cell line: 1>()
----> 1 from pywordseg import Wordseg

10 frames
/usr/local/lib/python3.10/dist-packages/overrides/signature.py in ensure_return_type_compatibility(super_type_hints, sub_type_hints, method_name)
    300     sub_return = sub_type_hints.get("return", None)
    301     if not _issubtype(sub_return, super_return) and super_return is not None:
--> 302         raise TypeError(
    303             f"{method_name}: return type `{sub_return}` is not a `{super_return}`."
    304         )

TypeError: Highway.forward: return type `<class 'torch.Tensor'>` is not a `<class 'NoneType'>`.

原因還沒時間研究, 先記下來吧! 不過這套件需要下載這麼大的模型且執行速度慢, 好像用 Jieba 就可以了. 

2023年11月30日 星期四

Python 學習筆記 : 用 Jieba 模組做中文斷詞

今天在 Hahow 企業版的 "開啟 AI 即戰力 : NL 無痛入門" 課程中, 學習到如何將中文斷詞模組 Jieba 用在語音聊天機器人的作法, 這模組在我手邊的幾本 Python 與 NLP 的書上都有介紹, 以前看完也沒時間測試, 趁著今天記憶猶新紀錄一下測試結果唄. 

Jieba 是中國搜尋引擎百度所開發, 是目前最為廣用的中文斷詞工具, 其預設詞庫據說最早是來自人民日報. Jieba 基本上使用詞庫匹配方式進行規則斷詞, 但對於詞庫中沒有的詞條也支援使用隱藏式馬可夫模型 (HMM) 進行統計斷詞. Jieba 採 MIT 開源政策, 其原始碼寄存於 GitHub, 參考 :


Jieba 的特點摘要如下 :
  • 支援簡體繁體斷詞
  • 支援自定義詞典
  • 提供四種斷詞模式 : 精確, 全文, 搜尋引擎, 以及 Paddle 模式
其中 Paddle 模式使用深度學習框架斷詞, 須先安裝 paddlepadle-tiny 模組才能使用, 且僅支援 Jieba 0.40 版以上. 雖然 Jieba 同時支援繁簡中文, 但因為預設是針對簡體中文詞彙用法而開發, 繁體文本的斷詞結果可能不甚完美, 但這可以透過所提供的設定詞庫功能下載繁體中文詞庫解決.


1. 安裝 Jieba :

可以直接在命令列下 pip install 指令直接安裝 Jieba 模組 :

pip install jieba   

我習慣使用 Thonny 開發 Python 程式, 所以是在 "套件管理" 搜尋 Jieba 後按 "安裝" :




在命令列用 import 匯入 jieba 若沒報錯即可開始用它來斷詞 :

>>> import jieba    
>>> jieba.__version__    
'0.42.1'

用 Python 內建函式 ddir() 檢視 Jieba 模組的內容 : 

>>> dir(jieba)    
['DEFAULT_DICT', 'DEFAULT_DICT_NAME', 'DICT_WRITING', 'PY2', 'Tokenizer', '__builtins__', '__cached__', '__doc__', '__file__', '__license__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', '_compat', '_get_abs_path', '_lcut', '_lcut_all', '_lcut_for_search', '_lcut_for_search_no_hmm', '_lcut_no_hmm', '_pcut', '_pcut_for_search', '_replace_file', 'absolute_import', 'add_word', 'calc', 'check_paddle_install', 'cut', 'cut_for_search', 'default_encoding', 'default_logger', 'del_word', 'disable_parallel', 'dt', 'enable_paddle', 'enable_parallel', 'finalseg', 'get_DAG', 'get_FREQ', 'get_dict_file', 'get_module_res', 'initialize', 'iteritems', 'iterkeys', 'itervalues', 'lcut', 'lcut_for_search', 'load_userdict', 'log', 'log_console', 'logging', 'marshal', 'md5', 'os', 'pkg_resources', 'pool', 're', 're_eng', 're_han_default', 're_skip_default', 're_userdict', 'resolve_filename', 'setLogLevel', 'set_dictionary', 'strdecode', 'string_types', 'suggest_freq', 'sys', 'tempfile', 'text_type', 'threading', 'time', 'tokenize', 'unicode_literals', 'user_word_tag_tab', 'xrange']

常用函式如下表 : 


 Jeiba 常用函式 說明
 cut(text) 支援精確, 全文, 與 Paddle 模式斷詞, 傳回一個 generator
 cut_for_search(text) 支援搜尋引擎模式斷詞, 傳回一個 generator
 lcut(text) 支援精確, 全文, 與 Paddle 模式斷詞, 傳回一個 list
 lcut_for_search(text) 支援搜尋引擎模式斷詞, 傳回一個 list
 enable_paddle() 開啟 Paddle 斷詞模式 (僅支援 0.40 以上版本)
 set_dictionary(file_name) 設定自訂詞典 (傳入字典檔案名稱)
 load_dictionary(file_name) 載入自訂詞典 (傳入字典檔案名稱)


其中的 cut() 與 cut_for_sarch() 函式為主要的斷詞的工具, 兩者均傳回一個生成器 generator, 可以將生成器傳給 list() 轉成串列, 或傳給字串的 join() 方法用一個 delimiter 字元將斷詞結果串接成字串, 但對於超大文本則可用迴圈迭代方式從 generator 取出斷詞結果, 以免發生記憶體不足問題.  

>>> type(jieba.cut)   
<class 'method'>  
>>> type(jieba.cut_for_search)  
<class 'method'>


2. 呼叫 jieba.cut() 或 jieba.cut_for_seach() 將文本斷詞 :

jieba.cut() 的參數有 4 個 : 

jieba.cut(text [, cut_all=False, HMM=False, use_paddle=False])

必要參數 text 為要進行斷詞的文本, 必須是 UTF-8 或 unicode 編碼的字串; 參數 cut_all 用來設定精確或完全斷詞模式 : 
  • cut_all=False (預設) : 精確模式, 將句子中的詞精確地切開
  • cut_all=True : 完全模式, 將句子中所有可以成詞的都切出來, 速度快但可能有歧異
HMM 參數用來設定是否使用隱藏式馬可夫模型, 用於處理未登錄詞; use_paddle 參數用來設定是否開啟 paddle 模式 (深度學習框架). 

jieba.cut_for_search() 是以精確模式為基礎對較長的詞彙再切分, 主要用在搜尋引擎的斷詞, 其參數有 2 個, 用法與 cut() 一樣 : 

jieba.cut(text [, HMM=False)

例如 :

>>> text='只有退潮的時候,你才知道誰在裸泳'    
>>> g=jieba.cut(text)    
>>> type(g)     
<class 'generator'>      
>>> '|'.join(g)    
'只有|退潮|的|時候|,|你|才|知道|誰|在|裸泳'    
>>> g=jieba.cut(text, cut_all=True)      >>> type(g)     
<class 'generator'>
>>> '|'.join(g)      
'只有|退潮|的|時|候|,|你|才|知道|誰|在|裸泳'     
>>> g=jieba.cut_for_search(text)        
>>> type(g)     
<class 'generator'>  
>>> '|'.join(g)      
'只有|退潮|的|時候|,|你|才|知道|誰|在|裸泳'    

可見不論是 cut() 還是 cut_for_search() 都會傳回一個 generator, 欲取得斷詞結果可以將這個 generator 傳給字串的 join() 方法, 用一個分界字元 (delimiter) 將其產生的元素串起來. 也可以傳給 list() 函式轉成串列 :

>>> text='只有退潮的時候,你才知道誰在裸泳'    
>>> g=jieba.cut(text)  
>>> glist=list(g)      
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\tony1\AppData\Local\Temp\jieba.cache
Loading model cost 0.363 seconds.
Prefix dict has been built successfully.
>>> list(g)     
['只有', '退潮', '的', '時候', ',', '你', '才', '知道', '誰', '在', '裸泳']

上面的測試句子不論用哪個模式得到的斷詞結果都一樣, 改用下面句子就能看出不同 :

>>> text='這次到台北一定要去搭捷運,還要去台北101看看'    
>>> g=jieba.cut(text)                               # 精確模式
>>> '|'.join(g)    
'這次|到|台北|一定|要|去|搭|捷運|,|還要|去|台北|101|看看'    
>>> g=jieba.cut(text, cut_all=True)       # 完全模式
>>> '|'.join(g)    
'這|次|到|台北|北一|一定|定要|去|搭|捷|運|,|還|要|去|台北|101|看看'    
>>> g=jieba.cut_for_search(text)           # 搜尋引擎模式
>>> '|'.join(g)      
'這次|到|台北|一定|要|去|搭|捷運|,|還要|去|台北|101|看看'   

此例可知完全模式會完全切出可能的詞, 例如 "一定要" 會切出 "一定" 與 "定要" 兩個詞; "台北一" 會切出 "台北" 與 "北一"; 而精確模式與搜尋引擎模式結果仍是相同.


3. 使用繁體中文詞庫 :

Jieba 預設詞庫為簡體中文, 詞彙與繁體中文有些差別, 這使得斷詞的結果有時並不恰當, 所幸 Jieba 提供詞庫設定功能, 可以從 GitHub 下載 Jieba 的繁體中文詞庫 dict.txt.big.txt (約 8.4MB, 詞條總數約 58 萬個) :


將其放在目前工作目錄下的 dict 子目錄下, 然後用 jieba.set_dictionary() 函式更改使用的詞庫, 這樣就會套用繁體中文詞庫了 :

jieba.set_dictionary('dict/dict.txt.big.txt')

用純文字編輯器開啟檢閱詞庫內容如下 :

1号店 3 n
1號店 3 n
4S店 3 n
4s店 3 n
AA制 3 n
AB型 3 n
AT&T 3 nz
A型 3 n
A座 3 n
A股 3 n
A輪 3 n
A轮 3 n
.... (略)....

詞條的格式為 : 

詞彙 頻率 詞性

其中詞類 n 為名詞, v 為動詞等. 




例如 :

>>> jieba.set_dictionary('dict/dict.txt.big.txt')   
>>> text='這次到台北一定要去搭捷運,還要去台北101看看'     
>>> g=jieba.cut(text)      
>>> '|'.join(g)    
Building prefix dict from D:\python\test\dict\dict.txt.big.txt ...
Dumping model to file cache C:\Users\tony1\AppData\Local\Temp\jieba.ufcd6a507dc974c1ccf010c4f07a7bccd.cache
Loading model cost 1.488 seconds.
Prefix dict has been built successfully.
'這次|到|台北|一定|要|去|搭|捷運|,|還要|去|台北|101|看看'

原本以為 '台北101' 會被切成一個詞, 可見這個繁中詞庫還是不夠完整, 甚至連 '小港機場' 都被切成 '小港' 與 '機場' 兩個詞, 例如 :

>>> text='這次去日本玩從小港機場出境'  
>>> jieba.set_dictionary('dict/dict.txt.big.txt')   
>>> g=jieba.cut(text)    
>>> '|'.join(g)   
Building prefix dict from D:\python\test\dict\dict.txt.big.txt ...
Loading model from cache C:\Users\tony1\AppData\Local\Temp\jieba.ufcd6a507dc974c1ccf010c4f07a7bccd.cache
Loading model cost 0.722 seconds.
Prefix dict has been built successfully.
'這次|去|日本|玩|從小|港|機場|出境'
>>> g=jieba.cut(text, cut_all=False, HMM=True)     # 啟動 HMM 結果相同
>>> '|'.join(g)   
'這次|去|日本|玩|從小|港|機場|出境'

看來它是把 '從' 與 '小' 切成一個詞了 ('從小' 是一個時間副詞). 解決此問題可以編輯繁中詞庫更改詞頻, 或者使用 Jieba 提供的自定義詞庫功能製作自己的詞庫, 其優先權最高. 


4. 自定義詞庫 :   

教學文件說可以使用自定義詞庫來解決, 先編輯一個自定義詞庫 user_dict.txt, 內容如下 :

小港機場 3 n
台北101 3 n
蔡英文


詞條後面的數字是頻率, 最後面的是詞類, 這兩項可有可無, 有的話格式一定要正確 (即藥用空格隔開), 否則載入時會出現剖析錯誤.  




以 UTF-8 編碼存檔放在 dict 目錄底下, 然後在呼叫 cut() 前先呼叫 load_userdict() 並傳入此字定義詞庫檔名, 但測試結果無效 :

>>> text='這次去日本玩從小港機場出境'   
>>> jieba.set_dictionary('dict/dict.txt.big.txt')   
>>> jieba.load_userdict('dict/user_dict.txt')      
Building prefix dict from D:\python\test\dict\dict.txt.big.txt ...
Dumping model to file cache C:\Users\tony1\AppData\Local\Temp\jieba.uad5f395071014f3797318be3d45203bb.cache
Loading model cost 0.837 seconds.
Prefix dict has been built successfully.
>>> g=jieba.cut(text)   
>>> '|'.join(g)   
'這次|去|日本|玩|從小|港|機場|出境'

我猜可能是版本問題, 於是在另一台電腦指定安裝 0.39 版的 Jieba 來驗證此問題 : 

E:\python\test>pip install jieba==0.39    
Collecting jieba==0.39
  Downloading jieba-0.39.zip (7.3 MB)
     ---------------------------------------- 7.3/7.3 MB 2.6 MB/s eta 0:00:00
  Preparing metadata (setup.py) ... done
Building wheels for collected packages: jieba
  Building wheel for jieba (setup.py) ... done
  Created wheel for jieba: filename=jieba-0.39-py3-none-any.whl size=7282594 sha256=cc61f12c9a40cf1b040490b5c38de9deded61c4d3428b44ac7ab2a3fbdc7bdd0
  Stored in directory: c:\users\yhhuang\appdata\local\pip\cache\wheels\40\03\0d\c7671d2efcae3ed01aee03a390bcb970518abcdcd3d5df6c88
Successfully built jieba
Installing collected packages: jieba
Successfully installed jieba-0.39

>>> import jieba   
>>> jieba.__version__     
'0.39'   
>>> text='這次去日本玩從小港機場出境'    
>>> g=jieba.cut(text)   
>>> '|'.join(g)   
Building prefix dict from the default dictionary ...
Dumping model to file cache C:\Users\yhhuang\AppData\Local\Temp\jieba.cache
Loading model cost 0.797 seconds.
Prefix dict has been built succesfully.
'這次|去|日本|玩|從|小港|機場|出境'

可見 0.39 版的 Jieba 在預設的簡體詞庫下, 正確地切出 '小港', 而上面的新版 0.43 版反而是錯誤地切成 '從小' 與 '港'. 

接著下載上面的繁體詞庫檔 dict.txt.big.txt 放在目前工作目錄的子目錄 dict 底下, 用 set_dictionary() 設定使用此繁體詞庫, 重新進行斷詞, 結果反而跟 0.43 版一樣得到不正確的切詞 :

>>> jieba.set_dictionary('dict/dict.txt.big.txt')   
>>> text='這次去日本玩從小港機場出境'    
>>> g=jieba.cut(text)    
>>> '|'.join(g)     
Building prefix dict from E:\python\test\dict\dict.txt.big.txt ...
Dumping model to file cache C:\Users\yhhuang\AppData\Local\Temp\jieba.ua930d4894de9d82d45250971e025c191.cache
Loading model cost 1.172 seconds.
Prefix dict has been built succesfully.
'這次|去|日本|玩|從小|港|機場|出境'   

這可能是使用的繁中詞庫的關係, 我在下面這個網站找到另一個繁中詞庫 (比較小, 才 4MB) 就能切出正確結果 :


將此 dict.txt 同樣放在 dict 資料夾下測試 OK :

>>> jieba.set_dictionary('dict/dict.txt')   
>>> g=jieba.cut(text)   
>>> '|'.join(g)     
Building prefix dict from E:\python\test\dict\dict.txt ...
Dumping model to file cache C:\Users\yhhuang\AppData\Local\Temp\jieba.u0fd681cbe0a9699d869d3e014fd1e58b.cache
Loading model cost 0.625 seconds.
Prefix dict has been built succesfully.
'這次|去|日本|玩|從|小港|機場|出境'   

我用 0.39 版重新測試使用者定義詞庫結果就正確了 :  

>>> text='這次去日本玩從小港機場出境'   
>>> jieba.set_dictionary('dict/dict.txt')    
>>> jieba.load_userdict('dict/user_dict.txt')     
Building prefix dict from E:\python\test\dict\dict.txt ...
Loading model from cache C:\Users\yhhuang\AppData\Local\Temp\jieba.u0fd681cbe0a9699d869d3e014fd1e58b.cache
Loading model cost 0.515 seconds.
Prefix dict has been built succesfully.
>>> g=jieba.cut(text)    
>>> '|'.join(g)    
'這次|去|日本|玩|從|小港機場|出境'
>>> text='這次到台北一定要去搭捷運,還要去台北101看看'   
>>> jieba.set_dictionary('dict/dict.txt')    
>>> jieba.load_userdict('dict/user_dict.txt')  
Building prefix dict from E:\python\test\dict\dict.txt ...
Loading model from cache C:\Users\yhhuang\AppData\Local\Temp\jieba.u0fd681cbe0a9699d869d3e014fd1e58b.cache
Loading model cost 0.500 seconds.
Prefix dict has been built succesfully.
>>> g=jieba.cut(text)   
>>> '|'.join(g)   
'這次|到|台北|一定|要|去|搭|捷運|,|還要|去|台北101|看看'

結果 '小港機場' 與 '台北101' 都能正確切分出來, 可見自定義詞庫 userdict 會最優先被套用. 總之, 斷詞結果取決於 Jieba 版本與所使用的詞庫.


5. 去除停用詞 (stop word) :   

所謂停用詞 stop words 是指在自然語言處理任務中, 會影響統計與演算效率的字詞符號 (token), 例如標點符號, 或 of, on, at, in, which 等語法功能詞, 這些通常需要在語料錢處理過程中濾除. 停用字並沒有嚴格限定, 是根據 NLP 任務需要而自行定義的一張表. 參考 :


從上面的測試範例可知, Jieba 在斷詞時會把標點符號與空格等字符也切分成一個詞 (token), 這在統計字頻的任務中會影響計算結果, 所以需要將其列入停用字, 在用 Jieba 斷詞後進一步將停用字濾掉. 不過 Jieba 未內建濾除停用字的函式, 必須自行用檔案處理方式濾掉. 

首先編輯一個停用字表 stopword.txt, 一列一個停用字 (此處只是針對標點符號), 然後以 UTF-8 編碼格式存檔, 放在目前工作目錄或例如 dict 的子目錄下 :





然後用檔案處理方式讀取這個停用字表存入串列中, 將文本用 Jieba 斷詞後用迴圈迭代所得倒的 generator, 在每次迭代中檢查 generator 產出的斷詞是否在停用詞表中, 沒有的話就輸出, 這樣就能濾掉停用字了, 例如 : 

>>> with open('dict/stopwords.txt', encoding='utf-8-sig') as f:   
    stopwords=f.read().split('\n')     # 讀取整個檔案後以跳行字元拆分為串列
    
>>> stopwords     
['.', ',', ';', '?', '!', ':', "'", '"', ',', '。', '[', ']', '(', ')', '{', '}', '\\', '/', '|', '「', '」', ':']   
>>> text='這次到台北一定要去搭捷運,還要去台北101看看'      
>>> g=jieba.cut(text)    
>>> words=[]     # 儲存過濾後的 token
>>> for token in g:     # 迭代產生器
    if token not in stopwords:     # 如果不是停用字就存入串列
        words.append(token)    
        
>>> '|'.join(words)      
'這次|到|台北|一定|要|去|搭|捷運|還要|去|台北101|看看'

可見逼點符號已經被濾掉了. 但是在語音聊天機器人中似乎不用濾掉, 因為在 TTS 中合成語音時標點符號可作為停頓標誌. 

參考 :

https://github.com/ldkrsi/jieba-zh_TW (有七年前的繁體詞庫)

2023年8月20日 星期日

字集排列篩選專案完工

用了 10 天閒暇時間終於搞定明中的字集排列篩選專案, 昨晚測試無誤後連同原始碼傳送後結案, 這次我將上回用 tkinter 幫公司寫 GUI 獲得的經驗與程式模板用在新版字集篩選程式上, 並加寫了驗證介面, 效果與 command line 介面就是不一樣 : 






這回改寫將過濾規則增加到 29 個 (實際需驗證的有 28 個, rule1 不算), 大幅地削減了通過的字集數量 (約 3%). 與之前公司維運軟體在架構上最大的不同是, 此專案不論是隨機模式或窮舉模式都是一個此生無法執行完畢的程序 (一是無窮 while 迴圈, 另一個是 38 次方等級的排列), 所以我修改了彈出視窗進度條的作法, 這幾天要抽空做個心得摘要, 不然時間一久我會忘記. 

交卷的感覺真好 (當然必須是自信滿滿), 過去 3 個月透過四個軟體專案的磨練, 我這才算是真正掌握 tkinter 的用法, 以前寫 tkinter 測試筆記其實是紙上談兵, 有學識沒見識, 所以學程式語言最終必須做過實戰的專案才算學玩了. 但 tkinter 還是有少數元件還沒用過, 得空時要把筆記補完. 總之, 我之前真是小看 tkinter 了, 這內建的套件其實還是蠻好用的, 需要寫更大的專案時再來學 PyQT. 

2023年5月17日 星期三

Python 學習筆記 : 字集排列的過濾 (十七)

本篇為追加過濾規則的最後一個關於字數的限制, 本系列之前的筆記參考 : 


第十五道過濾的規則是 : "每12個字的排列中,這12字加起來的字母總數介於64~85個字母". 此規則不需要用到正規式, 用 Python 的 len() 函式就能搞定. 

第一個測試語料是字母總數少於 64 個字元的情況 (應排除) :

#  perm_test_15.py
#  字母總數須介於 64~85 個字母
words=('air', 'root', 'arch', 'try', 'close', 'amber', 'silent', 'beauty', 'trial', 'engage', 'more', 'after')
chars=''.join(words)
print(chars)
print(len(chars))
if len(chars) < 64 or len(chars) > 85:  # 須 64~85 個字母才入選                      
    print('the permutation is excluded')       
else:      
    print('the permutation is included')     

執行結果為排除 (正確) :

>>> %Run perm_test_15.py   
airrootarchtrycloseambersilentbeautytrialengagemoreafter
56
the permutation is excluded

第二個測試語料是字母總數等於 64 個字元的情況 (應入選) :

#  perm_test_15.py
#  字母總數須介於 64~85 個字母
words=('air', 'root', 'machine', 'jacket', 'close', 'country', 'silent', 'beauty', 'trial', 'engage', 'more', 'after')
chars=''.join(words)
print(chars)
print(len(chars))
if len(chars) < 64 or len(chars) > 85:  # 須 64~85 個字母才入選                      
    print('the permutation is excluded')       
else:      
    print('the permutation is included')    

執行結果為入選 (正確) :

>>> %Run perm_test_15.py   
airrootmachinejacketclosecountrysilentbeautytrialengagemoreafter
64
the permutation is included

第三個測試語料是字母總數等於 85 個字元的情況 (應入選) :

#  perm_test_15.py
#  字母總數須介於 64~85 個字母
words=('abandon', 'negative', 'machine', 'jacket', 'champion', 'country', 'balance', 'beauty', 'obscure', 'engage', 'shoulder', 'identify')
chars=''.join(words)
print(chars)
print(len(chars))
if len(chars) < 64 or len(chars) > 85:  # 須 64~85 個字母才入選                      
    print('the permutation is excluded')       
else:      
    print('the permutation is included')
   
執行結果為入選 (正確) :

>>> %Run perm_test_15.py   
abandonnegativemachinejacketchampioncountrybalancebeautyobscureengageshoulderidentify
85
the permutation is included

第四個測試語料是字母總數多於 85 個字元的情況 (應排除) :

#  perm_test_15.py
#  字母總數須介於 64~85 個字母
words=('abandon', 'negative', 'machine', 'jacket', 'champion', 'country', 'balance', 'kangaroo', 'obscure', 'engage', 'shoulder', 'identify')
chars=''.join(words)
print(chars)
print(len(chars))
if len(chars) < 64 or len(chars) > 85:  # 須 64~85 個字母才入選                      
    print('the permutation is excluded')       
else:      
    print('the permutation is included')
   
執行結果為排除 (正確) :

>>> %Run perm_test_15.py   
abandonnegativemachinejacketchampioncountrybalancekangarooobscureengageshoulderidentify
87
the permutation is excluded


2023-05-19 補充 :

規則改為這 12 個字用空格串起來後長度要在 64~88 個之間, 故上面的測試程式改成如下 : 

chars=' '.join(words)     # 以空格串接排列中的 12 個字母
print(chars)
print(len(chars))
if len(chars) < 64 or len(chars) > 88:  # 含空格長度須 64~88 個字母才入選                      
    print('the permutation is excluded')       
else:      
    print('the permutation is included')

2023年5月13日 星期六

Python 學習筆記 : 字集排列的過濾 (十)

本篇為追加過濾兩個字尾母音規則的第二個測試, 本系列之前的筆記參考 : 


第八道過濾的規則是 : "每12字排列中,a, i, o, u 四個母音字母(變成一群),以這四個母音字母「結尾」的字有0~1個". 此規則的正規式如下 :

ptn=re.compile(r'\b\w*[aiou]\b')      # 以 a, i, o, u 四個母音字母結尾的字     

第一個測試語料是沒有以 a, i, o, u 四個母音字母結尾的字的情況 (應入選) :

>>> import re 
>>> words=('please', 'age', 'pole', 'check', 'close', 'open', 'silent', 'apology', 'trial', 'engage', 'more', 'license')   
>>> ptn=re.compile(r'\b\w*[aiou]\b')      # a, i, o, u 四個母音字母結尾的字
>>> end_aiou=[True if re.match(ptn, w) else False for w in words]   
>>> end_aiou    
[False, False, False, False, False, False, False, False, False, False, False, False]
>>> if end_aiou.count(True) > 1:    # True 個數須不超過 1 個 才入選                      
    print('the permutation is excluded')       
else:      
    print('the permutation is included') 
    
the permutation is included  

可見含有 0 個以 a, i, o, u 四個母音字母結尾的字被納入. 

其次測試含有 1 個以 a, i, o, u 四個母音字母結尾的語料 : 

>>> words=('please', 'ago', 'pole', 'check', 'close', 'open', 'silent', 'apology', 'trial', 'engage', 'more', 'license')   
>>> ptn=re.compile(r'\b\w*[aiou]\b')      # a, i, o, u 四個母音字母結尾的字
>>> end_aiou=[True if re.match(ptn, w) else False for w in words]   
>>> end_aiou    
[False, True, False, False, False, False, False, False, False, False, False, False]
>>> if end_aiou.count(True) > 1:    # True 個數須不超過 1 個 才入選                      
    print('the permutation is excluded')       
else:      
    print('the permutation is included') 
    
the permutation is included 

可見含有 1 個以 a, i, o, u 四個母音字母結尾的字也被納入. 

接下來測試含有 2 個以 a, i, o, u 四個母音字母結尾的語料 : 

>>> words=('please', 'ago', 'pole', 'check', 'close', 'open', 'silent', 'apology', 'trial', 'engage', 'more', 'alumni')   
>>> ptn=re.compile(r'\b\w*[aiou]\b')      # a, i, o, u 四個母音字母結尾的字
>>> end_aiou=[True if re.match(ptn, w) else False for w in words]   
>>> end_aiou    
[False, True, False, False, False, False, False, False, False, False, False, True]
>>> if end_aiou.count(True) > 1:    # True 個數須不超過 1 個 才入選                      
    print('the permutation is excluded')       
else:      
    print('the permutation is included') 
    
the permutation is excluded    

以上測試可以寫成如下程式於命令列執行 :

#  perm_test_8.py
#  以 a, i, o, u 四個母音字母結尾的字不超過 1 個
import re

words=('please', 'age', 'pole', 'check', 'close', 'open', 'silent', 'apology', 'trial', 'engage', 'more', 'license')
ptn=re.compile(r'\b\w*[aiou]\b')      # a, i, o, u 四個母音字母結尾的字
end_aiou=[True if re.match(ptn, w) else False for w in words]
print(end_aiou)
print(end_aiou.count(True))
if end_aiou.count(True) > 1:    # True 個數須不超過 1 個 才入選                      
    print('the permutation is excluded')       
else:      
    print('the permutation is included')     

2023年5月8日 星期一

Python 學習筆記 : 字集排列的過濾 (八)

這兩天重新審視了字集過濾的規則, 修正了規則 5 以免規則 6 形同具文, 為了驗證這些規則的正確性, 使用有限的 14 個排列來測試, 以下是測試紀錄. 本系列之前的文章參考 :

本系列之前的文章參考 : 

測試用的 14 個排列語料如下 (檔名 permutation_test_in.csv) : 

ability,give,about,echo,ugly,idea,fake,grow,close,brick,merge,nature
ability,give,about,echo,ugly,idea,fake,grow,close,obey,merge,nature
sugar,frown,pole,million,hair,close,silent,apology,engage,dish,harvest,license
abandon,ability,bag,balance,key,scan,select,shock,radar,radio,ugly,umbrella
abandon,baby,cabbage,dad,eager,fabric,gadget,habit,ice,jacket,kangaroo,lab
sugar,frown,pole,million,hair,close,silent,apology,engage,dish,harvest,license
cinnamon,merge,more,memory,grow,anchor,auto,major,push,desk,mass,swallow
knife,brick,quote,interest,kind,jealous,afraid,jar,job,much,hat,umbrella
cost,brick,gate,interest,depart,jealous,afraid,diamond,merit,much,hat,umbrella
cost,brick,gate,interest,depart,face,afraid,diamond,merit,much,hat,umbrella
give,fade,pole,million,gift,close,silent,apology,engage,face,ginger,license
give,fade,pole,million,date,close,silent,apology,engage,face,ginger,license
cheap,fade,pole,check,cheese,close,silent,apology,engage,this,thing,write
cheap,fade,pole,mail,cheese,close,silent,apology,engage,this,thing,write

測試程式如下 (檔名 words_permutation_test.py) :

import re
import time

start=time.time()
# 資料前處理 : 讀取 CSV 檔轉成串列
with open('permutation_test_in.csv', 'r', encoding='utf8') as fr:
    with open('permutation_test_out.csv', 'w', encoding='utf8') as fw:
        lines=fr.readlines()
        i=1 # 排列計數器
        for line in lines:
            words=line.replace('\n', '')
            words=words.split(',')
            print(i, end=':')  # 印出排列數
            print(words)       # 印出排列 (12 字的 tuple)
            i=i+1  # 排列數增量 1
            #rule1 : 母音字母 (a, e, i, o, u) 開頭的字最多出現 5 次
            ptn=re.compile('^[aeiou].*') # 母音字母開始
            first=[w[0] for w in words if re.match(ptn, w)]
            if len(first) > 5: # 母音開頭字超過 5 次
                print(" : rule 1 excluded")
                continue
            #rule2 : 相同字母開頭的字最少 1 組, 最多 4 組, 母音與子音可同時併計
            first=[w[0] for w in words]  # 找出各字之開頭字母串列
            first_diff=list(set(first))  # 找出不同開頭字母串列
            fc=[first.count(fd) for fd in first_diff]
            fc1=[True if c > 1 else False for c in fc]
            if fc1.count(True) < 2 or fc1.count(True) > 4: 
                print(" : rule 2 excluded")
                continue
            # rule3: 相同字母開頭的字最少 2 個, 最多 4 個
            first=[w[0] for w in words]
            first_diff=list(set(first))
            fc=[first.count(fd) for fd in first_diff]
            fc1=[True if first.count(fd) > 1 else False for fd in first_diff]
            fc2=[True if first.count(fd) > 4 else False for fd in first_diff]
            if fc1.count(True) < 1 or fc2.count(True) > 0: 
                print(" : rule 3 excluded")
                continue  
            # rule4: 以 j, k, q, y, z 開頭的字最多只能有 1 個
            ptn=re.compile('^[jkqyz].*')
            first=[w[0] for w in words  if re.match(ptn, w)]
            first_diff=list(set(first))
            fc=[first.count(fd) for fd in first_diff]
            if sum(fc) >= 2:         
                print(" : rule 4 excluded")
                continue
            # rule5: 前 2 個字母開頭相同的字最多只能出現 2 次 (sh, ch, th, wr, un 例外)
            ptn='^(?!(sh|ch|th|wr|un))[a-zA-Z]*'
            first2=[w[0:2] for w in words if re.match(ptn, w)]
            first2_diff=list(set(first2))
            f2c=[True if first2.count(fd) > 2 else False for fd in first2_diff]
            if f2c.count(True) > 0: # 
                print(" : rule 5 excluded")
                continue
            # rule6: 以 sh, ch, th, wr, un 開頭的字, 前三個字母相同者不能超過 2 個
            ptn=re.compile('^(sh|ch|th|wr|un)[a-zA-Z]*')
            first3=[w[0:3] for w in words if re.match(ptn, w)]
            first3_diff=list(set(first3))
            f3c=[True if first3.count(fd) > 2 else False for fd in first3_diff]
            if f3c.count(True) > 0: # 
                print(" : rule 6 excluded")
                continue
            # 通過上面 6 個過濾 : 存入檔案
            str=' '.join(words)
            print(str)
            fw.write(str + '\n')        
end=time.time()
print(f'time elapsed : {end-start}')

執行結果如下 :

>>> %Run words_permutation_test.py   
1:['ability', 'give', 'about', 'echo', 'ugly', 'idea', 'fake', 'grow', 'close', 'brick', 'merge', 'nature']
ability give about echo ugly idea fake grow close brick merge nature
2:['ability', 'give', 'about', 'echo', 'ugly', 'idea', 'fake', 'grow', 'close', 'obey', 'merge', 'nature']
 : rule 1 excluded   
3:['sugar', 'frown', 'pole', 'million', 'hair', 'close', 'silent', 'apology', 'engage', 'dish', 'harvest', 'license']
sugar frown pole million hair close silent apology engage dish harvest license
4:['abandon', 'ability', 'bag', 'balance', 'key', 'scan', 'select', 'shock', 'radar', 'radio', 'ugly', 'umbrella']
 : rule 2 excluded   
5:['abandon', 'baby', 'cabbage', 'dad', 'eager', 'fabric', 'gadget', 'habit', 'ice', 'jacket', 'kangaroo', 'lab']
 : rule 2 excluded   
6:['sugar', 'frown', 'pole', 'million', 'hair', 'close', 'silent', 'apology', 'engage', 'dish', 'harvest', 'license']
sugar frown pole million hair close silent apology engage dish harvest license
7:['cinnamon', 'merge', 'more', 'memory', 'grow', 'anchor', 'auto', 'major', 'push', 'desk', 'mass', 'swallow']
 : rule 3 excluded    
8:['knife', 'brick', 'quote', 'interest', 'kind', 'jealous', 'afraid', 'jar', 'job', 'much', 'hat', 'umbrella']
 : rule 4 excluded   
9:['cost', 'brick', 'gate', 'interest', 'depart', 'jealous', 'afraid', 'diamond', 'merit', 'much', 'hat', 'umbrella']
cost brick gate interest depart jealous afraid diamond merit much hat umbrella
10:['cost', 'brick', 'gate', 'interest', 'depart', 'face', 'afraid', 'diamond', 'merit', 'much', 'hat', 'umbrella']
cost brick gate interest depart face afraid diamond merit much hat umbrella
11:['give', 'fade', 'pole', 'million', 'gift', 'close', 'silent', 'apology', 'engage', 'face', 'ginger', 'license']
 : rule 5 excluded   
12:['give', 'fade', 'pole', 'million', 'date', 'close', 'silent', 'apology', 'engage', 'face', 'ginger', 'license']
give fade pole million date close silent apology engage face ginger license
13:['cheap', 'fade', 'pole', 'check', 'cheese', 'close', 'silent', 'apology', 'engage', 'this', 'thing', 'write']
 : rule 6 excluded
14:['cheap', 'fade', 'pole', 'mail', 'cheese', 'close', 'silent', 'apology', 'engage', 'this', 'thing', 'write']
cheap fade pole mail cheese close silent apology engage this thing write
time elapsed : 0.11959671974182129

可見規則 1~6 都有正確過濾 (7 個被濾掉), 輸出檔內容如下 :

ability give about echo ugly idea fake grow close brick merge nature
sugar frown pole million hair close silent apology engage dish harvest license
sugar frown pole million hair close silent apology engage dish harvest license
cost brick gate interest depart jealous afraid diamond merit much hat umbrella
cost brick gate interest depart face afraid diamond merit much hat umbrella
give fade pole million date close silent apology engage face ginger license
cheap fade pole mail cheese close silent apology engage this thing write

14 個排列有 7 個被規則 1~6 過濾掉, 剩下 7 個. 

2021年12月3日 星期五

Python 學習筆記 : 中文繁簡轉換套件 HanziConv

今天在 "Python 技術者實踐! (旗標, 2018)" 這本書的第 15 章讀到 HanziConv 這個中文簡繁體套件, 在中文語意分析與自然語言處理時很好用, 於是便馬上進行測試. HanziConv 為第三方套件, 需先用 pip 或 pip3 install 指令安裝後才能使用 :

pip install hanziconv    

C:\Users\User>pip install hanziconv    
Collecting hanziconv
  Downloading hanziconv-0.3.2.tar.gz (276 kB)
     Preparing metadata (setup.py) ... done
Building wheels for collected packages: hanziconv
  Building wheel for hanziconv (setup.py) ... done
  Created wheel for hanziconv: filename=hanziconv-0.3.2-py2.py3-none-any.whl size=23214 sha256=2e97cb6420e5c4f9c2096e34c338174d9ea8560905594bc74df25ea54cfe9500
  Stored in directory: c:\users\user\appdata\local\pip\cache\wheels\bf\e3\22\7bf50146a3ee95d1fdcbfabc44a1fe15b6e2ab7348ab7337bf
Successfully built hanziconv
Installing collected packages: hanziconv
Successfully installed hanziconv-0.3.2

安裝完成即可匯入 hanziconv , 然後用 dir() 函式來觀察此套件之內容 : 

>>> import hanziconv    
>>> dir(hanziconv)   
['HanziConv', '__all__', '__author__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'absolute_import', 'charmap', 'hanziconv', 'unicode_literals']

可以利用求值函式 eval() 取得成員的參考, 再用迴圈來檢視成員之資料類型 : 

>>> members=dir(hanziconv)     
>>> for mbr in members:                   # 走訪 hanziconv 模組成員
    obj=eval('hanziconv.' + mbr)         # 用 eval() 求值取得 hanziconv.成員之參考
    if not mbr.startswith('_'):               # 走訪所有不是 "_" 開頭的成員
        print(mbr, type(obj))                  # 輸出成員之類型
        
HanziConv <class 'type'>
absolute_import <class '__future__._Feature'>
charmap <class 'module'>
hanziconv <class 'module'>
unicode_literals <class '__future__._Feature'>

其中 HanziConv 就是這個套件定義的類別, 用 dir() 檢視其成員 : 

>>> dir(hanziconv.HanziConv)     
['_HanziConv__convert', '_HanziConv__simplified_charmap', '_HanziConv__traditional_charmap', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'same', 'toSimplified', 'toTraditional']

其中 toSimplified() 與 toTraditional() 就是用來做繁簡轉換的方法. 所以實際應用時不會用 import hanziconv 套件, 而是從套件 hanziconv 中指定匯入 HanziConv() 類別, 然後直接呼叫 toSimplified() 與 toTraditional() 這兩個方法 :

from hanziconv import HanziConv

>>> from hanziconv import HanziConv     
>>> dir(HanziConv)     
['_HanziConv__convert', '_HanziConv__simplified_charmap', '_HanziConv__traditional_charmap', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'same', 'toSimplified', 'toTraditional']

顧名思義, toSimplified() 是傳入繁體字串傳回簡體字串, 而 toTranditional() 則是傳入簡體字串傳回繁體字串, 可用 help() 查看其使用說明 : 

>>> help(HanziConv.toSimplified)      
Help on method toSimplified in module hanziconv.hanziconv:

toSimplified(text) method of builtins.type instance
    Convert `text` to simplified character string.  Assuming text is
    traditional character string
    
    :param text:  text to convert
    :returns:     converted UTF-8 characters
    
    >>> from hanziconv import HanziConv
    >>> print(HanziConv.toSimplified('繁簡轉換器'))
    繁简转换器

>>> help(HanziConv.toTraditional)   
Help on method toTraditional in module hanziconv.hanziconv:

toTraditional(text) method of builtins.type instance
    Convert `text` to traditional character string.  Assuming text is
    simplified character string
    
    :param text:  text to convert
    :returns:     converted UTF-8 characters
    
    >>> from hanziconv import HanziConv
    >>> print(HanziConv.toTraditional('繁简转换器'))
    繁簡轉換器

簡轉繁例如 : 

>>> HanziConv.toTraditional('游戏')    
'遊戲'
>>> HanziConv.toTraditional('软件')     
'軟件'
>>> HanziConv.toTraditional('内存')    
'內存'
>>> HanziConv.toTraditional('鼠标')   
'鼠標'
>>> HanziConv.toTraditional('打印机')    
'打印機'
>>> HanziConv.toTraditional('硬盘')    
'硬盤'

可見此套件只是逐字將簡體字轉成繁體字, 並沒有詞彙轉換功能 (例如將 '内存' 轉成 '記憶體', '打印机' 變 '印表機' 等).

繁轉簡例如 :

>>> HanziConv.toSimplified('中華民國萬歲')     
'中华民国万岁'
>>> HanziConv.toSimplified('蔣總統萬歲')   
'蒋总统万岁'
>>> HanziConv.toSimplified('智商高達 157 的阿北')     
'智商高达 157 的阿北'
>>> HanziConv.toSimplified('硬盤')    
'硬盘'
>>> HanziConv.toSimplified('硬碟')    
'硬碟'
>>> HanziConv.toSimplified('雷射')    
'雷射'

可見繁簡轉也是逐字轉換, 雷射不會得到激光. 關於兩岸用語差異參考 :


2021年10月19日 星期二

線上語料庫分析服務 Sketch Engine

語料庫是計算語言學的基礎, 除了可在本機使用 NLTK 或 SpaCy 等 Python 套件做語料分析外 (這需要基本的 Python 程式基礎), 現在也可以線上用瀏覽器來做語料庫文本分析, 例如 Sketch Engine, 其操作介面簡單, 完全不需要寫程式 : 

按官網首頁左上角的 "Sign up" 鈕註冊 30 天免費試用帳號 : 




按 "Free 30 Days Trial" 鈕 (右邊的 Individual user account 雖然有教育用途永久免費優惠, 但那只限歐盟區域內的教育機構) : 




填寫帳戶名稱與 e-mail 等資訊, 最底下三項需勾選 : 





按 "Sign Up" 鈕即完成註冊, 然後去收信, 裡面有登入密碼, 要記下來, 因為好像沒有提供更改的功能, 我的 30 天使用密碼是 xxtxXDPB3CWE : 




Sketch Engine 的收費方案如下 :




然後到官網首頁按 Login 鈕登入, 成功後會顯示 Dashboard (儀錶板) : 




可見 Sketch Engine 就是把類似 NLTK 的功能放在雲端, 讓使用者能透過瀏覽器很方便地進行基於語料庫的線上文本分析. 

按右上角的帳戶頭像, 點選 "My Account" 會顯示帳戶資訊 :





此處 Corpus storage used 是指已用掉的語料庫大小比率, Sketch Engine 可讓使用者上傳最多 100 萬字的語料庫.  

網站使用方法參考官網簡介影片 : 





另外 bilibili 也有一個中國人製作的 Sketch Engine 中文使用教學 : 


首先按左邊框第二個按鈕, 這樣右邊就會顯示語言選項, 可以點選語料庫之語言例如 English :




這樣右邊的 "Recently Used Corpus" 欄就會列出所選擇之語料庫 (此處為 EnTenTen 2020) : 




按 "Corpus Info" 鈕會顯示此語料庫的統計資訊, 例如總字數等 :




接下來示範如何做最常用的共詞句分析 (concordance, 即以指定詞為中心排列的例句). 先按左邊框上面第一個按鈕回到 Dashboard, 然後按 "Concordance" 鈕, 然後輸入欲搜尋的字詞, 按 Search 鈕即可 :





可見結果會以所搜尋的 make 為中心列出例句, 這就是共詞句 (concordance). 

除了內建的語料庫外, Sketch Engine 也支援上傳自建的語料庫. 以下示範如何將 NLTK 內建的台灣中央研究院中文語料庫 sinica_treebank 上傳至 Sketch Engine. 

首先要從 NLTK 中把 sinica_treebank 語料庫複製出來, NLTK 的語料庫位置可透過 nltk.data.path 指令查詢, 它會傳回一組可能路徑之串列, 例如 : 

>>> import nltk    
>>> nltk.data.path   
['C:\\Users\\User/nltk_data', 'C:\\Python37\\nltk_data', 'C:\\Python37\\share\\nltk_data', 'C:\\Python37\\lib\\nltk_data', 'C:\\Users\\User\\AppData\\Roaming\\nltk_data', 'C:\\nltk_data', 'D:\\nltk_data', 'E:\\nltk_data']

nltk_data 資料夾的實際位置要一個個去檢視 (因 Python 安裝方式與 Windows 版本而異), 例如我的電腦是在 User 的 AppData 下, 這是預設隱藏路徑, 必須於檢視中開啟顯示隱藏檔案才找得到 : 




找到 nltk_data 資料夾後, 將 parsed 這個語料庫檔案 (約 2.4MB) 複製到文件資料夾下並幫它加上副檔名 .txt, 因為 Sketch Engine 不接受無副檔名的語料庫上傳. 然後到 Sketch Engine 的 Dashboard 頁面按右上角的 "New Corpus" 鈕 : 




填寫名稱, 類型, 語言等欄位後按底下的 "Next" 鍵 : 




按右邊的 "I have my own texts" 鈕後點選複製的 sinica_treebank 語料庫檔 parsed.txt 上傳 : 






上傳完畢後顯示 -397777 表示已占用接近 40% 儲存空間, 按 "Next" 鈕 : 




按 "Compile" 鈕進行編譯 (需要一點時間, 看檔案大小) : 




編譯完按 "Corpus Dashboard" 鈕就可看到右邊多出了一個 sinica_treebank 語料庫了 :




按 "Corpus Info" 顯示此語料庫的統計資訊, 例如有 92 萬多個 token 等訊息 :  




按 "Manage Corpus" 鈕會出現語料庫的管理頁面, 因為是自建的, 故管理功能全部都打開了 : 




回 Dashboard 按 Concordance 來查詢共詞句, 輸入 "我" 按 Search 鈕 : 





因為上傳的是剖析過的語料庫, 所以查詢結果中含有許多語法標注 (例如 POS 等), 如果用 NLTK 查詢會列出原始文本 :

>>> import nltk   
>>> from nltk.corpus import sinica_treebank     
>>> sinica_text=nltk.Text(sinica_treebank.words())     
>>> sinica_text.concordance('我')      

Displaying 25 of 724 matches:
一 友情 嘉珍 和 我 住在 同一條 巷子 我們 是 鄰居 也 是 同班 同學 我們 常常 一起 上
居 也 是 同班 同學 我們 常常 一起 上學 一起 回家 有一天 上學 時 我 到 她 家 等候 按 了 門鈴 卻 沒有 任何 動靜 正當 我 想 離開 時
天 上學 時 我 到 她 家 等候 按 了 門鈴 卻 沒有 任何 動靜 正當 我 想 離開 時 門 內 突然 傳來 急促 的 腳步聲 嘉珍 打開 了 門 大聲
 突然 傳來 急促 的 腳步聲 嘉珍 打開 了 門 大聲 的 叫 著 快 點 我 媽媽 暈倒 了 嘉珍 抓起 我 的 手 急忙 往 屋 裡 跑 進入 房間 看
嘉珍 打開 了 門 大聲 的 叫 著 快 點 我 媽媽 暈倒 了 嘉珍 抓起 我 的 手 急忙 往 屋 裡 跑 進入 房間 看到 她 的 媽媽 趴 在 地 上
 她 的 媽媽 趴 在 地 上 臉色 蒼白 得 像 紙 一樣 這種 情景 把 我 嚇壞 了 怎麼辦 嘉珍 不停 的 哭泣 聲音 有些 顫抖 我 的 腦海 中 
這種 情景 把 我 嚇壞 了 怎麼辦 嘉珍 不停 的 哭泣 聲音 有些 顫抖 我 的 腦海 中 頓時 一片 空白 不 曉得 怎麼辦 才 好 過 了 一會兒 我
我 的 腦海 中 頓時 一片 空白 不 曉得 怎麼辦 才 好 過 了 一會兒 我 才 問 她 你 爸爸 呢 他 出差 了 嘉珍 擦 著 眼淚 我 握住 她 的
 了 一會兒 我 才 問 她 你 爸爸 呢 他 出差 了 嘉珍 擦 著 眼淚 我 握住 她 的 雙手 她 的 手 又 冰 又 冷 這時 有個 念頭 突然 閃過
握住 她 的 雙手 她 的 手 又 冰 又 冷 這時 有個 念頭 突然 閃過 我 的 眼前 我 幫 她 撥 了 一一九 請 救護車 來 而且 拍拍 她 的 背
雙手 她 的 手 又 冰 又 冷 這時 有個 念頭 突然 閃過 我 的 眼前 我 幫 她 撥 了 一一九 請 救護車 來 而且 拍拍 她 的 背 安慰 她 不
撥 了 一一九 請 救護車 來 而且 拍拍 她 的 背 安慰 她 不要 著急 我 會 陪 你 的 不久 救護車 停 在 她 家 門口 醫護 人員 很 快 的 
 醫護 人員 很 快 的 將 她 的 媽媽 抬上 救護車 嘉珍 上車 前 對 我 說 謝謝 你 的 幫忙 我 握 著 她 的 手 說 不用 謝 我 因為 我們
 她 的 媽媽 抬上 救護車 嘉珍 上車 前 對 我 說 謝謝 你 的 幫忙 我 握 著 她 的 手 說 不用 謝 我 因為 我們 是 好朋友 二 無私 的 
車 前 對 我 說 謝謝 你 的 幫忙 我 握 著 她 的 手 說 不用 謝 我 因為 我們 是 好朋友 二 無私 的 愛 愛 像 火紅 的 太陽 散發 光 
正 為了 缺乏 旅費 而 發愁 的 時候 員外 來 找 他 並且 對 他 說 我 看 你 悶悶不樂 是不是 進京 趕考 的 旅費 不 夠 這裡 有 一些 銀子
物 和 銀子 向 員外 道謝 時 員外 握 著 他 的 手 說 你 不用 還 我 了 我 只是 盡 一份 心力 而已 以後 有 機會 希望 你 也 能夠 幫助
銀子 向 員外 道謝 時 員外 握 著 他 的 手 說 你 不用 還 我 了 我 只是 盡 一份 心力 而已 以後 有 機會 希望 你 也 能夠 幫助 別人 
身 趕考 吧 書生 用 顫抖 的 手 接過 銀子 含 著 眼淚 說 謝謝 您 我 不 知道 要 怎麼 來 報答 您 江 巡撫 說 你 不必 謝 我 也 不必 
 謝謝 您 我 不 知道 要 怎麼 來 報答 您 江 巡撫 說 你 不必 謝 我 也 不必 回報 我 四 快樂 的 閱讀課 上 國語課 的 時候 老師 叫 我
知道 要 怎麼 來 報答 您 江 巡撫 說 你 不必 謝 我 也 不必 回報 我 四 快樂 的 閱讀課 上 國語課 的 時候 老師 叫 我們 這一組 做 讀書
 閱讀課 上 國語課 的 時候 老師 叫 我們 這一組 做 讀書 心得 報告 我 第一個 站起來 發言 我們 閱讀 的 好 書 是 小 王子 這 是 法國人 
 小 王子 告訴 他 在 星球 旅行 的 經過 兩 人 成為 知心 的 朋友 我 說完 之後 組長 站起來 補充 說 小 王子 是 一個 可愛 的 人 他 在
星 上 每天 照顧 會 說話 的 玫瑰花 、 清理 火山灰 而且 欣賞 落日 我 喜歡 這種 自由自在 的 生活 明珠 說 這本 書 最 精采 的 部分 遇到
 的 商人 等 小 王子 不 喜歡 自大 的 人 也 不 喜歡 虛偽 的 人 我 覺得 小 王子 很 正直 最後 阿平 說 這本 書 中 有 許多 值得 思考
>>> 

我將 sinica_treebank 語料庫放在 GitHub 方便下載進行測試 :


也可以上傳 sinica_treebank 的原始語料庫, 即 nltk_data 資料夾下的 raw 檔, 將其複製到文件夾後加上 .txt 副檔名, 然後新增一個語料庫取名為 sinica_treebank2, 再將 raw.txt上傳 : 





可見未剖析的原始檔所佔儲存空間少很多, 只有 10MB 左右. 同樣搜尋 "我" 做共詞句分析 (concordance) 結果就不會出現剖析用後的語法標記了 :




參考 :