jieba 分词 + 共现矩阵构建——《釜山行》人物关系提取(二)

jieba 分词 + 共现矩阵构建------《釜山行》人物关系提取(二)

标签: Python3 | jieba | 共现矩阵 | NLP | 釜山行 | 人物关系提取 实验环境: 华为云 FlexusX / Ubuntu 24.04 / Python 3.12.3 运行节点: node-01(公网 1.94.202.19)

1. 回顾与目标

第一篇已经搭好了四节点环境。本篇在 node-01 上完成:

  1. 加载自定义 jieba 词典;
  2. 对《釜山行》文本分词;
  3. 统计人物出现频次;
  4. 段落为窗口构建人物共现矩阵;
  5. 导出 nodes.csvedges.csvstats.json

2. 完整代码(busan_segment.py)

python 复制代码
#!/usr/bin/env python3
"""
jieba 分词 + 人物关系提取 --- 《釜山行》
节点: node-01 (1.94.202.19)
功能: 加载自定义词典 -> 分词 -> 统计人物出现频次 -> 按段落构建共现关系
"""
import jieba
import jieba.posseg as pseg
import json
from collections import defaultdict, Counter

# ============ 配置 ============
TEXT_FILE = "/root/busan_text.txt"
DICT_FILE = "/root/busan_dict.txt"
CHARACTERS = ["石宇", "秀安", "尚华", "盛京", "珍熙", "英国", "容国", 
              "老姐姐", "老妹妹", "乞丐", "列车长"]

# ============ 1. 加载自定义词典 ============
print("=" * 60)
print("步骤1: 加载自定义人物词典")
print("=" * 60)
jieba.load_userdict(DICT_FILE)
print(f"已加载词典: {DICT_FILE}")
print(f"人物列表: {CHARACTERS}")

# ============ 2. 读取文本 ============
print("\n" + "=" * 60)
print("步骤2: 读取《釜山行》文本")
print("=" * 60)
with open(TEXT_FILE, "r", encoding="utf-8") as f:
    text = f.read()
print(f"文本总长度: {len(text)} 字符")
print(f"文本前100字: {text[:100]}...")

# ============ 3. 分词处理 ============
print("\n" + "=" * 60)
print("步骤3: jieba 分词处理")
print("=" * 60)
words = jieba.lcut(text)                # 精确模式分词
words_pos = pseg.lcut(text)             # 带词性标注
print(f"分词总数: {len(words)} 个词")

# ============ 4. 统计人物出现频次 ============
print("\n" + "=" * 60)
print("步骤4: 统计人物出现频次")
print("=" * 60)
char_count = Counter()
for w in words:
    if w in CHARACTERS:
        char_count[w] += 1

for char, count in char_count.most_common():
    print(f"{char:<10} {count:<10}")

# ============ 5. 按段落构建共现关系 ============
print("\n" + "=" * 60)
print("步骤5: 构建人物共现关系(基于段落窗口)")
print("=" * 60)
paragraphs = [p.strip() for p in text.split("\n") if p.strip()]
print(f"段落数: {len(paragraphs)}")

co_occurrence = defaultdict(lambda: defaultdict(int))

for para in paragraphs:
    para_words = jieba.lcut(para)
    chars_in_para = list(set([w for w in para_words if w in CHARACTERS]))

    for i in range(len(chars_in_para)):
        for j in range(i + 1, len(chars_in_para)):
            c1, c2 = chars_in_para[i], chars_in_para[j]
            co_occurrence[c1][c2] += 1
            co_occurrence[c2][c1] += 1

# ============ 6. 输出共现关系 ============
edges = []
for c1 in CHARACTERS:
    for c2 in CHARACTERS:
        if c1 < c2 and co_occurrence[c1][c2] > 0:
            edges.append((c1, c2, co_occurrence[c1][c2]))

print(f"\n共现关系总数: {len(edges)} 条")

