Embedding និង Attention

មាតិកា

  1. ហេតុអ្វីពាក្យត្រូវក្លាយជាលេខ
  2. ពាក្យជាចំណុចមួយក្នុងលំហ
  3. Word Vector ធ្វើដោយដៃ
  4. Dot Product និង Norm
  5. Cosine Similarity៖ តើពាក្យពីរជិតគ្នាប៉ុនណា?
  6. អ្នកជិតខាងជិតបំផុត
  7. Embedding ពិតត្រូវបានរៀន
  8. ស្វែងរកតាមអត្ថន័យ
  9. បញ្ហា៖ មួយ Vector ក្នុងមួយពាក្យមិនគ្រប់គ្រាន់
  10. Attention៖ ពាក្យមើលពាក្យ
  11. Attention ក្នុងកូដ
  12. អានតារាង Attention
  13. Context ផ្លាស់ប្តូរអត្ថន័យ
  14. Transformer ពិត ដោយស្មោះត្រង់
  15. រូបមន្ត៖ Module embeddings និង attention
  16. សាកល្បងដោយខ្លួនឯង
  17. សំណួរដែលអ្នកនឹងត្រូវគេសួរ

1. ហេតុអ្វីពាក្យត្រូវក្លាយជាលេខ

កុំព្យូទ័រមិនអាចធ្វើនព្វន្ធលើពាក្យ "coffee" បានទេ។ នៅក្នុង Tokens and Tokenizers អ្នកបានបំប្លែងអត្ថបទទៅជាលេខ token ហើយនៅក្នុង How a Model Is Trained ពាក្យមួយបានក្លាយជាលេខជួរក្នុងតារាង។ ប៉ុន្តែលេខជួរគ្រាន់តែជាស្លាកមួយ។ ពាក្យលេខ 12 មិន "ជិត" ពាក្យលេខ 13 តាមរបៀបមានប្រយោជន៍ណាមួយទេ ហើយ model មិនដឹងថា coffee និង tea ស្រដៀងគ្នា ចំណែក coffee និង dog មិនស្រដៀងគ្នាទេ។

យើងចង់បានអ្វីដែលល្អជាងនេះ៖ ការតំណាងដែល ពាក្យស្រដៀងគ្នាទទួលបានលេខស្រដៀងគ្នា។ បន្ទាប់មកអ្វីដែល model រៀនអំពី coffee ជួយដល់ tea ដោយស្វ័យប្រវត្តិ ព្រោះលេខរបស់ពួកវា នៅជិតគ្នា។ ការតំណាងនោះហៅថា embedding៖ ពាក្យ (ឬ token) ដែលបំប្លែងទៅជា vector ដែលគ្រាន់តែជាបញ្ជីលេខ។

គំនិតនេះគឺជាគំនិតមួយក្នុងចំណោមគំនិតមានប្រយោជន៍បំផុតក្នុង AI សម័យទំនើប។ វាជារបៀប ដែល model ចាប់ផ្តើមចាប់យក អត្ថន័យ ហើយវាក៏ជំរុញការស្វែងរកតាមអត្ថន័យផងដែរ ដែលអ្នក នឹងប្រើក្នុងស៊េរីក្រោយៗ។

2. ពាក្យជាចំណុចមួយក្នុងលំហ

អ្នកស្គាល់ vector ដែលមានលេខពីរស្រាប់ហើយ៖ ចំណុចមួយលើផែនទី (x, y)។ លេខពីរដាក់ចំណុច មួយលើទំព័រសំប៉ែត។ លេខបីដាក់ចំណុចមួយក្នុងបន្ទប់។ លេខបួន ដប់ ឬមួយពាន់ដាក់ចំណុចមួយ ក្នុងលំហដែលយើងមិនអាចស្រមៃឃើញ ប៉ុន្តែនព្វន្ធដំណើរការតាមរបៀបដូចគ្នា។

ស្រមៃផែនទីមួយដែល របស់ស្រដៀងគ្នាត្រូវបានដាក់នៅជិតគ្នា៖

           animal
             ^
   cat  dog  |
    puppy    |
             |
             +------------> drink
                  tea
                coffee  milk

cat, dog និង puppy ប្រមូលផ្តុំគ្នានៅម្ខាង។ coffee, tea និង milk ប្រមូលផ្តុំ នៅម្ខាងទៀត។ ទិស និង ចម្ងាយ រវាងចំណុចផ្ទុកអត្ថន័យ។ Embedding ពិតធ្វើបែបនេះ ជាមួយវិមាត្ររាប់រយ ឬរាប់ពាន់ ប៉ុន្តែនៅទីនេះយើងនឹងប្រើបួន ដើម្បីឲ្យអ្នកអានរាល់លេខបាន។

3. Word Vector ធ្វើដោយដៃ

ក្នុង model ពិត គ្មាននរណាសរសេរលេខដោយដៃទេ។ សម្រាប់ការរៀន យើងនឹងសរសេរ ដើម្បីឲ្យអ្នកឃើញមូលហេតុដែលវាដំណើរការ។ ពាក្យនីមួយៗទទួលបានលេខបួន។ យើងកំណត់ថា វិមាត្រ ទាំងបួនមានន័យ [drink, animal, tech, person]៖ ថាតើពាក្យនោះអំពីប្រធានបទនីមួយៗ ប៉ុនណា ពី 0.0 ដល់ 1.0។

បង្កើត src/embeddings.js៖

// src/embeddings.js
// Hand-made 4-number vectors. Dimensions: [drink, animal, tech, person].
// Real models LEARN their vectors, with hundreds or thousands of numbers.
export const vectors = {
  coffee:  [0.9, 0.0, 0.1, 0.0],
  tea:     [0.8, 0.0, 0.0, 0.1],
  milk:    [0.7, 0.2, 0.0, 0.0],
  cat:     [0.1, 0.9, 0.0, 0.1],
  dog:     [0.0, 0.9, 0.0, 0.2],
  puppy:   [0.0, 0.8, 0.0, 0.3],
  phone:   [0.0, 0.0, 0.9, 0.1],
  laptop:  [0.0, 0.0, 0.9, 0.2],
  app:     [0.0, 0.0, 0.8, 0.3],
  student: [0.1, 0.0, 0.2, 0.9],
  teacher: [0.0, 0.0, 0.2, 0.9],
  sokha:   [0.2, 0.0, 0.1, 0.9],
};

