DLAI-VertexAI-笔记-全-

DLAI VertexAI 笔记(全)

001:课程介绍与概述 🚀

在本节课中,我们将要学习一门与Google Cloud合作推出的短期课程,主题是“使用Vertex AI理解和应用文本嵌入”。我们将了解文本嵌入的不同特性和应用场景,并一起探索如何计算任意长度文本序列的特征向量表示。最后,我们将看到这些句子嵌入如何成为分类、异常检测和文本聚类等众多应用的强大工具。

什么是文本嵌入? 🤔

上一节我们介绍了课程的整体目标,本节中我们来看看文本嵌入的基本概念。

如果你听说过像Word2Vec或GloVe这样的词嵌入算法,它们每次只分析单个单词。文本嵌入与此类似,但功能更强大、更通用。因为它是在句子甚至段落的语义层面上进行操作,并且对于训练集中未出现单词的句子同样有效。

核心公式/概念:一个文本序列(如句子)可以被转换为一个高维空间中的向量(即嵌入向量)。这个向量数学地表示了该文本的语义。

# 概念性代码:将句子转换为嵌入向量
sentence = "这是一个示例句子。"
embedding_vector = embed(sentence) # 例如,得到一个768维的向量

课程内容与讲师介绍 👨🏫👩🏫

以下是本课程将涵盖的核心主题和讲师信息。

本课程由两部分组成。前半部分由Andrew Ng主讲,我们将首先使用嵌入模型创建并探索一些文本嵌入。然后,我们将一起从概念上理解这些嵌入的工作原理,以及如何为任意长度的文本序列创建嵌入,并使用代码可视化嵌入的不同特性。

后半部分由Nikita Nam主讲。在你了解了嵌入的各种特性之后,你将看到如何将它们用于分类、聚类和异常检测。因为句子级嵌入开始捕捉整个句子的含义,这确实能帮助算法进行更深层次的推理,并对文本做出更好的决策。

此后,我们将学习如何使用文本生成模型以及可以调整的一些不同参数。最后,我们将把关于嵌入、语义相似性和文本生成的所有知识结合起来,构建一个小型的问答系统。

课程贡献者与第一课预告 🙏

许多人参与了本课程的创作,我们感谢来自Google Cloud团队的Eva Lu和Caltanana,以及来自DeepLearning.AI的Daniel Vigilara和Eddie S。

第一课将是关于如何开始进行文本嵌入。这听起来很棒,让我们开始吧。


本节课总结:在本节课中,我们一起学习了“使用Vertex AI理解和应用文本嵌入”课程的概述。我们了解了文本嵌入是一种强大的、句子级别的语义表示方法,对比了其与词嵌入的区别。我们还预览了课程将由Andrew Ng和Nikita Nam两位讲师共同授课,内容将涵盖从嵌入基础、可视化到实际应用(如分类、问答系统构建)的全流程。现在,我们已经为深入探索文本嵌入的世界做好了准备。

002:动手实践文本嵌入

在本节课中,我们将通过实际代码示例,学习如何使用Google Cloud的Vertex AI平台来计算和探索文本嵌入。我们将从环境设置开始,逐步计算单词和句子的嵌入,并比较不同文本之间的相似性。

环境设置与认证

上一节我们介绍了文本嵌入的基本概念,本节中我们来看看如何在代码中实际使用它。首先,我们需要设置环境并完成身份验证。

以下是设置步骤:

  1. 安装必要的库:如果在本地计算机上运行,需要安装Google Cloud AI平台库。命令为 pip install google-cloud-aiplatform
  2. 身份验证:使用特定函数加载项目凭据和项目ID。
  3. 初始化Vertex AI:指定项目ID、服务运行区域以及身份验证凭据。
# 导入Vertex AI库并初始化
import vertexai
vertexai.init(project=PROJECT_ID, location=LOCATION, credentials=CREDENTIALS)

计算文本嵌入

环境设置好后,我们就可以开始计算文本的嵌入向量了。

以下是计算单个单词嵌入的步骤:

  1. 导入文本嵌入模型:从Vertex AI库中导入 TextEmbeddingModel
  2. 加载特定模型:指定要使用的模型,例如 textembedding-gecko@001
  3. 调用模型获取嵌入:对目标文本字符串调用模型的 get_embeddings 方法。
from vertexai.language_models import TextEmbeddingModel

# 加载嵌入模型
model = TextEmbeddingModel.from_pretrained(“textembedding-gecko@001“)

# 计算单词“life”的嵌入
embedding = model.get_embeddings([“life“])
vector = embedding[0].values
print(f“向量维度: {len(vector)}“)
print(f“前10个元素: {vector[:10]}“)

执行上述代码会得到一个768维的向量,它代表了单词“life”的数值化特征。

比较句子相似性

嵌入的一个核心应用是衡量不同文本片段之间的语义相似性。我们通过计算嵌入向量之间的余弦相似度来实现。

让我们计算三个句子的嵌入并比较它们的相似性:

  1. 计算多个句子的嵌入:将多个句子作为列表传递给嵌入模型。
  2. 提取向量值:从返回的嵌入对象中提取数值向量。
  3. 计算余弦相似度:使用 sklearn.metrics.pairwise.cosine_similarity 函数计算向量两两之间的相似度。
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np

sentences = [
    “What is the meaning of life? Is it 42 or is it something else?“,
    “How does one spend their time well on earth?“,
    “Would you like a salad?“
]

# 获取所有句子的嵌入向量
embeddings = model.get_embeddings(sentences)
vectors = [e.values for e in embeddings]

# 计算并打印相似度矩阵
similarity_matrix = cosine_similarity(vectors)
print(similarity_matrix)

运行结果会显示,语义相近的句子(如关于生命意义的两个问题)其相似度得分会高于语义无关的句子(如关于生命意义和沙拉的问题)。这证明了嵌入模型能够理解文本的深层含义,而不仅仅是表面词汇。

