opex792 commited on
Commit
29b1a76
·
verified ·
1 Parent(s): 6505bc8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +62 -82
app.py CHANGED
@@ -51,6 +51,7 @@ for movie in movies_data:
51
 
52
  # Флаг, указывающий, что обработка фильмов завершена
53
  processing_complete = False
 
54
  # Флаг, указывающий, что выполняется поиск
55
  search_in_progress = False
56
 
@@ -74,11 +75,11 @@ def setup_database():
74
  conn = get_db_connection()
75
  if conn is None:
76
  return
77
-
78
  with conn.cursor() as cur:
79
  # Создаем расширение pgvector
80
  cur.execute("CREATE EXTENSION IF NOT EXISTS vector;")
81
-
82
  # Создаем таблицу для хранения эмбеддингов фильмов
83
  cur.execute(f"""
84
  CREATE TABLE IF NOT EXISTS {embeddings_table} (
@@ -89,7 +90,7 @@ def setup_database():
89
  embedding vector(1024)
90
  );
91
  """)
92
-
93
  # Создаем таблицу для кэширования эмбеддингов запросов
94
  cur.execute(f"""
95
  CREATE TABLE IF NOT EXISTS {query_cache_table} (
@@ -102,7 +103,7 @@ def setup_database():
102
  CREATE INDEX IF NOT EXISTS idx_query_crc32 ON {query_cache_table} (query_crc32);
103
  CREATE INDEX IF NOT EXISTS idx_created_at ON {query_cache_table} (created_at);
104
  """)
105
-
106
  # Создаем функцию и триггер для автоматического удаления старых записей из таблицы кэша запросов
107
  cur.execute(f"""
108
  CREATE OR REPLACE FUNCTION manage_query_cache_size()
@@ -113,11 +114,7 @@ def setup_database():
113
  BEGIN
114
  SELECT pg_total_relation_size('{query_cache_table}') INTO table_size;
115
  IF table_size > {MAX_CACHE_SIZE} THEN
116
- FOR row_to_delete IN
117
- SELECT query_crc32
118
- FROM {query_cache_table}
119
- ORDER BY created_at ASC
120
- LOOP
121
  DELETE FROM {query_cache_table} WHERE query_crc32 = row_to_delete.query_crc32;
122
  SELECT pg_total_relation_size('{query_cache_table}') INTO table_size;
123
  EXIT WHEN table_size <= {MAX_CACHE_SIZE};
@@ -132,7 +129,8 @@ def setup_database():
132
  FOR EACH ROW
133
  EXECUTE PROCEDURE manage_query_cache_size();
134
  """)
135
- conn.commit()
 
136
  conn.close()
137
 
138
  # Настраиваем базу данных при запуске приложения
@@ -164,14 +162,14 @@ def insert_embedding(conn, table_name, crc32_column, crc32_value, other_columns,
164
  columns = ', '.join([crc32_column] + list(other_columns.keys()) + ['model_name', 'embedding'])
165
  placeholders = ', '.join(['%s'] * (len(other_columns) + 3))
166
  values = (crc32_value,) + tuple(other_columns.values()) + (model_name, embedding.tolist())
167
-
168
  with conn.cursor() as cur:
169
  try:
170
  cur.execute(f"""
171
  INSERT INTO {table_name} ({columns})
172
  VALUES ({placeholders})
173
  ON CONFLICT ({crc32_column}) DO NOTHING;
174
- """, values)
175
  conn.commit()
176
  return True
177
  except Exception as e:
@@ -212,22 +210,22 @@ def process_movies():
212
  f"Название: {movie['name']}\nГод: {movie['year']}\nЖанры: {movie['genresList']}\nОписание: {movie['description']}"
213
  for movie in batch
214
  ]
215
-
216
  print(f"Создаются эмбеддинги для фильмов: {', '.join(titles)}...")
217
-
218
  with db_lock:
219
  for movie, embedding_string in zip(batch, embedding_strings):
220
  movie_id = movie['id']
221
  string_crc32 = calculate_crc32(embedding_string)
222
-
223
  # Проверяем, есть ли уже эмбеддинг для этого фильма в базе данных
224
  existing_embedding = get_embedding_from_db(conn, embeddings_table, "string_crc32", string_crc32, model_name)
225
-
226
  if existing_embedding is None:
227
  # Создаем эмбеддинг, только если его нет в базе данных
228
  embedding = encode_string(embedding_string)
229
- embedding_crc32 = calculate_crc32(embedding.numpy().tobytes()) # Исправлено
230
-
231
  if insert_embedding(conn, embeddings_table, "embedding_crc32", embedding_crc32, {"movie_id": movie_id, "string_crc32": string_crc32}, embedding):
