Tính năng tìm kiếm ngữ nghĩa (semantic search)

Tính năng tìm kiếm ngữ nghĩa (semantic search)

Tin ngắn hàng ngày dành cho bạn
  • Cảm ơn threads.net của nhà Meta vì nó là nguồn cảm hứng cho mình tạo ra chuyên mục này trên blog. Ban đầu hơi nghi ngờ về việc liệu tạo ra các bài viết ngắn như thế này có thu hút được người dùng, có ai ngày qua ngày quay trở lại đọc không, hay tất cả chỉ như dã tràng xe cát? Như mình đã nói rất nhiều là làm ra một tính năng không khó, nhưng vận hành nó làm sao cho hiệu quả mới là điều cần phải bận tâm.

    Giờ đây thời gian đã chứng minh tất cả. Chuyên mục Bài viết ngắn luôn đứng trong tốp 5 trang có lượt truy cập nhiều nhất trong ngày/tuần/tháng. Điều đó có nghĩa bạn đọc đã có thói quen quay trở lại nhiều hơn. Tại sao mình lại khẳng định như thế? Vì chuyên mục này gần như không hề được SEO trên các công cụ tìm kiếm như Google.

    Lại kể về thời xa xưa một chút. Thời gian đầu mình rất chịu khó đăng bài trên threads.net với hy vọng thu hút được nhiều người theo dõi, để từ đó khéo léo giới thiệu họ trở thành người dùng blog của mình. Nhưng càng về sau càng thấy "đuối" vì thuật toán của Threads ngày càng không phù hợp với định hướng của mình. Hay nói cách khác là nội dung tạo ra không ăn khách.

    Ví dụ các bài viết của mình thường mang khuynh hướng chia sẻ thông tin, tin tức, hoặc kinh nghiệm cá nhân rút ra sau khi học hoặc làm một cái gì đó. Dường như những bài viết như vậy không được đánh giá cao và thường bị chôn vùi chỉ sau hơn... 100 lượt xem. Hmm... Liệu vấn đề có phải là do mình? Biết thế sao không chịu thay đổi nội dung theo hướng phù hợp hơn với nền tảng?

    Mình đã quan sát Threads, các nội dung dễ lan toả nhất là có yếu tố gây tranh cãi hoặc một định kiến về vấn đề gì đó, đôi khi chỉ đơn giản là phát biểu "ngây ngô" một vấn đề gì đó mà họ biết chắc chắn có tương tác. Mà mình thì gần như là không hề thích định hướng người dùng theo nội dung kiểu này. Mọi người có thể bảo mình bảo thủ, mình chấp nhận. Mỗi người có định hướng nội dung và khán giả khác nhau, lựa chọn nằm ở họ.

    Thế là từ đó mình chủ yếu viết trên này. Chỉ thi thoảng có phát hiện hay lắm thì mới lên Threads "khoe". Ở đây hàng ngày vẫn có người vào đọc, dù cho bạn là ai thì mình tin chắc rằng các bạn nhận ra được thông điệp mà mình muốn truyền tải thông qua mỗi bài viết. Ít nhất chúng ta có chung một định hướng về nội dung. Đôi khi điều sợ nhất không phải là viết ra không ai đọc, mà là họ đọc xong rồi lãng quên trong phút chốc. Số lượng là quan trọng, nhưng chất lượng mới là thứ mang chúng ta lại gần nhau hơn.

    Cảm ơn tất cả 🤓

    » Xem thêm
  • Zed chắc là cộng đồng những nhà phát triển chịu khó lắng nghe người dùng nhất quả đất. Mới đây họ thêm tuỳ chọn để tắt tất tần tật tính năng AI có trong Zed. Trong khi nhiều bên khác đang muốn tích hợp sâu hơn và làm nhiều hơn với AI Agent. Quả là một nước đi táo bạo 🤔

    You Can Now Disable All AI Features in Zed

    » Xem thêm
  • Hôm nay mình đã cố gắng đi hẳn 8k bước trong một phiên để đo lường cho các bạn thấy. Quả là không ngoài dự đoán khi thời gian đi lên đến hơn 1 giờ và quãng đường ~6km 🤓

    À vài hôm nữa là hết tháng, tức là cũng tròn 1 tháng mình bắt đầu thói quen đi bộ mỗi ngày với mục tiêu 8k bước. Để đầu tháng sau mình tổng kết lại xem thế nào luôn ha.

    » Xem thêm

Vấn đề

Xin chào độc giả của 2coffee.dev. Các bạn ở Hà Nội có thấy tuần vừa rồi không khí mùa thu đã trở nên rõ rệt hơn không? Buổi sáng trời mát lạnh còn chiều tối thì đi kèm với những cơn gió lớn. Nhưng đằng sau đó lại là một tuần bận rộn đối với tôi. Vừa tập trung vào chạy "deadline" cho dự án ở công ty, tối về tranh thủ hoàn thiện chức năng tìm kiếm cho blog. Cái deadline này khác hẳn với mọi khi vì là tính năng chủ lực của năm cho sản phẩm. Còn về blog, tính năng tìm kiếm sớm hay muộn gì cũng phải hoàn thành, và đây là thời điểm thích hợp để làm điều đó.

