File size: 5,162 Bytes
f972c00
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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()