词嵌入与句嵌入的对比

为了理解句级嵌入的强大之处,我们将其与一种简单的词嵌入平均方法进行对比。

考虑以下两个句子:

  • A: “The kids play in the park.“
  • B: “The play was for kids in the park.“

如果去除“停用词”(如 the, in, for, was),两个句子都剩下相同的三个词:[“kids“, “play“, “park“]

以下是两种方法的对比:

  1. 词嵌入平均法

    • 分别计算每个单词的嵌入。
    • 将三个单词的嵌入向量求平均,作为整个句子的表示。
    • 对于上述两个句子,由于词汇完全相同,得到的平均向量也会相同。
    # 假设已获得单词列表的嵌入数组 embedding_array1 和 embedding_array2
    sentence_embedding_avg1 = np.mean(embedding_array1, axis=0)
    sentence_embedding_avg2 = np.mean(embedding_array2, axis=0)
    # sentence_embedding_avg1 将等于 sentence_embedding_avg2
    
  2. 句嵌入模型法

    • 直接将整个原始句子输入给 TextEmbeddingModel
    • 模型会考虑词序、语法结构和上下文,为每个句子生成一个独特的嵌入向量。
    • 对于上述两个句子,尽管词汇相似,但生成的句嵌入向量会截然不同,准确反映其语义差异(“孩子们在公园玩耍” vs “戏剧是为公园里的孩子们上演的”)。
    embedding_A = model.get_embeddings([“The kids play in the park.“])
    embedding_B = model.get_embeddings([“The play was for kids in the park.“])
    # embedding_A[0].values 将不等于 embedding_B[0].values
    

这个对比清晰地展示了现代句嵌入模型能够捕捉更丰富、更精确的语义信息。

核心语法总结

本节课中我们一起学习了使用Vertex AI进行文本嵌入的核心操作。

以下是关键语法回顾:

  • 初始化Vertex AI

    vertexai.init(project=PROJECT_ID, location=LOCATION, credentials=CREDENTIALS)
    
  • 加载模型与获取嵌入

    from vertexai.language_models import TextEmbeddingModel
    model = TextEmbeddingModel.from_pretrained(“textembedding-gecko@001“)
    embeddings = model.get_embeddings([“Your text here“])
    vector = embeddings[0].values
    

建议你暂停视频,在Jupyter笔记本中尝试输入不同的文本(例如关于编程、爱好或日常生活的句子),观察生成的嵌入和相似度结果,从而更直观地感受文本嵌入的工作原理和效果。完成实验后,我们将在下一个视频中深入探讨嵌入背后的概念和工作机制。

003:理解文本嵌入的工作原理 🧠

在本节课中,我们将要学习文本嵌入的核心概念和工作原理。我们将探讨嵌入如何表示文本的语义,以及现代技术是如何计算这些嵌入的。通过本教程,你将理解嵌入的基本机制及其在多种应用中的潜力。


嵌入的语义表示

上一节我们介绍了嵌入的基本概念,本节中我们来看看嵌入是如何在空间中表示数据点的,其中位置具有语义意义。“语义”一词指的是含义。这意味着嵌入的位置捕获了一段文本的某些含义。

例如,你可能会输入句子“失踪的火烈鸟在游泳池中被发现”,并输出一个嵌入向量。如果你嵌入多个句子,例如“失踪的火烈鸟在海滩上被冲浪板发现”或“熊猫宝宝喜欢划船”,那么这些句子在嵌入空间中的位置可能会彼此更接近。

相比之下,像“早餐主题餐车深受大家喜爱”和“新咖喱餐厅很美味”这样的句子,其嵌入位置则会距离较远。与动物和水相关的句子之间的成对距离,会远大于火烈鸟句子与早餐主题句子之间的距离。


嵌入是如何计算的?🔧

在接下来的内容中,我们将更深入地探讨技术细节。请注意,本节内容是可选的,跳过不会影响后续课程的学习。但如果你有兴趣了解嵌入的构建方式,请继续阅读。

一种简单的嵌入计算方法是分别嵌入句子中的每个单词,然后对所有单个词嵌入取和或平均值。长期以来,计算嵌入的主流方法是:列出一个最常见英语单词的列表,为每个单词单独训练一组参数以获得该单词的嵌入,然后对于一个句子,取所有这些词嵌入的平均值。

然而,正如上一课所看到的,这意味着句子级别的嵌入无法理解单词的顺序。因此,现代嵌入技术采用了更复杂的方法。

我们转而使用Transformer神经网络来计算每个单词的上下文感知表示,然后对这些上下文感知嵌入取平均值。如果你不理解这个示意图,不必担心。简单来说,Transformer神经网络会处理每个单词,并计算该单词的嵌入,同时考虑句子中出现的其他单词。

举个例子,单词“play”在“孩子们在玩耍”和“一场戏剧演出”中含义不同。Transformer网络通过查看周围单词的上下文,可以区分这两种含义,从而为同一个单词“play”生成不同的向量表示。这种方法使得句子嵌入能更准确地捕捉每个单词的含义。

此外,现代嵌入技术还有一个更强大的改进:它不再使用预定义的单词列表,而是为每个令牌计算嵌入。令牌通常对应于子词。这样做的好处是,即使对于新词或拼写错误的词,算法也能正常工作。

例如,如果我拼写错误地输入“life the unverse and everything”(一部我喜爱的小说),它仍然能为这个句子计算出一个相当不错的嵌入。相比之下,如果使用传统的嵌入技术,拼写错误的“unverse”将无法映射到与“universe”接近的嵌入。

现代大语言模型将句子分解为称为令牌的子词,通过学习令牌的嵌入,你可以输入任何字符串,它仍然能生成一个有意义的嵌入。


嵌入是如何学习的?📚

用于训练Transformer神经网络参数的技术称为对比学习。不同的研究团队仍在尝试不同的嵌入计算技术。

