🔢 Meaning, as a row of numbers · ความหมาย ในรูปแถวของตัวเลข · 意思,就是一排数字

transformers and sentence_transformers run on your own computer. No key, no server. Turn sentences into numbers. Search by meaning across languages. Sort mail with eight examples and no training. · transformers กับ sentence_transformers ที่รันบนเครื่องของคุณเอง ไม่ต้องมีคีย์ ไม่ต้องมีเซิร์ฟเวอร์ เปลี่ยนประโยคเป็นตัวเลข ค้นหาด้วยความหมายข้ามภาษา และแยกเมลเข้ากองด้วยตัวอย่างแปดอันโดยไม่ต้องเทรน · transformers 和 sentence_transformers,在你自己的电脑上跑,不用密钥也不用服务器。把句子变成数字,跨语言按意思搜索,用八个例子把邮件分堆,不用训练。

🇬🇧 English

Two libraries from Hugging Face. They run models on your own computer, with no server and no key.

transformers runs any model. sentence_transformers does one job very well: turning a sentence into numbers.

Those numbers are how a computer can tell that two sentences mean the same thing.

🇹🇭 ไทย

สองไลบรารีจาก Hugging Face มันรันโมเดลบนเครื่องคุณเอง ไม่ต้องมีเซิร์ฟเวอร์ ไม่ต้องมีคีย์

transformers รันโมเดลอะไรก็ได้ ส่วน sentence_transformers ทำงานเดียวแต่ทำได้ดีมาก คือเปลี่ยนประโยคให้เป็นตัวเลข

ตัวเลขพวกนั้นคือวิธีที่คอมพิวเตอร์รู้ได้ว่าสองประโยคหมายความเหมือนกัน

🇨🇳 中文

Hugging Face 的两个库。它们在你自己的电脑上跑模型,不用服务器,也不用密钥。

transformers 什么模型都能跑。sentence_transformers 只做一件事,但做得很好:把一句话变成数字。

那些数字,就是电脑判断两句话意思相同的办法。

OllamaOllamaOllamatransformerstransformerstransformerssentence_transformerssentence_transformerssentence_transformers
What it is forมีไว้ทำอะไร拿来干嘛Ollama: chatting with a big modelOllama: คุยกับโมเดลใหญ่Ollama:跟大模型聊天transformers: running any model yourselftransformers: รันโมเดลอะไรก็ได้ด้วยตัวเองtransformers:自己跑任何模型sentence_transformers: comparing meaningsentence_transformers: เทียบความหมายsentence_transformers:比较意思
How it answersตอบยังไง怎么回答Ollama: wordsOllama: เป็นคำOllama:用词transformers: whatever the model doestransformers: แล้วแต่โมเดลนั้นทำอะไรtransformers:看那个模型做什么sentence_transformers: a row of numberssentence_transformers: เป็นแถวตัวเลขsentence_transformers:一排数字
Speed hereเร็วแค่ไหนที่นี่在这儿多快Ollama: about 1.7 secondsOllama: ราว 1.7 วินาทีOllama:大约 1.7 秒transformers: depends on the modeltransformers: ขึ้นกับโมเดลtransformers:看模型sentence_transformers: about 2 millisecondssentence_transformers: ราว 2 มิลลิวินาทีsentence_transformers:大约 2 毫秒

💻 INSTALL THEM FIRST

pip install transformers sentence-transformers

# torch comes with them and is the big one: 0.47 GB here.
# Models download on first use and are then kept on disk.
THE FIRST RUN IS SLOW. THE SECOND IS NOT. Each model downloads once and is kept. Loading it afterwards took 7 to 15 seconds on this laptop. The English model is 91.6 MB and the multilingual one is 479.7 MB. Nothing here needs the internet twice. โมเดลแต่ละตัวจะดาวน์โหลดครั้งเดียวแล้วเก็บไว้ การโหลดมันหลังจากนั้นใช้เวลา 7 ถึง 15 วินาทีบนแล็ปท็อปเครื่องนี้ โมเดลภาษาอังกฤษขนาด 91.6 MB ส่วนตัวหลายภาษาขนาด 479.7 MB ไม่มีอะไรในนี้ที่ต้องใช้อินเทอร์เน็ตซ้ำอีก 每个模型只下载一次,然后留在硬盘上。之后加载它在这台笔记本上要 7 到 15 秒。英文模型 91.6 MB,多语言的那个 479.7 MB。这里没有任何东西需要第二次联网。

🧠 Tutorial 1 — A real model, on your computer · บทที่ 1 — โมเดลจริง บนเครื่องของคุณ · 教程 1 —— 真的模型,在你自己电脑上

🇬🇧 English

Three lines runs a trained model. Then the more useful question: what does it actually read?

🇹🇭 ไทย

สามบรรทัดก็รันโมเดลที่เทรนมาแล้วได้ แล้วตามด้วยคำถามที่มีประโยชน์กว่า มันอ่านอะไรกันแน่

🇨🇳 中文

三行就能跑一个训练好的模型。然后是更有用的问题:它到底读到了什么?

1 Three lines, and it has an opinion · สามบรรทัด แล้วมันก็มีความเห็น · 三行,它就有看法了

🇬🇧 English

pipeline picks a model, downloads it, and runs it. You say what job you want done.

Watch the third answer. The model has only two labels, so it must pick one, even when neither fits.

🇹🇭 ไทย

pipeline เลือกโมเดล ดาวน์โหลด แล้วรันให้ คุณแค่บอกว่าอยากได้งานแบบไหน

ดูคำตอบที่สามให้ดี โมเดลมีแค่สองป้าย มันจึงต้องเลือกหนึ่งอัน แม้ว่าจะไม่มีอันไหนตรงเลย

🇨🇳 中文

pipeline 帮你挑模型、下载、跑起来。你只说要做什么活。

看第三个答案。模型只有两个标签,所以它必须挑一个,哪怕一个都不合适。

📄 NEW FILE · feel1.py

"""feel1.py — a real model, on your own computer, in three lines."""
from transformers import pipeline

# pipeline downloads a model the first time and keeps it. After that it is
# a file on your disk and none of this needs the internet.
#   text-classification  - the job you want done
# Leave the model out and it picks a small English one for you.
judge = pipeline("text-classification")

LINES = ["The khao soi here is the best in Chiang Mai.",
         "The bus was two hours late and nobody said anything.",
         "It is raining."]


def main():
    """Ask the model how each line feels, and print what it said."""
    for line in LINES:
        # One line in, a list with one answer out. The answer is a dict
        # with a label and a score between 0 and 1.
        answer = judge(line)[0]
        print(f"{answer['label']:8} {answer['score']:.3f}  {line}")


if __name__ == "__main__":
    main()
LineWhat it does · ทำอะไร · 做什么
judge = pipeline("text-classification")The job, not the model. Leave the model out and it picks one.บอกงาน ไม่ใช่บอกโมเดล ถ้าไม่ระบุโมเดล มันจะเลือกให้เอง说的是活,不是模型。不指定模型,它会替你挑一个。
answer = judge(line)[0]One line in, a list with one answer out. Take the first.ใส่หนึ่งบรรทัด ได้ลิสต์ที่มีคำตอบเดียว หยิบอันแรกมา进一行,出一个只有一项的列表。取第一个。
answer['score']How sure it is, between 0 and 1. Here it means very little.ความมั่นใจ ระหว่าง 0 ถึง 1 แต่ตรงนี้มันแทบไม่มีความหมาย它有多确定,0 到 1 之间。在这儿它几乎没什么意义。

🖥️ WHAT IT PRINTED · three sentences, two labels

