sufya12 commited on
Commit
446e0bb
·
verified ·
1 Parent(s): acf4756

Upload french_title_api.py

Browse files
Files changed (1) hide show
  1. french_title_api.py +246 -0
french_title_api.py ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify
2
+ from flask_cors import CORS
3
+ import torch
4
+ import pandas as pd
5
+ import numpy as np
6
+ from transformers import MarianMTModel, MarianTokenizer
7
+ import torch.nn as nn
8
+ from torch.utils.data import Dataset, DataLoader
9
+ from sklearn.model_selection import train_test_split
10
+ import logging
11
+ import os
12
+ import sys
13
+ import time
14
+ from waitress import serve # new import for production server
15
+
16
+ # Configure logging
17
+ logging.basicConfig(
18
+ level=logging.INFO,
19
+ format='%(asctime)s - %(levelname)s - %(message)s',
20
+ handlers=[logging.StreamHandler()]
21
+ )
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # --------------------- MODEL CLASSES ---------------------
25
+ class FrenchTitleGenerator(nn.Module):
26
+ def __init__(self, model_name='Helsinki-NLP/opus-mt-en-fr'):
27
+ super(FrenchTitleGenerator, self).__init__()
28
+ try:
29
+ logger.info("Loading tokenizer and model...")
30
+ self.tokenizer = MarianTokenizer.from_pretrained(model_name)
31
+ self.model = MarianMTModel.from_pretrained(model_name)
32
+
33
+ special_tokens = ['[YEAR]', '[CITY]', '[DESC]', '[TITLE]']
34
+ self.tokenizer.add_tokens(special_tokens)
35
+ self.model.resize_token_embeddings(len(self.tokenizer))
36
+ logger.info("FrenchTitleGenerator initialized successfully")
37
+ except Exception as e:
38
+ logger.error(f"Failed to initialize FrenchTitleGenerator: {e}")
39
+ raise
40
+
41
+ def forward(self, input_ids, attention_mask, labels=None):
42
+ outputs = self.model(
43
+ input_ids=input_ids,
44
+ attention_mask=attention_mask,
45
+ labels=labels
46
+ )
47
+ return outputs
48
+
49
+ # --------------------- SERVICE CLASS ---------------------
50
+ class FrenchTitleService:
51
+ def __init__(self, csv_path="Data_1.csv", model_path=None):
52
+ self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
53
+ logger.info(f"Using device: {self.device}")
54
+
55
+ self.model = None
56
+ self.dataset_info = {}
57
+ self.csv_path = csv_path
58
+
59
+ if not os.path.exists(csv_path):
60
+ logger.warning(f"Dataset file '{csv_path}' not found. Creating sample data...")
61
+ self.create_sample_data()
62
+
63
+ self.load_model(model_path)
64
+ self.load_dataset_info(csv_path)
65
+
66
+ def create_sample_data(self):
67
+ sample_data = {
68
+ 'Year': [2025, 2025, 2024, 2024, 2025],
69
+ 'City_name': ['Montpellier', 'Montpellier', 'Paris', 'Lyon', 'Marseille'],
70
+ 'Original_title': [
71
+ 'Conference about Compostela Way',
72
+ 'The small papers - Last of the season!!!',
73
+ 'Classical music festival',
74
+ 'Modern art exhibition',
75
+ 'Open air jazz concert'
76
+ ],
77
+ 'Title': [
78
+ 'CONFÉRENCE SUR LE CHEMIN DE COMPOSTELLE',
79
+ 'LES PETITS PAPIERS - DERNIÈRE DE LA SAISON !!!',
80
+ 'FESTIVAL DE MUSIQUE CLASSIQUE',
81
+ 'EXPOSITION D\'ART MODERNE',
82
+ 'CONCERT DE JAZZ EN PLEIN AIR'
83
+ ]
84
+ }
85
+ pd.DataFrame(sample_data).to_csv(self.csv_path, index=False)
86
+ logger.info(f"Sample data created: {self.csv_path}")
87
+
88
+ def load_dataset_info(self, csv_path: str):
89
+ try:
90
+ logger.info(f"Loading dataset from {csv_path}")
91
+ df = pd.read_csv(csv_path)
92
+
93
+ required_columns = ['Year', 'City_name', 'Original_title', 'Title']
94
+ missing_columns = [col for col in required_columns if col not in df.columns]
95
+
96
+ if missing_columns:
97
+ logger.warning(f"Missing columns in dataset: {missing_columns}")
98
+
99
+ self.dataset_info = {
100
+ 'total_samples': len(df),
101
+ 'columns': df.columns.tolist(),
102
+ 'missing_columns': missing_columns,
103
+ 'sample_data': df.head(3).to_dict('records')
104
+ }
105
+
106
+ logger.info(f"Dataset loaded: {len(df)} samples")
107
+ return df
108
+
109
+ except Exception as e:
110
+ logger.error(f"Dataset loading failed: {e}")
111
+ self.dataset_info = {'error': str(e)}
112
+ return None
113
+
114
+ def load_model(self, model_path: str = None):
115
+ try:
116
+ if model_path and os.path.exists(model_path):
117
+ logger.info(f"Loading trained model from {model_path}")
118
+ self.model = FrenchTitleGenerator()
119
+ checkpoint = torch.load(model_path, map_location=self.device)
120
+ self.model.load_state_dict(checkpoint['model_state_dict'])
121
+ self.model.to(self.device)
122
+ self.model.eval()
123
+ logger.info("Trained model loaded successfully!")
124
+ else:
125
+ logger.info("Using base model...")
126
+ self.model = FrenchTitleGenerator()
127
+ self.model.to(self.device)
128
+ self.model.eval()
129
+ except Exception as e:
130
+ logger.error(f"Model loading failed: {e}")
131
+ logger.info("Trying to load base model as fallback...")
132
+ self.model = FrenchTitleGenerator()
133
+ self.model.to(self.device)
134
+ self.model.eval()
135
+
136
+ def generate_title(self, year: str, city: str, original_title: str,
137
+ max_length: int = 50, temperature: float = 0.8) -> str:
138
+ if self.model is None:
139
+ return f"{original_title} - {city} {year}"
140
+
141
+ try:
142
+ input_text = f"Generate title from: Year: {year} City: {city} Description: {original_title}"
143
+ inputs = self.model.tokenizer(
144
+ input_text,
145
+ return_tensors='pt',
146
+ max_length=128,
147
+ truncation=True,
148
+ padding=True
149
+ ).to(self.device)
150
+
151
+ with torch.no_grad():
152
+ outputs = self.model.model.generate(
153
+ inputs['input_ids'],
154
+ attention_mask=inputs['attention_mask'],
155
+ max_length=max_length,
156
+ temperature=temperature,
157
+ do_sample=True,
158
+ num_beams=3,
159
+ early_stopping=True
160
+ )
161
+
162
+ return self.model.tokenizer.decode(outputs[0], skip_special_tokens=True)
163
+ except Exception as e:
164
+ logger.error(f"Title generation failed: {e}")
165
+ return f"{original_title} - {city} {year}"
166
+
167
+ # --------------------- FLASK APP ---------------------
168
+ app = Flask(__name__)
169
+ CORS(app)
170
+ title_service = None
171
+
172
+ def initialize_service():
173
+ global title_service
174
+ try:
175
+ csv_path = "Data_1.csv"
176
+ model_path = "french_title_generator.pth"
177
+ title_service = FrenchTitleService(csv_path=csv_path, model_path=model_path)
178
+ logger.info("French Title Service initialized successfully")
179
+ return True
180
+ except Exception as e:
181
+ logger.error(f"Service initialization failed: {e}")
182
+ return False
183
+
184
+ @app.route('/api/health', methods=['GET'])
185
+ def health_check():
186
+ return jsonify({
187
+ 'status': 'healthy',
188
+ 'timestamp': time.strftime("%Y-%m-%d %H:%M:%S"),
189
+ 'model_loaded': title_service.model is not None if title_service else False
190
+ })
191
+
192
+ @app.route('/api/info', methods=['GET'])
193
+ def service_info():
194
+ if not title_service:
195
+ return jsonify({'error': 'Service not initialized'}), 500
196
+ return jsonify({
197
+ 'device': str(title_service.device),
198
+ 'model_loaded': title_service.model is not None,
199
+ 'dataset_info': title_service.dataset_info,
200
+ 'csv_path': title_service.csv_path
201
+ })
202
+
203
+ @app.route('/api/generate', methods=['POST'])
204
+ def generate_title():
205
+ if not title_service:
206
+ return jsonify({'error': 'Service not initialized'}), 500
207
+ try:
208
+ data = request.get_json()
209
+ for field in ['year', 'city', 'original_title']:
210
+ if field not in data:
211
+ return jsonify({'error': f'Missing field: {field}'}), 400
212
+ generated = title_service.generate_title(
213
+ year=str(data['year']),
214
+ city=str(data['city']),
215
+ original_title=str(data['original_title']),
216
+ temperature=data.get('temperature', 0.8),
217
+ max_length=data.get('max_length', 50)
218
+ )
219
+ return jsonify({
220
+ 'input': data,
221
+ 'generated_title': generated,
222
+ 'timestamp': time.strftime("%Y-%m-%d %H:%M:%S")
223
+ })
224
+ except Exception as e:
225
+ logger.error(f"Title generation error: {e}")
226
+ return jsonify({'error': str(e)}), 500
227
+
228
+ @app.route('/')
229
+ def home():
230
+ return jsonify({
231
+ 'message': ' French Title Generator API is running',
232
+ 'endpoints': {
233
+ '/api/health': 'Health check',
234
+ '/api/info': 'Service info',
235
+ '/api/generate': 'Generate a title'
236
+ }
237
+ })
238
+
239
+ if __name__ == '__main__':
240
+ if initialize_service():
241
+ port = 5000
242
+ logger.info(f" Starting French Title Generator API on port {port} (via Waitress)")
243
+ serve(app, host="0.0.0.0", port=port) # Waitress replaces Flask’s dev server
244
+ else:
245
+ logger.error("Failed to initialize service. Exiting.")
246
+ sys.exit(1)