File size: 5,471 Bytes
0dc6803 | 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 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | import os
import sys
import pickle
from pathlib import Path
import numpy as np
import torch
import torch.nn as nn
from transformers import AutoTokenizer, AutoModelForSequenceClassification
# ๊ธฐ๋ณธ ๊ฒฝ๋ก ์ค์
BASE_DIR = Path(__file__).resolve().parent
MODEL_DIR = BASE_DIR / "saved_models"
KC_DIR = MODEL_DIR / "kcbert_web"
DEBERTA_DIR = MODEL_DIR / "deberta_web"
CNN_PATH = MODEL_DIR / "char_cnn_web.pt"
VOCAB_PATH = MODEL_DIR / "vocab.pkl"
# Char-CNN ๋คํธ์ํฌ ๊ตฌ์กฐ ์ ์
class MultiScaleCharCNNEncoder(nn.Module):
def __init__(self, vocab_size, embed_dim, num_filters, filter_sizes):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
self.convs = nn.ModuleList([
nn.Conv1d(embed_dim, num_filters, kernel_size=fs)
for fs in filter_sizes
])
self.fc = nn.Linear(len(filter_sizes) * num_filters, 256)
def forward(self, x):
x = self.embedding(x).transpose(1, 2)
conved = [torch.relu(conv(x)) for conv in self.convs]
pooled = [torch.max_pool1d(c, c.shape[2]).squeeze(2) for c in conved]
return self.fc(torch.cat(pooled, dim=1))
class SiameseNetwork(nn.Module):
def __init__(self, encoder):
super().__init__()
self.encoder = encoder
self.classifier = nn.Sequential(
nn.Linear(256 * 2, 128),
nn.ReLU(),
nn.Linear(128, 2)
)
def forward(self, a, b):
a_vec = self.encoder(a)
b_vec = self.encoder(b)
return self.classifier(torch.cat((a_vec, b_vec), dim=1))
# ํ์ผ ์กด์ฌ ๊ฒ์ฆ
def check_model_files():
required_paths = {
"KcBERT ๋ชจ๋ธ ํด๋": KC_DIR,
"DeBERTa ๋ชจ๋ธ ํด๋": DEBERTA_DIR,
"Char-CNN ๊ฐ์ค์น": CNN_PATH,
"๋ฌธ์ vocab ํ์ผ": VOCAB_PATH,
}
missing = [f"{name}: {path}" for name, path in required_paths.items() if not path.exists()]
if missing:
raise FileNotFoundError("ํ์ ๋ชจ๋ธ ํ์ผ ๋๋ ํด๋๊ฐ ์์ต๋๋ค.\n\n" + "\n".join(missing))
# ๋ชจ๋ธ ๋ก๋ ํต์ฌ ํจ์
def load_all_models():
check_model_files()
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
with open(VOCAB_PATH, "rb") as f:
vocab = pickle.load(f)
# 1. KcBERT
kc_tokenizer = AutoTokenizer.from_pretrained(str(KC_DIR), local_files_only=True)
kc_model = AutoModelForSequenceClassification.from_pretrained(str(KC_DIR), local_files_only=True).to(device)
kc_model.eval()
# 2. DeBERTa
deberta_tokenizer = AutoTokenizer.from_pretrained(str(DEBERTA_DIR), local_files_only=True)
deberta_model = AutoModelForSequenceClassification.from_pretrained(str(DEBERTA_DIR), local_files_only=True).to(device)
deberta_model.eval()
# 3. Char-CNN
cnn_encoder = MultiScaleCharCNNEncoder(vocab_size=len(vocab), embed_dim=128, num_filters=128, filter_sizes=[3, 5, 7])
cnn_model = SiameseNetwork(cnn_encoder).to(device)
cnn_model.load_state_dict(torch.load(str(CNN_PATH), map_location=device))
cnn_model.eval()
return {
"device": device,
"vocab": vocab,
"kc_tokenizer": kc_tokenizer,
"kc_model": kc_model,
"deberta_tokenizer": deberta_tokenizer,
"deberta_model": deberta_model,
"cnn_model": cnn_model,
}
# Char-CNN ์ ์ฉ ํ ํฌ๋์ด์
def tokenize_char(text, vocab, max_len=256):
encoded = [vocab.get(char, 1) for char in str(text)]
encoded = encoded[:max_len] + [0] * max(0, max_len - len(encoded))
return torch.tensor(encoded[:max_len], dtype=torch.long)
# ์ต์ข
์์๋ธ ์์ธก ์คํ ํจ์
def predict_ensemble(text_a, text_b, bundle):
device = bundle["device"]
vocab = bundle["vocab"]
kc_tokenizer = bundle["kc_tokenizer"]
kc_model = bundle["kc_model"]
deberta_tokenizer = bundle["deberta_tokenizer"]
deberta_model = bundle["deberta_model"]
cnn_model = bundle["cnn_model"]
# ๋ชจ๋ธ ๊ฐ์ค์น ๋น์จ ์ธํ
(KcBERT 40%, CNN 20%, DeBERTa 40%)
weights = np.array([0.4, 0.2, 0.4])
with torch.no_grad():
# KcBERT ์ถ๋ก
kc_inputs = kc_tokenizer(text_a, text_b, return_tensors="pt", truncation=True, max_length=128, padding="max_length")
kc_inputs = {k: v.to(device) for k, v in kc_inputs.items()}
kc_prob = torch.softmax(kc_model(**kc_inputs).logits, dim=-1).cpu().numpy()[0]
# Char-CNN ์ถ๋ก
a_idx = tokenize_char(text_a, vocab).unsqueeze(0).to(device)
b_idx = tokenize_char(text_b, vocab).unsqueeze(0).to(device)
cnn_prob = torch.softmax(cnn_model(a_idx, b_idx), dim=-1).cpu().numpy()[0]
# DeBERTa ์ถ๋ก
deberta_inputs = deberta_tokenizer(text_a, text_b, return_tensors="pt", truncation=True, max_length=128, padding="max_length")
deberta_inputs = {k: v.to(device) for k, v in deberta_inputs.items()}
deberta_prob = torch.softmax(deberta_model(**deberta_inputs).logits, dim=-1).cpu().numpy()[0]
# ๊ฐ์ค ํ๊ท ๊ฒฐํฉ
final_prob = (kc_prob * weights[0]) + (cnn_prob * weights[1]) + (deberta_prob * weights[2])
pred_label = int(np.argmax(final_prob))
return {
"pred_label": pred_label,
"same_prob": float(final_prob[0]),
"diff_prob": float(final_prob[1]),
"kc_prob": kc_prob,
"cnn_prob": cnn_prob,
"deberta_prob": deberta_prob,
"device": str(device),
"model_dir": str(MODEL_DIR)
} |