Hệ khuyến nghị 2 — Collaborative Filtering

13 thg 7, 2026 2 lượt xem
#ai
#recommender-systems
#similarity
#collaborative-filtering

Từ bài trước đến bài này

Hệ khuyến nghị 1 — Tổng quan ta đã phác họa bức tranh lớn: một hệ khuyến nghị cần dữ liệu tương tác, một mô hình học pattern, và một tầng phục vụ để trả gợi ý theo thời gian thực. Bài này đào sâu họ thuật toán kinh điển nhất, cũng là nền móng của mọi thứ sau đó: Collaborative Filtering (CF).

CF trả lời một câu hỏi rất người: "Những người giống bạn thích gì?" Nếu hàng nghìn khách hàng có hành vi giống bạn đều mở gói tiết kiệm online sau khi dùng thẻ tín dụng, thì nhiều khả năng bạn cũng sẽ quan tâm gói đó — dù hệ thống không biết gì về nội dung của sản phẩm, chỉ nhìn vào ai đã tương tác với cái gì. Đây là điểm mạnh và cũng là ranh giới của CF.

Bối cảnh xuyên suốt: team dữ liệu NCB muốn xây một hệ gợi ý sản phẩm/dịch vụ tiếp theo cho khách hàng cá nhân — thẻ, tiết kiệm, vay tiêu dùng, bảo hiểm liên kết, ví điện tử. Ta chỉ có lịch sử hành vi giao dịch, không có nhiều mô tả nội dung sản phẩm được số hoá tốt. Đó chính là mảnh đất màu mỡ cho CF.

Ma trận user–item: điểm khởi đầu

Mọi thuật toán CF đều bắt đầu từ một ma trận R kích thước m × n: m người dùng (hàng), n mặt hàng/sản phẩm (cột). Ô R[u][i]mức độ tương tác của user u với item i.

Thẻ TDTiết kiệmVay TDBảo hiểm
KH A54··3
KH B45·2·
KH C··54·
KH D54···

Dấu ·ô trống — user chưa tương tác với item đó. Nhiệm vụ của CF chính là điền vào các ô trống: dự đoán mức độ user sẽ thích item họ chưa gặp, rồi xếp hạng và gợi ý top-N ô có điểm dự đoán cao nhất.

Có hai loại tín hiệu điền vào ma trận:

  • Explicit feedback: user chấm điểm rõ ràng (đánh giá 1–5 sao, like/dislike). Sạch nhưng hiếm — ngân hàng gần như không có.
  • Implicit feedback: suy ra từ hành vi (đã mua, số lần dùng, số tiền giao dịch, click). Nhiều nhưng nhiễu — "không tương tác" không có nghĩa là không thích, có thể chỉ là chưa biết tới. Đây là loại tín hiệu chủ đạo trong ngân hàng, xử lý riêng ở mục sau.

Hai trường phái: User-based vs Item-based

CF kinh điển (memory-based / neighborhood) không huấn luyện mô hình theo nghĩa tối ưu tham số. Nó tính độ tương đồng trực tiếp trên ma trận, tìm "hàng xóm", rồi mượn ý kiến của hàng xóm để dự đoán. Có hai cách nhìn hàng xóm.

User-based CF

Ý tưởng: "Tìm những user giống bạn nhất, xem họ thích gì mà bạn chưa có, gợi ý cái đó."

  1. Tính độ tương đồng giữa user mục tiêu u và mọi user khác dựa trên các item cả hai cùng tương tác.
  2. Chọn k user gần nhất (neighbors).
  3. Với mỗi item iu chưa có, tổng hợp ý kiến của các neighbor đã tương tác với i (công thức weighted sum bên dưới).

Nhược điểm thực tế: số user thường lớn hơn số item rất nhiều (hàng triệu khách vs vài chục sản phẩm), nên ma trận tương đồng user–user khổng lồ và thay đổi liên tục khi có hành vi mới. Rất khó cache, khó mở rộng.

Item-based CF

Ý tưởng: "Với mỗi item bạn đã thích, tìm các item tương tự nó, rồi gợi ý item tương tự." Độ tương đồng ở đây là item–item: hai item được coi là giống nhau nếu chúng thường được cùng một nhóm user tương tác.

Đây là bước ngoặt lịch sử: bài báo của Amazon (Linden, Smith, York, 2003) chỉ ra item-based CF ổn định và mở rộng tốt hơn nhiều so với user-based, và trở thành xương sống cho gợi ý "Khách mua sản phẩm này cũng mua…". Lý do:

  • Ma trận item–item nhỏ và ổn định: số item ít hơn user nhiều, quan hệ giữa hai sản phẩm thay đổi chậm → tính offline định kỳ rồi cache, phục vụ online cực nhanh.
  • Giải thích được: "Gợi ý vì bạn đã dùng X" dễ hiểu, tăng niềm tin — quan trọng với sản phẩm tài chính.
  • Tốt cho user mới ít hành vi: chỉ cần một tương tác là đã tìm được item tương tự.
