Embeddings: Bedeutung in Zahlen verwandeln
Mit einem Open-Source-Embedding-Modell für Chinesisch lokal Sätze in Vektoren verwandeln und ihre Bedeutung per Kosinus-Ähnlichkeit vergleichen; sehen, was es gut kann und was es nicht unterscheidet. Das ist die Grundlage für semantische Suche und RAG.
- Etwa 35 Minuten
- Niveau: Einsteiger
- Getestet: 2026-09-14 bge-small-zh-v1.5, sentence-transformers
Code und Programmausgaben stehen genau so da, wie sie gelaufen sind – Kommentare und Ausgaben sind daher auf Chinesisch.
Angenommen, du baust den Frage-Antwort-Assistenten für httpx. Der Nutzer fragt „Wie stelle ich in httpx ein Request-Timeout ein?“, in der Dokumentation steht aber „timeout configuration“; fragt er „Wie lasse ich eine Anfrage länger warten, bevor sie einen Fehler meldet?“, kommt in der Dokumentation das Wort „Timeout“ gar nicht vor. Mit einer Stichwortsuche findest du für keine dieser Formulierungen den passenden Absatz.
Du brauchst eine Methode, die „Bedeutung“ vergleicht statt „Wortlaut“. Genau das leisten Embeddings.
Was ein Embedding ist
Ein Embedding-Modell ist eine andere Art von Modell. Es erzeugt keinen Text, sondern tut nur eines: Es nimmt einen Text und gibt eine Zahlenfolge fester Länge aus, also einen Vektor. Trainiert wird es mit dem Ziel: Texte mit ähnlicher Bedeutung bekommen ähnliche Vektoren.
„Ähnlich“ misst man mit einer Zahl, am häufigsten mit der Kosinus-Ähnlichkeit: Je mehr die Richtungen zweier Vektoren übereinstimmen, desto näher liegt der Wert bei 1; haben sie nichts miteinander zu tun, liegt er nahe 0. Mit einem zweidimensionalen Beispiel bekommt man ein Gefühl dafür:
import numpy as np
def cosine(a, b):
return a @ b / (np.linalg.norm(a) * np.linalg.norm(b))
print(cosine(np.array([1, 2]), np.array([2, 4]))) # 方向完全相同,长度不同
print(cosine(np.array([1, 2]), np.array([2, -1]))) # 互相垂直
0.9999999999999998
0.0
[1, 2] und [2, 4] sind unterschiedlich lang, zeigen aber in genau dieselbe Richtung, daher ist die Ähnlichkeit 1 (die ausgegebenen 0.9999999999999998 sind ein winziger Rundungsfehler der Gleitkommarechnung und gelten als 1). Die Kosinus-Ähnlichkeit betrachtet nur die Richtung, nicht die Länge, und genau das wollen wir: Ob ein Satz etwas länger oder kürzer ist, soll seine Bedeutung nicht verändern.
Echte Embedding-Vektoren haben Hunderte bis Tausende Dimensionen und lassen sich nicht zeichnen, aber gerechnet wird genauso.
DeepSeek hat keine Embedding-Schnittstelle
Stand September 2026 bietet die API von DeepSeek nur Chat-Modelle an, keine Embedding-Modelle. Ein Aufruf der Embedding-Schnittstelle über das OpenAI-SDK liefert direkt 404.
Es gibt zwei Auswege:
- Die Embedding-API eines anderen Anbieters nutzen. Alibaba Cloud Bailian, Zhipu, OpenAI und andere bieten Embedding-Schnittstellen an, aufgerufen wie die Chat-Schnittstelle über das OpenAI-SDK (
client.embeddings.create(...)); die konkreten Modellnamen stehen in der jeweiligen Dokumentation. - Ein Open-Source-Embedding-Modell lokal ausführen. Embedding-Modelle sind viel kleiner als Chat-Modelle und laufen auf der CPU eines normalen Rechners.
Dieser Kurs nimmt den zweiten Weg, und zwar bge-small-zh-v1.5, das die Beijing Academy of Artificial Intelligence (BAAI) als Open Source veröffentlicht hat: speziell für Chinesisch trainiert, nur 24 Millionen Parameter, 92 MB Download, völlig kostenlos, ohne Internet nutzbar, und deine Unterlagen gelangen nicht zu Dritten.
Praxis: die Bedeutung einiger Sätze vergleichen
Ein Paket installieren:
uv add sentence-transformers
sentence-transformers ist eine Bibliothek speziell zum Ausführen von Embedding-Modellen. Beim ersten Start lädt sie das Modell von Hugging Face herunter; ist der Download aus China langsam, setz zuerst einen Mirror:
export HF_ENDPOINT=https://hf-mirror.com
Dann dieses Skript ausführen:
import numpy as np
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("BAAI/bge-small-zh-v1.5")
query = "httpx 怎么设置请求超时?"
sentences = [
"How do I set a timeout for requests in httpx?",
"httpx 的 timeout 参数默认是 5 秒。",
"给 httpx 客户端配置代理服务器",
"requests 库如何设置超时时间",
"今天中午吃了一碗牛肉面",
]
# normalize_embeddings=True 把每个向量的长度缩放成 1,这样点积就等于余弦相似度
vectors = model.encode([query] + sentences, normalize_embeddings=True)
print(f"每句话变成了一个 {vectors.shape[1]} 维的向量")
print(f"第一句的前 5 个数:{np.round(vectors[0][:5], 4)}")
print()
q, rest = vectors[0], vectors[1:]
scores = rest @ q
print(f"和「{query}」的相似度:")
for score, text in sorted(zip(scores, sentences), reverse=True):
print(f" {score:.3f} {text}")
Ergebnis (dieser Code ruft keine API auf, du solltest dieselben Zahlen bekommen):
每句话变成了一个 512 维的向量
第一句的前 5 个数:[-0.0232 0.0017 0.0812 0.0058 0.0116]
和「httpx 怎么设置请求超时?」的相似度:
0.777 requests 库如何设置超时时间
0.673 httpx 的 timeout 参数默认是 5 秒。
0.669 How do I set a timeout for requests in httpx?
0.659 给 httpx 客户端配置代理服务器
0.202 今天中午吃了一碗牛肉面
Jeder Satz wurde zu 512 Zahlen. Einzeln betrachtet haben sie keine Bedeutung; aussagekräftig ist die Ähnlichkeit zwischen den Vektoren.
Was im Ergebnis steckt
Bedeutungsverwandte Sätze erzielen hohe Werte, unverwandte niedrige. Die vier Sätze zu HTTP-Anfragen liegen alle über 0,65, „今天中午吃了一碗牛肉面“ (heute Mittag habe ich eine Schüssel Rindfleischnudeln gegessen) bei nur 0,2. Diese Unterscheidung ist klar.
Auch sprachübergreifend funktioniert es. Das englische „How do I set a timeout for requests in httpx?“ hat mit der chinesischen Frage kein einziges Zeichen gemeinsam und erreicht 0,669. Genau darin sind Embeddings der Stichwortsuche überlegen.
Aber es unterscheidet nicht, um welche Bibliothek es geht. Den höchsten Wert erzielt ausgerechnet „requests 库如何设置超时时间“ (wie stellt man in der Bibliothek requests die Timeout-Zeit ein), höher als beide httpx-Sätze. Für das Embedding-Modell sind Satzmuster und Bedeutung „wie stellt man in Bibliothek X ein Timeout ein“ das Wichtigste; ob es requests oder httpx ist, ist nur ein Nebenunterschied. Für einen Frage-Antwort-Assistenten ist aber genau das der entscheidende Unterschied: Beantwortet man eine httpx-Frage mit der requests-Dokumentation, liegt man falsch.
Ebenso handelt „给 httpx 客户端配置代理服务器“ (einen Proxy-Server für den httpx-Client konfigurieren) von Proxys, nicht von Timeouts, und bekommt trotzdem 0,659, nur knapp hinter den Timeout-Sätzen.
Das zeigt: Embeddings erfassen den „Grundsinn“ gut, reagieren aber wenig empfindlich auf Präzises wie Eigennamen, Funktionsnamen oder Versionsnummern. Modul 04, Lektion 4 kombiniert Embeddings mit Stichwortsuche, um genau dieses Problem zu lösen.
Die Werte haben keine absolute Bedeutung. Ist 0,67 hoch oder niedrig? Das lässt sich nicht allgemein sagen. Verschiedene Embedding-Modelle haben unterschiedliche Wertebereiche, und auch dasselbe Modell verhält sich in verschiedenen Fachgebieten anders. Nützlich ist der Vergleich: Welche Absätze stehen bei derselben Frage vorn? Schreib keine Regel wie „ab Ähnlichkeit 0,7 gilt als relevant“ fest in den Code, es sei denn, du hast diesen Schwellenwert an deinen eigenen Daten geprüft.
Wofür man Embeddings nutzt
- Semantische Suche: Die Vektoren aller Dokumentabsätze vorab berechnen und speichern; stellt der Nutzer eine Frage, deren Vektor berechnen und die ähnlichsten Absätze suchen. Das ist der Kern von RAG; Modul 04, Lektion 3 schreibt das von Grund auf.
- Duplikate finden: Sind die Vektoren zweier Texte fast gleich, handelt es sich meist um doppelten Inhalt.
- Klassifizieren und Clustern: Nutzerfeedback oder Issues automatisch nach Bedeutung gruppieren.
- Empfehlungen: Inhalte finden, die dem ähneln, was der Nutzer schon gesehen hat.
Fallen bei der Nutzung
Frage und Dokumente müssen dasselbe Modell verwenden. Vektoren verschiedener Embedding-Modelle sind völlig inkompatibel, wie zwei verschiedene Koordinatensysteme. Wechselst du das Embedding-Modell, müssen alle Dokumente neu berechnet werden.
Es gibt eine Längengrenze. bge-small-zh-v1.5 liest höchstens 512 Tokens; alles darüber wird einfach verworfen, ohne Fehlermeldung. Lange Dokumente muss man daher zuerst in kleine Abschnitte zerlegen und diese einzeln berechnen; das ist Thema von Modul 04, Lektion 2.
Manche Modelle wollen ein Präfix vor der Frage. Einige Embedding-Modelle empfehlen, der Suchanfrage einen festen Satz voranzustellen, etwa „为这个句子生成表示以用于检索相关文章:“ (erzeuge eine Repräsentation dieses Satzes für die Suche nach relevanten Artikeln:), was die Suchergebnisse verbessert. Ob und was, steht in der Beschreibung des Modells. In dieser Lektion habe ich es der Einfachheit halber weggelassen.
Übungen
- Füg
sentencesein paar eigene Sätze hinzu: einen, der dasselbe mit anderen Worten sagt („httpx 请求等太久怎么办“, was tun, wenn eine httpx-Anfrage zu lange wartet), und einen mit denselben Stichwörtern, aber anderer Bedeutung („超时费用怎么计算“, wie berechnet man Überziehungsgebühren). Wo landen sie? - Nimm als Anfrage das englische „How to configure a proxy in httpx?“ und schau, wie sich die Rangfolge ändert.
- Wenn du einen Schlüssel für Bailian, Zhipu oder OpenAI hast, ruf deren Embedding-Schnittstelle mit
client.embeddings.create(model=..., input=[...])auf, berechne die Ähnlichkeiten derselben Sätze und vergleiche mit dem lokalen Modell. Achtung: Die Wertebereiche können völlig verschieden sein; vergleiche nur die Rangfolge.
Selbsttest
1. Warum betrachtet die Kosinus-Ähnlichkeit nur die Richtung eines Vektors und nicht seine Länge?
Uns interessiert, ob zwei Texte ähnliche Bedeutung haben, nicht ihre Länge oder andere irrelevante Größen. Die Kosinus-Ähnlichkeit teilt durch die Längen der beiden Vektoren und behält nur die Richtung. Normiert man die Vektoren vorab auf Länge 1, ist das Skalarprodukt direkt die Kosinus-Ähnlichkeit, was schneller zu rechnen ist.
2. Der Nutzer fragt nach httpx, die Embedding-Suche setzt aber die Dokumentation von requests auf Platz eins. Warum? Was tun?
Das Embedding-Modell erfasst den Gesamtsinn; „wie stellt man in einer Bibliothek ein Timeout ein“ ist sehr ähnlich, und der Bibliotheksname ist nur ein Nebenunterschied, also bekommen beide hohe Werte. Abhilfe: Embedding-Suche mit Stichwortsuche kombinieren (die auf exakte Wörter wie „httpx“ reagiert) oder die Suche auf die httpx-Dokumentation beschränken.
3. Was passiert, wenn du einen Artikel von 5000 Zeichen direkt an bge-small-zh-v1.5 gibst?
Es liest nur die ersten 512 Tokens, der Rest wird abgeschnitten und verworfen, ohne Fehlermeldung. Der berechnete Vektor repräsentiert nur die Bedeutung des Anfangs. Lange Dokumente muss man daher in kleine Abschnitte zerlegen und jeden einzeln berechnen.
Fragen und Diskussion
Hängst du in dieser Lektion fest? Frag hier. Und wenn du die Frage von jemandem beantworten kannst, tu es gern.
Eine Frage bringt 3 Punkte, eine Antwort 6. Beiträge erscheinen nach der Prüfung.
Diskussion wird geladen…