Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
@@ -1,3 +1,6 @@
|
|
|
|
|
|
|
|
1 |
|
2 |
school_name_candidates = []
|
3 |
|
@@ -14,28 +17,19 @@ def mask_school_names(text):
|
|
14 |
else:
|
15 |
return full
|
16 |
|
17 |
-
# 붙어 있는 학교명 (수원매화초등학교)
|
18 |
text = re.sub(r"(\b[가-힣]{2,20})(초등학교|중학교|고등학교)", replacer, text)
|
19 |
|
20 |
-
# 후처리: 띄어쓰기 있는 패턴 (수원 매화 초등학교 등)
|
21 |
for name in school_name_candidates:
|
22 |
pattern = rf"{re.escape(name)}\s?(초등학교|중학교|고등학교)"
|
23 |
text = re.sub(pattern, to_chosung(name) + " " + r"\1", text)
|
24 |
return text
|
25 |
|
26 |
-
|
27 |
-
import gradio as gr
|
28 |
-
from transformers import AutoTokenizer, AutoModelForTokenClassification, pipeline
|
29 |
-
import re
|
30 |
-
|
31 |
-
# 모델 초기화
|
32 |
model_name = "Leo97/KoELECTRA-small-v3-modu-ner"
|
33 |
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
34 |
model = AutoModelForTokenClassification.from_pretrained(model_name)
|
35 |
ner_pipeline = pipeline("ner", model=model, tokenizer=tokenizer, grouped_entities=True)
|
36 |
|
37 |
def extract_names(text):
|
38 |
-
# 1. 기존 NER 기반 추출
|
39 |
results = ner_pipeline(text)
|
40 |
names = []
|
41 |
for entity in results:
|
@@ -44,19 +38,13 @@ def extract_names(text):
|
|
44 |
if len(name) >= 2 and name not in names:
|
45 |
names.append(name)
|
46 |
|
47 |
-
# 2. 직함 기반 이름 추출 보강
|
48 |
title_suffixes = [
|
49 |
-
# 회사 직함
|
50 |
'대표', '이사', '전무', '상무', '부장', '차장', '과장', '대리', '사원', '실장', '팀장', '소장', '국장', '본부장',
|
51 |
-
# 교육 관련
|
52 |
'선생님', '교사', '교장', '교감', '부교장', '조교수', '교수', '연구원', '박사', '석사', '학사',
|
53 |
-
# 학생 관련
|
54 |
'학생', '고등학생', '중학생', '초등학생', '학부모', '수험생',
|
55 |
-
# 기타 사회 호칭
|
56 |
'주임', '총무', '회장', '부회장', '사무장', '간호사', '의사', '원장', '기사님', '매니저', '지점장'
|
57 |
]
|
58 |
|
59 |
-
# ex: 김과장, 이선생님, 박학생 등 추출
|
60 |
pattern = r'\b([가-힣]{2,4})(' + '|'.join(title_suffixes) + r')\b'
|
61 |
matches = re.findall(pattern, text)
|
62 |
for match in matches:
|
@@ -66,7 +54,6 @@ def extract_names(text):
|
|
66 |
|
67 |
return names
|
68 |
|
69 |
-
|
70 |
def refactored_mask_names(original_text, names, start_counter=100):
|
71 |
korean_josa = ['이가','를','은','는','을','도','만','과','와','에게','에서','으로',
|
72 |
'까지','조차','마저','이며','이다','이나','이나마','밖에','이든','이라도',
|
@@ -109,16 +96,32 @@ def to_chosung(text):
|
|
109 |
result += ch
|
110 |
return result
|
111 |
|
112 |
-
|
113 |
-
|
114 |
def mask_department(text):
|
115 |
text = re.sub(r"([가-힣]{2,20}학과)", lambda m: to_chosung(m.group(1)[:-2]) + "학과", text)
|
116 |
return text
|
117 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
|
119 |
def sanitize_sensitive_info(text, keyword_string, replace_word):
|
120 |
text = mask_school_names(text)
|
121 |
text = mask_department(text)
|
|
|
|
|
122 |
text = re.sub(r"(\d)학년(\s?(\d)반)?", lambda m: "*학년" + (" *반" if m.group(3) else ""), text)
|
123 |
text = re.sub(r"(\d)학년\s?(\d)반", r"*학년 *반", text)
|
124 |
|
@@ -134,10 +137,15 @@ def sanitize_sensitive_info(text, keyword_string, replace_word):
|
|
134 |
text = re.sub(r"[\w\.-]+@[\w\.-]+", r"******@****", text)
|
135 |
text = re.sub(r"(\d{6})[-](\d)\d{6}", r"*******-\2*****", text)
|
136 |
text = re.sub(r"([가-힣]+(대로|로|길))\s?(\d+)(호|번길|가)?", r"\1 ***", text)
|
137 |
-
text = re.sub(r"(\d{2,6})[-]?(\d{2,6})[-]?(\d{2,6})",
|
138 |
-
|
139 |
-
text = re.sub(r"(\d{
|
140 |
-
|
|
|
|
|
|
|
|
|
|
|
141 |
return text
|
142 |
|
143 |
def final_name_remask_exact_only(text, mapping_dict):
|
@@ -169,7 +177,6 @@ with gr.Blocks() as demo:
|
|
169 |
gr.Markdown("""
|
170 |
🛡️ **민감정보 마스킹 [땡땡이 마스킹]**
|
171 |
이름 + 민감정보 + 초/중/고 마스킹기 (초성 기반)
|
172 |
-
|
173 |
⚠️ *완벽하지 않을 수 있습니다. 반드시 직접 최종 점검하세요.*
|
174 |
""")
|
175 |
input_text = gr.Textbox(lines=15, label="📥 원본 텍스트 입력")
|
@@ -178,7 +185,7 @@ with gr.Blocks() as demo:
|
|
178 |
run_button = gr.Button("🚀 마스킹 실행")
|
179 |
masked_output = gr.Textbox(lines=15, label="🔐 마스킹된 텍스트")
|
180 |
mapping_output = gr.Textbox(lines=10, label="🏷️ 이름 태그 매핑", interactive=False)
|
181 |
-
|
182 |
run_button.click(fn=apply_masking, inputs=[input_text, keyword_input, replace_input], outputs=[masked_output, mapping_output])
|
183 |
-
|
184 |
demo.launch()
|
|
|
1 |
+
import re
|
2 |
+
import gradio as gr
|
3 |
+
from transformers import AutoTokenizer, AutoModelForTokenClassification, pipeline
|
4 |
|
5 |
school_name_candidates = []
|
6 |
|
|
|
17 |
else:
|
18 |
return full
|
19 |
|
|
|
20 |
text = re.sub(r"(\b[가-힣]{2,20})(초등학교|중학교|고등학교)", replacer, text)
|
21 |
|
|
|
22 |
for name in school_name_candidates:
|
23 |
pattern = rf"{re.escape(name)}\s?(초등학교|중학교|고등학교)"
|
24 |
text = re.sub(pattern, to_chosung(name) + " " + r"\1", text)
|
25 |
return text
|
26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
model_name = "Leo97/KoELECTRA-small-v3-modu-ner"
|
28 |
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
29 |
model = AutoModelForTokenClassification.from_pretrained(model_name)
|
30 |
ner_pipeline = pipeline("ner", model=model, tokenizer=tokenizer, grouped_entities=True)
|
31 |
|
32 |
def extract_names(text):
|
|
|
33 |
results = ner_pipeline(text)
|
34 |
names = []
|
35 |
for entity in results:
|
|
|
38 |
if len(name) >= 2 and name not in names:
|
39 |
names.append(name)
|
40 |
|
|
|
41 |
title_suffixes = [
|
|
|
42 |
'대표', '이사', '전무', '상무', '부장', '차장', '과장', '대리', '사원', '실장', '팀장', '소장', '국장', '본부장',
|
|
|
43 |
'선생님', '교사', '교장', '교감', '부교장', '조교수', '교수', '연구원', '박사', '석사', '학사',
|
|
|
44 |
'학생', '고등학생', '중학생', '초등학생', '학부모', '수험생',
|
|
|
45 |
'주임', '총무', '회장', '부회장', '사무장', '간호사', '의사', '원장', '기사님', '매니저', '지점장'
|
46 |
]
|
47 |
|
|
|
48 |
pattern = r'\b([가-힣]{2,4})(' + '|'.join(title_suffixes) + r')\b'
|
49 |
matches = re.findall(pattern, text)
|
50 |
for match in matches:
|
|
|
54 |
|
55 |
return names
|
56 |
|
|
|
57 |
def refactored_mask_names(original_text, names, start_counter=100):
|
58 |
korean_josa = ['이가','를','은','는','을','도','만','과','와','에게','에서','으로',
|
59 |
'까지','조차','마저','이며','이다','이나','이나마','밖에','이든','이라도',
|
|
|
96 |
result += ch
|
97 |
return result
|
98 |
|
|
|
|
|
99 |
def mask_department(text):
|
100 |
text = re.sub(r"([가-힣]{2,20}학과)", lambda m: to_chosung(m.group(1)[:-2]) + "학과", text)
|
101 |
return text
|
102 |
|
103 |
+
def mask_general_human_terms(text):
|
104 |
+
human_terms = [
|
105 |
+
'엄마', '아빠', '어머니', '아버지', '부모', '부모님', '자식', '아들', '딸',
|
106 |
+
'할아버지', '할머니', '외할아버지', '외할머니',
|
107 |
+
'형', '누나', '오빠', '언니', '동생', '형제', '자매',
|
108 |
+
'이모', '고모', '삼촌', '외삼촌', '숙모', '고모부', '이모부', '조카', '손자', '손녀', '사촌',
|
109 |
+
'사위', '며느리', '장모', '장인', '처제', '시누이', '형수', '제수씨', '매형', '올케',
|
110 |
+
'아동', '아이', '학생', '주민', '피해자', '당사자', '보호자', '가족',
|
111 |
+
r'[가-힣]{1,3}씨', r'[가-힣]{1,3}님', r'[가-힣]{1,3}양', r'[가-힣]{1,3}군', r'[가-힣]{1,3}어르신'
|
112 |
+
]
|
113 |
+
|
114 |
+
for term in human_terms:
|
115 |
+
pattern = rf'\b{term}\b'
|
116 |
+
text = re.sub(pattern, '○○○', text)
|
117 |
+
|
118 |
+
return text
|
119 |
|
120 |
def sanitize_sensitive_info(text, keyword_string, replace_word):
|
121 |
text = mask_school_names(text)
|
122 |
text = mask_department(text)
|
123 |
+
text = mask_general_human_terms(text)
|
124 |
+
|
125 |
text = re.sub(r"(\d)학년(\s?(\d)반)?", lambda m: "*학년" + (" *반" if m.group(3) else ""), text)
|
126 |
text = re.sub(r"(\d)학년\s?(\d)반", r"*학년 *반", text)
|
127 |
|
|
|
137 |
text = re.sub(r"[\w\.-]+@[\w\.-]+", r"******@****", text)
|
138 |
text = re.sub(r"(\d{6})[-](\d)\d{6}", r"*******-\2*****", text)
|
139 |
text = re.sub(r"([가-힣]+(대로|로|길))\s?(\d+)(호|번길|가)?", r"\1 ***", text)
|
140 |
+
text = re.sub(r"(\d{2,6})[-]?(\d{2,6})[-]?(\d{2,6})",
|
141 |
+
lambda m: f"{m.group(1)[:2]}{'*'*(len(m.group(1))-2)}{'*'*len(m.group(2))}{m.group(3)[-4:]}", text)
|
142 |
+
text = re.sub(r"(\d{4})[- ]?(\d{4})[- ]?(\d{4})[- ]?(\d{4})",
|
143 |
+
lambda m: f"{m.group(1)}-****-****-{m.group(4)}", text)
|
144 |
+
text = re.sub(r"(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})",
|
145 |
+
lambda m: f"{m.group(1)}.{m.group(2)}.*.*", text)
|
146 |
+
text = re.sub(r"([가-힣]{1,10})(은행|동|로|길)\s?([\d\-]{4,})",
|
147 |
+
lambda m: m.group(1) + m.group(2) + " " + re.sub(r"\d", "*", m.group(3)), text)
|
148 |
+
|
149 |
return text
|
150 |
|
151 |
def final_name_remask_exact_only(text, mapping_dict):
|
|
|
177 |
gr.Markdown("""
|
178 |
🛡️ **민감정보 마스킹 [땡땡이 마스킹]**
|
179 |
이름 + 민감정보 + 초/중/고 마스킹기 (초성 기반)
|
|
|
180 |
⚠️ *완벽하지 않을 수 있습니다. 반드시 직접 최종 점검하세요.*
|
181 |
""")
|
182 |
input_text = gr.Textbox(lines=15, label="📥 원본 텍스트 입력")
|
|
|
185 |
run_button = gr.Button("🚀 마스킹 실행")
|
186 |
masked_output = gr.Textbox(lines=15, label="🔐 마스킹된 텍스트")
|
187 |
mapping_output = gr.Textbox(lines=10, label="🏷️ 이름 태그 매핑", interactive=False)
|
188 |
+
|
189 |
run_button.click(fn=apply_masking, inputs=[input_text, keyword_input, replace_input], outputs=[masked_output, mapping_output])
|
190 |
+
|
191 |
demo.launch()
|