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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +137 -157
app.py CHANGED
@@ -7,6 +7,7 @@ import queue
7
  import torch
8
  import psycopg2
9
  import zlib
 
10
  from urllib.parse import urlparse
11
 
12
  # Настройки базы данных PostgreSQL
@@ -46,8 +47,6 @@ except FileNotFoundError:
46
 
47
  # Очередь для необработанных фильмов
48
  movies_queue = queue.Queue()
49
- for movie in movies_data:
50
- movies_queue.put(movie)
51
 
52
  # Флаг, указывающий, что обработка фильмов завершена
53
  processing_complete = False
@@ -71,7 +70,7 @@ def get_db_connection():
71
  return None
72
 
73
  def setup_database():
74
- """Настраивает базу данных: создает расширение, таблицы и триггер."""
75
  conn = get_db_connection()
76
  if conn is None:
77
  return
@@ -83,57 +82,32 @@ def setup_database():
83
  # Создаем таблицу для хранения эмбеддингов фильмов
84
  cur.execute(f"""
85
  CREATE TABLE IF NOT EXISTS {embeddings_table} (
86
- movie_id INTEGER,
87
- embedding_crc32 BIGINT PRIMARY KEY,
88
  string_crc32 BIGINT,
89
  model_name TEXT,
90
- embedding vector(1024)
91
  );
 
92
  """)
93
 
94
- # Создаем таблицу для кэширования эмбеддингов запросов
95
  cur.execute(f"""
96
  CREATE TABLE IF NOT EXISTS {query_cache_table} (
97
  query_crc32 BIGINT PRIMARY KEY,
98
  query TEXT,
99
  model_name TEXT,
100
- embedding vector(1024),
101
  created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
102
  );
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()
110
- RETURNS TRIGGER AS $$
111
- DECLARE
112
- table_size BIGINT;
113
- row_to_delete RECORD;
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};
121
- END LOOP;
122
- END IF;
123
- RETURN NEW;
124
- END;
125
- $$ LANGUAGE plpgsql;
126
-
127
- CREATE OR REPLACE TRIGGER trg_manage_query_cache_size
128
- AFTER INSERT ON {query_cache_table}
129
- FOR EACH ROW
130
- EXECUTE PROCEDURE manage_query_cache_size();
131
  """)
132
 
133
  conn.commit()
134
  conn.close()
135
 
136
- # Настраиваем базу данных при запуске приложения
137
  setup_database()
138
 
139
  def calculate_crc32(text):
@@ -144,44 +118,78 @@ def encode_string(text):
144
  """Кодирует строку в эмбеддинг."""
145
  return model.encode(text, convert_to_tensor=True, normalize_embeddings=True)
146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  def get_embedding_from_db(conn, table_name, crc32_column, crc32_value, model_name):
148
- """
149
- Пытается получить эмбеддинг из указанной таблицы по CRC32.
150
- Возвращает эмбеддинг, если найден, иначе None.
151
- """
152
  with conn.cursor() as cur:
153
- cur.execute(f"SELECT embedding FROM {table_name} WHERE {crc32_column} = %s AND model_name = %s", (crc32_value, model_name))
 
154
  result = cur.fetchone()
155
- if result:
156
- return torch.tensor(result[0])
157
- else:
158
- return None
159
-
160
- def insert_embedding(conn, table_name, crc32_column, crc32_value, other_columns, embedding):
161
- """Вставляет эмбеддинг в указанную таблицу."""
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:
176
- print(f"Ошибка при вставке эмбеддинга в таблицу {table_name}: {e}")
177
  conn.rollback()
178
  return False
179
 
180
  def process_movies():
181
- """
182
- Обрабатывает фильмы из очереди, создавая для них эмбеддинги и сохраняя их в базу данных.
183
- """
184
  global processing_complete
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  conn = get_db_connection()
186
  if conn is None:
187
  processing_complete = True
@@ -189,136 +197,108 @@ def process_movies():
189
 
190
  while True:
191
  if search_in_progress:
192
- time.sleep(1) # Ждем, пока поиск не завершится
193
  continue
194
 
195
  batch = []
196
  while not movies_queue.empty() and len(batch) < batch_size:
197
  try:
198
- movie = movies_queue.get(timeout=1)
199
  batch.append(movie)
200
  except queue.Empty:
201
  break
202
 
203
  if not batch:
204
- print("Очередь фильмов пуста.")
205
- processing_complete = True
206
  break
207
 
208
- titles = [movie["name"] for movie in batch]
209
- embedding_strings = [
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:
232
- print(f"Ошибка сохранения эмбеддинга для фильма '{movie['name']}'.")
233
  else:
234
- print(f"Эмбеддинг для фильма '{movie['name']}' уже существует в базе данных.")
 
 
235
 
236
  conn.close()
237
- print("Обработка фильмов завершена.")
 
238
 
239
  def get_movie_embeddings(conn):
240
  """Загружает все эмбеддинги фильмов из базы данных."""
241
  movie_embeddings = {}
242
  with conn.cursor() as cur:
243
- cur.execute(f"SELECT movie_id, embedding FROM {embeddings_table}")
244
- rows = cur.fetchall()
245
- for row in rows:
246
- movie_id, embedding = row
247
- # Находим название фильма по его ID
 
248
  for movie in movies_data:
249
  if movie['id'] == movie_id:
250
- title = movie["name"]
251
- movie_embeddings[title] = torch.tensor(embedding)
252
  break
253
  return movie_embeddings
254
 
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)
 