232
  print(f"Эмбеддинг для фильма '{movie['name']}' сохранен в базе данных.")
233
  else:
@@ -237,7 +235,7 @@ def process_movies():
237
 
238
  conn.close()
239
  print("Обработка фильмов завершена.")
240
-
241
  def get_movie_embeddings(conn):
242
  """Загружает все эмбеддинги фильмов из базы данных."""
243
  movie_embeddings = {}
@@ -257,101 +255,83 @@ def get_movie_embeddings(conn):
257
  def search_movies(query, top_k=10):
258
  """
259
  Ищет наиболее похожие фильмы по запросу.
260
-
261
  Args:
262
  query: Текстовый запрос.
263
  top_k: Количество возвращаемых результатов.
264
-
265
  Returns:
266
  Строку с результатами поиска в формате HTML.
267
  """
268
  global search_in_progress
269
  search_in_progress = True
270
  start_time = time.time()
 
271
  print(f"\n\033[1mПоиск по запросу: '{query}'\033[0m")
272
-
273
  conn = get_db_connection()
274
  if conn is None:
275
  search_in_progress = False
276
  return "<p>Ошибка подключения к базе данных.</p>"
277
 
278
  query_crc32 = calculate_crc32(query)
279
-
280
  # Проверяем, есть ли уже эмбеддинг для этого запроса в кэше
281
  print(f"Начало поиска эмбеддинга запроса в кэше: {time.strftime('%Y-%m-%d %H:%M:%S')}")
282
  query_embedding_tensor = get_embedding_from_db(conn, query_cache_table, "query_crc32", query_crc32, model_name)
283
  print(f"Окончание поиска эмбеддинга запроса в кэше: {time.strftime('%Y-%m-%d %H:%M:%S')}")
284
 
285
  if query_embedding_tensor is None:
286
- print(f"Начало создания эмбеддинга для запроса: {time.strftime('%Y-%m-%d %H:%M:%S')}")
 
287
  query_embedding_tensor = encode_string(query)
288
- print(f"Окончание создания эмбеддинга для запроса: {time.strftime('%Y-%m-%d %H:%M:%S')}")
289
-
290
- # Вставляем эмбеддинг запроса в базу данных
291
  insert_embedding(conn, query_cache_table, "query_crc32", query_crc32, {"query": query}, query_embedding_tensor)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
 
293
- with db_lock:
294
- current_movie_embeddings = get_movie_embeddings(conn)
295
-
296
- conn.close()
297
-
298
- if not current_movie_embeddings:
299
- search_in_progress = False
300
- return "<p>Пока что нет обработанных фильмов. Попробуйте позже.</p>"
301
-
302
- # Преобразуем эмбеддинги фильмов в тензор
303
- movie_titles = list(current_movie_embeddings.keys())
304
- movie_embeddings_tensor = torch.stack(list(current_movie_embeddings.values()))
305
-
306
- print(f"Начало поиска похожих фильмов: {time.strftime('%Y-%m-%d %H:%M:%S')}")
307
- # Используем util.semantic_search для поиска похожих фильмов
308
- hits = util.semantic_search(query_embedding_tensor, movie_embeddings_tensor, top_k=top_k)[0]
309
- print(f"Окончание поиска похожих фильмов: {time.strftime('%Y-%m-%d %H:%M:%S')}")
310
-
311
- results_html = ""
312
- for hit in hits:
313
- title = movie_titles[hit['corpus_id']]
314
- score = hit['score']
315
- # Ищем полное описание фильма в исходных данных
316
- for movie in movies_data:
317
- if movie["name"] == title:
318
- description = movie["description"]
319
- year = movie["year"]
320
- genres = movie["genresList"]
321
- break
322
-
323
- results_html += f"<h3><b>{title} ({year})</b></h3>"
324
- results_html += f"<p><b>Жанры:</b> {genres}</p>"
325
- results_html += f"<p><b>Описание:</b> {description}</p>"
326
- results_html += f"<p><b>Сходство:</b> {score:.4f}</p>"
327
- results_html += "<hr>"
328
 
329
- end_time = time.time()
330
- execution_time = end_time - start_time
331
- print(f"Поиск завершен за {execution_time:.4f} секунд.")
332
  search_in_progress = False
333
- return results_html
 
 
334
 
335
- # Поток для обработки фильмов
 
 
336
  processing_thread = threading.Thread(target=process_movies)
 
337
 
338
  # Создаем интерфейс Gradio
