使用文本嵌入与余弦相似度构建语义搜索应用:generative-ai-for-beginners 第 08 课完整指南
本文基于 generative-ai-for-beginners 仓库第 08 课《Building a Search Applications》编写,系统讲解如何用文本嵌入(Embeddings)替代关键词匹配、构建可定位到视频时间戳的语义搜索应用。读完你既能掌握嵌入向量、余弦相似度与向量索引的核心原理,也能完整复现课程配套的 Azure OpenAI 资源部署命令,并读懂仓库中索引构建脚本与求解 Notebook 的实际实现。
课程背景:为教育机构构建“按问题搜索视频”的应用
大语言模型(LLM)的应用远不止聊天机器人与文本生成——利用嵌入(Embeddings)构建搜索应用同样是核心场景。嵌入是数据的数值表示,也称作向量(vector),可用于数据的语义检索。
本课程的场景设定:一家面向发展中国家学生提供免费教育的非营利创业公司,拥有大量可用来学习 AI 的 YouTube 视频(微软 AI Show 频道)。团队希望构建一个搜索应用:学生输入一个问题(例如 “What are Jupyter Notebooks?” 或 “What is Azure ML”),应用即返回与该问题相关的视频列表;更进一步,返回的链接会直接跳转到视频中包含答案的位置。
课程提供了 AI Show 频道 YouTube 字幕的现成嵌入索引(覆盖至 2023 年 10 月的全部视频字幕),你无需自己跑索引构建流程,直接用它就能完成搜索应用。下面的截图就是针对问题 “can you use rstudio with azure ml?” 的一次语义查询结果,注意返回的 YouTube URL 带有时间戳参数,可直达视频中回答问题的那一段。
课程覆盖内容与学习目标
本课程覆盖四个主题:语义搜索与关键词搜索的区别、什么是文本嵌入、如何创建文本嵌入索引、如何在索引中检索。
完成课程后,你将能够:
- 区分语义搜索(semantic search)与关键词搜索(keyword search);
- 解释文本嵌入是什么;
- 使用嵌入构建一个可以检索数据的应用。
什么是语义搜索?
语义搜索是一种利用查询中词语的语义(即含义)来返回相关结果的检索技术。
课程用一个买车例子说明:你搜索 “my dream car”(我梦想的车),语义搜索理解到你并不是想找“关于车的梦”,而是想购买你的“理想”座驾——它理解你的意图并返回相关结果。与之相对的是关键词搜索,它会字面上去匹配“车 + 梦想”这类词,往往返回不相关的结果。这个例子也点出了向量检索的核心价值:匹配的是“意图”,而不是“字面词”。
什么是文本嵌入?
文本嵌入(Text Embeddings)是自然语言处理(NLP)中常用的文本表示技术,是文本的语义化数值表示,把数据转换成机器易于理解的形式。业界有多种构建嵌入的模型,本课程聚焦 OpenAI 嵌入模型。
课程给出的示例:假设 AI Show 某期视频的字幕里有一句:
Today we are going to learn about Azure Machine Learning.
把这句话传给 OpenAI 嵌入 API,会返回一个由 1536 个数字构成的向量。向量中的每个数字都代表文本的某个不同侧面。为便于阅读,原文档只列出向量的前 10 个数字:
[-0.006655829958617687, 0.0026128944009542465, 0.008792596869170666, -0.02446001023054123, -0.008540431968867779, 0.022071078419685364, -0.010703742504119873, 0.003311325330287218, -0.01161632772162556648, -0.02187200076878071, ...]
这个 1536 维的设定在仓库的数据里可以直接验证:课程索引文件 embedding_index_3m.json 中每个片段的 ada_v2 字段就是一个长度 1536 的浮点数数组。
嵌入索引是如何创建的?
课程索引由一组 Python 脚本生成,脚本和说明位于 scripts/README.md。完成课程不需要自己运行这些脚本(索引已提供),但理解流水线是深入掌握 RAG 与向量检索的关键。脚本执行以下五步操作:
- 下载字幕:下载 AI Show 播放列表中每个 YouTube 视频的字幕;
- 提取演讲者:利用 OpenAI Functions,从每个视频前 3 分钟的字幕中尝试提取演讲者姓名,并存入索引
embedding_index_3m.json; - 按 3 分钟切分:把字幕文本切分为 3 分钟文本片段,每个片段会包含下一个片段约 20 个词的重叠内容,以确保片段的嵌入不被“截断”、并提升搜索上下文质量;
- 生成摘要:把每个片段传给 OpenAI 聊天 API,总结为 60 词的摘要,摘要同样存入
embedding_index_3m.json; - 生成嵌入:把片段文本传给 OpenAI 嵌入 API,得到代表该片段的 1536 维语义向量,与片段文本一起存入索引。
结合仓库源码看索引流水线的实现细节
仓库中这条流水线是真实可执行的,入口脚本为 prepare_transcripts_ai_show.sh。它设置了 TRANSCRIPT_FOLDER=transcripts_the_ai_show 与 TRANSCRIPT_BUCKET_MINUTES=3(即 3 分钟一个时间桶),然后按顺序调用六个脚本:
| 步骤 | 脚本 | 职责 |
|---|---|---|
| 1 | transcript_download.py | 从播放列表 PLlrxD0HtieHi0mwteKBOfEeOYf0LJU4O1 下载字幕 |
| 2 | transcript_enrich_speaker.py | 用 OpenAI Function Calling 提取演讲者姓名 |
| 3 | transcript_enrich_bucket.py | 按 N 分钟切分片段(-m 参数,脚本内默认 5 分钟,Shell 入口覆盖为 3) |
| 4 | transcript_enrich_summaries.py | 生成 60 词摘要 |
| 5 | transcript_enrich_embeddings.py | 生成 1536 维嵌入向量 |
| 6 | transcript_enrich_lite.py | 生成去除文本字段的轻量索引 |
从源码结构看,几个关键参数与教学文档的描述互相印证,且提供了文档未展开的工程细节:
- 片段切分与重叠:transcript_enrich_bucket.py 中定义
PERCENTAGE_OVERLAP = 0.05(上一片段尾部追加下一片段约 5% 的词数,即文档所说的“约 20 个词重叠”)和MAX_TOKENS = 2048(单片段 token 上限,同时为下一步摘要请求预留 1024 token)。切分前先拼入演讲者、标题、描述作为上下文前缀,再逐条解析.json.vtt字幕文件; - 摘要生成:transcript_enrich_summaries.py 的系统提示词要求“写一个权威的 60 词摘要,避免以 ‘This video’ 开头”,
temperature=0.7、top_p=0.0,默认模型gpt-4o-mini(可通过环境变量AZURE_OPENAI_MODEL_DEPLOYMENT_NAME覆盖); - 嵌入生成:transcript_enrich_embeddings.py 使用 6 个线程并发处理队列,默认嵌入部署名
text-embedding-ada-002(可用AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT覆盖),并用 tiktoken 检查文本——超过 8191 token 的片段直接跳过,这正是嵌入模型输入上限的工程保护;调用通过tenacity重试装饰器包裹(指数退避,最多 20 次); - 产物结构:流水线最后把结果重命名为
embedding_index_full_3m.json与embedding_index_3m.json。仓库中的 embedding_index_3m.json 共含 1409 个片段,每条记录包含speaker、title、videoId、start、seconds、summary、ada_v2(1536 维向量)七个字段——seconds字段正是搜索应用能生成“跳转到视频某一秒”链接的数据来源; - 依赖清单:scripts/requirements.txt 列出了流水线所需库,包括
openai>=1.54.0、pandas、tiktoken、youtube-transcript-api、tenacity、rich、scikit-learn、scipy等。
脚本运行所需的环境变量(Windows 用户变量或 Linux/macOS 的 ~/.bashrc/~/.zshrc)在 scripts/README.md 中有完整说明:AZURE_OPENAI_API_KEY、AZURE_OPENAI_ENDPOINT、AZURE_OPENAI_MODEL_DEPLOYMENT_NAME,以及用于下载 YouTube 字幕的 GOOGLE_DEVELOPER_API_KEY。
向量数据库
出于教学简化,课程索引保存在 JSON 文件 embedding_index_3m.json 中,并加载进 Pandas DataFrame。而在生产环境中,索引应存储在专门的向量数据库里,例如 Azure Cognitive Search、Redis、Pinecone、Weaviate 等——从 JSON + DataFrame 到向量数据库,是这类应用走向生产时的标准演进路径。
理解余弦相似度
学会了文本嵌入之后,下一步是学会如何用嵌入检索数据——核心是找到与查询最相似的嵌入,即余弦相似度(cosine similarity)。
什么是余弦相似度?
余弦相似度衡量两个向量之间的相似程度,它也常被称为“最近邻搜索(nearest neighbor search)”。执行一次余弦相似度检索的完整流程是:
- 用 OpenAI 嵌入 API 把查询文本向量化;
- 计算查询向量与索引中每个向量的余弦相似度(索引中每个 YouTube 字幕片段都有一个向量);
- 按相似度排序,相似度最高的文本片段就是与查询最相似的结果。
从数学角度看,余弦相似度度量的是两个向量投影到高维空间中夹角的余弦值。这一度量的好处在于:即使两份文档因长度差异导致欧氏距离很远,只要夹角小,余弦相似度依然可以很高——也就是说,相似度由“方向”而非“模长”决定,天然适合比较长短不一的文本片段。
求解 Notebook 中的实现
课程的官方解法在 aoai-solution.ipynb 中,其中的 cosine_similarity 函数用 NumPy 实现了标准公式:先对长度不齐的向量做零填充,再计算点积除以两向量模长之积(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))。配套的 get_videos 函数完整演示了检索主流程:
model = os.environ['AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT']
SIMILARITIES_RESULTS_THRESHOLD = 0.75 # 相似度过滤阈值
DATASET_NAME = "../embedding_index_3m.json"
def get_videos(query: str, dataset, rows: int):
video_vectors = dataset.copy()
# 1. 查询文本向量化
query_embeddings = client.embeddings.create(
input=query, model=model).data[0].embedding
# 2. 对索引每一行计算余弦相似度
video_vectors["similarity"] = video_vectors["ada_v2"].apply(
lambda x: cosine_similarity(np.array(query_embeddings), np.array(x)))
# 3. 阈值过滤:只保留相似度 >= 0.75 的行
mask = video_vectors["similarity"] >= SIMILARITIES_RESULTS_THRESHOLD
video_vectors = video_vectors[mask].copy()
# 4. 按相似度降序,取前 N 条(rows=5)
video_vectors = video_vectors.sort_values(
by="similarity", ascending=False).head(rows)
return video_vectors
可以看到,课程描述的“计算—过滤—排序”三步在代码里一一对应,且给出了两个可复用的关键参数:相似度阈值 0.75(低于它的结果视为不相关,直接丢弃)和返回条数 5(返回最相关的前 5 个片段)。display_results 函数则把命中片段转成带时间戳的链接:https://youtu.be/{videoId}?t={seconds},其中 seconds 正是索引里记录的字幕片段起始秒数——这就是“搜索应用能把用户带到视频中答案所在位置”的实现原理。
构建你的第一个搜索应用
环境要求
课程解法在 Windows 11、macOS、Ubuntu 22.04 上使用 Python 3.10 或更高版本构建并测试,可从 python.org 下载 Python。
实战任务:创建 Azure OpenAI 资源
课程的动手任务要求用 Azure OpenAI 服务构建搜索应用(需要一个 Azure 订阅)。完整操作步骤如下:
启动 Azure Cloud Shell:登录 Azure 门户,点击右上角的 Cloud Shell 图标,环境类型选择 Bash。
创建资源组(课程使用位于 East US 的资源组 semantic-video-search;可以改名字,但更换区域时须核对模型可用性表):
az group create --name semantic-video-search --location eastus
创建 Azure OpenAI 服务资源:
az cognitiveservices account create --name semantic-video-openai --resource-group semantic-video-search \
--location eastus --kind OpenAI --sku s0
获取端点与密钥(应用需要用到):
az cognitiveservices account show --name semantic-video-openai \
--resource-group semantic-video-search | jq -r .properties.endpoint
az cognitiveservices account keys list --name semantic-video-openai \
--resource-group semantic-video-search | jq -r .key1
部署 OpenAI 嵌入模型:
az cognitiveservices account deployment create \
--name semantic-video-openai \
--resource-group semantic-video-search \
--deployment-name text-embedding-ada-002 \
--model-name text-embedding-ada-002 \
--model-version "2" \
--model-format OpenAI \
--sku-capacity 100 --sku-name "Standard"
补充说明:脚本流水线 scripts/README.md 中还额外部署了 gpt-4o-mini 模型(用于摘要与演讲者提取),因为完整重建索引需要这两个模型协同工作。
运行求解 Notebook
在 GitHub Codespaces 中打开 aoai-solution.ipynb,按 Notebook 内提示配置 .env(AZURE_OPENAI_API_KEY、AZURE_OPENAI_ENDPOINT、AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT,即指向你部署的 text-embedding-ada-002),运行后会提示输入查询。主循环逻辑是:加载索引 → 等待用户输入 → 调用 get_videos 检索 → display_results 输出结果 → 继续等待下一次输入,输入 exit 退出。
可尝试的查询示例(来自 Notebook):
- What is Azure Machine Learning?
- How do convolutional neural networks work?
- What is a neural network?
- Can I use Jupyter Notebooks with Azure Machine Learning?
- What is ONNX?
输出会列出每条命中视频的标题、15 词摘要、带时间戳的 YouTube 链接、相似度分数与演讲者。
课程小结与延伸
这一课建立了生成式 AI 搜索应用的完整知识链:语义搜索理念 → 文本嵌入(1536 维向量)→ 索引构建流水线(字幕下载、演讲者提取、3 分钟切分、60 词摘要、嵌入生成)→ 余弦相似度检索(阈值 0.75、Top-5)→ 时间戳精准定位。仓库中从 脚本流水线、1409 条片段的真实索引数据 到 求解 Notebook 形成了可对照验证的完整证据链;英文原版课程文档在 08-building-search-applications/README.md。
完成本课之后,可以继续第 09 课,学习构建图像生成应用:09-building-image-applications/README.md。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00