Tiêu chíUser-basedItem-based
Tìm tương đồng giữauser ↔ useritem ↔ item
Kích thước ma trận simm × m (rất lớn)n × n (nhỏ hơn)
Độ ổn địnhthấp (hành vi user đổi liên tục)cao (quan hệ item bền)
Cache/tiền tínhkhódễ, tính offline
Khả năng mở rộngkémtốt (Amazon dùng)
Giải thích gợi ý"người giống bạn thích""vì bạn đã dùng X"

Với NCB, item-based là lựa chọn mặc định: số sản phẩm hữu hạn, quan hệ "khách dùng gói A thường dùng thêm gói B" ổn định theo thời gian, và có thể tiền tính bảng item–item hằng đêm.

Độ đo tương đồng

Trái tim của memory-based CF là hàm đo hai vector giống nhau bao nhiêu. Ba lựa chọn phổ biến:

Cosine similarity

Đo góc giữa hai vector, bỏ qua độ lớn:

cosine(a, b) = (a · b) / (||a|| × ||b||)

Giá trị trong [-1, 1] (với dữ liệu không âm thì [0, 1]). Phổ biến nhất vì rẻ và không phụ thuộc "độ hào phóng" khi chấm điểm. Với item-based, abhai cột item trong ma trận, so trên tập user chung.

Pearson correlation

Là cosine sau khi trừ trung bình của mỗi vector. Ưu điểm: khử bias người dùng — có user luôn chấm cao, có user luôn khắt khe. Trừ trung bình đưa họ về cùng thang. Phù hợp explicit rating; ít dùng cho implicit vì implicit thường không âm và thưa.

Jaccard (cho implicit nhị phân)

Khi tín hiệu chỉ là có/không (đã dùng sản phẩm hay chưa), coi mỗi item như một tập user. Jaccard đo tỉ lệ giao trên hợp:

jaccard(A, B) = |AB| / |AB|

|A ∩ B| = số user dùng cả hai sản phẩm; |A ∪ B| = số user dùng ít nhất một. Đây chính là công cụ tự nhiên cho bài toán "mua cùng nhau" và rất khớp với dữ liệu ngân hàng dạng đã-dùng/chưa-dùng. Ta sẽ tính nó bằng SQL ngay dưới đây.

Tính điểm dự đoán: weighted sum

Có độ tương đồng rồi, dự đoán điểm cho ô trống bằng trung bình có trọng số theo các neighbor. Với item-based, điểm dự đoán user u dành cho item i:

        Σ (sim(i, j) × r[u][j])      với j là item u ĐÃ tương tác
pred[u][i] = ───────────────────────
              Σ |sim(i, j)|

Trực giác: gom điểm của những item mà u đã dùng (r[u][j]), cân theo mức giống nhau giữa chúng và i. Item nào giống i hơn thì "phiếu bầu" của nó nặng hơn. Mẫu số chuẩn hoá lại để điểm về đúng thang.

Trong thực tế còn chỉ giữ top-k neighbor (ví dụ 20 item giống nhất) thay vì tất cả — vừa nhanh vừa lọc nhiễu từ các item chỉ giống nhau tình cờ. Với user-based, công thức đối xứng: thay sim(i,j) bằng sim(u,v), và thường trừ đi rồi cộng lại trung bình của user để khử bias.

Implicit feedback & confidence weighting

Ngân hàng gần như chỉ có implicit feedback: không ai chấm sao cho gói tiết kiệm. Ta phải suy ra sở thích từ hành vi, và điều này khác explicit ở hai điểm cốt lõi:

  1. Không có tín hiệu âm rõ ràng: user không mở gói vay không đồng nghĩa "ghét" — có thể chưa biết, chưa đủ điều kiện, hoặc chưa cần. Ô trống là thiếu dữ liệu, không phải điểm 0.
  2. Cường độ = độ tin cậy (confidence), không phải mức thích: một khách giao dịch qua ví 200 lần/tháng cho ta tin chắc họ dùng ví hơn khách mới dùng 1 lần. Ta tách đôi: preference (nhị phân: có dùng hay không) và confidence (tăng theo số lần/số tiền). Mô hình implicit kinh điển (Hu, Koren, Volinsky 2008) dùng confidence = 1 + α × (số lần tương tác) để cho tương tác nhiều "nói to hơn".