POSITIVE 1.000  The khao soi here is the best in Chiang Mai.
NEGATIVE 0.998  The bus was two hours late and nobody said anything.
POSITIVE 0.991  It is raining.
IT IS RAINING IS NOT POSITIVE The model scored that 0.991 positive. It was trained on film reviews, which are either good or bad. It has no way to say neither. A model that cannot say what you need gives wrong answers. A confident score does not change that. Always ask what the labels were. โมเดลให้คะแนนประโยคนั้นว่าเป็นบวก 0.991 มันถูกเทรนด้วยรีวิวหนัง ซึ่งมีแต่ดีกับแย่ มันจึงไม่มีทางบอกว่าไม่ใช่ทั้งสองอย่าง คะแนนที่มั่นใจจากโมเดลที่พูดสิ่งที่คุณต้องการไม่ได้ ก็ยังเป็นคำตอบที่ผิดอยู่ดี ให้ถามเสมอว่าป้ายกำกับของมันมีอะไรบ้าง 模型给这句打了 0.991 的正面。它是拿影评训练的,而影评非好即坏,所以它没办法说“两个都不是”。一个说不出你需要的话的模型,给出再自信的分数,也还是错的答案。永远先问:它的标签是什么。
YOUR TURN
  1. Run it. The first time it downloads 268 MB.
  2. Run it again. Notice how much faster it starts.
  3. Add a sentence in Thai. Read what it says.
  4. Try pipeline("zero-shot-classification") with your own labels.
  1. รันดู ครั้งแรกมันจะดาวน์โหลด 268 MB
  2. รันอีกครั้ง สังเกตว่ามันเริ่มเร็วขึ้นแค่ไหน
  3. เพิ่มประโยคภาษาไทยเข้าไป แล้วอ่านว่ามันว่ายังไง
  4. ลอง pipeline("zero-shot-classification") พร้อมป้ายของคุณเอง
  1. 运行。第一次会下载 268 MB。
  2. 再运行一次。注意它启动快了多少。
  3. 加一句泰文,读读它怎么说。
  4. 试试 pipeline("zero-shot-classification"),用你自己的标签。

2 What the model actually reads · สิ่งที่โมเดลอ่านจริง ๆ · 模型真正读到的东西

🇬🇧 English

A model never sees letters. It sees tokens, and a token is whatever its tokenizer decided was a useful piece.

The same sentence, through two tokenizers, comes out completely differently. This one page explains everything in step 4.

🇹🇭 ไทย

โมเดลไม่เคยเห็นตัวอักษร มันเห็นโทเคน และโทเคนคือชิ้นส่วนที่ตัวตัดคำของมันตัดสินใจว่ามีประโยชน์

ประโยคเดียวกัน ผ่านตัวตัดคำสองตัว ออกมาต่างกันคนละเรื่อง หน้านี้หน้าเดียวอธิบายทุกอย่างในขั้นที่ 4

🇨🇳 中文

模型从来看不到字母。它看到的是 token,而 token 就是它的分词器认为有用的那一块。

同一句话,过两个分词器,出来的东西完全不同。这一页就解释了第 4 步里的一切。

📄 NEW FILE · tokens1.py

"""tokens1.py — what the model actually reads, which is not letters."""
from transformers import AutoTokenizer

# Two tokenizers, because the answer is different for each. The first was
# trained on English. The second saw a hundred languages.
ENGLISH = "sentence-transformers/all-MiniLM-L6-v2"
MANY = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"

LINES = ["read my stars",
         "ขอดวงประจำวัน",
         "今天的运势如何"]


def show(name, text):
    """Split one line the way one model would, and print the pieces."""
    tokenizer = AutoTokenizer.from_pretrained(name)

    # A model never sees letters. It sees numbers, one per token, and a
    # token is whatever its tokenizer decided a useful piece was.
    pieces = tokenizer.tokenize(text)
    numbers = tokenizer.encode(text, add_special_tokens=False)
    print(f"  {len(pieces):3} pieces  {pieces}")
    print(f"      as numbers  {numbers}")


def main():
    for text in LINES:
        print()
        print(text)
        print("  -- a tokenizer trained on English --")
        show(ENGLISH, text)
        print("  -- a tokenizer that saw a hundred languages --")
        show(MANY, text)


if __name__ == "__main__":
    main()
LineWhat it does · ทำอะไร · 做什么
tokenizer.tokenize(text)The pieces, as text, so a person can read them.ชิ้นส่วนในรูปข้อความ เพื่อให้คนอ่านออก那些片段,以文字形式,好让人读得懂。
tokenizer.encode(text, add_special_tokens=False)The same pieces as numbers. That is what the model gets.ชิ้นส่วนเดียวกันในรูปตัวเลข นั่นคือสิ่งที่โมเดลได้รับ同样那些片段,换成数字。那才是模型拿到的。
[UNK]Unknown. The tokenizer has no piece for this and gives up.ไม่รู้จัก ตัวตัดคำไม่มีชิ้นส่วนสำหรับสิ่งนี้ จึงยอมแพ้未知。分词器没有对应的片段,于是放弃。

🖥️ WHAT IT PRINTED · the same three lines, two tokenizers

read my stars
  english      3 pieces  ['read', 'my', 'stars']
  multilingual 4 pieces  ['▁read', '▁my', '▁star', 's']

ขอดวงประจำวัน
  english      1 pieces  ['[UNK]']
  multilingual 3 pieces  ['▁ขอ', 'ดวง', 'ประจําวัน']

今天的运势如何
  english      7 pieces  ['[UNK]', '天', '的', '[UNK]', '[UNK]', '[UNK]', '[UNK]']
  multilingual 5 pieces  ['▁', '今天的', '运', '势', '如何']
A WHOLE THAI SENTENCE BECAME ONE UNKNOWN TOKEN Read that again. The English tokenizer turned ขอดวงประจำวัน into ['[UNK]']. One token, meaning nothing. Every Thai sentence becomes the same token. The model cannot tell them apart. Step 4 shows exactly that happening. อ่านอีกรอบ ตัวตัดคำภาษาอังกฤษเปลี่ยน ขอดวงประจำวัน ให้เป็น ['[UNK]'] โทเคนเดียว ที่ไม่มีความหมายอะไรเลย ประโยคภาษาไทยทุกประโยคจะกลายเป็นโทเคนเดียวกันนี้ โมเดลจึงแยกความแตกต่างของสองประโยคไม่ออกเลย ขั้นที่ 4 จะแสดงให้เห็นว่ามันเกิดขึ้นจริง 再读一遍。英文分词器把 ขอดวงประจำวัน 变成了 ['[UNK]']。一个 token,什么意思都没有。每一句泰文都会变成同一个 token,所以模型根本分不出任何两句的区别。第 4 步会让你看到这件事真的发生。
YOUR TURN
  1. Run it. Count the pieces for each language.
  2. Put your own name in. How many pieces is it?
  3. Try a long English word like unbelievable.
  4. Try an emoji. Then try it in the multilingual tokenizer.
  1. รันดู แล้วนับชิ้นส่วนของแต่ละภาษา
  2. ใส่ชื่อของคุณเองลงไป มันถูกตัดเป็นกี่ชิ้น
  3. ลองคำอังกฤษยาว ๆ อย่าง unbelievable
  4. ลองอิโมจิดู แล้วลองกับตัวตัดคำหลายภาษา
  1. 运行,数一数每种语言被切成几块。
  2. 把你自己的名字放进去,它是几块?
  3. 试一个长的英文词,比如 unbelievable。
  4. 试一个 emoji,再用多语言分词器试一次。

🔢 Tutorial 2 — Sentences as numbers · บทที่ 2 — ประโยคในรูปตัวเลข · 教程 2 —— 把句子变成数字

🇬🇧 English

This is the part worth learning. A sentence becomes a row of numbers, and close numbers mean close meanings.

🇹🇭 ไทย

นี่คือส่วนที่ควรเรียนรู้ ประโยคจะกลายเป็นแถวของตัวเลข และตัวเลขที่ใกล้กันก็หมายถึงความหมายที่ใกล้กัน

🇨🇳 中文

这是值得学的那部分。一句话变成一排数字,数字接近,意思就接近。

3 Four sentences, and how close they are · สี่ประโยค กับความใกล้ของมัน · 四句话,和它们有多近