大致流程是:首先在大量文本数据上对Transformer网络进行预训练(大语言模型就是这样训练的,使用互联网或其他来源的大量未标记文本数据)。然后,你会找到一个包含相似句子对的数据集,并调整神经网络,使更相似句子的嵌入在空间中更接近,而使不相似句子的嵌入更远离。

如何判断两个句子是否相似呢?相似句子的定义可以根据你的目标来定。例如,如果你要构建一个问答系统,你可以声明“问题”和其对应的“答案”是相似的,这将促使神经网络将答案的嵌入推近问题的嵌入。不相似的句子通常只是随机采样的句子对,因为随机挑选的两个句子在含义上很可能并不相似。

目前,研究人员仍在探索这个基本方法的不同变体,这也是为什么嵌入算法每隔几个月就会有所改进。这是一个令人兴奋的研究领域,但现有的嵌入技术已经足够好,可以立即投入使用。


嵌入的应用与多模态扩展

在本短期课程的后半部分,Nikkita将介绍如何在各种应用中使用文本嵌入,包括:

以下是文本嵌入的主要应用场景:

  • 文本分类
  • 聚类
  • 异常检测
  • 语义搜索

虽然本课程不会过多讨论产品推荐,但我们也可以想象:如果你购买了产品X、Y和Z,那么通过使用嵌入,基于产品描述找到与你喜欢的产品相似的其他产品,这对于产品推荐也非常有用。

我还想分享一个有趣的概念:多模态嵌入。我们不会在本课程中深入探讨,但它代表了嵌入技术的前沿。

多模态嵌入是指能够将文本和图片都嵌入到同一个(例如768维)空间中的算法。“多模态”指的是它可以处理文本或图片等不同形式的数据(研究人员也在研究音频)。

例如,一段文本“橘子正当季”可以被嵌入到一个点。同样的算法,也可以将一张橘子的图片嵌入到空间中一个靠近关于橘子的文本嵌入的位置。这些能将文本和图片嵌入到同一空间的多模态嵌入,是另一个令人兴奋的发展,正在逐步开启更多能够同时理解文本和图片的应用。


总结

本节课中我们一起学习了文本嵌入的工作原理。我们了解到嵌入如何在向量空间中表示文本的语义,探讨了从简单的词平均到基于Transformer的上下文感知嵌入的演进过程,并简要介绍了嵌入是如何通过对比学习进行训练的。最后,我们展望了嵌入在分类、搜索等领域的应用以及多模态嵌入的未来潜力。理解这些基本原理,将帮助我们更好地在后续课程中应用文本嵌入技术。

004:可视化文本嵌入

在本节课中,我们将学习如何可视化文本嵌入,以直观地理解嵌入模型如何将语义相似的文本映射到向量空间中相近的位置。我们将使用主成分分析(PCA)和热力图两种方法进行可视化。

上一节我们介绍了如何计算文本嵌入,本节中我们来看看如何将这些高维向量可视化,以便更好地理解它们之间的关系。

概述与准备

首先,我们需要像之前一样,在Vertex AI平台上进行身份验证并设置环境。

# 身份验证与初始化代码示意
# 此处为示意,实际代码需在对应平台环境中运行

接下来,我们将使用以下七个句子作为示例数据:

  1. Must St flamingo discover that swimming pool.
  2. See all the spots who have bought baby panda.
  3. Boat ride.
  4. Breakfast steamed food truck.
  5. New curry restaurants.
  6. Python developers are wonderful people.
  7. Typescript flips as a Java.

以下是导入必要库和设置嵌入模型的代码:

import numpy as np
# 设置嵌入模型(此处为示意)
embedding_model = setup_embedding_model()

计算嵌入向量

我们将循环处理这七个句子,为每一个计算其嵌入向量。

sentences = [sentence1, sentence2, sentence3, sentence4, sentence5, sentence6, sentence7]
embeddings = []

for sentence in sentences:
    embedding = embedding_model(sentence).values
    embeddings.append(embedding)

embeddings_array = np.array(embeddings)
print(embeddings_array.shape)  # 输出应为 (7, 768)

现在,我们得到了一个形状为 (7, 768) 的数组,表示7个句子,每个句子被编码成一个768维的向量。

使用PCA进行降维可视化

由于我们无法直接在二维屏幕上绘制768维的数据,因此需要使用主成分分析(PCA)技术将其压缩到二维。

PCA 是一种用于将高维数据降维的技术,它通过找到数据中方差最大的方向(主成分)来实现。对于本视频的目的,你只需知道它能将768维数据压缩到2维以便绘图。

以下是使用PCA的代码:

from sklearn.decomposition import PCA

pca = PCA(n_components=2)
embeddings_2d = pca.fit_transform(embeddings_array)
print(embeddings_2d.shape)  # 输出应为 (7, 2)

现在,每个句子对应一个二维坐标点,我们可以将其绘制在散点图上。

import matplotlib.pyplot as plt

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-vtx-ai/img/138d9d8caf2da03cd169121d8d42c018_13.png)

plt.figure(figsize=(8, 6))
plt.scatter(embeddings_2d[:, 0], embeddings_2d[:, 1])

# 为每个点添加标签
for i, sentence in enumerate(sentences):
    plt.annotate(f'In{i+1}', (embeddings_2d[i, 0], embeddings_2d[i, 1]))

plt.xlabel('PCA Component 1')
plt.ylabel('PCA Component 2')
plt.title('2D Visualization of Sentence Embeddings')
plt.grid(True)
plt.show()

观察生成的图表,你会发现:

  • 关于动物的句子(如“baby panda”、“flamingo”)在图中位置接近。
  • 关于食物的句子(如“food truck”、“curry restaurant”)也聚集在一起。
  • 关于编程的句子(如“Python developers”、“Typescript”)则位于另一区域。