Cách chuẩn bị dữ liệu implicit cho CF:

  • Đếm hành vi: bao nhiêu giao dịch mỗi loại, mỗi kênh, mỗi sản phẩm.
  • Nhị phân hoá khi cần Jaccard: dùng ≥ ngưỡng → 1, ngược lại 0.
  • Confidence weighting: giữ số đếm (hoặc log của nó) làm trọng số khi tính tương đồng, để một cặp sản phẩm dùng-cùng-nhau nhiều lần được đánh giá tin cậy hơn.

Co-occurrence: "dùng cùng nhau" bằng SQL

Bản chất item-based CF ở mức đơn giản nhất chính là co-occurrence: đếm xem hai item bao nhiêu lần xuất hiện cùng trên một user (hoặc một tài khoản), rồi chuẩn hoá thành độ tương đồng. Trên sandbox NCB, ta không có bảng sản phẩm, nhưng có transactions(kind)loại giao dịch đóng vai trò "item". Câu hỏi CF thu nhỏ: "Những loại giao dịch nào hay đi cùng nhau trên một tài khoản?"

Ta self-join transactions theo account_id, ghép mỗi cặp loại kind khác nhau, và đếm số tài khoản mà cả hai cùng xuất hiện. Đây là ma trận co-occurrence item–item ở dạng thô:

-- ▶ Chạy được
WITH acct_kind AS (
  SELECT DISTINCT account_id, kind
  FROM transactions
)
SELECT a.kind AS kind_a,
       b.kind AS kind_b,
       COUNT(*) AS accounts_dung_ca_hai
FROM acct_kind a
JOIN acct_kind b
  ON a.account_id = b.account_id
 AND a.kind < b.kind
GROUP BY a.kind, b.kind
ORDER BY accounts_dung_ca_hai DESC
LIMIT 20;

Điều kiện a.kind < b.kind vừa bỏ cặp trùng chính nó, vừa tránh đếm mỗi cặp hai lần (A–B và B–A). DISTINCT ở CTE đảm bảo mỗi tài khoản chỉ tính một lần cho mỗi loại — đúng tinh thần "co-occurrence theo user", không bị thổi phồng bởi tài khoản giao dịch nhiều.

Số đếm thô có popularity bias: loại giao dịch phổ biến sẽ đi cùng mọi thứ. Để có tương đồng thực sự, ta chuẩn hoá bằng Jaccard — chia giao cho hợp. Câu dưới tính cho từng cặp loại: số tài khoản dùng cả hai, chia cho số tài khoản dùng ít nhất một:

-- ▶ Chạy được
WITH ak AS (
  SELECT DISTINCT account_id, kind FROM transactions
),
cnt AS (
  SELECT kind, COUNT(*) AS n FROM ak GROUP BY kind
),
pair AS (
  SELECT a.kind AS kind_a, b.kind AS kind_b,
         COUNT(*) AS inter
  FROM ak a
  JOIN ak b ON a.account_id = b.account_id AND a.kind < b.kind
  GROUP BY a.kind, b.kind
)
SELECT p.kind_a, p.kind_b,
       p.inter,
       ca.n + cb.n - p.inter AS union_cnt,
       ROUND(p.inter::numeric / (ca.n + cb.n - p.inter), 4) AS jaccard
FROM pair p
JOIN cnt ca ON ca.kind = p.kind_a
JOIN cnt cb ON cb.kind = p.kind_b
ORDER BY jaccard DESC
LIMIT 15;

Kết quả là một bảng item–item similarity đúng nghĩa. Trong hệ thực tế, "item" là mã sản phẩm chứ không phải kind, và ta tiền tính bảng này hằng đêm rồi cache. Khi khách mở một sản phẩm, chỉ cần tra các sản phẩm có jaccard cao nhất với nó để gợi ý — đó chính là item-based CF ở dạng phục vụ được.

Điểm mạnh và điểm yếu của CF

Điểm mạnh

  • Không cần metadata / đặc trưng nội dung: chỉ cần lịch sử tương tác. Tránh được công đoạn số hoá mô tả sản phẩm vốn tốn công.
  • Bắt được pattern ẩn: CF phát hiện các liên hệ mà không ai lập trình sẵn — "khách dùng thẻ TD ở nước ngoài thường mở gói bảo hiểm du lịch" — miễn dữ liệu có tín hiệu đó.
  • Càng dùng càng tốt: thêm hành vi thì gợi ý mượt hơn, không cần tái thiết kế đặc trưng.

Điểm yếu (và hướng khắc phục)

