fuzzy-logic-movies/main.py

176 lines
5.5 KiB
Python

"""
!pip install scikit-learn
!pip install pandas
!pip install fastapi
!pip install "uvicorn[standard]"
!uvicorn main:app --reload
"""
import multiprocessing
import time
from multiprocessing import Pool
import numpy as np
import pandas as pd
import pandas.core.series
from fastapi import FastAPI
from scipy.spatial.distance import cosine
from sklearn.preprocessing import MultiLabelBinarizer
from engine import fuzzy_system
app = FastAPI()
data = pd.DataFrame()
mlb = MultiLabelBinarizer()
def inference(first: pandas.core.series.Series,
second_id: str,
release_year_param='similar',
runtime_param='similar',
seasons_param='similar',
genres_param='same',
emotions_param='same',
df=None):
if df is not None:
second = df.loc[second_id]
else:
second = data.loc[second_id]
FS = fuzzy_system(release_year_param=release_year_param,
runtime_param=runtime_param,
seasons_param=seasons_param,
genres_param=genres_param,
emotions_param=emotions_param)
year_diff = int(first['release_year'] - second['release_year'])
FS.set_variable('RELEASE_YEAR', year_diff)
runtime_diff = int(first['runtime'] - second['runtime'])
FS.set_variable('RUNTIME', runtime_diff)
if not (np.isnan(first['seasons']) or np.isnan(second['seasons'])):
season_diff = int(first['seasons'] - second['seasons'])
FS.set_variable('SEASONS', season_diff)
else:
FS.set_variable('SEASONS', 0)
genre_diff = 1 - cosine(first['genres'], second['genres'])
FS.set_variable('GENRES', genre_diff)
emotion_diff = 1 - cosine(first['emotions'], second['emotions'])
FS.set_variable('EMOTIONS', emotion_diff)
return second_id, FS.inference(['RECOMMENDATION'])['RECOMMENDATION']
def process_dataframe(df,
production,
release_year_param,
runtime_param,
seasons_param,
genres_param,
emotions_param
):
scores = []
for index, row in df.iterrows():
scores.append(inference(production,
str(index),
release_year_param,
runtime_param,
seasons_param,
genres_param,
emotions_param,
df))
return scores
@app.on_event('startup')
async def startup_event():
global data
global mlb
data = pd.read_csv('processed_data.csv', index_col='id', converters={'genres': pd.eval})
all_genres = data.genres.explode().unique()
mlb.fit([all_genres])
data['genres'] = data['genres'].apply(lambda x: mlb.transform([x])[0])
data['emotions'] = data[['Happy', 'Angry', 'Surprise', 'Sad', 'Fear']].values.tolist()
@app.get('/find/{title}')
def titles(title: str):
response = []
for index, row in data.iterrows():
if title.lower() in row['title'].lower():
response.append({'id': index, 'title': row['title'], 'year': row['release_year']})
return response
@app.get('/details/{production_id}')
def details(production_id: str):
try:
production = data.loc[production_id]
except:
return {'error': f'{production_id} is not a valid id'}
genres = production['genres']
genres = mlb.inverse_transform(genres.reshape(1, -1))[0]
return {
'title': production['title'],
'type': production['type'],
'description': production['description'],
'year': int(production['release_year']),
'runtime': int(production['runtime']),
'genres': genres,
}
@app.get('/score/{first_id}/{second_id}')
def rec_score(first_id: str, second_id: str):
try:
first = data.loc[first_id]
except KeyError:
return {'error': f'{first_id} is not a valid id'}
try:
second = data.loc[second_id]
except KeyError:
return {'error': f'{second_id} is not a valid id'}
return inference(first, second_id)
@app.get('/recs/{production_id}')
async def recs(production_id: str,
release_year_param: str | None = 'similar',
runtime_param: str | None = 'similar',
seasons_param: str | None = 'similar',
genres_param: str | None = 'same',
emotions_param: str | None = 'same',
count: int | None = 5):
try:
first = data.loc[production_id]
except KeyError:
return {'error': f'{production_id} is not a valid id'}
scores = []
time_start = time.time()
cpus = multiprocessing.cpu_count()
df_list = np.array_split(data, cpus)
pool = Pool(cpus)
results = [pool.apply_async(process_dataframe,
[df,
first,
release_year_param,
runtime_param,
seasons_param,
genres_param,
emotions_param]) for df in df_list]
for r in results:
r.wait()
for r in results:
scores += r.get()
print(f'time elapsed = {time.time() - time_start}')
scores = [idx[0] for idx in sorted(scores, key=lambda x: x[1], reverse=True)[:count + 1]]
scores.remove(production_id)
return {
'id': scores
}