ដប់ពីរពាក្យ លេខបួនក្នុងមួយពាក្យ។ coffee ភាគច្រើនជា "drink" ជាមួយតិចតួចនៃ "tech" (laptop និងហាងកាហ្វេ បន្តិចបន្តួចនៃការបន្លំលេងសើច)។ puppy ភាគច្រើនជា "animal" ជាមួយ "person" បន្តិច។ ពាក្យអំពីប្រធានបទតែមួយទទួលបានជួរលេខស្រដៀងគ្នា។

ក្នុង model ពិត វិមាត្រ គ្មានឈ្មោះ ទេ។ គ្មាននរណាសម្រេចថាវិមាត្រទី 3 មានន័យថា "tech" ទេ។ លេខត្រូវបានរៀន ហើយអត្ថន័យត្រូវបានចែកចាយលើគ្រប់វិមាត្ររួមគ្នា។ យើងដាក់ស្លាកវិមាត្រ របស់យើងតែដើម្បីឲ្យអ្នកតាមដានគំនិតបាន។

4. Dot Product និង Norm

ដើម្បីវាស់ថា vector ពីរទាក់ទងគ្នាប៉ុនណា យើងត្រូវការឧបករណ៍តូចពីរ។

Dot product គុណ vector ពីរតាមទីតាំងម្តងមួយ ហើយបូកលទ្ធផល៖

dot([1, 2, 3], [4, 5, 6]) = 1*4 + 2*5 + 3*6 = 4 + 10 + 18 = 32

Vector ដែលធំនៅទីតាំង ដូចគ្នា ផ្តល់ dot product ធំ។ Vector ដែលធំនៅទីតាំង ខុសគ្នា ផ្តល់តូច។ ដូច្នេះ dot product គឺជាពិន្ទុប្រហាក់ប្រហែលនៃ "តើពីរនេះយល់ស្របគ្នាប៉ុនណា"។

Norm គឺជាប្រវែងនៃ vector ពី Pythagoras៖ ធ្វើការេរាល់លេខ បូកវា យកឬសការេ។ សម្រាប់ [3, 4] វាគឺ sqrt(9 + 16) = 5 ដូចអង្កត់ទ្រូងនៃត្រីកោណ 3-4-5។

បន្ថែម function ទាំងពីរទៅ src/embeddings.js៖

export function dot(a, b) {
  return a.reduce((sum, x, i) => sum + x * b[i], 0);
}

export function norm(a) {
  return Math.sqrt(dot(a, a));
}

(norm គឺជាឬសការេនៃ dot product នៃ vector ជាមួយខ្លួនវាផ្ទាល់៖ 3*3 + 4*4 = 25 ហើយ sqrt(25) = 5។) ពិនិត្យវា៖

// demo-dot.js
import { dot, norm } from "./src/embeddings.js";
console.log(dot([1, 2, 3], [4, 5, 6]));
console.log(norm([3, 4]));
// 32
// 5

ទាំងពីរបន្ទាត់ត្រឹមត្រូវពិតប្រាកដ និងមានស្ថេរភាព។

5. Cosine Similarity៖ តើពាក្យពីរជិតគ្នាប៉ុនណា?

Dot product ឆៅមានចំណុចខ្វះខាតមួយ៖ vector វែងផ្តល់ dot product ធំ គ្រាន់តែព្រោះវាវែង។ យើងខ្វល់អំពី ទិស "តើពួកវាចង្អុលទៅផ្លូវដូចគ្នាទេ?" មិនមែនប្រវែងទេ។ ដូច្នេះយើងចែក នឹងប្រវែងទាំងពីរ។ លទ្ធផលគឺ cosine similarity៖

// 1 = same direction, 0 = unrelated, -1 = opposite.
export function cosine(a, b) {
  return dot(a, b) / (norm(a) * norm(b));
}

អានលទ្ធផលជាពិន្ទុភាពស្រដៀងគ្នា។ 1 មានន័យថាចង្អុលទៅផ្លូវដូចគ្នាពិតប្រាកដ 0 មានន័យថា គ្មានអ្វីរួមគ្នា ហើយ (ជាមួយ vector ដែលអាចអវិជ្ជមាន) -1 មានន័យថាផ្ទុយគ្នា។ Vector ធ្វើដោយដៃរបស់យើងគ្មានលេខអវិជ្ជមានទេ ដូច្នេះរបស់យើងនឹងស្ថិតចន្លោះ 0 និង 1។

// demo-cosine.js
import { vectors, cosine } from "./src/embeddings.js";
console.log(cosine(vectors.coffee, vectors.tea).toFixed(3));
console.log(cosine(vectors.coffee, vectors.dog).toFixed(3));
console.log(cosine(vectors.cat, vectors.dog).toFixed(3));
console.log(cosine(vectors.phone, vectors.laptop).toFixed(3));
// 0.986
// 0.000
// 0.988
// 0.994

coffee និង tea ទទួលបាន 0.986៖ ស្ទើរតែទិសដូចគ្នា។ coffee និង dog ទទួលបាន 0.000 ពិតប្រាកដ៖ គ្មានអ្វីរួមគ្នា។ cat ជាមួយ dog និង phone ជាមួយ laptop ក៏ទទួលបានលើស 0.98 ដែរ។ លេខប្រាប់អ្វីដែលអ្នកនឹងនិយាយ៖ coffee ដូច tea ឆ្មាដូចឆ្កែ ទូរស័ព្ទដូច laptop ហើយ coffee មិនដូចឆ្កែទេ។

ទាំងបួនបន្ទាត់មានស្ថេរភាព ព្រោះគ្មានភាពចៃដន្យ។ តម្លៃគឺជារបស់យើងដែលធ្វើដោយដៃ ដូច្នេះវាមិនបញ្ជាក់អ្វីអំពីភាសាពិតទេ គ្រាន់តែថាវិធីសាស្ត្រដំណើរការនៅពេល vector ល្អ។

6. អ្នកជិតខាងជិតបំផុត

