ข้ามไปยังเนื้อหาหลัก

[LLM 8/10] Guardrails: รั้วนิรภัยที่แท้จริงไม่ใช่โมเดล แต่คือ threshold

· อ่าน 8 นาที
Kobkrit Viriyayudhakorn
CEO, iApp Technology

แชตบอตภาษาไทยที่ผมและทีม deploy ให้ลูกค้าใช้งานจริง ไม่ได้เจอแต่คำถามสุภาพ ๆ — มีคนขอสูตรทำของผิดกฎหมาย มีคนพยายามหลอกให้มันด่าคนอื่น และมีวันที่โมเดลพ่นเบอร์โทรลูกค้าออกมาเอง บทนี้เราจะสร้างระบบป้องกันทั้งสองทิศ: classifier ตรวจ prompt อันตรายขาเข้า และ ตัวกรอง PII ขาออก แต่ประเด็นแกนกลางของบทไม่ใช่ตัวโมเดล — มันคือความจริงที่ว่า guardrail เป็นการตัดสินใจเรื่อง threshold ภายใต้ต้นทุนที่ไม่สมมาตร และตัวเลข "accuracy 94%" ที่คนชอบโชว์กันนั้น แทบไม่มีความหมายเลย

Open in Colab08_guardrails.ipynb

1. ปัญหา (Problem statement)

บทที่ผ่าน ๆ มาเราเทรนโมเดลให้ "เก่งขึ้น" มาตลอด แต่โมเดลที่เก่งแค่ไหนก็ตาม พอเจอผู้ใช้จริงจะเจอความเสียหายได้สองทิศทางเสมอ:

ทิศทางตัวอย่างความเสียหายเครื่องมือของบทนี้
ขาเข้า (input)ผู้ใช้ขอวิธีทำร้ายคนอื่น สูตรของผิดกฎหมาย วิธีโกง — แล้วโมเดลตอบให้classifier ตรวจ prompt อันตราย
ขาออก (output)โมเดลพ่นเลขบัตรประชาชน เบอร์โทร เลขบัญชี ที่หลุดมาจาก context หรือข้อมูลเทรนตัวกรอง PII แบบ deterministic

อย่าลืมว่า SFT ในบทที่ 2 มีผลข้างเคียงที่เงียบมาก: การ fine-tune ด้วยข้อมูลแคบ ๆ กัดกร่อนพฤติกรรมปฏิเสธ (refusal) ที่โมเดลเคยมี โมเดลที่คุณปรับแต่งเองจึงมักปลอดภัยน้อยกว่าต้นฉบับ นี่คือเหตุผลที่ระบบจริงต้องมีรั้วอีกชั้นที่อยู่นอกตัวโมเดล

แล้วทำไมถึงซื้อ guardrail สำเร็จรูปที่โฆษณาว่า "แม่น 94%" ไม่ได้เลย? เพราะประโยคนั้นยังไม่ได้ตอบคำถามที่สำคัญที่สุดสามข้อ:

  1. แม่น 94% ที่ threshold ไหน — ตัวเลขเดียวกันนี้เลื่อนได้ทั้งเส้นโค้ง
  2. วัดบนชุดทดสอบที่มี unsafe กี่เปอร์เซ็นต์ — ใน production ทราฟฟิกอันตรายจริงมักไม่ถึง 1%
  3. และมันบล็อกผู้ใช้บริสุทธิ์กี่เปอร์เซ็นต์ — ตัวเลขที่แทบไม่มีใครยอมรายงาน

Guardrail ที่บล็อกลูกค้าที่ถามเรื่องปกติ ไม่ใช่ระบบที่ปลอดภัย มันคือผลิตภัณฑ์ที่พัง

2. เราจะทำอะไร (Solution)

เราจะสร้าง guardrail จริงสองตัวบน Colab ฟรี แล้ววัดมันอย่างตรงไปตรงมา:

ชั้นตำแหน่งเทคนิคเวลาแฝงโดยประมาณ
Input guardrailก่อน prompt ไปถึง LLMQwen3-0.6B + sequence-classification head + LoRA r=8~15–30 ms
Output guardrailหลัง LLM ตอบ ก่อนถึงผู้ใช้regex + mod-11 checksum (ไม่มี ML เลย)~0.1 ms
แนวคิดหลักของบทนี้

Guardrail ไม่ใช่โมเดล — มันคือการตัดสินใจเรื่อง threshold ภายใต้ต้นทุนที่ไม่สมมาตร โมเดลให้แค่คะแนน pϕ(unsafex)p_\phi(\text{unsafe}\mid x) ส่วนการเลือกจุดตัด τ\tau คือการตอบคำถามเชิงธุรกิจว่า "การปล่อยของอันตรายหลุดหนึ่งครั้ง แพงกว่าการบล็อกลูกค้าบริสุทธิ์หนึ่งคนกี่เท่า"

