首页
/ 用 Embeddings 构建语义搜索应用:generative-ai-for-beginners 第 8 课视频搜索实战

用 Embeddings 构建语义搜索应用:generative-ai-for-beginners 第 8 课视频搜索实战

2026-09-07 22:37:04作者:董斯意

本课面向生成式 AI 初学者,以本仓库 generative-ai-for-beginners 第 8 课(源码对应目录 08-building-search-applications)为核心,讲解如何用**文本 Embeddings(向量)**替代关键词匹配,构建真正理解用户意图的语义搜索应用。文中会带你理解语义搜索与关键词搜索的本质差异、学会把视频转录稿切分并向量化、基于余弦相似度实现检索,最终基于 OpenAI Embedding 索引打造一个“输入问题 → 返回相关视频 + 精确到秒的定位链接”的实战应用。

本课配套索引 embedding_index_3m.json 覆盖了 Microsoft AI Show YouTube 频道截至 2023 年 10 月的全部视频转录稿,属于开源仓库提供的真实数据资产,因此你无需联网下载转录稿,也能完整跑通全文所述的搜索链路。读完你将掌握三类能力:区分语义检索与关键词检索、解释文本 Embeddings 的含义、独立构建基于 Embeddings 的数据检索程序。

语义查询“can you use rstudio with azure ml?”返回精确到时间戳的视频链接

为什么要构建搜索应用

大语言模型(LLM)的价值远不止聊天机器人和文本生成。在本课的叙事背景下,一家面向发展中国家学生提供免费 AI 教育的公益机构,拥有大量 YouTube 教学视频。学生希望通过输入一个自然语言问题(例如 “What are Jupyter Notebooks?” 或 “What is Azure ML?”)找到相关视频——更进一步,最好能直接给出答案出现在视频中的精确位置链接

构建这样一个搜索应用,是掌握 Embeddings 检索范式的最佳练兵场:它不要求你训练模型,只要求你懂得如何把文本“翻译”成机器可计算的数值向量,再做相似度比对。这也是后续构建 RAG 应用的基础能力。

语义搜索 vs 关键词搜索

语义搜索(semantic search)利用查询词中词语的语义/含义来返回相关结果。文档给出的例子非常直观:假如你想买车,搜索 “my dream car”,语义搜索会理解你并不是在“梦见”一辆车,而是想购买一辆“理想之车”,从而返回真正相关的购车内容;而**关键词搜索(keyword search)**只会字面匹配含有 “dream” 和 “car” 的文本,往往返回不相关内容。

这正是本应用选择 Embeddings 的原因:学生的问题和视频转录稿几乎不可能字面一致,只有理解语义,才能跨表达方式完成匹配。

什么是文本 Embeddings

文本 Embeddings 是自然语言处理中的一种文本数值化表示技术,它把文本编码为机器易于理解、且携带语义信息的数字向量。可以用多种模型生成 Embeddings,本课聚焦 OpenAI 的 Embedding 模型(text-embedding-ada-002)。

以 AI Show 某期节目转录稿中的一句话为例:

Today we are going to learn about Azure Machine Learning.

把该文本送入 OpenAI Embedding API 后,会返回一个由 1536 个数字组成的向量,向量中每个数字表征文本的某个侧面。以下为该向量的前 10 个数字:

[-0.006655829958617687, 0.0026128944009542465, 0.008792596869170666,
 -0.02446001023054123, -0.008540431968867779, 0.022071078419685364,
 -0.010703742504119873, 0.003311325330287218, -0.011632772162556648,
 -0.02187200076878071, ...]

语义上相近的句子,其向量在多维空间中夹角更小、距离更近——这正是后面用余弦相似度做检索的物理基础。

索引是怎么构建的:转录稿 → 分片 → 摘要 → 向量