ដោយមាន vector ណាមួយ យើងអាចរកពាក្យជិតបំផុតដោយដាក់ពិន្ទុរាល់ពាក្យ ហើយតម្រៀប។ នោះគឺ nearest៖

// The k words whose vectors are most similar to vec.
export function nearest(vec, k = 3, table = vectors) {
  return Object.entries(table)
    .map(([word, v]) => ({ word, score: cosine(vec, v) }))
    .sort((a, b) => b.score - a.score)
    .slice(0, k);
}

វាត្រឡប់ object k ដូចជា { word: "tea", score: 0.986 }។ ពាក្យជិតបំផុតនឹង vector របស់ពាក្យមួយគឺជាពាក្យនោះផ្ទាល់ជានិច្ច (ពិន្ទុ 1) ដូច្នេះដើម្បីឃើញអ្នកជិតខាងរបស់វា យើងសុំ k + 1 ហើយរំលងមួយទីមួយ៖

// demo-nearest.js
import { vectors, nearest } from "./src/embeddings.js";

const fmt = (r) => `${r.word} ${r.score.toFixed(2)}`;
for (const w of ["coffee", "dog", "laptop", "teacher"]) {
  console.log(w, "->", nearest(vectors[w], 4).slice(1).map(fmt).join(", "));
}

// A made-up query: "something to drink", all weight on dimension 0.
console.log("query [1,0,0,0] ->", nearest([1, 0, 0, 0], 3).map(fmt).join(", "));
// coffee -> tea 0.99, milk 0.96, sokha 0.23
// dog -> puppy 0.99, cat 0.99, milk 0.27
// laptop -> phone 0.99, app 0.99, teacher 0.42
// teacher -> student 0.99, sokha 0.97, app 0.55
// query [1,0,0,0] -> coffee 0.99, tea 0.99, milk 0.96

អ្នកជិតខាងជិតបំផុតរបស់ coffee គឺ tea និង milk។ របស់ dog គឺ puppy និង cat។ របស់ laptop គឺ phone និង app។ បន្ទាប់ពីពីរ ឬបីដំបូង ពិន្ទុធ្លាក់ចុះខ្លាំង៖ អ្នកជិតខាងទីបីរបស់ coffee គឺ sokha នៅ 0.23 ដែលជាមូលដ្ឋាន "គ្មានអ្វីរួមគ្នា"។

បន្ទាត់ចុងក្រោយគឺគួរឲ្យចាប់អារម្មណ៍។ យើងមិនបានរកមើលពាក្យទេ។ យើងបង្កើត ទិស មួយ ("drink") ហើយសួរ៖ អ្វីខ្លះនៅជិតវា? ចម្លើយគឺ drink ទាំងបី។ ប្រតិបត្តិការ "រកអ្វីដែលនៅជិត vector នេះ" គឺជាបេះដូងនៃ vector search។

7. Embedding ពិតត្រូវបានរៀន

តើ vector ពិតមកពីណា? មកពី ការបង្វឹក ដោយប្រើ loop ដូចគ្នាដែលអ្នកបានឃើញក្នុងជំពូក ចុងក្រោយ។ Vector ជា parameter ជាតារាងមួយជួរក្នុងមួយ token ហើយវាចាប់ផ្តើមជាលេខចៃដន្យ។ នៅពេល model បង្វឹកលើអត្ថបទ gradient descent រុញវា។ ពាក្យដែលប្រើក្នុង context ស្រដៀងគ្នា ("a cup of ___", "I drank some ___") ទទួលបានការរុញស្រដៀងគ្នា ហើយរសាត់ទៅរកគ្នា។ បន្ទាប់ពីការបង្វឹកគ្រប់គ្រាន់ coffee និង tea ចុងក្រោយនៅជិតគ្នា ព្រោះ model ត្រូវការ ពួកវាជិតគ្នាដើម្បីទាយអត្ថបទបានល្អ។

ចំណុចស្មោះត្រង់បី៖

  • គ្មាននរណាដាក់ស្លាកវិមាត្រទេ។ គ្មានអ័ក្ស "drink" ទេ។ រចនាសម្ព័ន្ធលេចចេញពីទិន្នន័យ។
  • វាផ្ទុកទម្លាប់របស់ទិន្នន័យ។ ប្រសិនបើ corpus ប្រើពាក្យខ្លះជាមួយគ្នាតាមរបៀបមាន bias vector ឆ្លុះបញ្ចាំងវា។ នេះជាវិធីមួយដែល bias ពីជំពូកបង្វឹកទៅដល់ model។
  • មានប្រភេទផ្សេងៗ។ Embedding model ខ្លះត្រូវបានសាងសង់ តែ ដើម្បីបំប្លែងអត្ថបទទៅ ជា vector សម្រាប់ស្វែងរក។ Vector នៅខាងក្នុង chat model ត្រូវបានសាងសង់ដើម្បីជួយ ទាយ token បន្ទាប់។ ទាំងពីរត្រូវបានរៀន ដោយមានគោលដៅខុសគ្នា។

Vector ពិតជាធម្មតាមានលេខរាប់រយទៅរាប់ពាន់ មិនមែនបួនទេ ហើយ model ពិតរក្សាទុកមួយ សម្រាប់រាល់ token ក្នុង vocabulary របស់វា។ គ្មានអ្វីអំពីប្រដាប់ក្មេងលេងលេខបួនរបស់យើង ជារចនាសម្ព័ន្ធពិតទេ ប៉ុន្តែប្រតិបត្តិការ (dot, cosine, nearest) គឺពិតជាអ្វីដែលត្រូវបានប្រើ។

8. ស្វែងរកតាមអត្ថន័យ

ការស្វែងរកតាម keyword រកឯកសារដែលមាន អក្សរ ដូចគ្នានឹង query របស់អ្នក។ ស្វែងរក "puppy" ហើយឯកសារដែលនិយាយតែ "dog" ត្រូវបានខកខាន។ Embedding អនុញ្ញាតឲ្យអ្នកស្វែងរកតាម អត្ថន័យ៖ បំប្លែងទាំង query និងរាល់ឯកសារទៅជា vector បន្ទាប់មករកអ្នកជិតបំផុត។

