TM04 Chinese word segmentation

1. 中文斷詞基礎流程

1.1 載入資料

with open("data/blessing.txt", encoding="utf8") as fin:
    text = fin.read()
print(type(fin))
print(type(text))
print("Number of characters: %d" % len(text))
print(text[:int(len(text)/10)])

1.2 斷句

在斷句前先清除每個段落前的首行縮排(兩個全形空白)。並且取代換行符號(要注意並不是每次都先取代換行符號,比方說你可以比較一下這章節所用的文本和PTT文章在斷句上的差異)。

text_refined = text.replace(" ", "").replace("\n", "")

sentences = text_refined.split("。")
print("Number of sentences: %d" % len(sentences))
print(sentences[:20])

1.3 斷詞

# !pip install jieba
import jieba
print(sentences[0])
print(jieba.cut(sentences[0]))
print(list(jieba.cut(sentences[0])))

2. 繁簡斷詞效果比較

jieba套件在繁簡兩種字體的斷詞效果差不少,必須要謹慎考量。但若要用結巴,比較好的方法應該是先轉為簡體,然後用簡體斷詞的位置來斷開繁體句子。

2.1 繁簡互換

繁轉簡,問題不大,但簡又轉回繁,幾乎跟原本的繁體字意義都不同了。

# !pip install hanziconv
from hanziconv import HanziConv

sim = HanziConv.toSimplified('臺北市長柯文哲')
print(sim)

tra = HanziConv.toTraditional(sim)
print(tra)

台北市长柯文哲 颱北市長柯文哲

2.2 繁簡斷詞結果比較

tra = '台北市長柯文哲'
sim = HanziConv.toSimplified(tra)
print(" / ".join(jieba.cut(tra)))
print(" / ".join(jieba.cut(sim)))

台北市 / 長 / 柯文 / 哲 # 直接斷繁體 台北 / 市长 / 柯文 / 哲 # 直接斷簡體

sim = HanziConv.toSimplified(sentences[0])

sim_cutted = list(jieba.cut(sim))
sim_cutted_to_tra = [HanziConv.toTraditional(term) for term in sim_cutted]

print(" / ".join(sim_cutted_to_tra))
print(" / ".join(jieba.cut(sim)))

舊曆 / 的 / 年底 / 畢竟 / 最像 / 年底 / , / 村鎮 / 上 / 不必 / 說 / , / 就 / 在 / 天空 / 中 / 也 / 顯齣 / 將 / 到 / 新年 / 的 / 氣象 / 來 旧历 / 的 / 年底 / 毕竟 / 最像 / 年底 / , / 村镇 / 上 / 不必 / 说 / , / 就 / 在 / 天空 / 中 / 也 / 显出 / 将 / 到 / 新年 / 的 / 气象 / 来

2.3 用簡體斷詞位置切割繁體字詞

方法一(推薦)

def restore(text, toks):
    results = []
    offset = 0
    for tok in toks:
        results.append(text[offset:offset + len(tok)])
        offset += len(tok)
    return results

print(restore(tra, list(jieba.cut(HanziConv.toSimplified(tra)))))

['國民黨', '高雄市', '長', '候選人', '韓國', '瑜', '今天', '晚上', '開', '直播', ',', '針對', '最近', '引起爭議', '的', '一些', '事', '說明', ',', '強調', '他', '並', '沒有', '消費', '愛心', '菜販', '陳樹菊', ',', '全力支持', '「', '館長', '」', '做', '公益', ',', '所謂', '高雄', '三鳳宮', '靈籤', '可能', '是', '「', '新型', '態', '選舉', '詐騙', '」', ';', '至於', '為', '強調', '招商引資', '說出', '「', '陪', '睡', '」', '遭批', ',', '他', '以後', '不會', '說', '這麼', '有', '張力', '的', '用語', ',', '畢竟', '是', '市長', '候選人', '。']

方法一(測試二)

