Token Classification
Transformers
PyTorch
Chinese
named-entity-recognition
ner
ernie
crf
chinese-nlp
person-name-extraction
financial-documents
Instructions to use warfbro/Human-Name-extraction with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use warfbro/Human-Name-extraction with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("token-classification", model="warfbro/Human-Name-extraction")# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("warfbro/Human-Name-extraction", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Upload extract.py with huggingface_hub
Browse files- extract.py +185 -0
extract.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
人名提取程序 — ERNIE+CRF 模型
|
| 3 |
+
用法: python extract.py <input.xlsx> [output.xlsx]
|
| 4 |
+
"""
|
| 5 |
+
import sys
|
| 6 |
+
import torch
|
| 7 |
+
import openpyxl
|
| 8 |
+
|
| 9 |
+
import re
|
| 10 |
+
|
| 11 |
+
from config import BIO_ID2LABEL
|
| 12 |
+
from model import load_model
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def clean_names(names: list) -> list:
|
| 16 |
+
"""后处理:去掉 '等N人'、'等N' 后缀"""
|
| 17 |
+
cleaned = []
|
| 18 |
+
for n in names:
|
| 19 |
+
n = re.sub(r"等\d*人?$", "", n)
|
| 20 |
+
if n.strip():
|
| 21 |
+
cleaned.append(n.strip())
|
| 22 |
+
return cleaned
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def expand_bracket_names(names: list, title: str) -> list:
|
| 26 |
+
"""如果名字旁有括号且括号旁是英文,扩展为 英文+括号+中文名 整体"""
|
| 27 |
+
expanded = []
|
| 28 |
+
for name in names:
|
| 29 |
+
idx = title.find(name)
|
| 30 |
+
if idx == -1:
|
| 31 |
+
expanded.append(name)
|
| 32 |
+
continue
|
| 33 |
+
|
| 34 |
+
found = False
|
| 35 |
+
|
| 36 |
+
# 模式1: EnglishName(中文名) — 名字在括号内
|
| 37 |
+
if idx > 0 and title[idx - 1] == "(":
|
| 38 |
+
# 找右边的 )
|
| 39 |
+
right = idx + len(name)
|
| 40 |
+
if right < len(title) and title[right] == ")":
|
| 41 |
+
# 找左边的英文
|
| 42 |
+
left = idx - 2
|
| 43 |
+
if left >= 0 and re.match(r"[A-Za-z]", title[left]):
|
| 44 |
+
start = left
|
| 45 |
+
while start > 0 and re.match(r"[A-Za-z]", title[start - 1]):
|
| 46 |
+
start -= 1
|
| 47 |
+
found = True
|
| 48 |
+
expanded.append(title[start:right + 1])
|
| 49 |
+
|
| 50 |
+
# 模式2: (中文名)EnglishName — 名字在括号内,英文在右边
|
| 51 |
+
if not found and idx > 0 and title[idx - 1] == "(":
|
| 52 |
+
right = idx + len(name)
|
| 53 |
+
if right < len(title) and title[right] == ")":
|
| 54 |
+
after = right + 1
|
| 55 |
+
if after < len(title) and re.match(r"[A-Za-z]", title[after]):
|
| 56 |
+
end = after
|
| 57 |
+
while end + 1 < len(title) and re.match(r"[A-Za-z]", title[end + 1]):
|
| 58 |
+
end += 1
|
| 59 |
+
found = True
|
| 60 |
+
expanded.append(title[idx - 1:end + 1])
|
| 61 |
+
|
| 62 |
+
# 模式3: 中文名(EnglishName) — 名字在括号左边
|
| 63 |
+
if not found:
|
| 64 |
+
right = idx + len(name)
|
| 65 |
+
if right < len(title) and title[right] == "(":
|
| 66 |
+
# 找右边的 )
|
| 67 |
+
close = title.find(")", right)
|
| 68 |
+
if close != -1:
|
| 69 |
+
between = title[right + 1:close]
|
| 70 |
+
if re.match(r"[A-Za-z]", between):
|
| 71 |
+
found = True
|
| 72 |
+
expanded.append(title[idx:close + 1])
|
| 73 |
+
|
| 74 |
+
# 模式4: )EnglishName(中文名 — 名字左右都有括号+英文
|
| 75 |
+
# 已经被上面覆盖,不额外处理
|
| 76 |
+
|
| 77 |
+
if not found:
|
| 78 |
+
expanded.append(name)
|
| 79 |
+
|
| 80 |
+
# 去重:若某名字是另一名字的子串,去掉短的
|
| 81 |
+
deduped = []
|
| 82 |
+
for n in expanded:
|
| 83 |
+
if not any(n != other and n in other for other in expanded):
|
| 84 |
+
deduped.append(n)
|
| 85 |
+
return deduped
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def model_extract(title, model, tokenizer, device):
|
| 89 |
+
"""用模型从标题中提取人名"""
|
| 90 |
+
chars = list(title)
|
| 91 |
+
ids = [tokenizer.cls_token_id]
|
| 92 |
+
for c in chars:
|
| 93 |
+
ids.extend(tokenizer.encode(c, add_special_tokens=False))
|
| 94 |
+
ids.append(tokenizer.sep_token_id)
|
| 95 |
+
|
| 96 |
+
input_ids = torch.tensor([ids], device=device)
|
| 97 |
+
mask = torch.ones_like(input_ids)
|
| 98 |
+
with torch.no_grad():
|
| 99 |
+
preds = model(input_ids, mask)[0]
|
| 100 |
+
preds = preds[1:1 + len(chars)]
|
| 101 |
+
|
| 102 |
+
names, cur = [], []
|
| 103 |
+
for char, lid in zip(title, preds):
|
| 104 |
+
tag = BIO_ID2LABEL.get(lid, "O")
|
| 105 |
+
if tag == "B-PER":
|
| 106 |
+
if cur:
|
| 107 |
+
names.append("".join(cur))
|
| 108 |
+
cur = [char]
|
| 109 |
+
elif tag == "I-PER" and cur:
|
| 110 |
+
cur.append(char)
|
| 111 |
+
else:
|
| 112 |
+
if cur:
|
| 113 |
+
names.append("".join(cur))
|
| 114 |
+
cur = []
|
| 115 |
+
if cur:
|
| 116 |
+
names.append("".join(cur))
|
| 117 |
+
return names
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def main():
|
| 121 |
+
if len(sys.argv) < 2:
|
| 122 |
+
print("用法: python extract.py <input.xlsx> [output.xlsx]")
|
| 123 |
+
sys.exit(1)
|
| 124 |
+
|
| 125 |
+
input_xlsx = sys.argv[1]
|
| 126 |
+
output_xlsx = sys.argv[2] if len(sys.argv) > 2 else "提取结果.xlsx"
|
| 127 |
+
use_frozen = "--frozen" in sys.argv or "--fc2" not in sys.argv # 默认 frozen
|
| 128 |
+
use_fc2 = "--fc2" in sys.argv
|
| 129 |
+
|
| 130 |
+
# 加载模型
|
| 131 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 132 |
+
if use_fc2:
|
| 133 |
+
model_name = "fc2"
|
| 134 |
+
elif use_frozen:
|
| 135 |
+
model_name = "frozen"
|
| 136 |
+
else:
|
| 137 |
+
model_name = "finetune"
|
| 138 |
+
print(f"加载模型 ({device}, {model_name})...")
|
| 139 |
+
model, tokenizer = load_model(device, frozen=use_frozen, fc2=use_fc2)
|
| 140 |
+
|
| 141 |
+
# 读取
|
| 142 |
+
print(f"读取: {input_xlsx}")
|
| 143 |
+
wb = openpyxl.load_workbook(input_xlsx)
|
| 144 |
+
ws = wb.active
|
| 145 |
+
|
| 146 |
+
# 输出 xlsx
|
| 147 |
+
out_wb = openpyxl.Workbook()
|
| 148 |
+
out_ws = out_wb.active
|
| 149 |
+
out_ws.append(["A��:原数据", "B列:提取人名", "C列:方法", "D列:姓名字数"])
|
| 150 |
+
|
| 151 |
+
stats = {"模型": 0, "未检出": 0}
|
| 152 |
+
|
| 153 |
+
for row in ws.iter_rows(min_row=2, min_col=1, max_col=12, values_only=True):
|
| 154 |
+
title = str(row[0]) if row[0] else ""
|
| 155 |
+
org = str(row[11]) if len(row) > 11 and row[11] else "" # L列=被许可对象
|
| 156 |
+
|
| 157 |
+
if not title:
|
| 158 |
+
continue
|
| 159 |
+
|
| 160 |
+
# 预处理: 用L列机构名清洗标题
|
| 161 |
+
clean_title = title
|
| 162 |
+
if org and org in title:
|
| 163 |
+
clean_title = title.replace(org, "").replace(" ", " ").strip()
|
| 164 |
+
|
| 165 |
+
names = model_extract(clean_title, model, tokenizer, device)
|
| 166 |
+
method = "模型" if names else "未检出"
|
| 167 |
+
stats[method] += 1
|
| 168 |
+
|
| 169 |
+
names = clean_names(names)
|
| 170 |
+
names = expand_bracket_names(names, title)
|
| 171 |
+
# 后处理: 检查提取人名是否在原标题中存在,不存在则丢弃
|
| 172 |
+
names = [n for n in names if n in title]
|
| 173 |
+
name_str = "、".join(names) if names else ""
|
| 174 |
+
name_len = "、".join(str(len(n)) for n in names) if names else "0"
|
| 175 |
+
out_ws.append([title, name_str, method, name_len])
|
| 176 |
+
|
| 177 |
+
out_wb.save(output_xlsx)
|
| 178 |
+
|
| 179 |
+
print(f"模型提取: {stats['模型']} 条")
|
| 180 |
+
print(f"未检出: {stats['未检出']} 条")
|
| 181 |
+
print(f"已保存: {output_xlsx}")
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
if __name__ == "__main__":
|
| 185 |
+
main()
|