opex792 commited on
Commit
0365be0
·
verified ·
1 Parent(s): 50e2501

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +123 -134
app.py CHANGED
@@ -11,6 +11,7 @@ import numpy as np
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,27 +40,16 @@ logging.info("Модель загружена успешно.")
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,40 +72,49 @@ def setup_database():
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,33 +140,43 @@ def get_movies_without_embeddings():
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,10 +192,9 @@ def insert_embedding(conn, table_name, movie_id, embedding_crc32, string_crc32,
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,12 +206,10 @@ def insert_embedding(conn, table_name, movie_id, embedding_crc32, string_crc32,
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,24 +242,22 @@ def process_movies():
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,13 +270,15 @@ def get_movie_embeddings(conn):
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,89 +288,72 @@ def search_movies(query, top_k=10):
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> {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
  # Запускаем интерфейс
 
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
  # Имена таблиц
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
  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
  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
  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
  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
  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
  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
  """Выполняет поиск фильмов по запросу."""
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
  # Запускаем интерфейс