这直观地展示了嵌入模型将语义相似的句子映射到向量空间相近位置的能力。

重要提示:在实际应用中,我们不会使用二维嵌入来计算相似度。为了可视化,PCA丢弃了大量信息。测量相似度时,应始终在原始的768维(或模型输出的原始维度)空间中进行,例如使用余弦相似度公式:similarity(A, B) = (A·B) / (||A|| * ||B||),这样结果才更准确。

使用热力图分析嵌入向量

接下来,我们看另一个例子,使用热力图来观察嵌入向量各个维度的数值。

我们使用以下四个句子:

  1. He couldn‘t desert his post at a power plant.
  2. The power plant needed him at the time.
  3. Cacti are able to withstand dry environments.
  4. Desert plants can survive droughts.

使用相同的代码计算这四个句子的嵌入向量。

# 计算四个新句子的嵌入向量
new_sentences = [sentence1, sentence2, sentence3, sentence4]
new_embeddings = compute_embeddings(new_sentences) # 假设的封装函数

以下是绘制热力图的代码:

import seaborn as sns
import matplotlib.pyplot as plt

plt.figure(figsize=(12, 3))
sns.heatmap(new_embeddings, cmap='RdBu', center=0)
plt.xlabel('Embedding Dimension (768 total)')
plt.ylabel('Sentence')
plt.title('Heatmap of Embedding Vectors')
plt.show()

在热力图中,颜色表示每个嵌入向量在不同维度上的数值大小。你可以观察到,句子1和句子2的图案模式看起来更相似,句子3和句子4的图案模式也更相似。这再次印证了嵌入模型对相似语义的编码具有一致性。

技术说明:这种基于单个维度数值的可视化(如热力图或查看第一个主成分)在数学上并不完全严谨,因为嵌入空间的坐标轴方向具有一定的任意性(例如,可能发生随机旋转)。因此,它主要是一种帮助建立直觉的非正式可视化方法。可靠的操作是计算嵌入向量之间的成对相似度(如余弦相似度)。

总结

本节课中我们一起学习了两种可视化文本嵌入的方法:

  1. PCA散点图:通过降维将高维嵌入绘制在二维平面上,直观展示不同文本在语义空间中的相对位置。
  2. 向量热力图:展示每个嵌入向量在各个维度上的数值,用于观察不同向量之间的模式相似性。

这些可视化工具能帮助我们建立对嵌入模型的直觉理解,即它将语义相似的文本映射到高维空间中彼此靠近的点。请记住,在实际构建应用(如测量相似度、进行聚类或检索)时,应使用原始高维嵌入向量进行计算,以获得最准确的结果。

在下一节课中,我们将开始深入探讨如何利用这些嵌入向量来构建各种实际应用。

005:文本嵌入的实际应用

在本节课中,我们将学习如何将文本嵌入应用于实际场景,包括数据聚类、异常检测和作为监督学习的特征。我们将使用Stack Overflow数据集作为示例,通过Vertex AI的文本嵌入模型来处理和分析数据。

概述

上一节我们介绍了文本嵌入的基本概念和直觉。本节中,我们将探索文本嵌入在几个不同机器学习任务中的具体应用。我们将使用Google Cloud的Vertex AI平台和BigQuery服务来获取和处理数据。

设置环境与获取数据

首先,我们需要设置环境并获取数据。我们将使用BigQuery中的Stack Overflow问答数据集。

以下是设置步骤:

  1. 配置凭据并进行身份验证。
  2. 指定运行服务的区域。
  3. 导入Vertex AI Python SDK并初始化。

完成设置后,我们开始加载数据。我们将从BigQuery中提取特定编程语言标签下的Stack Overflow帖子。

# 导入BigQuery客户端
from google.cloud import bigquery

# 定义执行SQL查询的函数
def run_query(query):
    client = bigquery.Client()
    query_job = client.query(query)
    results = query_job.result()
    return results.to_dataframe()

由于数据集很大,我们只查询几种编程语言标签下的前500个帖子,以避免内存问题。

# 定义要查询的语言标签列表
languages = [‘python‘, ‘javascript‘, ‘java‘, ‘html‘]

# 创建空DataFrame,用于存储所有数据
all_data = pd.DataFrame()

# 循环查询每种语言的数据
for lang in languages:
    query = f“““
        SELECT title, body, tags
        FROM `bigquery-public-data.stackoverflow.posts_questions`
        WHERE tags LIKE ‘%{lang}%‘
        LIMIT 500
    “““
    df = run_query(query)
    all_data = pd.concat([all_data, df], ignore_index=True)

如果执行BigQuery代码时遇到错误,或者不想使用BigQuery,可以直接从CSV文件加载数据。

现在,我们有了一个包含2000行(每种语言500个帖子)和3列的数据框。这三列分别是:

  • input_text:帖子标题与问题的拼接。
  • output_text:社区对该帖子的采纳答案。
  • category:编程语言标签。

生成文本嵌入

接下来,我们需要为这些文本数据生成嵌入向量。我们将使用Vertex AI的textembedding-gecko模型。

由于数据量较大,并且API对每次请求的文本实例数量有限制(最多5个),我们需要编写辅助函数来分批处理数据。

以下是辅助函数:

# 定义分批函数,每批5个实例
def generate_batches(data, batch_size=5):
    for i in range(0, len(data), batch_size):
        yield data[i:i + batch_size]

# 定义嵌入生成函数(包装了get_embeddings)
def encode_text_to_embeddings(text_instances):
    embeddings = []
    for instance in text_instances:
        emb = get_embeddings(instance)
        embeddings.append(emb)
    return embeddings

此外,Google Cloud服务有速率限制。因此,我们使用一个更完善的批处理函数encode_text_to_embeddings_batched,它同时处理分批和速率限制。

