ganeshasapu commited on
Commit
930c998
Β·
verified Β·
1 Parent(s): 7bae197

Upload handler.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. handler.py +220 -0
handler.py ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import numpy as np
3
+ from typing import Dict, Any
4
+ import os
5
+ import pickle
6
+ import re
7
+ from gensim.models.doc2vec import Doc2Vec
8
+ import json
9
+
10
+ class EndpointHandler:
11
+ """
12
+ Custom handler for Hugging Face Inference Endpoints.
13
+ Handles Doc2Vec + Classifier content classification model.
14
+ """
15
+
16
+ def __init__(self, path: str = "."):
17
+ """
18
+ Initialize the handler.
19
+
20
+ Args:
21
+ path (str): Path to the model files
22
+ """
23
+ self.path = path
24
+
25
+ # Load model metadata to find the best model
26
+ summary_path = os.path.join(path, "model_summary.json")
27
+
28
+ try:
29
+ # Load model summary to get best model info
30
+ with open(summary_path, 'r') as f:
31
+ summary = json.load(f)
32
+
33
+ # Get the best model (rank 1)
34
+ best_model_info = summary['top_3_models'][0] # rank 1 is the best
35
+
36
+ # Load the classifier
37
+ classifier_path = os.path.join(path, best_model_info['files']['classifier'])
38
+ with open(classifier_path, 'rb') as f:
39
+ self.classifier = pickle.load(f)
40
+
41
+ # Load the MATCHING Doc2Vec model (same one used to train this classifier)
42
+ doc2vec_path = os.path.join(path, best_model_info['files']['doc2vec'])
43
+ print(f"πŸ” Loading Doc2Vec from: {doc2vec_path}")
44
+
45
+ # Check if associated files exist
46
+ for suffix in ['.wv.vectors.npy', '.syn1neg.npy']:
47
+ associated_file = doc2vec_path + suffix
48
+ if os.path.exists(associated_file):
49
+ print(f"βœ… Found associated file: {associated_file}")
50
+ else:
51
+ print(f"⚠️ Missing associated file: {associated_file}")
52
+
53
+ try:
54
+ self.doc2vec_model = Doc2Vec.load(doc2vec_path)
55
+ except Exception as e:
56
+ print(f"❌ Failed to load Doc2Vec model: {str(e)}")
57
+ print(f"❌ Current working directory: {os.getcwd()}")
58
+ print(f"❌ Files in path {path}:")
59
+ if os.path.exists(path):
60
+ for f in os.listdir(path):
61
+ print(f" - {f}")
62
+ raise Exception(f"Failed to load Doc2Vec model from {doc2vec_path}: {str(e)}")
63
+
64
+ # Verify feature dimensions match
65
+ expected_features = self.doc2vec_model.vector_size
66
+ print(f"βœ… Doc2Vec vector size: {expected_features}")
67
+ print(f"βœ… Model config: {best_model_info['config']}")
68
+
69
+ # Double-check that vector_size matches config
70
+ config_vector_size = best_model_info['config']['vector_size']
71
+ if expected_features != config_vector_size:
72
+ raise Exception(f"❌ Vector size mismatch! Doc2Vec model has {expected_features} but config says {config_vector_size}")
73
+
74
+ print(f"βœ… Vector dimensions verified: {expected_features} features")
75
+
76
+ # Additional debugging - test a sample vector
77
+ test_text = "test sample text for debugging"
78
+ test_vector = self.get_document_vector_for_test(test_text)
79
+ print(f"πŸ” Test vector shape: {test_vector.shape}")
80
+ print(f"πŸ” Classifier type: {type(self.classifier).__name__}")
81
+
82
+ # Store model info for reference
83
+ self.model_info = best_model_info
84
+
85
+ print(f"βœ… Loaded best model: {best_model_info['classifier']} ({best_model_info['algorithm_type']})")
86
+ print(f"βœ… Model accuracy: {best_model_info['accuracy']:.4f}")
87
+ print(f"βœ… Doc2Vec config: {best_model_info['config']}")
88
+
89
+ except Exception as e:
90
+ print(f"❌ Error details: {str(e)}")
91
+ print(f"❌ Error type: {type(e)}")
92
+ raise Exception(f"❌ Failed to load models: {str(e)}")
93
+
94
+ def __call__(self, data: Dict[str, Any]) -> Dict[str, Any]:
95
+ """
96
+ Process inference request.
97
+
98
+ Args:
99
+ data (Dict): Request payload
100
+
101
+ Expected input formats:
102
+ Single prediction:
103
+ {
104
+ "inputs": {
105
+ "content": "text content",
106
+ "meta_description": "meta text"
107
+ }
108
+ }
109
+
110
+ Returns:
111
+ Dict: Prediction results
112
+ """
113
+ try:
114
+ # Extract inputs from request
115
+ inputs = data.get("inputs", {})
116
+
117
+ if not inputs:
118
+ return {"error": "Missing 'inputs' in request data"}
119
+
120
+ # Handle single request
121
+ if isinstance(inputs, dict):
122
+ # Single prediction
123
+ return self._predict_single(inputs)
124
+ else:
125
+ return {"error": "Invalid input format. Expected dict."}
126
+
127
+ except Exception as e:
128
+ return {"error": f"Prediction failed: {str(e)}"}
129
+
130
+ def _predict_single(self, inputs: Dict[str, str]) -> Dict[str, Any]:
131
+ """Handle single prediction request"""
132
+ content = inputs.get("content", "")
133
+ meta_description = inputs.get("meta_description", "")
134
+
135
+ if not content:
136
+ return {"error": "Missing required field: 'content'"}
137
+
138
+ # Ensure inputs are strings
139
+ content = str(content)
140
+ meta_description = str(meta_description)
141
+
142
+ try:
143
+ # Combine text features (same as training)
144
+ combined_text = content + ' ' + meta_description
145
+
146
+ # Preprocess text for Doc2Vec (same as training preprocessing)
147
+ preprocessed_words = self._preprocess_text(combined_text)
148
+
149
+ if not preprocessed_words:
150
+ # Handle empty text case
151
+ doc_vector = np.zeros(self.doc2vec_model.vector_size)
152
+ else:
153
+ # Get document vector from Doc2Vec model
154
+ doc_vector = self.doc2vec_model.infer_vector(preprocessed_words)
155
+
156
+ # Reshape for classifier (expects 2D array)
157
+ doc_vector = doc_vector.reshape(1, -1)
158
+
159
+ # Get predictions from classifier
160
+ probabilities = self.classifier.predict_proba(doc_vector)[0]
161
+
162
+ # Get class names from classifier
163
+ classes = self.classifier.classes_
164
+
165
+ # Create results in the same format as original handler
166
+ results = []
167
+ for i, label in enumerate(classes):
168
+ results.append({
169
+ "label": label,
170
+ "score": float(probabilities[i])
171
+ })
172
+
173
+ return results
174
+
175
+ except Exception as e:
176
+ return {"error": f"Single prediction failed: {str(e)}"}
177
+
178
+ def _preprocess_text(self, text):
179
+ """Clean and preprocess text for Doc2Vec (same as training)"""
180
+ if pd.isna(text) or not text:
181
+ return []
182
+
183
+ # Convert to lowercase and remove special characters
184
+ text = str(text).lower()
185
+ text = re.sub(r'[^a-zA-Z\s]', '', text)
186
+ text = re.sub(r'\s+', ' ', text).strip()
187
+
188
+ # Split into words
189
+ words = text.split()
190
+
191
+ # Remove very short words
192
+ words = [word for word in words if len(word) > 2]
193
+
194
+ return words
195
+
196
+ def get_document_vector_for_test(self, text):
197
+ """Get document vector for testing (same as _preprocess_text + infer_vector)"""
198
+ words = self._preprocess_text(text)
199
+ if not words:
200
+ return np.zeros(self.doc2vec_model.vector_size)
201
+ else:
202
+ return self.doc2vec_model.infer_vector(words)
203
+
204
+
205
+ # For local testing
206
+ if __name__ == "__main__":
207
+ # Test the handler locally
208
+ handler = EndpointHandler(".")
209
+
210
+ # Test single prediction
211
+ test_single = {
212
+ "inputs": {
213
+ "content": "Machine learning models for content attribution analysis using Doc2Vec embeddings",
214
+ "meta_description": "A comprehensive guide to ML-based content classification with neural networks"
215
+ }
216
+ }
217
+
218
+ print("=== Single Prediction Test ===")
219
+ result = handler(test_single)
220
+ print(result)