🇬🇧 English

encode turns each sentence into 384 numbers. similarity compares them.

1.0 means the same sentence. Lower means further apart. Nothing here was trained by you.

🇹🇭 ไทย

encode เปลี่ยนแต่ละประโยคเป็นตัวเลข 384 ตัว แล้ว similarity เอามาเทียบกัน

1.0 คือประโยคเดียวกัน ยิ่งต่ำยิ่งห่างกัน และไม่มีอะไรในนี้ที่คุณต้องเทรนเอง

🇨🇳 中文

encode 把每句话变成 384 个数字,similarity 拿来比较。

1.0 表示是同一句。越低就离得越远。这里没有任何东西是你训练的。

📄 NEW FILE · embed1.py

"""embed1.py — a sentence, turned into numbers you can compare."""
from sentence_transformers import SentenceTransformer

# A small model, about 90 MB. It is downloaded once and kept.
model = SentenceTransformer("all-MiniLM-L6-v2")

LINES = ["I would like a horoscope please",
         "read my stars",
         "what do the stars say today",
         "your package has shipped"]


def main():
    """Turn four sentences into numbers, then compare them."""
    # encode gives one row of numbers per sentence. Sentences that mean
    # similar things end up with similar numbers.
    vectors = model.encode(LINES)
    print("shape:", vectors.shape)
    print("each sentence is", model.get_embedding_dimension(), "numbers")
    print("the first eight of the first one:", vectors[0][:8].round(3))

    print()
    print("how close is each line to the first one:")

    # similarity compares rows. 1.0 is the same sentence, 0 is unrelated.
    scores = model.similarity(vectors[0:1], vectors)[0]
    for line, score in zip(LINES, scores):
        bar = "#" * int(max(0, float(score)) * 40)
        print(f"  {float(score):6.3f} {bar:40} {line}")


if __name__ == "__main__":
    main()
LineWhat it does · ทำอะไร · 做什么
SentenceTransformer("all-MiniLM-L6-v2")A small English model, 91.6 MB. Downloaded once, then kept.โมเดลภาษาอังกฤษตัวเล็ก 91.6 MB ดาวน์โหลดครั้งเดียวแล้วเก็บไว้一个小的英文模型,91.6 MB。下载一次,然后留着。
vectors = model.encode(LINES)One row of numbers per sentence. Four sentences, four rows.หนึ่งแถวตัวเลขต่อหนึ่งประโยค สี่ประโยค สี่แถว每句话一行数字。四句话,四行。
model.get_embedding_dimension()How long each row is. Renamed in version 6; the old name warns.แต่ละแถวยาวเท่าไหร่ ชื่อนี้เปลี่ยนในเวอร์ชัน 6 ชื่อเดิมจะขึ้นคำเตือน每行有多长。第 6 版改了名字,用旧名会有警告。
model.similarity(vectors[0:1], vectors)[0]Compare one row against all of them. Returns one number each.เทียบหนึ่งแถวกับทุกแถว คืนค่าออกมาแถวละหนึ่งตัวเลข拿一行去比所有行。每行回一个数字。

🖥️ WHAT IT PRINTED · 384 numbers a sentence

shape: (4, 384)
the first eight of the first one: [-0.054  0.005 -0.002  0.058 -0.055 -0.032 -0.017 -0.024]

how close is each line to the first one:
   1.000  I would like a horoscope please
   0.418  read my stars
   0.327  what do the stars say today
   0.179  your package has shipped

🇬🇧 English

read my stars scores 0.418 against I would like a horoscope please.

They share no words at all. That is the entire point.

🇹🇭 ไทย

read my stars ได้ 0.418 เมื่อเทียบกับ I would like a horoscope please

ทั้งสองไม่มีคำไหนตรงกันเลยสักคำ นั่นแหละคือประเด็นทั้งหมด

🇨🇳 中文

read my stars 和 I would like a horoscope please 得 0.418。

它们一个词都不重合。要点全在这儿。

YOUR TURN
  1. Run it. Read the four scores.
  2. Add a sentence that means the same as the first.
  3. Add one about something else entirely. Compare.
  4. Print vectors[0] in full. It is only numbers.
  1. รันดู แล้วอ่านคะแนนทั้งสี่
  2. เพิ่มประโยคที่มีความหมายเหมือนประโยคแรก
  3. เพิ่มอีกประโยคที่พูดเรื่องอื่นไปเลย แล้วเทียบดู
  4. พิมพ์ vectors[0] ออกมาทั้งแถว มันมีแต่ตัวเลข
  1. 运行,读那四个分数。
  2. 加一句和第一句意思一样的。
  3. 再加一句完全无关的,比一比。
  4. 把 vectors[0] 整行打印出来。它就只是数字。

4 The English model cannot read Thai · โมเดลภาษาอังกฤษอ่านภาษาไทยไม่ออก · 英文模型读不懂泰文

🇬🇧 English

The same seven sentences, through two models. Compare the two Thai lines in the first one.

A horoscope request and an electricity bill score the same number. Step 2 already told you why.

🇹🇭 ไทย

เจ็ดประโยคเดียวกัน ผ่านสองโมเดล ลองเทียบสองบรรทัดภาษาไทยในโมเดลแรกดู

คำขอดูดวงกับใบแจ้งหนี้ค่าไฟได้คะแนนเท่ากันเป๊ะ ขั้นที่ 2 บอกเหตุผลไปแล้ว

🇨🇳 中文

同样七句话,过两个模型。比一比第一个模型里那两句泰文。

一个求运势,一个电费单,分数一模一样。第 2 步已经告诉你为什么了。

📄 NEW FILE · multi1.py

"""multi1.py — the same sentences, through two models. Only one can read Thai."""
from sentence_transformers import SentenceTransformer

ENGLISH = "all-MiniLM-L6-v2"
MANY = "paraphrase-multilingual-MiniLM-L12-v2"

LINES = ["I would like a horoscope please",
         "read my stars",
         "your package has shipped",
         "ขอดวงประจำวัน",          # may I have today's horoscope
         "ใบแจ้งหนี้ค่าไฟ",          # the electricity bill
         "今天的运势如何",           # how is today's fortune
         "您的快递已送达"]           # your parcel has arrived


def compare(name):
    """Score every line against the first one, and draw it."""
    model = SentenceTransformer(name)
    vectors = model.encode(LINES)
    scores = model.similarity(vectors[0:1], vectors)[0]

    print()
    print(name)
    for line, score in zip(LINES, scores):
        bar = "#" * int(max(0, float(score)) * 40)
        print(f"  {float(score):6.3f} {bar:40} {line}")


def main():
    compare(ENGLISH)
    compare(MANY)


if __name__ == "__main__":
    main()

🖥️ WHAT IT PRINTED · all-MiniLM-L6-v2, the English one

   1.000  I would like a horoscope please
   0.418  read my stars
   0.179  your package has shipped
   0.119  ขอดวงประจำวัน        <- asking for a horoscope
   0.119  ใบแจ้งหนี้ค่าไฟ       <- the electricity bill
   0.284  今天的运势如何
   0.145  您的快递已送达

🖥️ AND THEN · paraphrase-multilingual-MiniLM-L12-v2

   1.000  I would like a horoscope please
   0.317  read my stars
   0.047  your package has shipped
   0.366  ขอดวงประจำวัน        <- now the highest but one
   0.106  ใบแจ้งหนี้ค่าไฟ       <- and clearly apart
   0.241  今天的运势如何
   0.106  您的快递已送达
