Hệ thống RAG và TxtAi Phần 2: Cơ sở Dữ liệu và Chỉ mục Vector

Trong phần đầu, chúng ta đã thảo luận về cách tương tác với một mô hình để có được một cuộc đối thoại dựa trên thông tin mà nó chưa được huấn luyện. Tóm lại, chúng ta thêm vào ngữ cảnh các thông tin mong muốn. Nhưng giả sử bạn muốn sử dụng toàn bộ cơ sở dữ liệu kiến thức? Điều đó sẽ là quá nhiều thông tin để thêm vào ngữ cảnh. Để làm được điều này, chúng ta cần đưa toàn bộ thông tin mà chúng ta muốn cung cấp cho người dùng vào cơ sở dữ liệu. Chúng ta sẽ phân chia nội dung dưới dạng đoạn văn và áp dụng chỉ mục vector. Chỉ mục này chuyển đổi văn bản thành các vector số học biểu diễn ý nghĩa ngữ nghĩa của từ và cụm từ. Điều tuyệt vời là điều này cho phép thực hiện tìm kiếm dựa trên ý nghĩa thay vì chỉ dựa trên các từ chính xác.

Trong số các cơ sở dữ liệu cho phép thực hiện loại tìm kiếm này, chúng ta có thể kể đến:

  1. Elasticsearch: Đây là một công cụ tìm kiếm và phân tích phân tán được sử dụng rộng rãi cho tìm kiếm văn bản. Elasticsearch có thể được sử dụng để tạo chỉ mục vector nhờ vào các plugin và tích hợp của nó, đặc biệt là cho việc xử lý ngôn ngữ tự nhiên.

  2. Faiss (Facebook AI Similarity Search): Phát triển bởi Facebook AI, Faiss là một thư viện cho phép phân loại index vector một cách hiệu quả và tìm kiếm sự tương đồng. Nó rất phù hợp để quản lý các bộ sưu tập vector lớn và thường được sử dụng trong các hệ thống đề xuất và tìm kiếm ngữ nghĩa.

  3. Milvus: Milvus là một hệ thống quản lý cơ sở dữ liệu vector mã nguồn mở. Nó được thiết kế để quản lý chỉ mục vector quy mô lớn và tương thích với nhiều mô hình học máy, bao gồm những mô hình được sử dụng cho xử lý ngôn ngữ tự nhiên.

  4. Pinecone: Pinecone là một cơ sở dữ liệu vector được thiết kế cho các ứng dụng học máy. Nó cung cấp quản lý và tìm kiếm vector quy mô lớn, hữu ích cho các ứng dụng tìm kiếm ngữ nghĩa và NLP.

  5. Weaviate: Weaviate là một cơ sở dữ liệu kiến thức vector, cho phép lưu trữ dữ liệu dưới dạng vector. Nó hỗ trợ các truy vấn ngữ nghĩa và được tối ưu hoá cho các trường hợp sử dụng liên quan đến xử lý ngôn ngữ tự nhiên.

  6. Annoy (Approximate Nearest Neighbors Oh Yeah): Annoy là một thư viện C++ với các bindings Python được thiết kế để tìm kiếm láng giềng gần nhất trong các không gian chiều lớn. Nó được sử dụng để tạo chỉ mục vector và hiệu quả cho các truy vấn tìm kiếm nhanh.

  7. HNSW (Hierarchical Navigable Small World): HNSW là một thuật toán phổ biến cho việc tìm kiếm láng giềng gần nhất trong các không gian chiều cao. Một số hệ thống quản lý cơ sở dữ liệu tích hợp HNSW để tạo chỉ mục vector.

  8. Postgresql (pgvector): Postgresql sử dụng HNSW để tạo chỉ mục vector trong khuôn khổ tiện ích mở rộng pgvector.Bạn có thể tìm thấy một danh sách đầy đủ hơn tại địa chỉ này: https://js.langchain.com/docs/integrations/vectorstores Điều này đã mang lại khá nhiều khả năng.