# 处理分批和速率限制的嵌入生成函数
def encode_text_to_embeddings_batched(data):
    # 分批逻辑
    batches = generate_batches(data)
    all_embeddings = []
    for batch in batches:
        # 调用API并处理速率限制
        embeddings = encode_text_to_embeddings(batch)
        all_embeddings.extend(embeddings)
        time.sleep(0.1)  # 简单的速率控制示例
    return np.array(all_embeddings)

考虑到在线课堂的速率限制,我们不会现场为所有2000行数据生成嵌入,而是加载预先计算好的嵌入向量。

# 加载预先计算好的嵌入向量
import pickle
with open(‘stackoverflow_embeddings.pkl‘, ‘rb‘) as f:
    embeddings_array = pickle.load(f)

print(embeddings_array.shape)  # 输出应为 (2000, 768)

现在,我们有了一个形状为(2000, 768)的嵌入数组,代表2000个帖子,每个帖子由768维的向量表示。

应用一:数据聚类

第一个应用是使用K-means算法对帖子进行聚类。我们将使用Python和HTML标签的帖子进行演示。

以下是聚类步骤:

  1. 导入必要的库:KMeansPCA
  2. 为了便于可视化,我们只使用前1000行数据(Python和HTML帖子)。
  3. 定义聚类数量为2。
  4. 创建并拟合K-means模型。
  5. 使用PCA将768维数据降为2维以便可视化。
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt

# 使用前1000行数据进行聚类
clustering_data = embeddings_array[:1000]

# 定义K-means模型
kmeans = KMeans(n_clusters=2, random_state=42)
kmeans.fit(clustering_data)
labels = kmeans.labels_

# 使用PCA降维
pca = PCA(n_components=2)
reduced_data = pca.fit_transform(clustering_data)

# 可视化聚类结果
plt.scatter(reduced_data[:, 0], reduced_data[:, 1], c=labels, cmap=‘viridis‘)
plt.title(‘Stack Overflow Posts Clustering‘)
plt.show()

可视化结果会显示两个明显的簇。左侧的红色点代表HTML帖子,右侧的蓝色点代表Python帖子。聚类模型仅基于嵌入向量就能很好地将数据分为两个不同的类别。

应用二:异常检测

嵌入向量不仅能找到相似的数据点,还能帮助识别异常或分布外的数据点。我们将使用Isolation Forest算法进行异常检测。

以下是异常检测步骤:

  1. 在数据集中添加一个与编程无关的问题(例如关于烘焙的问题)。
  2. 为这个新问题生成嵌入向量。
  3. 将新嵌入向量添加到原始嵌入数组中。
  4. 使用Isolation Forest模型拟合数据并预测异常。
from sklearn.ensemble import IsolationForest

# 添加一个异常问题(关于烘焙)
anomaly_text = “I‘m making cookies, but I don‘t remember the correct ingredient proportions. I‘ve been unable to find anything on the web.”
anomaly_embedding = get_embeddings(anomaly_text)

# 将异常嵌入添加到数组中
all_embeddings = np.vstack([embeddings_array, anomaly_embedding.reshape(1, -1)])

# 创建并拟合Isolation Forest模型
iso_forest = IsolationForest(contamination=0.01, random_state=42)
predictions = iso_forest.fit_predict(all_embeddings)

# 找出被预测为异常的数据点(标签为-1)
outliers = np.where(predictions == -1)[0]
print(“Detected outlier indices:“, outliers)

模型会成功地将烘焙问题识别为异常。同时,它可能还会识别出一些关于R语言的编程问题作为异常,这可能是因为这些帖子被错误标记或内容特殊。

完成异常检测后,我们从数据中移除烘焙问题,以便进行下一个应用。

应用三:监督学习分类

嵌入向量可以作为监督学习模型的输入特征。我们将尝试使用嵌入向量来预测帖子的类别(编程语言)。

以下是分类步骤:

  1. 导入必要的库:RandomForestClassifier, accuracy_score, train_test_split
  2. 定义特征X(嵌入向量)和标签Y(帖子类别)。
  3. 将数据随机打乱并分割为训练集和测试集(80%-20%)。
  4. 创建并训练随机森林分类器。
  5. 在测试集上进行预测并计算准确率。
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-vtx-ai/img/1b2a72fa69ebbfce1d8a3163d8d8850a_4.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-vtx-ai/img/1b2a72fa69ebbfce1d8a3163d8d8850a_5.png)

# 定义特征和标签
X = embeddings_array
Y = df[‘category‘].values  # 假设df是包含类别的DataFrame

# 分割数据集
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.2, random_state=42)

# 创建并训练随机森林分类器
clf = RandomForestClassifier(n_estimators=200, random_state=42)
clf.fit(X_train, y_train)

# 预测并评估
y_pred = clf.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(f“Classification Accuracy: {accuracy:.2f}“)

经过非常简单的预处理,模型可以达到约0.70的准确率,这是一个不错的结果。

总结

本节课中,我们一起学习了文本嵌入在三个实际机器学习任务中的应用:

  1. 数据聚类:我们使用K-means算法和PCA可视化,成功将Stack Overflow帖子按编程语言分成了不同的簇。
  2. 异常检测:我们使用Isolation Forest算法,成功识别出与编程无关的异常问题。
  3. 监督学习分类:我们将嵌入向量作为特征输入随机森林分类器,有效预测了帖子的类别。

这些应用展示了文本嵌入作为通用、强大的文本表示方法,能够服务于多种下游任务。在下一个教程中,我们将暂时离开嵌入的话题,简要探讨文本生成。

006:结合文本生成模型构建问答系统

概述

在本节课中,我们将学习如何利用大型语言模型的文本生成能力,并结合之前学习的嵌入技术,构建一个更强大的问答系统。我们将从基础开始,介绍如何使用Vertex AI的文本生成模型,并探讨如何通过调整参数来控制模型的输出。


导入与设置

首先,我们需要导入必要的凭证并进行身份验证,以便使用Vertex AI服务。这包括设置区域、导入Vertex AI库并初始化SDK。

