File size: 6,823 Bytes
9d263bd | 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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | import pandas as pd
import re
from sentence_transformers import SentenceTransformer, util
import streamlit as st
from nltk.corpus import stopwords
import nltk
import numpy as np
from datasets import load_dataset
# Download necessary NLTK data
nltk.download('stopwords')
nltk.download('punkt')
# Load the dataset from Hugging Face
try:
dataset = load_dataset("Sasivar/Analytics_Vidhya_Free_Course_Data")
df = pd.DataFrame(dataset['train']) # Assuming the data is in the 'train' split
except Exception as e:
st.error(f"Error loading dataset: {e}")
# Clean and preprocess text
stop_words = set(stopwords.words('english'))
# Abbreviation to full form mapping
abbreviation_map = {
"ml": "machine learning",
"dl": "deep learning",
"llm": "large language model",
"ai": "artificial intelligence",
"nlp": "natural language processing",
"cv": "computer vision",
"knn": "k nearest neighbors",
"regression": "regression analysis"
}
def expand_abbreviation(query):
"""Expand abbreviations to their full forms."""
query = query.lower() # Convert to lowercase for consistency
for abbreviation, full_form in abbreviation_map.items():
query = re.sub(rf"\b{abbreviation}\b", full_form, query) # Word boundary match to avoid partial matches
return query
def clean_text(text):
"""Clean and preprocess text (remove non-word characters and stopwords)."""
text = text.lower() # Convert to lowercase for consistency
text = re.sub(r'\W', ' ', text) # Remove non-word characters (punctuation, etc.)
tokens = text.split()
tokens = [word for word in tokens if word not in stop_words] # Remove stopwords
return ' '.join(tokens)
# Handle missing ratings
def handle_missing_ratings(df, strategy="average"):
"""Fill missing ratings based on the chosen strategy."""
df['Rating'] = df['Rating'].replace('Unknown', np.nan) # Replace 'Unknown' with NaN
if strategy == "average":
# Fill with average rating
average_rating = df['Rating'].astype(float).mean()
df['Rating'] = df['Rating'].fillna(average_rating)
elif strategy == "mode":
# Fill with mode rating
mode_rating = df['Rating'].mode()[0]
df['Rating'] = df['Rating'].fillna(mode_rating)
elif strategy == "random":
# Fill with random rating in the range of existing ratings
min_rating = df['Rating'].min()
max_rating = df['Rating'].max()
random_ratings = np.random.randint(min_rating, max_rating + 1, size=df['Rating'].isna().sum())
df.loc[df['Rating'].isna(), 'Rating'] = random_ratings
return df
# Add 'Course Info' column by combining key information fields
df = handle_missing_ratings(df, strategy="average") # Choose your strategy here: "average", "mode", "random"
df['Course Info'] = (
df['Course Title'] + ' ' + df['Description'].fillna('') + ' ' + df['Level'].fillna('Unknown')
).apply(lambda x: expand_abbreviation(clean_text(x))) # Clean and expand abbreviations
# Load and cache model
@st.cache_resource
def load_model():
return SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
# Cache course embeddings
@st.cache_data
def get_course_embeddings(_model):
return _model.encode(df['Course Info'].tolist(), convert_to_tensor=True)
# Load model and encode courses
model = load_model()
course_embeddings = get_course_embeddings(model)
# Define search function with improved processing
def search_courses(query, level_filter="All", rating_filter=0.0, threshold=0.5):
"""Search for the most relevant courses based on the user's query and filters."""
try:
# Expand abbreviations in the query
query = expand_abbreviation(query)
# Clean the expanded query text and compute its embedding
query = clean_text(query)
query_embedding = model.encode(query, convert_to_tensor=True)
# Calculate similarity scores using cosine similarity
similarities = util.pytorch_cos_sim(query_embedding, course_embeddings)[0]
df['Score'] = similarities.cpu().numpy()
# Apply threshold and filter by score
sorted_df = df[df['Score'] > threshold].sort_values(by='Score', ascending=False)
# Apply level filter if a specific level is selected, include 'Unknown' in the filtering process
if level_filter != "All":
sorted_df = sorted_df[(sorted_df['Level'] == level_filter) | (sorted_df['Level'] == "Unknown")]
else:
sorted_df = sorted_df[sorted_df['Level'] != "Unknown"] # Exclude 'Unknown' if 'All' is selected
# Apply rating filter
sorted_df = sorted_df[sorted_df['Rating'] >= rating_filter]
# Remove duplicates based on the course title and score
sorted_df = sorted_df.drop_duplicates(subset=['Course Title'])
# Select top results
top_courses = sorted_df.head(5)[['Course Title', 'Duration', 'Course Link', 'Description', 'Rating', 'Level']]
# Round the rating to 2 decimal places for cleaner display
top_courses['Rating'] = top_courses['Rating'].round(2)
# Prepare results for display
results = [{
"Title": row['Course Title'],
"Duration": row['Duration'],
"Link": row['Course Link'],
"Description": row['Description'],
"Rating": row['Rating'],
"Level": row['Level']
} for _, row in top_courses.iterrows()]
return results
except Exception as e:
st.error(f"Error in search_courses function: {e}")
return []
# Streamlit app interface
def run_app():
st.title("Smart Course Search")
st.write("Find the best free courses tailored to your search query!")
# Input form for user query and filters
query = st.text_input("Enter your search query:")
level_filter = st.selectbox("Select Course Level", options=["All", "Beginner", "Intermediate", "Advanced"])
rating_filter = st.slider("Minimum Rating", 0.0, 5.0, 0.0)
if query:
results = search_courses(query, level_filter, rating_filter)
# Display results with enhanced formatting
if results:
st.subheader("Search Results:")
for result in results:
st.markdown(f"### {result['Title']}")
st.write(
f"**Duration:** {result['Duration']} | **Rating:** {result['Rating']} ⭐ | **Level:** {result['Level']}")
st.markdown(f"**Description:** {result['Description']}")
st.markdown(f"[Course Link]({result['Link']})")
st.write("---")
else:
st.write("No results found.")
else:
st.write("Please enter a query to search for courses.")
# Run the Streamlit app
if __name__ == "__main__":
run_app()
|