# Segmenting traditional Chinese directly
sample = "國民黨高雄市長候選人韓國瑜今天晚上開直播,針對最近引起爭議的一些事說明,強調他並沒有消費愛心菜販陳樹菊,全力支持「館長」做公益,所謂高雄三鳳宮靈籤可能是「新型態選舉詐騙」;至於為強調招商引資說出「陪睡」遭批,他以後不會說這麼有張力的用語,畢竟是市長候選人。"
print(" / ".join(jieba.cut(sample)))
# Segmenting traditional Chinese by cutted simplified index
print("/ ".join(restore(sample, list(jieba.cut(HanziConv.toSimplified(sample))))))

國民黨 / 高雄市 / 長 / 候選人 / 韓國瑜 / 今天 / 晚上 / 開 / 直播 / , / 針對 / 最近 / 引起 / 爭議 / 的 / 一些 / 事 / 說明 / , / 強調 / 他 / 並沒有 / 消費 / 愛心 / 菜販 / 陳樹菊 / , / 全力支持 / 「 / 館長 / 」 / 做 / 公益 / , / 所謂 / 高雄 / 三鳳 / 宮靈 / 籤 / 可能 / 是 / 「 / 新型 / 態選舉 / 詐騙 / 」 / ; / 至於 / 為 / 強調 / 招商 / 引資 / 說 / 出 / 「 / 陪 / 睡 / 」 / 遭批 / , / 他 / 以後不會 / 說 / 這麼 / 有 / 張力 / 的 / 用語 / , / 畢 / 竟是 / 市長 / 候選人 / 。 國民黨/ 高雄市/ 長/ 候選人/ 韓國/ 瑜/ 今天/ 晚上/ 開/ 直播/ ,/ 針對/ 最近/ 引起爭議/ 的/ 一些/ 事/ 說明/ ,/ 強調/ 他/ 並/ 沒有/ 消費/ 愛心/ 菜販/ 陳樹菊/ ,/ 全力支持/ 「/ 館長/ 」/ 做/ 公益/ ,/ 所謂/ 高雄/ 三鳳宮/ 靈籤/ 可能/ 是/ 「/ 新型/ 態/ 選舉/ 詐騙/ 」/ ;/ 至於/ 為/ 強調/ 招商引資/ 說出/ 「/ 陪/ 睡/ 」/ 遭批/ ,/ 他/ 以後/ 不會/ 說/ 這麼/ 有/ 張力/ 的/ 用語/ ,/ 畢竟/ 是/ 市長/ 候選人/ 。

方法二

import numpy
print(sentences[0])

def tra_cutted_by_sim(tra):
    sim_cutted = jieba.cut(HanziConv.toSimplified(tra))
    term_len = [len(w) for w in list(sim_cutted)]
    term_index = [0] + list(numpy.cumsum(term_len)[:-1])
    term_dict = {k:v for k, v, in zip(term_index, term_len)}
    tra_cutted  = [tra[k:k+v] for k, v in term_dict.items()]
    return tra_cutted

print(tra_cutted_by_sim(sentences[0]))

sim_cutted = jieba.cut(HanziConv.toSimplified(sentences[0]))
sim_cutted_to_tra = [HanziConv.toTraditional(term) for term in sim_cutted]

print(sim_cutted_to_tra)

舊曆的年底畢竟最像年底,村鎮上不必說,就在天空中也顯出將到新年的氣象來 ['舊曆', '的', '年底', '畢竟', '最像', '年底', ',', '村鎮', '上', '不必', '說', ',', '就', '在', '天空', '中', '也', '顯出', '將', '到', '新年', '的', '氣象', '來'] ['舊曆', '的', '年底', '畢竟', '最像', '年底', ',', '村鎮', '上', '不必', '說', ',', '就', '在', '天空', '中', '也', '顯齣', '將', '到', '新年', '的', '氣象', '來']

(Option) 斷詞效果的測試工具

