ZEROTSUDIOS's picture
Upload folder using huggingface_hub
c3637f5 verified
Raw History Blame Contribute Delete
11.4 kB
from flask import Flask, request, jsonify, render_template
from transformers import AutoModelForSequenceClassification, AutoTokenizer
import torch
import numpy as np
import pickle
import os
import json
import logging
import csv
from datetime import datetime
from flask_cors import CORS
# Configure logging
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[logging.FileHandler("intent_api.log"),
logging.StreamHandler()])
logger = logging.getLogger(__name__)
app = Flask(__name__)
CORS(app) # Enable Cross-Origin Resource Sharing
# Global variables for intent classification
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
MODEL_SAVE_PATH = os.path.join(BASE_DIR, "model")
intent_model = None
intent_tokenizer = None
intent_classes = None
intent_thresholds = None
# CSV file for evaluation data
EVAL_CSV = "model_evaluation.csv"
def setup_evaluation_csv():
"""Set up the CSV file for tracking model performance"""
if not os.path.exists(EVAL_CSV):
with open(EVAL_CSV, 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow([
'timestamp',
'input_text',
'predicted_intent',
'is_ood',
'confidence',
'energy_score',
'detection_method'
])
logger.info(f"Created evaluation CSV file: {EVAL_CSV}")
def save_prediction_to_csv(input_text, result, method):
"""Save prediction results to CSV for later analysis"""
with open(EVAL_CSV, 'a', newline='') as f:
writer = csv.writer(f)
writer.writerow([
datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
input_text,
result['intent'],
result['is_ood'],
result['confidence'],
result['energy_score'],
method
])
def load_ood_thresholds(model_path):
"""Load the OOD thresholds from the model directory"""
threshold_path = os.path.join(model_path, "ood_thresholds.json")
if os.path.exists(threshold_path):
with open(threshold_path, "r") as f:
return json.load(f)
else:
# Provide default thresholds if file not found
logger.warning(f"Threshold file not found at {threshold_path}. Using default values.")
return {
"energy_threshold": 0.0, # Replace with your default value
"msp_threshold": 0.5 # Replace with your default value
}
def load_intent_resources():
"""Load model, tokenizer, intent classes, and thresholds for intent classification."""
global intent_model, intent_tokenizer, intent_classes, intent_thresholds
logger.info(f"Loading intent resources from {MODEL_SAVE_PATH}...")
try:
# Load model and tokenizer
intent_model = AutoModelForSequenceClassification.from_pretrained(MODEL_SAVE_PATH)
intent_tokenizer = AutoTokenizer.from_pretrained(MODEL_SAVE_PATH)
# Load intent classes
intent_classes_path = os.path.join(MODEL_SAVE_PATH, "intent_classes.pkl")
if os.path.exists(intent_classes_path):
with open(intent_classes_path, "rb") as f:
intent_classes = pickle.load(f)
else:
raise FileNotFoundError(f"Intent classes file not found at {intent_classes_path}")
# Load OOD thresholds
intent_thresholds = load_ood_thresholds(MODEL_SAVE_PATH)
logger.info("Intent resources loaded successfully")
logger.info(f"Loaded {len(intent_classes)} intent classes")
logger.info(f"Thresholds: {intent_thresholds}")
return True
except Exception as e:
logger.error(f"Failed to load intent resources: {str(e)}", exc_info=True)
return False
def predict_intent_with_enhanced_ood(text, model, tokenizer, intent_classes,
energy_threshold, msp_threshold, method='combined'):
"""
Predict intent with enhanced out-of-distribution detection and detailed logging.
"""
logger.info("\n========== INTENT PREDICTION DEBUG ==========")
logger.info(f"Input Text: {text}")
logger.info(f"Detection Method: {method}")
# Tokenize input
inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True, max_length=512)
# Get model outputs
with torch.no_grad():
outputs = model(**inputs)
logits = outputs.logits
logger.info(f"Logits: {logits.numpy().tolist()}")
# Get probabilities
probs = torch.nn.functional.softmax(logits, dim=-1)
max_prob, pred_idx = torch.max(probs, dim=-1)
logger.info(f"Softmax Probabilities: {probs.numpy().tolist()}")
logger.info(f"Max Probability (Confidence): {max_prob.item():.4f}")
logger.info(f"Predicted Index: {pred_idx.item()}")
# Calculate energy score
energy = -torch.logsumexp(logits, dim=-1)
logger.info(f"Energy Score: {energy.item():.4f}")
# OOD detection
is_ood = False
if method == 'energy':
is_ood = energy.item() > energy_threshold
elif method == 'msp':
is_ood = max_prob.item() < msp_threshold
elif method == 'combined':
is_ood = (energy.item() > energy_threshold) and (max_prob.item() < msp_threshold)
logger.info(f"OOD Detection -> is_ood: {is_ood}")
if is_ood:
logger.info("Prediction marked as OUT-OF-DISTRIBUTION.")
else:
logger.info("Prediction marked as IN-DISTRIBUTION.")
# Get intent label
predicted_intent = intent_classes[pred_idx.item()] if not is_ood else "unknown"
logger.info(f"Predicted Intent: {predicted_intent}")
logger.info("=============================================\n")
return {
"intent": predicted_intent,
"is_ood": is_ood,
"confidence": max_prob.item(),
"energy_score": energy.item(),
# Add all class probabilities for detailed analysis
"class_probabilities": {
intent_classes[i]: float(prob)
for i, prob in enumerate(probs[0].numpy())
}
}
def initialize_models():
"""Load intent classification model on startup."""
# Create evaluation CSV if it doesn't exist
setup_evaluation_csv()
# Load intent classification model
model_loaded = load_intent_resources()
if model_loaded:
logger.info("Intent classification model loaded successfully!")
return True
else:
logger.error("Failed to load intent model.")
return False
@app.route('/api/health', methods=['GET'])
def health_check():
"""Endpoint to check if the API is running and models are loaded."""
models_loaded = intent_model is not None and intent_tokenizer is not None
return jsonify({
"status": "healthy" if models_loaded else "unhealthy",
"intent_model_loaded": models_loaded,
"available_endpoints": ["/", "/api/health", "/api/analyze", "/api/stats"]
})
@app.route('/api/analyze', methods=['POST'])
def analyze():
"""Endpoint to predict intent from text."""
# Check if request contains JSON
if not request.is_json:
return jsonify({"error": "Request must be JSON"}), 400
# Get text from request
data = request.get_json()
if 'text' not in data:
return jsonify({"error": "Missing 'text' field in request"}), 400
text = data['text']
# Default to combined method unless specified
method = data.get('method', 'combined')
if method not in ['energy', 'msp', 'combined']:
return jsonify({"error": "Invalid method. Must be 'energy', 'msp', or 'combined'"}), 400
# Make prediction
result = predict_intent_with_enhanced_ood(
text,
intent_model,
intent_tokenizer,
intent_classes,
intent_thresholds["energy_threshold"],
intent_thresholds["msp_threshold"],
method=method
)
# Save result to CSV for evaluation
save_prediction_to_csv(text, result, method)
# Return prediction as JSON
return jsonify(result)
@app.route('/api/stats', methods=['GET'])
def get_stats():
"""Get statistics about model usage and predictions."""
try:
stats = {
"model_info": {
"num_intent_classes": len(intent_classes) if intent_classes else 0,
"model_path": MODEL_SAVE_PATH,
"thresholds": intent_thresholds
},
"usage": {}
}
# Read CSV to generate statistics if it exists
if os.path.exists(EVAL_CSV):
with open(EVAL_CSV, 'r') as f:
reader = csv.DictReader(f)
rows = list(reader)
stats["usage"] = {
"total_queries": len(rows),
"ood_count": sum(1 for row in rows if row["is_ood"] == "True"),
"top_intents": {}
}
# Count intents for statistical analysis
intent_counts = {}
for row in rows:
intent = row["predicted_intent"]
if intent not in intent_counts:
intent_counts[intent] = 0
intent_counts[intent] += 1
# Get top 5 intents
top_intents = sorted(intent_counts.items(), key=lambda x: x[1], reverse=True)[:5]
stats["usage"]["top_intents"] = dict(top_intents)
return jsonify(stats)
except Exception as e:
logger.error(f"Error in stats endpoint: {str(e)}", exc_info=True)
return jsonify({
"error": "Processing error",
"message": f"An error occurred while retrieving stats: {str(e)}"
}), 500
# Add this new endpoint to download the evaluation data
@app.route('/api/download_eval_data', methods=['GET'])
def download_eval_data():
"""Return the evaluation data as JSON for analysis"""
try:
if not os.path.exists(EVAL_CSV):
return jsonify({"error": "No evaluation data available yet"}), 404
with open(EVAL_CSV, 'r') as f:
reader = csv.DictReader(f)
rows = list(reader)
return jsonify({
"count": len(rows),
"data": rows
})
except Exception as e:
logger.error(f"Error downloading evaluation data: {str(e)}", exc_info=True)
return jsonify({
"error": "Processing error",
"message": f"An error occurred: {str(e)}"
}), 500
if __name__ == '__main__':
# Initialize models when the app starts
model_loaded = initialize_models()
# Set port from environment variable or default to 5000
port = int(os.environ.get('PORT', 5000))
# For development use debug=True, for production use debug=False
app.run(host='0.0.0.0', port=5000, debug=False, use_reloader=False)