import os import pandas as pd import sqlite3 import faiss import numpy as np import pickle import logging from sentence_transformers import SentenceTransformer # --- Cấu hình Logging --- # Thiết lập hệ thống ghi log để theo dõi tiến trình một cách chi tiết logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) # --- Định nghĩa các đường dẫn và hằng số --- # Lấy đường dẫn tuyệt đối của thư mục chứa script này (scripts) SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) # Đi ngược lên một cấp để lấy thư mục gốc của project PROJECT_ROOT = os.path.dirname(SCRIPT_DIR) # Định nghĩa các đường dẫn CSDL và thư mục đầu ra DB_PATH = os.path.join(PROJECT_ROOT, 'data', 'processed', 'phongthuy.sqlite') OUTPUT_DIR = os.path.join(PROJECT_ROOT, 'data', 'processed') # Tên mô hình embedding mạnh mẽ cho tiếng Việt (chuyên cho retrieval) MODEL_NAME = 'bkai-foundation-models/vietnamese-bi-encoder' def main(): """ Hàm chính điều phối toàn bộ quá trình tạo embeddings cho vật phẩm phong thủy. """ logging.info("--- BẮT ĐẦU QUÁ TRÌNH TẠO EMBEDDINGS CHO VẬT PHẨM ---") # --- 1. Tải mô hình Sentence Transformer --- logging.info(f"Đang tải mô hình Sentence Transformer: '{MODEL_NAME}'...") try: model = SentenceTransformer(MODEL_NAME) logging.info("Tải mô hình thành công.") except Exception as e: logging.error(f"Không thể tải mô hình embedding. Lỗi: {e}") logging.error("Vui lòng kiểm tra kết nối mạng và tên mô hình.") return # --- 2. Kết nối và đọc dữ liệu từ SQLite --- logging.info(f"Đang kết nối tới CSDL tại: {DB_PATH}") try: conn = sqlite3.connect(DB_PATH) df = pd.read_sql_query("SELECT * FROM vat_pham_phong_thuy", conn) conn.close() logging.info(f"Đã đọc thành công {len(df)} vật phẩm từ CSDL.") except Exception as e: logging.error(f"Không thể đọc dữ liệu từ bảng 'vat_pham_phong_thuy'. Lỗi: {e}") return if df.empty: logging.warning("Bảng 'vat_pham_phong_thuy' không có dữ liệu. Dừng quá trình.") return # --- 3. Chuẩn bị Corpus và Dữ liệu tra cứu ngược (Metadata) --- logging.info("Đang chuẩn bị corpus để tạo embedding...") # Kết hợp các cột có giá trị ngữ nghĩa để tạo ra một văn bản đại diện phong phú # .fillna('') để xử lý các ô trống, tránh lỗi cols_to_combine = ['tenvatpham', 'tengoikhac', 'congdung_keywords', 'mota_truyenthuyet'] df['corpus'] = df[cols_to_combine].fillna('').astype(str).agg(' '.join, axis=1) # Lấy danh sách corpus và metadata theo đúng thứ tự corpus_list = df['corpus'].tolist() # Metadata này dùng để tra cứu ngược từ index của vector về tên vật phẩm metadata_list = [{'name': row['tenvatpham']} for index, row in df.iterrows()] logging.info(f"Đã tạo {len(corpus_list)} văn bản trong corpus.") # --- 4. Tạo Embeddings --- logging.info("Đang tạo embeddings cho toàn bộ corpus vật phẩm...") # model.encode sẽ trả về một numpy array corpus_embeddings = model.encode(corpus_list, convert_to_numpy=True, show_progress_bar=True) logging.info(f"Tạo embeddings thành công, shape: {corpus_embeddings.shape}") # --- 5. Xây dựng chỉ mục FAISS (sử dụng Cosine Similarity) --- logging.info("Đang xây dựng chỉ mục FAISS...") # Chuẩn hóa các vector (bước bắt buộc để IndexFlatIP hoạt động như Cosine Similarity) faiss.normalize_L2(corpus_embeddings) # Lấy số chiều của vector dimension = corpus_embeddings.shape[1] # Sử dụng IndexFlatIP, tối ưu cho việc tìm kiếm Cosine Similarity index = faiss.IndexFlatIP(dimension) # Thêm các vector đã được chuẩn hóa vào chỉ mục index.add(corpus_embeddings) logging.info(f"Đã xây dựng chỉ mục FAISS thành công với {index.ntotal} vectors.") # --- 6. Lưu các file kết quả --- # Đảm bảo thư mục đầu ra tồn tại os.makedirs(OUTPUT_DIR, exist_ok=True) index_path = os.path.join(OUTPUT_DIR, 'item.index') info_path = os.path.join(OUTPUT_DIR, 'item_info.pkl') logging.info(f"Đang lưu chỉ mục FAISS vào: {index_path}") faiss.write_index(index, index_path) logging.info(f"Đang lưu thông tin metadata vào: {info_path}") with open(info_path, 'wb') as f: pickle.dump(metadata_list, f) logging.info("--- HOÀN TẤT! Đã tạo và lưu thành công các file embedding cho vật phẩm. ---") if __name__ == "__main__": # Dòng này đảm bảo hàm main() chỉ chạy khi script được thực thi trực tiếp # từ command line bằng lệnh: python scripts/create_item_embeddings.py main()