Trước khi chuyển sang Fresh, blog vốn đã có tính năng tìm kiếm. Cách làm lúc đó là sử dụng fulltext-search của postgres. Nói thêm cho bạn đọc chưa biết, trước khi dùng postgres thì tôi đã dùng cả redisearch để phục vụ tìm kiếm. Nhìn chung thì postgres vẫn cho ra kết quả tốt hơn trong khi redisearch thì phức tạp hơn. Mà thực tế thì dữ liệu bài viết không nhiều đến mức để redisearch phát huy được hết tác dụng.

Thời điểm chuyển sang Fresh, lúc đó AI đang bùng nổ. Rất nhiều ánh nhìn đổ sang phía AI và cả những gì mà nó làm được. Sau khi hoàn thành những chức năng cơ bản đến bước chuẩn bị làm chức năng tìm kiếm thì tôi chợt nghĩ: "Hay là mình cũng thử ứng dụng AI!?". Thế là tôi quyết định "release" phiên bản blog mới mà không có chức năng tìm kiếm.

Để làm được tính năng tìm kiếm có sự góp mặt của AI thì tôi phải dành thời gian nghiên cứu cũng như thử nghiệm rất nhiều. Tìm hiểu về cách thức thực hiện, cách sử dụng các mô hình LLMs, mô hình embeddings, kiểu dữ liệu vector, cách biến dữ liệu thành vector và cả cách truy vấn...

Nói một cách ngắn gọn, vector là một tập hợp hữu hạn của các con số như trong toán học, số lượng của các con số đó tạo thành kích thước (dimension) của vector. Kích thước càng lớn, vector càng có khả năng tổng quát hóa dữ liệu mà nó biểu thị. Để biến một dữ liệu thông thường (văn bản, giọng nói, hình ảnh...) sang vector thì có nhiều cách, nhưng nhờ sự phổ biến của các mô hình LLMs hiện nay mà chỉ cần dưa dữ liệu vào một mô hình embeddings nào đó thì sẽ cho ra dữ liệu vector.

Lại nói về tìm kiếm ngữ nghĩa (semantic) nó khác với tìm kiếm theo keyword (fulltext) truyền thống. Tìm kiếm fulltext sẽ dựa vào lượng ký tự văn bản nhập vào để so khớp và cho ra những đoạn có chứa nhiều hoặc khớp nhất với từ khoá. Trong khi tìm kiếm ngữ nghĩa lại theo khuynh hướng nội dung. Giả sử bài viết của bạn đang giải thích cách hoạt động của node.js thì khi tìm kiếm theo cụm từ "node.js hoạt động như thế nào?" khả năng cao semantic sẽ tìm thấy được bài viết này. Trong khi đó fulltext sẽ cố tìm xem bài viết nào có chứa các từ "node.js", "hoạt", "động", "như"... Tất nhiên là không đến mức đó vì các thuật toán fulltext đã có thể loại bỏ được những từ "thừa" hoặc gom nhóm những từ đồng nghĩa...

Để truy vấn dữ liệu vector cần tối thiểu 2 bước. Đầu tiên là biến câu truy vấn thành vector, sau đó sử dụng các hàm truy vấn. Ví dụ với pg-vector - là một extensions hỗ trợ vector cho Postgres có các hàm truy vấn:

pg-vector search

Bạn đọc có thể nhìn thấy L2 distance, Cosine distance, L1 distance... là các dạng so sánh vector. Tuỳ vào trường hợp sử dụng mà lựa chọn kiểu truy vấn cho hợp lý. Ví dụ như trong bài toán tìm kiếm, tôi lựa chọn kiểu Cosine distance - tức là hình dạng của 2 vector càng giống nhau thì càng tốt.

Cách làm

Flow

Đầu tiên là lựa chọn cơ sở dữ liệu phù hợp. Tôi đang dùng Turso làm cơ sở dữ liệu chính. Tuy nhiên Turso hoạt động dựa trên SQLite, không tối ưu cho dữ liệu dạng vector. Mặc dù họ có giới thiệu một extension để hỗ trợ cho vecter nhưng hơi phức tạp.

pg-vector thì ngược lại, được nhiều người sử dụng. Đây là một extensions dành cho Postgres. Nhắc đến Postgres thì lại nghĩ ngay đến Supabase - cho dùng miễn phí. Supabase tích hợp sẵn pg-vector, kích hoạt chỉ bằng một nút bấm nên nó là một sự lựa chọn tuyệt vời.

Bước tiếp theo là chọn models. Để tiết kiệm thì ngay từ đầu tôi đã tìm những models miễn phí, hoặc các dịch vụ cung cấp models miễn phí. Không thể không nhắc đến groq với các API Completions. Tuy nhiên groq không có các mô hình embeddings nên lại phải tìm một cái khác.

