Spaces:
Sleeping
Sleeping
second version of app
Browse files- .gitattributes +1 -0
- README.md +52 -1
- app.py +121 -0
- clean_series_data.csv +3 -0
- embeddings.npy +3 -0
- pages/model_w_clustering.py +137 -0
- requirements.txt +8 -0
.gitattributes
CHANGED
@@ -2,6 +2,7 @@
|
|
2 |
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
|
|
5 |
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
*.gz filter=lfs diff=lfs merge=lfs -text
|
|
|
2 |
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.csv filter=lfs diff=lfs merge=lfs -text
|
6 |
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
7 |
*.ftz filter=lfs diff=lfs merge=lfs -text
|
8 |
*.gz filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
@@ -9,4 +9,55 @@ app_file: app.py
|
|
9 |
pinned: false
|
10 |
---
|
11 |
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
pinned: false
|
10 |
---
|
11 |
|
12 |
+
## Описание проекта
|
13 |
+
|
14 |
+
Сегодняшний поиск на стриминговом сервисе происходит только по режиссёру, актёрам и названию сериала, при этом не учитывается описание сериала, которое может содержать ценную информацию для пользовательского запроса. Этот проект направлен на сбор выборки из не менее 5000 описаний сериалов и построение системы поиска наиболее подходящих под пользовательский запрос вариантов.
|
15 |
+
|
16 |
+
## Язык описаний
|
17 |
+
Описания сериалов собирались на русском языке
|
18 |
+
|
19 |
+
## Требования
|
20 |
+
|
21 |
+
Чтобы запустить сервис, необходимо установить следующие зависимости:
|
22 |
+
- streamlit
|
23 |
+
- sentence-transformers
|
24 |
+
- faiss-cpu
|
25 |
+
- pandas
|
26 |
+
- numpy
|
27 |
+
- requests
|
28 |
+
- pillow
|
29 |
+
|
30 |
+
Чтобы установить все зависимости, необходимо выполнить команду:
|
31 |
+
**pip install -r requirements.txt**
|
32 |
+
|
33 |
+
|
34 |
+
## Сбор данных и обработка
|
35 |
+
|
36 |
+
Для начала работы было необходимо собрать данные с описаниями сериалов. Для этого использовали парсинг [сайта](https://myshows.me/), было собрано около 10 000 описаний к разным сериалам. Важной частью являлась обработка текста, например, удаление скрытых символов и фраз по типу "ПОЖАЛУЙСТА, ОБРАТИТЕ ВНИМАНИЕ" и т.п.
|
37 |
+
|
38 |
+
## Модель
|
39 |
+
|
40 |
+
Для получения эмбеддингов использовалась языковая модель - [cointegrated/rubert-tiny2](https://huggingface.co/cointegrated/rubert-tiny2)
|
41 |
+
|
42 |
+
## Использование и запуск сервиса
|
43 |
+
|
44 |
+
Чтобы запустить сервис, выполните команду:
|
45 |
+
streamlit run app.py
|
46 |
+
|
47 |
+
Далее откройте браузер и перейдите по адресу, указанному в терминале.
|
48 |
+
|
49 |
+
## Ввод запроса
|
50 |
+
1. Введите ваш запрос в текстовое поле "Введите описание сериала"
|
51 |
+
2. Установите ползунок в диапазоне от 1 до 10 для рекомендации необходимого количества сериалов
|
52 |
+
|
53 |
+
## Результаты поиска
|
54 |
+
Сервис вернёт список сериалов, отсортированных по метрике - косинусному сходству, к вашему запросу.
|
55 |
+
|
56 |
+
## Структура репозитория
|
57 |
+
|
58 |
+
- app.py — главный файл приложения
|
59 |
+
- clean_series_data.csv — файл с описаниями сериалов
|
60 |
+
- embeddings.npy - полученные эмбеддинги
|
61 |
+
- requirements.txt — файл с перечнем зависимостей
|
62 |
+
- README.md — этот файл с описанием проекта и инструкцией по запуску
|
63 |
+
|
app.py
ADDED
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
from sentence_transformers import SentenceTransformer
|
3 |
+
import faiss
|
4 |
+
import numpy as np
|
5 |
+
import streamlit as st
|
6 |
+
import requests
|
7 |
+
from PIL import Image
|
8 |
+
from io import BytesIO
|
9 |
+
from langchain_community.chat_models.gigachat import GigaChat
|
10 |
+
|
11 |
+
st.title('Рекомендации сериалов по описанию пользователя с помощью асимметричного семантического поиска')
|
12 |
+
st.divider()
|
13 |
+
df = pd.read_csv('clean_series_data.csv')
|
14 |
+
embeddings = np.load('embeddings.npy')
|
15 |
+
|
16 |
+
|
17 |
+
def load_image_from_url(url):
|
18 |
+
try:
|
19 |
+
response = requests.get(url)
|
20 |
+
response.raise_for_status()
|
21 |
+
return Image.open(BytesIO(response.content))
|
22 |
+
except Exception as e:
|
23 |
+
st.error(f"Не удалось загрузить изображение: {e}")
|
24 |
+
return None
|
25 |
+
|
26 |
+
|
27 |
+
model = SentenceTransformer('cointegrated/rubert-tiny2')
|
28 |
+
model.cpu()
|
29 |
+
# embeddings_desc = df['Описание'].apply(lambda x: model.encode(x))
|
30 |
+
# embeddings_gan = df['Жанры'].apply(lambda x: model.encode(x))
|
31 |
+
|
32 |
+
# embeddings = embeddings_desc + embeddings_gan
|
33 |
+
metric = st.radio('Выберите метрику для поиска', [
|
34 |
+
'Евклидово расстояние', 'Косинусное сходство'])
|
35 |
+
if metric == 'Евклидово расстояние':
|
36 |
+
embeddings = np.array(embeddings).astype(np.float32)
|
37 |
+
faiss.normalize_L2(embeddings)
|
38 |
+
dimension = embeddings.shape[1]
|
39 |
+
index = faiss.IndexFlatL2(dimension)
|
40 |
+
index.add(embeddings)
|
41 |
+
|
42 |
+
button = st.button('Вывести результаты')
|
43 |
+
query = [st.text_area('Введите описание сериала')]
|
44 |
+
if button:
|
45 |
+
if query:
|
46 |
+
query_embedding = model.encode(query).astype(np.float32)
|
47 |
+
# Две строки ниже можно будет убрать
|
48 |
+
# query_embedding = np.array(
|
49 |
+
# query_embedding, dtype=np.float32).reshape(1, -1)
|
50 |
+
# faiss.normalize_L2(query_embedding)
|
51 |
+
|
52 |
+
k = st.slider('Сколько сериалов рекомендовать?',
|
53 |
+
min_value=1, max_value=10, value=3, step=1)
|
54 |
+
distances, indices = index.search(query_embedding, k)
|
55 |
+
|
56 |
+
st.subheader('Похожие сериалы:')
|
57 |
+
for i in range(k):
|
58 |
+
url = df.loc[indices[0][i]]["Изображение"]
|
59 |
+
image = load_image_from_url(url)
|
60 |
+
st.image(image)
|
61 |
+
st.write(f'Название: {df.loc[indices[0][i]]["Название"]}')
|
62 |
+
st.write(f'Рейтинг: {df.loc[indices[0][i]]["Рейтинг"]}')
|
63 |
+
st.write(f'Жанр: {df.loc[indices[0][i]]["Жанры"]}')
|
64 |
+
st.write(f'Страна: {df.loc[indices[0][i]]["Страна"]}')
|
65 |
+
st.write(
|
66 |
+
f'Длительность одной серии: {df.loc[indices[0][i]]["Длительность"]}')
|
67 |
+
st.write(
|
68 |
+
f'Количество серий: {df.loc[indices[0][i]]["Количество серий"]}')
|
69 |
+
st.write(f'Описание: {df.loc[indices[0][i]]["Описание"]}')
|
70 |
+
st.write(f'Евклидово расстояние: {distances[0][i]:.4f}')
|
71 |
+
st.divider()
|
72 |
+
else:
|
73 |
+
embeddings = np.array(embeddings).astype(np.float32)
|
74 |
+
faiss.normalize_L2(embeddings)
|
75 |
+
dimension = embeddings.shape[1]
|
76 |
+
index = faiss.IndexFlatIP(dimension)
|
77 |
+
index.add(embeddings)
|
78 |
+
|
79 |
+
query = [st.text_area('Введите описание сериала')]
|
80 |
+
|
81 |
+
button = st.button('Вывести результаты')
|
82 |
+
if button:
|
83 |
+
if query:
|
84 |
+
query_embedding = model.encode(query).astype(np.float32)
|
85 |
+
# Две строки ниже можно будет убрать
|
86 |
+
# query_embedding = np.array(
|
87 |
+
# query_embedding, dtype=np.float32).reshape(1, -1)
|
88 |
+
# faiss.normalize_L2(query_embedding)
|
89 |
+
|
90 |
+
k = st.slider('Сколько сериалов рекомендовать?',
|
91 |
+
min_value=1, max_value=10, value=3, step=1)
|
92 |
+
distances, indices = index.search(query_embedding, k)
|
93 |
+
|
94 |
+
st.subheader('Похожие сериалы:')
|
95 |
+
for i in range(k):
|
96 |
+
url = df.loc[indices[0][i]]["Изображение"]
|
97 |
+
image = load_image_from_url(url)
|
98 |
+
st.image(image)
|
99 |
+
st.write(f'Название: {df.loc[indices[0][i]]["Название"]}')
|
100 |
+
st.write(f'Рейтинг: {df.loc[indices[0][i]]["Рейтинг"]}')
|
101 |
+
st.write(f'Жанр: {df.loc[indices[0][i]]["Жанры"]}')
|
102 |
+
st.write(f'Страна: {df.loc[indices[0][i]]["Страна"]}')
|
103 |
+
st.write(
|
104 |
+
f'Длительность одной серии: {df.loc[indices[0][i]]["Длительность"]}')
|
105 |
+
st.write(
|
106 |
+
f'Количество серий: {df.loc[indices[0][i]]["Количество серий"]}')
|
107 |
+
st.write(f'Описание: {df.loc[indices[0][i]]["Описание"]}')
|
108 |
+
st.write(f'Косинусное сходство: {distances[0][i]:.4f}')
|
109 |
+
st.divider()
|
110 |
+
|
111 |
+
st.subheader(
|
112 |
+
'Генерация краткого содержания сериала с помощью SberGigaChat')
|
113 |
+
name_of_series = st.text_input('Введите название сериала')
|
114 |
+
gen_button = st.button('Показать краткое содержание')
|
115 |
+
giga = GigaChat(
|
116 |
+
credentials='MjA2MGEzNjItZjE0Mi00NWE5LTllMDItMWVjZWRlNDA2ODM0OjNhNzNlZDJmLTY4NWUtNDI1Zi1iZjg4LTkxOWFjMjkxZDg0OA==', verify_ssl_certs=False)
|
117 |
+
if gen_button:
|
118 |
+
with st.spinner('Генерация текста...'):
|
119 |
+
st.write(giga.invoke(
|
120 |
+
f"Расскажи cюжет сериала {name_of_series}").content)
|
121 |
+
st.divider()
|
clean_series_data.csv
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:925177133ae0f6561279290bd5bf9e34df1014d8436fb8c05e39ac047412c44a
|
3 |
+
size 7397331
|
embeddings.npy
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:f005047f4c848ead774b1db9e0f3f3bc2ec18b0122b188e6770d579fdd71f0b0
|
3 |
+
size 8696192
|
pages/model_w_clustering.py
ADDED
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
from sentence_transformers import SentenceTransformer
|
3 |
+
import faiss
|
4 |
+
import numpy as np
|
5 |
+
import streamlit as st
|
6 |
+
import requests
|
7 |
+
from PIL import Image
|
8 |
+
from io import BytesIO
|
9 |
+
from langchain_community.chat_models.gigachat import GigaChat
|
10 |
+
|
11 |
+
st.title('Рекомендации сериалов по описанию пользователя с помощью симметричного семантического поиска')
|
12 |
+
st.divider()
|
13 |
+
df = pd.read_csv('clean_series_data.csv')
|
14 |
+
embeddings = np.load('embeddings.npy')
|
15 |
+
|
16 |
+
|
17 |
+
def load_image_from_url(url):
|
18 |
+
try:
|
19 |
+
response = requests.get(url)
|
20 |
+
response.raise_for_status()
|
21 |
+
return Image.open(BytesIO(response.content))
|
22 |
+
except Exception as e:
|
23 |
+
st.error(f"Не удалось загрузить изображение: {e}")
|
24 |
+
return None
|
25 |
+
|
26 |
+
|
27 |
+
model = SentenceTransformer('cointegrated/rubert-tiny2')
|
28 |
+
model.cpu()
|
29 |
+
# embeddings_desc = df['Описание'].apply(lambda x: model.encode(x))
|
30 |
+
# embeddings_gan = df['Жанры'].apply(lambda x: model.encode(x))
|
31 |
+
|
32 |
+
# embeddings = embeddings_desc + embeddings_gan
|
33 |
+
metric = st.radio('Выберите метрику для поиска', [
|
34 |
+
'Евклидово расстояние', 'Косинусное сходство'])
|
35 |
+
if metric == 'Евклидово расстояние':
|
36 |
+
embeddings = np.array(embeddings).astype(np.float32)
|
37 |
+
faiss.normalize_L2(embeddings)
|
38 |
+
|
39 |
+
dimension = embeddings.shape[1]
|
40 |
+
|
41 |
+
nlist = 150
|
42 |
+
|
43 |
+
quantizer = faiss.IndexFlatL2(dimension)
|
44 |
+
|
45 |
+
index = faiss.IndexIVFFlat(quantizer, dimension, nlist, faiss.METRIC_L2)
|
46 |
+
index.train(embeddings)
|
47 |
+
# index = faiss.IndexFlatIP(dimension)
|
48 |
+
|
49 |
+
index.add(embeddings)
|
50 |
+
query = [st.text_area('Введите описание сериала')]
|
51 |
+
|
52 |
+
button = st.button('Вывести результаты')
|
53 |
+
if button:
|
54 |
+
if query:
|
55 |
+
query_embedding = model.encode(query).astype(np.float32)
|
56 |
+
# Две строки ниже можно будет убрать
|
57 |
+
query_embedding = np.array(
|
58 |
+
query_embedding, dtype=np.float32).reshape(1, -1)
|
59 |
+
faiss.normalize_L2(query_embedding)
|
60 |
+
|
61 |
+
k = st.slider('Сколько сериалов рекомендовать?',
|
62 |
+
min_value=1, max_value=10, value=3, step=1)
|
63 |
+
distances, indices = index.search(query_embedding, k)
|
64 |
+
|
65 |
+
st.subheader('Похожие сериалы:')
|
66 |
+
for i in range(k):
|
67 |
+
url = df.loc[indices[0][i]]["Изображение"]
|
68 |
+
image = load_image_from_url(url)
|
69 |
+
st.image(image)
|
70 |
+
st.write(f'Название: {df.loc[indices[0][i]]["Название"]}')
|
71 |
+
st.write(f'Рейтинг: {df.loc[indices[0][i]]["Рейтинг"]}')
|
72 |
+
st.write(f'Жанр: {df.loc[indices[0][i]]["Жанры"]}')
|
73 |
+
st.write(f'Страна: {df.loc[indices[0][i]]["Страна"]}')
|
74 |
+
st.write(
|
75 |
+
f'Длительность одной серии: {df.loc[indices[0][i]]["Длительность"]}')
|
76 |
+
st.write(
|
77 |
+
f'Количество серий: {df.loc[indices[0][i]]["Количество серий"]}')
|
78 |
+
st.write(f'Описание: {df.loc[indices[0][i]]["Описание"]}')
|
79 |
+
st.write(f'Евклидово расстояние: {distances[0][i]:.4f}')
|
80 |
+
st.divider()
|
81 |
+
else:
|
82 |
+
embeddings = np.array(embeddings).astype(np.float32)
|
83 |
+
faiss.normalize_L2(embeddings)
|
84 |
+
|
85 |
+
dimension = embeddings.shape[1]
|
86 |
+
nlist = 150
|
87 |
+
quantizer = faiss.IndexFlatIP(dimension)
|
88 |
+
index = faiss.IndexIVFFlat(
|
89 |
+
quantizer, dimension, nlist, faiss.METRIC_INNER_PRODUCT)
|
90 |
+
index.train(embeddings)
|
91 |
+
# index = faiss.IndexFlatIP(dimension)
|
92 |
+
|
93 |
+
index.add(embeddings)
|
94 |
+
|
95 |
+
query = [st.text_area('Введите описание сериала')]
|
96 |
+
|
97 |
+
button = st.button('Вывести результаты')
|
98 |
+
if button:
|
99 |
+
if query:
|
100 |
+
query_embedding = model.encode(query).astype(np.float32)
|
101 |
+
# Две строки ниже можно будет убрать
|
102 |
+
query_embedding = np.array(
|
103 |
+
query_embedding, dtype=np.float32).reshape(1, -1)
|
104 |
+
faiss.normalize_L2(query_embedding)
|
105 |
+
|
106 |
+
k = st.slider('Сколько сериалов рекомендовать?',
|
107 |
+
min_value=1, max_value=10, value=3, step=1)
|
108 |
+
distances, indices = index.search(query_embedding, k)
|
109 |
+
|
110 |
+
st.subheader('Похожие сериалы:')
|
111 |
+
for i in range(k):
|
112 |
+
url = df.loc[indices[0][i]]["Изображение"]
|
113 |
+
image = load_image_from_url(url)
|
114 |
+
st.image(image)
|
115 |
+
st.write(f'Название: {df.loc[indices[0][i]]["Название"]}')
|
116 |
+
st.write(f'Рейтинг: {df.loc[indices[0][i]]["Рейтинг"]}')
|
117 |
+
st.write(f'Жанр: {df.loc[indices[0][i]]["Жанры"]}')
|
118 |
+
st.write(f'Страна: {df.loc[indices[0][i]]["Страна"]}')
|
119 |
+
st.write(
|
120 |
+
f'Длительность одной серии: {df.loc[indices[0][i]]["Длительность"]}')
|
121 |
+
st.write(
|
122 |
+
f'Количество серий: {df.loc[indices[0][i]]["Количество серий"]}')
|
123 |
+
st.write(f'Описание: {df.loc[indices[0][i]]["Описание"]}')
|
124 |
+
st.write(f'Косинусное сходство: {distances[0][i]:.4f}')
|
125 |
+
st.divider()
|
126 |
+
|
127 |
+
st.subheader('Генерация краткого содержания сериала с помощью SberGigaChat')
|
128 |
+
|
129 |
+
name_of_series = st.text_input('Введите название сериала')
|
130 |
+
gen_button = st.button('Показать краткое содержание')
|
131 |
+
giga = GigaChat(
|
132 |
+
credentials='MjA2MGEzNjItZjE0Mi00NWE5LTllMDItMWVjZWRlNDA2ODM0OjNhNzNlZDJmLTY4NWUtNDI1Zi1iZjg4LTkxOWFjMjkxZDg0OA==', verify_ssl_certs=False)
|
133 |
+
if gen_button:
|
134 |
+
with st.spinner('Генерация текста...'):
|
135 |
+
st.write(giga.invoke(
|
136 |
+
f"Расскажи cюжет сериала {name_of_series}").content)
|
137 |
+
st.divider()
|
requirements.txt
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
streamlit
|
2 |
+
pandas
|
3 |
+
sentence_transformers
|
4 |
+
faiss-cpu
|
5 |
+
numpy
|
6 |
+
requests
|
7 |
+
pillow
|
8 |
+
gigachain_community
|