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 目录下,读者可以直接复制到本地重复实验。

相关推荐
怪奇云呼军12 小时前
知识库也会注入指令?闪电智能VoiceAgent 如何防住 Prompt Injection
人工智能·python·算法·云计算·音视频
zmzb010313 小时前
Python课后习题训练记录Day194
python·opencv·计算机视觉
benchmark_cc14 小时前
批量获取量化数据时,如何设置合理的超时和重试机制?——QuantDash 高性能实战指南
开发语言·人工智能·python·pandas·量化·quantdash
2601_9669496514 小时前
使用 Pandas 读取批量数据时如何避免内存溢出?QuantDash 量化数据工程师避坑指南
开发语言·python·pandas·tushare·akshare·quantdash
65岁退休Coder14 小时前
LangChain v1.3.4 笔记 - 08 MCP & 相关概念
后端·python·langchain
wdloumiga14 小时前
使用 Construct 3零基础做一些2D小游戏
python·游戏·游戏2d
用户83562907805115 小时前
使用 Python 查找和替换 Word 文档中的文本
后端·python
黑马水牛15 小时前
Carla仿真系列:9_Carla 单目障碍物测距,四种方法原理与实测
经验分享·python·深度学习·计算机视觉·ros·传感器·carla仿真
Python私教16 小时前
多个项目怎么安全合并?适配层、双轨验证与可回滚切换
python·软件架构·系统迁移
船厂电气自动化ai大模型16 小时前
AI大模型与数学第42课:泰勒级数完整展开(神经网络近似核心)
人工智能·python·深度学习·算法·机器学习