# ============ 7. 保存结果 ============
with open("/root/nodes.csv", "w", encoding="utf-8") as f:
    f.write("Id,Label,Weight\n")
    for char in CHARACTERS:
        count = char_count.get(char, 0)
        f.write(f"{char},{char},{count}\n")

with open("/root/edges.csv", "w", encoding="utf-8") as f:
    f.write("Source,Target,Weight\n")
    for c1, c2, w in sorted(edges, key=lambda x: -x[2]):
        f.write(f"{c1},{c2},{w}\n")

stats = {
    "total_words": len(words),
    "total_paragraphs": len(paragraphs),
    "character_frequency": dict(char_count.most_common()),
    "co_occurrence_edges": sorted(edges, key=lambda x: -x[2]),
    "total_edges": len(edges),
}
with open("/root/stats.json", "w", encoding="utf-8") as f:
    json.dump(stats, f, ensure_ascii=False, indent=2)

print("\n处理完成!")

3. 真实服务器输出

text 复制代码
root@ecs-11e6-0001:~# cd /root && python3 busan_segment.py
Building prefix dict from the default dictionary ...
Dumping model to file cache /tmp/jieba.cache
Loading model cost 0.432 seconds.
Prefix dict has been built successfully.
============================================================
步骤1: 加载自定义人物词典
============================================================
已加载词典: /root/busan_dict.txt
人物列表: ['石宇', '秀安', '尚华', '盛京', '珍熙', '英国', '容国', '老姐姐', '老妹妹', '乞丐', '列车长']

============================================================
步骤2: 读取《釜山行》文本
============================================================
文本总长度: 1854 字符

============================================================
步骤3: jieba 分词处理
============================================================
分词总数: 1175 个词

============================================================
步骤4: 统计人物出现频次
============================================================
石宇         34
秀安         26
盛京         24
尚华         20
珍熙         17
英国         16
容国         15
乞丐         8
老妹妹        6
老姐姐        5
列车长        3

============================================================
步骤5: 构建人物共现关系(基于段落窗口)
============================================================
段落数: 16

============================================================
步骤6: 人物共现关系表
============================================================
  石宇 <-> 秀安: 共现 12 次
  石宇 <-> 盛京: 共现 11 次
  秀安 <-> 盛京: 共现 11 次
  石宇 <-> 尚华: 共现 9 次
  尚华 <-> 盛京: 共现 9 次
  珍熙 <-> 秀安: 共现 9 次
  容国 <-> 秀安: 共现 9 次
  容国 <-> 珍熙: 共现 9 次
  ...
  列车长 <-> 老妹妹: 共现 2 次

共现关系总数: 55 条

节点文件: /root/nodes.csv
边文件: /root/edges.csv
统计文件: /root/stats.json

4. 关键结果解读

4.1 人物出现频次

人物 出现次数 角色定位
石宇 34 男主 / 基金经理
秀安 26 石宇女儿 / 核心线索人物
盛京 24 尚华妻子 / 孕妇
尚华 20 肌肉男 / 保护者
珍熙 17 女高中生
英国 16 自私的运营部长 / 反派
容国 15 棒球少年
乞丐 8 善良流浪汉
老妹妹 6 老妹妹
老姐姐 5 老姐姐
列车长 3 列车工作人员

频次直接反映人物在文本中的叙事权重。石宇和秀安是父女双主角,因此频次最高。

4.2 最强共现关系(Top 10)

人物A 人物B 共现次数 关系说明
石宇 秀安 12 父女
石宇 盛京 11 共同逃亡 / 尚华之托
秀安 盛京 11 共同被保护
石宇 尚华 9 并肩作战的战友
尚华 盛京 9 夫妻
珍熙 秀安 9 女性互助
容国 秀安 9 青年组共同逃亡
容国 珍熙 9 恋人
石宇 英国 8 冲突对抗
秀安 英国 8 被阻拦的受害者