English modelโมเดลอังกฤษ英文模型Multilingualหลายภาษา多语言
Thai horoscope requestคำขอดูดวงภาษาไทย泰文求运势English model: 0.119โมเดลอังกฤษ: 0.119英文模型:0.119multilingual: 0.366หลายภาษา: 0.366多语言:0.366
Thai electricity billใบแจ้งหนี้ค่าไฟภาษาไทย泰文电费单English model: 0.119โมเดลอังกฤษ: 0.119英文模型:0.119multilingual: 0.106หลายภาษา: 0.106多语言:0.106
Told apart?แยกออกไหม分得开吗English model: no, identicalโมเดลอังกฤษ: ไม่ เท่ากันเป๊ะ英文模型:分不开,完全一样multilingual: yes, three timesหลายภาษา: ได้ ต่างกันสามเท่า多语言:分得开,差三倍
Sizeขนาด大小English model: 91.6 MBโมเดลอังกฤษ: 91.6 MB英文模型:91.6 MBmultilingual: 479.7 MBหลายภาษา: 479.7 MB多语言:479.7 MB
PICK THE MODEL FOR THE LANGUAGES YOU HAVE The English model is not broken. It was never shown Thai. Every Thai sentence looks the same to it. This is the most expensive mistake on this page, because nothing fails. You get numbers, they look fine, and they mean nothing. โมเดลภาษาอังกฤษไม่ได้เสีย มันแค่ไม่เคยเห็นภาษาไทย ประโยคภาษาไทยทุกประโยคจึงดูเหมือนกันหมดในสายตามัน นี่คือความผิดพลาดที่แพงที่สุดในหน้านี้ เพราะไม่มีอะไรพัง คุณจะได้ตัวเลขมา มันดูปกติดี และมันไม่มีความหมายอะไรเลย 英文模型没坏。它只是从没见过泰文,所以每一句泰文在它眼里都长得一样。这是这一页上最贵的错误,因为什么都不会报错。你会拿到数字,它们看起来没问题,而它们什么都不是。
YOUR TURN
  1. Run it. Find the two 0.119 lines.
  2. Add two more Thai sentences. Are they the same number too?
  3. Go back to tokens1.py and tokenize them.
  4. Now you know why.
  1. รันดู แล้วหาสองบรรทัดที่ได้ 0.119
  2. เพิ่มประโยคภาษาไทยอีกสองประโยค ได้ตัวเลขเดียวกันอีกไหม
  3. กลับไปที่ tokens1.py แล้วตัดคำประโยคพวกนั้นดู
  4. ทีนี้คุณก็รู้แล้วว่าทำไม
  1. 运行,找出那两行 0.119。
  2. 再加两句泰文。它们也是同一个数字吗?
  3. 回到 tokens1.py,把它们分词看看。
  4. 现在你知道为什么了。

🔍 Tutorial 3 — Doing something with it · บทที่ 3 — เอาไปใช้ทำอะไรสักอย่าง · 教程 3 —— 拿它做点事

🇬🇧 English

Two jobs that need no training and no model call: search by meaning, and sorting into piles.

Both are a handful of lines, and both run in milliseconds.

🇹🇭 ไทย

สองงานที่ไม่ต้องเทรนและไม่ต้องเรียกโมเดล คือค้นหาด้วยความหมาย และแยกของเข้ากอง

ทั้งคู่ใช้โค้ดไม่กี่บรรทัด และทำงานเสร็จในหลักมิลลิวินาที

🇨🇳 中文

两件不用训练、也不用调模型的活:按意思搜索,和分堆。

两个都只要几行,而且都在毫秒级跑完。

5 Search by meaning, in any language · ค้นหาด้วยความหมาย ในภาษาไหนก็ได้ · 按意思搜索,什么语言都行

🇬🇧 English

Encode the documents once. After that, searching is arithmetic.

The good part is at the bottom. The question is in Thai and the pages are in English.

🇹🇭 ไทย

เข้ารหัสเอกสารไว้ครั้งเดียว หลังจากนั้นการค้นหาก็เป็นแค่การคำนวณ

ส่วนที่ดีอยู่ข้างล่างสุด คำถามเป็นภาษาไทย ส่วนหน้าเว็บเป็นภาษาอังกฤษ

🇨🇳 中文

文档只编码一次。之后搜索就只是算术。

好的部分在最下面。问题是泰文的,而页面是英文的。

📄 NEW FILE · search1.py

"""search1.py — search by meaning, not by matching words."""
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")

# Pretend these are the pages on a website. Nine short descriptions.
PAGES = [
    "A bot that reads your mail once an hour and replies with a horoscope",
    "Checking the bitcoin price and placing real orders with Python",
    "Build a chat window with tkinter and a model that remembers",
    "Classes in Python, taught by building a window piece by piece",
    "Turn a Python function into a web page with Gradio",
    "Send email from Python, and read your inbox with IMAP",
    "Automate jobs with n8n, without writing much code",
    "Put a website on the internet with Astro and Vercel",
    "Give a model tools it can choose to call, with smolagents",
]

# Encoded once, when the program starts. Searching is then only arithmetic.
LIBRARY = model.encode(PAGES)

QUESTIONS = ["how do I make a window",
             "I want to sell things online",
             "ส่งอีเมลด้วย Python",
             "让模型自己调用工具"]


def find(question, how_many=3):
    """Return the closest pages, best first."""
    asked = model.encode([question])

    # One number per page: how close it is to the question.
    scores = model.similarity(asked, LIBRARY)[0]

    # sorted() on pairs sorts by the first item, so put the score first.
    ranked = sorted(zip(scores.tolist(), PAGES), reverse=True)
    return ranked[:how_many]


def main():
    for question in QUESTIONS:
        print()
        print("Q:", question)
        for score, page in find(question):
            print(f"   {score:.3f}  {page}")


if __name__ == "__main__":
    main()
LineWhat it does · ทำอะไร · 做什么
LIBRARY = model.encode(PAGES)Once, when the program starts. This is why searching is fast.ทำครั้งเดียวตอนโปรแกรมเริ่ม นี่คือเหตุผลที่การค้นหาเร็ว程序一启动就做一次。搜索快就是因为这个。
scores = model.similarity(asked, LIBRARY)[0]One number per page. No text is compared at all.หนึ่งตัวเลขต่อหนึ่งหน้า ไม่มีการเทียบข้อความเลยสักตัว每页一个数字。完全没有比较文字。
sorted(zip(scores.tolist(), PAGES), reverse=True)sorted uses the first item, so put the score first.sorted ใช้ค่าตัวแรกในการเรียง จึงต้องวางคะแนนไว้ข้างหน้าsorted 按第一项排,所以把分数放前面。

🖥️ WHAT IT PRINTED · four questions, nine pages

Q: how do I make a window
   0.398  Classes in Python, taught by building a window piece by piece
   0.373  Build a chat window with tkinter and a model that remembers

Q: I want to sell things online
   0.366  Put a website on the internet with Astro and Vercel
   0.249  Checking the bitcoin price and placing real orders with Python

Q: ส่งอีเมลด้วย Python
   0.755  Send email from Python, and read your inbox with IMAP
   0.388  Turn a Python function into a web page with Gradio

Q: 让模型自己调用工具
   0.556  Give a model tools it can choose to call, with smolagents
THE THAI QUESTION SCORED HIGHEST OF ALL ส่งอีเมลด้วย Python found the English email page at 0.755. Not one word matched. The two are not even in the same script. The multilingual model puts a sentence and its translation nearly together. Search built this way works in languages you never planned for. ส่งอีเมลด้วย Python เจอหน้าอีเมลภาษาอังกฤษที่คะแนน 0.755 ไม่มีคำไหนตรงกันเลย และทั้งสองไม่ได้ใช้ตัวอักษรชุดเดียวกันด้วยซ้ำ โมเดลหลายภาษาวางประโยคกับคำแปลของมันไว้ในที่แทบจะเดียวกัน การค้นหาที่สร้างแบบนี้ใช้ได้กับภาษาที่คุณไม่เคยวางแผนไว้เลย ส่งอีเมลด้วย Python 以 0.755 找到了那个英文邮件页面。一个词都没对上,两者甚至不是同一种文字。多语言模型把一句话和它的译文放在几乎同一个位置。这样做出来的搜索,在你根本没计划过的语言上也能用。
YOUR TURN
  1. Run it. Ask something in your own language.
  2. Add three pages of your own to the list.
  3. Ask a question no page answers. What comes back?
  4. That is the catch. There is always a best match.
  1. รันดู แล้วถามด้วยภาษาของคุณเอง
  2. เพิ่มหน้าของคุณเองสามหน้าเข้าไปในลิสต์
  3. ถามคำถามที่ไม่มีหน้าไหนตอบได้ จะได้อะไรกลับมา
  4. นั่นแหละคือข้อควรระวัง มันมีอันที่ใกล้ที่สุดเสมอ
  1. 运行,用你自己的语言问一句。
  2. 往列表里加三个你自己的页面。
  3. 问一个没有页面能回答的问题。回来的是什么?
  4. 这就是坑。总会有一个最接近的。