សម្រាប់ vector ប្រយោគរហ័ស យើងប្រើល្បិចសាមញ្ញបំផុត៖ មធ្យមភាគ នៃ word vector។

// demo-search.js
import { vectors, cosine } from "./src/embeddings.js";

// A sentence vector: the average of its word vectors (words we know).
function embed(text) {
  const vecs = text.split(" ").filter((w) => vectors[w]).map((w) => vectors[w]);
  return vecs[0].map((_, d) => vecs.reduce((s, v) => s + v[d], 0) / vecs.length);
}

const docs = [
  "coffee tea milk",
  "cat dog puppy",
  "phone laptop app",
  "student teacher sokha",
];

function search(query) {
  const q = embed(query);
  return docs
    .map((doc) => ({ doc, score: cosine(q, embed(doc)) }))
    .sort((a, b) => b.score - a.score);
}

for (const query of ["puppy", "tea", "laptop app", "teacher"]) {
  const best = search(query)[0];
  console.log(query.padEnd(11), "->", best.doc, best.score.toFixed(2));
}
// puppy       -> cat dog puppy 0.99
// tea         -> coffee tea milk 0.99
// laptop app  -> phone laptop app 1.00
// teacher     -> student teacher sokha 0.99

Query puppy រកឃើញឯកសារ cat dog puppy ហើយ teacher រកឃើញឯកសារមនុស្ស ទោះបី ពាក្យ query មិនមែនជាឯកសារទាំងមូលក៏ដោយ។ ប្រព័ន្ធពិតធ្វើដូចគ្នាជាមួយ embedding model ដែលបានរៀន និងឯកសារច្រើនជាងនេះ។

គំនិតតែមួយនេះ embed អ្វីៗទាំងអស់ បន្ទាប់មករក vector ជិតបំផុត គឺជារបៀបដែលកម្មវិធី AI ច្រើនអនុញ្ញាតឲ្យ model រកមើលអ្វីៗក្នុងឯកសាររបស់អ្នកផ្ទាល់។ វាជាមូលដ្ឋាននៃបច្ចេកទេសមួយ ដែលហៅថា retrieval-augmented generation (RAG) ដែលស៊េរីក្រោយគ្របដណ្តប់។ សម្រាប់ឥឡូវ សូមចងចាំថាវាគឺជា cosine similarity ធម្មតា ពិតជាអ្វីដែលអ្នកទើបតែសរសេរ។

9. បញ្ហា៖ មួយ Vector ក្នុងមួយពាក្យមិនគ្រប់គ្រាន់

Embedding គឺជាការចាប់ផ្តើមដ៏ល្អ ប៉ុន្តែវាមានដែនកំណត់។ ពាក្យនីមួយៗមាន vector មួយ មិនថាវាលេចឡើងនៅទីណាទេ។ គិតអំពីពាក្យ mouse។ ក្នុង "the mouse ran away from the cat" វាជាសត្វ។ ក្នុង "click the mouse to open the laptop" វាជាឧបករណ៍។ Vector ថេរមិនអាច ជាទាំងពីរបានទេ។ យើងអាចឲ្យ mouse vector លាយ ពាក់កណ្តាល animal ពាក់កណ្តាល tech ដូចជា [0.0, 0.5, 0.5, 0.0] ប៉ុន្តែបន្ទាប់មកវាត្រូវតែ ពាក់កណ្តាល គ្រប់ទីកន្លែង។

ភាសាពេញដោយរឿងនេះ៖ "bank" (មាត់ទន្លេ ឬប្រាក់) "light" (មិនធ្ងន់ ឬមិនងងឹត) "match" (ល្បែង ឬភ្លើង)។ អ្វីដែលយើងត្រូវការគឺវិធីមួយដើម្បីឲ្យ vector របស់ពាក្យ ផ្លាស់ប្តូរអាស្រ័យលើ ពាក្យជុំវិញ។ Mouse ក្បែរ cat គួរងាកទៅរក animal ចំណែក mouse ក្បែរ laptop ទៅរក tech។

នោះគឺជាអ្វីដែល attention ធ្វើពិតប្រាកដ។

10. Attention៖ ពាក្យមើលពាក្យ

គំនិតរបស់ attention សាមញ្ញក្នុងការនិយាយជាពាក្យធម្មតា៖

សម្រាប់ពាក្យនីមួយៗ សូមមើលពាក្យផ្សេងទៀតទាំងអស់ក្នុងប្រយោគ សម្រេចថាពាក្យនីមួយៗសំខាន់ ប៉ុនណា ចំពោះពាក្យនេះ ហើយលាយព័ត៌មានរបស់ពួកវាចូលដោយប្រើចំនួនទាំងនោះ។

បីជំហាន ដែលអ្នកដឹងស្រាប់ទាំងអស់៖

  1. ពិន្ទុ។ សម្រាប់ពាក្យមួយ (ពាក្យដែលកំពុងមើល) គណនា dot product ជាមួយរាល់ពាក្យ ក្នុងប្រយោគ រួមទាំងខ្លួនវាផ្ទាល់។ ពិន្ទុខ្ពស់មានន័យថា "ពីរនេះទាក់ទងគ្នា"។
  2. ទម្ងន់។ បញ្ជូនពិន្ទុតាម softmax (ពី Probabilities, Temperature and Sampling) ដើម្បីឲ្យវាក្លាយជាភាគរយដែលបូកស្មើ 1។ ទាំងនេះគឺ attention weight។
  3. លាយ។ បង្កើត vector ថ្មីសម្រាប់ពាក្យ៖ មធ្យមភាគមានទម្ងន់ នៃ word vector ទាំងអស់ ដោយប្រើទម្ងន់ទាំងនោះ។ ពាក្យដែលមានទម្ងន់ 0.8 រួមចំណែក 80% នៃ vector ថ្មី។

Vector ថ្មីគឺជាពាក្យដដែល ក្នុង context។ ពាក្យបានមើលអ្នកជិតខាងរបស់វា ហើយស្រូបយក អ្វីមួយពីពួកគេ។ Transformer ពិតធ្វើបែបនេះសម្រាប់រាល់ពាក្យក្នុងពេលតែមួយ ក្នុងរាល់ស្រទាប់។