339
  iface = gr.Interface(
340
  fn=search_movies,
341
- inputs=gr.Textbox(label="Введите запрос:"),
342
- outputs=gr.HTML(label="Результаты поиска:"),
343
- title="Поиск фильмов по описанию",
344
- description="Введите запрос, и система найдет наиболее похожие фильмы по их описаниям.",
345
- examples=[
346
- ["Фильм про ограбление"],
347
- ["Комедия 2019 года"],
348
- ["Фантастика про космос"],
349
- ],
350
  )
351
 
352
- # Запускаем поток для обработки фильмов
353
- processing_thread.start()
354
-
355
- # Запускаем приложение
356
- iface.queue()
357
- iface.launch()
 
51
 
52
  # Флаг, указывающий, что обработка фильмов завершена
53
  processing_complete = False
54
+
55
  # Флаг, указывающий, что выполняется поиск
56
  search_in_progress = False
57
 
 
75
  conn = get_db_connection()
76
  if conn is None:
77
  return
78
+
79
  with conn.cursor() as cur:
80
  # Создаем расширение pgvector
81
  cur.execute("CREATE EXTENSION IF NOT EXISTS vector;")
82
+
83
  # Создаем таблицу для хранения эмбеддингов фильмов
84
  cur.execute(f"""
85
  CREATE TABLE IF NOT EXISTS {embeddings_table} (
 
90
  embedding vector(1024)
91
  );
92
  """)
93
+
94
  # Создаем таблицу для кэширования эмбеддингов запросов
95
  cur.execute(f"""
96
  CREATE TABLE IF NOT EXISTS {query_cache_table} (
 
103
  CREATE INDEX IF NOT EXISTS idx_query_crc32 ON {query_cache_table} (query_crc32);
104
  CREATE INDEX IF NOT EXISTS idx_created_at ON {query_cache_table} (created_at);
105
  """)
106
+
107
  # Создаем функцию и триггер для автоматического удаления старых записей из таблицы кэша запросов
108
  cur.execute(f"""
109
  CREATE OR REPLACE FUNCTION manage_query_cache_size()
 
114
  BEGIN
115
  SELECT pg_total_relation_size('{query_cache_table}') INTO table_size;
116
  IF table_size > {MAX_CACHE_SIZE} THEN
117
+ FOR row_to_delete IN SELECT query_crc32 FROM {query_cache_table} ORDER BY created_at ASC LOOP
 
 
 
 
118
  DELETE FROM {query_cache_table} WHERE query_crc32 = row_to_delete.query_crc32;
119
  SELECT pg_total_relation_size('{query_cache_table}') INTO table_size;
120
  EXIT WHEN table_size <= {MAX_CACHE_SIZE};
 
129
  FOR EACH ROW
130
  EXECUTE PROCEDURE manage_query_cache_size();
131
  """)
132
+
133
+ conn.commit()
134
  conn.close()
135
 
136
  # Настраиваем базу данных при запуске приложения
 
162
  columns = ', '.join([crc32_column] + list(other_columns.keys()) + ['model_name', 'embedding'])
163
  placeholders = ', '.join(['%s'] * (len(other_columns) + 3))
164
  values = (crc32_value,) + tuple(other_columns.values()) + (model_name, embedding.tolist())
165
+
166
  with conn.cursor() as cur:
167
  try:
168
  cur.execute(f"""
169
  INSERT INTO {table_name} ({columns})
170
  VALUES ({placeholders})
171
  ON CONFLICT ({crc32_column}) DO NOTHING;
172
+ """, values)
173
  conn.commit()
174
  return True
175
  except Exception as e:
 
210
  f"Название: {movie['name']}\nГод: {movie['year']}\nЖанры: {movie['genresList']}\nОписание: {movie['description']}"
211
  for movie in batch
212
  ]
213
+
214
  print(f"Создаются эмбеддинги для фильмов: {', '.join(titles)}...")
215
+
216
  with db_lock:
217
  for movie, embedding_string in zip(batch, embedding_strings):
218
  movie_id = movie['id']
219
  string_crc32 = calculate_crc32(embedding_string)
220
+
221
  # Проверяем, есть ли уже эмбеддинг для этого фильма в базе данных
222
  existing_embedding = get_embedding_from_db(conn, embeddings_table, "string_crc32", string_crc32, model_name)
223
+
224
  if existing_embedding is None:
225
  # Создаем эмбеддинг, только если его нет в базе данных
226
  embedding = encode_string(embedding_string)
227
+ embedding_crc32 = calculate_crc32(embedding.cpu().numpy().tobytes())
228
+
229
  if insert_embedding(conn, embeddings_table, "embedding_crc32", embedding_crc32, {"movie_id": movie_id, "string_crc32": string_crc32}, embedding):
230
  print(f"Эмбеддинг для фильма '{movie['name']}' сохранен в базе данных.")
231
  else:
 
