D1 — Tiny Poker Model (EV + Psych + λ=f(n))

⚠️ D1 KHÔNG phải là LLM / language model. Nó là một mạng nơ-ron nhỏ (MLP, 281,577 params ≈ 1.1 MB) học ra quyết định chơi poker bằng EV kết hợp phân tích tâm lý đối thủ theo hướng AISTATS09 (Data Biased Robust Counter Strategies). Không có tokenizer, không sinh text — input là vector feature 148 chiều mô tả một "spot" poker, output là EV dự đoán, xác suất fold của đối thủ (p_fold), và policy hành động.

Version: v3 (2026-08-03) — nâng từ v2 (17.6K params) lên 281K params + data g1 (13,838 spots) + pot-control serve-time (chống raise-war).

Model card tổng quan

Thuộc tính Giá trị
Kiến trúc MLP 3 heads (backbone shared), width=336, dropout=0.1
Input Vector 148 chiều (spot state + stats đối thủ + λ)
Heads ev_head (EV bb/action ×4), psych_head (p_fold logit), policy_head (policy ×4)
Loss CE(policy) + γ·CE(ev_best) + α·MSE(ev) + β·BCE(p_fold), nhãn p_fold dạng soft label f2c/100, sentinel -1 masked
Params 281,577 (~1.1 MB)
Export Keras .keras (3.4 MB) · TFLite f32 (1.1 MB) · TFLite int8 (307 KB)
Dữ liệu train 13,838 spots poker 6-max (A9 dataset + g1), split 11,762 / 2,076
Số tay benchmark 15 seeds × 500 hands × 4 bots × 2 modes (120,000 hands heads-up)
Trận A9 thật 4 trận × 100 hands vs A9 production trên API /decide

Ý tưởng thiết kế

Dựa trên AISTATS09 – Data Biased Robust Counter Strategies (Ganzfried & Sandholm): khi chưa rõ đối thủ thì chơi chiến thuật robust (EV thuần), càng quan sát được đối thủ thì càng nghiêng sang exploit.

  • λ = f(n)input (feature 147) và đồng thời là gate: feature stats đối thủ (feature 141–146: vpip/pfr/af/f2c…) bị nhân với λ trước khi vào backbone. λ≈0 → model không nhìn thấy stats đối thủ → chơi EV/robust thuần; λ≈1 → thấy đầy đủ → khai thác tâm lý.
  • psych_head dự đoán p_fold (xác suất đối thủ fold khi gặp c-bet) — hồi quy liên tục trên soft label f2c/100, đúng tinh thần AISTATS09 là dùng tần suất hành động quan sát được thay vì lớp persona rời rạc.
  • policy_head trộn backbone + EV dự đoán + p_fold + λ → chọn hành động (fold/check/call/raise).

Huấn luyện

  • 13,838 spots từ dataset A9 (spot generator 6-max) + spots_g1_retry_early (~3.4k spots mới), 40 epochs.
  • Split train/val: 11,762 / 2,076.
  • Loss: CE(policy) + γ·CE(ev_best) + α·MSE(ev) + β·BCE(p_fold) với EV-guided policy loss (γ=0.5) — policy head bị ép theo nhãn argmax EV proxy trên legal, không chỉ copy teacher (fix "quên value-bet" 2026-08-03).
  • Kết quả (val):
    • policy_acc 76.8% (chọn đúng hành động trong 4 legal)
    • ev_best_acc 73.1% (độ khớp với hành động EV-tối ưu — model học "ra quyết định bằng EV")
    • psych_mae 0.019 (~sai số 2 điểm f2c % — psych head thực sự học được tín hiệu; so với nhãn persona 2 lớp trước đây val ~49% = đoán mò)
    • ev_mae_scaled 0.055

Lưu ý params: tăng 17.6K → 281K KHÔNG tạo đột phá bb/100 (bottleneck là dữ liệu 13.8k spots) nhưng giảm variance rõ (station std 1.81→0.65) và tăng raise% (aggressive hơn) — xem bảng benchmark dưới.

Chi tiết: train_report.json

Benchmark (self-play heads-up)

Giao thức: 15 seeds × 500 hands/matchup, đấu 4 bot baseline, so sánh chế độ adaptive (λ=f(n) thật, tăng theo số tay quan sát) vs λ=0 (robust thuần, không xem stats đối thủ). Kết quả bb/100 (mean±std):

Đối thủ λ adaptive λ=0 (robust) Nhận xét
random +21.34±1.27 +21.53±1.22 Thắng đều cả 2 mode
potodds −21.53±1.41 −21.46±0.86 Thua bot equity-aware (giới hạn EV proxy)
station +20.93±0.65 +20.91±1.56 Ổn định nhất (std 0.65, so v2 1.81)
foldheavy +14.66±10.2 +18.61±3.68 Thắng bot fold nhiều (adaptive std lớn, caveat)

Bằng chứng ADAPTATION (điểm chính)

Model adaptive đổi chiến thuật theo HUD đối thủ đo thật (tracker đo f2c khi gặp bet postflop):

vs random vs potodds vs station vs foldheavy
f2c đo được của bot 36.0% 24.8% 0% 25.0%
raise% λ adaptive 36.7% 16.5% 14.5% 67.3%
raise% λ=0 68.5% 61.9% 62.3% 64.1%