6 Sorting into piles, with no training · แยกของเข้ากอง โดยไม่ต้องเทรน · 分堆,不用训练

🇬🇧 English

Write a few examples of each pile. Put each new thing on whichever side it sits closer to.

This is the mailbot's question again: is this person asking for a horoscope?

Look at the last line of the output before you believe it.

🇹🇭 ไทย

เขียนตัวอย่างของแต่ละกองไว้ไม่กี่อัน แล้ววางของชิ้นใหม่ลงฝั่งที่มันอยู่ใกล้กว่า

นี่คือคำถามเดิมของบอทอีเมล ว่าคนนี้กำลังขอดูดวงหรือเปล่า

ดูบรรทัดสุดท้ายของผลลัพธ์ก่อนจะเชื่อมัน

🇨🇳 中文

给每一堆写几个例子。新来的东西,靠哪边近就放哪边。

这又是邮件机器人那个问题:这个人是在求运势吗?

在你相信它之前,先看输出的最后一行。

📄 NEW FILE · sort1.py

"""sort1.py — sorting mail into two piles, with no training at all.

Write a few examples of each pile. Turn everything into numbers. Put each
new subject on whichever side it sits closer to. That is the whole method.
"""
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")

ASKING = ["can I have my horoscope",
          "what do the stars say about me today",
          "please send me a fortune reading",
          "tell me about my star sign"]

NOT_ASKING = ["your invoice is attached",
              "the meeting has been moved",
              "your parcel is on its way",
              "please stop sending me this newsletter"]

# Encoded once. Every subject after this is compared against these.
YES = model.encode(ASKING)
NO = model.encode(NOT_ASKING)

SUBJECTS = ["horoscope please",
            "read my stars",
            "ขอดวงประจำวัน",
            "今天的运势如何",
            "Your flight is confirmed",
            "my horoscope was wrong yesterday"]


def is_asking(subject):
    """Which pile is this subject closer to? Returns the answer and both scores."""
    vector = model.encode([subject])

    # max(), not mean(). One good match is enough, and averaging lets three
    # unrelated examples drown out the one that fits.
    near_yes = float(model.similarity(vector, YES).max())
    near_no = float(model.similarity(vector, NO).max())
    return near_yes > near_no, near_yes, near_no


def main():
    for subject in SUBJECTS:
        asking, yes, no = is_asking(subject)
        print(f"{'reply ' if asking else 'ignore'}  "
              f"yes={yes:.3f} no={no:.3f}   {subject}")


if __name__ == "__main__":
    main()
LineWhat it does · ทำอะไร · 做什么
YES = model.encode(ASKING)The examples, encoded once. Eight sentences is the whole training.ตัวอย่างที่เข้ารหัสไว้ครั้งเดียว แปดประโยคคือการเทรนทั้งหมด例子,只编码一次。八句话就是全部的训练。
model.similarity(vector, YES).max()max, not mean. One good match is enough.ใช้ max ไม่ใช่ mean เพราะตรงกันดีอันเดียวก็พอแล้ว用 max,不用 mean。对上一个就够了。
return near_yes > near_noWhichever side it sits closer to. That is the whole rule.ฝั่งไหนใกล้กว่าก็ฝั่งนั้น นั่นคือกฎทั้งหมด靠哪边近就是哪边。规则就这一条。

🖥️ WHAT IT PRINTED · five right, one badly wrong

reply   yes=0.870 no=0.140   horoscope please
reply   yes=0.686 no=0.207   read my stars
reply   yes=0.451 no=0.345   ขอดวงประจำวัน
reply   yes=0.507 no=0.304   今天的运势如何
ignore  yes=0.294 no=0.381   Your flight is confirmed
reply   yes=0.766 no=0.148   my horoscope was wrong yesterday

🇬🇧 English

The last line is a complaint, not a request. It scored 0.766 on the asking side.

Scored against all 57 subjects from the mail bot page, this gets 45 right. Every single trap is wrong.

🇹🇭 ไทย

บรรทัดสุดท้ายคือคำบ่น ไม่ใช่คำขอ แต่ได้คะแนนฝั่งขอไปถึง 0.766

เมื่อวัดกับหัวข้อทั้ง 57 อันจากหน้าบอทอีเมล อันนี้ถูก 45 อัน ส่วนกับดักผิดหมดทุกอัน

🇨🇳 中文

最后一行是抱怨,不是请求。它在“在问”那边拿了 0.766。

拿邮件机器人那页全部 57 个标题来评,这个对 45 个。所有的陷阱题,一个没对。

Judgeตัวตัดสิน裁判Right of 57ถูกจาก 5757 个里对几个Time eachเวลาต่ออัน每个多久Trapsกับดัก陷阱题
the word horoscopeคำว่า horoscopehoroscope 这个词26 of 57 rightถูก 26 จาก 5757 个里对 26 个instantทันที瞬间1 trap of 9กับดัก 1 จาก 9陷阱题 9 个里对 1 个
embeddings, English modelเอมเบดดิง โมเดลอังกฤษ嵌入,英文模型42 of 57 rightถูก 42 จาก 5757 个里对 42 个1.8 ms each1.8 ms ต่ออัน每个 1.8 毫秒0 traps of 9กับดัก 0 จาก 9陷阱题 9 个里对 0 个
embeddings, multilingualเอมเบดดิง หลายภาษา嵌入,多语言45 of 57 rightถูก 45 จาก 5757 个里对 45 个2.6 ms each2.6 ms ต่ออัน每个 2.6 毫秒0 traps of 9กับดัก 0 จาก 9陷阱题 9 个里对 0 个
llama3.2 through Ollamallama3.2 ผ่าน Ollama经由 Ollama 的 llama3.257 of 57 rightถูก 57 จาก 5757 个全对1,670 ms each1,670 ms ต่ออัน每个 1,670 毫秒9 traps of 9กับดัก 9 จาก 9陷阱题 9 个全对
EMBEDDINGS MEASURE THE TOPIC, NOT THE REQUEST Every trap on that page is about horoscopes. None of them is asking. A complaint, a bill, an unsubscribe. They sit close to the asking examples. They are about the same thing. No threshold fixes this. It is what the tool measures. กับดักทุกอันในหน้านั้นคือเมลที่เกี่ยวกับดวง แต่ไม่ได้ขอดูดวง ทั้งคำบ่น ใบแจ้งหนี้ และการขอยกเลิกรับข่าว มันอยู่ใกล้กับตัวอย่างฝั่งขอ เพราะมันพูดเรื่องเดียวกัน ไม่มีเกณฑ์ไหนแก้เรื่องนี้ได้ นี่คือสิ่งที่เครื่องมือนี้วัด 那一页上每一个陷阱,都是关于运势但并不是在求运势的邮件。抱怨、账单、退订。它们离“在问”的例子很近,因为它们说的是同一件事。没有哪个阈值能修好这个。这就是这个工具所测量的东西。
YOUR TURN
  1. Run it. Read the last line's two numbers.
  2. Add your own subject lines to the list.
  3. Try to fix the last line by changing the examples.
  4. Challenge 2 is exactly that, measured.
  1. รันดู แล้วอ่านตัวเลขสองตัวของบรรทัดสุดท้าย
  2. เพิ่มหัวข้อของคุณเองเข้าไปในลิสต์
  3. ลองแก้บรรทัดสุดท้ายด้วยการเปลี่ยนตัวอย่างดู
  4. โจทย์ข้อ 2 คือเรื่องนี้เป๊ะ ๆ พร้อมตัวเลขวัดผล
  1. 运行,读最后一行那两个数字。
  2. 把你自己的标题加进列表。
  3. 试着改例子,把最后一行修好。
  4. 第 2 题就是这件事,而且有测量。