本课使用的 Embedding 索引 embedding_index_3m.json(位于 08-building-search-applications/embedding_index_3m.jsonscripts 子目录也有一份副本)由一组 Python 脚本流水线生成。脚本与完整使用说明在 scripts/README.md完成本课任务无需自行运行这些脚本(索引已随仓库提供),但理解流水线能让你透彻掌握向量索引的数据结构。整条流水线分为五步:

  1. 下载转录稿:通过 YouTube API 下载 AI Show 播放列表中每个视频的字幕转录。对应实现 transcript_download.py,依赖 GOOGLE_DEVELOPER_API_KEY 环境变量与 youtube-transcript-api 库。
  2. 提取演讲者姓名:用 OpenAI Function Calling,从前约 3 分钟转录中提取演讲者名单,写入索引的 speaker 字段。对应实现 transcript_enrich_speaker.py,内部通过 get_speaker_name 函数定义约束模型输出结构化结果。
  3. 按 3 分钟切分文本段:转录文本被切分为约 3 分钟一段的文本片段,相邻片段间保留约 20 个词的重叠区域,既保证 Embedding 不因边界截断而语义残缺,又为检索提供更好的上下文衔接。通用切分逻辑在 transcript_enrich_bucket.py:该脚本默认 SEGMENT_LENGTH_MINUTESPERCENTAGE_OVERLAP 等参数可调,并预留 token 预算给后续摘要请求;切分完成后,每个片段还带有 start(形如 00:00:00 的时间戳)与 seconds(绝对秒数)字段。
  4. 生成 60 词摘要:把每段文本送入 OpenAI Chat API,压缩为约 60 词的摘要,存入 summary 字段。对应实现 transcript_enrich_summaries.py
  5. 向量化:把每段文本送入 OpenAI Embedding API,得到 1536 维向量,连同片段元数据写入 embedding_index_3m.json。对应实现 transcript_enrich_embeddings.py:内部用 tiktoken.get_encoding("cl100k_base") 统计 token,超过上限(约 8191 token,见脚本中 len(tokenizer.encode(text)) > 8191 的判断)的片段会被跳过,并借助 tenacity 指数退避策略应对限流。

因此,索引中每条记录的核心结构是(以仓库内实际 JSON 样例为准):

{
  "speaker": "Seth Juarez, Josh Lovejoy, Sarah Bird",
  "title": "You're Not Solving the Problem You Think You're Solving",
  "videoId": "-tJQm4mSh1s",
  "start": "00:00:00",
  "seconds": 0,
  "summary": "Join Seth Juarez as he discusses ...",
  "ada_v2": [0.0043573323637247086, -0.02840915322303772, "...1536 个浮点数..."]
}

字段含义分别是:videoId(用于拼 YouTube 链接)、start/seconds(定位到视频中的精确时刻)、summary(60 词摘要,用于结果展示)、ada_v2(文本段的 1536 维 Embedding 向量)、speaker/title(展示用元数据)。

从 JSON 索引到向量数据库

为降低教学门槛,索引以 JSON 文件存储、用 Pandas DataFrame 载入内存。生产环境中应改用真正的向量数据库承载,例如 Azure Cognitive Search、Redis、Pinecone、Weaviate 等。这类系统专职解决大规模向量的近邻检索、持久化与高并发问题,本课的 JSON+Pandas 方案则是理解其核心检索逻辑的最小可运行形态。

理解余弦相似度

有了文本向量,下一步就是“如何找与查询最相似的向量”。这里使用的度量是余弦相似度(cosine similarity),也常被称为近邻检索(nearest neighbor search)。检索流程分四步:

  1. 用 OpenAI Embedding API 把用户查询文本向量化
  2. 逐条计算查询向量与索引中每个文本段向量的余弦相似度
  3. 按相似度从高到低排序;
  4. 相似度最高的文本段即与查询最相关。

从数学上看,余弦相似度衡量的是多维空间中两个向量的夹角余弦值。它的好处在于:即使两个文档因长度不同而在欧氏距离上相距很远,只要方向(语义)接近,夹角依然很小、余弦相似度依然很高。向量点积除以模长即为余弦相似度,即 cos(a,b) = a·b / (|a|·|b|)

前置准备:创建 Azure OpenAI 服务并部署 Embedding 模型

应用依赖 Azure OpenAI 服务,需要 Azure 订阅。以下是任务要求的资源创建过程,均在 **Azure Cloud Shell(选择 Bash 环境)**中完成:

1. 创建资源组(文档按 East US 区域的 semantic-video-search 命名;若修改地域,请对照模型可用性表确认支持):

az group create --name semantic-video-search --location eastus

2. 创建 Azure OpenAI 服务资源

az cognitiveservices account create --name semantic-video-openai --resource-group semantic-video-search \
    --location eastus --kind OpenAI --sku s0

3. 获取 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

4. 部署 Embedding 模型 text-embedding-ada-002(版本 2)

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 同时要求部署 chat 模型 gpt-4o-mini(供摘要/函数调用流水线使用);本课搜索 notebook 只需 Embedding 模型。实际运行时需通过环境变量注入密钥、Endpoint 与部署名(notebook 读取 AZURE_OPENAI_API_KEYAZURE_OPENAI_ENDPOINTAZURE_OPENAI_EMBEDDINGS_DEPLOYMENT,并在 .env 中配置,供 aoai-solution.ipynb 加载)。若你没有 Azure 环境,仓库同时也提供了基于纯 OpenAI 的练习:oai-assignment.ipynbaoai-assignment.ipynb