11. Attention ក្នុងកូដ

បង្កើត src/attention.js។ វា import dot ពីឯកសារដែលអ្នកទើបតែសរសេរ និង softmax ពីជំពូក sampling៖

// src/attention.js
import { dot } from "./embeddings.js";
import { softmax } from "./sampling.js";

// For each word, score every word (dot product), turn scores into
// weights with softmax, then blend the vectors using those weights.
export function attention(sentence, table, scale = 1) {
  const vecs = sentence.map((w) => table[w]);
  const weights = [];
  const blended = [];
  for (const q of vecs) {
    const scores = vecs.map((k) => dot(q, k) * scale);
    const w = softmax(scores);
    weights.push(w);
    blended.push(
      q.map((_, d) => w.reduce((sum, wi, i) => sum + wi * vecs[i][d], 0)),
    );
  }
  return { weights, blended };
}

export function printTable(sentence, weights) {
  const pad = Math.max(...sentence.map((w) => w.length)) + 1;
  console.log("".padEnd(pad) + sentence.map((w) => w.padStart(pad)).join(""));
  sentence.forEach((w, i) => {
    console.log(
      w.padEnd(pad) + weights[i].map((x) => x.toFixed(2).padStart(pad)).join(""),
    );
  });
}

attention(sentence, table, scale) ទទួល array នៃពាក្យ តារាងរកមើលពីពាក្យទៅ vector និង scale។ សម្រាប់រាល់ពាក្យ q ក្នុងប្រយោគ វា៖

  • ដាក់ពិន្ទុរាល់ពាក្យ k ដោយ dot(q, k) គុណនឹង scale។
  • បំប្លែងពិន្ទុទៅជា w ដោយ softmax។
  • សាងសង់ vector លាយ៖ សម្រាប់រាល់វិមាត្រ d ផលបូកនៃ weight * លេខរបស់ពាក្យនោះ។

វាត្រឡប់ពីរ៖ weights (ជួរភាគរយមួយក្នុងមួយពាក្យ) និង blended (vector ថ្មីដែលដឹង context)។

scale គឺជាប៊ូតុងភាពមុត៖ តម្លៃធំជាងធ្វើឲ្យ softmax ជ្រើសរើសអ្នកដែលវាចូលចិត្តកាន់តែខ្លាំង។ Transformer ពិតមានកត្តាមាត្រដ្ឋានថេរដែលភ្ជាប់មកស្រាប់ (វាចែកនឹងអ្វីមួយទាក់ទងនឹងប្រវែង vector) ដូច្នេះអ្នកអាចគិតថាវាជាផ្នែកមួយនៃរូបមន្ត។

12. អានតារាង Attention

សាកល្បងវាលើប្រយោគបីពាក្យ។ យើងបន្ថែម vector សម្រាប់ drinks ព្រោះវាមិនមានក្នុង បញ្ជីពាក្យរបស់យើង៖

// demo-attention.js
import { vectors } from "./src/embeddings.js";
import { attention, printTable } from "./src/attention.js";

const table = { ...vectors, drinks: [0.8, 0.0, 0.0, 0.3] };
const sentence = ["sokha", "drinks", "tea"];

console.log("scale 1:");
printTable(sentence, attention(sentence, table, 1).weights);
console.log("scale 4:");
const { weights, blended } = attention(sentence, table, 4);
printTable(sentence, weights);
console.log("tea before:", table.tea.join(" "));
console.log("tea after: ", blended[2].map((x) => x.toFixed(2)).join(" "));
// scale 1:
//          sokha drinks    tea
// sokha     0.46   0.30   0.25
// drinks    0.28   0.37   0.35
// tea       0.25   0.38   0.37
// scale 4:
//          sokha drinks    tea
// sokha     0.79   0.14   0.07
// drinks    0.14   0.48   0.38
// tea       0.09   0.47   0.44
// tea before: 0.8 0 0 0.1
// tea after:  0.75 0.00 0.01 0.27

ជួរដេក នីមួយៗគឺជាពាក្យមួយដែលកំពុងមើល។ ជួរឈរ នីមួយៗគឺជាពាក្យដែលត្រូវបានមើល។ ជួរដេកមួយបូកស្មើ 1 (ដល់ការបង្គត់)។ សូមអានតារាង scale 4៖

  • ជួរ tea៖ ពេលដំណើរការ "tea" 47% នៃ attention ទៅ drinks 44% ទៅខ្លួនវា ហើយ តែ 9% ទៅ sokha។ ពាក្យ tea មើលភាគច្រើនទៅកិរិយាសព្ទ drinks និងខ្លួនវា ព្រោះ ទាំងពីរជាពាក្យ "drink" ដែលមានលេខស្រដៀងគ្នា។
  • ជួរ sokha៖ ពាក្យមនុស្សភាគច្រើនមើលខ្លួនវា (79%) ព្រោះគ្មានអ្វីផ្សេងក្នុងប្រយោគ អំពីមនុស្សទេ។

នៅ scale 1 តារាងស្ទើរតែរាបស្មើ (អ្វីៗទទួលបានប្រហែលមួយភាគបី) ព្រោះ vector របស់យើង ជាលេខតូច ហើយពិន្ទុមិនខុសគ្នាច្រើនទេ។ scale ធំជាងធ្វើឲ្យរូបភាពមុតជាង។ Model ពិតក៏ រៀន របៀបមើលដែរ ដែលយើងរំលង។

ឥឡូវនេះជាផលប៉ះពាល់។ tea ចាប់ផ្តើមជា 0.8 0 0 0.1។ បន្ទាប់ពី attention វាជា 0.75 0.00 0.01 0.27។ វាទទួលបាន "person" ខ្លះ (ពី drinks ដែលមានផ្នែកដូចមនុស្ស និង sokha)៖ ឥឡូវវាផ្ទុកដានតិចតួចនៃ "តែនេះកំពុងត្រូវបានផឹកដោយនរណាម្នាក់"។ ពាក្យត្រូវបាន ផ្លាស់ប្តូរដោយ context របស់វា។

អ្នកអាចពិនិត្យមួយជួរដោយដៃ។ សម្រាប់ tea ពិន្ទុទាំងបីគឺ dot product ជាមួយ sokha drinks និង tea៖