🏆 Tutorial 4 — Four challenges · บทที่ 4 — โจทย์สี่ข้อ · 教程 4 —— 四道题

🇬🇧 English

Each one was run before it was written down. Try it first.

🇹🇭 ไทย

ทุกข้อถูกรันจริงก่อนจะถูกเขียนลงมา ลองทำเองก่อน

🇨🇳 中文

每一道都是先跑过才写下来的。先自己试。

7 Try first. The answers are folded away. · ลองเองก่อน เฉลยพับซ่อนไว้ · 先自己试。答案折起来放着。

🇬🇧 English

The first two are a few lines each. The last two add a second model and a web page.

🇹🇭 ไทย

สองข้อแรกใช้โค้ดไม่กี่บรรทัด สองข้อหลังเพิ่มโมเดลตัวที่สองกับหน้าเว็บ

🇨🇳 中文

前两道各几行。后两道多加一个模型和一个网页。

1Find the notes that say the same thing · หาโน้ตที่พูดเรื่องเดียวกัน · 找出说的是同一件事的便条

🇬🇧 English

Seven notes. Some are the same thing written twice. Find every pair that means the same.

One of the duplicates is in Thai and its twin is in English.

🇹🇭 ไทย

โน้ตเจ็ดอัน บางอันคือเรื่องเดียวกันที่เขียนสองครั้ง ให้หาทุกคู่ที่มีความหมายเหมือนกัน

คู่ซ้ำคู่หนึ่งเป็นภาษาไทย ส่วนฝาแฝดของมันเป็นภาษาอังกฤษ

🇨🇳 中文

七张便条。有些是同一件事写了两遍。找出所有意思相同的配对。

其中一对里,一张是泰文,它的双胞胎是英文。

Show the answerดูเฉลย看答案

📄 dupes.py

"""dupes.py — challenge 1: find the sentences that say the same thing."""
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")

NOTES = [
    "The wifi password is on the fridge",
    "Wifi code is stuck to the fridge door",
    "Bring an umbrella, it rains every afternoon",
    "Take an umbrella. Afternoon rain is guaranteed",
    "The market closes at six",
    "รหัสไวไฟติดอยู่ที่ตู้เย็น",
    "Please water the plants on Tuesday",
]
CLOSE_ENOUGH = 0.7


def find_pairs():
    """Every pair that scores above the line, closest first."""
    vectors = model.encode(NOTES)
    scores = model.similarity(vectors, vectors)

    found = []
    # range(row + 1, ...) so each pair is looked at once, and never against
    # itself. Comparing a sentence to itself always gives 1.0.
    for row in range(len(NOTES)):
        for column in range(row + 1, len(NOTES)):
            score = float(scores[row][column])
            if score >= CLOSE_ENOUGH:
                found.append((score, NOTES[row], NOTES[column]))
    return sorted(found, reverse=True)


def main():
    print(len(NOTES), "notes, anything scoring", CLOSE_ENOUGH, "or more:")
    print()
    for score, one, two in find_pairs():
        print(f"  {score:.3f}")
        print(f"     {one}")
        print(f"     {two}")


if __name__ == "__main__":
    main()
LineWhat it does · ทำอะไร · 做什么
for column in range(row + 1, len(NOTES)):Start past row, so each pair is looked at once only.เริ่มหลังจาก row เพื่อให้แต่ละคู่ถูกดูแค่ครั้งเดียว从 row 之后开始,每一对只看一次。
CLOSE_ENOUGH = 0.7A judgement, not a fact. Read the careful box below.เป็นการตัดสินใจ ไม่ใช่ข้อเท็จจริง อ่านกล่องคำเตือนข้างล่าง这是判断,不是事实。读下面的警告框。

🖥️ WHAT IT PRINTED · at 0.7, it found two pairs

0.848  Bring an umbrella, it rains every afternoon
       Take an umbrella. Afternoon rain is guaranteed
0.800  The wifi password is on the fridge
       Wifi code is stuck to the fridge door
IT MISSED THE THAI ONE BY NINE THOUSANDTHS รหัสไวไฟติดอยู่ที่ตู้เย็น is the wifi note again, in Thai. It scored 0.691 and 0.688 against the two English ones. The line was drawn at 0.700. A match across two languages scores lower than one inside a language. Lower the number to 0.65 and all three appear. รหัสไวไฟติดอยู่ที่ตู้เย็น คือโน้ตเรื่องไวไฟอันเดิม แต่เป็นภาษาไทย มันได้ 0.691 กับ 0.688 เมื่อเทียบกับสองอันภาษาอังกฤษ ส่วนเส้นแบ่งถูกขีดไว้ที่ 0.700 การจับคู่ข้ามสองภาษาจะได้คะแนนต่ำกว่าการจับคู่ในภาษาเดียวกันเล็กน้อย ลดตัวเลขลงเหลือ 0.65 แล้วทั้งสามอันจะโผล่มา รหัสไวไฟติดอยู่ที่ตู้เย็น 就是那张 wifi 便条,泰文版。它跟两张英文的分别得 0.691 和 0.688,而线画在 0.700。跨两种语言的配对,分数会比同一种语言内的略低一点。把数字降到 0.65,三张就都出来了。

2Fix the traps, and measure what it costs · แก้กับดัก แล้ววัดว่ามันแลกด้วยอะไร · 修好陷阱题,并量出代价

🇬🇧 English

sort1.py got every trap wrong. Mail about horoscopes that is not asking for one.

Fix it by changing only the examples. Then check what happened to the requests it used to get right.

🇹🇭 ไทย

sort1.py ผิดกับดักทุกอัน คือเมลที่เกี่ยวกับดวงแต่ไม่ได้ขอดูดวง

แก้โดยเปลี่ยนแค่ตัวอย่าง แล้วไปดูว่าเกิดอะไรขึ้น กับคำขอที่มันเคยตอบถูก

🇨🇳 中文

sort1.py 每个陷阱都答错了:关于运势但并不是在求运势的邮件。

只改例子把它修好。然后看看它原本答对的那些请求,怎么样了。

Show the answerดูเฉลย看答案

📄 better_sort.py

"""better_sort.py — challenge 2: the traps, and what fixing them costs.

sort1.py got every trap wrong. Mail that is ABOUT horoscopes but is not
asking for one sits close to the asking examples, because embeddings
measure what a sentence is about. The fix is to show it the difference.
"""
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")

ASKING = ["can I have my horoscope",
          "what do the stars say about me today",
          "please send me a fortune reading",
          "tell me about my star sign"]

PLAIN_NO = ["your invoice is attached",
            "the meeting has been moved",
            "your parcel is on its way",
            "please stop sending me this newsletter"]

# The same four, plus four that are about horoscopes without asking for one.
# These are the shape sort1.py could not see.
BETTER_NO = PLAIN_NO + ["your horoscope yesterday was wrong",
                        "feedback on your astrology column",
                        "unsubscribe me from the horoscope newsletter",
                        "invoice for the horoscope app"]

TRAPS = ["my horoscope was wrong yesterday",
         "Horoscope column feedback",
         "Unsubscribe me from the Daily Horoscope newsletter",
         "Invoice for horoscope app subscription"]
REAL = ["horoscope please", "read my stars", "ขอดวงประจำวัน"]


def sorter(no_examples):
    """Build a sorter from one set of examples."""
    yes = model.encode(ASKING)
    no = model.encode(no_examples)

    def is_asking(subject):
        vector = model.encode([subject])
        return (float(model.similarity(vector, yes).max())
                > float(model.similarity(vector, no).max()))
    return is_asking