รายงานผลที่ซื่อสัตย์จึงต้องมีสองตัวเลขเสมอ: unsafe ที่จับได้ และ benign ที่ถูกบล็อก ตัวเลขเดียวโดด ๆ คือการตลาด ไม่ใช่วิศวกรรม

และเราจะได้เห็นด้วยว่า guardrail ที่ดีที่สุดบางตัวไม่ใช่ ML เลย — ตัวกรอง PII ด้วย regex + checksum นั้น deterministic, ทดสอบได้แบบ unit test, เร็วระดับไมโครวินาที และไม่มีวันโดน jailbreak

3. สมการ (Equation)

3.1 Loss ของ classifier — ของคุ้นเคย

L(ϕ)=E(x,y)D[ylogpϕ(unsafex)+(1y)log(1pϕ(unsafex))]\mathcal{L}(\phi) = -\mathbb{E}_{(x,y)\sim\mathcal{D}}\Big[\,y\log p_\phi(\text{unsafe}\mid x) + (1-y)\log\big(1-p_\phi(\text{unsafe}\mid x)\big)\Big]

binary cross-entropy ธรรมดา (y=1y=1 คือ unsafe) ไม่มีอะไรใหม่ — และนั่นแหละคือประเด็น: ส่วนที่เป็น ML ของ guardrail คือส่วนที่ง่ายที่สุดของทั้งระบบ ของจริงอยู่ข้างล่างนี้

3.2 กฎการตัดสินใจ และต้นทุนคาดหวัง — เนื้อหาจริงของบทนี้

block(x)=1[pϕ(unsafex)>τ]\text{block}(x) = \mathbf{1}\big[\,p_\phi(\text{unsafe}\mid x) > \tau\,\big]

โมเดลจบหน้าที่ที่การให้คะแนน การบล็อกหรือปล่อยคือการเทียบกับ τ\tau ซึ่งเราเลือกโดย minimize ต้นทุนคาดหวัง:

C(τ)=cFNP(unsafe)FNR(τ)  +  cFPP(safe)FPR(τ)τ=argminτC(τ)C(\tau) = c_{\text{FN}}\,P(\text{unsafe})\,\text{FNR}(\tau) \;+\; c_{\text{FP}}\,P(\text{safe})\,\text{FPR}(\tau) \qquad\qquad \tau^* = \arg\min_\tau C(\tau)
  • FNR(τ)\text{FNR}(\tau) = สัดส่วน unsafe ที่หลุดรอด (false negative rate)
  • FPR(τ)\text{FPR}(\tau) = สัดส่วน benign ที่ถูกบล็อก (false positive rate)
  • cFN,cFPc_{\text{FN}}, c_{\text{FP}} = ราคาของความผิดพลาดแต่ละแบบ

สังเกตว่าสมการนี้บังคับให้คุณตอบคำถามที่ ML ตอบแทนไม่ได้: cFN/cFPc_{\text{FN}}/c_{\text{FP}} ของผลิตภัณฑ์คุณคือเท่าไหร่ นี่คือการตัดสินใจเชิงผลิตภัณฑ์ล้วน ๆ และแต่ละผลิตภัณฑ์ตอบไม่เหมือนกัน:

ผลิตภัณฑ์ราคาของ FN (ปล่อย unsafe หลุด)ราคาของ FP (บล็อกคนบริสุทธิ์)cFN/cFPc_{\text{FN}}/c_{\text{FP}} ที่สมเหตุสมผล
แชตบอตให้คำปรึกษาสุขภาพอันตรายถึงชีวิต + ความรับผิดทางกฎหมายผู้ใช้หงุดหงิดเล็กน้อย50:1 ขึ้นไป
ผู้ช่วยลูกค้าองค์กรเป็นข่าวเสียหายลูกค้าติดต่อ support เพิ่ม~10:1
เครื่องมือภายในสำหรับพนักงานจำกัด (ผู้ใช้คือพนักงานที่ระบุตัวได้)งานสะดุดทุกวัน~2:1

ถ้าคุณไม่เคยเขียนอัตราส่วนนี้ลงเอกสารอย่างชัดเจน แปลว่าที่ผ่านมามีคนเลือก τ\tau ให้คุณโดยบังเอิญ

3.3 บทเรียนที่แพงที่สุดของบท: base rate ทำ precision พังได้

สมมติ classifier ของเราจับ unsafe ได้ TPR = 95% และบล็อกผิดแค่ FPR = 5% — ฟังดูเยี่ยม คำถาม: ในบรรดาข้อความที่ถูกบล็อก มีกี่เปอร์เซ็นต์ที่ unsafe จริง? ใช้ Bayes ตรง ๆ ให้ π=P(unsafe)\pi = P(\text{unsafe}) คือสัดส่วน unsafe ในทราฟฟิกจริง:

precision=P(unsafeblock)=P(blockunsafe)πP(block)=TPRπTPRπ+FPR(1π)\text{precision} = P(\text{unsafe}\mid\text{block}) = \frac{P(\text{block}\mid\text{unsafe})\,\pi}{P(\text{block})} = \frac{\text{TPR}\cdot\pi}{\text{TPR}\cdot\pi + \text{FPR}\cdot(1-\pi)}