235
 
236
  conn.close()
237
  print("Обработка фильмов завершена.")
238
+
239
  def get_movie_embeddings(conn):
240
  """Загружает все эмбеддинги фильмов из базы данных."""
241
  movie_embeddings = {}
 
255
  def search_movies(query, top_k=10):
256
  """
257
  Ищет наиболее похожие фильмы по запросу.
 
258
  Args:
259
  query: Текстовый запрос.
260
  top_k: Количество возвращаемых результатов.
 
261
  Returns:
262
  Строку с результатами поиска в формате HTML.
263
  """
264
  global search_in_progress
265
  search_in_progress = True
266
  start_time = time.time()
267
+
268
  print(f"\n\033[1mПоиск по запросу: '{query}'\033[0m")
269
+
270
  conn = get_db_connection()
271
  if conn is None:
272
  search_in_progress = False
273
  return "<p>Ошибка подключения к базе данных.</p>"
274
 
275
  query_crc32 = calculate_crc32(query)
276
+
277
  # Проверяем, есть ли уже эмбеддинг для этого запроса в кэше
278
  print(f"Начало поиска эмбеддинга запроса в кэше: {time.strftime('%Y-%m-%d %H:%M:%S')}")
279
  query_embedding_tensor = get_embedding_from_db(conn, query_cache_table, "query_crc32", query_crc32, model_name)
280
  print(f"Окончание поиска эмбеддинга запроса в кэше: {time.strftime('%Y-%m-%d %H:%M:%S')}")
281
 
282
  if query_embedding_tensor is None:
283
+ # Если эмбеддинга нет в кэше, создаем новый
284
+ print(f"Начало создания эмбеддинга запроса: {time.strftime('%Y-%m-%d %H:%M:%S')}")
285
  query_embedding_tensor = encode_string(query)
286
+ print(f"Окончание создания эмбеддинга запроса: {time.strftime('%Y-%m-%d %H:%M:%S')}")
287
+
288
+ # Сохраняем эмбеддинг запроса в кэш
289
  insert_embedding(conn, query_cache_table, "query_crc32", query_crc32, {"query": query}, query_embedding_tensor)
290
+ else:
291
+ print("Эмбеддинг запроса найден в кэше.")
292
+
293
+ # Загружаем эмбеддинги фильмов
294
+ print(f"Начало загрузки эмбеддингов фильмов: {time.strftime('%Y-%m-%d %H:%M:%S')}")
295
+ movie_embeddings = get_movie_embeddings(conn)
296
+ print(f"Окончание загрузки эмбеддингов фильмов: {time.strftime('%Y-%m-%d %H:%M:%S')}")
297
+
298
+ # Вычисляем косинусное сходство
299
+ print(f"Начало вычисления косинусного сходства: {time.strftime('%Y-%m-%d %H:%M:%S')}")
300
+ similarities = []
301
+ for title, movie_embedding in movie_embeddings.items():
302
+ similarity = util.pytorch_cos_sim(query_embedding_tensor, movie_embedding).item()
303
+ similarities.append((title, similarity))
304
+
305
+ # Сортируем результаты
306
+ similarities.sort(key=lambda x: x[1], reverse=True)
307
+ top_results = similarities[:top_k]
308
+ print(f"Окончание вычисления косинусного сходства: {time.strftime('%Y-%m-%d %H:%M:%S')}")
309
 
310
+ # Формируем HTML-строку с результатами
311
+ results_html = "<ol>"
312
+ for title, score in top_results:
313
+ results_html += f"<li><strong>{title}</strong> (Сходство: {score:.4f})</li>"
314
+ results_html += "</ol>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
 
 
 
 
316
  search_in_progress = False
317
+ end_time = time.time()
318
+ search_time = end_time - start_time
319
+ print(f"\033[1mПоиск завершен за {search_time:.2f} секунд.\033[0m")
320
 
321
+ return f"<p>Время поиска: {search_time:.2f} секунд</p>" + results_html
322
+
323
+ # Запускаем обработку фильмов в отдельном потоке
324
  processing_thread = threading.Thread(target=process_movies)
325
+ processing_thread.start()
326
 
327
  # Создаем интерфейс Gradio
328
  iface = gr.Interface(
329
  fn=search_movies,
330
+ inputs=gr.Textbox(lines=2, placeholder="Введите запрос для поиска фильмов..."),
331
+ outputs=gr.HTML(label="Результаты поиска"),
332
+ title="Семантический поиск фильмов",
333
+ description="Введите описание фильма, который вы ищете, и система найдет наиболее похожие фильмы."
 
 
 
 
 
334
  )
335
 
336
+ # Запускаем интерфейс
337
+ iface.launch()