import pandas as pd from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity from sklearn.neighbors import NearestNeighbors from sentence_transformers import SentenceTransformer import numpy as np import streamlit as st import os from rapidfuzz import process import re def load_data(filepath): """Load and deduplicate the dataset.""" data = pd.read_csv(filepath) data = data.drop_duplicates(subset=['book_name'], keep='first').reset_index(drop=True) return data def preprocess_data(data): """Preprocess the dataset by cleaning text fields.""" data['book_name'] = data['book_name'].fillna('').str.lower() data['summaries'] = data['summaries'].fillna('').str.lower() data['categories'] = data['categories'].fillna('').str.lower() data['combined_text'] = ( data['book_name'] + ' ' + data['summaries'] * 2 + ' ' + data['categories'] ) return data def feature_engineering(data): """Generate features using TF-IDF and SentenceTransformer embeddings.""" # TF-IDF Vectorization vectorizer = TfidfVectorizer(stop_words='english') tfidf_matrix = vectorizer.fit_transform(data['combined_text']) # SentenceTransformer embeddings model = SentenceTransformer('paraphrase-MiniLM-L3-v2') embeddings_file = "semantic_embeddings.npy" if not os.path.exists(embeddings_file): data['semantic_embeddings'] = data['combined_text'].apply(lambda x: model.encode(x)) np.save(embeddings_file, np.vstack(data['semantic_embeddings'])) else: semantic_embeddings = np.load(embeddings_file) data['semantic_embeddings'] = [embedding for embedding in semantic_embeddings] # Fit NearestNeighbors model nn_model = NearestNeighbors(metric='cosine', algorithm='auto') nn_model.fit(np.vstack(data['semantic_embeddings'])) return vectorizer, tfidf_matrix, model, nn_model def calculate_similarity(input_title, data, vectorizer, tfidf_matrix, model, nn_model, n_recommendations=5, min_similarity=0.3): """Calculate similarities and generate recommendations.""" try: # Compute TF-IDF similarity input_vector = vectorizer.transform([input_title.lower()]) cosine_similarities = cosine_similarity(input_vector, tfidf_matrix).flatten() min_similarity = max(0.1, cosine_similarities.mean() - cosine_similarities.std()) # Compute semantic similarity using NearestNeighbors input_embedding = model.encode(input_title.lower()) distances, indices = nn_model.kneighbors([input_embedding], n_neighbors=len(data)) # Combine similarities recommended_books = [] seen_titles = set() for idx, dist in zip(indices[0], distances[0]): book_title = data['book_name'].iloc[idx] round_sim_value = round(1 - dist, 2) # Convert distance to similarity if book_title not in seen_titles and round_sim_value >= min_similarity and str(book_title).lower() != str(input_title).lower(): recommended_books.append((book_title, round_sim_value)) seen_titles.add(book_title) if len(recommended_books) == n_recommendations: break # If no meaningful recommendations, return fallback message if not any(score >= min_similarity for _, score in recommended_books): return [("No meaningful recommendations found.", 0.0)] return recommended_books except Exception as e: return [(f"An error occurred: {str(e)}", 0.0)] def validate_input(input_title, data, threshold=70): """Validate input title using fuzzy matching against dataset titles.""" titles = data['book_name'].tolist() closest_match, score, _ = process.extractOne(input_title, titles) if score >= threshold: return closest_match return None def deploy_ui(data, vectorizer, tfidf_matrix, model, nn_model): """Streamlit-based UI for interacting with the recommendation system.""" st.title("Content-Based Book Recommender") st.write("Enter a book title to get up to 5 similar recommendations.") book_title = st.text_input("Enter a book title") if st.button("Submit"): # Input validation if len(book_title.strip()) < 3: st.write("Input is too short. Please enter a more meaningful book title.") return closest_title = validate_input(book_title, data) if closest_title is None: st.write("No close match found. Please try a different title.") return if closest_title: st.write(f"Did you mean: {closest_title}?") book_title = closest_title # Generate recommendations recommendations = calculate_similarity(book_title, data, vectorizer, tfidf_matrix, model, nn_model, min_similarity=0.3) if "error" in recommendations[0][0]: st.write("Error occured. Please try again later") elif recommendations is None or len(recommendations) == 0 or recommendations[0][0] == "No meaningful recommendations found.": st.write("No meaningful recommendations found for the input. Please try again with a different title.") else: st.write("Recommended Books:") idx = 1 for book, score in recommendations: cleaned_book = re.sub(r"[^A-Za-z0-9]+$", "", book) st.write(f"{idx}: {cleaned_book}") idx += 1 if __name__ == "__main__": filepath = "books_summary.csv" data = load_data(filepath) data = preprocess_data(data) vectorizer, tfidf_matrix, model, nn_model = feature_engineering(data) deploy_ui(data, vectorizer, tfidf_matrix, model, nn_model)