这些强共现关系基本对应了电影里的核心关系线:父女、夫妻、情侣、战友、对抗。

5. 生成的 CSV 数据格式

nodes.csv

csv 复制代码
Id,Label,Weight
石宇,石宇,34
秀安,秀安,26
盛京,盛京,24
...

Gephi 导入时,列 Id 作为节点唯一标识,Label 作为显示标签,Weight 控制节点大小。

edges.csv

csv 复制代码
Source,Target,Weight
石宇,秀安,12
盛京,石宇,11
盛京,秀安,11
...

SourceTarget 分别对应人物名称,Weight 控制边的粗细。

6. 为什么段落窗口得到的是"完全图"?

细心的读者会发现在本实验中 11 个人物之间两两都有共现(55 条边 = C(11,2)),因此后续的网络密度为 1.0。这并不意外:

  • 文本较短,只有 16 个段落;
  • 故事发生在一列封闭的列车上,所有主要人物在同一灾难空间中高频交互;
  • 老姐姐、老妹妹、列车长等次要人物仍出现在多个段落中。

为了得到更稀疏、更聚焦的核心网络,第三篇会用边权重过滤 (只保留 Weight >= 7 的边),那时网络会呈现清晰的社群结构。

7. 代码要点解析

7.1 jieba.load_userdict

自定义词典是识别人名的关键。如果不加载,jieba 可能会把"石宇"切成"石/宇",从而丢失人物信息。

7.2 set() 去重

同一人物在一段落中可能出现多次,必须去重,否则共现次数会被重复计算。

python 复制代码
chars_in_para = list(set([w for w in para_words if w in CHARACTERS]))

7.3 无向边处理

共现关系是无向的,所以统计时加两次,保证矩阵对称:

python 复制代码
co_occurrence[c1][c2] += 1
co_occurrence[c2][c1] += 1

输出时只取 c1 < c2 避免重复输出。

8. 数据传递到下一节点

生成的 nodes.csv / edges.csv 通过 paramiko.SFTPnode-01 传送到 node-03 做网络分析,以及 node-04 做可视化。第三篇会展示这一过程。

9. 下一篇预告

第三篇将在 node-03NetworkX 计算度中心性、介数中心性、接近中心性、特征向量中心性、聚类系数、网络密度、平均路径长度,并导出 .gexf 供 Gephi 使用。

10. 完整代码文件获取

  • busan_text.txt:电影文本
  • busan_dict.txt:自定义人名词典
  • busan_segment.py:分词与共现提取脚本
  • nodes.csv / edges.csv:节点与边数据
  • stats.json:统计结果

这些文件已保存在 node-01(1.94.202.19)的 /root 目录下,读者可以直接复制到本地重复实验。

相关推荐
RSABLOCKCHAIN8 小时前
AI Agents in LangGraph-2
人工智能·python
WA内核拾荒者9 小时前
WhatsApp 账号异常检测的自动化告警系统设计
数据库·python·自动化
码流怪侠9 小时前
【GitHub】Bend:让 GPU 并行编程像写 Python 一样简单
python·github
2401_8949155310 小时前
GEO 搜索优化完整源码从零部署:环境配置、集群搭建全流程
开发语言·python·tcp/ip·算法·unity
zhiSiBuYu051712 小时前
Python3 模块开发与应用实战指南
python
databook13 小时前
用方差阈值过滤掉“惰性特征”
python·机器学习·scikit-learn
Freak嵌入式13 小时前
MCU 低功耗模式解析:时钟门控、电源门控、深度休眠
人工智能·python·单片机·嵌入式硬件·开源·依赖倒置原则·micropython
weixin_BYSJ198714 小时前
springboot校园自习室管理小程序---附源码32142
java·javascript·spring boot·python·django·flask·php
用户83562907805114 小时前
使用 Python 在 PDF 中绘制线条、矩形和自定义图形
后端·python