Vấn đềBản chấtẢnh hưởng ngân hàng
Cold startUser/item mới chưa có tương tác → không có neighborKhách mới mở tài khoản, sản phẩm vừa ra mắt đều "vô hình" với CF
SparsityMa trận cực thưa (>99% ô trống)Ít item chung → độ tương đồng không tin cậy
Popularity biasItem phổ biến áp đảo, đè item ngáchAi cũng được gợi ý gói tiết kiệm hot, mất cá nhân hoá
Khó mở rộngNeighborhood tính trên ma trận lớn tốn kémTriệu khách × sự kiện realtime → không kham nổi bằng memory-based thuần

Bốn điểm yếu này chính là động lực cho bài tiếp theo. Matrix factorization (Hệ khuyến nghị 3) nén ma trận thưa xuống không gian latent ít chiều, giảm sparsity và scale tốt hơn hẳn. Cold start được xử lý bằng cách kết hợp nội dung (Content-based & Hybrid). Còn popularity bias và chất lượng xếp hạng cuối cùng được cân bằng ở tầng ranking & đánh giá.

Use case thực tế

Bài toán: NCB muốn gợi ý "sản phẩm tiếp theo" trên app cho 1,2 triệu khách hàng cá nhân. Danh mục có 18 sản phẩm số hoá (thẻ, tiết kiệm kỳ hạn, vay tiêu dùng, bảo hiểm, ví…). Không có rating; chỉ có lịch sử đăng ký/sử dụng.

Cách làm bằng item-based CF:

  1. Dựng ma trận implicit: mỗi khách × mỗi sản phẩm, ô = số lần dùng trong 12 tháng. Nhị phân hoá ở ngưỡng ≥ 1 lần cho biến "đã dùng".
  2. Tính item–item Jaccard cho toàn bộ 18×18 cặp sản phẩm (như SQL trên, "item" là mã sản phẩm) — ma trận nhỏ, tính offline hằng đêm bằng Spark/SQL, cache vào Redis.
  3. Phục vụ: khi khách A đã dùng {thẻ TD, tiết kiệm}, hệ tra các sản phẩm có Jaccard cao nhất với hai cái đó, cộng dồn theo weighted sum, loại bỏ sản phẩm A đã có, lấy top-3.
  4. Chống popularity bias: dùng Jaccard (đã chuẩn hoá) thay vì count thô, thêm re-ranking hạ điểm sản phẩm quá phổ biến.
  5. Xử lý cold start: khách mới (<1 tương tác) không rơi vào CF — chuyển sang gợi ý theo phân khúc (xem RFM segmentation) hoặc sản phẩm phổ biến của nhóm nhân khẩu tương tự.

Kết quả kỳ vọng (minh hoạ): thử nghiệm A/B (xem A/B testing) trên 5% lưu lượng — nhóm CF cho tỉ lệ click gợi ý cao hơn nhóm "sản phẩm phổ biến chung" khoảng 1,6–2 lần, vì gợi ý bám theo hành vi cá nhân thay vì một danh sách cố định. Con số cụ thể phải đo trên dữ liệu thật; đây chỉ là mức tăng minh hoạ thường thấy.

Ghi nhớ

  • CF = học từ hành vi tập thể, không cần đặc trưng nội dung: xuất phát từ ma trận user–item và đi điền các ô trống.
  • User-based tìm người giống bạn; item-based tìm sản phẩm giống nhau. Item-based ổn định, mở rộng tốt, giải thích được → mặc định cho ngân hàng (Amazon phổ biến hoá).
  • Độ đo tương đồng: cosine (rẻ, chung), Pearson (khử bias người chấm, explicit), Jaccard (implicit nhị phân, khớp bài toán "dùng cùng nhau").
  • Điểm dự đoán = weighted sum theo neighbor, cân theo độ tương đồng, thường giữ top-k để lọc nhiễu.
  • Implicit feedback: ô trống ≠ ghét; tách preference (có/không) khỏi confidence (tăng theo cường độ).
  • Co-occurrence bằng SQL: self-join theo khoá dùng chung (account_id), đếm cặp, chuẩn hoá Jaccard → ra ma trận item–item tiền tính được.
  • Bốn điểm yếu — cold start, sparsity, popularity bias, khó mở rộng — là lý do chuyển sang matrix factorizationhybrid ở các bài sau.

Bài viết liên quan

Phân biệt AI/ML/DL, các kiểu học máy, quy trình ML end-to-end và thuật ngữ nền tảng.

13 thg 7, 2026 15

Chất lượng dữ liệu, xử lý thiếu/ngoại lai, mã hoá, chuẩn hoá, tạo đặc trưng và tránh data leakage.

13 thg 7, 2026 13

Mô hình ngôn ngữ lớn hoạt động ra sao, token, context window, tham số sinh, và kỹ thuật viết prompt.

13 thg 7, 2026 13

Retrieval-Augmented Generation, vector database, tool calling, agent và MCP để xây ứng dụng LLM thực tế.

13 thg 7, 2026 11