7
  import torch
8
  import psycopg2
9
  import zlib
10
+ import numpy as np
11
  from urllib.parse import urlparse
12
 
13
  # Настройки базы данных PostgreSQL
 
47
 
48
  # Очередь для необработанных фильмов
49
  movies_queue = queue.Queue()
 
 
50
 
51
  # Флаг, указывающий, что обработка фильмов завершена
52
  processing_complete = False
 
70
  return None
71
 
72
  def setup_database():
73
+ """Настраивает базу данных: создает расширение, таблицы и индексы."""
74
  conn = get_db_connection()
75
  if conn is None:
76
  return
 
82
  # Создаем таблицу для хранения эмбеддингов фильмов
83
  cur.execute(f"""
84
  CREATE TABLE IF NOT EXISTS {embeddings_table} (
85
+ movie_id INTEGER PRIMARY KEY,
86
+ embedding_crc32 BIGINT,
87
  string_crc32 BIGINT,
88
  model_name TEXT,
89
+ embedding float8[]
90
  );
91
+ CREATE INDEX IF NOT EXISTS idx_movie_embeddings_crc32 ON {embeddings_table} (string_crc32);
92
  """)
93
 
94
+ # Создаем таблицу для кэширования запросов
95
  cur.execute(f"""
96
  CREATE TABLE IF NOT EXISTS {query_cache_table} (
97
  query_crc32 BIGINT PRIMARY KEY,
98
  query TEXT,
99
  model_name TEXT,
100
+ embedding float8[],
101
  created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
102
  );
103
+ CREATE INDEX IF NOT EXISTS idx_query_cache_crc32 ON {query_cache_table} (query_crc32);
104
+ CREATE INDEX IF NOT EXISTS idx_query_cache_created ON {query_cache_table} (created_at);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  """)
106
 
107
  conn.commit()
108
  conn.close()
109
 
110
+ # Настраиваем базу данных при запуске
111
  setup_database()
112
 
113
  def calculate_crc32(text):
 
118
  """Кодирует строку в эмбеддинг."""
119
  return model.encode(text, convert_to_tensor=True, normalize_embeddings=True)
120
 
121
+ def get_movies_without_embeddings():
122
+ """Получает список фильмов, для которых нужно создать эмбеддинги."""
123
+ conn = get_db_connection()
124
+ if conn is None:
125
+ return []
126
+
127
+ movies_to_process = []
128
+ with conn.cursor() as cur:
129
+ # Получаем список ID фильмов, которые уже есть в базе
130
+ cur.execute(f"SELECT movie_id FROM {embeddings_table}")
131
+ existing_ids = {row[0] for row in cur.fetchall()}
132
+
133
+ # Фильтруем только те фильмы, которых нет в базе
134
+ for movie in movies_data:
135
+ if movie['id'] not in existing_ids:
136
+ movies_to_process.append(movie)
137
+
138
+ conn.close()
139
+ return movies_to_process
140
+
141
+ def vector_to_list(vector):
142
+ """Преобразует вектор PyTorch в список float."""
143
+ return vector.detach().cpu().numpy().tolist()
144
+
145
+ def list_to_vector(lst):
146
+ """Преобразует список float в вектор PyTorch."""
147
+ return torch.tensor(lst)
148
+
149
  def get_embedding_from_db(conn, table_name, crc32_column, crc32_value, model_name):
150
+ """Получает эмбеддинг из базы данных."""
 
 
 
151
  with conn.cursor() as cur:
152
+ cur.execute(f"SELECT embedding FROM {table_name} WHERE {crc32_column} = %s AND model_name = %s",
153
+ (crc32_value, model_name))
154
  result = cur.fetchone()
155
+ if result and result[0]:
156
+ return list_to_vector(result[0])
157
+ return None
158
+
159
+ def insert_embedding(conn, table_name, movie_id, embedding_crc32, string_crc32, embedding):
160
+ """Вставляет эмбеддинг в базу данных."""
161
+ embedding_list = vector_to_list(embedding)
 
 
 
 
162
  with conn.cursor() as cur:
163
  try:
164
  cur.execute(f"""
165
+ INSERT INTO {table_name}
166
+ (movie_id, embedding_crc32, string_crc32, model_name, embedding)
167
+ VALUES (%s, %s, %s, %s, %s)
168
+ ON CONFLICT (movie_id) DO NOTHING
169
+ """, (movie_id, embedding_crc32, string_crc32, model_name, embedding_list))
170
  conn.commit()
171
  return True
172
  except Exception as e:
173
+ print(f"Ошибка при вставке эмбеддинга: {e}")
174
  conn.rollback()
175
  return False
176
 
177
  def process_movies():
178
+ """Обрабатывает фильмы, создавая для них эмбеддинги."""
 
 
179
  global processing_complete