→ Model biết "đối thủ fold thì bluff nhiều" (raise 67.3% vs foldheavy nhưng chỉ 14.5% vs station không bao giờ fold); λ=0 chơi một kiểu với tất cả.

λ ramp thật theo số tay quan sát: first-50 λ≈0.12 → last-50 λ≈0.97–1.0. raise% theo block λ khớp hoàn hảo: vs random/potodds/station raise hạ dần khi λ tăng (63.6→27.2% / 52.4→11.3% / 58.7→13.2%), vs foldheavy giữ cao (64.2→69.7%) — model học "thăm dò lúc chưa rõ, khai thác khi đã rõ".

⚠️ Adaptation ≠ improvement: trước EV-guided fix, adaptive rất passive vs potodds/station (check nhiều) — vs station λ0 (+20.76) thắng đậm adaptive (+10.94). Sau fix (2026-08-03), adaptive vs station lên +20.58 (chênh λ0 chỉ 0.9) — model đã học value-bet song song với tránh bluff vào đối thủ không fold. Bằng chứng đáng tin cậy nhất là λ ramp + action distribution đổi theo đối thủ.

Trận A9 thật (4 trận × 100 hands, API /decide)

Đấu heads-up vs A9 production (LLM ~2B params finetune, latency ~3.4s vs D1 23ms):

Trận D1 A9 Ghi chú
seed 42 −112 BB +112 1 hand cooler −96BB (full house 6 vs full house J)
seed 7 +101 BB −101 Thắng áp đảo
seed 99 −7.5 BB +7.5 Giằng co
seed 123 −14 BB +14 Giằng co
Trung bình −8.1 BB/100 +8.1 ≈ ngang cơ A9

Pot-control serve-time (fix raise-war 2026-08-03, không cần retrain):

  • Cùng street re-raise ≥3 lần & eq<0.90 → hạ raise xuống call/check (≥5 lần thì cấm raise bất kể eq, trừ all-in).
  • Đã đầu tư ≥50% stack → cấm fold.
  • Kết quả: max re-raise/street 8–10 → 3–4, hết "fold sau khi commit 77BB", hết hand −91BB/2 hand của v3 gốc.

Chi tiết đầy đủ: benchmark_report.json

Các file trong repo

File Mô tả
model.keras Model Keras đầy đủ (3.4 MB)
model.tflite TFLite float32 (1.1 MB)
model_int8.tflite TFLite int8 quantized (307 KB) — khuyên dùng cho edge
model_size.txt Số params + kích thước từng export
train_report.json Metrics train/val + size
benchmark_report.json Kết quả 8 matchup × 15 seeds đầy đủ
README.md Model card này

Cách dùng

Load Keras model

import tensorflow as tf

model = tf.keras.models.load_model("model.keras")
# Input: (batch, 148) float32 — vector từ features.encode_spot()
# Output: [policy(4), ev_bb(4), psych_logit(1)] (split output)

Load TFLite int8 (edge, 307 KB)

import numpy as np
import tensorflow as tf

interp = tf.lite.Interpreter(model_path="model_int8.tflite")
interp.allocate_tensors()
inp = interp.get_input_details()[0]
out = interp.get_output_details()

x = np.random.rand(1, 148).astype(np.float32)
interp.set_tensor(inp["index"], x)
interp.invoke()
policy, ev, psych_logit = [interp.get_tensor(o["index"])[0] for o in out]

Parity Keras ↔ TFLite int8 đã verify: action + λ-gating khớp nhau.

Feature encoding (148 chiều)

Vector được tạo từ một "spot" gồm: stack/pot/to_call/spr/pot_odds, street, position, hole cards, board, legal actions, action history, stats đối thủ (vpip/pfr/af/f2c…) — và λ (số tay quan sát). Công thức encode đầy đủ trong features.py của repo nguồn (xem phần "Repo nguồn").

Giới hạn trung thực

  1. EV là proxy, chưa phải solver EV: ev = eq·(pot+2X) − X (xấp xỉ bằng equity Monte-Carlo + cấu trúc pot), không phải rollout đầy đủ.
  2. Chỉ min-raise sizing — model chưa học bet sizing; mọi raise đều min.
  3. Benchmark heads-up, model train trên spot 6-max — kết luận chỉ có giá trị trong miền đó.
  4. Adaptive ≠ thắng λ0 vs mọi đối thủ (foldheavy adaptive std ±10).
  5. psych_head dự đoán p_fold tốt (MAE 2 điểm) nhưng chưa dùng p_fold để chọn sizing — đó là bước tiếp theo.
  6. Pot-control là rule serve-time chưa học trong model — bước tiếp theo là đưa vào loss/feature để model tự học.
  7. Params 281K > 17.6K không tạo đột phá bb/100 (bottleneck là dữ liệu 13.8k spots) — lợi ích chính là ổn định hơn (std giảm).

Repo nguồn

Code đầy đủ (train, benchmark, D1 decider, 43 unit tests, verify TFLite parity) nằm tại thư mục tf_model/ trong repo github.com/braitoli/finetune-poker-model (nhánh eval/sixway_selfplay_summary). Design doc: docs/2026-08-02-tiny-tf-ev-psych-design.md.


Model D1 — prototype nghiên cứu. Được tạo 2026-08-02 bởi braitoli.

Downloads last month
-
Video Preview
loading