核心实现:从向量化查询到结果展示

搜索应用主逻辑全部集中在方案 notebook中,以下逐段拆解其核心代码,这也是本文的可直接运行的核心示例。

① 初始化客户端并加载索引。通过环境变量连接 Azure OpenAI,将 embedding_index_3m.json 载入 DataFrame,并丢弃体积较大、检索用不到的 text 原始文本列(errors="ignore" 保证兼容):

import os
import pandas as pd
import numpy as np
from openai import AzureOpenAI
from dotenv import load_dotenv

load_dotenv()

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"

def load_dataset(source: str) -> pd.core.frame.DataFrame:
    pd_vectors = pd.read_json(source)
    return pd_vectors.drop(columns=["text"], errors="ignore").fillna("")

② 定义余弦相似度与检索函数get_videos 内部按“复制索引 → 计算查询向量 → 逐行计算相似度 → 按 0.75 阈值过滤 → 排序取前 N 条”的顺序执行,返回 Top 5 结果:

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))

def get_videos(query, dataset, rows):
    video_vectors = dataset.copy()
    query_embeddings = client.embeddings.create(input=query, model=model).data[0].embedding

    video_vectors["similarity"] = video_vectors["ada_v2"].apply(
        lambda x: cosine_similarity(np.array(query_embeddings), np.array(x))
    )
    mask = video_vectors["similarity"] >= SIMILARITIES_RESULTS_THRESHOLD
    video_vectors = video_vectors[mask].copy()
    video_vectors = video_vectors.sort_values(by="similarity", ascending=False).head(rows)
    return video_vectors.head(rows)

代码细节值得注意:cosine_similarity 先用 np.pad 对齐两个向量长度再计算 a·b/(|a||b|),是标准余弦相似度的向量化实现;0.75 的相似度阈值用于剔除明显不相关片段,属于可按数据集质量调节的超参数。

③ 展示结果并生成带时间戳的 YouTube 链接。这是应用价值的体现——把命中片段的 videoIdseconds 拼接成 https://youtu.be/{videoId}?t={seconds} 链接,并同时打印标题、摘要前 15 词、相似度与演讲者:

def display_results(videos, query):
    def _gen_yt_url(video_id: str, seconds: int) -> str:
        return f"https://youtu.be/{video_id}?t={seconds}"

    print(f"\nVideos similar to '{query}':")
    for _, row in videos.iterrows():
        youtube_url = _gen_yt_url(row["videoId"], row["seconds"])
        print(f" - {row['title']}")
        print(f"   Summary: {' '.join(row['summary'].split()[:15])}...")
        print(f"   YouTube: {youtube_url}")
        print(f"   Similarity: {row['similarity']}")
        print(f"   Speakers: {row['speaker']}")

④ 主循环:加载索引,反复接收用户查询,直到输入 exit 退出:

pd_vectors = load_dataset(DATASET_NAME)

while True:
    query = input("Enter a query: ")
    if query == "exit":
        break
    videos = get_videos(query, pd_vectors, 5)
    display_results(videos, query)

运行 notebook 后会出现查询输入框:

notebook 中的查询输入框

建议尝试的查询

文档和 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?

输入 “What is Azure Machine Learning?” 这类问题后,程序会返回相似度最高的若干视频段,并给出如 https://youtu.be/{videoId}?t={秒数} 的定位链接,点击即跳转到答案出现的位置。

源码印证:仓库中的工程化参考

除 Python notebook 外,本课还提供了多种工程形态的实现,可作为将该 demo 落地的参考:

小结与下一步

本课完整覆盖了 Embeddings 语义搜索的“为什么、是什么、怎么做”:从语义 vs 关键词检索的动机,到 1536 维向量与余弦相似度的原理,再到 JSON 索引构建流水线和可运行的检索 notebook,最后落到真实应用上——学生输入问题即可获得带时间戳定位的视频答案链接。整套代码可直接在 GitHub Codespaces 中打开 aoai-solution.ipynb 按提示运行。

本课验证的是“文本向量化 + 相似度检索”这条基线范式;进入第 9 课,我们将探讨如何构建图像生成应用,把生成式 AI 从理解文本扩展到生成视觉内容。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.74 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.81 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
595
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
920
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.63 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
518
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
389