// demo-hand.js
import { vectors, dot } from "./src/embeddings.js";
import { softmax } from "./src/sampling.js";

const tea = vectors.tea;
const sentence = ["sokha", "drinks", "tea"];
const table = { ...vectors, drinks: [0.8, 0.0, 0.0, 0.3] };
const scores = sentence.map((w) => dot(tea, table[w]));
console.log("scores :", scores.map((s) => s.toFixed(2)).join(" "));
console.log("scaled :", scores.map((s) => (s * 4).toFixed(2)).join(" "));
console.log("weights:", softmax(scores.map((s) => s * 4)).map((p) => p.toFixed(2)).join(" "));
// scores : 0.25 0.67 0.65
// scaled : 1.00 2.68 2.60
// weights: 0.09 0.47 0.44

ទាំងនេះគឺ 0.09 0.47 0.44 ដូចគ្នាទៅនឹងជួរ tea ខាងលើ។ គ្មានវេទមន្តទេ៖ dot product softmax មធ្យមភាគមានទម្ងន់។ រាល់លេខក្នុងលទ្ធផលទាំងនេះមានស្ថេរភាពនៅគ្រប់ការដំណើរការ។

13. Context ផ្លាស់ប្តូរអត្ថន័យ

ឥឡូវនេះផលប្រយោជន៍ ជាមួយពាក្យមិនច្បាស់លាស់ mouse ពីផ្នែកទី 9។ ឲ្យវា vector លាយ ពាក់កណ្តាល animal ពាក់កណ្តាល tech បន្ទាប់មកដាក់វាក្នុង context ពីរពាក្យខុសគ្នាពីរ៖

// demo-mouse.js
import { vectors, nearest } from "./src/embeddings.js";
import { attention, printTable } from "./src/attention.js";

const table = { ...vectors, mouse: [0.0, 0.5, 0.5, 0.0] };
const fmt = (x) => `${x.word} ${x.score.toFixed(2)}`;

console.log("mouse alone:", nearest(table.mouse, 3).map(fmt).join(", "));

for (const other of ["cat", "laptop"]) {
  const s = ["mouse", other];
  const r = attention(s, table, 4);
  console.log(`\n"${s.join(" ")}"`);
  printTable(s, r.weights);
  console.log("mouse now closest to:", nearest(r.blended[0], 2).map(fmt).join(", "));
}
// mouse alone: phone 0.70, cat 0.70, dog 0.69
//
// "mouse cat"
//        mouse   cat
// mouse   0.55  0.45
// cat     0.18  0.82
// mouse now closest to: cat 0.93, dog 0.91
//
// "mouse laptop"
//          mouse laptop
// mouse     0.55   0.45
// laptop    0.17   0.83
// mouse now closest to: phone 0.93, laptop 0.92

ឯកឯង mouse នៅជិត phone និង cat ស្មើគ្នា (ប្រហែល 0.70) ដូច្នេះ vector ថេរមិនអាច ជ្រើសរើសបានទេ។ បន្ទាប់ពី attention ក្នុង "mouse cat" mouse vector ថ្មីនៅជិត cat និង dog បំផុត។ បន្ទាប់ពី attention ក្នុង "mouse laptop" វានៅជិត phone និង laptop បំផុត។ ពាក្យដូចគ្នា បរិយាកាសខុសគ្នា vector ខុសគ្នា។ នោះគឺជារបៀបដែល Transformer អាចដោះស្រាយ "bank" ក្នុងប្រយោគមាត់ទន្លេ និងក្នុងប្រយោគប្រាក់។

នេះជាមូលហេតុដែល attention សំខាន់ខ្លាំង។ វាធ្វើឲ្យអត្ថន័យអាស្រ័យលើ context ដោយប្រើតែ ជំហានសាមញ្ញដែលអ្នកទើបតែដំណើរការ។

14. Transformer ពិត ដោយស្មោះត្រង់

អ្វីដែលអ្នកបានសាងសង់គឺជា រូបភាពសាមញ្ញ នៃគំនិតនៅចំណុចកណ្តាលនៃ Transformer ដែលជា ការរចនាដែលនៅពីក្រោយ LLM សម័យទំនើប។ នេះជាអ្វីដែលខុសគ្នានៅក្នុងរបស់ពិត។ ព័ត៌មានលម្អិត ជាគំនិត៖ កុំយកប្រដាប់ក្មេងលេងរបស់យើងជាការណែនាំអនុវត្តន៍ឡើយ។

  • ការរកមើលដែលបានរៀន។ ក្នុង demo របស់យើង ពាក្យនីមួយៗត្រូវបានដាក់ពិន្ទុដោយប្រើ vector របស់វាផ្ទាល់។ Model ពិតដំបូងបញ្ជូនរាល់ vector តាម matrix ដែលបានរៀនដើម្បីបង្កើត កំណែបីរបស់វា (ជាធម្មតាហៅថា query, key និង value)៖ មួយសម្រាប់ "អ្វីដែលខ្ញុំកំពុង រក" មួយសម្រាប់ "អ្វីដែលខ្ញុំផ្តល់" មួយសម្រាប់ "អ្វីដែលខ្ញុំនឹងរួមចំណែក"។ Matrix ទាំងនេះជា parameter ហើយត្រូវបានបង្វឹកដូចអ្វីៗផ្សេងទៀត។
  • ច្រើន head។ Model ពិតដំណើរការប្រតិបត្តិការ attention ច្រើនស្របគ្នា ហៅថា head ដែលនីមួយៗសេរីក្នុងការរៀនទំនាក់ទំនងប្រភេទផ្សេង (មួយអាចតាមដានវេយ្យាករណ៍ មួយទៀត តាមដានថា pronoun សំដៅលើ noun មួយណា)។ យើងបានសាងសង់ head មួយ ដោយគ្មានការរៀន។
  • ច្រើនស្រទាប់។ លទ្ធផលនៃ attention ឆ្លងកាត់នព្វន្ធដែលបានរៀនបន្ថែម ហើយបន្ទាប់មក ចូលទៅជុំ attention មួយទៀត ដែលជង់ច្រើនដប់ដង។ ស្រទាប់ដំបូងមានទំនោរចាប់យកគំរូសាមញ្ញ ហើយស្រទាប់ក្រោយៗចាប់យកគំរូអរូបីជាង ទោះបីជារបៀបពិតប្រាកដនៅតែជាផ្នែកស្រាវជ្រាវសកម្ម។
  • លំដាប់សំខាន់។ ប្រដាប់ក្មេងលេងរបស់យើងមិនខ្វល់លំដាប់ពាក្យទេ។ Model ពិតបន្ថែម ព័ត៌មានអំពី ទីតាំង របស់ token នីមួយៗ បើមិនដូច្នេះទេ "dog bites man" នឹងមើលទៅដូច "man bites dog"។
  • មិនបានលួចមើលទៅមុខ។ នៅពេលទាយ token បន្ទាប់ model ត្រូវបានអនុញ្ញាតឲ្យ attend តែ token ដែលនៅ មុន ពេលបច្ចុប្បន្នប៉ុណ្ណោះ មិនមែន token អនាគតទេ។
  • មាត្រដ្ឋាន។ Vector ពិតមានលេខរាប់រយ ឬរាប់ពាន់ ហើយ vocabulary មាន token រាប់មុឺន។

