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

相关推荐
用户298698530141 小时前
Python 实现 Excel 新旧格式互转:告别 Office 依赖的自动化方案
后端·python·excel
进击切图仔1 小时前
基于千问的白盒蒸馏 SOP
运维·服务器·人工智能·python·效率
2401_868534781 小时前
案例介绍-小型网络规划及搭
python·计算机网络
ONE_SIX_MIX2 小时前
lightweight-charts-onesixth v2.8.1 → v3.1.1 更新总览
java·前端·python
YUS云生2 小时前
Python学习笔记·第29天:Git进阶——分支操作与合并冲突
笔记·python·学习
奋斗的小方2 小时前
Java进阶篇1-3:集合框架(单列集合)
java·windows·python
乐以礼2 小时前
#100DaysOfAgent Day 5
python·ai
江华森2 小时前
Gephi 可视化 + NetworkX 网络分析——《釜山行》人物关系(三)
数据库·python
进击切图仔3 小时前
基于千问的白盒蒸馏操作教学文档
服务器·人工智能·python