tra_cutted = list(jieba.cut(sample))
cut_by_sim = tra_cutted_by_sim(sample)
i, j  = 0, 0
print("Cutted tra directly:", tra_cutted)
print("Cutted by sim:", cut_by_sim)
print(len(tra_cutted), len(cut_by_sim))


while i < len(tra_cutted) and j < len(cut_by_sim):
        if tra_cutted[i] == cut_by_sim[j]:
            print("[%d]%s\t[%d]%s" % (i, tra_cutted[i], j, cut_by_sim[j]))
            i += 1
            j += 1            
        else:
            ei, ej = 1, 1
            listi, listj = [], []
            listi.append(tra_cutted[i])
            listj.append(cut_by_sim[j])
            while "".join(listi) != "".join(listj):
                if(len("".join(listi)) < len("".join(listj))):
                    listi.append(tra_cutted[i+ei])
                    ei += 1
                else:
                    listj.append(cut_by_sim[j+ej])
                    ej += 1
            print("\t[%d_%d]%s\t[%d_%d]%s" % (i, ei, listi, j, ej, listj))
            i += ei
            j += ej

3. POS、停用詞、標點符號去除

jieba使用者自定義詞典

jieba.load_userdict("data/userdict.txt")
print(" / ".join(jieba.cut(sentences[0])))

舊曆 / 的 / 年底 / 畢竟 / 最 / 像 / 年底 / , / 村鎮 / 上 / 不必 / 說 / , / 就 / 在 / 天空 / 中 / 也 / 顯出 / 將到 / 新年 / 的 / 氣象 / 來

標記POS

import jieba.posseg
print(list((jieba.posseg.cut(sample))))

去除標點符號並計算詞頻

Removing punctuation marks with the information of unicode category: https://en.wikipedia.org/wiki/Unicode_character_property

# Just an testing
import unicodedata
chars = "『「,。、標點符號"
print([unicodedata.category(ch) for ch in chars])

['Ps', 'Ps', 'Po', 'Po', 'Po', 'Lo', 'Lo', 'Lo', 'Lo']

import unicodedata

from collections import Counter
word_counts = Counter()
for sentence in sentences:
    for word in jieba.cut(sentence):
        if len(word) > 1 or not unicodedata.category(word).startswith('P'):
            word_counts[word] += 1
print(word_counts.most_common(20))

[('的', 345), ('了', 197), ('她', 155), ('我', 88), ('是', 86), ('也', 77), ('說', 65), ('在', 61), ('來', 57), ('就', 56), ('裏', 54), ('有', 46), ('麽', 45), ('不', 37), ('祥林嫂', 35), ('又', 34), ('一個', 33), ('你', 33), ('去', 32), ('他', 28)]

刪去停用詞

Manipulate a Chinese stopword list.

What is "U+FEFF"? The Unicode character U+FEFF is the byte order mark, or BOM, and is used to tell the difference between big- and little-endian UTF-16 encoding. If you decode the web page using the right codec, Python will remove it for you. Examples:

Sol. 1. Add stop words temporarily

stopwords = ["的", "了", "是", "也", "在"]

Sol. 2. Add stop words from files

with open("data/stopwords_zh-tw.txt", encoding="utf-8") as fin:
    stopwords = fin.read().split("\n")[1:]
print(stopwords)
print(len(stopwords))

Remove stop words

import unicodedata

from collections import Counter
word_counts = Counter()

for sentence in sentences:
    for word in jieba.cut(sentence):
        if word in stopwords:
            continue
        if len(word) > 1 or not unicodedata.category(word).startswith('P'):
            word_counts[word] += 1

print(word_counts.most_common(20))
print(len(word_counts))

Using CKIPTagger

  1. CKIPTagger在沒有GPU資源時非常的慢,所以即使有CPU,跑起來也不快。

  2. 他是用bi-lstm,所以要先安裝tensorflow,版本大於1.14,小於2,那就是1.15

Last updated