Pipeline ពីទិដ្ឋភាពទូទៅនៃស៊េរីឥឡូវមានរូបរាងកាន់តែច្រើន៖ អត្ថបទត្រូវបានបំបែកទៅជា token រាល់ token ក្លាយជា embedding ស្រទាប់ attention ដែលជង់លាយ context ចូលក្នុងរាល់ vector ហើយ vector ចុងក្រោយត្រូវបានបំប្លែងដោយជំហានដែលបានរៀនមួយទៀតទៅជាពិន្ទុសម្រាប់រាល់ token បន្ទាប់ដែលអាចមាន។ ពិន្ទុទាំងនោះឆ្លងកាត់ softmax និង sampling ដូចក្នុងជំពូកទី 4។

ត្រូវប្រុងប្រយ័ត្នជាមួយក្តីស្មានផងដែរ។ វាគួរឲ្យទាក់ទាញក្នុងការនិយាយថា "attention គឺជា របៀបដែល model យល់" ប៉ុន្តែនោះគឺជារឿងដែលយើងប្រាប់។ អ្នកស្រាវជ្រាវសិក្សាអ្វីដែល attention head ធ្វើ ហើយរូបភាពគឺមិនពេញលេញ៖ head ខ្លះហាក់ដូចជាធ្វើអ្វីមួយដែលងាយ ពណ៌នា ចំណែកច្រើនទៀតមិនមែនទេ។ ចាត់ទុកតារាងដែលអ្នកទើបតែបោះពុម្ពជាការបង្ហាញបង្រៀន នៃយន្តការមួយ មិនមែនជាបង្អួចចូលទៅក្នុងចិត្តទេ។

15. រូបមន្ត៖ Module embeddings និង attention

អ្នកបានបង្កើតឯកសារទាំងពីរនៅពេលដើរមកហើយ។ រួមគ្នា៖

  • src/embeddings.js export vectors, dot(a, b), norm(a), cosine(a, b) និង nearest(vec, k = 3, table = vectors)។
  • src/attention.js export attention(sentence, table, scale = 1) និង printTable(sentence, weights)។ វាត្រូវការ softmax ពី src/sampling.js ពី Probabilities, Temperature and Sampling។

រាល់ demo ក្នុងជំពូកនេះគឺជាឯកសារតូចមួយនៅជាប់ src/ (ឧទាហរណ៍ demo-nearest.js) ដែលអ្នកដំណើរការដោយ node demo-nearest.js។ ត្រូវប្រាកដថា "type": "module" ត្រូវបាន កំណត់ក្នុង package.json ដូចក្នុងជំពូកទី 1 ដើម្បីឲ្យ import ដំណើរការ។

16. សាកល្បងដោយខ្លួនឯង

  1. បន្ថែមពាក្យ។ បន្ថែម juice: [0.8, 0.0, 0.0, 0.0] និង tablet (ជ្រើសលេខរបស់វា)។ បោះពុម្ព nearest សម្រាប់ពាក្យនីមួយៗ។ តើពួកវាធ្លាក់ត្រង់កន្លែងដែលអ្នករំពឹងទេ?
  2. ធ្វើឲ្យ vector ខូច។ ឲ្យ tea លេខ [0.0, 0.0, 0.9, 0.0] (ធ្វើឲ្យវាមើលទៅដូច tech) ហើយបោះពុម្ព cosine(coffee, tea) ម្តងទៀត។ តើមានអ្វីកើតឡើងចំពោះលទ្ធផលស្វែងរកសម្រាប់ "tea"? តើនេះប្រាប់អ្នកអ្វីអំពីសារៈសំខាន់នៃ embedding ល្អ?
  3. ពាក្យប្លែកមួយ។ សរសេរ oddOneOut(words) ដែលត្រឡប់ពាក្យដែលមាន cosine similarity មធ្យម ទាបបំផុតទៅនឹងពាក្យផ្សេង។ សាកល្បង ["coffee", "tea", "milk", "laptop"]។
  4. ឯកសារបន្ថែម។ បន្ថែមឯកសារបួនទៀតទៅ search demo រួមទាំងមួយដែលមានពាក្យមិនមាន ក្នុង vectors (កូដរំលងពាក្យមិនស្គាល់)។ តើអ្វីខូចប្រសិនបើឯកសារមានតែពាក្យមិនស្គាល់ ហើយតើអ្នកនឹងការពារយ៉ាងដូចម្តេច?
  5. ផ្លាស់ប្តូរ scale។ ដំណើរការតារាង attention "sokha drinks tea" ជាមួយ scale កំណត់ជា 0.1, 1, 10 និង 50។ ពណ៌នាអ្វីដែលកើតឡើងចំពោះជួរនីមួយៗ។ នៅ scale ធំខ្លាំង តើពាក្យនីមួយៗ attend ទៅអ្វី?
  6. ពាក្យមិនច្បាស់លាស់ថ្មី។ បង្កើត bank ដែលមាន vector នៅពាក់កណ្តាលផ្លូវរវាង វិមាត្រ [drink, animal, tech, person] ដែលអ្នកជ្រើស ហើយសាកល្បងវាក្បែរពាក្យ "money" និងពាក្យ "river" ដែលអ្នកក៏បង្កើតដែរ។ បោះពុម្ពពាក្យជិតបំផុតបន្ទាប់ពី attention ដូចក្នុងផ្នែកទី 13។