แทนตัวเลขสองสถานการณ์:

  • ชุดทดสอบสมดุล (π=0.5\pi = 0.5): precision =0.95×0.50.95×0.5+0.05×0.5=95%= \dfrac{0.95 \times 0.5}{0.95 \times 0.5 + 0.05 \times 0.5} = 95\%
  • ทราฟฟิกจริง (π=0.01\pi = 0.01): precision =0.95×0.010.95×0.01+0.05×0.9916%= \dfrac{0.95 \times 0.01}{0.95 \times 0.01 + 0.05 \times 0.99} \approx 16\%

classifier ตัวเดียวกันเป๊ะ ๆ — แต่ใน production ทุก ๆ 6 ข้อความที่ถูกบล็อก มี 5 ข้อความเป็นผู้ใช้บริสุทธิ์ เพราะเมื่อ unsafe หายาก (π\pi เล็ก) พจน์ FPR(1π)\text{FPR}\cdot(1-\pi) ในตัวหารจะกลืนทุกอย่าง นี่คือเหตุผลที่การประเมินบนชุดสมดุลแล้วเอาไปคุยว่า "แม่น 95%" คือการหลอกตัวเอง

3.4 การป้องกันหลายชั้น (layered defence)

ถ้าวาง guardrail อิสระกัน KK ชั้น (blocklist → classifier → system prompt → มนุษย์สุ่มตรวจ) โดย unsafe จะหลุดได้ต้องหลอกทุกชั้น แต่ benign โดนบล็อกถ้าชั้นใดชั้นหนึ่งจับผิด:

FNRsys=k=1KFNRkFPRsys=1k=1K(1FPRk)\text{FNR}_{\text{sys}} = \prod_{k=1}^{K}\text{FNR}_k \qquad\qquad \text{FPR}_{\text{sys}} = 1 - \prod_{k=1}^{K}\big(1-\text{FPR}_k\big)

FNR ลดแบบเรขาคณิต (ดีมาก) แต่ FPR ทบต้นขึ้นเรื่อย ๆ (บิลที่ต้องจ่าย)

สมมติฐานอิสระต่อกันคือการมองโลกในแง่ดีเกินจริง

สมการ kFNRk\prod_k \text{FNR}_k ใช้ได้ต่อเมื่อแต่ละชั้นพลาดแบบอิสระต่อกัน ซึ่งในความเป็นจริงแทบไม่เคยจริง — เทคนิคหลบเลี่ยงหนึ่งท่า (เช่น แทรก zero-width space กลางคำ) มักหลอกทุกชั้นที่อิงข้อความดิบพร้อมกัน ความผิดพลาดของแต่ละชั้นจึงสหสัมพันธ์กัน และ FNRsys\text{FNR}_{\text{sys}} จริงจะแย่กว่าสูตรนี้เสมอ มองสูตรนี้เป็น best case ไม่ใช่คำสัญญา

3.5 Constrained decoding — guardrail ที่ไม่ใช่ ML และคนมองข้ามที่สุด

ถ้า use case ของคุณตอบได้แค่ชุดจำกัด (เมนู, หมวดหมู่, JSON ตาม schema) อย่าไปตรวจข้อความทีหลัง — บังคับตั้งแต่ตอน generate ด้วยการ renormalize บนเซตโทเคนที่อนุญาต A\mathcal{A}:

p(tx)=pθ(tx)1[tA]tApθ(tx)p'(t\mid x) = \frac{p_\theta(t\mid x)\,\mathbf{1}[t\in\mathcal{A}]}{\sum_{t'\in\mathcal{A}} p_\theta(t'\mid x)}

โทเคนนอก A\mathcal{A} มีความน่าจะเป็นเป็นศูนย์เป๊ะ ไม่ใช่ "น้อยมาก" ผลคือ guardrail ที่ deterministic, เวลาแฝงเพิ่มเป็นศูนย์, และไม่มี prompt ใดในจักรวาล bypass ได้ เพราะมันไม่ได้ห้ามโมเดล "อยากพูด" — มันทำให้คำนอกเซตไม่มีอยู่ในสารบบตั้งแต่แรก ที่ไหนใช้ constrained decoding ได้ ให้ใช้ก่อนเสมอ แล้วค่อยเอา classifier ไปเฝ้าส่วนที่เป็น free text จริง ๆ

เนื้อหาเต็มอยู่ในคอร์ส

บทความนี้คือประมาณ 30% แรกของบทเรียน — ส่วนที่เหลือ (การเตรียมสภาพแวดล้อม, การเตรียมข้อมูล, โค้ดหลัก, ผลลัพธ์จริง และบทสรุป) อยู่ในคอร์ส LLM Finetuning ซึ่งเรียนฟรี เพียงเข้าสู่ระบบด้วย Google

อ่านเนื้อหาเต็มในคอร์ส →