generative-ai-for-beginners 第 08 课实战:基于文本嵌入与余弦相似度的语义搜索应用构建
本篇技术指南以《generative-ai-for-beginners》课程第 08 课"构建搜索应用"为核心,完整讲解如何用文本嵌入(Embeddings)将"关键词匹配"升级为"语义理解",并结合课程仓库中真实的嵌入索引文件、数据准备脚本与 Jupyter Notebook 解决方案,带你从零搭建一个能对 YouTube 视频字幕进行语义检索、并精确定位到答案所在时间点的搜索应用。读完本文,你将掌握嵌入索引的构建流程、余弦相似度的计算原理,以及 Azure OpenAI 服务从创建、密钥获取到嵌入模型部署的完整命令链。
场景设定:为教育初创组织构建视频搜索应用
本课程设定了一个具体场景:一个面向发展中国家学生提供免费教育的非营利组织,拥有大量 AI/ML 教学 YouTube 视频(素材来自微软 AI Show 频道)。需求是让学生输入自然语言问题(例如"What are Jupyter Notebooks?"或"What is Azure ML?"),应用返回与问题相关的视频列表——更进一步,返回的链接带有时间戳,直接跳转到视频中包含答案的位置。
课程学习目标(继承自课程 README):
- 区分语义搜索与关键词搜索;
- 解释什么是文本嵌入(Text Embeddings);
- 创建一个使用嵌入来检索数据的应用程序。
课程提供的嵌入索引覆盖 AI Show 频道截至 2023 年 10 月的全部 YouTube 视频转录文本,可直接用于完成练习,无需自行重建索引。
语义搜索 vs 关键词搜索
语义搜索(Semantic search)是一种利用查询词语的**语义(含义)**而非字面匹配来返回相关结果的技术。课程给出一个经典例子:当你想买一辆"我梦想的车"(my dream car)时,语义搜索能理解你并非在"做梦",而是在寻找"理想"的车,从而返回相关结果;而关键词搜索(Keyword search)会按字面去检索"关于车的梦",往往返回大量无关内容。
这正是本课程的出发点:把学生输入的口语化问题当作"意图"来检索,而不是把问题拆成词去匹配字幕文本。
什么是文本嵌入(Text Embeddings)
文本嵌入是自然语言处理中一种文本表示技术:把文本编码为语义化的数值向量,使机器能够以可计算的方式理解文本含义。课程中嵌入的生成使用 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.011632772162556648, -0.02187200076878071, ...]
嵌入索引的结构与构建流程
索引文件的真实数据结构
课程附带一个名为 embedding_index_3m.json 的嵌入索引文件(约 48 MB,位于 08-building-search-applications/embedding_index_3m.json)。从仓库实际内容看,该文件是一个 JSON 数组,共 1409 条记录,每条记录对应一段约 3 分钟的视频字幕片段,字段包括:
| 字段 | 说明 |
|---|---|
title |
视频标题 |
speaker |
视频中识别出的演讲者姓名 |
videoId |
YouTube 视频 ID(用于拼接带时间戳的链接) |
start / seconds |
该片段的起始时间与对应的秒数(用于生成 ?t=秒数 跳转链接) |
summary |
该片段的 60 词以内摘要(由 Chat 模型生成) |
ada_v2 |
该片段的 OpenAI text-embedding-ada-002 嵌入向量,长度 1536 |
文件名中的 3m 即"3 分钟片段"(3-minute segment)的缩写,与下面第 3 步的切分策略一一对应。
五步流水线:索引是如何生成的
课程文档说明该索引由一组 Python 脚本生成,脚本及说明见 08-building-search-applications/scripts/README.md。完整流程为:
- 下载转录文本:从 AI Show 播放列表下载每个 YouTube 视频的转录文本。对应 scripts/transcript_download.py,其依赖
youtube-transcript-api(见 scripts/requirements.txt)。 - 识别演讲者:利用 OpenAI Functions(函数调用)尝试从每个视频转录的前 3 分钟中提取演讲者姓名,存入索引。这一点可从源码印证:scripts/transcript_enrich_speaker.py 中定义了
get_speaker_name函数工具(tool_choice强制调用该函数),并设有SEGMENT_MIN_LENGTH_MINUTES = 3常量,与文档"前 3 分钟"的描述一致。 - 切分为 3 分钟文本片段:转录文本被切分为 3 分钟的文本段,且每个片段约包含 20 个词与下一片段重叠,目的是避免片段的嵌入被"截断"、同时提供更好的搜索上下文。对应的切分逻辑在 scripts/transcript_enrich_bucket.py 中实现,其中定义了
SEGMENT_LENGTH_MINUTES(默认值 5,可通过命令行参数--minutes覆盖)、PERCENTAGE_OVERLAP = 0.05重叠比例与MAX_TOKENS = 2048上限,并使用tiktoken进行 token 计数。 - 生成 60 词摘要:每个文本片段被送入 OpenAI Chat API,总结为 60 词以内的摘要,摘要同样存入索引。
- 生成嵌入向量:片段文本最终送入 OpenAI 嵌入 API,返回代表该片段语义的 1536 维向量,与片段一起写入索引。scripts/transcript_enrich_embeddings.py 即执行此步:它通过
AzureOpenAI客户端调用嵌入服务(部署名默认回退为text-embedding-ada-002),并使用PROCESSING_THREADS = 6的多线程并发加速批量生成。
完成课程练习不需要运行这些脚本,因为索引文件已随仓库提供;脚本价值在于展示"从原始转录到可检索向量库"的完整数据工程链路。
向量数据库
为了教学简洁,索引以 JSON 文件形式存储并加载进 Pandas DataFrame。但在生产环境中,嵌入索引会存入专用向量数据库,例如 Azure Cognitive Search、Redis、Pinecone、Weaviate 等——课程在英文原版 README中给出了这些方案的参考链接。
理解余弦相似度
学会文本嵌入后,下一步是学习如何用嵌入检索数据:给定查询,找出索引中与之最相似的嵌入,即"余弦相似度"(Cosine similarity),也常被称为"最近邻搜索"。
流程为:
- 用 OpenAI 嵌入 API 把查询文本向量化;
- 计算查询向量与索引中每个向量之间的余弦相似度(索引中每个 YouTube 字幕片段都有一个向量);
- 按相似度排序,相似度最高的片段即与查询最相关。
从数学角度看,余弦相似度衡量的是两个向量在多维空间中投影夹角的余弦值。这一度量的优势在于:即使两个文档因长度不同而在欧氏距离上相距较远,只要它们方向相近(夹角小),仍可获得较高的余弦相似度——因此它比原始距离更适合比较文本语义。
课程解决方案 Notebook 中对这一公式给出了直接实现(python/aoai-solution.ipynb):
def cosine_similarity(a, b):
if len(a) > len(b):
b = np.pad(b, (0, len(a) - len(b)), 'constant')
elif len(b) > len(a):
a = np.pad(a, (0, len(b) - len(a)), 'constant')
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
即标准公式 cos(θ) = A·B / (|A|·|B|),并在长度不一致时用零填充保证可计算。
解决方案 Notebook 全解析
该解决方案在 Windows 11、macOS、Ubuntu 22.04 上使用 Python 3.10+ 构建与测试(Python 可从 python.org 下载)。打开 08-building-search-applications/python/aoai-solution.ipynb,各单元格的职责如下:
1. 初始化客户端与常量。 使用 openai 1.x SDK 的 AzureOpenAI 客户端,api_version 为 2024-10-21,模型取环境变量 AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT 指向的部署名:
client = AzureOpenAI(
api_key=os.environ['AZURE_OPENAI_API_KEY'], # 这也是默认值,可省略
api_version = "2024-10-21",
azure_endpoint = os.environ['AZURE_OPENAI_ENDPOINT']
)
model = os.environ['AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT']
SIMILARITIES_RESULTS_THRESHOLD = 0.75
DATASET_NAME = "../embedding_index_3m.json"
注意 SIMILARITIES_RESULTS_THRESHOLD = 0.75 这一相似度阈值:它是控制结果"宁缺毋滥"的关键参数,低于 0.75 的片段会被过滤掉。
2. 加载索引。 用 pd.read_json 读取索引,并丢弃原始 text 列(搜索只需要向量与元数据):
def load_dataset(source: str) -> pd.core.frame.DataFrame:
# Load the video session index
pd_vectors = pd.read_json(source)
return pd_vectors.drop(columns=["text"], errors="ignore").fillna("")
3. 核心检索函数 get_videos。 按查询检索并返回 Top 5 结果,逻辑为:
- 复制索引数据框;
- 调用
client.embeddings.create(input=query, model=model)生成查询向量; - 对
ada_v2列逐行计算与查询向量的余弦相似度,写入新列similarity; - 过滤出
similarity >= 0.75的行; - 按
similarity降序排序并返回前 N 行。
4. 结果展示函数 display_results。 遍历结果行,为每条记录生成形如 https://youtu.be/{videoId}?t={seconds} 的带时间戳 YouTube 链接,并打印视频标题、摘要前 15 词、相似度与演讲者。这正是"跳转到答案所在位置"功能的实现——seconds 字段来自索引中每个片段的起始时间。
5. 主循环。 加载数据集后进入 while True 交互循环:提示用户输入查询,输入 exit 退出,否则调用 get_videos(query, pd_vectors, 5) 检索并打印结果。
运行后会出现查询输入框,课程还建议尝试这些查询:
- 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?
环境准备:Azure OpenAI 资源部署与依赖
Azure CLI 命令链
本练习需要创建一个 Azure 订阅,并在 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
获取应用所需 endpoint 与密钥:
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 嵌入模型 text-embedding-ada-002:
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"
环境变量与依赖
Notebook 通过 dotenv 读取 .env 文件,需要以下环境变量:
AZURE_OPENAI_API_KEY:Azure OpenAI 服务 API 密钥;AZURE_OPENAI_ENDPOINT:服务 endpoint;AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT:嵌入模型部署名(代码中实际引用的变量名;Notebook 首段说明文字写作AZURE_OPENAI_EMBEDDINGS_ENDPOINT,以代码为准)。
若需要重建索引,脚本侧还需要 GOOGLE_DEVELOPER_API_KEY(用于 Google API 获取视频元数据),见 scripts/README.md。Python 依赖见 08-building-search-applications/python/requirements.txt(pandas、numpy、scipy、scikit-learn 等),数据准备脚本的依赖见 scripts/requirements.txt,其中 openai 版本约束为 >=1.54.0,<2.0.0,与 Notebook 中 from openai import AzureOpenAI 的 1.x 风格 API 相匹配。从源码结构看,python/requirements.txt 中锁定的 openai>=0.28.0,<0.29.0 与 Notebook 实际使用的 1.x API 存在版本差异,运行时若出现导入错误,优先按 Notebook 与脚本 README 使用 1.x 以上版本排查。
小结与延伸
本课程通过"视频语义搜索"这一完整案例,串起了嵌入技术的关键链路:文本 → 1536 维语义向量 → 余弦相似度排序 → 阈值过滤 → 带时间戳的精准链接。仓库中 08-building-search-applications/embedding_index_3m.json 提供了 1409 个片段的现成索引,scripts 目录展示了从 YouTube 转录到向量化索引的数据工程全过程,python/aoai-solution.ipynb 则给出了可直接运行的检索应用。完成本课后可继续第 09 课构建图像生成应用,扩展对多模态生成的理解。
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 StartedRust0627
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

