warfbro commited on
Commit
91e2986
·
verified ·
1 Parent(s): b0132ae

Upload extract.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. 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()