Về phần lưu trữ, làm thế nào để chúng ta tạo ra các vector này? Chúng ta sử dụng những thứ gọi là 'embeddings'. Các embeddings biến đổi dữ liệu phức tạp, như từ ngữ, thành các vector trong không gian đa chiều. Ví dụ, mỗi từ trong một văn bản có thể được biểu diễn bằng một vector của 50, 100, 300 chiều hoặc nhiều hơn. Những vector này được thiết kế theo cách mà các từ có nghĩa giống nhau sẽ gần nhau trong không gian vector này. Ví dụ, 'vua' và 'hoàng hậu' sẽ gần nhau, phản ánh mối quan hệ ngữ nghĩa của chúng. Có các mô hình embeddings được tiền huấn luyện như Word2Vec, GloVe hay BERT, đã được huấn luyện trên những bộ sưu tập văn bản lớn và có thể được sử dụng để nhận được các embeddings từ ngữ chất lượng cao.

Bạn có thể tìm thấy danh sách các hệ thống embeddings khác nhau có sẵn tại địa chỉ này: https://js.langchain.com/docs/integrations/text_embedding

Quan trọng là phải hiểu rằng khi bạn sử dụng một mô hình để tạo embeddings của mình, bạn sẽ cần sử dụng cùng một mô hình để tạo các truy vấn của mình sau này. Việc duy trì sự nhất quán là cần thiết để tối đa hóa khả năng cho công cụ cơ sở dữ liệu so sánh chính xác khoảng cách giữa các vector của yêu cầu bạn tạo và những vector được lưu trữ trong cơ sở dữ liệu của bạn.

Tóm lại, bạn có trong cơ sở dữ liệu một loạt vector, và bạn sẽ thực hiện một truy vấn với một loạt vector khác. Cơ sở dữ liệu sẽ so sánh khoảng cách; khoảng cách càng nhỏ, ý nghĩa càng tương đồng.

Để nghiên cứu sâu hơn, tôi đã thấy những ví dụ trong bài viết này khá dễ hiểu: https://medium.com/@aravilliatchutaram/intent-classification-using-vector-database-txtai-sqlite-and-large-language-models-821f939b87ba

Nếu bạn muốn sử dụng một hệ thống khác biệt so với hệ thống mà txtai đề xuất mặc định, đây là một nguồn tài nguyên cuối cùng: https://neuml.hashnode.dev/customize-your-own-embeddings-database Ở đây, bạn sẽ tìm thấy ví dụ về việc tạo ra embeddings với NumPy, PyTorch, Faiss, HNSW và cả việc sử dụng API bên ngoài như Huggingface.

Cần lưu ý rằng txtai mặc định tạo vector với mô hình https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2 sử dụng Faiss như là index Ann backend.

Để tóm tắt:

  • Tạo một embedding
from txtai.embeddings import Embeddings

embeddings = Embeddings()
  • Tạo một embedding với một bản ghi trong cơ sở dữ liệu: SQLITE / all-MiniLM-L6-v2 / Faiss
from txtai.embeddings import Embeddings

embeddings = Embeddings(content=True)
  • Để tạo một embedding với Postgresql / gte-large / Faiss
pipenv shell
pipenv install psycopg2-binary
from datasets import load_dataset
import txtai

# Load dataset
ds = load_dataset("ag_news", split="train")

embeddings = txtai.Embeddings(
    content="postgresql+psycopg2://testuser:testpwd@localhost:5432/vectordb",
    objects=True,
    backend="faiss",
    path="thenlper/gte-large"
)

# indexer
embeddings.index(ds["text"])
# sauvegarder l'index
embeddings.save("./index")
# charger l'index sauvegardé
embeddings.load("./index")
## ou dans le cloud,compressé
embeddings.save("/path/to/save/index.tar.gz", cloud={...})

Trong ví dụ này, chúng ta lưu trữ nội dung trong cơ sở dữ liệu và tạo chỉ mục của chúng ta với FAISS. Điều này đòi hỏi bạn phải lưu trữ chỉ mục của bạn để có thể tải lại sau này. Bạn cũng có khả năng lưu trữ chúng trên đám mây hoặc trong Elasticsearch. Cần phải lưu ý nếu bạn thay đổi nội dung trong cơ sở dữ liệu, bạn sẽ cần phải tạo lại chỉ mục của mình.

import txtai

embeddings = txtai.Embeddings(
    content="postgresql+psycopg2://testuser:testpwd@localhost:5432/vectordb",
    objects=True,
    backend="faiss",
    path="thenlper/gte-large"
)


