-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmovie_averager_app.py
More file actions
141 lines (117 loc) · 7.4 KB
/
movie_averager_app.py
File metadata and controls
141 lines (117 loc) · 7.4 KB
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
# import dotenv
import os
from movie_averager_app_src import logic as lg
import streamlit as st
st.set_page_config(
page_title="Movie Averager",
page_icon="🧪",
layout="wide")
if "messages" not in st.session_state:
st.session_state.messages = []
if "ai_messages" not in st.session_state:
st.session_state.ai_messages = []
# TODO: make sure chatbot's messages get preserved in its memory
# dotenv.load_dotenv()
os.environ["OPENAI_API_KEY"] = st.secrets["OPENAI_API_KEY"]
# I want a movie that's like Finding Nemo but a little more like Rush Hour # this one puts more weight on rush hour
# I want a movie that's like Finding Nemo but a little bit like Rush Hour
# I want a movie that's like Finding Nemo but a tiny bit like Rush Hour
#
# Give me a cross between Robocop and Marley & Me
# Give me the love child of American Psycho and The Matrix
# Give me the love child of Finding Nemo and Robocop
#
# What if Juno and Clueless had a baby?
# Give me a movie that's like Clueless but a tiny bit like Pearl Harbor
#
# back to the future x goonies
# zach: robocop x care bears
# Give me the next best thing to Mission Impossible and Ocean's Eleven that are not British
_, cntr, _ = st.columns([2,7,2])
with cntr:
st.title("Movie Averager")
st.subheader("Tell our chatbot in natural language what two movies you'd like to mash up, and it'll return its best recommendation out of its database of 1000+ movies!")
st.write("Some example prompts:")
st.write('"Give me the love child of Finding Nemo and Rush Hour"')
st.write(""" "I want a movie that's like Clueless but a little bit like The Truman Show" """ )
left, right = st.columns([3,5])
testbot = lg.Bot(advanced = True)
# testbot = lg.Bot(advanced = False)
with left:
st.header('Movies you can use')
st.write("You can use your browser's search function to quickly see if a specific movie is in the database. Be sure to spell the movies correctly when using the bot!")
with st.container(height = 600):
st.table(testbot.synopses)
with right:
st.header('Chat here')
bot_interface = st.container(height = 600)
for message in st.session_state.messages:
with bot_interface.chat_message(message["role"]):
st.markdown(message["content"])
if input_text := st.chat_input('What would you like to watch today?'):
with bot_interface.chat_message('user'):
st.markdown(input_text)
st.session_state.messages.append({"role": "user", "content": input_text})
st.session_state.ai_messages = testbot.messages
init_response = testbot.process_input(user_input = input_text)
try: # verify that the bot outputs a list
correct_output_type = type(eval(init_response)) == list
except:
correct_output_type = False
if correct_output_type: # if the bot successfully produces the movies dict, proceed to compute recs
print(init_response)
recs_list = testbot.average_embeddings(eval(init_response))
natural_language_recs = testbot.give_recs(recs_list)
with bot_interface.chat_message('assistant'):
st.markdown(natural_language_recs)
st.session_state.messages.append({"role": "assistant", "content": natural_language_recs})
st.session_state.ai_messages = testbot.messages
else: # if not, because the user gives off-topic input, write the bot's "error" message to screen
with bot_interface.chat_message('assistant'):
st.markdown(init_response)
st.session_state.messages.append({"role": "assistant", "content": init_response})
st.session_state.ai_messages = testbot.messages
# st.write(testbot.messages)
_, cntr, _ = st.columns([2, 7, 2])
with cntr:
st.subheader('How it works')
st.write("""
This prototype was made possible by scraping the longest synopsis we could find on IMDB for each movie,
and getting OpenAI's embeddings for each one. Embeddings are essentially a mapping of texts based on semantic
similarity (in this case they are mapped in 1500+ dimensional space). Therefore, the bot's ability to compare
movies is limited by the level of detail written in the synopses.
We wanted the bot's recommendations to resemble what a human implies when they ask for the combo of two movies,
i.e. they are probably not asking for a perfect mathematical average between the two movie synopses (which
often results in recommendations that aren't related to either movie), but rather they want to mash up the most
distinctive features of each one.
The conversational bot itself is powered with GPT-4--in this case it is relying on our small database of
knowledge rather than the encyclopedic knowledge that ChatGPT has access to. When you tell it "I want a movie
that's a like A but a little bit like B", the GPT is interpreting your wording to decide to how to weight the
movies when mashing up their key features.
""")
with st.expander("More mathematical details here...", expanded=False):
st.write("""
To find movie recommendations with embeddings, we first used a method called Principal
Component Analysis (PCA) to reduce the embeddings' 1500+ dimensions into only 10 dimensions that best summarize
the many different ways that movies can vary from one another. Though we didn't try to label these dimensions,
one could imagine that some of these dimensions roughly mimic features that humans would use to categorize
movies, such as the degree of realism, degree of action, degree of romance etc. Then, for each movie in a
user's query, we only look at the 2 features where that movie has the largest absolute value (i.e. a feature
where that particular movie is more of an outlier), find the point where those key features intersect, and
look for recommendations around that point in space. If the two movies in the user query share any "key
features", we take the average between those. If one movie is weighted more heavily than the other, we
moderate the combination of the key features in proportion to the weights. Then we return the closest handful
of movies to this point in space.
""")
st.subheader('How this prototype could improve')
st.write("""
If we were to have used the full scripts for each of these movies, the embeddings would account
for much more detail, and the recommendations would probably be much more accurate. There are other ways we
could use AI to analyze the scripts and map out the movies in more dimensions—for instance, using AI to
identify how many murders are in each movie, or how many female characters there are, and then using those as potential
dimensions for mashing up movies. Of course, we would also expect to get more relevant recommendations
if our database consisted of more movies!
There are many more subtleties that could be explored in terms of the bot's ability to understand user requests.
For instance, adding more data on each individual movie would allow users to add a filter to their query. We
could also find ways to mathematize a statement like "I want a movie that's similar to A but not like B".
""")