首页
/ generative-ai-for-beginners 第 08 课实战:基于文本嵌入与余弦相似度的语义搜索应用构建

generative-ai-for-beginners 第 08 课实战:基于文本嵌入与余弦相似度的语义搜索应用构建

2026-09-07 16:03:02作者:咎竹峻Karen

本篇技术指南以《generative-ai-for-beginners》课程第 08 课"构建搜索应用"为核心,完整讲解如何用文本嵌入(Embeddings)将"关键词匹配"升级为"语义理解",并结合课程仓库中真实的嵌入索引文件、数据准备脚本与 Jupyter Notebook 解决方案,带你从零搭建一个能对 YouTube 视频字幕进行语义检索、并精确定位到答案所在时间点的搜索应用。读完本文,你将掌握嵌入索引的构建流程、余弦相似度的计算原理,以及 Azure OpenAI 服务从创建、密钥获取到嵌入模型部署的完整命令链。

语义查询示例:对问题"can you use rstudio with azure ml?"的检索结果,返回带时间戳的 YouTube 视频链接

场景设定:为教育初创组织构建视频搜索应用

本课程设定了一个具体场景:一个面向发展中国家学生提供免费教育的非营利组织,拥有大量 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。完整流程为:

  1. 下载转录文本:从 AI Show 播放列表下载每个 YouTube 视频的转录文本。对应 scripts/transcript_download.py,其依赖 youtube-transcript-api(见 scripts/requirements.txt)。
  2. 识别演讲者:利用 OpenAI Functions(函数调用)尝试从每个视频转录的前 3 分钟中提取演讲者姓名,存入索引。这一点可从源码印证:scripts/transcript_enrich_speaker.py 中定义了 get_speaker_name 函数工具(tool_choice 强制调用该函数),并设有 SEGMENT_MIN_LENGTH_MINUTES = 3 常量,与文档"前 3 分钟"的描述一致。
  3. 切分为 3 分钟文本片段:转录文本被切分为 3 分钟的文本段,且每个片段约包含 20 个词与下一片段重叠,目的是避免片段的嵌入被"截断"、同时提供更好的搜索上下文。对应的切分逻辑在 scripts/transcript_enrich_bucket.py 中实现,其中定义了 SEGMENT_LENGTH_MINUTES(默认值 5,可通过命令行参数 --minutes 覆盖)、PERCENTAGE_OVERLAP = 0.05 重叠比例与 MAX_TOKENS = 2048 上限,并使用 tiktoken 进行 token 计数。
  4. 生成 60 词摘要:每个文本片段被送入 OpenAI Chat API,总结为 60 词以内的摘要,摘要同样存入索引。
  5. 生成嵌入向量:片段文本最终送入 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),也常被称为"最近邻搜索"。

流程为:

  1. 用 OpenAI 嵌入 API 把查询文本向量化
  2. 计算查询向量与索引中每个向量之间的余弦相似度(索引中每个 YouTube 字幕片段都有一个向量);
  3. 按相似度排序,相似度最高的片段即与查询最相关。

从数学角度看,余弦相似度衡量的是两个向量在多维空间中投影夹角的余弦值。这一度量的优势在于:即使两个文档因长度不同而在欧氏距离上相距较远,只要它们方向相近(夹角小),仍可获得较高的余弦相似度——因此它比原始距离更适合比较文本语义。

课程解决方案 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_version2024-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 结果,逻辑为:

  1. 复制索引数据框;
  2. 调用 client.embeddings.create(input=query, model=model) 生成查询向量;
  3. ada_v2 列逐行计算与查询向量的余弦相似度,写入新列 similarity
  4. 过滤出 similarity >= 0.75 的行;
  5. 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?

Jupyter Notebook 中的查询输入框,用户可输入语义查询进行视频检索

环境准备: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 课构建图像生成应用,扩展对多模态生成的理解。

登录后查看全文
热门项目推荐
相关项目推荐