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)