File size: 6,063 Bytes
1ebb586
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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)