FakeAD-Detector / myapp /step2_semantic.py
sungho7373's picture
Initial commit: clean code without secrets
1ebb586
Raw History Blame Contribute Delete
6.06 kB
import torch
import torch.nn.functional as F
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import gc
class SemanticAnalyzer:
def __init__(self):
print("๐Ÿง  [Step 2] ์˜๋ฏธ๋ก ์  ๋”ฅ๋Ÿฌ๋‹ ๋ถ„์„๊ธฐ(KoELECTRA)๋ฅผ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค...")
# M1 Max GPU(MPS) ์„ธํŒ…
self.device = torch.device("mps" if torch.backends.mps.is_available() else "cpu")
# ๊ฐ€๋ณ๊ณ  ์„ฑ๋Šฅ์ด ๋›ฐ์–ด๋‚œ KoELECTRA ๋ชจ๋ธ ๋กœ๋“œ
self.model_name = "monologg/koelectra-base-v3-discriminator"
self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
# ๐ŸŒŸ ํ•ต์‹ฌ: output_hidden_states=True ์˜ต์…˜์„ ์ผœ์•ผ
# ๋ถ„๋ฅ˜ Logit๊ณผ ์ฝ”์‚ฌ์ธ ์œ ์‚ฌ๋„์šฉ Vector๋ฅผ ํ•œ ๋ฒˆ์˜ ์—ฐ์‚ฐ์œผ๋กœ ๋‘˜ ๋‹ค ๋ฝ‘์•„๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!
self.model = AutoModelForSequenceClassification.from_pretrained(
self.model_name,
num_labels=2, # 0: ์ •์ƒ, 1: ๊ณผ๋Œ€๊ด‘๊ณ 
output_hidden_states=True
).to(self.device)
self.model.eval() # ์ถ”๋ก  ๋ชจ๋“œ ์ „ํ™˜
# [๋ฒกํ„ฐ A ๊ตฌ์ถ•] ๊ธฐ์กด์— ์ ๋ฐœ๋œ ํ—ˆ์œ„๊ด‘๊ณ  ๋ ˆํผ๋Ÿฐ์Šค ๋ฌธ์žฅ๋“ค (์˜ˆ์‹œ)
self.reference_bad_texts = [
"๋‹จ ์ผ์ฃผ์ผ ๋งŒ์— ์ง€๋ฐฉ์ด 100% ๋ถ„ํ•ด๋˜๋Š” ๊ธฐ์ ์˜ ํฌ๋ฆผ",
"์‹์•ฝ์ฒ˜์—์„œ ์ธ์ฆํ•œ ๋งŒ๋ณ‘ํ†ต์น˜์•ฝ, ์•”์„ธํฌ ์™„๋ฒฝ ์ œ๊ฑฐ",
"์ด๊ฒƒ๋งŒ ๋จน์œผ๋ฉด ๋…์†Œ๊ฐ€ ๋ฐฐ์ถœ๋˜๊ณ  ์„ธํฌ๊ฐ€ ์ฆ‰๊ฐ ์žฌ์ƒ๋ฉ๋‹ˆ๋‹ค",
"์˜์‚ฌ๋“ค์ด ๋ฌด์กฐ๊ฑด ์ถ”์ฒœํ•˜๋Š” ๋ถ€์ž‘์šฉ ์—†๋Š” ์™„์น˜์ œ"
]
print(" -> ๋ ˆํผ๋Ÿฐ์Šค ํ—ˆ์œ„๊ด‘๊ณ  ๋ฌธ์žฅ๋“ค์„ ๋ฒกํ„ฐ(Vector) ๊ณต๊ฐ„์— ๋ฐฐ์น˜ ์ค‘...")
self.reference_embeddings = self._get_embeddings(self.reference_bad_texts)
def clear_memory(self):
"""๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€์šฉ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜"""
gc.collect()
if torch.backends.mps.is_available():
torch.mps.empty_cache()
def _get_embeddings(self, texts):
"""ํ…์ŠคํŠธ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ž…๋ ฅ๋ฐ›์•„ ๋ฌธ์žฅ ์ž„๋ฒ ๋”ฉ ๋ฒกํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค."""
inputs = self.tokenizer(texts, padding=True, truncation=True, max_length=512, return_tensors="pt").to(self.device)
with torch.no_grad():
outputs = self.model(**inputs)
# ๋งˆ์ง€๋ง‰ ๋ ˆ์ด์–ด์˜ hidden states์—์„œ [CLS] ํ† ํฐ(์ธ๋ฑ์Šค 0)์˜ ๋ฒกํ„ฐ๋ฅผ ๋ฌธ์žฅ ๋Œ€ํ‘œ ๋ฒกํ„ฐ๋กœ ์‚ฌ์šฉ
sentence_embeddings = outputs.hidden_states[-1][:, 0, :]
return sentence_embeddings
def calculate_x2_score(self, text):
if not text or len(text.strip()) < 5:
return 0.0
print("\n๐Ÿง  [Step 2] ๋”ฅ๋Ÿฌ๋‹ ๋ฌธ๋งฅ ๋ฐ ์œ ์‚ฌ๋„(Semantic) ๋ถ„์„ ์‹œ์ž‘...")
# ๋„ˆ๋ฌด ๊ธด ํ…์ŠคํŠธ๋Š” PLM ํ•œ๊ณ„(512ํ† ํฐ)์— ๊ฑธ๋ฆฌ๋ฏ€๋กœ ์ž˜๋ผ์„œ ์ž…๋ ฅ
inputs = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=512).to(self.device)
with torch.no_grad(): # ์—ญ์ „ํŒŒ(ํ•™์Šต) ์—ฐ์‚ฐ์„ ๊บผ์„œ M1 ๋ฉ”๋ชจ๋ฆฌ ์ ˆ์•ฝ
outputs = self.model(**inputs)
# =========================================================
# [์ˆ˜์‹ 1] ๋ถ„๋ฅ˜ ํ™•๋ฅ  (Softmax) : X2
# =========================================================
logits = outputs.logits # ๋ชจ๋ธ์˜ ๋‚ ๊ฒƒ ์ถœ๋ ฅ๊ฐ’ (z)
probs = F.softmax(logits, dim=-1) # Softmax ์ ์šฉ: e^z / sum(e^z)
# ๋ผ๋ฒจ 1(๊ณผ๋Œ€๊ด‘๊ณ )์— ํ•ด๋‹นํ•˜๋Š” ํ™•๋ฅ ๊ฐ’ (0.0 ~ 1.0)
prob_fake = probs[0][1].item()
# =========================================================
# [์ˆ˜์‹ 2] ์˜๋ฏธ๋ก ์  ์œ ์‚ฌ๋„ (Cosine Similarity)
# =========================================================
# ํ˜„์žฌ ๊ฒ€์‚ฌ ์ค‘์ธ ํ…์ŠคํŠธ์˜ ๋ฒกํ„ฐ B ์ถ”์ถœ
current_embedding = outputs.hidden_states[-1][:, 0, :]
# ๋ ˆํผ๋Ÿฐ์Šค ๋ฒกํ„ฐ A๋“ค๊ณผ ๋ฒกํ„ฐ B์˜ ์œ ์‚ฌ๋„ ๊ณ„์‚ฐ: (A * B) / (|A| * |B|)
similarities = F.cosine_similarity(current_embedding, self.reference_embeddings)
# ๊ฐ€์žฅ ๋ฌธ๋งฅ์ด ๋น„์Šทํ•˜๋‹ค๊ณ  ํŒ์ •๋œ ๋ ˆํผ๋Ÿฐ์Šค์™€์˜ ์ตœ๊ณ  ์œ ์‚ฌ๋„ ์ ์ˆ˜
max_sim = torch.max(similarities).item()
# ๋ถ„์„์ด ๋๋‚˜๋ฉด ์ฆ‰์‹œ VRAM ๋ฐ˜ํ™˜
del inputs, outputs, logits, probs, current_embedding, similarities
self.clear_memory()
# =========================================================
# ์ตœ์ข… X2 ์Šค์ฝ”์–ด ์‚ฐ์ถœ (๋ถ„๋ฅ˜ ํ™•๋ฅ  + ์œ ์‚ฌ๋„ ์•™์ƒ๋ธ”)
# =========================================================
# ๋ถ„๋ฅ˜๊ธฐ ํ™•๋ฅ ์„ 100์  ๋งŒ์ ์œผ๋กœ ๋ณ€ํ™˜
classification_score = prob_fake * 100
# ์ฝ”์‚ฌ์ธ ์œ ์‚ฌ๋„๋ฅผ 100์  ๋งŒ์ ์œผ๋กœ ๋ณ€ํ™˜ (์œ ์‚ฌ๋„๊ฐ€ 0 ์ดํ•˜๋ฉด 0์  ์ฒ˜๋ฆฌ)
similarity_score = max(max_sim, 0) * 100
# ๐Ÿ’ก [์‹ค๋ฌด ํŒ] ํ˜ผํ•ฉ ๊ฐ€์ค‘์น˜ ์ ์šฉ
# ํ˜„์žฌ KoELECTRA ๋ชจ๋ธ์€ '๊ณผ๋Œ€๊ด‘๊ณ  ์ „์šฉ'์œผ๋กœ ํŒŒ์ธํŠœ๋‹ ๋˜์ง€ ์•Š์€ ์Œฉ์–ผ ์ƒํƒœ์ž…๋‹ˆ๋‹ค.
# ๋”ฐ๋ผ์„œ Softmax ๋ถ„๋ฅ˜ ํ™•๋ฅ ์€ ๋žœ๋ค์— ๊ฐ€๊น๊ณ , ์ฝ”์‚ฌ์ธ ์œ ์‚ฌ๋„๊ฐ€ ํ›จ์”ฌ ์ •ํ™•ํ•ฉ๋‹ˆ๋‹ค.
# ์œ ์‚ฌ๋„ ์ ์ˆ˜์— 80%, ๋ถ„๋ฅ˜ ์ ์ˆ˜์— 20% ๊ฐ€์ค‘์น˜๋ฅผ ์ฃผ์–ด ์ตœ์ข… ์ ์ˆ˜๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
x2_score = (similarity_score * 0.8) + (classification_score * 0.2)
print(f" -> ๐Ÿ“Š ๋ชจ๋ธ ๋ถ„๋ฅ˜ ํ™•๋ฅ  (Softmax): {prob_fake*100:.1f}%")
print(f" -> ๐Ÿ”— ์ตœ๋Œ€ ๋ฌธ๋งฅ ์œ ์‚ฌ๋„ (Cosine Sim): {max_sim*100:.1f}%")
print(f"๐Ÿ“ˆ ์ตœ์ข… X2 ์ ์ˆ˜ (0~100): {x2_score:.2f}์ ")
return x2_score
# ==========================================
# ๋‹จ๋… ํ…Œ์ŠคํŠธ ์ฝ”๋“œ
# ==========================================
if __name__ == "__main__":
analyzer = SemanticAnalyzer()
# ๋ ˆํผ๋Ÿฐ์Šค("๋‹จ ์ผ์ฃผ์ผ ๋งŒ์—...")์™€ ์˜๋ฏธ๊ฐ€ ์œ ์‚ฌํ•œ ๋ฌธ์žฅ ํ…Œ์ŠคํŠธ
test_text = "๋‹จ 7์ผ๋งŒ ํˆฌ์žํ•˜์„ธ์š”! ์ง€๋ฐฉ์ด ์™„์ „ํžˆ ํŒŒ๊ดด๋˜๋Š” ๋†€๋ผ์šด ๋งˆ๋ฒ•์„ ๊ฒช๊ฒŒ ๋ฉ๋‹ˆ๋‹ค."
analyzer.calculate_x2_score(test_text)