完成这些必要的设置后,我们就可以开始了。

# 导入Vertex AI并初始化
import vertexai
from vertexai.language_models import TextGenerationModel

# 设置项目ID和区域
PROJECT_ID = "your-project-id"
REGION = "us-central1"

vertexai.init(project=PROJECT_ID, location=REGION)

加载文本生成模型

上一节我们介绍了嵌入模型,本节中我们来看看文本生成模型。我们将从Vertex AI加载一个名为text-bison的模型。这个模型针对多种自然语言任务进行了微调,例如情感分析、分类、摘要和提取。

需要注意的是,text-bison模型适用于可以通过单次API响应完成的任务,而不是持续的对话。如果需要来回交互,可以使用另一个名为chat-bison的模型。

# 加载文本生成模型
generation_model = TextGenerationModel.from_pretrained("text-bison@001")

理解提示词与生成文本

在大型语言模型的语境中,文本生成是指模型接收一段输入文本(称为提示词),并生成一段可能接续的文本作为输出。

让我们从一个开放式的生成任务开始。我们将定义一个提示词,然后查看模型的响应。

# 定义一个开放式提示词
prompt = "I'm a high school student. Recommend me a programming activity to improve my skills."

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-vtx-ai/img/b6bb6f7e963b6187aa8856db0edfac9e_9.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-vtx-ai/img/b6bb6f7e963b6187aa8856db0edfac9e_10.png)

# 调用模型生成文本
response = generation_model.predict(prompt=prompt)
print(response.text)

模型可能会建议:“编写一个解决你感兴趣问题的程序”,或者“参加编程课程”等。这是一个相当开放且多变的回答。


通过提示词工程控制输出

我们可以通过编写策略性的输入文本来引导模型产生不同的行为。例如,如果我们想要一个限制性更强的答案,可以将开放式生成任务转化为分类任务,以减少输出的可变性。

以下是重新表述的提示词:

# 定义一个更具限制性的提示词
prompt = """
I'm a high school student. Which of these activities do you suggest and why?
A. Learn Python
B. Learn JavaScript
C. Learn Fortran
"""

response = generation_model.predict(prompt=prompt)
print(response.text)

这次,模型可能会明确建议学习Python,并给出理由。这种为特定用例寻找最佳提示词的艺术和科学,被称为提示词工程


使用模型提取与转换信息

大型语言模型一个有趣的应用是信息提取,即将一种格式的数据重新格式化为另一种格式。

假设我们有一段关于一部虚构电影的冗长描述,其中包含角色、他们的职业以及扮演他们的演员。我们可以指示模型提取所有这些字段。

# 定义包含信息的文本和提取指令
synopsis = """
[此处是一段虚构的电影剧情简介,描述了角色、职业和演员。]
"""
prompt = f"""
{synopsis}
Extract all characters, their jobs, and the actors who played them from the above message.
"""

response = generation_model.predict(prompt=prompt)
print(response.text)

模型将提取出结构化的信息。我们甚至可以要求它以表格形式输出:

prompt = f"""
{synopsis}
Extract this information from the above message as a table.
"""
response = generation_model.predict(prompt=prompt)
print(response.text)

这样,我们就得到了一个格式良好的Markdown表格。


控制模型输出的关键参数

除了调整提示词的措辞和顺序,我们还可以设置一些超参数来影响模型的输出结果。

理解解码策略

从本质上讲,模型接收输入文本后,会输出一个可能的下一个令牌的概率数组。令牌是大型语言模型处理文本的基本单位,可能是单词、子词或其他文本片段。

我们需要一个策略从这个数组中选择下一个令牌,这被称为解码策略

  • 贪婪解码:每次都选择概率最高的令牌。这可能导致答案单调或重复。
  • 随机采样:根据概率分布随机选择令牌。这可能导致不寻常的响应。

温度参数

温度参数用于控制这种随机性程度。

  • 低温度值(接近0):模型输出更确定、更可预测。适用于分类、提取等任务。
    • temperature = 0 时,模型总是选择最可能的令牌,输出是完全确定的。
  • 高温度值(接近1):模型输出更具创造性、更多样化。适用于头脑风暴、创意写作等开放式任务。

在数学上,温度通过修改Softmax函数的输入来工作:
softmax_with_temperature(z_i) = exp(z_i / T) / sum(exp(z_j / T))
其中 z_i 是逻辑值,T 是温度。

# 使用温度参数
prompt = "Complete the sentence: As I prepared the picture frame, I reached into my toolkit to fetch my"

# 温度 = 0 (确定性)
response_deterministic = generation_model.predict(prompt=prompt, temperature=0)
print(f"Temperature 0: {response_deterministic.text}")

# 温度 = 1 (更具创造性)
response_creative = generation_model.predict(prompt=prompt, temperature=1)
print(f"Temperature 1: {response_creative.text}")

Top-K 和 Top-P 采样

以下是两种更高级的采样方法:

  • Top-K采样:仅从概率最高的K个令牌中采样。
  • Top-P采样(核采样):从累积概率超过P的最小令牌集合中采样。这能动态适应概率分布。

参数协同工作的顺序是:先按Top-K过滤,再按Top-P过滤,最后使用温度采样选择最终令牌。

# 同时设置温度、Top-P和Top-K
prompt = "Write an advertisement for jackets that involves blue elephants and avocados."

response = generation_model.predict(
    prompt=prompt,
    temperature=0.9,  # 高创造性
    top_p=0.8,        # 使用累积概率80%内的令牌
    top_k=20          # 仅考虑前20个最可能的令牌
)
print(response.text)

核心语法总结