embeddings.reindex(path="sentence-transformers/all-MiniLM-L6-v2", backend="hnsw")
embeddings.save("./index")

chiến lược phân chia hoặc phân đoạn tài liệu của bạn

Có nhiều chiến lược khi nói đến việc tạo ra các phân đoạn dữ liệu (chunks) của bạn.1. Chunking Cố Định Kích Thước

Đây là phương pháp phổ biến và đơn giản nhất. Nó bao gồm việc xác định một số token cố định cho mỗi chunk, với sự chồng chéo giữa chúng có thể xảy ra để bảo toàn ngữ cảnh ý nghĩa.

  1. Chunking "Nhận Thức Nội Dung"

Cách tiếp cận này sử dụng bản chất của nội dung để phân đoạn một cách tinh vi hơn. Nó bao gồm:

  • Tách Câu: Sử dụng công cụ như NLTK hoặc spaCy để chia văn bản thành các câu, đảm bảo việc bảo toàn ngữ cảnh tốt hơn.

  • Chunking Đệ Quy: Chia văn bản thành các chunk nhỏ hơn một cách cấp bậc và lặp lại, sử dụng các dấu phân cách hoặc tiêu chí khác nhau cho đến khi đạt được kích thước hoặc cấu trúc của chunk mong muốn.

Những kỹ thuật này thay đổi tùy thuộc vào nội dung và mục tiêu ứng dụng, và việc lựa chọn phương pháp phù hợp phụ thuộc vào nhiều yếu tố, bao gồm bản chất nội dung, mô hình mã hóa được sử dụng, và mục tiêu của ứng dụng.

Cài Đặt Đơn Giản Cơ Sở Dữ Liệu PostgreSQL:

Không có gì bí ẩn ở đây, chúng ta tiến hành một cách đơn giản. Chúng ta sẽ sử dụng hình ảnh Docker cung cấp bởi nhà phát triển của mô-đun pgvector.

## docker-compose.yaml
services:
  db:
    hostname: db
    image: ankane/pgvector
    ports:
     - 5432:5432
    restart: always
    environment:
      - POSTGRES_DB=vectordb
      - POSTGRES_USER=testuser
      - POSTGRES_PASSWORD=testpwd
      - POSTGRES_HOST_AUTH_METHOD=trust
    volumes:
     - ./init.sql:/docker-entrypoint-initdb.d/init.sql
## init.sql
CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE IF NOT EXISTS embeddings (
  id SERIAL PRIMARY KEY,
  embedding vector,
  text text,
  created_at timestamptz DEFAULT now()
);
docker compose up -d

Vậy là chúng ta đã hoàn thành phần thứ hai này, phần nào mang tính lý thuyết. Tôi chỉ gợi ý qua các chủ đề, và bạn có thể hiểu rằng mỗi thành phần liên quan đều chứa đựng các cài đặt tinh chỉnh kỹ lưỡng. Vẻ đẹp của TxtAi nằm ở chỗ mọi thứ đều đơn giản và đã được cấu hình sẵn. Những ví dụ này không thực sự hữu ích nếu chúng ta muốn thiết lập một hệ thống đơn giản. Tuy nhiên, trong thực tế của một dự án, hiếm khi chúng ta sử dụng mọi thứ nguyên trạng. Đó là lý do tôi muốn cho thấy mỗi yếu tố có khả năng cung cấp gì thông qua khuôn khổ này. Mọi thứ đều có thể cấu hình, không có gì để bỏ qua cả. Điều tuyệt vời với khuôn khổ này, đặc biệt nếu bạn mới tiếp xúc với lĩnh vực này, là bạn có thể khám phá thông qua tài liệu hướng dẫn các khối xây dựng cần thiết cho một hệ thống như vậy. Mọi thứ có thể trở nên vô cùng phức tạp khi chúng ta muốn vượt ra khỏi khuôn khổ đã được thiết lập.

Trong phần thứ ba của loạt bài về hệ thống RAG, chúng ta sẽ đắm mình nhiều hơn vào công việc thực tế. Phần lý thuyết khó nhằn đã được đề cập, chúng ta có thể tập trung vào việc lập trình.