File size: 3,557 Bytes
5a2205b
9d8c305
 
7845f63
9d8c305
7845f63
 
9d8c305
 
7845f63
9d8c305
 
7845f63
4e7c7b1
 
 
 
7845f63
9d8c305
7845f63
 
 
 
9d8c305
 
7845f63
 
9d8c305
7845f63
9d8c305
7845f63
 
9d8c305
7845f63
 
 
9d8c305
7845f63
 
9d8c305
7845f63
 
9d8c305
7845f63
9d8c305
 
7845f63
9d8c305
7845f63
9d8c305
 
 
 
 
7845f63
 
 
9d8c305
7845f63
 
 
 
 
 
 
9d8c305
7845f63
 
9d8c305
 
 
 
7845f63
 
9d8c305
 
 
 
 
 
 
 
 
 
7845f63
 
9d8c305
 
 
5a2205b
9d8c305
 
 
 
 
7845f63
9d8c305
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import streamlit as st
import pandas as pd
import joblib
import numpy as np
from sklearn.neighbors import NearestNeighbors
from sklearn.preprocessing import MinMaxScaler 
import os 

# --- Load Assets (Model and Data) ---
# Uses caching to load heavy files (model, idlist, scaler, and large DataFrame) only once.
@st.cache_resource
def load_assets():
    # File Names
    MODEL_FILENAME = 'src/book_recommendation_model.jobjob'
    IDLIST_FILENAME = 'src/knn_idlist.jobjob'
    SCALER_FILENAME = 'src/min_max_scaler.jobjob'
    DATA_FILENAME = 'src/books.csv'

    try:
        # 1. Load Saved Objects
        model = joblib.load(MODEL_FILENAME)
        idlist = joblib.load(IDLIST_FILENAME)
        scaler = joblib.load(SCALER_FILENAME)

        # 2. Load the DataFrame
        df = pd.read_csv(DATA_FILENAME, on_bad_lines='skip')
        df.columns = df.columns.str.strip() # Clean column names
        
        # 3. Check for essential column
        if 'title' not in df.columns:
            st.error("The DataFrame must contain a 'title' column.")
            return None, None, None, None 

        # Successful return of 4 objects
        return model, df, idlist, scaler
    
    except FileNotFoundError:
        st.error(f"Required files not found. Ensure '{MODEL_FILENAME}', '{IDLIST_FILENAME}', '{SCALER_FILENAME}', and '{DATA_FILENAME}' are in the same directory.")
        return None, None, None, None

# Load assets upon application start
loaded_model, df, idlist, scaler = load_assets()

# --- Recommendation Function ---
def get_recommendations(book_name, df_data, id_list_data):
    """
    Retrieves the titles of books similar to the given book name.
    """
    # Find the index of the input book
    try:
        book_idx = df_data[df_data['title'] == book_name].index[0]
    except IndexError:
        return ["Error: The specified book title was not found in the dataset."], False

    # Get neighbor indices from the pre-calculated idlist
    # Note: idlist[book_idx] contains the indices of the neighbors
    neighbor_indices = id_list_data[book_idx]

    # Retrieve the titles of the neighboring books
    # .unique() helps handle potential duplicates in the dataset
    recommended_titles = df_data.loc[neighbor_indices, 'title'].unique().tolist()
    
    # Remove the queried book itself from the list of recommendations
    if book_name in recommended_titles:
        recommended_titles.remove(book_name)
    
    # Return the top 5 (or N-1 if N neighbors were used) recommendations
    return recommended_titles[:5], True 

# --- Streamlit Interface ---

if loaded_model is not None and df is not None:
    st.title("📚 Book Recommendation System")
    st.markdown("This application uses a pre-trained K-Nearest Neighbors model to suggest similar books.")

    # Select box options
    book_options = df['title'].unique()
    
    selected_book = st.selectbox(
        "Please select a book:", 
        options=book_options,
        index=0
    )

    st.markdown("---")

    if st.button("Generate Recommendations"):
        with st.spinner('Searching for similar books...'):
            recommendations, success = get_recommendations(selected_book, df, idlist)

            if success:
                st.subheader(f"Recommendations Similar to '{selected_book}':")
                
                # Display recommendations as a numbered list
                for i, title in enumerate(recommendations, 1):
                    st.success(f"{i}. **{title}**")
            else:
                st.error(recommendations[0])