🎬 SISTEMA DI RACCOMANDAZIONE AVANZATO
Progetto Finale che Unisce Database, ML e Reti Neurali
🎯 Progetto Finale: Netflix-Style Recommendation Engine
📺 Scenario Reale: Netflix Tech Lead
Sei stato assunto come AI Engineer in una startup di streaming video (MovieFlix). Il tuo compito è costruire un sistema di raccomandazione professionale che combina:
- Database SQL per gestire milioni di utenti e film
- Machine Learning Classico per raccomandazioni collaborative
- Reti Neurali per raccomandazioni basate su contenuto
- Ensemble Learning per combinare i migliori modelli
- API REST per servire raccomandazioni in tempo reale
🏗️ Architettura del Sistema
1️⃣ Database Layer
PostgreSQL con 1M+ utenti, 50k film, 10M+ rating
2️⃣ ML Layer
Collaborative Filtering + Content-Based Filtering
3️⃣ Deep Learning Layer
Reti Neurali per embeddings e similarità
4️⃣ API Layer
FastAPI per servire raccomandazioni in real-time
🚀 Obiettivi del Progetto
- Costruire database relazionale scalabile per dati di streaming
- Implementare 3+ algoritmi di raccomandazione diversi
- Creare sistema ibrido che combina ML classico e reti neurali
- Sviluppare API REST per servire raccomandazioni
- Valutare performance con metriche professionali (RMSE, Precision@K)
- Deploy su cloud (simulato) con Docker
Parte 1: Database Professionale per Streaming Video
Setup Ambiente e Creazione Database Complesso
# ================================================
# 🎬 PROGETTO: SISTEMA DI RACCOMANDAZIONE AVANZATO
# ================================================
print("🚀 INIZIO PROGETTO: SISTEMA RACCOMANDAZIONE NETFLIX-STYLE")
print("=" * 70)
# 1.1 IMPORTAZIONE LIBRERIE COMPLETE
print("\n📦 IMPORT TUTTE LE LIBRERIE NECESSARIE...")
# Database e Data Manipulation
import sqlite3
import pandas as pd
import numpy as np
from sqlalchemy import create_engine, text
import json
from datetime import datetime, timedelta
import random
# Machine Learning
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import mean_squared_error, mean_absolute_error
from sklearn.preprocessing import StandardScaler, LabelEncoder, MinMaxScaler
from sklearn.decomposition import TruncatedSVD
from sklearn.neighbors import NearestNeighbors
import scipy.sparse as sp
# Deep Learning
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, models, optimizers, losses, metrics
from tensorflow.keras.layers import Embedding, Flatten, Dot, Add, Concatenate, Dense, Dropout, Input
from tensorflow.keras.models import Model
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau
# NLP e Text Processing (per descrizioni film)
import re
from collections import Counter
import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
nltk.download('punkt', quiet=True)
nltk.download('stopwords', quiet=True)
# Visualizzazione
import matplotlib.pyplot as plt
import seaborn as sns
from wordcloud import WordCloud
import plotly.graph_objects as go
import plotly.express as px
# API e Deployment
from fastapi import FastAPI, HTTPException
import uvicorn
import pickle
import joblib
# Configurazione
pd.set_option('display.max_columns', None)
pd.set_option('display.width', 1000)
np.random.seed(42)
tf.random.set_seed(42)
plt.style.use('seaborn-v0_8-darkgrid')
sns.set_palette("husl")
print("✅ Tutte le librerie importate correttamente!")
print(f"📊 TensorFlow Version: {tf.__version__}")
print(f"📊 Scikit-learn Version: {pd.__version__}")
# 1.2 CREAZIONE DATABASE COMPLESSO PER STREAMING
print("\n" + "=" * 70)
print("🗄️ CREAZIONE DATABASE MOVIEFLIX (SIMULAZIONE REALISTICA)")
print("=" * 70)
# Crea database SQLite in memoria (in produzione sarebbe PostgreSQL)
conn = sqlite3.connect(':memory:')
cursor = conn.cursor()
# 1.3 SCHEMA DATABASE PROFESSIONALE
print("\n🏗️ CREAZIONE SCHEMA DATABASE AVANZATO...")
tables_sql = [
# Tabella Utenti (con features demografiche)
'''CREATE TABLE IF NOT EXISTS users (
user_id INTEGER PRIMARY KEY,
age INTEGER CHECK(age >= 13 AND age <= 100),
gender TEXT CHECK(gender IN ('M', 'F', 'O')),
country TEXT,
subscription_type TEXT CHECK(subscription_type IN ('basic', 'standard', 'premium')),
join_date DATE,
total_watch_time_hours DECIMAL(10,2) DEFAULT 0,
avg_rating DECIMAL(3,2) DEFAULT 0
)''',
# Tabella Film (con metadata dettagliato)
'''CREATE TABLE IF NOT EXISTS movies (
movie_id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
release_year INTEGER,
genre TEXT, # Separato da pipe: "Action|Adventure|Sci-Fi"
director TEXT,
duration_minutes INTEGER,
imdb_rating DECIMAL(2,1),
description TEXT,
main_actors TEXT, # Separato da pipe
language TEXT,
production_country TEXT,
budget_millions DECIMAL(10,2),
revenue_millions DECIMAL(10,2)
)''',
# Tabella Rating (interazioni utente-film)
'''CREATE TABLE IF NOT EXISTS ratings (
rating_id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
movie_id INTEGER,
rating DECIMAL(2,1) CHECK(rating >= 0.5 AND rating <= 5.0),
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
watch_duration_minutes INTEGER,
device_type TEXT CHECK(device_type IN ('mobile', 'tablet', 'tv', 'desktop')),
FOREIGN KEY (user_id) REFERENCES users(user_id),
FOREIGN KEY (movie_id) REFERENCES movies(movie_id),
UNIQUE(user_id, movie_id) # Un utente può rateare un film una volta sola
)''',
# Tabella Watch History (per sessioni di visione)
'''CREATE TABLE IF NOT EXISTS watch_history (
session_id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
movie_id INTEGER,
start_time DATETIME,
end_time DATETIME,
completed BOOLEAN DEFAULT 0,
pause_count INTEGER DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES users(user_id),
FOREIGN KEY (movie_id) REFERENCES movies(movie_id)
)''',
# Tabella Simili (film simili, pre-calcolata)
'''CREATE TABLE IF NOT EXISTS similar_movies (
movie_id INTEGER,
similar_movie_id INTEGER,
similarity_score DECIMAL(4,3),
similarity_type TEXT CHECK(similarity_type IN ('content', 'collaborative', 'hybrid')),
PRIMARY KEY (movie_id, similar_movie_id),
FOREIGN KEY (movie_id) REFERENCES movies(movie_id),
FOREIGN KEY (similar_movie_id) REFERENCES movies(movie_id)
)'''
]
# Esegui creazione tabelle
for i, table_sql in enumerate(tables_sql, 1):
cursor.execute(table_sql)
print(f"✅ Tabella {i}/5 creata: {table_sql.split('(')[0].split()[-1]}")
# 1.4 GENERAZIONE DATI SINTETICI REALISTICI
print("\n📊 GENERAZIONE DATI SINTETICI PER 10,000 UTENTI E 5,000 FILM...")
# Genera utenti
countries = ['US', 'UK', 'CA', 'AU', 'DE', 'FR', 'IT', 'ES', 'JP', 'BR', 'IN', 'CN']
subscriptions = ['basic', 'standard', 'premium']
users_data = []
for user_id in range(1, 10001):
age = random.randint(13, 80)
gender = random.choice(['M', 'F', 'O'])
country = random.choice(countries)
subscription = random.choices(subscriptions, weights=[0.4, 0.4, 0.2])[0]
join_date = datetime(2020, 1, 1) + timedelta(days=random.randint(0, 1460))
watch_time = random.expovariate(1/100) # Media 100 ore
users_data.append((
user_id, age, gender, country, subscription,
join_date.strftime('%Y-%m-%d'), round(watch_time, 2)
))
cursor.executemany('''
INSERT INTO users (user_id, age, gender, country, subscription_type, join_date, total_watch_time_hours)
VALUES (?, ?, ?, ?, ?, ?, ?)
''', users_data)
print(f"✅ {len(users_data)} utenti generati")
# Genera film
print("🎬 Generazione catalogo film...")
# Dataset di film reali (sintetizzato)
movie_templates = [
# Titolo, Anno, Generi, Regista, Durata, Rating IMDb
("The Matrix", 1999, "Action|Sci-Fi", "Lana Wachowski", 136, 8.7),
("Inception", 2010, "Action|Sci-Fi|Thriller", "Christopher Nolan", 148, 8.8),
("The Dark Knight", 2008, "Action|Crime|Drama", "Christopher Nolan", 152, 9.0),
("Pulp Fiction", 1994, "Crime|Drama", "Quentin Tarantino", 154, 8.9),
("Forrest Gump", 1994, "Drama|Romance", "Robert Zemeckis", 142, 8.8),
("The Godfather", 1972, "Crime|Drama", "Francis Ford Coppola", 175, 9.2),
("Fight Club", 1999, "Drama", "David Fincher", 139, 8.8),
("The Shawshank Redemption", 1994, "Drama", "Frank Darabont", 142, 9.3),
("Interstellar", 2014, "Adventure|Drama|Sci-Fi", "Christopher Nolan", 169, 8.6),
("Parasite", 2019, "Comedy|Drama|Thriller", "Bong Joon Ho", 132, 8.6),
]
# Espandi con variazioni
movies_data = []
movie_id = 1
genres_list = ["Action", "Adventure", "Comedy", "Drama", "Horror", "Romance",
"Sci-Fi", "Thriller", "Documentary", "Animation", "Fantasy"]
directors = ["Christopher Nolan", "Steven Spielberg", "Martin Scorsese",
"Quentin Tarantino", "James Cameron", "David Fincher",
"Peter Jackson", "Tim Burton", "Ridley Scott", "Alfred Hitchcock"]
actors_pool = ["Leonardo DiCaprio", "Meryl Streep", "Tom Hanks", "Scarlett Johansson",
"Robert De Niro", "Jennifer Lawrence", "Denzel Washington",
"Emma Stone", "Brad Pitt", "Angelina Jolie", "Morgan Freeman"]
for _ in range(5000):
if movie_id <= len(movie_templates):
title, year, genre, director, duration, imdb = movie_templates[movie_id-1]
else:
# Genera film casuali
title = f"Movie {movie_id}"
year = random.randint(1980, 2023)
num_genres = random.randint(1, 3)
genre = "|".join(random.sample(genres_list, num_genres))
director = random.choice(directors)
duration = random.randint(80, 180)
imdb = round(random.uniform(5.0, 9.5), 1)
# Genera descrizione
description_words = ["epic", "thrilling", "emotional", "mind-bending",
"heartwarming", "suspenseful", "funny", "dramatic"]
description = f"A {random.choice(description_words)} story about {random.choice(['love', 'war', 'friendship', 'betrayal', 'redemption'])}."
# Attori principali
num_actors = random.randint(2, 4)
main_actors = "|".join(random.sample(actors_pool, num_actors))
# Altri metadati
language = random.choice(["English", "Spanish", "French", "German", "Japanese"])
country = random.choice(["USA", "UK", "France", "Japan", "South Korea", "India"])
budget = round(random.uniform(10, 300), 2)
revenue = round(budget * random.uniform(1, 10), 2)
movies_data.append((
movie_id, title, year, genre, director, duration, imdb,
description, main_actors, language, country, budget, revenue
))
movie_id += 1
cursor.executemany('''
INSERT INTO movies VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', movies_data)
print(f"✅ {len(movies_data)} film generati")
# 1.5 GENERAZIONE RATING REALISTICI (10% SPARSITÀ)
print("\n⭐ GENERAZIONE RATING UTENTE-FILM (MATRICE SPARSA)...")
ratings_data = []
num_ratings = 0
# Simula che ogni utente abbia visto/rateato alcuni film
for user_id in range(1, 10001):
# Numero di film rateati da questo utente (power law distribution)
num_movies_rated = int(np.random.power(0.7) * 200) + 1
# Seleziona film casuali (non tutti gli utenti vedono tutti i film)
rated_movies = random.sample(range(1, 5001), min(num_movies_rated, 100))
for movie_id in rated_movies:
# Bias rating basato su età, genere, etc.
base_rating = random.uniform(2.5, 4.5)
# Aggiungi rumore
rating = max(0.5, min(5.0, round(base_rating + random.uniform(-1, 1), 1)))
# Altri dati
watch_duration = random.randint(10, duration if 'duration' in locals() else 120)
device = random.choice(['mobile', 'tablet', 'tv', 'desktop'])
timestamp = datetime(2023, 1, 1) + timedelta(
days=random.randint(0, 365),
hours=random.randint(0, 23),
minutes=random.randint(0, 59)
)
ratings_data.append((
user_id, movie_id, rating, timestamp.strftime('%Y-%m-%d %H:%M:%S'),
watch_duration, device
))
num_ratings += 1
if len(ratings_data) % 100000 == 0:
print(f" Generati {len(ratings_data):,} rating...")
# Inserimento in batch
batch_size = 50000
for i in range(0, len(ratings_data), batch_size):
batch = ratings_data[i:i+batch_size]
cursor.executemany('''
INSERT INTO ratings (user_id, movie_id, rating, timestamp, watch_duration_minutes, device_type)
VALUES (?, ?, ?, ?, ?, ?)
''', batch)
print(f"✅ {len(ratings_data):,} rating generati")
print(f" • Sparsità: {(1 - len(ratings_data)/(10000*5000))*100:.1f}%")
print(f" • Rating per utente medio: {len(ratings_data)/10000:.1f}")
# 1.6 AGGIORNA STATISTICHE UTENTI
print("\n📈 CALCOLO STATISTICHE UTENTI...")
# Aggiorna avg_rating per ogni utente
cursor.execute('''
UPDATE users
SET avg_rating = (
SELECT AVG(rating)
FROM ratings
WHERE ratings.user_id = users.user_id
)
WHERE EXISTS (
SELECT 1 FROM ratings WHERE ratings.user_id = users.user_id
)
''')
# 1.7 VERIFICA DATABASE
print("\n🔍 VERIFICA DATABASE COMPLETATO...")
tables_info = cursor.execute('''
SELECT name FROM sqlite_master WHERE type='table'
''').fetchall()
print("📊 TABELLE NEL DATABASE:")
for table in tables_info:
count = cursor.execute(f'SELECT COUNT(*) FROM {table[0]}').fetchone()[0]
print(f" • {table[0]}: {count:,} righe")
# Statistiche rating
rating_stats = cursor.execute('''
SELECT
COUNT(*) as total_ratings,
AVG(rating) as avg_rating,
MIN(rating) as min_rating,
MAX(rating) as max_rating
FROM ratings
''').fetchone()
print(f"\n⭐ STATISTICHE RATING:")
print(f" • Totale rating: {rating_stats[0]:,}")
print(f" • Rating medio: {rating_stats[1]:.2f}/5.0")
print(f" • Range: {rating_stats[2]:.1f} - {rating_stats[3]:.1f}")
# Distribuzione rating
distribuzione = cursor.execute('''
SELECT rating, COUNT(*) as count
FROM ratings
GROUP BY rating
ORDER BY rating
''').fetchall()
print(f"\n📈 DISTRIBUZIONE RATING:")
for rating, count in distribuzione:
percent = (count / rating_stats[0]) * 100
print(f" • {rating:.1f}: {count:6,} ({percent:5.1f}%) {'█' * int(percent/2)}")
# 1.8 SALVA DATABASE SU FILE (PER PERSISTENZA)
conn.commit()
print("\n💾 DATABASE SALVATO CON SUCCESSO!")
print(" • Utenti: 10,000")
print(" • Film: 5,000")
print(" • Rating: ~1,000,000")
print(" • Pronto per analisi ML e Deep Learning!")
print("\n" + "=" * 70)
print("✅ PARTE 1 COMPLETATA: DATABASE PROFESSIONALE CREATO")
print("=" * 70)
Parte 2: Analisi Dati e Feature Engineering Avanzato
EDA Completa e Preparazione Dati per ML
# ================================================
# 📊 PARTE 2: ANALISI DATI E FEATURE ENGINEERING
# ================================================
print("\n" + "=" * 70)
print("📈 ANALISI ESPLORATIVA DATI (EDA) E FEATURE ENGINEERING")
print("=" * 70)
# 2.1 ESTRAZIONE DATI DA SQL A PANDAS
print("\n🔍 ESTRAZIONE DATI PER ANALISI...")
# Carica dati principali in DataFrame
users_df = pd.read_sql_query("SELECT * FROM users", conn)
movies_df = pd.read_sql_query("SELECT * FROM movies", conn)
ratings_df = pd.read_sql_query("SELECT * FROM ratings", conn)
print(f"✅ Dati estratti:")
print(f" • Utenti: {users_df.shape[0]:,} x {users_df.shape[1]} colonne")
print(f" • Film: {movies_df.shape[0]:,} x {movies_df.shape[1]} colonne")
print(f" • Rating: {ratings_df.shape[0]:,} x {ratings_df.shape[1]} colonne")
# 2.2 ANALISI UTENTI
print("\n👥 ANALISI DEMOGRAFICA UTENTI...")
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
fig.suptitle('Analisi Demografica Utenti', fontsize=16, fontweight='bold')
# Distribuzione età
axes[0, 0].hist(users_df['age'], bins=30, edgecolor='black', alpha=0.7, color='skyblue')
axes[0, 0].set_xlabel('Età')
axes[0, 0].set_ylabel('Frequenza')
axes[0, 0].set_title('Distribuzione Età Utenti')
axes[0, 0].grid(True, alpha=0.3)
# Distribuzione genere
gender_counts = users_df['gender'].value_counts()
axes[0, 1].pie(gender_counts.values, labels=gender_counts.index, autopct='%1.1f%%',
colors=['lightblue', 'lightpink', 'lightgreen'])
axes[0, 1].set_title('Distribuzione Genere')
# Distribuzione paese
top_countries = users_df['country'].value_counts().head(10)
axes[0, 2].barh(top_countries.index, top_countries.values, color='lightcoral')
axes[0, 2].set_xlabel('Numero Utenti')
axes[0, 2].set_title('Top 10 Paesi Utenti')
axes[0, 2].invert_yaxis()
# Tipo abbonamento
subscription_counts = users_df['subscription_type'].value_counts()
axes[1, 0].bar(subscription_counts.index, subscription_counts.values,
color=['lightgray', 'gold', 'lightblue'])
axes[1, 0].set_xlabel('Tipo Abbonamento')
axes[1, 0].set_ylabel('Numero Utenti')
axes[1, 0].set_title('Distribuzione Abbonamenti')
for i, v in enumerate(subscription_counts.values):
axes[1, 0].text(i, v + 50, str(v), ha='center')
# Tempo visione totale
axes[1, 1].hist(users_df['total_watch_time_hours'], bins=50,
edgecolor='black', alpha=0.7, color='mediumseagreen')
axes[1, 1].set_xlabel('Ore Totali Visione')
axes[1, 1].set_ylabel('Frequenza')
axes[1, 1].set_title('Distribuzione Ore Visione')
axes[1, 1].grid(True, alpha=0.3)
# Rating medio utenti
axes[1, 2].hist(users_df['avg_rating'].dropna(), bins=30,
edgecolor='black', alpha=0.7, color='orange')
axes[1, 2].set_xlabel('Rating Medio')
axes[1, 2].set_ylabel('Frequenza')
axes[1, 2].set_title('Distribuzione Rating Medio Utenti')
axes[1, 2].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 2.3 ANALISI FILM
print("\n🎬 ANALISI CATALOGO FILM...")
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
fig.suptitle('Analisi Catalogo Film', fontsize=16, fontweight='bold')
# Distribuzione anni di uscita
axes[0, 0].hist(movies_df['release_year'], bins=30, edgecolor='black', alpha=0.7, color='coral')
axes[0, 0].set_xlabel('Anno di Uscita')
axes[0, 0].set_ylabel('Numero Film')
axes[0, 0].set_title('Distribuzione Anni di Uscita')
axes[0, 0].grid(True, alpha=0.3)
# Distribuzione durata
axes[0, 1].hist(movies_df['duration_minutes'], bins=30, edgecolor='black', alpha=0.7, color='lightseagreen')
axes[0, 1].set_xlabel('Durata (minuti)')
axes[0, 1].set_ylabel('Numero Film')
axes[0, 1].set_title('Distribuzione Durata Film')
axes[0, 1].grid(True, alpha=0.3)
# Distribuzione rating IMDb
axes[0, 2].hist(movies_df['imdb_rating'], bins=20, edgecolor='black', alpha=0.7, color='gold')
axes[0, 2].set_xlabel('Rating IMDb')
axes[0, 2].set_ylabel('Numero Film')
axes[0, 2].set_title('Distribuzione Rating IMDb')
axes[0, 2].grid(True, alpha=0.3)
# Analisi generi (più frequenti)
# Estrai tutti i generi
all_genres = []
for genres in movies_df['genre'].dropna():
all_genres.extend(genres.split('|'))
genre_counts = pd.Series(all_genres).value_counts().head(15)
axes[1, 0].barh(genre_counts.index, genre_counts.values, color='mediumpurple')
axes[1, 0].set_xlabel('Numero Film')
axes[1, 0].set_title('Top 15 Generi')
axes[1, 0].invert_yaxis()
# Budget vs Revenue
axes[1, 1].scatter(movies_df['budget_millions'], movies_df['revenue_millions'],
alpha=0.5, color='dodgerblue', s=20)
axes[1, 1].set_xlabel('Budget (milioni $)')
axes[1, 1].set_ylabel('Revenue (milioni $)')
axes[1, 1].set_title('Budget vs Revenue')
axes[1, 1].grid(True, alpha=0.3)
# Cloud parole descrizioni
# Combina tutte le descrizioni
all_descriptions = ' '.join(movies_df['description'].dropna().astype(str))
wordcloud = WordCloud(width=800, height=400, background_color='white',
max_words=100, contour_width=3, contour_color='steelblue').generate(all_descriptions)
axes[1, 2].imshow(wordcloud, interpolation='bilinear')
axes[1, 2].axis('off')
axes[1, 2].set_title('Word Cloud Descrizioni Film')
plt.tight_layout()
plt.show()
# 2.4 ANALISI RATING E INTERAZIONI
print("\n⭐ ANALISI RATING E COMPORTAMENTO UTENTI...")
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
fig.suptitle('Analisi Rating e Interazioni', fontsize=16, fontweight='bold')
# Heatmap rating nel tempo (sample)
rating_sample = ratings_df.sample(10000, random_state=42)
rating_sample['hour'] = pd.to_datetime(rating_sample['timestamp']).dt.hour
rating_sample['day_of_week'] = pd.to_datetime(rating_sample['timestamp']).dt.dayofweek
pivot_data = rating_sample.pivot_table(
index='day_of_week',
columns='hour',
values='rating',
aggfunc='mean'
)
im = axes[0, 0].imshow(pivot_data, cmap='YlOrRd', aspect='auto')
axes[0, 0].set_xlabel('Ora del Giorno')
axes[0, 0].set_ylabel('Giorno della Settimana')
axes[0, 0].set_title('Heatmap Rating per Ora/Giorno')
axes[0, 0].set_xticks(range(0, 24, 4))
axes[0, 0].set_yticks(range(7))
axes[0, 0].set_yticklabels(['Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab', 'Dom'])
plt.colorbar(im, ax=axes[0, 0])
# Distribuzione device
device_counts = ratings_df['device_type'].value_counts()
axes[0, 1].pie(device_counts.values, labels=device_counts.index, autopct='%1.1f%%',
colors=['lightblue', 'lightgreen', 'lightcoral', 'gold'])
axes[0, 1].set_title('Distribuzione Device')
# Rating per genere film (top 10)
# Unisci rating con generi film
rating_with_genre = pd.merge(
ratings_df[['movie_id', 'rating']],
movies_df[['movie_id', 'genre']],
on='movie_id'
)
# Espandi generi (un film può avere multiple generi)
expanded_ratings = []
for idx, row in rating_with_genre.iterrows():
if pd.notna(row['genre']):
for genre in row['genre'].split('|'):
expanded_ratings.append({'genre': genre, 'rating': row['rating']})
genre_ratings_df = pd.DataFrame(expanded_ratings)
genre_avg_rating = genre_ratings_df.groupby('genre')['rating'].agg(['mean', 'count']).sort_values('count', ascending=False).head(10)
axes[0, 2].barh(genre_avg_rating.index, genre_avg_rating['mean'], color='lightseagreen')
axes[0, 2].set_xlabel('Rating Medio')
axes[0, 2].set_title('Top 10 Generi per Rating Medio')
axes[0, 2].invert_yaxis()
# Distribuzione numero rating per utente
user_rating_counts = ratings_df['user_id'].value_counts()
axes[1, 0].hist(user_rating_counts.values, bins=50, edgecolor='black', alpha=0.7, color='mediumorchid', log=True)
axes[1, 0].set_xlabel('Numero Rating per Utente')
axes[1, 0].set_ylabel('Frequenza (log)')
axes[1, 0].set_title('Distribuzione Rating per Utente (log scale)')
axes[1, 0].grid(True, alpha=0.3)
# Distribuzione numero rating per film
movie_rating_counts = ratings_df['movie_id'].value_counts()
axes[1, 1].hist(movie_rating_counts.values, bins=50, edgecolor='black', alpha=0.7, color='darkorange', log=True)
axes[1, 1].set_xlabel('Numero Rating per Film')
axes[1, 1].set_ylabel('Frequenza (log)')
axes[1, 1].set_title('Distribuzione Rating per Film (log scale)')
axes[1, 1].grid(True, alpha=0.3)
# Correlazione età utente con rating medio
user_age_rating = pd.merge(
users_df[['user_id', 'age', 'avg_rating']],
ratings_df[['user_id', 'rating']],
on='user_id'
).groupby('age')['rating'].mean().reset_index()
axes[1, 2].scatter(user_age_rating['age'], user_age_rating['rating'], alpha=0.5, color='crimson', s=30)
axes[1, 2].set_xlabel('Età Utente')
axes[1, 2].set_ylabel('Rating Medio')
axes[1, 2].set_title('Rating Medio per Età Utente')
axes[1, 2].grid(True, alpha=0.3)
# Regression line
z = np.polyfit(user_age_rating['age'], user_age_rating['rating'], 1)
p = np.poly1d(z)
axes[1, 2].plot(user_age_rating['age'], p(user_age_rating['age']), "r--", alpha=0.8)
plt.tight_layout()
plt.show()
# 2.5 FEATURE ENGINEERING AVANZATO
print("\n🔧 FEATURE ENGINEERING AVANZATO PER ML...")
# A. Features per Utenti
print("\n👤 CREAZIONE FEATURES UTENTI:")
user_features = users_df.copy()
# 1. Activity level basato su numero rating
user_activity = ratings_df['user_id'].value_counts().reset_index()
user_activity.columns = ['user_id', 'num_ratings']
user_features = pd.merge(user_features, user_activity, on='user_id', how='left')
user_features['num_ratings'] = user_features['num_ratings'].fillna(0)
# 2. Rating std dev (quanto è severo/indulgente)
user_rating_stats = ratings_df.groupby('user_id')['rating'].agg(['std', 'min', 'max']).reset_index()
user_rating_stats.columns = ['user_id', 'rating_std', 'rating_min', 'rating_max']
user_features = pd.merge(user_features, user_rating_stats, on='user_id', how='left')
# 3. Genre preference (top 3 generi preferiti)
user_genre_pref = pd.merge(
ratings_df[['user_id', 'movie_id', 'rating']],
movies_df[['movie_id', 'genre']],
on='movie_id'
)
# Espandi generi e calcola preferenze
user_genre_scores = []
for user_id in user_features['user_id']:
user_ratings = user_genre_pref[user_genre_pref['user_id'] == user_id]
if len(user_ratings) > 0:
genre_scores = {}
for idx, row in user_ratings.iterrows():
if pd.notna(row['genre']):
genres = row['genre'].split('|')
for genre in genres:
if genre not in genre_scores:
genre_scores[genre] = []
genre_scores[genre].append(row['rating'])
# Calcola rating medio per ogni genere
genre_avg = {genre: np.mean(scores) for genre, scores in genre_scores.items() if len(scores) > 2}
if genre_avg:
top_genres = sorted(genre_avg.items(), key=lambda x: x[1], reverse=True)[:3]
user_genre_scores.append({
'user_id': user_id,
'top_genre_1': top_genres[0][0] if len(top_genres) > 0 else None,
'top_genre_1_score': top_genres[0][1] if len(top_genres) > 0 else 0,
'top_genre_2': top_genres[1][0] if len(top_genres) > 1 else None,
'top_genre_3': top_genres[2][0] if len(top_genres) > 2 else None
})
user_genre_df = pd.DataFrame(user_genre_scores)
user_features = pd.merge(user_features, user_genre_df, on='user_id', how='left')
print(f" • Features utenti create: {user_features.shape[1]} colonne")
# B. Features per Film
print("\n🎬 CREAZIONE FEATURES FILM:")
movie_features = movies_df.copy()
# 1. Popularity score (basato su numero rating)
movie_popularity = ratings_df['movie_id'].value_counts().reset_index()
movie_popularity.columns = ['movie_id', 'num_ratings']
movie_features = pd.merge(movie_features, movie_popularity, on='movie_id', how='left')
movie_features['num_ratings'] = movie_features['num_ratings'].fillna(0)
# 2. Average rating e variabilità
movie_rating_stats = ratings_df.groupby('movie_id')['rating'].agg(['mean', 'std', 'count']).reset_index()
movie_rating_stats.columns = ['movie_id', 'avg_rating', 'rating_std', 'rating_count']
movie_features = pd.merge(movie_features, movie_rating_stats, on='movie_id', how='left')
# 3. Genre encoding (one-hot per top 15 generi)
top_genres = pd.Series(all_genres).value_counts().head(15).index.tolist()
for genre in top_genres:
movie_features[f'genre_{genre.lower().replace(" ", "_")}'] = movie_features['genre'].apply(
lambda x: 1 if pd.notna(x) and genre in x.split('|') else 0
)
# 4. Text features dalle descrizioni (lunghezza, complessità)
movie_features['desc_length'] = movie_features['description'].apply(
lambda x: len(str(x).split()) if pd.notna(x) else 0
)
movie_features['desc_sentiment'] = movie_features['description'].apply(
lambda x: len([w for w in str(x).lower().split() if w in ['great', 'awesome', 'amazing', 'love', 'best']]) -
len([w for w in str(x).lower().split() if w in ['bad', 'terrible', 'worst', 'boring', 'slow']])
)
# 5. Success score (revenue/budget)
movie_features['success_ratio'] = movie_features.apply(
lambda row: row['revenue_millions'] / row['budget_millions']
if row['budget_millions'] > 0 else 0, axis=1
)
print(f" • Features film create: {movie_features.shape[1]} colonne")
# C. Matrice Interazioni Utente-Film
print("\n🔗 CREAZIONE MATRICE INTERAZIONI PER COLLABORATIVE FILTERING...")
# Crea user-item matrix
user_item_matrix = ratings_df.pivot_table(
index='user_id',
columns='movie_id',
values='rating'
).fillna(0)
print(f" • User-Item Matrix: {user_item_matrix.shape[0]} utenti x {user_item_matrix.shape[1]} film")
print(f" • Sparsità: {(user_item_matrix == 0).sum().sum() / (user_item_matrix.shape[0] * user_item_matrix.shape[1]) * 100:.2f}%")
# D. Preparazione Dataset per ML
print("\n📊 PREPARAZIONE DATASET FINALE PER MODELLI ML...")
# Crea dataset combinato per supervised learning
ml_dataset = ratings_df[['user_id', 'movie_id', 'rating']].copy()
# Aggiungi features utenti
ml_dataset = pd.merge(ml_dataset, user_features, on='user_id', how='left')
# Aggiungi features film
ml_dataset = pd.merge(ml_dataset, movie_features, on='movie_id', how='left')
# Rimuovi colonne non necessarie
columns_to_drop = ['title', 'genre', 'director', 'description', 'main_actors',
'language', 'production_country', 'join_date', 'top_genre_1',
'top_genre_2', 'top_genre_3', 'timestamp', 'device_type']
ml_dataset = ml_dataset.drop(columns=[col for col in columns_to_drop if col in ml_dataset.columns])
# Gestione valori mancanti
ml_dataset = ml_dataset.fillna(0)
print(f"✅ Dataset ML creato:")
print(f" • Dimensioni: {ml_dataset.shape[0]:,} samples x {ml_dataset.shape[1]} features")
print(f" • Target: 'rating' (valore da 0.5 a 5.0)")
print(f" • Features numeriche: {len(ml_dataset.select_dtypes(include=[np.number]).columns)}")
print(f" • Features categoriche: {len(ml_dataset.select_dtypes(include=['object']).columns)}")
# E. Salvataggio dataset per uso futuro
ml_dataset.to_csv('movieflix_ml_dataset.csv', index=False)
user_features.to_csv('user_features.csv', index=False)
movie_features.to_csv('movie_features.csv', index=False)
print("\n💾 DATASET SALVATI:")
print(" • movieflix_ml_dataset.csv - Dataset completo per ML")
print(" • user_features.csv - Features utenti")
print(" • movie_features.csv - Features film")
print("\n" + "=" * 70)
print("✅ PARTE 2 COMPLETATA: ANALISI DATI E FEATURE ENGINEERING")
print("=" * 70)
Parte 3: Modelli Machine Learning Classici
Implementazione Algoritmi Raccomandazione Classici
# ================================================
# 🤖 PARTE 3: MODELLI MACHINE LEARNING CLASSICI
# ================================================
print("\n" + "=" * 70)
print("🧠 IMPLEMENTAZIONE ALGORITMI RACCOMANDAZIONE CLASSICI")
print("=" * 70)
# Carica dataset preparato
print("\n📂 CARICAMENTO DATASET PREPARATO...")
ml_dataset = pd.read_csv('movieflix_ml_dataset.csv')
user_features = pd.read_csv('user_features.csv')
movie_features = pd.read_csv('movie_features.csv')
print(f"📊 Dataset caricati:")
print(f" • ML Dataset: {ml_dataset.shape}")
print(f" • User Features: {user_features.shape}")
print(f" • Movie Features: {movie_features.shape}")
# 3.1 PREPARAZIONE DATI PER MODELLI DIVERSI
print("\n🔧 PREPARAZIONE DATI PER MODELLI ML...")
# Separazione features e target
X = ml_dataset.drop(columns=['rating', 'user_id', 'movie_id'])
y = ml_dataset['rating']
# Identifica colonne numeriche e categoriche
numeric_cols = X.select_dtypes(include=[np.number]).columns.tolist()
categorical_cols = X.select_dtypes(include=['object']).columns.tolist()
print(f" • Features numeriche: {len(numeric_cols)}")
print(f" • Features categoriche: {len(categorical_cols)}")
# Encoding features categoriche
label_encoders = {}
for col in categorical_cols:
le = LabelEncoder()
X[col] = le.fit_transform(X[col].astype(str))
label_encoders[col] = le
# Split train/test
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=pd.cut(y, bins=10)
)
# Scaling features numeriche
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
print(f"\n🎯 SPLIT DATASET:")
print(f" • Training set: {X_train.shape[0]:,} samples")
print(f" • Test set: {X_test.shape[0]:,} samples")
print(f" • Features: {X_train.shape[1]}")
# 3.2 MODEL 1: RANDOM FOREST REGRESSOR (Baseline)
print("\n" + "=" * 50)
print("🌲 MODELLO 1: RANDOM FOREST REGRESSOR")
print("=" * 50)
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
print("\n🚀 Addestramento Random Forest...")
rf_model = RandomForestRegressor(
n_estimators=100,
max_depth=15,
min_samples_split=5,
min_samples_leaf=2,
random_state=42,
n_jobs=-1
)
rf_model.fit(X_train_scaled, y_train)
# Predizioni
y_pred_rf = rf_model.predict(X_test_scaled)
# Metriche
rf_rmse = np.sqrt(mean_squared_error(y_test, y_pred_rf))
rf_mae = mean_absolute_error(y_test, y_pred_rf)
rf_r2 = r2_score(y_test, y_pred_rf)
print(f"\n📊 PERFORMANCE RANDOM FOREST:")
print(f" • RMSE: {rf_rmse:.4f}")
print(f" • MAE: {rf_mae:.4f}")
print(f" • R² Score: {rf_r2:.4f}")
# Feature importance
rf_feature_importance = pd.DataFrame({
'feature': X.columns,
'importance': rf_model.feature_importances_
}).sort_values('importance', ascending=False)
print(f"\n🏆 TOP 10 FEATURE IMPORTANCE:")
print(rf_feature_importance.head(10).to_string(index=False))
# 3.3 MODEL 2: MATRIX FACTORIZATION (SVD)
print("\n" + "=" * 50)
print("🔢 MODELLO 2: MATRIX FACTORIZATION (SVD)")
print("=" * 50)
print("\n🎯 Preparazione User-Item Matrix per Collaborative Filtering...")
# Crea user-item matrix sparsa
user_item_matrix = ratings_df.pivot_table(
index='user_id',
columns='movie_id',
values='rating'
).fillna(0)
# Converti in formato sparse per efficienza
sparse_matrix = sp.csr_matrix(user_item_matrix.values)
print(f" • User-Item Matrix: {sparse_matrix.shape}")
print(f" • Sparsity: {(sparse_matrix == 0).sum() / (sparse_matrix.shape[0] * sparse_matrix.shape[1]) * 100:.2f}%")
# Applica SVD (Truncated SVD per matrici sparse)
n_components = 50
print(f"\n🔧 Applicando Truncated SVD con {n_components} componenti...")
svd = TruncatedSVD(n_components=n_components, random_state=42)
user_factors = svd.fit_transform(sparse_matrix)
item_factors = svd.components_.T
print(f" • Explained variance: {svd.explained_variance_ratio_.sum():.3f}")
print(f" • User factors shape: {user_factors.shape}")
print(f" • Item factors shape: {item_factors.shape}")
# Funzione per predizioni SVD
def predict_svd_rating(user_idx, movie_idx):
"""Predici rating usando fattori SVD"""
if user_idx >= user_factors.shape[0] or movie_idx >= item_factors.shape[0]:
return 2.5 # Valore di default
return np.dot(user_factors[user_idx], item_factors[movie_idx])
# Test su subset
test_sample = ml_dataset.sample(10000, random_state=42)
svd_predictions = []
for _, row in test_sample.iterrows():
user_id = row['user_id']
movie_id = row['movie_id']
# Converti ID a indici
user_idx = user_id - 1 if user_id <= user_item_matrix.shape[0] else None
movie_idx = movie_id - 1 if movie_id <= user_item_matrix.shape[1] else None
if user_idx is not None and movie_idx is not None:
pred = predict_svd_rating(user_idx, movie_idx)
# Clip tra 0.5 e 5.0
pred = max(0.5, min(5.0, pred))
svd_predictions.append(pred)
else:
svd_predictions.append(2.5)
# Valuta solo predizioni valide
valid_idx = [i for i, pred in enumerate(svd_predictions) if pred != 2.5]
if valid_idx:
svd_rmse = np.sqrt(mean_squared_error(
test_sample.iloc[valid_idx]['rating'],
np.array(svd_predictions)[valid_idx]
))
svd_mae = mean_absolute_error(
test_sample.iloc[valid_idx]['rating'],
np.array(svd_predictions)[valid_idx]
)
else:
svd_rmse = svd_mae = float('inf')
print(f"\n📊 PERFORMANCE SVD:")
print(f" • RMSE: {svd_rmse:.4f}")
print(f" • MAE: {svd_mae:.4f}")
print(f" • Predizioni valide: {len(valid_idx)}/{len(test_sample)}")
# 3.4 MODEL 3: K-NEAREST NEIGHBORS (Item-Based CF)
print("\n" + "=" * 50)
print("👥 MODELLO 3: K-NEAREST NEIGHBORS (Item-Based)")
print("=" * 50)
print("\n🔍 Costruzione modello KNN per similarità film...")
# Usa item factors da SVD per similarità
knn_model = NearestNeighbors(
n_neighbors=20,
metric='cosine',
algorithm='brute'
)
knn_model.fit(item_factors)
# Funzione per raccomandazioni KNN
def knn_recommendations(movie_id, n_recommendations=10):
"""Trova film simili usando KNN"""
movie_idx = movie_id - 1
if movie_idx >= item_factors.shape[0]:
return []
distances, indices = knn_model.kneighbors(
item_factors[movie_idx].reshape(1, -1),
n_neighbors=n_recommendations + 1
)
# Escludi il film stesso (primo risultato)
similar_movies = []
for i in range(1, len(indices[0])):
similar_movie_id = indices[0][i] + 1
similarity = 1 - distances[0][i] # Converti distanza in similarità
similar_movies.append((similar_movie_id, similarity))
return similar_movies
# Test KNN su alcuni film
test_movies = [1, 100, 500, 1000, 2500] # Matrix, Inception, etc.
print("\n🎯 TEST RACCOMANDAZIONI KNN:")
for movie_id in test_movies:
movie_title = movies_df[movies_df['movie_id'] == movie_id]['title'].values
title = movie_title[0] if len(movie_title) > 0 else f"Movie {movie_id}"
recommendations = knn_recommendations(movie_id, 5)
print(f"\n🎬 Per '{title}' (ID: {movie_id}):")
for rec_id, similarity in recommendations:
rec_title = movies_df[movies_df['movie_id'] == rec_id]['title'].values
rec_title = rec_title[0] if len(rec_title) > 0 else f"Movie {rec_id}"
print(f" • {rec_title} (similarity: {similarity:.3f})")
# 3.5 MODEL 4: CONTENT-BASED FILTERING
print("\n" + "=" * 50)
print("📝 MODELLO 4: CONTENT-BASED FILTERING")
print("=" * 50)
print("\n🔠 Estrazione features dai testi delle descrizioni...")
# Preprocessing descrizioni
def preprocess_text(text):
"""Pulizia e tokenizzazione testo"""
if pd.isna(text):
return ""
# Converti in lowercase
text = str(text).lower()
# Rimuovi caratteri speciali
text = re.sub(r'[^a-z\s]', '', text)
# Tokenizza
tokens = word_tokenize(text)
# Rimuovi stopwords
stop_words = set(stopwords.words('english'))
tokens = [word for word in tokens if word not in stop_words]
return ' '.join(tokens)
# Applica preprocessing
movies_df['processed_description'] = movies_df['description'].apply(preprocess_text)
# TF-IDF Vectorization
from sklearn.feature_extraction.text import TfidfVectorizer
print("📊 Creazione TF-IDF vectors per descrizioni film...")
tfidf = TfidfVectorizer(
max_features=1000,
stop_words='english',
ngram_range=(1, 2)
)
tfidf_matrix = tfidf.fit_transform(movies_df['processed_description'].fillna(''))
print(f" • TF-IDF Matrix: {tfidf_matrix.shape}")
print(f" • Vocabolario: {len(tfidf.get_feature_names_out())} parole")
# Cosine similarity tra film basata su TF-IDF
from sklearn.metrics.pairwise import cosine_similarity
print("\n🔍 Calcolo similarità cosine tra film...")
content_similarity = cosine_similarity(tfidf_matrix, tfidf_matrix)
# Funzione per raccomandazioni content-based
def content_based_recommendations(movie_id, n_recommendations=10):
"""Raccomandazioni basate su similarità di contenuto"""
movie_idx = movie_id - 1
if movie_idx >= content_similarity.shape[0]:
return []
# Prendi similarity scores per questo film
sim_scores = list(enumerate(content_similarity[movie_idx]))
# Ordina per similarità (escludendo il film stesso)
sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)[1:n_recommendations+1]
# Estrai ID film e similarity scores
recommendations = [(score[0] + 1, score[1]) for score in sim_scores]
return recommendations
# Test content-based filtering
print("\n🎯 TEST RACCOMANDAZIONI CONTENT-BASED:")
for movie_id in test_movies[:3]: # Solo primi 3 per brevità
movie_title = movies_df[movies_df['movie_id'] == movie_id]['title'].values
title = movie_title[0] if len(movie_title) > 0 else f"Movie {movie_id}"
recommendations = content_based_recommendations(movie_id, 5)
print(f"\n🎬 Per '{title}' (ID: {movie_id}):")
for rec_id, similarity in recommendations:
rec_title = movies_df[movies_df['movie_id'] == rec_id]['title'].values
rec_title = rec_title[0] if len(rec_title) > 0 else f"Movie {rec_id}"
print(f" • {rec_title} (similarity: {similarity:.3f})")
# 3.6 MODEL 5: HYBRID APPROACH
print("\n" + "=" * 50)
print("🤝 MODELLO 5: APPROCCIO IBRIDO (Collaborative + Content)")
print("=" * 50)
print("\n🔗 Combinazione Collaborative Filtering e Content-Based Filtering...")
def hybrid_recommendations(user_id, movie_id=None, n_recommendations=10, alpha=0.7):
"""
Raccomandazioni ibride che combinano:
- Collaborative filtering (peso: alpha)
- Content-based filtering (peso: 1 - alpha)
"""
recommendations = {}
# Se movie_id è fornito, usa come punto di partenza
if movie_id:
# 1. Content-based recommendations
content_recs = content_based_recommendations(movie_id, n_recommendations * 2)
# 2. Per ogni film raccomandato, trova utenti simili che l'hanno visto
for rec_id, content_score in content_recs:
# Trova utenti che hanno visto questo film
movie_ratings = ratings_df[ratings_df['movie_id'] == rec_id]
if len(movie_ratings) > 0:
# Calcola rating medio per questo film
avg_rating = movie_ratings['rating'].mean()
# Trova similarità con utente corrente (se presente)
user_rating = ratings_df[
(ratings_df['user_id'] == user_id) &
(ratings_df['movie_id'] == rec_id)
]
if len(user_rating) == 0: # Utente non ha ancora visto
# Punteggio ibrido
hybrid_score = (alpha * avg_rating/5.0) + ((1 - alpha) * content_score)
recommendations[rec_id] = hybrid_score
else:
# Solo content-based se nessun rating
recommendations[rec_id] = (1 - alpha) * content_score
# Se abbiamo abbastanza raccomandazioni, restituisci le migliori
if recommendations:
sorted_recs = sorted(recommendations.items(), key=lambda x: x[1], reverse=True)
return [(rec_id, score) for rec_id, score in sorted_recs[:n_recommendations]]
else:
# Fallback: top film popolari
popular_movies = movie_features.sort_values('num_ratings', ascending=False).head(n_recommendations)
return [(row['movie_id'], row['num_ratings']/1000) for _, row in popular_movies.iterrows()]
# Test approccio ibrido
print("\n🎯 TEST RACCOMANDAZIONI IBRIDE:")
test_user = 42
test_movie = 1 # The Matrix
movie_title = movies_df[movies_df['movie_id'] == test_movie]['title'].values[0]
print(f"\n👤 Utente {test_user} ha visto '{movie_title}' (ID: {test_movie})")
print("🎬 Raccomandazioni ibride:")
hybrid_recs = hybrid_recommendations(test_user, test_movie, n_recommendations=8, alpha=0.6)
for i, (rec_id, score) in enumerate(hybrid_recs, 1):
rec_title = movies_df[movies_df['movie_id'] == rec_id]['title'].values
rec_title = rec_title[0] if len(rec_title) > 0 else f"Movie {rec_id}"
print(f" {i}. {rec_title} (score: {score:.3f})")
# 3.7 VALUTAZIONE COMPARATIVA MODELLI
print("\n" + "=" * 50)
print("📈 VALUTAZIONE COMPARATIVA MODELLI")
print("=" * 50)
# Prepara risultati comparativi
models_comparison = {
'Model': ['Random Forest', 'SVD', 'Hybrid (Sample)'],
'RMSE': [rf_rmse, svd_rmse, 'N/A'],
'MAE': [rf_mae, svd_mae, 'N/A'],
'R²': [rf_r2, 'N/A', 'N/A'],
'Type': ['Supervised ML', 'Matrix Factorization', 'Hybrid CF + CB'],
'Strengths': ['Utilizza tutte le features', 'Scopre pattern nascosti', 'Combina multiple approcci']
}
comparison_df = pd.DataFrame(models_comparison)
print("\n📊 COMPARAZIONE MODELLI:")
print(comparison_df.to_string(index=False))
# Visualizzazione comparativa
fig, axes = plt.subplots(1, 2, figsize=(15, 6))
# Confronto errori
models = ['Random Forest', 'SVD']
rmse_values = [rf_rmse, svd_rmse]
mae_values = [rf_mae, svd_mae]
x = np.arange(len(models))
width = 0.35
axes[0].bar(x - width/2, rmse_values, width, label='RMSE', color='lightcoral', alpha=0.8)
axes[0].bar(x + width/2, mae_values, width, label='MAE', color='skyblue', alpha=0.8)
axes[0].set_xlabel('Modello')
axes[0].set_ylabel('Error Value')
axes[0].set_title('Confronto Errori: RMSE vs MAE')
axes[0].set_xticks(x)
axes[0].set_xticklabels(models)
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# Feature importance top 10
top_features = rf_feature_importance.head(10)
axes[1].barh(top_features['feature'], top_features['importance'], color='mediumseagreen')
axes[1].set_xlabel('Importance')
axes[1].set_title('Top 10 Feature Importance (Random Forest)')
axes[1].invert_yaxis()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 3.8 SALVATAGGIO MODELLI
print("\n💾 SALVATAGGIO MODELLI E COMPONENTI...")
# Salva Random Forest
joblib.dump(rf_model, 'models/random_forest_model.pkl')
print("✅ Random Forest salvato")
# Salva SVD components
svd_components = {
'user_factors': user_factors,
'item_factors': item_factors,
'explained_variance': svd.explained_variance_ratio_
}
joblib.dump(svd_components, 'models/svd_components.pkl')
print("✅ Componenti SVD salvati")
# Salva KNN model
joblib.dump(knn_model, 'models/knn_model.pkl')
print("✅ Modello KNN salvato")
# Salva TF-IDF vectorizer
joblib.dump(tfidf, 'models/tfidf_vectorizer.pkl')
print("✅ TF-IDF vectorizer salvato")
# Salva content similarity matrix
joblib.dump(content_similarity, 'models/content_similarity.pkl')
print("✅ Matrice similarità contenuto salvata")
# Salva preprocessing objects
preprocessing_objects = {
'scaler': scaler,
'label_encoders': label_encoders
}
joblib.dump(preprocessing_objects, 'models/preprocessing_objects.pkl')
print("✅ Oggetti preprocessing salvati")
print("\n" + "=" * 70)
print("✅ PARTE 3 COMPLETATA: MODELLI ML CLASSICI IMPLEMENTATI")
print("=" * 70)
Parte 4: Reti Neurali per Raccomandazioni
Neural Collaborative Filtering e Reti Avanzate
# ================================================
# 🧠 PARTE 4: DEEP LEARNING PER RACCOMANDAZIONI
# ================================================
print("\n" + "=" * 70)
print("⚡ NEURAL COLLABORATIVE FILTERING E RETI AVANZATE")
print("=" * 70)
print("\n🔮 Implementazione state-of-the-art per raccomandazioni con Deep Learning")
# 4.1 PREPARAZIONE DATI PER NEURAL NETWORKS
print("\n🔧 PREPARAZIONE DATI PER RETI NEURALI...")
# Carica dati rating
ratings_sample = ratings_df[['user_id', 'movie_id', 'rating']].copy()
# Normalizza rating tra 0 e 1 (per attivazione sigmoid)
ratings_sample['rating_norm'] = (ratings_sample['rating'] - 0.5) / 4.5
# Encoding user e movie ID per embedding layers
n_users = ratings_sample['user_id'].nunique()
n_movies = ratings_sample['movie_id'].nunique()
print(f" • Utenti unici: {n_users:,}")
print(f" • Film unici: {n_movies:,}")
print(f" • Rating totali: {len(ratings_sample):,}")
# Mapping user e movie ID a indici sequenziali
user_to_index = {user_id: idx for idx, user_id in enumerate(ratings_sample['user_id'].unique())}
movie_to_index = {movie_id: idx for idx, movie_id in enumerate(ratings_sample['movie_id'].unique())}
ratings_sample['user_idx'] = ratings_sample['user_id'].map(user_to_index)
ratings_sample['movie_idx'] = ratings_sample['movie_id'].map(movie_to_index)
# Split train/test
train_data, test_data = train_test_split(
ratings_sample[['user_idx', 'movie_idx', 'rating_norm']].values,
test_size=0.2,
random_state=42
)
print(f"\n🎯 SPLIT DATASET NEURAL NETWORK:")
print(f" • Training samples: {len(train_data):,}")
print(f" • Test samples: {len(test_data):,}")
# 4.2 MODEL 1: NEURAL COLLABORATIVE FILTERING (NCF)
print("\n" + "=" * 50)
print("🧠 MODELLO 1: NEURAL COLLABORATIVE FILTERING")
print("=" * 50)
print("\n🏗️ Costruzione architettura NCF...")
def create_ncf_model(n_users, n_movies, embedding_dim=50):
"""
Neural Collaborative Filtering Model
Combina user e movie embeddings con MLP
"""
# Input layers
user_input = Input(shape=(1,), name='user_input')
movie_input = Input(shape=(1,), name='movie_input')
# Embedding layers
user_embedding = Embedding(
input_dim=n_users,
output_dim=embedding_dim,
name='user_embedding'
)(user_input)
movie_embedding = Embedding(
input_dim=n_movies,
output_dim=embedding_dim,
name='movie_embedding'
)(movie_input)
# Flatten embeddings
user_vec = Flatten()(user_embedding)
movie_vec = Flatten()(movie_embedding)
# Concatenate embeddings
concatenated = Concatenate()([user_vec, movie_vec])
# Deep layers
dense1 = Dense(256, activation='relu')(concatenated)
dropout1 = Dropout(0.3)(dense1)
dense2 = Dense(128, activation='relu')(dropout1)
dropout2 = Dropout(0.2)(dense2)
dense3 = Dense(64, activation='relu')(dropout2)
# Output layer (rating prediction 0-1)
output = Dense(1, activation='sigmoid', name='rating_output')(dense3)
# Create model
model = Model(inputs=[user_input, movie_input], outputs=output)
return model
# Crea e compila modello
ncf_model = create_ncf_model(n_users, n_movies, embedding_dim=64)
ncf_model.compile(
optimizer=optimizers.Adam(learning_rate=0.001),
loss=losses.MeanSquaredError(),
metrics=[metrics.RootMeanSquaredError(), metrics.MeanAbsoluteError()]
)
print("✅ Modello NCF creato!")
ncf_model.summary()
# Callbacks
ncf_callbacks = [
EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True),
ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=3, min_lr=1e-6)
]
# Addestramento
print("\n🚀 Addestramento Neural Collaborative Filtering...")
ncf_history = ncf_model.fit(
x=[train_data[:, 0], train_data[:, 1]],
y=train_data[:, 2],
batch_size=512,
epochs=30,
validation_split=0.1,
callbacks=ncf_callbacks,
verbose=1
)
# Valutazione
print("\n📊 Valutazione modello NCF...")
ncf_test_loss = ncf_model.evaluate(
x=[test_data[:, 0], test_data[:, 1]],
y=test_data[:, 2],
verbose=0
)
# Converti RMSE back to original scale (0.5-5.0)
ncf_rmse = ncf_test_loss[1] * 4.5 # Denormalize
ncf_mae = ncf_test_loss[2] * 4.5
print(f"✅ Performance NCF:")
print(f" • RMSE (denormalized): {ncf_rmse:.4f}")
print(f" • MAE (denormalized): {ncf_mae:.4f}")
# 4.3 MODEL 2: DEEP AUTORECODER PER DIMENSIONALITY REDUCTION
print("\n" + "=" * 50)
print("🌀 MODELLO 2: DEEP AUTOENCODER PER USER EMBEDDINGS")
print("=" * 50)
print("\n🔍 Costruzione autoencoder per apprendere rappresentazioni utente dense...")
# Prepara user-item matrix per autoencoder
user_item_dense = user_item_matrix.values # Dense matrix
# Normalizza
user_item_scaled = StandardScaler().fit_transform(user_item_dense)
def create_autoencoder(input_dim, encoding_dim=128):
"""Autoencoder per compressione features utente"""
# Encoder
input_layer = Input(shape=(input_dim,))
encoded = Dense(512, activation='relu')(input_layer)
encoded = Dropout(0.3)(encoded)
encoded = Dense(256, activation='relu')(encoded)
encoded = Dropout(0.2)(encoded)
encoded = Dense(encoding_dim, activation='relu')(encoded)
# Decoder
decoded = Dense(256, activation='relu')(encoded)
decoded = Dropout(0.2)(decoded)
decoded = Dense(512, activation='relu')(decoded)
decoded = Dropout(0.3)(decoded)
decoded = Dense(input_dim, activation='linear')(decoded)
# Autoencoder model
autoencoder = Model(input_layer, decoded)
# Encoder model (per estrazione features)
encoder = Model(input_layer, encoded)
return autoencoder, encoder
# Crea autoencoder
input_dim = user_item_scaled.shape[1]
autoencoder, encoder = create_autoencoder(input_dim, encoding_dim=128)
autoencoder.compile(
optimizer='adam',
loss='mse'
)
print(f"✅ Autoencoder creato per {input_dim} features → 128 encoding dim")
# Addestramento su subset (per performance)
train_size = min(5000, user_item_scaled.shape[0])
train_data_ae = user_item_scaled[:train_size]
val_data_ae = user_item_scaled[train_size:min(train_size+1000, user_item_scaled.shape[0])]
print(f"\n🚀 Addestramento autoencoder su {train_size} utenti...")
ae_history = autoencoder.fit(
train_data_ae, train_data_ae,
epochs=20,
batch_size=256,
validation_data=(val_data_ae, val_data_ae),
verbose=1
)
# Estrai user embeddings
print("\n🔎 Estrazione user embeddings dall'encoder...")
user_embeddings = encoder.predict(user_item_scaled, verbose=0)
print(f"✅ User embeddings estratti: {user_embeddings.shape}")
print(f" • Utenti: {user_embeddings.shape[0]}")
print(f" • Dimensioni embedding: {user_embeddings.shape[1]}")
# 4.4 MODEL 3: TWO-TOWER NEURAL NETWORK
print("\n" + "=" * 50)
print("🏗️ MODELLO 3: TWO-TOWER NEURAL NETWORK")
print("=" * 50)
print("\n🧱 Costruzione two-tower architecture per matching utente-film...")
def create_two_tower_model(n_users, n_movies, user_features_dim, movie_features_dim):
"""
Two-tower model: una torre per utenti, una per film
Output: similarity score
"""
# User Tower
user_id_input = Input(shape=(1,), name='user_id_input')
user_features_input = Input(shape=(user_features_dim,), name='user_features_input')
user_embedding = Embedding(n_users, 64)(user_id_input)
user_embedding = Flatten()(user_embedding)
# Combina embedding con features
user_combined = Concatenate()([user_embedding, user_features_input])
user_dense1 = Dense(128, activation='relu')(user_combined)
user_dense1 = Dropout(0.3)(user_dense1)
user_dense2 = Dense(64, activation='relu')(user_dense1)
user_tower_output = Dense(32, activation='relu', name='user_tower_output')(user_dense2)
# Movie Tower
movie_id_input = Input(shape=(1,), name='movie_id_input')
movie_features_input = Input(shape=(movie_features_dim,), name='movie_features_input')
movie_embedding = Embedding(n_movies, 64)(movie_id_input)
movie_embedding = Flatten()(movie_embedding)
# Combina embedding con features
movie_combined = Concatenate()([movie_embedding, movie_features_input])
movie_dense1 = Dense(128, activation='relu')(movie_combined)
movie_dense1 = Dropout(0.3)(movie_dense1)
movie_dense2 = Dense(64, activation='relu')(movie_dense1)
movie_tower_output = Dense(32, activation='relu', name='movie_tower_output')(movie_dense2)
# Dot product similarity
dot_product = Dot(axes=1, normalize=True)([user_tower_output, movie_tower_output])
# Output (similarity score 0-1)
output = Dense(1, activation='sigmoid', name='similarity_output')(dot_product)
# Create model
model = Model(
inputs=[user_id_input, user_features_input, movie_id_input, movie_features_input],
outputs=output
)
return model
# Prepara features per two-tower
print("\n📊 Preparazione features per two-tower model...")
# Seleziona features numeriche rilevanti
user_numeric_features = user_features.select_dtypes(include=[np.number]).drop(
columns=['user_id', 'avg_rating'], errors='ignore'
).columns.tolist()
movie_numeric_features = movie_features.select_dtypes(include=[np.number]).drop(
columns=['movie_id', 'avg_rating', 'imdb_rating'], errors='ignore'
).columns.tolist()
print(f" • User features: {len(user_numeric_features)}")
print(f" • Movie features: {len(movie_numeric_features)}")
# Crea dataset per two-tower
two_tower_data = ratings_sample[['user_idx', 'movie_idx', 'rating_norm']].copy()
# Aggiungi features
two_tower_data = pd.merge(
two_tower_data,
user_features[['user_id'] + user_numeric_features],
left_on='user_id',
right_on='user_id',
how='left'
)
two_tower_data = pd.merge(
two_tower_data,
movie_features[['movie_id'] + movie_numeric_features],
left_on='movie_id',
right_on='movie_id',
how='left'
)
# Fill NaN
two_tower_data = two_tower_data.fillna(0)
# Split features
X_user_id = two_tower_data['user_idx'].values
X_user_features = two_tower_data[user_numeric_features].values
X_movie_id = two_tower_data['movie_idx'].values
X_movie_features = two_tower_data[movie_numeric_features].values
y = two_tower_data['rating_norm'].values
# Split train/test
X_train_user_id, X_test_user_id, X_train_user_feat, X_test_user_feat, \
X_train_movie_id, X_test_movie_id, X_train_movie_feat, X_test_movie_feat, \
y_train_tt, y_test_tt = train_test_split(
X_user_id, X_user_features, X_movie_id, X_movie_features, y,
test_size=0.2, random_state=42
)
# Crea e addestra two-tower model
two_tower_model = create_two_tower_model(
n_users=n_users,
n_movies=n_movies,
user_features_dim=len(user_numeric_features),
movie_features_dim=len(movie_numeric_features)
)
two_tower_model.compile(
optimizer='adam',
loss='mse',
metrics=['mae']
)
print(f"\n🚀 Addestramento two-tower model...")
tt_history = two_tower_model.fit(
x=[X_train_user_id, X_train_user_feat, X_train_movie_id, X_train_movie_feat],
y=y_train_tt,
batch_size=512,
epochs=20,
validation_split=0.1,
verbose=1
)
# Valutazione
tt_test_loss = two_tower_model.evaluate(
x=[X_test_user_id, X_test_user_feat, X_test_movie_id, X_test_movie_feat],
y=y_test_tt,
verbose=0
)
tt_rmse = np.sqrt(tt_test_loss[0]) * 4.5
tt_mae = tt_test_loss[1] * 4.5
print(f"\n✅ Performance Two-Tower Model:")
print(f" • RMSE (denormalized): {tt_rmse:.4f}")
print(f" • MAE (denormalized): {tt_mae:.4f}")
# 4.5 MODEL 4: ATTENTION-BASED RECOMMENDER
print("\n" + "=" * 50)
print("🎯 MODELLO 4: ATTENTION-BASED RECOMMENDER")
print("=" * 50)
print("\n🔍 Implementazione meccanismo di attention per ponderare features importanti...")
class AttentionLayer(layers.Layer):
"""Custom attention layer per ponderare features"""
def __init__(self, **kwargs):
super(AttentionLayer, self).__init__(**kwargs)
def build(self, input_shape):
self.W = self.add_weight(name="attention_weight",
shape=(input_shape[-1], 1),
initializer="random_normal",
trainable=True)
self.b = self.add_weight(name="attention_bias",
shape=(1,),
initializer="zeros",
trainable=True)
super(AttentionLayer, self).build(input_shape)
def call(self, x):
# Calcola attention scores
e = tf.keras.activations.tanh(tf.tensordot(x, self.W, axes=1) + self.b)
# Softmax per pesi
alpha = tf.keras.activations.softmax(e, axis=1)
# Applica attention
context = x * alpha
return context
def create_attention_model(n_users, n_movies, feature_dim):
"""Modello con attention mechanism"""
# Inputs
user_input = Input(shape=(1,))
movie_input = Input(shape=(1,))
features_input = Input(shape=(feature_dim,))
# Embeddings
user_embedding = Embedding(n_users, 32)(user_input)
movie_embedding = Embedding(n_movies, 32)(movie_input)
user_vec = Flatten()(user_embedding)
movie_vec = Flatten()(movie_embedding)
# Combina tutti gli input
combined = Concatenate()([user_vec, movie_vec, features_input])
# Attention layer
attention = AttentionLayer()(combined)
# Deep layers
dense1 = Dense(128, activation='relu')(attention)
dropout1 = Dropout(0.3)(dense1)
dense2 = Dense(64, activation='relu')(dropout1)
dropout2 = Dropout(0.2)(dense2)
dense3 = Dense(32, activation='relu')(dropout2)
# Output
output = Dense(1, activation='sigmoid')(dense3)
return Model(inputs=[user_input, movie_input, features_input], outputs=output)
# Prepara features combinate per attention model
print("\n📊 Preparazione features per attention model...")
# Combina user e movie features
attention_features = pd.concat([
two_tower_data[user_numeric_features].reset_index(drop=True),
two_tower_data[movie_numeric_features].reset_index(drop=True)
], axis=1)
feature_dim = attention_features.shape[1]
# Crea e addestra attention model
attention_model = create_attention_model(n_users, n_movies, feature_dim)
attention_model.compile(
optimizer='adam',
loss='mse',
metrics=['mae']
)
# Split per attention model
X_train_att, X_test_att, y_train_att, y_test_att = train_test_split(
attention_features.values, y, test_size=0.2, random_state=42
)
# Aggiungi user e movie indices
X_train_user_idx = two_tower_data.iloc[y_train_att.index]['user_idx'].values
X_test_user_idx = two_tower_data.iloc[y_test_att.index]['user_idx'].values
X_train_movie_idx = two_tower_data.iloc[y_train_att.index]['movie_idx'].values
X_test_movie_idx = two_tower_data.iloc[y_test_att.index]['movie_idx'].values
print(f"\n🚀 Addestramento attention model...")
att_history = attention_model.fit(
x=[X_train_user_idx, X_train_movie_idx, X_train_att],
y=y_train_att,
batch_size=512,
epochs=15,
validation_split=0.1,
verbose=1
)
# Valutazione
att_test_loss = attention_model.evaluate(
x=[X_test_user_idx, X_test_movie_idx, X_test_att],
y=y_test_att,
verbose=0
)
att_rmse = np.sqrt(att_test_loss[0]) * 4.5
att_mae = att_test_loss[1] * 4.5
print(f"\n✅ Performance Attention Model:")
print(f" • RMSE (denormalized): {att_rmse:.4f}")
print(f" • MAE (denormalized): {att_mae:.4f}")
# 4.6 ENSEMBLE DI TUTTI I MODELLI NEURALI
print("\n" + "=" * 50)
print("🤝 ENSEMBLE NEURAL NETWORKS")
print("=" * 50)
print("\n🔗 Combinazione predizioni di tutti i modelli neurali...")
def neural_ensemble_predict(user_id, movie_id):
"""
Ensemble di predizioni da tutti i modelli neurali
"""
predictions = []
# 1. NCF prediction
user_idx = user_to_index.get(user_id)
movie_idx = movie_to_index.get(movie_id)
if user_idx is not None and movie_idx is not None:
# NCF
ncf_pred = ncf_model.predict(
[np.array([user_idx]), np.array([movie_idx])],
verbose=0
)[0][0] * 4.5 + 0.5 # Denormalize
predictions.append(('NCF', ncf_pred))
# Two-Tower (se features disponibili)
try:
user_feat = user_features[user_features['user_id'] == user_id][user_numeric_features].values[0]
movie_feat = movie_features[movie_features['movie_id'] == movie_id][movie_numeric_features].values[0]
tt_pred = two_tower_model.predict(
[
np.array([user_idx]),
user_feat.reshape(1, -1),
np.array([movie_idx]),
movie_feat.reshape(1, -1)
],
verbose=0
)[0][0] * 4.5 + 0.5
predictions.append(('Two-Tower', tt_pred))
except:
pass
# Attention (se features disponibili)
try:
combined_feat = np.concatenate([user_feat, movie_feat]).reshape(1, -1)
att_pred = attention_model.predict(
[
np.array([user_idx]),
np.array([movie_idx]),
combined_feat
],
verbose=0
)[0][0] * 4.5 + 0.5
predictions.append(('Attention', att_pred))
except:
pass
# Se abbiamo predizioni, calcola media pesata
if predictions:
# Pesiamo più i modelli migliori
weights = {'NCF': 0.4, 'Two-Tower': 0.35, 'Attention': 0.25}
weighted_sum = 0
total_weight = 0
for model_name, pred in predictions:
weight = weights.get(model_name, 0.3)
weighted_sum += pred * weight
total_weight += weight
ensemble_pred = weighted_sum / total_weight
return ensemble_pred, predictions
else:
return 2.5, [] # Default rating
# Test ensemble
print("\n🎯 TEST ENSEMBLE NEURAL NETWORKS:")
test_cases = [
(42, 1), # User 42, The Matrix
(42, 100), # User 42, altro film
(100, 1), # User 100, The Matrix
]
for user_id, movie_id in test_cases:
movie_title = movies_df[movies_df['movie_id'] == movie_id]['title'].values
title = movie_title[0] if len(movie_title) > 0 else f"Movie {movie_id}"
ensemble_pred, individual_preds = neural_ensemble_predict(user_id, movie_id)
print(f"\n👤 Utente {user_id}, 🎬 '{title}' (ID: {movie_id}):")
print(f" • Ensemble prediction: {ensemble_pred:.2f}/5.0")
for model_name, pred in individual_preds:
print(f" • {model_name}: {pred:.2f}/5.0")
# 4.7 VISUALIZZAZIONE EMBEDDINGS E PERFORMANCE
print("\n" + "=" * 50)
print("📊 VISUALIZZAZIONE RISULTATI NEURAL NETWORKS")
print("=" * 50)
fig, axes = plt.subplots(2, 2, figsize=(15, 12))
# Training history NCF
axes[0, 0].plot(ncf_history.history['loss'], label='Training Loss', color='blue')
axes[0, 0].plot(ncf_history.history['val_loss'], label='Validation Loss', color='orange')
axes[0, 0].set_xlabel('Epoch')
axes[0, 0].set_ylabel('Loss')
axes[0, 0].set_title('Neural Collaborative Filtering - Training History')
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3)
# Confronto performance modelli neurali
neural_models = ['NCF', 'Two-Tower', 'Attention']
neural_rmse = [ncf_rmse, tt_rmse, att_rmse]
neural_mae = [ncf_mae, tt_mae, att_mae]
x = np.arange(len(neural_models))
width = 0.35
axes[0, 1].bar(x - width/2, neural_rmse, width, label='RMSE', color='lightcoral', alpha=0.8)
axes[0, 1].bar(x + width/2, neural_mae, width, label='MAE', color='skyblue', alpha=0.8)
axes[0, 1].set_xlabel('Modello')
axes[0, 1].set_ylabel('Error Value')
axes[0, 1].set_title('Confronto Performance Modelli Neurali')
axes[0, 1].set_xticks(x)
axes[0, 1].set_xticklabels(neural_models)
axes[0, 1].legend()
axes[0, 1].grid(True, alpha=0.3)
# Autoencoder reconstruction error
axes[1, 0].plot(ae_history.history['loss'], label='Training Loss', color='green')
axes[1, 0].plot(ae_history.history['val_loss'], label='Validation Loss', color='red')
axes[1, 0].set_xlabel('Epoch')
axes[1, 0].set_ylabel('Reconstruction Error')
axes[1, 0].set_title('Autoencoder - Training History')
axes[1, 0].legend()
axes[1, 0].grid(True, alpha=0.3)
# User embeddings visualization (PCA)
from sklearn.decomposition import PCA
# PCA su user embeddings
pca = PCA(n_components=2)
user_embeddings_2d = pca.fit_transform(user_embeddings[:1000]) # Primi 1000 utenti
scatter = axes[1, 1].scatter(user_embeddings_2d[:, 0], user_embeddings_2d[:, 1],
alpha=0.6, s=10, c=user_item_dense[:1000].mean(axis=1),
cmap='viridis')
axes[1, 1].set_xlabel('PCA Component 1')
axes[1, 1].set_ylabel('PCA Component 2')
axes[1, 1].set_title('User Embeddings Visualization (PCA)')
plt.colorbar(scatter, ax=axes[1, 1], label='Average Rating')
axes[1, 1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 4.8 SALVATAGGIO MODELLI NEURALI
print("\n💾 SALVATAGGIO MODELLI NEURALI...")
# Crea directory se non esiste
import os
os.makedirs('models/neural', exist_ok=True)
# Salva NCF
ncf_model.save('models/neural/ncf_model.h5')
print("✅ NCF model salvato")
# Salva Autoencoder
encoder.save('models/neural/user_encoder.h5')
print("✅ User encoder salvato")
# Salva Two-Tower
two_tower_model.save('models/neural/two_tower_model.h5')
print("✅ Two-tower model salvato")
# Salva Attention model
attention_model.save('models/neural/attention_model.h5')
print("✅ Attention model salvato")
# Salva mapping dictionaries
mappings = {
'user_to_index': user_to_index,
'movie_to_index': movie_to_index,
'user_numeric_features': user_numeric_features,
'movie_numeric_features': movie_numeric_features
}
joblib.dump(mappings, 'models/neural/mappings.pkl')
print("✅ Mappings salvati")
print("\n" + "=" * 70)
print("✅ PARTE 4 COMPLETATA: RETI NEURALI IMPLEMENTATE")
print("=" * 70)
Parte 5: API REST e Sistema di Produzione
API REST e Deployment per Sistema di Raccomandazione
# ================================================
# 🌐 PARTE 5: API REST E SISTEMA DI PRODUZIONE
# ================================================
print("\n" + "=" * 70)
print("🚀 IMPLEMENTAZIONE API REST E DEPLOYMENT")
print("=" * 70)
print("\n🔧 Creazione API REST professionale per servire raccomandazioni in tempo reale")
# 5.1 SETUP API CON FASTAPI
print("\n🏗️ Configurazione FastAPI...")
# Import per API
from fastapi import FastAPI, HTTPException, Query, Depends
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any
import uvicorn
from datetime import datetime
# Modelli Pydantic per request/response
class RecommendationRequest(BaseModel):
"""Schema per richiesta raccomandazioni"""
user_id: int = Field(..., description="ID dell'utente")
movie_id: Optional[int] = Field(None, description="ID film di riferimento (opzionale)")
n_recommendations: int = Field(10, ge=1, le=50, description="Numero di raccomandazioni")
algorithm: str = Field("hybrid", description="Algoritmo da usare: hybrid, neural, collaborative, content")
class RecommendationItem(BaseModel):
"""Schema per singola raccomandazione"""
movie_id: int
title: str
predicted_rating: float
confidence: float
genres: List[str]
reason: str
class RecommendationResponse(BaseModel):
"""Schema per risposta API"""
user_id: int
recommendations: List[RecommendationItem]
algorithm_used: str
processing_time_ms: float
timestamp: datetime
class HealthResponse(BaseModel):
"""Schema per health check"""
status: str
models_loaded: List[str]
database_connected: bool
timestamp: datetime
# 5.2 SISTEMA DI RACCOMANDAZIONE PROFESSIONALE
print("\n🔌 Implementazione Recommendation System Engine...")
class MovieFlixRecommender:
"""Sistema di raccomandazione professionale che unisce tutti i modelli"""
def __init__(self, conn):
self.conn = conn
self.models_loaded = False
self.load_all_models()
def load_all_models(self):
"""Carica tutti i modelli pre-addestrati"""
print("📦 Caricamento modelli pre-addestrati...")
try:
# Carica modelli ML classici
self.rf_model = joblib.load('models/random_forest_model.pkl')
self.svd_components = joblib.load('models/svd_components.pkl')
self.knn_model = joblib.load('models/knn_model.pkl')
self.tfidf = joblib.load('models/tfidf_vectorizer.pkl')
self.content_similarity = joblib.load('models/content_similarity.pkl')
self.preprocessing = joblib.load('models/preprocessing_objects.pkl')
# Carica modelli neurali
self.ncf_model = tf.keras.models.load_model('models/neural/ncf_model.h5')
self.two_tower_model = tf.keras.models.load_model('models/neural/two_tower_model.h5')
self.attention_model = tf.keras.models.load_model('models/neural/attention_model.h5')
self.user_encoder = tf.keras.models.load_model('models/neural/user_encoder.h5')
self.mappings = joblib.load('models/neural/mappings.pkl')
self.models_loaded = True
print("✅ Tutti i modelli caricati con successo!")
except Exception as e:
print(f"❌ Errore caricamento modelli: {e}")
self.models_loaded = False
def get_user_history(self, user_id: int):
"""Recupera storico visioni utente"""
query = """
SELECT m.movie_id, m.title, r.rating, m.genre
FROM ratings r
JOIN movies m ON r.movie_id = m.movie_id
WHERE r.user_id = ?
ORDER BY r.timestamp DESC
LIMIT 50
"""
df = pd.read_sql_query(query, self.conn, params=(user_id,))
return df
def get_movie_details(self, movie_ids: List[int]):
"""Recupera dettagli per multiple film"""
if not movie_ids:
return pd.DataFrame()
placeholders = ','.join(['?'] * len(movie_ids))
query = f"""
SELECT movie_id, title, genre, imdb_rating, release_year
FROM movies
WHERE movie_id IN ({placeholders})
"""
df = pd.read_sql_query(query, self.conn, params=tuple(movie_ids))
return df
def collaborative_filtering_recommendations(self, user_id: int, n: int = 10):
"""Raccomandazioni basate su collaborative filtering"""
try:
# Trova utenti simili
user_idx = self.mappings['user_to_index'].get(user_id)
if user_idx is None:
return []
# Usa user embeddings per trovare utenti simili
user_embedding = self.user_encoder.predict(
np.array([user_idx]).reshape(1, -1), verbose=0
)
# Calcola similarità con tutti gli utenti
all_user_embeddings = self.user_encoder.predict(
np.arange(len(self.mappings['user_to_index'])).reshape(-1, 1),
verbose=0
)
similarities = cosine_similarity(user_embedding, all_user_embeddings)[0]
# Trova utenti più simili
similar_users_idx = np.argsort(similarities)[-20:-1] # Top 20 (escludendo se stesso)
# Trova film che utenti simili hanno apprezzato
movie_scores = {}
for sim_user_idx in similar_users_idx:
sim_user_id = list(self.mappings['user_to_index'].keys())[
list(self.mappings['user_to_index'].values()).index(sim_user_idx)
]
# Rating dell'utente simile
user_ratings_query = """
SELECT movie_id, rating
FROM ratings
WHERE user_id = ?
AND rating >= 4.0
"""
sim_ratings = pd.read_sql_query(
user_ratings_query, self.conn, params=(sim_user_id,)
)
for _, row in sim_ratings.iterrows():
movie_id = row['movie_id']
rating = row['rating']
similarity = similarities[sim_user_idx]
if movie_id not in movie_scores:
movie_scores[movie_id] = []
movie_scores[movie_id].append(rating * similarity)
# Calcola score medio per ogni film
avg_scores = {
movie_id: np.mean(scores)
for movie_id, scores in movie_scores.items()
if len(scores) >= 2 # Almeno 2 utenti simili l'hanno votato
}
# Ordina e restituisci top N
sorted_movies = sorted(avg_scores.items(), key=lambda x: x[1], reverse=True)
return [movie_id for movie_id, _ in sorted_movies[:n]]
except Exception as e:
print(f"Errore collaborative filtering: {e}")
return []
def content_based_recommendations(self, movie_id: int, n: int = 10):
"""Raccomandazioni basate su contenuto"""
try:
movie_idx = movie_id - 1
if movie_idx >= self.content_similarity.shape[0]:
return []
# Trova film simili
sim_scores = list(enumerate(self.content_similarity[movie_idx]))
sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)[1:n+1]
return [score[0] + 1 for score in sim_scores]
except:
return []
def neural_recommendations(self, user_id: int, n: int = 10):
"""Raccomandazioni usando modelli neurali"""
try:
user_idx = self.mappings['user_to_index'].get(user_id)
if user_idx is None:
return []
# Predici rating per tutti i film usando NCF
all_movie_indices = np.arange(len(self.mappings['movie_to_index']))
user_indices = np.full_like(all_movie_indices, user_idx)
predictions = self.ncf_model.predict(
[user_indices, all_movie_indices],
batch_size=1024,
verbose=0
).flatten()
# Denormalizza predictions
predictions = predictions * 4.5 + 0.5
# Ordina per rating predetto
top_indices = np.argsort(predictions)[::-1][:n*2] # Prendi più del necessario
# Converti indici a movie_id
movie_id_to_idx = {v: k for k, v in self.mappings['movie_to_index'].items()}
top_movies = [movie_id_to_idx[idx] for idx in top_indices if idx in movie_id_to_idx]
# Filtra film già visti
user_history = self.get_user_history(user_id)
watched_movies = set(user_history['movie_id'].values)
top_movies = [m for m in top_movies if m not in watched_movies]
return top_movies[:n]
except Exception as e:
print(f"Errore neural recommendations: {e}")
return []
def hybrid_recommendations(self, user_id: int, reference_movie_id: Optional[int] = None, n: int = 10):
"""Raccomandazioni ibride che combinano multiple algoritmi"""
all_recommendations = []
# 1. Raccomandazioni collaborative
collab_recs = self.collaborative_filtering_recommendations(user_id, n)
all_recommendations.extend([(movie_id, 'collaborative', 0.4) for movie_id in collab_recs])
# 2. Raccomandazioni neurali
neural_recs = self.neural_recommendations(user_id, n)
all_recommendations.extend([(movie_id, 'neural', 0.4) for movie_id in neural_recs])
# 3. Se c'è un film di riferimento, aggiungi content-based
if reference_movie_id:
content_recs = self.content_based_recommendations(reference_movie_id, n)
all_recommendations.extend([(movie_id, 'content', 0.2) for movie_id in content_recs])
# Calcola score aggregato per ogni film
movie_scores = {}
for movie_id, algo, weight in all_recommendations:
if movie_id not in movie_scores:
movie_scores[movie_id] = 0
movie_scores[movie_id] += weight
# Ordina per score e restituisci top N
sorted_movies = sorted(movie_scores.items(), key=lambda x: x[1], reverse=True)
return [movie_id for movie_id, _ in sorted_movies[:n]]
def generate_recommendations(self, user_id: int, reference_movie_id: Optional[int] = None,
n: int = 10, algorithm: str = "hybrid") -> List[Dict]:
"""Genera raccomandazioni complete con dettagli"""
start_time = time.time()
# Seleziona algoritmo
if algorithm == "collaborative":
movie_ids = self.collaborative_filtering_recommendations(user_id, n)
elif algorithm == "neural":
movie_ids = self.neural_recommendations(user_id, n)
elif algorithm == "content" and reference_movie_id:
movie_ids = self.content_based_recommendations(reference_movie_id, n)
else: # hybrid default
movie_ids = self.hybrid_recommendations(user_id, reference_movie_id, n)
# Recupera dettagli film
if not movie_ids:
# Fallback: film popolari
popular_query = """
SELECT movie_id FROM movies
ORDER BY imdb_rating DESC
LIMIT ?
"""
movie_ids = pd.read_sql_query(popular_query, self.conn, params=(n,))['movie_id'].tolist()
movie_details = self.get_movie_details(movie_ids)
# Crea risposta strutturata
recommendations = []
for _, movie in movie_details.iterrows():
# Calcola confidence score basato su algoritmo
confidence = 0.7 # Default
if algorithm == "hybrid":
confidence = 0.85
elif algorithm == "neural":
confidence = 0.8
recommendation = {
'movie_id': int(movie['movie_id']),
'title': movie['title'],
'predicted_rating': round(movie['imdb_rating'], 1) if pd.notna(movie['imdb_rating']) else 3.5,
'confidence': round(confidence, 2),
'genres': movie['genre'].split('|') if pd.notna(movie['genre']) else [],
'reason': f"Basato sul tuo profilo e {algorithm} filtering"
}
recommendations.append(recommendation)
processing_time = (time.time() - start_time) * 1000 # Converti a ms
return recommendations, processing_time
# 5.3 IMPLEMENTAZIONE API
print("\n🌐 Creazione API FastAPI...")
# Inizializza FastAPI app
app = FastAPI(
title="MovieFlix Recommendation API",
description="API professionale per sistema di raccomandazione film",
version="1.0.0",
docs_url="/docs",
redoc_url="/redoc"
)
# Inizializza recommendation engine
recommender = None
@app.on_event("startup")
async def startup_event():
"""Inizializza recommendation engine all'avvio"""
global recommender
print("🚀 Inizializzazione Recommendation Engine...")
recommender = MovieFlixRecommender(conn)
print("✅ Recommendation Engine pronto!")
@app.get("/", tags=["Root"])
async def root():
"""Endpoint root"""
return {
"message": "MovieFlix Recommendation API",
"version": "1.0.0",
"endpoints": {
"/docs": "API Documentation",
"/health": "Health Check",
"/recommend": "Get Recommendations",
"/user/{user_id}/history": "Get User History"
}
}
@app.get("/health", response_model=HealthResponse, tags=["Monitoring"])
async def health_check():
"""Health check endpoint"""
models_loaded = [
"Random Forest", "SVD", "KNN", "NCF",
"Two-Tower", "Attention", "Autoencoder"
] if recommender and recommender.models_loaded else []
return HealthResponse(
status="healthy" if recommender and recommender.models_loaded else "degraded",
models_loaded=models_loaded,
database_connected=conn is not None,
timestamp=datetime.now()
)
@app.get("/user/{user_id}/history", tags=["User"])
async def get_user_history(user_id: int):
"""Recupera storico visioni utente"""
if not recommender:
raise HTTPException(status_code=503, detail="Service unavailable")
history = recommender.get_user_history(user_id)
if history.empty:
raise HTTPException(status_code=404, detail="User not found")
return {
"user_id": user_id,
"total_watched": len(history),
"average_rating": round(history['rating'].mean(), 2),
"history": history.to_dict(orient='records')
}
@app.post("/recommend", response_model=RecommendationResponse, tags=["Recommendations"])
async def get_recommendations(request: RecommendationRequest):
"""Endpoint principale per raccomandazioni"""
if not recommender:
raise HTTPException(status_code=503, detail="Service unavailable")
start_time = time.time()
# Genera raccomandazioni
recommendations, processing_time = recommender.generate_recommendations(
user_id=request.user_id,
reference_movie_id=request.movie_id,
n=request.n_recommendations,
algorithm=request.algorithm
)
# Converti in formato Pydantic
recommendation_items = []
for rec in recommendations:
item = RecommendationItem(
movie_id=rec['movie_id'],
title=rec['title'],
predicted_rating=rec['predicted_rating'],
confidence=rec['confidence'],
genres=rec['genres'],
reason=rec['reason']
)
recommendation_items.append(item)
total_time = (time.time() - start_time) * 1000
return RecommendationResponse(
user_id=request.user_id,
recommendations=recommendation_items,
algorithm_used=request.algorithm,
processing_time_ms=round(total_time, 2),
timestamp=datetime.now()
)
@app.get("/movie/{movie_id}/similar", tags=["Movies"])
async def get_similar_movies(
movie_id: int,
n: int = Query(10, ge=1, le=50)
):
"""Trova film simili a un film specifico"""
if not recommender:
raise HTTPException(status_code=503, detail="Service unavailable")
similar_movie_ids = recommender.content_based_recommendations(movie_id, n)
similar_movies = recommender.get_movie_details(similar_movie_ids)
if similar_movies.empty:
raise HTTPException(status_code=404, detail="Movie not found")
return {
"movie_id": movie_id,
"similar_movies": similar_movies.to_dict(orient='records')
}
# 5.4 DOCKERFILE E DEPLOYMENT
print("\n🐳 Creazione Dockerfile per deployment...")
dockerfile_content = """
# Dockerfile per MovieFlix Recommendation API
FROM python:3.9-slim
WORKDIR /app
# Installa dipendenze sistema
RUN apt-get update && apt-get install -y \
gcc \
g++ \
&& rm -rf /var/lib/apt/lists/*
# Copia requirements
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copia codice applicazione
COPY . .
# Copia modelli pre-addestrati
COPY models/ ./models/
# Crea directory per logs
RUN mkdir -p /app/logs
# Esponi porta
EXPOSE 8000
# Comando di avvio
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
"""
print("📝 Dockerfile creato:")
print(dockerfile_content)
# 5.5 REQUIREMENTS.TXT
print("\n📋 Creazione requirements.txt...")
requirements_content = """
# Core
fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.5.0
# Data Science
numpy==1.24.3
pandas==2.1.3
scikit-learn==1.3.2
scipy==1.11.4
# Deep Learning
tensorflow==2.15.0
keras==2.15.0
# NLP
nltk==3.8.1
# Database
sqlalchemy==2.0.23
# Utilità
python-multipart==0.0.6
joblib==1.3.2
matplotlib==3.8.2
seaborn==0.13.0
plotly==5.18.0
wordcloud==1.9.3
"""
print(requirements_content)
# 5.6 TEST API LOCALE
print("\n🧪 Test API locale...")
# Funzione per testare API localmente
def test_api_locally():
"""Test delle funzionalità API"""
print("🔧 Test delle funzionalità recommendation engine...")
# Crea engine
test_engine = MovieFlixRecommender(conn)
if not test_engine.models_loaded:
print("❌ Modelli non caricati, skipping test")
return
# Test 1: User history
print("\n📚 Test 1: User History")
user_history = test_engine.get_user_history(42)
print(f" • Utente 42 ha visto {len(user_history)} film")
if len(user_history) > 0:
print(f" • Ultimo film: {user_history.iloc[0]['title']} ({user_history.iloc[0]['rating']}/5)")
# Test 2: Collaborative Filtering
print("\n👥 Test 2: Collaborative Filtering")
collab_recs = test_engine.collaborative_filtering_recommendations(42, 5)
print(f" • Raccomandazioni collaborative: {len(collab_recs)} film")
if collab_recs:
movie_details = test_engine.get_movie_details(collab_recs[:3])
for _, movie in movie_details.iterrows():
print(f" - {movie['title']}")
# Test 3: Neural Recommendations
print("\n🧠 Test 3: Neural Recommendations")
neural_recs = test_engine.neural_recommendations(42, 5)
print(f" • Raccomandazioni neurali: {len(neural_recs)} film")
# Test 4: Hybrid Recommendations
print("\n🤝 Test 4: Hybrid Recommendations")
recs, proc_time = test_engine.generate_recommendations(
user_id=42,
n=5,
algorithm="hybrid"
)
print(f" • Raccomandazioni ibride ({len(recs)} film, {proc_time:.0f}ms):")
for rec in recs[:3]:
print(f" - {rec['title']} (confidence: {rec['confidence']})")
print("\n✅ Test completati con successo!")
# Esegui test
test_api_locally()
# 5.7 DEPLOYMENT SCRIPT
print("\n🚀 Creazione script di deployment...")
deployment_script = """
#!/bin/bash
# Deployment script per MovieFlix Recommendation System
set -e
echo "🚀 Starting MovieFlix Recommendation System Deployment"
# 1. Build Docker image
echo "📦 Building Docker image..."
docker build -t movieflix-recommender:latest .
# 2. Run container
echo "🐳 Starting container..."
docker run -d \
--name movieflix-recommender \
-p 8000:8000 \
-v ./logs:/app/logs \
-e "ENVIRONMENT=production" \
movieflix-recommender:latest
# 3. Health check
echo "🏥 Performing health check..."
sleep 10 # Wait for service to start
curl -f http://localhost:8000/health || {
echo "❌ Health check failed!"
docker logs movieflix-recommender
exit 1
}
echo "✅ Deployment completed successfully!"
echo "📊 API available at: http://localhost:8000"
echo "📚 Documentation at: http://localhost:8000/docs"
"""
print(deployment_script)
# 5.8 MONITORING E LOGGING
print("\n📊 Configurazione monitoring e logging...")
monitoring_config = """
# monitoring_config.py
import logging
from datetime import datetime
import json
def setup_logging():
"""Configura logging professionale"""
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(f'logs/movieflix_{datetime.now().strftime("%Y%m")}.log'),
logging.StreamHandler()
]
)
return logging.getLogger(__name__)
class RecommendationMonitor:
"""Monitor per tracciare performance raccomandazioni"""
def __init__(self):
self.metrics = {
'requests_total': 0,
'requests_by_algorithm': {},
'avg_processing_time': 0,
'errors_total': 0
}
self.logger = setup_logging()
def log_request(self, user_id: int, algorithm: str, processing_time: float):
"""Logga una richiesta di raccomandazione"""
self.metrics['requests_total'] += 1
if algorithm not in self.metrics['requests_by_algorithm']:
self.metrics['requests_by_algorithm'][algorithm] = 0
self.metrics['requests_by_algorithm'][algorithm] += 1
# Aggiorna tempo medio di processing
total_time = self.metrics['avg_processing_time'] * (self.metrics['requests_total'] - 1)
self.metrics['avg_processing_time'] = (total_time + processing_time) / self.metrics['requests_total']
self.logger.info(
f"Recommendation request - User: {user_id}, "
f"Algorithm: {algorithm}, "
f"Time: {processing_time:.2f}ms"
)
def log_error(self, error_type: str, details: str):
"""Logga un errore"""
self.metrics['errors_total'] += 1
self.logger.error(f"{error_type}: {details}")
def get_metrics(self):
"""Restituisce metriche corrente"""
return {
**self.metrics,
'timestamp': datetime.now().isoformat()
}
def save_metrics(self):
"""Salva metriche su file"""
filename = f"logs/metrics_{datetime.now().strftime('%Y%m%d')}.json"
with open(filename, 'w') as f:
json.dump(self.get_metrics(), f, indent=2)
# Utilizzo nel FastAPI app
monitor = RecommendationMonitor()
# In ogni endpoint, chiama monitor.log_request()
"""
print(monitoring_config)
# 5.9 CONCLUSIONE E PROSSIMI PASSI
print("\n" + "=" * 70)
print("🎉 PROGETTO COMPLETATO: SISTEMA DI RACCOMANDAZIONE AVANZATO")
print("=" * 70)
print("\n🏆 ARCHITETTURA IMPLEMENTATA CON SUCCESSO:")
print("""
┌─────────────────────────────────────────────────────────┐
│ CLIENT APPLICATIONS │
│ (Web, Mobile, TV Apps) │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ FASTAPI REST API (Port: 8000) │
│ • /recommend - Get personalized recommendations │
│ • /user/{id}/history - Get user viewing history │
│ • /health - System health check │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ RECOMMENDATION ENGINE (Python) │
│ • Neural Collaborative Filtering (NCF) │
│ • Matrix Factorization (SVD) │
│ • Content-Based Filtering (TF-IDF) │
│ • K-Nearest Neighbors │
│ • Two-Tower Neural Network │
│ • Attention-Based Model │
│ • Hybrid Ensemble Approach │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ DATABASE LAYER (PostgreSQL) │
│ • Users: 10,000+ profiles │
│ • Movies: 5,000+ with metadata │
│ • Ratings: 1,000,000+ interactions │
│ • Watch History: User viewing sessions │
└─────────────────────────────────────────────────────────┘
""")
print("\n📊 PERFORMANCE RIEPILOGO:")
print("""
Algorithm RMSE MAE Type
---------- ----- ----- -----
Random Forest {rf_rmse:.4f} {rf_mae:.4f} Supervised ML
SVD Matrix Factorization {svd_rmse:.4f} {svd_mae:.4f} Collaborative
Neural Collaborative Filtering {ncf_rmse:.4f} {ncf_mae:.4f} Deep Learning
Two-Tower Network {tt_rmse:.4f} {tt_mae:.4f} Neural Matching
Attention Model {att_rmse:.4f} {att_mae:.4f} Attention-Based
""".format(
rf_rmse=rf_rmse, rf_mae=rf_mae,
svd_rmse=svd_rmse, svd_mae=svd_mae,
ncf_rmse=ncf_rmse, ncf_mae=ncf_mae,
tt_rmse=tt_rmse, tt_mae=tt_mae,
att_rmse=att_rmse, att_mae=att_mae
))
print("\n🚀 PROSSIMI PASSI PER PRODUZIONE:")
print("""
1. 🐳 Dockerize: Containerizzare l'applicazione
2. ☁️ Cloud Deployment: Deploy su AWS/Azure/GCP
3. 📈 Monitoring: Implementare Prometheus + Grafana
4. 🔄 CI/CD: Pipeline automatizzata con GitHub Actions
5. 🚀 Scaling: Load balancing e auto-scaling
6. 🎯 A/B Testing: Testare diversi algoritmi
7. 📊 Analytics: Dashboard per analisi performance
8. 🔍 Explainability: Aggiungere spiegazioni raccomandazioni
""")
print("\n💡 COMPETENZE ACQUISITE IN QUESTO PROGETTO:")
print("""
✅ Database Design: Schema relazionale complesso per streaming
✅ SQL Avanzato: Query complesse per feature engineering
✅ Machine Learning: 5+ algoritmi di raccomandazione
✅ Deep Learning: Reti neurali per collaborative filtering
✅ NLP: Text processing per content-based filtering
✅ API Development: REST API professionale con FastAPI
✅ System Design: Architettura scalabile end-to-end
✅ MLOps: Deployment e monitoring modelli ML
✅ Ensemble Methods: Combinazione di multiple modelli
✅ Production Readiness: Docker, logging, monitoring
""")
print("\n" + "=" * 70)
print("🎓 COMPLIMENTI! HAI COMPLETATO IL PROGETTO FINALE")
print("🎬 SISTEMA DI RACCOMANDAZIONE NETFLIX-STYLE")
print("=" * 70)
print("\n🌟 SEI ORA PRONTO PER:")
print(" • Lavorare come ML Engineer in aziende tech")
print(" • Costruire sistemi di raccomandazione reali")
print(" • Gestire progetti AI end-to-end")
print(" • Lavorare con database, ML e Deep Learning")
print("\n🚀 IL TUO VIAGGIO NELL'AI È INIZIATO!")
print(" Continua ad apprendere, costruire e innovare!")
🎓 PERCORSO COMPLETATO CON SUCCESSO!
📚 Concetti Appresi
- Database Design per AI
- Feature Engineering SQL
- Collaborative Filtering
- Content-Based Filtering
- Neural Networks per ML
- API REST Development
- System Architecture
🛠️ Tecnologie Utilizzate
- PostgreSQL/SQLite
- Pandas & NumPy
- Scikit-learn
- TensorFlow & Keras
- FastAPI
- Docker
- NLTK & NLP
🎯 Competenze Professionali
- ML Engineer
- Data Engineer
- AI Developer
- Backend Developer
- System Architect
- Data Scientist
🚀 Prossime Tappe nel Tuo Percorso AI
NLP Avanzato
Reinforcement Learning
MLOps
Big Data AI
"Il sistema che hai costruito è a livello industriale. Sei pronto per il mondo reale dell'AI!"
🎉 COMPLIMENTI PER IL COMPLETAMENTO!
Hai completato il Percorso Completo Python AI per la Classe Quinta
Ora possiedi le competenze per costruire sistemi AI reali!