def main():
    plain = sorter(PLAIN_NO)
    better = sorter(BETTER_NO)

    print(f"{'subject':52} {'4 examples':>12} {'8 examples':>12}")
    print("-" * 78)
    for subject in TRAPS:
        print(f"{subject[:52]:52} {str(plain(subject)):>12} "
              f"{str(better(subject)):>12}   (should be False)")
    for subject in REAL:
        print(f"{subject[:52]:52} {str(plain(subject)):>12} "
              f"{str(better(subject)):>12}   (should be True)")


if __name__ == "__main__":
    main()
LineWhat it does · ทำอะไร · 做什么
BETTER_NO = PLAIN_NO + [Four more examples, all about horoscopes, none asking.ตัวอย่างเพิ่มอีกสี่อัน เกี่ยวกับดวงทั้งหมด แต่ไม่มีอันไหนขอ再加四个例子,都关于运势,都不是在求。
def sorter(no_examples):Returns a function. Two sets of examples, two sorters.คืนค่าเป็นฟังก์ชัน ตัวอย่างสองชุด ก็ได้ตัวแยกสองตัว返回一个函数。两组例子,两个分类器。

🖥️ WHAT IT PRINTED · the four traps, then three real requests

subject                                        4 ex    8 ex
my horoscope was wrong yesterday               True   False  (want False)
Horoscope column feedback                      True   False  (want False)
Unsubscribe me from the Daily Horoscope        True   False  (want False)
Invoice for horoscope app subscription         True   False  (want False)
horoscope please                               True    True  (want True)
read my stars                                  True    True  (want True)
ขอดวงประจำวัน                                  True    True  (want True)

🇬🇧 English

Across all 57 subjects the score went from 45 to 50. The traps went from 0 of 9 to 8 of 9.

But the real requests fell from 17 of 17 to 14 of 17. You moved the boundary, and it moved for everybody.

🇹🇭 ไทย

เมื่อวัดกับหัวข้อทั้ง 57 อัน คะแนนขยับจาก 45 เป็น 50 กับดักขยับจาก 0 จาก 9 เป็น 8 จาก 9

แต่คำขอจริงตกจาก 17 จาก 17 เหลือ 14 จาก 17 คุณขยับเส้นแบ่ง และมันขยับสำหรับทุกคน

🇨🇳 中文

在全部 57 个标题上,分数从 45 变成 50。陷阱题从 0/9 变成 8/9。

但真正的请求从 17/17 掉到 14/17。你挪动了边界,而它对所有人都挪了。

EXAMPLES ARE THE ONLY DIAL YOU HAVE There is no training here and no prompt to tune. The examples are the whole configuration. Adding one kind of example pulls the boundary towards it. Something on the other side falls off. Measure both sides, every time. ตรงนี้ไม่มีการเทรน และไม่มีพรอมต์ให้ปรับ ตัวอย่างคือการตั้งค่าทั้งหมด การเพิ่มตัวอย่างแบบหนึ่งจะดึงเส้นแบ่งไปทางนั้น แล้วอะไรบางอย่างอีกฝั่งก็จะหลุดออกไป ให้วัดทั้งสองฝั่งทุกครั้ง 这里没有训练,也没有 prompt 可调。例子就是全部的配置。加一类例子,就会把边界往那边拉,另一边就会掉下去一些。每一次都要两边都量。

3A slower model, to check the fast one · โมเดลที่ช้ากว่า ไว้ตรวจงานตัวที่เร็ว · 用一个更慢的模型,去检查快的那个

🇬🇧 English

An embedding model reads the question and the page separately, which is why it is fast and a little blind.

A cross encoder reads both at once. Too slow to search with. Right for reordering the few you found.

🇹🇭 ไทย

โมเดลเอมเบดดิงอ่านคำถามกับหน้าเว็บแยกกัน นั่นคือเหตุผลที่มันเร็วและตาบอดนิดหน่อย

ส่วน cross encoder อ่านทั้งคู่พร้อมกัน ช้าเกินกว่าจะใช้ค้นหา แต่เหมาะกับการจัดลำดับใหม่ให้ไม่กี่อันที่เจอมา

🇨🇳 中文

嵌入模型分别读问题和页面,所以它快,也有点瞎。

cross encoder 一次把两个一起读。太慢,不能拿来搜索,但正好用来给找回来的那几个重新排序。

Show the answerดูเฉลย看答案

📄 rerank.py

"""rerank.py — challenge 3: a slower model, to check the fast one's work.

An embedding model reads the question and the page separately, so the
comparison is cheap but blind. A cross encoder reads both at once and
scores the pair. Too slow to search with, exactly right for checking the
handful the search brought back.
"""
from sentence_transformers import CrossEncoder, SentenceTransformer

fast = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")
careful = CrossEncoder("cross-encoder/ms-marco-MiniLM-L6-v2")

PAGES = [
    "A bot that reads your mail once an hour and replies with a horoscope",
    "Checking the bitcoin price and placing real orders with Python",
    "Build a chat window with tkinter and a model that remembers",
    "Classes in Python, taught by building a window piece by piece",
    "Turn a Python function into a web page with Gradio",
    "Send email from Python, and read your inbox with IMAP",
    "Automate jobs with n8n, without writing much code",
    "Put a website on the internet with Astro and Vercel",
    "Give a model tools it can choose to call, with smolagents",
]
LIBRARY = fast.encode(PAGES)


def search(question, keep=4):
    """The fast model picks a few. The slow one puts them in order."""
    scores = fast.similarity(fast.encode([question]), LIBRARY)[0]
    shortlist = sorted(zip(scores.tolist(), PAGES), reverse=True)[:keep]

    # The cross encoder wants pairs, and gives one number per pair.
    pairs = [(question, page) for _, page in shortlist]
    second = careful.predict(pairs)

    reordered = sorted(zip(second.tolist(), [p for _, p in shortlist]),
                       reverse=True)
    return shortlist, reordered


def main():
    for question in ["how do I put my site online",
                     "I want a program that answers emails"]:
        first, second = search(question)
        print()
        print("Q:", question)
        print("  the fast model's order:")
        for score, page in first:
            print(f"     {score:6.3f}  {page}")
        print("  after the cross encoder looked again:")
        for score, page in second:
            print(f"     {score:6.2f}  {page}")


if __name__ == "__main__":
    main()
LineWhat it does · ทำอะไร · 做什么
CrossEncoder("cross-encoder/ms-marco-MiniLM-L6-v2")A different class, and a different kind of model. 91.8 MB.คนละคลาส และเป็นโมเดลคนละแบบ ขนาด 91.8 MB不同的类,也是不同种类的模型。91.8 MB。
pairs = [(question, page) for _, page in shortlist]It wants pairs. One score comes back per pair.มันต้องการคู่ และจะคืนคะแนนกลับมาคู่ละหนึ่งค่า它要的是成对。每一对回一个分数。
careful.predict(pairs)Raw logits, not a 0 to 1 score. Only the order matters.เป็นค่า logit ดิบ ไม่ใช่คะแนน 0 ถึง 1 สนใจแค่ลำดับก็พอ是原始 logits,不是 0 到 1 的分数。只有顺序有意义。

🖥️ WHAT IT PRINTED · the bottom two swapped places

Q: how do I put my site online
  fast model:   0.612  Astro and Vercel
                0.387  Gradio
                0.156  bitcoin
                0.092  tkinter
  cross encoder: -1.49  Astro and Vercel
                -10.81  Gradio
                -11.26  tkinter
                -11.41  bitcoin
THE NUMBERS ARE NOT ON THE SAME SCALE The first column is a similarity between 0 and 1. The second is a raw logit. Every one of these is negative. Do not compare them to each other. Do not show them to anyone. Use them to sort, then throw them away. คอลัมน์แรกคือความคล้ายระหว่าง 0 ถึง 1 ส่วนคอลัมน์ที่สองคือค่า logit ดิบ และทุกค่าติดลบหมด อย่าเอาสองอันนี้มาเทียบกัน และอย่าเอาไปแสดงให้ใครดู ใช้มันเรียงลำดับ แล้วก็ทิ้งไป 第一列是 0 到 1 之间的相似度。第二列是原始 logit,而且每一个都是负的。别拿它们互相比较,也别拿给任何人看。用它们排个序,然后扔掉。

4Put the search on a web page · เอาการค้นหาไปไว้บนหน้าเว็บ · 把搜索放到网页上

🇬🇧 English

Take step 5 and give it a box to type in. Results update as you type, because a search costs milliseconds.

Use Gradio, from the page before this one.

🇹🇭 ไทย

เอาขั้นที่ 5 มาใส่ช่องให้พิมพ์ ผลลัพธ์จะอัปเดตขณะพิมพ์ เพราะการค้นหาใช้เวลาแค่มิลลิวินาที

ใช้ Gradio จากหน้าก่อนหน้านี้

🇨🇳 中文

把第 5 步加一个输入框。边打字边更新结果,因为一次搜索只要几毫秒。

用 Gradio,就是上一页那个。

💻 INSTALL IT TOO

pip install gradio
Show the answerดูเฉลย看答案

📄 search_app.py

"""search_app.py — challenge 4: the search from step 5, as a web page."""
import gradio as gr
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")

PAGES = [
    "A bot that reads your mail once an hour and replies with a horoscope",
    "Checking the bitcoin price and placing real orders with Python",
    "Build a chat window with tkinter and a model that remembers",
    "Classes in Python, taught by building a window piece by piece",
    "Turn a Python function into a web page with Gradio",
    "Send email from Python, and read your inbox with IMAP",
    "Automate jobs with n8n, without writing much code",
    "Put a website on the internet with Astro and Vercel",
    "Give a model tools it can choose to call, with smolagents",
]

# Encoded once, when the program starts, not once per search. This is the
# whole reason a search like this is fast.
LIBRARY = model.encode(PAGES)


def find(question, how_many):
    """Return the best pages as rows, ready for a table."""
    if not question.strip():
        return []
    scores = model.similarity(model.encode([question]), LIBRARY)[0]
    ranked = sorted(zip(scores.tolist(), PAGES), reverse=True)
    return [[round(score, 3), page] for score, page in ranked[:int(how_many)]]


with gr.Blocks(title="Search by meaning") as demo:
    gr.Markdown("## Search by meaning\nAsk in English, ไทย or 中文.")
    with gr.Row():
        question = gr.Textbox(label="What are you looking for?", scale=3)
        how_many = gr.Slider(1, 9, value=3, step=1, label="How many", scale=1)
    results = gr.Dataframe(headers=["score", "page"], label="Closest pages",
                           wrap=True)

    # Searching costs milliseconds, so there is no need for a button.
    # change() runs as the person types.
    question.change(fn=find, inputs=[question, how_many], outputs=results)
    how_many.change(fn=find, inputs=[question, how_many], outputs=results)

    gr.Examples(examples=["how do I make a window", "ส่งอีเมลด้วย Python",
                          "让模型自己调用工具"],
                inputs=question)

if __name__ == "__main__":
    demo.launch()
LineWhat it does · ทำอะไร · 做什么
LIBRARY = model.encode(PAGES)Outside every function, so it runs once and not per search.อยู่นอกทุกฟังก์ชัน จึงทำงานครั้งเดียว ไม่ใช่ทุกครั้งที่ค้น放在所有函数外面,所以只跑一次,不是每次搜索都跑。
question.change(fn=find,change, not click. It runs on every keystroke.ใช้ change ไม่ใช่ click มันทำงานทุกครั้งที่กดแป้น用 change,不是 click。每敲一个键它就跑一次。
gr.Dataframe(headers=["score", "page"],A table. Return a list of rows and gradio draws it.เป็นตาราง คืนค่าเป็นลิสต์ของแถว แล้ว gradio จะวาดให้一个表格。返回一个行的列表,gradio 就画出来。

🖥️ WHAT IT PRINTED · and the page really served

find('how do I make a window', 3)
  [0.398, 'Classes in Python, taught by building a window piece by piece']
  [0.373, 'Build a chat window with tkinter and a model that remembers']
  [0.218, 'Turn a Python function into a web page with Gradio']

served at http://127.0.0.1:7895/   status 200, 17,768 bytes

🇬🇧 English

Ten files. The useful idea is one sentence long: meaning can be a row of numbers.

Once it is numbers, a computer can sort it, search it and group it, in milliseconds, in any language.

What it cannot do is tell a question from a complaint. For that you still need a model that reads.

🇹🇭 ไทย

สิบไฟล์ แนวคิดที่มีประโยชน์ยาวแค่ประโยคเดียว คือความหมายเป็นแถวตัวเลขได้

พอมันเป็นตัวเลขแล้ว คอมพิวเตอร์ก็เรียง ค้น และจัดกลุ่มมันได้ ในหลักมิลลิวินาที ในภาษาไหนก็ได้

สิ่งที่มันทำไม่ได้คือแยกคำถามออกจากคำบ่น เรื่องนั้นคุณยังต้องใช้โมเดลที่อ่านเป็น

🇨🇳 中文

十个文件。有用的那个想法只有一句话:意思可以是一排数字。

一旦变成数字,电脑就能在毫秒之内给它排序、搜索、分组,什么语言都行。

它做不到的,是分清一个问题和一句抱怨。那还是得靠一个会读的模型。

You wantคุณอยากได้你想要Look upไปหาคำว่า去查
Millions of documentsเอกสารเป็นล้าน上百万篇文档FAISS, or a vector databaseFAISS หรือฐานข้อมูลเวกเตอร์FAISS,或者向量数据库
Better answers on your own dataคำตอบที่ดีขึ้นกับข้อมูลของคุณเอง在你自己数据上更好的答案fine-tuning, with labelled pairsการไฟน์จูน ด้วยคู่ข้อมูลที่ติดป้ายแล้ว微调,用标注好的配对
Pictures, not sentencesรูปภาพ ไม่ใช่ประโยค图片,不是句子CLIP
Run it fasterให้มันเร็วขึ้น跑得更快ONNX, or a GPUONNX หรือ GPUONNX,或者一块 GPU
A model to chat withโมเดลไว้คุยด้วย一个能聊天的模型Ollama, on its own pageOllama ซึ่งมีหน้าของมันเองOllama,它有自己的一页

🇬🇧 English

Next: Gradio, the mail bot whose question this page answered again, or agents and tools.

🇹🇭 ไทย

ต่อไป: Gradio, บอทอีเมล ที่หน้านี้ตอบคำถามของมันอีกครั้ง หรือ เอเจนต์และเครื่องมือ

🇨🇳 中文

接下来:Gradio、那个邮件机器人(这一页又回答了它的问题),或者 智能体和工具。

Fifteen lines, your own address, and an app password. That is the whole trick. · สิบห้าบรรทัด อีเมลของคุณเอง และ app password หนึ่งอัน เคล็ดลับมีแค่นี้ · 十五行代码、你自己的邮箱地址,加一个应用专用密码。诀窍就这些。

All ten files on this page were run on 24 September 2026 against transformers 5.8.0 and sentence_transformers 6.1.0, and every output shown is copied from those runs. The accuracy figures in step 6 and challenge 2 come from the same 57 subject lines used on the mail bot page, so the rows can be compared: the substring check got 26, embeddings 45, and llama3.2 57. Two things on this page are version-specific and were checked rather than remembered. pipeline("summarization"), "translation" and "question-answering" raise a KeyError in transformers 5, although "sentiment-analysis" still resolves; and get_sentence_embedding_dimension is deprecated in sentence_transformers 6 in favour of get_embedding_dimension. Sizes measured on this laptop: torch 0.47 GB, the English model 91.6 MB, the multilingual one 479.7 MB.