opex792 commited on
Commit
3e7e517
·
verified ·
1 Parent(s): 0365be0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +132 -121
app.py CHANGED
@@ -11,7 +11,6 @@ import numpy as np
11
  from urllib.parse import urlparse
12
  import logging
13
  from sklearn.preprocessing import normalize
14
- import json
15
 
16
  # Настройка логирования
17
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
@@ -40,16 +39,27 @@ logging.info("Модель загружена успешно.")
40
  # Имена таблиц
41
  embeddings_table = "movie_embeddings"
42
  query_cache_table = "query_cache"
43
- movies_table = "Movies" # Новая таблица Movies
44
 
45
  # Максимальный размер таблицы кэша запросов в байтах (50MB)
46
  MAX_CACHE_SIZE = 50 * 1024 * 1024
47
 
 
 
 
 
 
 
 
 
 
 
48
  # Очередь для необработанных фильмов
49
  movies_queue = queue.Queue()
50
 
51
- # Флаги
52
  processing_complete = False
 
 
53
  search_in_progress = False
54
 
55
  # Блокировка для доступа к базе данных
@@ -72,49 +82,40 @@ def setup_database():
72
  conn = get_db_connection()
73
  if conn is None:
74
  return
 
75
  try:
76
  with conn.cursor() as cur:
77
  # Создаем расширение pgvector если его нет
78
  cur.execute("CREATE EXTENSION IF NOT EXISTS vector;")
79
-
80
  # Удаляем существующие таблицы если они есть
81
  cur.execute(f"DROP TABLE IF EXISTS {embeddings_table}, {query_cache_table};")
82
 
83
  # Создаем таблицу для хранения эмбеддингов фильмов
84
  cur.execute(f"""
85
- CREATE TABLE IF NOT EXISTS {embeddings_table} (
86
- movie_id INTEGER PRIMARY KEY,
87
- embedding_crc32 BIGINT,
88
- string_crc32 BIGINT,
89
- model_name TEXT,
90
- embedding vector(1024)
91
- );
92
- CREATE INDEX IF NOT EXISTS idx_embeddings_string_crc32 ON {embeddings_table} (string_crc32);
93
  """)
94
-
95
  # Создаем таблицу для кэширования запросов
96
  cur.execute(f"""
97
- CREATE TABLE IF NOT EXISTS {query_cache_table} (
98
- query_crc32 BIGINT PRIMARY KEY,
99
- query TEXT,
100
- model_name TEXT,
101
- embedding vector(1024),
102
- created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
103
- );
104
- CREATE INDEX IF NOT EXISTS idx_cache_query_crc32 ON {query_cache_table} (query_crc32);
105
- CREATE INDEX IF NOT EXISTS idx_cache_created_at ON {query_cache_table} (created_at);
106
- """)
107
-
108
- # Проверяем существование таблицы Movies
109
- cur.execute(f"""
110
- SELECT EXISTS (
111
- SELECT FROM information_schema.tables
112
- WHERE table_name = '{movies_table}'
113
- );
114
  """)
115
- if not cur.fetchone()[0]:
116
- logging.error(f"Таблица {movies_table} не существует в базе данных.")
117
-
118
  conn.commit()
119
  logging.info("База данных успешно настроена.")
120
  except Exception as e:
@@ -140,43 +141,33 @@ def get_movies_without_embeddings():
140
  conn = get_db_connection()
141
  if conn is None:
142
  return []
143
-
144
  movies_to_process = []
145
  try:
146
  with conn.cursor() as cur:
147
- # Получаем список ID фильмов, которые уже есть в таблице эмбеддингов
148
  cur.execute(f"SELECT movie_id FROM {embeddings_table}")
149
  existing_ids = {row[0] for row in cur.fetchall()}
150
-
151
- # Получаем фильмы из таблицы Movies, которых нет в таблице эмбеддингов
152
- cur.execute(f"""
153
- SELECT id, data FROM {movies_table}
154
- WHERE id NOT IN (SELECT movie_id FROM {embeddings_table})
155
- """)
156
-
157
- for row in cur.fetchall():
158
- movie_id, movie_data = row
159
- movie_info = json.loads(movie_data)
160
- movies_to_process.append({
161
- 'id': movie_id,
162
- 'name': movie_info.get('name', ''),
163
- 'description': movie_info.get('description', ''),
164
- 'genres': [genre['name'] for genre in movie_info.get('genres', [])]
165
- })
166
-
167
  logging.info(f"Найдено {len(movies_to_process)} фильмов для обработки.")
168
  except Exception as e:
169
  logging.error(f"Ошибка при получении списка фильмов для обработки: {e}")
170
  finally:
171
  conn.close()
172
-
173
  return movies_to_process
174
 
175
  def get_embedding_from_db(conn, table_name, crc32_column, crc32_value, model_name):
176
  """Получает эмбеддинг из базы данных."""
177
  try:
178
  with conn.cursor() as cur:
179
- cur.execute(f"SELECT embedding FROM {table_name} WHERE {crc32_column} = %s AND model_name = %s", (crc32_value, model_name))
 
180
  result = cur.fetchone()
181
  if result and result[0]:
182
  # Нормализуем эмбеддинг после извлечения из БД
@@ -192,9 +183,10 @@ def insert_embedding(conn, table_name, movie_id, embedding_crc32, string_crc32,
192
  normalized_embedding = normalize(embedding.reshape(1, -1))[0]
193
  with conn.cursor() as cur:
194
  cur.execute(f"""
195
- INSERT INTO {table_name} (movie_id, embedding_crc32, string_crc32, model_name, embedding)
196
- VALUES (%s, %s, %s, %s, %s)
197
- ON CONFLICT (movie_id) DO NOTHING
 
198
  """, (movie_id, embedding_crc32, string_crc32, model_name, normalized_embedding.tolist()))
199
  conn.commit()
200
  return True
@@ -206,10 +198,12 @@ def insert_embedding(conn, table_name, movie_id, embedding_crc32, string_crc32,
206
  def process_movies():
207
  """Обрабатывает фильмы, создавая для них эмбеддинги."""
208
  global processing_complete
 
209
  logging.info("Начало обработки фильмов.")
210
-
211
  # Получаем список фильмов, которые нужно обработать
212
  movies_to_process = get_movies_without_embeddings()
 
213
  if not movies_to_process:
214
  logging.info("Все фильмы уже обработаны.")
215
  processing_complete = True
@@ -242,22 +236,24 @@ def process_movies():
242
  break
243
 
244
  logging.info(f"Обработка пакета из {len(batch)} фильмов...")
 
245
  for movie in batch:
246
- embedding_string = f"Название: {movie['name']}\nЖанры: {', '.join(movie['genres'])}\nОписание: {movie['description']}"
247
  string_crc32 = calculate_crc32(embedding_string)
248
 
249
  # Проверяем существующий эмбеддинг
250
  existing_embedding = get_embedding_from_db(conn, embeddings_table, "string_crc32", string_crc32, model_name)
 
251
  if existing_embedding is None:
252
  embedding = encode_string(embedding_string)
253
  embedding_crc32 = calculate_crc32(str(embedding.tolist()))
 
254
  if insert_embedding(conn, embeddings_table, movie['id'], embedding_crc32, string_crc32, embedding):
255
  logging.info(f"Сохранен эмбеддинг для '{movie['name']}'")
256
  else:
257
  logging.error(f"Ошибка сохранения эмбеддинга для '{movie['name']}'")
258
  else:
259
  logging.info(f"Эмбеддинг для '{movie['name']}' уже существу��т")
260
-
261
  except Exception as e:
262
  logging.error(f"Ошибка при обработке фильмов: {e}")
263
  finally:
@@ -270,15 +266,13 @@ def get_movie_embeddings(conn):
270
  movie_embeddings = {}
271
  try:
272
  with conn.cursor() as cur:
273
- cur.execute(f"""
274
- SELECT m.id, m.data, e.embedding
275
- FROM {movies_table} m
276
- JOIN {embeddings_table} e ON m.id = e.movie_id
277
- """)
278
- for movie_id, movie_data, embedding in cur.fetchall():
279
- movie_info = json.loads(movie_data)
280
- movie_name = movie_info.get('name', '')
281
- movie_embeddings[movie_name] = normalize(np.array(embedding).reshape(1, -1))[0]
282
  logging.info(f"Загружено {len(movie_embeddings)} эмбеддингов фильмов.")
283
  except Exception as e:
284
  logging.error(f"Ошибка при загрузке эмбеддингов фильмов: {e}")
@@ -288,72 +282,89 @@ def search_movies(query, top_k=10):
288
  """Выполняет поиск фильмов по запросу."""
289
  global search_in_progress
290
  search_in_progress = True
 
291
 
292
  try:
293
  conn = get_db_connection()
294
  if conn is None:
295
- return []
296
-
297
- # Загружаем эмбеддинги фильмов
298
- movie_embeddings = get_movie_embeddings(conn)
299
-
300
- # Получаем эмбеддинг запроса
301
- query_embedding = encode_string(query)
302
-
303
- # Выполняем поиск
304
- scores = util.dot_score(query_embedding, list(movie_embeddings.values())).cpu().tolist()[0]
305
- top_results = sorted(zip(scores, movie_embeddings.keys()), key=lambda x: x[0], reverse=True)[:top_k]
306
-
307
- results = []
308
- for score, movie_name in top_results:
309
- # Получаем полную информацию о фильме из базы данных
 
 
 
 
 
 
 
 
310
  with conn.cursor() as cur:
311
- cur.execute(f"SELECT data FROM {movies_table} WHERE data->>'name' = %s", (movie_name,))
312
- movie_data = cur.fetchone()
313
- if movie_data:
314
- movie_info = json.loads(movie_data[0])
315
- results.append({
316
- 'name': movie_name,
317
- 'description': movie_info.get('description', ''),
318
- 'genres': [genre['name'] for genre in movie_info.get('genres', [])],
319
- 'score': f"{score:.2f}"
320
- })
321
-
322
- return results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
  except Exception as e:
324
- logging.error(f"Ошибка при поиске фильмов: {e}")
325
- return []
 
326
  finally:
327
  if conn:
328
  conn.close()
329
  search_in_progress = False
330
 
331
- def start_processing():
332
- """Запускает обработку фильмов в отдельном потоке."""
333
- thread = threading.Thread(target=process_movies)
334
- thread.start()
335
-
336
- # Запускаем обработку фильмов при старте приложения
337
- start_processing()
338
-
339
- # Функция для интерфейса Gradio
340
- def search_interface(query):
341
- results = search_movies(query)
342
- output = ""
343
- for movie in results:
344
- output += f"Название: {movie['name']}\n"
345
- output += f"Жанры: {', '.join(movie['genres'])}\n"
346
- output += f"Описание: {movie['description']}\n"
347
- output += f"Оценка: {movie['score']}\n\n"
348
- return output
349
 
350
  # Создаем интерфейс Gradio
351
  iface = gr.Interface(
352
- fn=search_interface,
353
- inputs="text",
354
- outputs="text",
355
- title="Поиск фильмов",
356
- description="Введите запрос для поиска фильмов"
357
  )
358
 
359
  # Запускаем интерфейс
 
11
  from urllib.parse import urlparse
12
  import logging
13
  from sklearn.preprocessing import normalize
 
14
 
15
  # Настройка логирования
16
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
39
  # Имена таблиц
40
  embeddings_table = "movie_embeddings"
41
  query_cache_table = "query_cache"
 
42
 
43
  # Максимальный размер таблицы кэша запросов в байтах (50MB)
44
  MAX_CACHE_SIZE = 50 * 1024 * 1024
45
 
46
+ # Загружаем данные из файла movies.json
47
+ try:
48
+ import json
49
+ with open("movies.json", "r", encoding="utf-8") as f:
50
+ movies_data = json.load(f)
51
+ logging.info(f"Загружено {len(movies_data)} фильмов из movies.json")
52
+ except FileNotFoundError:
53
+ logging.error("Ошибка: Файл movies.json не найден.")
54
+ movies_data = []
55
+
56
  # Очередь для необработанных фильмов
57
  movies_queue = queue.Queue()
58
 
59
+ # Флаг, указывающий, что обработка фильмов завершена
60
  processing_complete = False
61
+
62
+ # Флаг, указывающий, что выполняется поиск
63
  search_in_progress = False
64
 
65
  # Блокировка для доступа к базе данных
 
82
  conn = get_db_connection()
83
  if conn is None:
84
  return
85
+
86
  try:
87
  with conn.cursor() as cur:
88
  # Создаем расширение pgvector если его нет
89
  cur.execute("CREATE EXTENSION IF NOT EXISTS vector;")
90
+
91
  # Удаляем существующие таблицы если они есть
92
  cur.execute(f"DROP TABLE IF EXISTS {embeddings_table}, {query_cache_table};")
93
 
94
  # Создаем таблицу для хранения эмбеддингов фильмов
95
  cur.execute(f"""
96
+ CREATE TABLE {embeddings_table} (
97
+ movie_id INTEGER PRIMARY KEY,
98
+ embedding_crc32 BIGINT,
99
+ string_crc32 BIGINT,
100
+ model_name TEXT,
101
+ embedding vector(1024)
102
+ );
103
+ CREATE INDEX ON {embeddings_table} (string_crc32);
104
  """)
105
+
106
  # Создаем таблицу для кэширования запросов
107
  cur.execute(f"""
108
+ CREATE TABLE {query_cache_table} (
109
+ query_crc32 BIGINT PRIMARY KEY,
110
+ query TEXT,
111
+ model_name TEXT,
112
+ embedding vector(1024),
113
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
114
+ );
115
+ CREATE INDEX ON {query_cache_table} (query_crc32);
116
+ CREATE INDEX ON {query_cache_table} (created_at);
 
 
 
 
 
 
 
 
117
  """)
118
+
 
 
119
  conn.commit()
120
  logging.info("База данных успешно настроена.")
121
  except Exception as e:
 
141
  conn = get_db_connection()
142
  if conn is None:
143
  return []
144
+
145
  movies_to_process = []
146
  try:
147
  with conn.cursor() as cur:
148
+ # Получаем список ID фильмов, которые уже есть в базе
149
  cur.execute(f"SELECT movie_id FROM {embeddings_table}")
150
  existing_ids = {row[0] for row in cur.fetchall()}
151
+
152
+ # Фильтруем только те фильмы, которых нет в базе
153
+ for movie in movies_data:
154
+ if movie['id'] not in existing_ids:
155
+ movies_to_process.append(movie)
156
+
 
 
 
 
 
 
 
 
 
 
 
157
  logging.info(f"Найдено {len(movies_to_process)} фильмов для обработки.")
158
  except Exception as e:
159
  logging.error(f"Ошибка при получении списка фильмов для обработки: {e}")
160
  finally:
161
  conn.close()
162
+
163
  return movies_to_process
164
 
165
  def get_embedding_from_db(conn, table_name, crc32_column, crc32_value, model_name):
166
  """Получает эмбеддинг из базы данных."""
167
  try:
168
  with conn.cursor() as cur:
169
+ cur.execute(f"SELECT embedding FROM {table_name} WHERE {crc32_column} = %s AND model_name = %s",
170
+ (crc32_value, model_name))
171
  result = cur.fetchone()
172
  if result and result[0]:
173
  # Нормализуем эмбеддинг после извлечения из БД
 
183
  normalized_embedding = normalize(embedding.reshape(1, -1))[0]
184
  with conn.cursor() as cur:
185
  cur.execute(f"""
186
+ INSERT INTO {table_name}
187
+ (movie_id, embedding_crc32, string_crc32, model_name, embedding)
188
+ VALUES (%s, %s, %s, %s, %s)
189
+ ON CONFLICT (movie_id) DO NOTHING
190
  """, (movie_id, embedding_crc32, string_crc32, model_name, normalized_embedding.tolist()))
191
  conn.commit()
192
  return True
 
198
  def process_movies():
199
  """Обрабатывает фильмы, создавая для них эмбеддинги."""
200
  global processing_complete
201
+
202
  logging.info("Начало обработки фильмов.")
203
+
204
  # Получаем список фильмов, которые нужно обработать
205
  movies_to_process = get_movies_without_embeddings()
206
+
207
  if not movies_to_process:
208
  logging.info("Все фильмы уже обработаны.")
209
  processing_complete = True
 
236
  break
237
 
238
  logging.info(f"Обработка пакета из {len(batch)} фильмов...")
239
+
240
  for movie in batch:
241
+ embedding_string = f"Название: {movie['name']}\nГод: {movie['year']}\nЖанры: {movie['genresList']}\nОписание: {movie['description']}"
242
  string_crc32 = calculate_crc32(embedding_string)
243
 
244
  # Проверяем существующий эмбеддинг
245
  existing_embedding = get_embedding_from_db(conn, embeddings_table, "string_crc32", string_crc32, model_name)
246
+
247
  if existing_embedding is None:
248
  embedding = encode_string(embedding_string)
249
  embedding_crc32 = calculate_crc32(str(embedding.tolist()))
250
+
251
  if insert_embedding(conn, embeddings_table, movie['id'], embedding_crc32, string_crc32, embedding):
252
  logging.info(f"Сохранен эмбеддинг для '{movie['name']}'")
253
  else:
254
  logging.error(f"Ошибка сохранения эмбеддинга для '{movie['name']}'")
255
  else:
256
  logging.info(f"Эмбеддинг для '{movie['name']}' уже существу��т")
 
257
  except Exception as e:
258
  logging.error(f"Ошибка при обработке фильмов: {e}")
259
  finally:
 
266
  movie_embeddings = {}
267
  try:
268
  with conn.cursor() as cur:
269
+ cur.execute(f"SELECT movie_id, embedding FROM {embeddings_table}")
270
+ for movie_id, embedding in cur.fetchall():
271
+ # Находим название фильма по ID
272
+ for movie in movies_data:
273
+ if movie['id'] == movie_id:
274
+ movie_embeddings[movie['name']] = normalize(np.array(embedding).reshape(1, -1))[0]
275
+ break
 
 
276
  logging.info(f"Загружено {len(movie_embeddings)} эмбеддингов фильмов.")
277
  except Exception as e:
278
  logging.error(f"Ошибка при загрузке эмбеддингов фильмов: {e}")
 
282
  """Выполняет поиск фильмов по запросу."""
283
  global search_in_progress
284
  search_in_progress = True
285
+ start_time = time.time()
286
 
287
  try:
288
  conn = get_db_connection()
289
  if conn is None:
290
+ return "<p>Ошибка подключения к базе данных</p>"
291
+
292
+ query_crc32 = calculate_crc32(query)
293
+ query_embedding = get_embedding_from_db(conn, query_cache_table, "query_crc32", query_crc32, model_name)
294
+
295
+ if query_embedding is None:
296
+ query_embedding = encode_string(query)
297
+
298
+ try:
299
+ with conn.cursor() as cur:
300
+ cur.execute(f"""
301
+ INSERT INTO {query_cache_table} (query_crc32, query, model_name, embedding)
302
+ VALUES (%s, %s, %s, %s)
303
+ ON CONFLICT (query_crc32) DO NOTHING
304
+ """, (query_crc32, query, model_name, query_embedding.tolist()))
305
+ conn.commit()
306
+ logging.info(f"Сохранен новый эмбеддинг запроса: {query}")
307
+ except Exception as e:
308
+ logging.error(f"Ошибка при сохранении эмбеддинга запроса: {e}")
309
+ conn.rollback()
310
+
311
+ # Используем косинусное расстояние для поиска
312
+ try:
313
  with conn.cursor() as cur:
314
+ cur.execute(f"""
315
+ WITH query_embedding AS (
316
+ SELECT embedding
317
+ FROM {query_cache_table}
318
+ WHERE query_crc32 = %s
319
+ )
320
+ SELECT m.movie_id, 1 - (m.embedding <=> (SELECT embedding FROM query_embedding)) as similarity
321
+ FROM {embeddings_table} m, query_embedding
322
+ ORDER BY similarity DESC
323
+ LIMIT %s
324
+ """, (query_crc32, top_k))
325
+
326
+ results = cur.fetchall()
327
+ logging.info(f"Найдено {len(results)} результатов поиска.")
328
+ except Exception as e:
329
+ logging.error(f"Ошибка при выполнении поискового запроса: {e}")
330
+ results = []
331
+
332
+ output = ""
333
+ for movie_id, similarity in results:
334
+ # Находим фильм по ID
335
+ movie = next((m for m in movies_data if m['id'] == movie_id), None)
336
+ if movie:
337
+ output += f"<h3>{movie['name']} ({movie['year']})</h3>\n"
338
+ output += f"<p><strong>Жанры:</strong> {', '.join(movie['genresList'])}</p>\n"
339
+ output += f"<p><strong>Описание:</strong> {movie['description']}</p>\n"
340
+ output += f"<p><strong>Релевантность:</strong> {similarity:.4f}</p>\n"
341
+ output += "<hr>\n"
342
+
343
+ search_time = time.time() - start_time
344
+ logging.info(f"Поиск выполнен за {search_time:.2f} секунд.")
345
+
346
+ return f"<p>Время поиска: {search_time:.2f} сек</p>{output}"
347
+
348
  except Exception as e:
349
+ logging.error(f"Ошибка при выполнении поиска: {e}")
350
+ return "<p>Произошла ошибка при выполнении поиска.</p>"
351
+
352
  finally:
353
  if conn:
354
  conn.close()
355
  search_in_progress = False
356
 
357
+ # Запускаем обработку фильмов в отдельном потоке
358
+ processing_thread = threading.Thread(target=process_movies)
359
+ processing_thread.start()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
 
361
  # Создаем интерфейс Gradio
362
  iface = gr.Interface(
363
+ fn=search_movies,
364
+ inputs=gr.Textbox(lines=2, placeholder="Введите запрос для поиска фильмов..."),
365
+ outputs=gr.HTML(label="Результаты поиска"),
366
+ title="Семантический поиск фильмов",
367
+ description="Введите описание фильма, который вы ищете, и система найдет наиболее похожие фильмы."
368
  )
369
 
370
  # Запускаем интерфейс