17. សំណួរដែលអ្នកនឹងត្រូវគេសួរ

"Embedding គឺជាអ្វី ក្នុងមួយប្រយោគ?" វាគឺជាបញ្ជីលេខដែលតំណាងឲ្យពាក្យ token ឬអត្ថបទទាំងមូល ដើម្បីឲ្យរបស់ដែលមានអត្ថន័យ ស្រដៀងគ្នាទទួលបានលេខស្រដៀងគ្នា។ នោះអនុញ្ញាតឲ្យកុំព្យូទ័រវាស់ "តើរបស់ទាំងពីរនេះ ដូចគ្នាប៉ុនណា" ដោយនព្វន្ធ។

"អ្នកណាសម្រេចថាលេខមានន័យអ្វី?" គ្មាននរណាទេ។ វាត្រូវបានរៀនក្នុងពេលបង្វឹក៖ model រុញលេខដើម្បីឲ្យការទាយរបស់វាកាន់តែល្អ ហើយពាក្យដែលប្រើតាមរបៀបស្រដៀងគ្នាចុងក្រោយទទួលបានលេខស្រដៀងគ្នា។ វិមាត្រនីមួយៗ ជាធម្មតាគ្មានអត្ថន័យដែលមនុស្សអានបាន។

"Attention គឺជាអ្វី?" វាគឺជាវិធីដែល model អនុញ្ញាតឲ្យពាក្យនីមួយៗមើលពាក្យផ្សេងទៀត ហើយលាយព័ត៌មានពីអ្នក ដែលសំខាន់បំផុត។ តាមគណិតវិទ្យា វាគឺជាពិន្ទុសម្រាប់រាល់គូពាក្យ បំប្លែងទៅជាភាគរយ ប្រើដើម្បីយកមធ្យមភាគមានទម្ងន់។ នោះជាមូលហេតុដែលពាក្យតែមួយអាចមានអត្ថន័យខុសគ្នា ក្នុងប្រយោគខុសគ្នា។

"តើនេះគឺជារបៀបដែល ChatGPT ដំណើរការពិតប្រាកដទេ?" វាជាគំនិតស្នូលដូចគ្នា ពង្រីកធំ ហើយមានអ្វីជាច្រើនបន្ថែម៖ matrix query/key/value ដែលបានរៀន ច្រើន head និងស្រទាប់ ព័ត៌មានទីតាំង និង parameter រាប់ពាន់លាន។ កូដរបស់ យើងគឺជាប្រដាប់ក្មេងលេងបង្រៀន។ វាបង្ហាញយន្តការ មិនមែនការអនុវត្តន៍ផលិតកម្មទេ ហើយយើង មិនអាចនិយាយព័ត៌មានលម្អិតរបស់ផលិតផលជាក់លាក់ណាមួយបានទេ។

"តើ model យល់អត្ថន័យទេ ប្រសិនបើពាក្យក្លាយជា vector?" វាចាប់យកគំរូអត្ថន័យពិតប្រាកដ ព្រោះពាក្យដែលទាក់ទងគ្នាបញ្ចប់នៅជិតគ្នា ហើយ model ប្រើវាដើម្បីទាយបានល្អ។ ថាតើនោះរាប់ជា "ការយល់" ឬអត់ គឺជាសំណួរដែលមានការជជែកដេញដោល ដែលអ្នកវិទ្យាសាស្ត្រ និងទស្សនវិទូមិនយល់ស្របគ្នា ហើយយើងនឹងត្រឡប់មកវាវិញនៅចុងបញ្ចប់នៃស៊េរី។

"តើ search engine រកលទ្ធផលតាមអត្ថន័យយ៉ាងដូចម្តេច?" វាបំប្លែង query របស់អ្នក និងឯកសារនីមួយៗទៅជា embedding បន្ទាប់មកត្រឡប់ឯកសារដែល vector របស់វានៅជិត query vector បំផុត ជារឿយៗដោយប្រើ cosine similarity។ នោះគឺជាអ្វីដែល អ្នកបានសាងសង់ក្នុងផ្នែកទី 8 គ្រាន់តែជាមួយ vector ដែលបានរៀន និងទិន្នន័យច្រើនជាងនេះ។

"ហេតុអ្វី model មិនអាចប្រើពាក្យតាមដែលវាជា?" ព្រោះកុំព្យូទ័រគណនាជាមួយលេខ ហើយព្រោះពាក្យតែឯងគ្មានគំនិតនៃភាពស្រដៀងគ្នា៖ "coffee" និង "tea" គ្មានទំនាក់ទំនងដូច "coffee" និង "dog" ជាអត្ថបទធម្មតា។ Vector ផ្តល់ឲ្យ model នូវលំហមួយដែលភាពស្រដៀងគ្នាអាចវាស់បាន។

ទៅបន្តទៀត។ ឥឡូវនេះអ្នកអាចពន្យល់ពីរបៀបដែលពាក្យក្លាយជាលេខ និងរបៀបដែលពាក្យ មើលគ្នាទៅវិញទៅមក។ ប៉ុន្តែ model អាចមើលអត្ថបទបានតែចំនួនកំណត់ក្នុងពេលតែមួយ ហើយវា ភ្លេចអ្វីៗទាំងអស់រវាង request លើកលែងតែអ្នកផ្ញើវាម្តងទៀត។ ដែនកំណត់នោះ និងល្បិច ដែលប្រើដើម្បីរស់នៅជាមួយវា គឺជាប្រធានបទនៃ Context Windows and Memory។

ទិញកាហ្វេជូនខ្ញុំ

ចូលចិត្តមេរៀននេះមែនទេ? សូមពិចារណាទិញកាហ្វេជូនខ្ញុំមួយកែវ ដើម្បីគាំទ្រការបង្កើតមាតិកាឥតគិតថ្លៃបន្ថែមទៀត!