nomic-embed-text là một embeddings tìm thấy trong thư viện của Ollama. Nó có khả năng vector hoá văn bản. Ngoài ra Nomic cũng cung cấp API embeddings miễn phí kèm giới hạn. Nhưng cũng phải nhắc lại rằng nomic không hẳn là một mô hình đa ngôn ngữ, nó hỗ trợ tiếng Việt một cách hạn chế. Nên rất có thể vector cho ra không được tối ưa hoá theo ngữ nghĩa tiếng Việt.

Sau khi đã chuẩn bị xong các bước trên thì đến lượt viết mã để thêm dữ liệu vector và logic tìm kiếm.

Đầu tiên, cần chuyển nội dung bài viết thành vector và lưu vào Supabase. Ở đây thay vì chuyển toàn bộ nội dung bài viết thì tôi qua một bước tóm tắt lại nội dung chính của bài viết rồi mới đưa vào nomic-embed-text. Điều này giúp loại bỏ những ý phụ hoạ, tập trung vào nội dung chính và giảm lượng token đầu vào cho mô hình xử lý.

Một lưu ý nữa là mặc dù các models trên có API miễn phí nhưng chúng luôn đi kèm với giới hạn. Quá trình xử lý dữ liệu lần đầu rất tốn kém vì tôi có hơn 400 bài viết cả tiếng Việt lẫn tiếng Anh. Nên cách tốt hơn là chạy mô hình Llama 3.2 3B và nomic-embed-text ở dưới local. Ở đây sử dụng LM Studio.

Logic tìm kiếm cũng đơn giản. Nhận câu truy vấn của người dùng -> đi qua nomic-embed-text để thành vector -> truy vấn cosin với vector bài viết và sắp xếp theo khoảng cách gần nhất giữa 2 vector.

Tuy vậy, nếu người dùng tìm kiếm theo từ khoá ví dụ như node.js, javascript... thì nhiều khả năng semantic sẽ không cho ra kết quả vì dữ liệu quá ngắn, vector tạo ra không mang đủ ý, khiến cho khoảng cách cosin trở nên quá xa. Vậy nên để xử lý trường hợp này phải duy trì thêm một phong cách tìm kiếm fulltext. May mắn là Supabase cũng hỗ trợ loại tìm kiếm này.

Thách thức

Kể ra thì có vẻ đơn giản, tuy nhiên điều khó nhất với tôi là các bước tiền xử lý dữ liệu.

Một bài viết thường biểu đạt nhiều nội dung, có nội dung chính cả nội dung phụ hoạ. Thông thường thì người tìm kiếm chỉ quan tâm đến nội dung chính của bài viết và họ có khuynh hướng tìm kiếm những thứ liên quan đến nội dung chính. Nếu như chuyển đổi toàn bộ nội dung của bài viết thành vector sẽ làm "loãng" hoặc "nhiễu" vì kích thước của vector là có hạn. Tôi nghĩ rằng nếu loại bỏ được yếu tố phụ hoạ và tăng cường ý chính thì tìm kiếm sẽ chuẩn xác hơn. Tưởng tượng, một bài viết 1500 từ chuyển sang vector 1024 chiều, so với một bài chỉ chứa nội dung chính 500 từ cũng trong vector đó thì cái nào biểu thị "sắc nét" hơn?

Quy tắc tìm kiếm của người dùng cũng rất khó đoán vì mỗi người có cách tìm kiếm khác nhau. Có người thích ngắn gọn, cũng có người thích viết dài hoặc có người thích cung cấp cả ngữ cảnh cho câu hỏi... vì thế xử lý dữ liệu nhập vào của người dùng cũng là một thách thức, làm sao để chuyển đổi nó thành một câu hỏi ngắn gọn mà đủ ý và phù hợp với nội dung tìm kiếm trong blog.

Chất lượng mô hình AI sử dụng cũng là một vấn đề. Thường thì model nào được đào tạo nhiều thì càng tốt, cũng như model được thương mại hoá sẽ đi kèm với chất lượng. Nhưng vì giảm thiểu chi phí, hiện tại tôi đang sử dụng các mô hình LLMs miễn phí đi kèm với nhiều giới hạn. Hy vọng một ngày nào đó tôi sẽ tích hợp được các models mạnh mẽ hơn để tăng chất lượng tìm kiếm cho blog của mình.

Cao cấp
Hello

Tôi & khao khát "chơi chữ"

Bạn đã thử viết? Và rồi thất bại hoặc chưa ưng ý? Tại 2coffee.dev chúng tôi đã có quãng thời gian chật vật với công việc viết. Đừng nản chí, vì giờ đây chúng tôi đã có cách giúp bạn. Hãy bấm vào để trở thành hội viên ngay!

Bạn đã thử viết? Và rồi thất bại hoặc chưa ưng ý? Tại 2coffee.dev chúng tôi đã có quãng thời gian chật vật với công việc viết. Đừng nản chí, vì giờ đây chúng tôi đã có cách giúp bạn. Hãy bấm vào để trở thành hội viên ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.

Bình luận (0)

Nội dung bình luận...