首先直接从“二进桂师.docx”文档开始处理,从中提取出第一篇文章,直接保存为二进桂师.txt文件。 然后针对二进桂师.txt文件进行数据清洗,去除重复值、无效值、空格等。
步骤 1: 读取和清洗文本 目标:从文本文件中读取内容,进行清洗并分段。
# 步骤 1: 读取和清洗文本
# 目标:从文本文件中读取内容,进行清洗并分段。
import re
with open('二进桂师.txt', 'r', encoding='utf-8') as file:
text = file.read()
# Step 1: 文本清洗与分段
def clean_and_split_text(text):
# 1. 清除所有空白字符(包括空格、制表符、换行符等)
cleaned_text = re.sub(r'\s+', '', text)
# 2. 清除重复的标点符号
cleaned_text = re.sub(r'[。!?]+', '。', cleaned_text)
# 3. 移除特殊字符和无效值(根据需要可以添加更多)
cleaned_text = re.sub(r'[①②③④⑤⑥⑦⑧⑨⑩""''()\(\)\[\]\{\}]', '', cleaned_text)
# 4. 分割句子
sentences = re.split(r'。', cleaned_text)
# 5. 过滤空字符串和只包含标点的句子
filtered_sentences = []
for sentence in sentences:
sentence = sentence.strip()
if sentence and not re.match(r'^[,、:;]+$', sentence):
# 6. 去除重复的句子
if sentence not in filtered_sentences:
filtered_sentences.append(sentence)
return filtered_sentences
# 获取分段的句子
sentences = clean_and_split_text(text)
# 输出前几段以检查结果
print("分段后的文本:")
print(sentences[:5])
# 保存清洗后的文本
with open('clean.txt', 'w', encoding='utf-8') as file:
# 将清洗后的句子重新组合,使用句号连接
cleaned_text = '。'.join(sentences) + '。' # 添加最后的句号
file.write(cleaned_text)
分段后的文本: ['二进桂师唐肇华我曾两次进桂师工作,避风', '第一次是在1940年2月,我被迫离开灵川国民中学,到桂师任师训班导师和二班物理教师,7月回广西大学复学;第二次是在1941年9月,我被武装接长西大的高阳勒令离校,回桂师任三、四合班导师和师五班物理课,翌年7月离校,两次都是唐现之校长得知我的困境后,叫我到桂师工作的', '1938年11月我在桂林接中共广西省工委指示参加广西学生军,翌年一月受命打入三青团干训班受训,以争夺该团部分领导权', '2月,干训班结束,我便前往全州县筹建三青团分团', '省工委指示,全州县是广西的北大门,地理位置很重要,是蒋介石南下的要冲,驻有他的嫡系部队新五军军长杜聿明军部和该部主力陆军二百师,要利用三青团这个公开组织为合法阵地,按上级党的指示宣传党的抗日民族统一战线的方针政策,团结广大青年群众,开展抗日救亡活动,以适应革命形势发展的需要,并在可能条件下把影响伸进军营']
<>:17: SyntaxWarning: invalid escape sequence '\('
<>:17: SyntaxWarning: invalid escape sequence '\('
C:\Users\Administrator\AppData\Local\Temp\ipykernel_16088\286409681.py:17: SyntaxWarning: invalid escape sequence '\('
cleaned_text = re.sub(r'[①②③④⑤⑥⑦⑧⑨⑩""''()\(\)\[\]\{\}]', '', cleaned_text)
自定义停用词¶
基于文档自动生成词频统计 从文档中提取高频词,并结合人工审阅筛选出领域词汇。 生成词库文件 根据统计结果和人工判断,整理出专有词汇后,保存为自定义词典格式。
# 导入必要的库
import jieba
from collections import Counter
import pandas as pd
# 加载文本数据
file_path = "二进桂师.txt"
with open(file_path, "r", encoding="utf-8") as file:
text = file.read()
# 导入哈工大停用词
with open("hit_stopwords.txt", "r", encoding="utf-8") as file:
stopwords = set(file.read().splitlines())
# 文本分词并过滤停用词
words = [word for word in jieba.cut(text) if word.strip() and word not in stopwords]
# 统计词频
word_freq = Counter(words)
# 将词频结果转为 DataFrame
df_word_freq = pd.DataFrame(word_freq.items(), columns=["词语", "频率"]).sort_values(by="频率", ascending=False)
# 筛选高频词(频率 > 5)作为候选领域词汇
high_freq_words = df_word_freq[df_word_freq["频率"] > 5]["词语"].tolist()
# 人工筛选后领域词汇(这里可结合实际情况调整)
# 示例:领域词汇
custom_terms = high_freq_words # 假设全选高频词,实际可手动调整
# 保存词频统计结果
df_word_freq.to_csv("词频统计结果.csv", index=False, encoding="utf-8")
# 保存自定义词库
with open("自定义词库.txt", "w", encoding="utf-8") as f:
for term in custom_terms:
f.write(f"{term}\n")
print("词频统计已保存为 '词频统计结果.csv'")
print("自定义词库已保存为 '自定义词库.txt'")
Building prefix dict from the default dictionary ... Dumping model to file cache C:\Users\Administrator\AppData\Local\Temp\jieba.cache Loading model cost 0.390 seconds. Prefix dict has been built successfully.
词频统计已保存为 '词频统计结果.csv' 自定义词库已保存为 '自定义词库.txt'
jieba分词¶
虽然HanLP自带分词功能,并且在命名实体识别(NER)过程中已经自动处理分词。 但是对于这篇历史档案文档,之前提取出的精确度不高,因此想要增强提取的精确度:
- 在NER前对文本进行jieba分词。
- 并且为了提升分词精确度,采用哈工大停用词库,且根据文档自定义分词库
- 利用分词结果更准确地提取实体和关系。
## 加入jieba分词
# 导入jieba
import jieba
# 加载自定义词库和哈工大停用词
jieba.load_userdict("自定义词库.txt")
def load_stopwords(file_path):
with open(file_path, "r", encoding="utf-8") as f:
return set(line.strip() for line in f)
stopwords = load_stopwords("hit_stopwords.txt")
# 分词函数,支持去停用词
def segment_sentences(sentences, stopwords=None):
segmented_sentences = []
for sentence in sentences:
words = jieba.lcut(sentence) # 使用精确模式分词
if stopwords:
words = [word for word in words if word not in stopwords]
segmented_sentences.append(" ".join(words)) # 拼接为分词后的字符串
return segmented_sentences
# 加载文本数据并分割成句子
text_file = "二进桂师.txt"
with open(text_file, "r", encoding="utf-8") as file:
text = file.read()
# 按句号、换行符分割文本为句子
sentences = [line.strip() for line in text.split('。') if line.strip()]
# 分词并去停用词
segmented_sentences = segment_sentences(sentences, stopwords=stopwords)
# 输出分词结果示例
print("分词后的句子示例:")
for i, sentence in enumerate(segmented_sentences[:5], 1):
print(f"{i}: {sentence}")
# 保存分词后的文本
output_file = "jieba_result.txt"
with open(output_file, "w", encoding="utf-8") as file:
# 将清洗后的句子重新组合,使用句号连接
jieba_text = '。'.join(segmented_sentences) + '。' # 添加最后的句号
file.write(jieba_text)
print(f"分词结果已保存到文件 '{output_file}'")
分词后的句子示例: 1: 二 进 桂 师 唐 肇 华 曾 两次 进桂师 工作 避风 2: 第一次 1940 年 2 月 被迫 离开 灵川 国民中学 桂师 师训 班 导师 二班 物理 教师 7 月 回 广西大学 复学 第二次 1941 年 9 月 武装 接长 西大 高阳 勒令 离校 回 桂师 任三 四合 班 导师 师 五班 物理课 翌年 7 月 离校 两次 都 唐现 校长 得知 困境 后 桂师 工作 3: 1938 年 11 月 桂林 接 中共 广西省 工委 指示 参加 广 西 学生 军 翌年 一月 受命 打入 三青团 干训班 受训 争夺 该团 部分 领导权 4: 2 月 干训班 结束 便 前往 全州县 筹建 三青团 分团 5: 省 工委 指示 全州县 广西 北大 门 地理位置 很 重要 蒋介石 南下 要冲 驻有 嫡系 部队 新五军 军长 杜聿明 军部 该部 主力 陆军 二百 师 利用 三青团 公开 组织 合法 阵地 上级 党 指示 宣传 党 抗日民族统一战线 方针政策 团结 广大青年 群众 开展 抗日救亡 活动 适应 革命 形势 发展 需要 可能 条件 下 影响 伸进 军营 分词结果已保存到文件 'jieba_result.txt'
# 步骤 2: 命名实体识别(NER)
## 结合上述的 jieba 分词
# 导入必要的库
import os
import jieba
import hanlp
import pandas as pd
import datetime
from tqdm import tqdm
import re
# 加载 HanLP 预训练模型
HanLP = hanlp.load(hanlp.pretrained.mtl.CLOSE_TOK_POS_NER_SRL_DEP_SDP_CON_ELECTRA_BASE_ZH)
# 定义命名实体存储字典
entities = {
"PERSON": [], # 人名
"GPE": [], # 地名
"ORG": [], # 组织机构
"DATE": [], # 日期
"EVENT":[] # 事件
}
# 实体类型映射
entity_type_mapping = {
"NR": "PERSON", # 人名
"NS": "GPE", # 地名
"NT": "ORG", # 组织机构
"TIME": "DATE", # 时间
"EVENT": "EVENT"
}
# 日期补充识别正则表达式
date_patterns = [
r'\d{4}年\d{1,2}月\d{1,2}日', # 1949年10月1日
r'\d{4}年\d{1,2}月', # 1949年10月
r'\d{4}年', # 1949年
r'\d{1,2}月\d{1,2}日' # 10月1日
]
# 加载自定义词库和停用词
jieba.load_userdict("自定义词库_modify.txt") # 使用修改之后的自定义词库
def load_stopwords(file_path):
with open(file_path, "r", encoding="utf-8") as f:
return set(line.strip() for line in f)
stopwords = load_stopwords("hit_stopwords.txt")
# 分词预处理函数
def preprocess_sentences(sentences):
preprocessed_sentences = []
for sentence in sentences:
words = [word for word in jieba.lcut(sentence) if word.strip() and word not in stopwords]
preprocessed_sentences.append(" ".join(words)) # 拼接成分词后的句子
return preprocessed_sentences
# 实体提取函数
def extract_entities(sentences):
for sentence in tqdm(sentences, desc="正在进行命名实体识别"):
# 调用 HanLP 模型进行 NER
doc = HanLP(sentence)
for ent in doc['ner/msra']:
entity_text = ent[0]
entity_type = entity_type_mapping.get(ent[1], ent[1])
if entity_type in entities:
entities[entity_type].append(entity_text)
# 正则表达式补充日期提取
for pattern in date_patterns:
matches = re.findall(pattern, sentence)
entities["DATE"].extend(matches)
# 处理文本数据
text_file = "二进桂师.txt"
with open(text_file, "r", encoding="utf-8") as file:
text = file.read()
# 按句号和换行符分割文本
sentences = [line.strip() for line in text.split('。') if line.strip()]
# 预处理:分词并过滤停用词
preprocessed_sentences = preprocess_sentences(sentences)
# 提取实体
extract_entities(preprocessed_sentences)
# 实体去重
for key in entities:
entities[key] = list(set(entities[key]))
# 输出提取结果
print("\n提取的命名实体:")
for key, value in entities.items():
print(f"{key}: {value}")
# 创建 DataFrame
df_entities = pd.DataFrame()
for entity_type, values in entities.items():
chinese_type = {
"PERSON": "人名",
"GPE": "地名",
"ORG": "组织机构",
"DATE": "日期"
}.get(entity_type, entity_type)
df_temp = pd.DataFrame({
'实体类型': [chinese_type] * len(values),
'实体内容': values
})
df_entities = pd.concat([df_entities, df_temp])
# 重置索引
df_entities.reset_index(drop=True, inplace=True)
# 保存结果到 CSV 文件
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
output_file = f"entities_{timestamp}.csv"
try:
df_entities.to_csv(output_file, index=False, encoding='utf-8-sig')
print(f"结果已保存至: {output_file}")
except Exception as e:
print(f"保存文件失败: {e}")
# 输出统计信息
print("\n实体统计信息:")
print(df_entities['实体类型'].value_counts())
正在进行命名实体识别: 100%|██████████| 64/64 [00:05<00:00, 11.58it/s]
提取的命名实体: PERSON: ['马君武', '陈岸', '于辉坤', '马坤元', '欧苇', '桂师', '唐 肇 华', '杜聿明', '雷', '唐', '康', '周可传', '张丽 贞', '陈赐珍', '李宗仁', '李', '赵', '高', '李四光', '唐现之', '雷沛 鸿', '王祥彻', '黄 立志', '石文忠', '赵建勋', '蒋介石', '梁漱溟', '钱念文', '梁', '汤', '高阳', '张丽贞', '汤松年', '汤 观感'] GPE: [] ORG: [] DATE: ['一九八九年', '翌年', '8 月', '中旬', '7 月', '1941 年', '一月', '6 月', '11 月', '10 月', '31', '晚上', '1942', '2 月', '九月', '1938 年', '9 月', '1941', '1940 年', '七 九', '六月', '暑假'] EVENT: [] 结果已保存至: entities_20241214_215453.csv 实体统计信息: 实体类型 人名 34 日期 22 Name: count, dtype: int64
实体类型分析后的结果¶
- 目前可以提取人名、时间等实体信息
- 因此可以构建时间轴,地图标记,但其他知识图谱无法构建
通过以上分析 ,发现地名、组织机构名、事件等实体类型,无法识别出来
- 是否需要全部手工标记?
- 执行步骤、代码有问题?
采用的是doccano工具进行人工标注,简单标记了一些信息
采用doccano的目的是为了进行人工标记
采用Linux操作系统的Docker安装,用的84端口,账号:admin,密码:123,邮箱:qq
导入clean.txt文件 ,然后采用5个标志,做一些简要标注
然后是进行机器学习模型训练或分析
采用BERT学习模型,用到BIO格式文件
将jsonl文件华为BIO格式
## 将jsonl文件华为BIO格式
import json
def jsonl_to_bio(input_file, output_file):
with open(input_file, 'r', encoding='utf-8') as infile, open(output_file, 'w', encoding='utf-8') as outfile:
for line in infile:
# 解析每一行的 JSON 数据
data = json.loads(line.strip())
text = data['text']
entities = data['entities']
# 初始化 BIO 标签
bio_tags = ['O'] * len(text)
# 标注实体
for entity in entities:
start = entity['start_offset']
end = entity['end_offset']
label = entity['label']
# 标注 BIO 格式
bio_tags[start] = f"B-{label}" # 实体起始为 B-<LABEL>
for i in range(start + 1, end):
bio_tags[i] = f"I-{label}" # 实体中间部分为 I-<LABEL>
# 将字符和标签逐行写入文件
for char, tag in zip(text, bio_tags):
if char.strip(): # 跳过空白字符
outfile.write(f"{char} {tag}\n")
outfile.write("\n") # 每段文本间隔空行
# 转换文件
input_file = '1222_annotate.jsonl'
output_file = '1222_bio_output.txt'
jsonl_to_bio(input_file, output_file)
有了BIO数据后,接下来采用BERT模型进行训练 通过 BIO 格式数据构建知识图谱和本体,需要将实体和关系结构化存储,并定义概念、关系、以及图谱的逻辑。以下是完整的流程和实现方式:
- 流程概述 (1) 数据准备 从 BIO 数据中提取实体及其标签,组织成三元组 (subject, predicate, object) 格式。
(2) 构建本体 定义领域内的概念(实体类型)、属性、关系规则等。
(3) 知识图谱存储 将提取的三元组存储在图数据库中(如 Neo4j),便于查询和可视化。
(4) 可视化和推理 通过图数据库查询知识图谱,验证结果并进行推理。
## 从 BIO 数据中提取实体及其标签,组织成三元组 (subject, predicate, object) 格式。
import time
def bio_to_entities_relations(bio_file, output_file_entities, output_file_relations):
entities = []
relations = []
current_entity = {"type": None, "start": None, "end": None, "text": ""}
with open(bio_file, "r", encoding="utf-8") as file:
for line in file:
line = line.strip()
if not line: # 遇到空行,处理当前实体并清空
if current_entity["type"]:
entities.append(current_entity)
current_entity = {"type": None, "start": None, "end": None, "text": ""}
continue
word, label = line.split()
if label.startswith("B-"):
# 保存上一个实体
if current_entity["type"]:
entities.append(current_entity)
# 开始新实体
current_entity = {"type": label[2:], "start": len(entities), "end": len(entities) + 1, "text": word}
elif label.startswith("I-") and current_entity["type"] == label[2:]:
current_entity["text"] += word
current_entity["end"] += 1
else:
# 遇到非实体标记,保存当前实体
if current_entity["type"]:
entities.append(current_entity)
current_entity = {"type": None, "start": None, "end": None, "text": ""}
# 将实体和关系保存到文件
with open(output_file_entities + "_" + str(int(time.time())) + ".txt", "w", encoding="utf-8") as file_entities:
for entity in entities:
file_entities.write(f"{entity['type']}: {entity['text']}\n")
with open(output_file_relations + "_" + str(int(time.time())) + ".txt", "w", encoding="utf-8") as file_relations:
for relation in relations:
file_relations.write(f"{relation}\n")
# 示例使用
bio_file = "1222_bio_output.txt"
output_file_entities = "entities.txt"
output_file_relations = "relations.txt"
bio_to_entities_relations(bio_file, output_file_entities, output_file_relations)
print("实体已保存到", output_file_entities)
print("关系已保存到", output_file_relations)
实体已保存到 entities.txt 关系已保存到 relations.txt
本体构建:定义实体、属性和关系 本体的构建基于提取的实体,结合领域需求来定义核心概念(实体类型)、属性(描述实体特征)、以及关系(实体之间的连接)。
(1) 核心实体类型 根据提取的实体,可以定义以下类型:
人物 (person):如 唐肇华、杜聿明。 时间 (time):如 1940年2月、1938年11月。 地点 (location):如 灵川、广西、全州县。 组织 (org):如 桂师、国民中学、三青团。 事件或行为 (thing):如 任师训班导师和二班物理教师、勒令离校。 其他领域特定概念:根据需求可扩展。 (2) 属性定义 为每个实体类型定义适当的属性:
人物: name:名字。 role:角色,如“校长”、“指挥官”。 时间: date:具体时间。 context:时间的上下文信息。 地点: name:地点名称。 region:所属区域,如 广西。 组织: name:组织名称。 type:组织类别,如“教育机构”、“军事机构”。 事件: description:事件描述。 time:事件发生时间。 (3) 定义关系 结合实体,定义关系类型:
人物与事件: 参与 (participated_in):如 唐肇华 参与 到桂师工作。 指挥 (commanded):如 杜聿明 指挥 抗日。 事件与时间: 发生于 (occurred_on):如 到桂师工作 发生于 1940年2月。 事件与地点: 发生在 (happened_in):如 抗日 发生在 广西。 组织与地点: 位于 (located_in):如 国民中学 位于 灵川。
# 本体框架示例
# 使用简单的 JSON 结构描述本体:
{
"entities": {
"person": {"attributes": ["name", "role"]},
"time": {"attributes": ["date", "context"]},
"location": {"attributes": ["name", "region"]},
"org": {"attributes": ["name", "type"]},
"thing": {"attributes": ["description", "time"]}
},
"relations": [
{"name": "参与", "domain": "person", "range": "thing"},
{"name": "指挥", "domain": "person", "range": "thing"},
{"name": "发生于", "domain": "thing", "range": "time"},
{"name": "发生在", "domain": "thing", "range": "location"},
{"name": "位于", "domain": "org", "range": "location"}
]
}
定义的本体: {'concepts': ['person', 'event', 'time', 'location'], 'relations': ['participated_in', 'happened_in', 'occurred_on'], 'attributes': {'person': ['name', 'role'], 'event': ['name', 'description'], 'time': ['date'], 'location': ['name', 'region']}}
# 查询示例
for relation in relations:
print(f"{relation['subject']} {relation['predicate']} {relation['object']}")
唐肇华 参与 到桂师工作 到桂师工作 发生于 1940年2月 到桂师工作 发生在 灵川
从目前来看,有事件,时间,地点等信息了,但是目前来看人工标注太繁琐,因此需要将自动标注和人工标注合在一起使用 流程如下: 1.自动标注 → 2. 生成初始 BIO 数据 → 3. 人工修正 → 4. 实体与关系提取 → 5. 知识图谱构建与应用
工具和方法推荐 工具: 自动标注:Hugging Face Transformers、HanLP、spaCy。 手动补充:Doccano 或直接修改 BIO 数据。 存储: 轻量存储:使用 Pandas 或 NetworkX。 复杂场景:使用 RDF 或 Neo4j。