用jieba整理水井村地方故事與關鍵詞
讓「水井三寶、姻緣花、柴井車、智慧養殖」不再被切散,也不讓演算法取代社區自己的說法。
完成本篇後,你能解釋中文斷詞的必要性,使用jieba精確模式處理繁體中文,建立水井村自訂詞典與停用詞,統計詞頻、搜尋上下文、擷取TF-IDF關鍵詞,建立可人工審查的結果檔,並說明口述故事的同意、去識別、台語轉寫及地方詮釋限制。
一、電腦為何不知道「姻緣花」是一個詞?
英文常以空白分隔單字,中文句子通常沒有天然空白。「水井三寶包含烏龜、白馬與姻緣花」若逐字處理,就無法直接知道「水井三寶」和「姻緣花」是地方概念。斷詞的工作,就是提出一種詞語邊界。
| 原文 | 可能結果 | 問題 |
|---|---|---|
| 水井三寶 | 水井/三寶 | 地方品牌詞被拆開 |
| 姻緣花 | 姻緣/花 | 地方文化名稱失去完整性 |
| 魚菜共生淨水 | 魚菜/共生/淨水 | 可能需要保留「魚菜共生」 |
| HUB 8735 Ultra | 英數與空白混合 | 需另外設計技術詞正規化 |
二、安裝jieba並記錄維護風險
import subprocess
import sys
subprocess.run(
[sys.executable, "-m", "pip", "install", "jieba"],
check=True,
)截至本篇核對時,PyPI顯示的最新版仍是2020年發布的0.42.1。它適合教學與既有專案評估,但正式採用前應重新檢查維護狀態、Python相容性、授權、安全與替代工具,不能因為安裝成功就直接長期部署。
三、第一個繁體中文斷詞
import jieba
text = "水井三寶包含烏龜、白馬與姻緣花。"
tokens = list(jieba.cut(text, cut_all=False, HMM=True))
print(tokens)
print("/".join(tokens))cut_all=False是精確模式,較適合一般文字分析;HMM可嘗試辨識未登錄詞,但不保證地方詞一定正確。
四、三種模式用途不同
sentence = "水井村推動智慧養殖與魚菜共生淨水。"
precise = list(jieba.cut(sentence, cut_all=False))
full = list(jieba.cut(sentence, cut_all=True))
search = list(jieba.cut_for_search(sentence))
print("精確模式:", precise)
print("全模式:", full)
print("搜尋模式:", search)| 模式 | 適合 | 限制 |
|---|---|---|
| 精確模式 | 詞頻、一般分析 | 仍可能切錯地方詞 |
| 全模式 | 觀察可能詞語 | 重疊多,不能直接計算一般詞頻 |
| 搜尋引擎模式 | 建立搜尋索引 | 長詞會再次切分,詞數不可和精確模式混比 |
五、使用獨立Tokenizer,避免全域設定互相干擾
import jieba
tokenizer = jieba.Tokenizer()
for word in [
"水井三寶",
"姻緣花",
"柴井車",
"魚菜共生",
"智慧養殖",
"工作蝦",
]:
tokenizer.add_word(word, freq=100000)
text = "水井三寶與柴井車承載地方記憶。"
print(list(tokenizer.cut(text, cut_all=False)))在多人專案或測試中,獨立Tokenizer比修改全域 jieba.dt更容易控制。詞頻100000只是確保教學示例優先成詞,不代表真實語料頻率。
六、繁體中文詞典與地方詞典分工
jieba專案提供較大的 extra_dict/dict.txt.big,可作為繁體中文候選詞典;實際專案應把經審核的詞典檔納入版本管理,記錄來源、授權與雜湊,不在程式執行時臨時從不明網址下載。
from pathlib import Path
import jieba
def build_tokenizer(base_dictionary=None, user_dictionary=None):
tokenizer = jieba.Tokenizer(
dictionary=str(base_dictionary) if base_dictionary else None
)
if user_dictionary:
path = Path(user_dictionary)
if not path.is_file():
raise FileNotFoundError(path)
tokenizer.load_userdict(str(path))
return tokenizer
# 專案備妥詞典後再啟用:
# tokenizer = build_tokenizer(
# "data/dict.txt.big", "data/shuijing_words.txt"
# )七、自訂詞典格式
自訂詞典每行為「詞語 詞頻 詞性」,詞頻與詞性可省略,檔案使用UTF-8。建議由社區與課程團隊共同審查:
水井三寶 100000 nz
姻緣花 100000 nz
柴井車 100000 nz
工作蝦 100000 nz
魚菜共生 100000 nz
智慧養殖 100000 nz
風頭水尾 100000 nznz只是常見自訂專名標記,詞性標註並非本篇主要目標。地方詞典還應另有說明表,記錄詞義、別稱、使用者、例句、審核者與版本日期。
八、先做最小文字清理
import re
import unicodedata
def normalize_text(text):
text = unicodedata.normalize("NFKC", str(text))
text = text.replace("\u3000", " ")
text = re.sub(r"[ \t]+", " ", text)
text = re.sub(r"\n{3,}", "\n\n", text)
return text.strip()
sample = "水井村 推動 智慧養殖。"
print(normalize_text(sample))NFKC會統一部分全形與相容字元,但也可能改變原始書寫形式。口述史原稿應另外保存;分析使用的是衍生副本,並記錄清理規則。
九、停用詞:刪掉常見詞也可能刪掉地方語氣
STOPWORDS = {
"的", "了", "在", "和", "與", "是",
"有", "也", "就", "我們", "一個",
}
def meaningful_tokens(text, tokenizer, stopwords=STOPWORDS):
for token in tokenizer.cut(normalize_text(text)):
token = token.strip()
if not token:
continue
if token in stopwords:
continue
if not re.search(r"[\u3400-\u9fffA-Za-z0-9]", token):
continue
yield token
story = "我們在水井村用智慧養殖守護工作蝦。"
print(list(meaningful_tokens(story, tokenizer)))停用詞必須依任務制定。長者口述中的「阮、咱、彼時、做伙」可能承載語氣、群體關係與台語特色,不應直接套用一般中文停用詞刪除。
十、用Counter統計地方詞頻
from collections import Counter
stories = [
"水井村推動智慧養殖,也用魚菜共生淨水。",
"水井三寶包含烏龜、白馬與姻緣花。",
"姻緣花承載水井村的地方記憶與祝福。",
]
counter = Counter()
for story in stories:
counter.update(meaningful_tokens(story, tokenizer))
for word, count in counter.most_common(10):
print(word, count)詞頻高表示在這批語料出現多,不等於對社區最重要。一次只被長者說出的罕見詞,可能反而是珍貴文化線索。
十一、同一詞在幾篇故事出現?
document_frequency = Counter()
for story in stories:
unique_words = set(
meaningful_tokens(story, tokenizer)
)
document_frequency.update(unique_words)
for word, documents in document_frequency.most_common(10):
print(word, "出現在", documents, "篇")詞頻計算總出現次數;文件頻率計算出現於幾篇文件。兩者回答不同問題,不能混稱「熱門程度」。
十二、顯示關鍵詞所在原句
def find_contexts(stories, keyword):
results = []
for index, story in enumerate(stories, start=1):
sentences = re.split(r"(?<=[。!?])", story)
for sentence in sentences:
sentence = sentence.strip()
if keyword in sentence:
results.append({
"story": index,
"sentence": sentence,
})
return results
for item in find_contexts(stories, "姻緣花"):
print(item)詞頻表應能回到原句,才能讓社區夥伴確認用法與意義。公開結果若含可識別人物或私密經驗,仍需授權與去識別。
十三、TF-IDF擷取關鍵詞
import jieba.analyse
corpus_text = "\n".join(stories)
keywords = jieba.analyse.extract_tags(
corpus_text,
topK=8,
withWeight=True,
)
for word, weight in keywords:
print(word, round(weight, 4))TF-IDF權重不是機率,也不是社區重要性分數。jieba預設IDF來自其內建語料,不一定代表臺灣農漁村或水井村;地方分析可建立經審查的領域語料與IDF,但需要足夠文件及清楚方法。
十四、讓TF-IDF使用自訂Tokenizer的地方詞
jieba.analyse預設使用全域斷詞器,可能和前面的獨立Tokenizer設定不同。簡單教材可在分析前把地方詞加入全域詞典,但要在單一初始化函式集中管理:
DOMAIN_WORDS = [
"水井三寶", "姻緣花", "柴井車",
"魚菜共生", "智慧養殖", "工作蝦",
]
def configure_global_jieba():
for word in DOMAIN_WORDS:
jieba.add_word(word, freq=100000, tag="nz")
configure_global_jieba()
keywords = jieba.analyse.extract_tags(
"\n".join(stories), topK=8, withWeight=True
)
print(keywords)測試要先重新初始化或固定執行順序,避免全域狀態讓測試互相污染。大型系統可評估更易注入Tokenizer的分析設計。
十五、比較詞頻與TF-IDF,不急著選「唯一答案」
frequency_top = [
word for word, count in counter.most_common(8)
]
tfidf_top = [word for word, weight in keywords]
comparison = {
"both": sorted(set(frequency_top) & set(tfidf_top)),
"frequency_only": [
word for word in frequency_top if word not in tfidf_top
],
"tfidf_only": [
word for word in tfidf_top if word not in frequency_top
],
}
print(comparison)共同出現的詞可優先檢視;只在一邊出現的詞也不能直接丟掉。最終主題命名應回到原句、訪談脈絡與社區共同討論。
十六、為地方詞典建立回歸測試
CASES = {
"水井三寶包含姻緣花": {"水井三寶", "姻緣花"},
"魚菜共生協助淨水": {"魚菜共生"},
"智慧養殖記錄工作蝦": {"智慧養殖", "工作蝦"},
}
def test_domain_dictionary(tokenizer):
failures = []
for sentence, expected in CASES.items():
actual = set(tokenizer.cut(sentence, cut_all=False))
missing = expected - actual
if missing:
failures.append({
"sentence": sentence,
"missing": sorted(missing),
"actual": sorted(actual),
})
return failures
failures = test_domain_dictionary(tokenizer)
assert not failures, failures
print("地方詞典測試通過")每次更新jieba、基礎詞典或地方詞典都執行測試,避免原本正確的地方詞突然被拆開。
十七、輸出可審查的JSON,而非只有詞雲
import json
from datetime import datetime, timezone
report = {
"created_at": datetime.now(timezone.utc).isoformat(),
"method": "jieba精確模式+自訂地方詞+詞頻與TF-IDF",
"documents": len(stories),
"top_frequency": counter.most_common(10),
"top_tfidf": [
{"word": word, "weight": weight}
for word, weight in keywords
],
"review_status": "待社區人工審查",
}
print(json.dumps(report, ensure_ascii=False, indent=2))詞雲視覺吸引人,但會省略方法、分母與上下文。研究或USR成果應保留版本、語料範圍、清理規則、詞典、停用詞、程式與人工審查紀錄。
十八、台語口述故事要多一層語言尊重
| 階段 | 應注意 |
|---|---|
| 語音轉文字 | 台語辨識可能誤轉人名、地名、農產與料理 |
| 轉寫選擇 | 漢字、台羅或混合書寫需保留原則與原始錄音 |
| jieba斷詞 | 主要針對中文文字,不等於台語語言分析工具 |
| 修訂 | 由說話者或熟悉地方語言的人確認,不擅自「改成標準中文」 |
| 公開 | 確認用途、範圍、署名/匿名、撤回及保存期限 |
十九、個資去識別不能只靠正規表示式
import re
def flag_possible_sensitive_text(text):
patterns = {
"phone": r"(?<!\d)09\d{8}(?!\d)",
"email": r"[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}",
"address_hint": r"(?:路|街|巷|弄|號)",
}
return {
name: bool(re.search(pattern, text))
for name, pattern in patterns.items()
}
print(flag_possible_sensitive_text(
"這是一段不含聯絡方式的示範故事。"
))這只能標記部分可能風險,無法辨識暱稱、親屬關係、罕見事件或組合資訊。去識別必須人工審查;即使移除姓名,故事仍可能讓社區成員辨識當事人。
二十、課堂挑戰
二十一、用AI協助整理,但不能代替受訪者確認
請擔任地方故事斷詞與關鍵詞分析助教。
語料來自已同意教學分析並完成去識別的社區故事。
請先檢查繁體中文、台語轉寫、地方專名、同義詞、停用詞與個資風險,
再協助比較詞頻、文件頻率與TF-IDF結果。
不得把演算法關鍵詞稱為「社區最重要的文化」,
不得擅自改寫長者原意,也不要要求提供真實姓名或未授權逐字稿。
請輸出待人工確認清單、原句索引與地方詞典修訂建議。
沒有留言:
張貼留言