📗 บทที่ 4 — Cosine Similarity: สมการเดียวที่ต้องรู้¶
คู่กับ: หนังสือบทที่ 4 · สไลด์ 2/4 "ลากปลายลูกศรดูเอง"
$$\cos(A,B) = \frac{A \cdot B}{\|A\|\,\|B\|}$$
บทนี้ไม่มี database เลย — คำนวณมือให้เห็นว่าข้างในไม่มีเวทมนตร์
1) ฟังก์ชันทั้งหมด 5 บรรทัด¶
In [1]:
import math
def cosine(a, b):
dot = sum(x * y for x, y in zip(a, b))
na = math.sqrt(sum(x * x for x in a))
nb = math.sqrt(sum(x * x for x in b))
return dot / (na * nb)
print('พร้อม — คูณ บวก หาร แค่นั้น')
พร้อม — คูณ บวก หาร แค่นั้น
2) โลกของเล่น 2 มิติ: [ความเป็นแมว, ความเป็นรถ]¶
In [2]:
cat = [0.9, 0.1]
kitten = [0.85, 0.15]
car = [0.1, 0.95]
print(f'cos(แมว, ลูกแมว) = {cosine(cat, kitten):.4f} ← ใกล้ 1 = ความหมายใกล้')
print(f'cos(แมว, รถยนต์) = {cosine(cat, car):.4f} ← ใกล้ 0 = คนละเรื่อง')
cos(แมว, ลูกแมว) = 0.9980 ← ใกล้ 1 = ความหมายใกล้ cos(แมว, รถยนต์) = 0.2139 ← ใกล้ 0 = คนละเรื่อง
✅ วัดผลตัวเอง #4a — คำนวณมือแล้วเช็คกับโค้ด¶
คิดบนกระดาษ: dot = 0.9×0.85 + 0.1×0.15 = ? · |แมว| = √(0.81+0.01) = ? แล้วเทียบ
In [3]:
hand_dot = 0.9*0.85 + 0.1*0.15
hand_cos = hand_dot / (math.sqrt(0.9**2 + 0.1**2) * math.sqrt(0.85**2 + 0.15**2))
assert abs(hand_cos - cosine(cat, kitten)) < 1e-9
print(f'✅ มือ = โค้ด = {hand_cos:.4f} — สมการเดียวจริงๆ')
✅ มือ = โค้ด = 0.9980 — สมการเดียวจริงๆ
3) สเกลขึ้นของจริง: 1024 มิติ ฟังก์ชันเดิมเป๊ะ¶
In [4]:
# ---- ตัว embed อัจฉริยะ: เครื่องเรา→Ollama (เร็ว+privacy) · Colab→sentence-transformers ----
import urllib.request, json
import numpy as np
def _ollama_ok():
try:
urllib.request.urlopen('http://localhost:11434/api/tags', timeout=2)
return True
except Exception:
return False
if _ollama_ok():
def embed_texts(texts):
req = urllib.request.Request('http://localhost:11434/api/embed',
data=json.dumps({'model': 'bge-m3', 'input': list(texts)}).encode(),
headers={'Content-Type': 'application/json'})
with urllib.request.urlopen(req, timeout=180) as r:
V = np.array(json.load(r)['embeddings'])
return V / np.linalg.norm(V, axis=1, keepdims=True)
print('embed ด้วย bge-m3 ผ่าน Ollama (local, ข้อมูลไม่ออกเครื่อง) ✓')
else:
from sentence_transformers import SentenceTransformer
_model = SentenceTransformer('BAAI/bge-m3')
def embed_texts(texts):
return _model.encode(list(texts), normalize_embeddings=True)
print('embed ด้วย bge-m3 ผ่าน sentence-transformers ✓')
texts = ['แมวนอนบนโซฟา', 'ลูกแมวหลับอยู่บนเก้าอี้', 'ราคาน้ำมันดีเซลปรับขึ้นวันนี้']
vecs = embed_texts(texts).tolist()
print('มิติจริง:', len(vecs[0]))
print(f'cos(แมวนอนโซฟา, ลูกแมวหลับเก้าอี้) = {cosine(vecs[0], vecs[1]):.4f}')
print(f'cos(แมวนอนโซฟา, ราคาน้ำมันดีเซล) = {cosine(vecs[0], vecs[2]):.4f}')
embed ด้วย bge-m3 ผ่าน Ollama (local, ข้อมูลไม่ออกเครื่อง) ✓
มิติจริง: 1024 cos(แมวนอนโซฟา, ลูกแมวหลับเก้าอี้) = 0.8181 cos(แมวนอนโซฟา, ราคาน้ำมันดีเซล) = 0.3065
✅ วัดผลตัวเอง #4b — ประโยคแมว 2 ประโยค ไม่มีคำซ้ำกันเลย¶
In [5]:
a, b = set('แมวนอนบนโซฟา'.split()), set('ลูกแมวหลับอยู่บนเก้าอี้'.split())
sim_cats = cosine(vecs[0], vecs[1]); sim_oil = cosine(vecs[0], vecs[2])
assert sim_cats > sim_oil + 0.3, 'คู่แมวควรชนะคู่น้ำมันขาด'
print(f'✅ ผ่าน! แมว-แมว {sim_cats:.3f} ≫ แมว-น้ำมัน {sim_oil:.3f}')
print('→ vector DB ทุกตัว = ที่เก็บเวกเตอร์ + เครื่องหา cosine สูงสุดแบบเร็วมาก')
✅ ผ่าน! แมว-แมว 0.818 ≫ แมว-น้ำมัน 0.306 → vector DB ทุกตัว = ที่เก็บเวกเตอร์ + เครื่องหา cosine สูงสุดแบบเร็วมาก
📊 เห็นภาพ — เวกเตอร์ 2 มิติ (static ของ playground)¶
In [6]:
# @@VIZ@@
import matplotlib, matplotlib.pyplot as plt
from matplotlib import font_manager as _fm
_av={f.name for f in _fm.fontManager.ttflist}
for _f in ['Thonburi','Loma','Sarabun','Noto Sans Thai','TH Sarabun New']:
if _f in _av: matplotlib.rcParams['font.family']=_f; break
matplotlib.rcParams['axes.unicode_minus']=False
cat=[0.9,0.1]; kitten=[0.85,0.15]; car=[0.1,0.95]
plt.figure(figsize=(5,5)); ax=plt.gca()
for v,nm,cl in [(cat,'แมว','#2a78d6'),(kitten,'ลูกแมว','#1baf7a'),(car,'รถยนต์','#e34948')]:
ax.annotate('',xy=v,xytext=(0,0),arrowprops=dict(arrowstyle='-|>',color=cl,lw=2.5))
ax.text(v[0]*1.03,v[1]*1.03,nm,color=cl,fontsize=13,fontweight='bold')
ax.set_xlim(0,1.1); ax.set_ylim(0,1.1); ax.set_aspect('equal'); plt.grid(alpha=.3)
ax.set_xlabel('แกน: ความเป็นแมว'); ax.set_ylabel('แกน: ความเป็นรถ')
ax.set_title(f'บทที่ 4 — cos(แมว,ลูกแมว)={cosine(cat,kitten):.2f} · cos(แมว,รถ)={cosine(cat,car):.2f}')
plt.tight_layout(); plt.show()
🏋️ แบบฝึก¶
- เขียนประโยค 3 ประโยคของคุณ (2 ใกล้ 1 ไกล) แล้วทายค่า cosine ก่อนรัน — ทายแม่นไหม?
- ลอง cos ระหว่างประโยคเดียวกันเป๊ะ — ได้เท่าไร? ทำไม?
บทต่อไป: ch05_embedder_shootout.ipynb + semantic_map_colab.ipynb