180
+
181
+ # Получаем список фильмов, которые нужно обработать
182
+ movies_to_process = get_movies_without_embeddings()
183
+
184
+ if not movies_to_process:
185
+ print("Все фильмы уже обработаны.")
186
+ processing_complete = True
187
+ return
188
+
189
+ # Добавляем фильмы в очередь
190
+ for movie in movies_to_process:
191
+ movies_queue.put(movie)
192
+
193
  conn = get_db_connection()
194
  if conn is None:
195
  processing_complete = True
 
197
 
198
  while True:
199
  if search_in_progress:
200
+ time.sleep(1)
201
  continue
202
 
203
  batch = []
204
  while not movies_queue.empty() and len(batch) < batch_size:
205
  try:
206
+ movie = movies_queue.get_nowait()
207
  batch.append(movie)
208
  except queue.Empty:
209
  break
210
 
211
  if not batch:
 
 
212
  break
213
 
214
+ print(f"Обработка пакета из {len(batch)} фильмов...")
 
 
 
 
 
 
215
 
216
+ for movie in batch:
217
+ embedding_string = f"Название: {movie['name']}\nГод: {movie['year']}\nЖанры: {movie['genresList']}\nОписание: {movie['description']}"
218
+ string_crc32 = calculate_crc32(embedding_string)
219
+
220
+ # Проверяем существующий эмбеддинг
221
+ existing_embedding = get_embedding_from_db(conn, embeddings_table, "string_crc32", string_crc32, model_name)
222
+
223
+ if existing_embedding is None:
224
+ embedding = encode_string(embedding_string)
225
+ embedding_crc32 = calculate_crc32(str(vector_to_list(embedding)))
226
 
227
+ if insert_embedding(conn, embeddings_table, movie['id'], embedding_crc32, string_crc32, embedding):
228
+ print(f"Сохранен эмбеддинг для '{movie['name']}'")
 
 
 
 
 
 
 
 
 
 
229
  else:
230
+ print(f"Ошибка сохранения эмбеддинга для '{movie['name']}'")
231
+ else:
232
+ print(f"Эмбеддинг для '{movie['name']}' уже существует")
233
 
234
  conn.close()
235
+ processing_complete = True
236
+ print("Обработка фильмов завершена")
237
 
238
  def get_movie_embeddings(conn):
239
  """Загружает все эмбеддинги фильмов из базы данных."""
240
  movie_embeddings = {}
241
  with conn.cursor() as cur:
242
+ cur.execute(f"""
243
+ SELECT e.movie_id, e.embedding
244
+ FROM {embeddings_table} e
245
+ """)
246
+ for movie_id, embedding in cur.fetchall():
247
+ # Находим название фильма по ID
248
  for movie in movies_data:
249
  if movie['id'] == movie_id:
250
+ movie_embeddings[movie['name']] = list_to_vector(embedding)
 
251
  break
252
  return movie_embeddings
253
 
254
  def search_movies(query, top_k=10):
255
+ """Выполняет поиск фильмов по запросу."""
 
 
 
 
 
 
 
256
  global search_in_progress
257
  search_in_progress = True
258
  start_time = time.time()
 
 
 
 
 
 
 
259
 
260
+ try:
261
+ conn = get_db_connection()
262
+ if conn is None:
263
+ return "<p>Ошибка подключения к базе данных</p>"
264
+
265
+ query_crc32 = calculate_crc32(query)
266
+ query_embedding = get_embedding_from_db(conn, query_cache_table, "query_crc32", query_crc32, model_name)
267
+
268
+ if query_embedding is None:
269
+ query_embedding = encode_string(query)
270
+ embedding_list = vector_to_list(query_embedding)
271
+
272
+ with conn.cursor() as cur:
273
+ cur.execute(f"""
274
+ INSERT INTO {query_cache_table} (query_crc32, query, model_name, embedding)
275
+ VALUES (%s, %s, %s, %s)
276
+ ON CONFLICT (query_crc32) DO NOTHING
277
+ """, (query_crc32, query, model_name, embedding_list))
278
+ conn.commit()
279
+
280
+ movie_embeddings = get_movie_embeddings(conn)
281
 
282
+ similarities = []
283
+ for title, movie_embedding in movie_embeddings.items():
284
+ similarity = util.pytorch_cos_sim(query_embedding, movie_embedding).item()
285
+ similarities.append((title, similarity))
286
+
287
+ similarities.sort(key=lambda x: x[1], reverse=True)
288
+ top_results = similarities[:top_k]
289
+
290
+ results_html = "<ol>"
291
+ for title, score in top_results:
292
+ results_html += f"<li><strong>{title}</strong> (Сходство: {score:.4f})</li>"
293
+ results_html += "</ol>"
294
+
295
+ search_time = time.time() - start_time
296
+ conn.close()
297
+
298
+ return f"<p>Время поиска: {search_time:.2f} сек</p>{results_html}"
299
 
300
+ finally:
301
+ search_in_progress = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
 
303
  # Запускаем обработку фильмов в отдельном потоке
304
  processing_thread = threading.Thread(target=process_movies)