trungdang2901 commited on
Commit
8f85a32
·
verified ·
1 Parent(s): e8f878e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +355 -40
app.py CHANGED
@@ -1,46 +1,361 @@
1
- import gradio as gr
2
- import requests
3
  import json
 
 
 
 
 
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- url = 'https://api.coze.com/open_api/v2/chat'
7
- headers = {
8
- 'Authorization': 'Bearer pat_6d5X5P8ykxPvtmOzaetgOAWIn4YI5lX99YdUWwcEDT241YZYunmJQLo9Y5oToEpb',
9
- 'Content-Type': 'application/json',
10
- 'Accept': '*/*',
11
- 'Host': 'api.coze.com',
12
- 'Connection': 'keep-alive',
13
- }
14
- def print_like_dislike(x: gr.LikeData):
15
- print(x.index, x.value, x.liked)
16
-
17
 
18
- with gr.Blocks() as demo:
19
- chatbot = gr.Chatbot(
20
- elem_id="Xombot",
21
- bubble_full_width=False,
22
- label = "Xombot"
23
- )
24
- msg = gr.Textbox(interactive=True, placeholder="Đặt câu hỏi ở đây...", show_label=False)
25
- clear = gr.ClearButton([msg, chatbot])
26
-
27
- def respond(query, history):
28
- data = {
29
- "conversation_id": "123",
30
- "bot_id": "7369918746766868488",
31
- "user": "996999",
32
- "query": f"{query}",
33
- "stream": False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  }
35
- response = requests.post(url, headers=headers, data=json.dumps(data))
36
- response_json = response.json()
37
- for message in response_json['messages']:
38
- if message['role'] == 'assistant' and message['type'] == 'answer':
39
- history.append((query, message['content']))
40
- return "", history # message['content']
41
-
42
- msg.submit(respond, [msg, chatbot], [msg, chatbot])
43
- chatbot.like(print_like_dislike, None, None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
- if __name__ == "__main__":
46
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
 
2
  import json
3
+ import unicodedata
4
+ from collections import defaultdict
5
+ import numpy as np
6
+ import onnxruntime as ort
7
+ import gradio as gr
8
 
9
+ # =====================================================================
10
+ # CHUẨN ĐẶC TẢ BERT TOKENIZER (KEEP ORIGINAL LOGIC)
11
+ # =====================================================================
12
+ class PureBertTokenizer:
13
+ def __init__(self, vocab_file, do_lower_case=None):
14
+ self.vocab = {}
15
+ self.id_to_vocab = {}
16
+ with open(vocab_file, "r", encoding="utf-8") as f:
17
+ for idx, line in enumerate(f):
18
+ token = line.strip("\r\n")
19
+ self.vocab[token] = idx
20
+ self.id_to_vocab[idx] = token
21
+
22
+ if do_lower_case is None:
23
+ has_uppercase = any(any(c.isupper() for c in t) for t in self.vocab if not t.startswith("["))
24
+ self.do_lower_case = not has_uppercase
25
+ else:
26
+ self.do_lower_case = do_lower_case
27
 
28
+ def _clean_text(self, text):
29
+ output = []
30
+ for char in text:
31
+ cp = ord(char)
32
+ if cp == 0 or cp == 0xfffd or self._is_control(char):
33
+ continue
34
+ if self._is_whitespace(char):
35
+ output.append(" ")
36
+ else:
37
+ output.append(char)
38
+ return "".join(output)
39
 
40
+ def _is_whitespace(self, char):
41
+ if char in [" ", "\t", "\n", "\r"]:
42
+ return True
43
+ return unicodedata.category(char) == "Zs"
44
+
45
+ def _is_control(self, char):
46
+ if char in [" ", "\t", "\n", "\r"]:
47
+ return False
48
+ return unicodedata.category(char).startswith("C")
49
+
50
+ def _tokenize_chinese_chars(self, text):
51
+ output = []
52
+ for char in text:
53
+ if self._is_chinese_char(ord(char)):
54
+ output.append(" ")
55
+ output.append(char)
56
+ output.append(" ")
57
+ else:
58
+ output.append(char)
59
+ return "".join(output)
60
+
61
+ def _is_chinese_char(self, cp):
62
+ if ((cp >= 0x4E00 and cp <= 0x9FFF) or (cp >= 0x3400 and cp <= 0x4DBF) or
63
+ (cp >= 0x20000 and cp <= 0x2A6DF) or (cp >= 0x2A700 and cp <= 0x2B73F) or
64
+ (cp >= 0x2B740 and cp <= 0x2B81F) or (cp >= 0x2B820 and cp <= 0x2CEAF) or
65
+ (cp >= 0xF900 and cp <= 0xFAFF) or (cp >= 0x2F800 and cp <= 0x2FA1F)):
66
+ return True
67
+ return False
68
+
69
+ def _run_strip_accents(self, text):
70
+ text = unicodedata.normalize("NFD", text)
71
+ output = []
72
+ for char in text:
73
+ if unicodedata.category(char) == "Mn":
74
+ continue
75
+ output.append(char)
76
+ return "".join(output)
77
+
78
+ def _run_split_on_punc(self, text):
79
+ chars = list(text)
80
+ i = 0
81
+ start_new_token = True
82
+ output = []
83
+ while i < len(chars):
84
+ char = chars[i]
85
+ if self._is_punctuation(char):
86
+ output.append([char])
87
+ start_new_token = True
88
+ else:
89
+ if start_new_token:
90
+ output.append([])
91
+ start_new_token = False
92
+ output[-1].append(char)
93
+ i += 1
94
+ return ["".join(x) for x in output]
95
+
96
+ def _is_punctuation(self, char):
97
+ cp = ord(char)
98
+ if (33 <= cp <= 47) or (58 <= cp <= 64) or (91 <= cp <= 96) or (123 <= cp <= 126):
99
+ return True
100
+ return unicodedata.category(char).startswith("P")
101
+
102
+ def tokenize(self, text):
103
+ text = unicodedata.normalize("NFC", text)
104
+ text = self._clean_text(text)
105
+ text = self._tokenize_chinese_chars(text)
106
+
107
+ orig_tokens = text.split()
108
+ split_tokens = []
109
+ for token in orig_tokens:
110
+ if self.do_lower_case:
111
+ token = token.lower()
112
+ token = self._run_strip_accents(token)
113
+ split_tokens.extend(self._run_split_on_punc(token))
114
+
115
+ output_tokens = []
116
+ for token in split_tokens:
117
+ chars = list(token)
118
+ if len(chars) > 100:
119
+ output_tokens.append("[UNK]")
120
+ continue
121
+
122
+ is_bad = False
123
+ start = 0
124
+ sub_tokens = []
125
+ while start < len(chars):
126
+ end = len(chars)
127
+ cur_substr = None
128
+ while start < end:
129
+ substr = "".join(chars[start:end])
130
+ if start > 0:
131
+ substr = "##" + substr
132
+ if substr in self.vocab:
133
+ cur_substr = substr
134
+ break
135
+ end -= 1
136
+ if cur_substr is None:
137
+ is_bad = True
138
+ break
139
+ sub_tokens.append(cur_substr)
140
+ start = end
141
+
142
+ if is_bad:
143
+ output_tokens.append("[UNK]")
144
+ else:
145
+ output_tokens.extend(sub_tokens)
146
+
147
+ return output_tokens
148
+
149
+ def encode_batch(self, texts, max_length=512, padding=True, truncation=True):
150
+ batch_input_ids, batch_attention_mask, batch_token_type_ids = [], [], []
151
+ max_len_in_batch = 0
152
+ tokenized_batch = []
153
+
154
+ for text in texts:
155
+ tokens = self.tokenize(text)
156
+ if truncation and len(tokens) > max_length - 2:
157
+ tokens = tokens[:max_length - 2]
158
+
159
+ input_ids = (
160
+ [self.vocab["[CLS]"]] +
161
+ [self.vocab.get(t, self.vocab["[UNK]"]) for t in tokens] +
162
+ [self.vocab["[SEP]"]]
163
+ )
164
+ tokenized_batch.append(input_ids)
165
+ if len(input_ids) > max_len_in_batch:
166
+ max_len_in_batch = len(input_ids)
167
+
168
+ target_len = max_length if (padding and max_len_in_batch > max_length) else max_len_in_batch
169
+ if not padding:
170
+ target_len = max_len_in_batch
171
+
172
+ for input_ids in tokenized_batch:
173
+ if len(input_ids) > target_len:
174
+ input_ids = input_ids[:target_len]
175
+ pad_len = target_len - len(input_ids)
176
+
177
+ attention_mask = [1] * len(input_ids) + [0] * pad_len
178
+ token_type_ids = [0] * target_len
179
+ input_ids = input_ids + [self.vocab["[PAD]"]] * pad_len
180
+
181
+ batch_input_ids.append(input_ids)
182
+ batch_attention_mask.append(attention_mask)
183
+ batch_token_type_ids.append(token_type_ids)
184
+
185
+ return {
186
+ "input_ids": np.array(batch_input_ids, dtype=np.int64),
187
+ "attention_mask": np.array(batch_attention_mask, dtype=np.int64),
188
+ "token_type_ids": np.array(batch_token_type_ids, dtype=np.int64)
189
+ }
190
+
191
+ def convert_ids_to_tokens(self, ids):
192
+ return [self.id_to_vocab.get(int(i), "[UNK]") for i in ids]
193
+
194
+
195
+ def join_bert_tokens(token_list):
196
+ text = ""
197
+ for token in token_list:
198
+ if token.startswith("##"):
199
+ text += token[2:]
200
+ else:
201
+ if text and ('\u4e00' <= token <= '\u9fff' or ('\u4e00' <= text[-1] <= '\u9fff')):
202
+ text += token
203
+ else:
204
+ text += (" " if text else "") + token
205
+ return text.strip()
206
+
207
+
208
+ # =====================================================================
209
+ # KHỞI TẠO MÔ HÌNH TOÀN CỤC (LOAD ONCE)
210
+ # =====================================================================
211
+ ONNX_MODEL_PATH = "bert_ner_fp32.onnx"
212
+ VOCAB_FILE = "vocab.txt"
213
+ CONFIG_FILE = "config.json"
214
+ BATCH_SIZE = 64
215
+ MAX_LENGTH = 512
216
+
217
+ # Kiểm tra file cấu hình bắt buộc trước khi load ứng dụng
218
+ if not all(os.path.exists(f) for f in [ONNX_MODEL_PATH, VOCAB_FILE, CONFIG_FILE]):
219
+ raise FileNotFoundError("Thiếu file bert_ner_fp32.onnx, vocab.txt hoặc config.json ở thư mục hiện tại!")
220
+
221
+ tokenizer = PureBertTokenizer(VOCAB_FILE)
222
+
223
+ with open(CONFIG_FILE, "r", encoding="utf-8") as f:
224
+ config_data = json.load(f)
225
+ id2label = {int(k): v for k, v in config_data["id2label"].items()}
226
+
227
+ sess_options = ort.SessionOptions()
228
+ sess_options.intra_op_num_threads = 0
229
+ sess_options.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL
230
+ sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
231
+ session = ort.InferenceSession(ONNX_MODEL_PATH, sess_options, providers=["CPUExecutionProvider"])
232
+
233
+
234
+ # =====================================================================
235
+ # HÀM XỬ LÝ GRADIO INFERENCE
236
+ # =====================================================================
237
+ def ner_inference(file_obj, selected_entities, min_count):
238
+ if file_obj is None:
239
+ return "Vui lòng tải lên một file dữ liệu dạng văn bản (.txt)"
240
+
241
+ # Đọc dữ liệu đầu vào từ file tạm do Gradio tạo ra
242
+ with open(file_obj.name, "r", encoding="utf-8") as f:
243
+ lines = [line.strip() for line in f if line.strip()]
244
+
245
+ if not lines:
246
+ return "File được tải lên không có dữ liệu văn bản hợp lệ."
247
+
248
+ lines.sort(key=len)
249
+ batches = [lines[i:i + BATCH_SIZE] for i in range(0, len(lines), BATCH_SIZE)]
250
+ entity_count = defaultdict(int)
251
+
252
+ # Vòng lặp Inference từng batch
253
+ for batch_lines in batches:
254
+ encoded = tokenizer.encode_batch(batch_lines, padding=True, truncation=True, max_length=MAX_LENGTH)
255
+
256
+ ort_inputs = {
257
+ "input_ids": encoded["input_ids"],
258
+ "attention_mask": encoded["attention_mask"],
259
+ "token_type_ids": encoded["token_type_ids"]
260
  }
261
+
262
+ ort_outputs = session.run(["logits"], ort_inputs)
263
+ logits = ort_outputs[0]
264
+ predictions = np.argmax(logits, axis=-1)
265
+
266
+ for i in range(len(batch_lines)):
267
+ input_ids_seq = encoded["input_ids"][i]
268
+ attention_mask_seq = encoded["attention_mask"][i]
269
+ pred_seq = predictions[i]
270
+
271
+ tokens = tokenizer.convert_ids_to_tokens(input_ids_seq)
272
+ current_entity_tokens = []
273
+ current_entity_label = None
274
+
275
+ for token, mask, label_id in zip(tokens, attention_mask_seq, pred_seq):
276
+ if mask == 0 or token == "[SEP]":
277
+ break
278
+ if token == "[CLS]":
279
+ continue
280
+
281
+ label = id2label[label_id]
282
+ prefix = label.split("-")[0] if "-" in label else label
283
+ ent_type = label.split("-")[1] if "-" in label else None
284
+
285
+ if prefix in ["B", "S", "O"] or (prefix in ["M", "I", "E"] and ent_type != current_entity_label):
286
+ current_entity_tokens = []
287
+ current_entity_label = None
288
+
289
+ if prefix == "S":
290
+ final_name = join_bert_tokens([token])
291
+ if final_name:
292
+ entity_count[(final_name, ent_type)] += 1
293
+ elif prefix == "B":
294
+ current_entity_label = ent_type
295
+ current_entity_tokens.append(token)
296
+ elif prefix in ["M", "I"]:
297
+ if current_entity_label == ent_type:
298
+ current_entity_tokens.append(token)
299
+ elif prefix == "E":
300
+ if current_entity_label == ent_type:
301
+ current_entity_tokens.append(token)
302
+ final_name = join_bert_tokens(current_entity_tokens)
303
+ if final_name:
304
+ entity_count[(final_name, current_entity_label)] += 1
305
+ current_entity_tokens = []
306
+ current_entity_label = None
307
+
308
+ # Lọc thực thể dựa trên cài đặt UI
309
+ output_lines = []
310
+ for (name, label), count in entity_count.items():
311
+ if count >= min_count and (not selected_entities or label in selected_entities):
312
+ output_lines.append(f"{name}={label}={count}")
313
+
314
+ if not output_lines:
315
+ return "Không tìm thấy thực thể nào tương ứng với các bộ lọc hiện tại."
316
+
317
+ return "\n".join(output_lines)
318
+
319
+
320
+ # =====================================================================
321
+ # THIẾT KẾ GIAO DIỆN GRADIO
322
+ # =====================================================================
323
+ css = "h1 { text-align: center; color: #2D3748; }"
324
+
325
+ with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
326
+ gr.Markdown("# BERT NER Inference Engine (ONNX Pure Python)")
327
+ gr.Markdown("Tải lên file văn bản `.txt` để trích xuất thực thể tên riêng theo mong muốn bằng mô hình ONNX.")
328
 
329
+ with gr.Row():
330
+ with gr.Column(scale=1):
331
+ input_file = gr.File(label="Upload File (.txt)", file_types=[".txt"])
332
+ entity_filter = gr.CheckboxGroup(
333
+ label="Entities Filter",
334
+ choices=["PER", "ORG", "LOC", "GPE"],
335
+ value=["PER", "ORG", "LOC", "GPE"]
336
+ )
337
+ count_entities = gr.Number(
338
+ label="Min Frequency Threshold",
339
+ minimum=1,
340
+ maximum=50,
341
+ step=1,
342
+ value=1
343
+ )
344
+ submit_btn = gr.Button("Extract Entities", variant="primary")
345
+
346
+ with gr.Column(scale=1):
347
+ output_text = gr.Textbox(
348
+ label="Output Results",
349
+ show_copy_button=True,
350
+ interactive=False,
351
+ lines=15,
352
+ max_lines=25
353
+ )
354
+
355
+ submit_btn.click(
356
+ fn=ner_inference,
357
+ inputs=[input_file, entity_filter, count_entities],
358
+ outputs=[output_text]
359
+ )
360
+
361
+ demo.launch()