本节课我们一起学习了使用Vertex AI文本生成模型的核心步骤:

  1. 导入并加载模型
    from vertexai.language_models import TextGenerationModel
    model = TextGenerationModel.from_pretrained("text-bison@001")
    
  2. 定义提示词:这是输入给模型的文本。
  3. 调用预测并设置参数
    response = model.predict(
        prompt="你的提示词",
        temperature=0.2,
        top_p=0.95,
        top_k=40
    )
    

总结与下一步

在本节课中,我们一起学习了:

  • 如何加载和使用Vertex AI的文本生成模型(text-bison)。
  • 如何通过提示词工程引导模型行为。
  • 如何利用模型进行信息提取和格式转换
  • 控制模型输出随机性的三个关键参数:温度Top-KTop-P,以及它们的工作原理和协同方式。

现在,你已经掌握了文本生成的基础知识。我鼓励你打开笔记本,尝试不同的温度、Top-P和Top-K值,并试验各种提示词,看看能让这些大型语言模型展现出哪些有趣的行为。

当你准备好后,我们将在下一课中,将你学到的文本生成知识与嵌入技术结合起来,构建一个完整的问答系统。

007:构建基于嵌入的问答系统

在本节课中,我们将综合运用之前学到的关于嵌入、语义相似度和文本生成的知识,构建一个完整的问答系统。该系统将接收用户关于Python编程的问题,并基于Stack Overflow帖子的数据库返回答案。

概述

大型语言模型(LLM)虽然强大,但其知识通常局限于训练数据。为了回答特定领域(如公司内部文档)或最新信息的问题,我们需要将LLM连接到外部知识库。直接将这些文档全部放入提示词中是不现实的,因为会很快超出上下文长度限制。此外,将回答与具体文档来源关联,有助于验证答案的准确性,避免模型“幻觉”。本节课将展示如何利用嵌入和提示工程,无需微调模型,即可构建这样的系统。

数据准备与嵌入

上一节我们介绍了文本嵌入的基本概念,本节中我们来看看如何为我们的问答系统准备数据。

首先,我们进行常规的身份验证、区域设置,并初始化Vertex AI Python SDK。

完成设置后,我们开始准备数据。与之前的实验类似,我们将使用BigQuery中的Stack Overflow数据集。但这次我们不直接运行BigQuery代码,而是使用一个预先准备好的CSV文件。我们使用Pandas导入这个数据。

import pandas as pd
so_database = pd.read_csv('stack_overflow_data.csv')
print(so_database.shape)
print(so_database.head())

这个数据框包含2000行,代表2000个不同的Stack Overflow帖子。它有三列:

  • input_text:帖子的问题和标题的拼接。
  • output_text:社区对该问题的采纳答案。
  • category:帖子标记的编程语言。

现在,我们可以为这些数据生成嵌入。我们导入文本嵌入模型并加载text-embedding-gecko模型。

处理大量数据时,需要注意批处理和速率限制。encode_text_to_embeddings_batched这个工具函数会为我们处理这些细节。以下是生成嵌入的代码示例:

from vertexai.language_models import TextEmbeddingModel
model = TextEmbeddingModel.from_pretrained("textembedding-gecko")
# 实际项目中运行此代码来生成嵌入
# embeddings = model.encode_text_to_embeddings_batched(texts=so_database['input_text'].tolist())

为了节省API调用,我们直接加载预先计算好的嵌入数据,这些数据保存在一个pickle文件中。

import pickle
with open('precomputed_embeddings.pkl', 'rb') as f:
    embeddings_array = pickle.load(f)
print(embeddings_array)

最后,我们将这些嵌入向量作为新的一列添加到数据框中,便于后续构建问答系统。

so_database['embeddings'] = list(embeddings_array)

核心原理:相似度搜索与最近邻

我们为何要嵌入所有数据?我们的Stack Overflow数据包含问题和答案。系统目标是:接收用户查询,在数据库的所有Stack Overflow问题中寻找语义相似的问题。如果找到,我们就获得了对应答案,从而可以回答用户。

之前我们讨论过,嵌入可以帮助我们找到相似的数据点。我们可以使用距离度量来量化两个嵌入向量的相似度。以下是几种常见的度量方式:

  • 欧几里得距离(L2距离):计算两个向量端点之间的直线距离。公式为:distance = sqrt(∑(Ai - Bi)^2)
  • 余弦相似度:计算两个向量之间夹角的余弦值。其值在-1到1之间,值越接近1表示越相似。公式为:similarity = (A·B) / (||A|| * ||B||)
  • 点积:计算两个向量的点积,等于余弦相似度乘以两个向量的模长。公式为:dot_product = A·B = ∑(Ai * Bi)。点积同时考虑了向量的角度和大小。

在我们的例子中,我们将使用余弦相似度。当向量被归一化为单位长度(模长为1)时,余弦相似度和点积是等价的。

接下来,我们将用户查询进行嵌入,然后计算该查询嵌入与数据库中每一个Stack Overflow问题嵌入之间的余弦相似度。完成计算后,我们可以找出哪些问题嵌入最为相似,这些被称为最近邻

实现问答流程

让我们在代码中实现上述流程。首先导入必要的库。

import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.metrics import euclidean_distances

假设用户提问:“how to concatenate data frames in pandas”。我们首先嵌入这个查询。

user_query = "how to concatenate data frames in pandas"
query_embedding = model.get_embeddings([user_query])[0].values

接着,计算查询嵌入与数据库中所有嵌入的余弦相似度。

# 将数据库中的嵌入列表转换为二维数组
db_embeddings_list = list(so_database['embeddings'])
similarities = cosine_similarity([query_embedding], db_embeddings_list)
print(similarities.shape) # 输出 (1, 2000)

这个数组的形状是1x2000,意味着我们计算了查询与2000个Stack Overflow嵌入中每一个的相似度得分。我们需要从中找出相似度最高的索引。

most_similar_idx = np.argmax(similarities)

现在,我们可以查看这个索引对应的问题和答案。

