Spaces:
Sleeping
Sleeping
| import torch | |
| import torch.nn as nn | |
| import gradio as gr | |
| import os | |
| import pickle | |
| class LayerNorm(nn.Module): | |
| def __init__(self, emb_dim): | |
| super().__init__() | |
| self.eps = 1e-5 | |
| self.scale = nn.Parameter(torch.ones(emb_dim)) | |
| self.shift = nn.Parameter(torch.zeros(emb_dim)) | |
| def forward(self, x): | |
| mean = x.mean(dim=-1, keepdim=True) | |
| var = x.var(dim=-1, keepdim=True) | |
| norm_x = (x - mean) / torch.sqrt(var + self.eps) | |
| return self.scale * norm_x + self.shift | |
| class GELU(nn.Module): | |
| def __init__(self): | |
| super().__init__() | |
| def forward(self, x): | |
| return 0.5 * x * (1 + torch.tanh(torch.sqrt(torch.tensor(2.0 / torch.pi)) * (x + 0.044715 * torch.pow(x, 3)))) | |
| class MultiHeadAttention(nn.Module): | |
| def __init__(self, d_in, d_out, context_length, dropout, num_head, qkv_bias=False): | |
| super().__init__() | |
| assert (d_out % num_head == 0) | |
| self.d_out = d_out | |
| self.num_head = num_head | |
| self.head_dim = d_out // num_head | |
| self.W_query = torch.nn.Linear(d_in, d_out, bias=qkv_bias) | |
| self.W_key = torch.nn.Linear(d_in, d_out, bias=qkv_bias) | |
| self.W_value = torch.nn.Linear(d_in, d_out, bias=qkv_bias) | |
| self.out_proj = torch.nn.Linear(d_out, d_out) | |
| self.dropout = torch.nn.Dropout(dropout) | |
| self.register_buffer("mask", torch.triu(torch.ones(context_length, context_length), diagonal=1)) | |
| def forward(self, x): | |
| b, num_tokens, d_in = x.shape | |
| keys = self.W_key(x) | |
| queries = self.W_query(x) | |
| values = self.W_value(x) | |
| keys = keys.view(b, num_tokens, self.num_head, self.head_dim) | |
| values = values.view(b, num_tokens, self.num_head, self.head_dim) | |
| queries = queries.view(b, num_tokens, self.num_head, self.head_dim) | |
| keys = keys.transpose(1, 2) | |
| values = values.transpose(1, 2) | |
| queries = queries.transpose(1, 2) | |
| attn_score = queries @ keys.transpose(2, 3) | |
| mask_bool = self.mask.to(torch.bool)[:num_tokens, :num_tokens] | |
| attn_score.masked_fill_(mask_bool, -torch.inf) | |
| attn_weight = torch.softmax(attn_score / keys.shape[-1] ** 0.5, dim=-1) | |
| attn_weight = self.dropout(attn_weight) | |
| context_vector = (attn_weight @ values).transpose(1, 2) | |
| context_vector = context_vector.contiguous().view(b, num_tokens, self.d_out) | |
| context_vector = self.out_proj(context_vector) | |
| return context_vector | |
| class FeedForward(nn.Module): | |
| def __init__(self, cfg): | |
| super().__init__() | |
| self.layers = nn.Sequential( | |
| nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]), | |
| GELU(), | |
| nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"]) | |
| ) | |
| def forward(self, x): | |
| return self.layers(x) | |
| class TransformerBlock(nn.Module): | |
| def __init__(self, cfg): | |
| super().__init__() | |
| self.att = MultiHeadAttention( | |
| d_in=cfg["emb_dim"], | |
| d_out=cfg["emb_dim"], | |
| context_length=cfg["context_length"], | |
| num_head=cfg["n_heads"], | |
| dropout=cfg.get("drop_rate", 0.0), | |
| qkv_bias=cfg.get("qkv_bias", False) | |
| ) | |
| self.ff = FeedForward(cfg) | |
| self.norm1 = LayerNorm(cfg["emb_dim"]) | |
| self.norm2 = LayerNorm(cfg["emb_dim"]) | |
| self.drop_shortcut = nn.Dropout(cfg.get("drop_rate", 0.0)) | |
| def forward(self, x): | |
| shortcut = x | |
| x = self.norm1(x) | |
| x = self.att(x) | |
| x = self.drop_shortcut(x) | |
| x = x + shortcut | |
| shortcut = x | |
| x = self.norm2(x) | |
| x = self.ff(x) | |
| x = self.drop_shortcut(x) | |
| x = x + shortcut | |
| return x | |
| class GPTModel(nn.Module): | |
| def __init__(self, cfg): | |
| super().__init__() | |
| self.tok_emb = nn.Embedding(cfg["vocab_size"], cfg["emb_dim"]) | |
| self.pos_emb = nn.Embedding(cfg["context_length"], cfg["emb_dim"]) | |
| self.drop_emb = nn.Dropout(cfg.get("drop_rate", 0.0)) | |
| self.trf_blocks = nn.Sequential( | |
| *[TransformerBlock(cfg) for _ in range(cfg["n_layers"]) ] | |
| ) | |
| self.final_norm = LayerNorm(cfg["emb_dim"]) | |
| self.out_head = nn.Linear(cfg["emb_dim"], cfg["vocab_size"], bias=False) | |
| def forward(self, in_idx): | |
| batch_size, seq_len = in_idx.shape | |
| tok_embeds = self.tok_emb(in_idx) | |
| pos_embeds = self.pos_emb(torch.arange(seq_len, device=in_idx.device)) | |
| x = tok_embeds + pos_embeds | |
| x = self.drop_emb(x) | |
| x = self.trf_blocks(x) | |
| x = self.final_norm(x) | |
| logits = self.out_head(x) | |
| return logits | |
| model_path = "review_classifier_model.pth" | |
| if not os.path.exists(model_path): | |
| raise FileNotFoundError(f"{model_path} not found. Please check the path.") | |
| try: | |
| loaded_full = None | |
| safe_ctx = getattr(torch.serialization, "safe_globals", None) | |
| add_safe = getattr(torch.serialization, "add_safe_globals", None) | |
| if safe_ctx is not None: | |
| try: | |
| with torch.serialization.safe_globals([GPTModel]): | |
| loaded_full = torch.load(model_path, map_location=torch.device("cpu"), weights_only=False) | |
| except Exception: | |
| loaded_full = None | |
| elif add_safe is not None: | |
| try: | |
| # older helper: register globally then load | |
| torch.serialization.add_safe_globals([GPTModel]) | |
| loaded_full = torch.load(model_path, map_location=torch.device("cpu"), weights_only=False) | |
| except Exception: | |
| loaded_full = None | |
| else: | |
| # If neither helper exists, try loading with weights_only=False (may execute code during unpickle). | |
| try: | |
| loaded_full = torch.load(model_path, map_location=torch.device("cpu"), weights_only=False) | |
| except Exception: | |
| loaded_full = None | |
| if loaded_full is not None and hasattr(loaded_full, "state_dict") and not isinstance(loaded_full, dict): | |
| model = loaded_full | |
| print(f"Loaded full model object from {model_path}") | |
| else: | |
| state = None | |
| try: | |
| state = torch.load(model_path, map_location=torch.device("cpu"), weights_only=True) | |
| except Exception: | |
| try: | |
| if safe_ctx is not None: | |
| with torch.serialization.safe_globals([GPTModel]): | |
| tmp = torch.load(model_path, map_location=torch.device("cpu"), weights_only=False) | |
| elif add_safe is not None: | |
| torch.serialization.add_safe_globals([GPTModel]) | |
| tmp = torch.load(model_path, map_location=torch.device("cpu"), weights_only=False) | |
| else: | |
| tmp = torch.load(model_path, map_location=torch.device("cpu"), weights_only=False) | |
| if hasattr(tmp, "state_dict"): | |
| state = tmp.state_dict() | |
| else: | |
| state = tmp | |
| except Exception as e: | |
| raise RuntimeError(f"Unable to load checkpoint as full model or weights-only. Last error: {e}") | |
| if isinstance(state, dict): | |
| print("Attempting to load checkpoint state into a GPTModel instance...") | |
| BASE_CONFIG = { | |
| "vocab_size": 50257, | |
| "context_length": 1024, | |
| "drop_rate": 0.0, | |
| "qkv_bias": True, | |
| "emb_dim": 768, | |
| "n_layers": 12, | |
| "n_heads": 12, | |
| } | |
| model = GPTModel(BASE_CONFIG) | |
| if "model_state_dict" in state: | |
| state_dict = state["model_state_dict"] | |
| elif "state_dict" in state: | |
| state_dict = state["state_dict"] | |
| else: | |
| state_dict = state | |
| model.load_state_dict(state_dict, strict=False) | |
| print("Loaded state_dict into GPTModel instance (non-strict).") | |
| else: | |
| raise RuntimeError("Unrecognized checkpoint format and unable to construct model from checkpoint.") | |
| except Exception as e: | |
| raise RuntimeError(f"Failed to load model checkpoint: {e}") | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| model.to(device) | |
| model.eval() | |
| print(f"Model loaded and moved to {device}") | |
| tokenizer_path = "tokenizer.pkl" | |
| if os.path.exists(tokenizer_path): | |
| with open(tokenizer_path, "rb") as f: | |
| tokenizer = pickle.load(f) | |
| print(f"Tokenizer loaded from {tokenizer_path}") | |
| else: | |
| raise FileNotFoundError(f"{tokenizer_path} not found. Please check the path.") | |
| MAX_SEQUENCE_LENGTH = 120 | |
| def classify_review(text, model, tokenizer_obj, device, max_length=MAX_SEQUENCE_LENGTH, pad_token_id=50256): | |
| model.eval() | |
| input_ids = tokenizer_obj.encode(text) | |
| input_ids = input_ids[:max_length] + [pad_token_id] * (max_length - len(input_ids)) | |
| input_tensor = torch.tensor(input_ids, device=device).unsqueeze(0) | |
| with torch.no_grad(): | |
| logits = model(input_tensor)[:, -1, :] | |
| predicted_label = torch.argmax(logits, dim=-1).item() | |
| return "spam" if predicted_label == 1 else "not spam" | |
| def chatbot_classify(message, history): | |
| result = classify_review( | |
| message, | |
| model, | |
| tokenizer, | |
| device, | |
| max_length=MAX_SEQUENCE_LENGTH | |
| ) | |
| return result | |
| print("Launching Gradio interface...") | |
| iface = gr.ChatInterface( | |
| chatbot_classify, | |
| title="📬 Spam Detection System", | |
| description="Enter an SMS message below...", | |
| theme="compact", | |
| css=""" | |
| /* Customize chat bubble colors */ | |
| .chatbot-message { | |
| background-color: #e0f7fa; /* Light cyan */ | |
| color: #006064; /* Dark teal text */ | |
| font-weight: 600; | |
| border-radius: 12px; | |
| padding: 12px; | |
| } | |
| .user-message { | |
| background-color: #c8e6c9; /* Light green */ | |
| color: #1b5e20; /* Dark green text */ | |
| font-weight: 600; | |
| border-radius: 12px; | |
| padding: 12px; | |
| } | |
| .chat-ending-message { | |
| font-style: italic; | |
| color: #555; | |
| } | |
| """, | |
| ) | |
| ICON_CDN = "https://img.icons8.com/color/48/mail-envelope.png" | |
| custom_head_html = f""" | |
| <link rel="icon" href="{ICON_CDN}" type="image/x-icon"> | |
| """ | |
| iface.launch( | |
| share=True, | |
| favicon_path=ICON_CDN | |
| ) |