similar_question = so_database.iloc[most_similar_idx]['input_text']
similar_answer = so_database.iloc[most_similar_idx]['output_text']
print(f"最相似的问题:{similar_question}")
print(f"对应的答案:{similar_answer}")

如果直接将这个原始答案返回给用户,体验可能不佳(格式混乱,缺乏上下文)。因此,我们将使用一个大语言模型,以上述信息为相关上下文,生成一个更友好、更对话式的回答。

使用LLM生成友好回答

为了实现这一点,我们导入并加载文本生成模型(例如text-bison)。

from vertexai.language_models import TextGenerationModel
generation_model = TextGenerationModel.from_pretrained("text-bison")

接下来,我们构建一个提示词(Prompt)。首先创建上下文信息。

context = f"""
问题:{similar_question}
答案:{similar_answer}
"""

然后,将上下文和用户查询整合到提示词中。

prompt = f"""
以下是上下文信息:
{context}
请根据上下文中的相关信息,回答以下查询。如果上下文中的信息不相关,请回复:“我在文档数据库中找不到与您查询匹配的良好答案。”
查询:{user_query}
答案:
"""

最后,调用模型生成回答。

response = generation_model.predict(
    prompt=prompt,
    temperature=0.2,
    max_output_tokens=256
)
print(response.text)

这样,我们就得到了一个基于Stack Overflow答案、但表述更流畅、更用户友好的回答。

处理无关查询与优化搜索

当前的流程返回嵌入数据库中最相似的问题。但如果用户查询与数据库信息完全无关呢?除了生成对话式回答,我们还可以利用文本生成模型来处理这种情况。

让我们尝试一个不同的查询:“how to make the perfect lasagna”。这显然不在Python编程的Stack Overflow数据库中。

我们重复之前的步骤:嵌入查询、计算余弦相似度、找到最相似的文档。然后,使用相同的提示词模板,将最相似的(尽管不相关的)Stack Overflow问答作为上下文输入。

由于我们在提示词中明确指示模型在信息不相关时返回特定语句,模型应该会输出“我在文档数据库中找不到与您查询匹配的良好答案。”这演示了系统如何处理超出知识范围的查询。

在结束之前,需要指出:我们计算了查询嵌入与数据库中每一个嵌入的相似度,这种穷举搜索在数据库有数亿甚至数十亿向量时是不可行的。在生产环境中,通常会使用执行近似匹配的算法。

如果你使用向量数据库,这个功能可能已经内置。如果你想尝试这种近似最近邻算法,一个可用的开源库是ScaNN(可扩展最近邻)。它能大规模高效地进行向量相似性搜索。

以下是使用ScaNN的简要示例:

# 安装: pip install scann
import scann
import time

# 1. 构建索引
db_embeddings_array = np.array(db_embeddings_list)
searcher = scann.scann_ops_pybind.builder(db_embeddings_array, 10, "dot_product").tree(
    num_leaves=1000, num_leaves_to_search=100).score_ah(2).reorder(100).build()

# 2. 近似搜索
query = "how to concatenate data frames in pandas"
start_time = time.time()
query_embedding = model.get_embeddings([query])[0].values
neighbors, distances = searcher.search(query_embedding, final_num_neighbors=1)
latency_approx = time.time() - start_time
print(f"近似搜索找到的最近邻ID:{neighbors[0]}, 相似度:{distances[0]}")
print(f"近似搜索耗时:{latency_approx*1000:.2f} 毫秒")

# 3. 对比穷举搜索
start_time = time.time()
similarities = cosine_similarity([query_embedding], db_embeddings_list)
most_similar_idx = np.argmax(similarities)
latency_exhaustive = time.time() - start_time
print(f"穷举搜索找到的最近邻ID:{most_similar_idx}")
print(f"穷举搜索耗时:{latency_exhaustive*1000:.2f} 毫秒")

在小数据集上,速度差异可能不明显,但在海量数据集中,近似搜索的速度优势将非常显著。

总结

本节课中我们一起学习了如何构建一个基于嵌入的问答系统。我们回顾一下完整的流程:

  1. 数据嵌入:将外部知识库(如Stack Overflow问答对)通过嵌入模型转化为向量,并存储。
  2. 查询处理:将用户查询同样转化为嵌入向量。
  3. 相似度匹配:计算查询向量与知识库中所有向量之间的余弦相似度,执行最近邻搜索,找到最相关的文档。
  4. 答案生成:将找到的相关文档(问题和答案)作为上下文,与原始用户查询一起构建提示词,输入到文本生成模型(LLM)中,生成一个流畅、准确且可追溯来源的最终答案。
  5. 边界处理:通过设计提示词,让LLM能够判断上下文是否相关,并妥善处理无关查询。
  6. 性能优化:对于大规模向量数据库,可以采用近似最近邻算法(如ScaNN)来加速搜索过程。

通过本教程,你不仅构建了一个小型的Stack Overflow问答系统,更重要的是掌握了将LLM与外部知识库结合的核心模式。你可以将这套方法应用于你自己的数据集,构建定制化的问答、客服或知识检索系统。

008:SC-GCP_Conclusion

概述

在本节课中,我们将对使用Vertex AI理解和应用文本嵌入的短期课程进行总结,回顾所学到的核心知识与技能。

课程总结

恭喜您完成了这个短期课程。

总结一下,您学会了如何使用Vertex AI计算文本嵌入,了解了它们的工作原理。

您还了解了一些有趣的应用,如聚类、分类和异常检测。

还有人挑选了一些饼干吗?这是一个异常值。迪兹。

我希望您能将所学到的知识用于构建一些非常酷的应用。

结束语

本节课中,我们一起学习了如何利用Vertex AI平台计算文本嵌入,并探索了其在聚类、分类和异常检测等场景下的应用。希望这些知识能帮助您构建出创新的应用。

posted @ 2026-03-26 08:12  绝不原创的飞龙  阅读(3)  评论(0)    收藏  举报