TowardsDataScience 2023 博客中文翻译(六十四)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

使用 pyvis 构建互动网络图

原文:towardsdatascience.com/building-interactive-network-graphs-using-pyvis-5b8e6e25cf64

学习如何让你的网络图栩栩如生

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 魏梦李

·发布在 Towards Data Science ·阅读时间 7 分钟·2023 年 3 月 6 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 DeepMind 提供,来源于 Unsplash

在我之前关于创建网络图的文章中,我展示了如何使用 NetworkX 包构建一个图。NetworkX 的主要问题是生成的图是静态的。一旦图被绘制出来,用户就无法与之互动(例如重新排列节点等)。如果用户可以与图进行互动,网络图会更直观(也更有趣!)。这也是本文的主要关注点。

## 使用 Python 绘制网络图

学习如何使用 NetworkX 包可视化复杂网络

towardsdatascience.com

在这篇文章中,我将展示如何使用 pyvis 包创建一个互动网络图。

pyvis 包是流行的 visJS JavaScript 库的一个封装,它使你可以轻松地在 Python 中生成可视化网络图。

安装 pyvis

要安装 pyvis 包,请使用 pip 命令:

!pip install pyvis

创建网络

首先,使用 pyvis 中的 Network 类创建一个新图:

from pyvis.network import Network

net = Network(
    notebook=True,
)

要在 Jupyter Notebook 中显示图形,将 notebook 参数设置为 True。上述代码片段创建了一个无向图。

添加节点

现在你可以向图中添加节点了:

net.add_node("Singapore")
net.add_node("San Francisco")
net.add_node("Tokyo")
net.add_nodes(["Riga", "Copenhagen"],
              color=['lightgreen', 'yellow'])

add_node() 函数添加一个节点,而 add_nodes() 函数则可以向图中添加多个节点。你还可以为这两个函数设置可选的 color 参数,以设置节点的颜色。

要显示图形,请调用show()函数并为输出指定名称:

net.show('mygraph.html')

节点现在应该显示出来:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

所有图片由作者提供

添加边

在图中添加了节点后,现在可以添加边来连接这些节点:

net.add_edge("Singapore","San Francisco") 
net.add_edge("San Francisco","Tokyo")
net.add_edges(
    [
        ("Riga","Copenhagen"),
        ("Copenhagen","Singapore"),
        ("Singapore","Tokyo"),
        ("Riga","San Francisco"),
        ("San Francisco","Singapore"),
    ]
)

net.show('mygraph.html')

add_edge()函数添加一个连接两个节点的边,而add_edges()函数接受一个包含多个节点的元组列表。

图现在应该显示连接各个节点的边。试着拖动每个节点,看看它们在释放后是如何被拉回的:

所有视频由作者提供

有向图

如果你想要一个有向图,你应该在Network类中设置directed参数:

net = Network(
    notebook=True,
    directed=True
)

如果你修改了之前的代码并重新运行所有代码片段,你现在应该能看到一个有向图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

修改图的物理效果

如果你点击并拖动图中的节点,你会注意到节点会四处弹跳。当你放开鼠标时,节点会弹回到原来的位置。所有这些行为非常像由弹簧(边)束缚的真实球体(节点)。你可以使用repulsion()函数自定义图形背后的物理效果(它们如何弹回、弹簧的阻尼等)。以下语句显示了repulsion()函数中所有参数的默认值:

net.repulsion(
    node_distance=100,
    central_gravity=0.2,
    spring_length=200,
    spring_strength=0.05,
    damping=0.09,
)

这里是各种参数的用途:

  • node_distance — 这是排斥力的影响范围。

  • central_gravity — 使整个网络被吸引到中心的引力。

  • spring_length — 边的静态长度。

  • spring_strength — 边的弹簧强度。

  • damping — 一个从 0 到 1 的值,表示前一次物理模拟迭代中的速度有多少会延续到下一次迭代。

来源: pyvis.readthedocs.io/en/latest/documentation.html?highlight=repulsion#pyvis.network.Network.repulsion

理解各种参数的最佳方法是尝试一下。以下示例设置了spring_lengthdamping参数:

net.repulsion(
    spring_length=400,
    damping=0.01,
)
net.show('mygraph.html')

这是图的样子:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

以下视频展示了拖动和释放节点时图的行为:

你还可以使用show_buttons()函数显示 UI 以动态改变图的物理效果:

net.show_buttons(filter_='physics')
net.show('mygraph.html')

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

filter_参数可以选择以下选项之一:

show_buttons(filter_=['nodes', 'edges', 'physics'])

如果你想显示所有过滤器,将其设置为True

net.show_buttons(filter_= True)

我将留给你尝试这些过滤器的效果以及它们的工作方式。可视化航班延误数据集

现在你已经熟悉了使用pyvis包的基础知识,我们将使用它来可视化2015 年航班延误数据集中的各种机场之间的航班。

2015 年航班延误数据集 (airports.csv)。来源: www.kaggle.com/datasets/usdot/flight-delays许可CC0: 公共领域

首先,将flights.csv文件加载到 Pandas DataFrame 中。由于这个 CSV 文件很大,我将只加载执行工作所需的三列:

import pandas as pd
df = pd.read_csv('flights.csv', 
                 usecols = ["ORIGIN_AIRPORT", "DESTINATION_AIRPORT","YEAR"])

一旦数据框加载完成,我将继续统计从一个机场到另一个机场的航班数量:

df_between_airports = df.groupby(by=["ORIGIN_AIRPORT", "DESTINATION_AIRPORT"]).count()
df_between_airports = df_between_airports['YEAR'].rename('COUNT').reset_index() 
df_between_airports = df_between_airports.query('ORIGIN_AIRPORT.str.len() <= 3 & DESTINATION_AIRPORT.str.len() <= 3')
df_between_airports = df_between_airports.sort_values(by="COUNT", ascending=False)
df_between_airports

结果输出如图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由于航班组合超过 4500 种,我们只选择前 130 种组合:

top = 130
df_between_airports = df_between_airports.head(top)
df_between_airports['COUNT'] = df_between_airports['COUNT'] / 5000
df_between_airports

注意,我将COUNT列中的值除以 5000,因为稍后我将使用COUNT列中的值作为连接两个机场的边的线宽。因此,值需要缩小到一个较小的范围。前 130 种组合现在如图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

接下来,我将汇总每个机场的起始航班(记住之前的计数已经标准化):

node_sizes = df_between_airports.groupby('ORIGIN_AIRPORT').COUNT.agg(sum)
node_sizes

每个机场起始航班的数量将作为节点的大小:

从一个机场起飞的航班越多,节点越大。

ORIGIN_AIRPORT
ANC     1.2766
ATL    20.2544
BOS     6.3382
BWI     1.1674
CLT     1.2614
DAL     1.2524
DCA     4.0138
DEN    11.5638
DFW     5.5244
EWR     2.0252
FLL     2.5436
HNL     5.1544
HOU     1.2592
JAX     1.0192
JFK     6.1684
KOA     1.2694
LAS     6.8754
LAX    21.0822
LGA     7.3132
LIH     1.1710
MCO     2.7096
MIA     2.2936
MSP     2.3608
MSY     1.1186
OAK     1.2562
OGG     1.6626
ORD    12.6836
PHL     2.3876
PHX     7.2886
SAN     2.4130
SEA     7.3736
SFO    12.2998
SJC     1.2678
SLC     3.4424
SMF     1.1148
TPA     1.4166
Name: COUNT, dtype: float64

绘制图表

你现在可以绘制图表:

from pyvis.network import Network

net = Network(
    notebook = True,
    directed = True,            # directed graph
    bgcolor = "black",          # background color of graph 
    font_color = "yellow",      # use yellow for node labels
    cdn_resources = 'in_line',  # make sure Jupyter notebook can display correctly
    height = "1000px",          # height of chart
    width = "100%",             # fill the entire width    
    )

# get all the nodes from the two columns
nodes = list(set([*df_between_airports['ORIGIN_AIRPORT'], 
                  *df_between_airports['DESTINATION_AIRPORT']
                 ]))

# extract the size of each airport
values = [node_sizes[node] for node in nodes]

# extract the edges between airports
edges = df_between_airports.values.tolist()

# use this if you don't need to set the width of the edges
# edges = df_between_airports.iloc[:,:2].values.tolist()

# add the nodes, the value is to set the size of the nodes
net.add_nodes(nodes, value = values)

# add the edges
net.add_edges(edges)

net.show('flights.html')

图表如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们稍微放大一下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

你可以看到ATLLAX的起始航班最多(它们是两个最大节点)。你可以通过将这两个节点的颜色更改为红色来突出显示它们。为此,你可以使用nodes属性遍历所有节点,并检查value键的值。如果值大于 20,则使用color键将节点颜色设置为红色:

...
...

# add the edges
net.add_edges(edges)

# color the nodes red if their count is more than 20
for n in net.nodes:
    if n['value'] > 20:
        n['color'] = 'red'

net.show('flights.html')

ATLLAX节点现在显示为红色:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果你喜欢阅读我的文章,并且它对你的职业/学习有所帮助,请考虑注册成为 Medium 会员。每月 5 美元,能够无限访问 Medium 上的所有文章(包括我的)。如果你使用以下链接注册,我将赚取一小部分佣金(对你没有额外费用)。你的支持意味着我可以投入更多时间撰写类似的文章。

[## 使用我的推荐链接加入 Medium — Wei-Meng Lee

阅读 Wei-Meng Lee 的每个故事(以及 Medium 上其他成千上万的作者)。你的会员费直接支持…

weimenglee.medium.com

摘要

在这篇文章中,你学习了如何使用pyvis包创建交互式网络图。pyvis包最有趣的地方在于它能够让你的网络图生动起来。交互式网络图非常适合用于社交网络、公司结构或其他你希望可视化实体之间关系的网络。享受使用pyvis的乐趣,并告诉我你使用它处理的数据类型!

使用 OPL 堆栈构建 LLMs 驱动的应用程序

原文:towardsdatascience.com/building-llms-powered-apps-with-opl-stack-c1d31b17110f?source=collection_archive---------2-----------------------#2023-04-03

OPL:OpenAI、Pinecone 和 Langchain 用于知识驱动的 AI 助手

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Wen Yang

·

关注 发表在 Towards Data Science ·12 min 阅读·2023 年 4 月 3 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Midjourney 提示:一个女孩用多个积木块建造乐高桥梁

我记得一个月前,Eugene Yan 在 LinkedIn 上发布了一项 投票

你是否感到因为没有参与 LLMs/生成型 AI 而错失良机?

大多数人回答了“是”。考虑到 chatGPT 引发的广泛关注以及现在 gpt-4 的发布,很容易理解为什么会这样。人们形容大型语言模型(LLMs)的崛起就像 iPhone 的时刻。然而,我认为真的没有必要感到 FOMO。考虑一下:错过了开发 iPhones 的机会并不排除创造创新 iPhone 应用程序的巨大潜力。LLMs 也是如此。我们刚刚进入了一个新时代,现在正是利用 LLMs 整合构建强大应用程序的绝佳时机。

在这篇文章中,我将涵盖以下主题:

  1. 什么是 OPL 技术栈?

  2. 如何使用 OPL 构建具有领域知识的 chatGPT?(包含代码演示的关键组件)

  3. 生产考虑因素

  4. 常见误解

1. 什么是 OPL 技术栈?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者创建的图像

OPL 代表 OpenAI、Pinecone 和 Langchain, 它已逐渐成为克服 LLMs 两个局限性的行业解决方案:

  1. LLMs 幻觉: chatGPT 有时会提供过度自信的错误回答。一个潜在的原因是这些语言模型被训练得非常有效地预测下一个词,或者更准确地说是下一个标记。给定输入文本,chatGPT 会返回高概率的词,这并不意味着 chatGPT 具有推理能力。

  2. 知识更新不够及时: chatGPT 的训练数据仅限于 2021 年 9 月之前的互联网数据。因此,如果你的问题涉及最近的趋势或话题,它可能会产生不太理想的回答。

常见的解决方案是在 LLMs 上添加知识库,并使用 Langchain 作为构建流水线的框架。每项技术的关键组件可以总结如下:

  • OpenAI

    • 提供对强大 LLMs 如 chatGPT 和 gpt-4 的 API 访问

    • 提供将文本转换为嵌入的模型。

  • Pinecone:提供嵌入向量存储、语义相似度比较和快速检索。

  • Langchain:它包含 6 个模块(ModelsPromptsIndexesMemoryChainsAgents)。

    • Models 提供了灵活的嵌入模型、聊天模型和 LLMs,包括但不限于 OpenAI 的产品。你还可以使用 Hugging Face 上的其他模型,如 BLOOM 和 FLAN-T5。

    • Memory : 有多种方式可以让聊天机器人记住过去的对话记录。根据我的经验,实体记忆效果好且高效。

    • Chains : 如果你是 Langchain 的新手,Chains 是一个很好的起点。它遵循类似流水线的结构来处理用户输入,选择 LLM 模型,应用 Prompt 模板,并从知识库中搜索相关上下文。

接下来,我将介绍我使用 OPL 技术栈构建的应用程序。

2. 如何使用 OPL 构建具有领域知识的 chatGPT?(包含代码演示的关键组件)

我构建的应用程序称为chatOutside,它有两个主要部分:

  • chatGPT:让你直接与 chatGPT 聊天,格式类似于问答应用,每次接收一个输入和输出。

  • chatOutside:让你与具有户外活动及趋势专业知识的 chatGPT 版本进行对话。格式更类似于聊天机器人的风格,所有消息在对话过程中都会被记录。我还包含了一个提供源链接的部分,这可以增强用户信心,并且总是有用的。

如你所见,如果你问同样的问题:“2023 年最好的跑鞋是什么?我的预算在$200 左右。”chatGPT 会说“作为一个 AI 语言模型,我无法访问未来的信息。”而 chatOutside 会为你提供更及时的答案,并附上源链接。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

开发过程涉及三个主要步骤:

  • 第一步:在 Pinecone 中构建外部知识库

  • 第二步:使用 Langchain 进行问答服务

  • 第三步:在 Streamlit 中构建我们的应用程序

每个步骤的实施细节将在下面讨论。

步骤 1: 在 Pinecone 中构建外部知识库

  • 步骤 1.1: 我连接到我们的外部目录数据库,并选择了在 2022 年 1 月 1 日至 2023 年 3 月 29 日之间发布的文章。这为我们提供了大约 20,000 条记录。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外部的示例数据预览

接下来,我们需要进行两个数据转换。

  • 步骤 1.2: 将上述数据框转换为字典列表,以确保数据可以正确地插入到 Pinecone 中。
# Convert dataframe to a list of dict for Pinecone data upsert
data = df_item.to_dict('records')
  • 步骤 1.3: 使用 Langchain 的RecursiveCharacterTextSplittercontent拆分成更小的块。将文档拆分为更小的块有两个好处:

    • 一篇典型文章可能超过 1000 个字符,这非常长。想象一下我们想检索前 3 篇文章作为提示给 chatGPT,我们很容易就会超过 4000 个字元限制。

    • 更小的块提供更相关的信息,从而为 chatGPT 提供更好的提示上下文。

from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=400,
    chunk_overlap=20,
    length_function=tiktoken_len,
    separators=["\n\n", "\n", " ", ""]
)

分割后,每条记录的内容被拆分成多个部分,每个部分少于 400 个字元。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

将内容拆分为多个块

值得注意的是,使用的文本分割器称为RecursiveCharacterTextSplitter,这是 Langchain 的创建者 Harrison Chase 推荐使用的。基本思路是首先按段落拆分,然后按句子拆分,重叠(20 字元)。这有助于保留来自周围句子的有意义信息和上下文。

  • 步骤 1.4: 将数据插入 Pinecone。下面的代码改编自 James Briggs 的精彩 教程
import pinecone
from langchain.embeddings.openai import OpenAIEmbeddings

# 0\. Initialize Pinecone Client
with open('./credentials.yml', 'r') as file:
    cre = yaml.safe_load(file)
    # pinecone API
    pinecone_api_key = cre['pinecone']['apikey']

pinecone.init(api_key=pinecone_api_key, environment="us-west1-gcp")

# 1\. Create a new index
index_name = 'outside-chatgpt'

# 2\. Use OpenAI's ada-002 as embedding model
model_name = 'text-embedding-ada-002'
embed = OpenAIEmbeddings(
    document_model_name=model_name,
    query_model_name=model_name,
    openai_api_key=OPENAI_API_KEY
)
embed_dimension = 1536

# 3\. check if index already exists (it shouldn't if this is first time)
if index_name not in pinecone.list_indexes():
    # if does not exist, create index
    pinecone.create_index(
        name=index_name,
        metric='cosine',
        dimension=embed_dimension
    )

# 3\. Connect to index
index = pinecone.Index(index_name)

我们批量上传并嵌入所有文章,这花费了大约 20 分钟来插入 2 万条记录。请根据你的环境调整 tqdm 导入(你不需要同时导入两个!)

# If using terminal
from tqdm.auto import tqdm

# If using in Jupyter notebook
from tqdm.autonotebook import tqdm

from uuid import uuid4

batch_limit = 100

texts = []
metadatas = []

for i, record in enumerate(tqdm(data)):
    # 1\. Get metadata fields for this record
    metadata = {
        'item_uuid': str(record['id']),
        'source': record['url'],
        'title': record['title']
    }
    # 2\. Create chunks from the record text
    record_texts = text_splitter.split_text(record['content'])

    # 3\. Create individual metadata dicts for each chunk
    record_metadatas = [{
        "chunk": j, "text": text, **metadata
    } for j, text in enumerate(record_texts)]

    # 4\. Append these to current batches
    texts.extend(record_texts)
    metadatas.extend(record_metadatas)

    # 5\. Special case: if we have reached the batch_limit we can add texts
    if len(texts) >= batch_limit:
        ids = [str(uuid4()) for _ in range(len(texts))]
        embeds = embed.embed_documents(texts)
        index.upsert(vectors=zip(ids, embeds, metadatas))
        texts = []
        metadatas = []

在将 Outside 文章数据插入后,我们可以通过使用 index.describe_index_stats() 检查我们的 Pinecone 索引。需要注意的统计数据之一是 index_fullness,在我们的案例中为 0.2。这意味着 Pinecone pod 已满 20%,暗示一个 p1 pod 大约可以存储 10 万篇文章。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在将数据插入 Pinecone 后

步骤 2:使用 Langchain 进行问答服务

注意:Langchain 最近更新非常快,下面代码使用的版本是 *0.0.118*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

OPL 堆栈中的数据流

上面的草图说明了推理阶段数据流动的方式:

  • 用户提问:“2023 年最好的跑鞋是什么?”

  • 问题使用 ada-002 模型转换为嵌入。

  • 用户问题嵌入与 Pinecone 中存储的所有向量通过 similarity_search 函数进行比较,该函数检索最有可能回答问题的前三个文本块。

  • Langchain 然后将前三个文本块作为 context,与用户问题一起传递给 gpt-3.5(ChatCompletion)生成答案。

所有这些都可以通过不到 30 行代码实现:

from langchain.vectorstores import Pinecone
from langchain.chains import VectorDBQAWithSourcesChain
from langchain.embeddings.openai import OpenAIEmbeddings

# 1\. Specify Pinecone as Vectorstore
# =======================================
# 1.1 get pinecone index name
index = pinecone.Index(index_name) #'outside-chatgpt'

# 1.2 specify embedding model
model_name = 'text-embedding-ada-002'
embed = OpenAIEmbeddings(
    document_model_name=model_name,
    query_model_name=model_name,
    openai_api_key=OPENAI_API_KEY
)

# 1.3 provides text_field
text_field = "text"

vectorstore = Pinecone(
    index, embed.embed_query, text_field
)

# 2\. Wrap the chain as a function
qa_with_sources = VectorDBQAWithSourcesChain.from_chain_type(
    llm=llm,
    chain_type="stuff",
    vectorstore=vectorstore
)

现在我们可以通过提出一个与徒步旅行相关的问题进行测试:“你能推荐一些加州湾区带水景的高级徒步旅行路线吗?”

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Langchain VectorDBQA 带来源

步骤 3:在 Streamlit 中构建我们的应用

在 Jupyter notebook 中验证逻辑有效后,我们可以将所有内容整合在一起,并使用 streamlit 构建前端。在我们的 streamlit 应用中,有两个 python 文件:

  • app.py:前端的主要 python 文件,驱动应用

  • utils.py:由 app.py 调用的支持函数

这就是我的 utils.py 看起来的样子:

import pinecone
import streamlit as st
from langchain.chains import VectorDBQAWithSourcesChain
from langchain.chat_models import ChatOpenAI
from langchain.vectorstores import Pinecone
from langchain.embeddings.openai import OpenAIEmbeddings

# ------OpenAI: LLM---------------
OPENAI_API_KEY = st.secrets["OPENAI_KEY"]
llm = ChatOpenAI(
    openai_api_key=OPENAI_API_KEY,
    model_name='gpt-3.5-turbo',
    temperature=0.0
)

# ------OpenAI: Embed model-------------
model_name = 'text-embedding-ada-002'
embed = OpenAIEmbeddings(
    document_model_name=model_name,
    query_model_name=model_name,
    openai_api_key=OPENAI_API_KEY
)

# --- Pinecone ------
pinecone_api_key = st.secrets["PINECONE_API_KEY"]
pinecone.init(api_key=pinecone_api_key, environment="us-west1-gcp")
index_name = "outside-chatgpt"
index = pinecone.Index(index_name)
text_field = "text"
vectorstore = Pinecone(index, embed.embed_query, text_field)

#  ======= Langchain ChatDBQA with source chain =======
def qa_with_sources(query):
    qa = VectorDBQAWithSourcesChain.from_chain_type(
        llm=llm,
        chain_type="stuff",
        vectorstore=vectorstore
    )

    response = qa(query)
    return response 

最后,这就是我的 app.py 看起来的样子:

 import os
import openai
from PIL import Image
from streamlit_chat import message
from utils import *

openai.api_key = st.secrets["OPENAI_KEY"]
# For Langchain
os.environ["OPENAI_API_KEY"] = openai.api_key

# ==== Section 1: Streamlit Settings ======
with st.sidebar:
    st.markdown("# Welcome to chatOutside 🙌")
    st.markdown(
        "**chatOutside** allows you to talk to version of **chatGPT** \n"
        "that has access to latest Outside content!  \n"
        )
    st.markdown(
        "Unlike chatGPT, chatOutside can't make stuff up\n"
        "and will answer from Outside knowledge base. \n"
    )
    st.markdown("👩‍🏫 Developer: Wen Yang")
    st.markdown("---")
    st.markdown("# Under The Hood 🎩 🐇")
    st.markdown("How to Prevent Large Language Model (LLM) hallucination?")
    st.markdown("- **Pinecone**: vector database for Outside knowledge")
    st.markdown("- **Langchain**: to remember the context of the conversation")

# Homepage title
st.title("chatOutside: Outside + ChatGPT")
# Hero Image
image = Image.open('VideoBkg_08.jpg')
st.image(image, caption='Get Outside!')

st.header("chatGPT 🤖")

# ====== Section 2: ChatGPT only ======
def chatgpt(prompt):
    res = openai.ChatCompletion.create(
        model='gpt-3.5-turbo',
        messages=[
            {"role": "system",
             "content": "You are a friendly and helpful assistant. "
                        "Answer the question as truthfully as possible. "
                        "If unsure, say you don't know."},
            {"role": "user", "content": prompt},
        ],
        temperature=0,
    )["choices"][0]["message"]["content"]

    return res

input_gpt = st.text_input(label='Chat here! 💬')
output_gpt = st.text_area(label="Answered by chatGPT:",
                          value=chatgpt(input_gpt), height=200)
# ========= End of Section 2 ===========

# ========== Section 3: chatOutside ============================
st.header("chatOutside 🏕️")

def chatoutside(query):
    # start chat with chatOutside
    try:
        response = qa_with_sources(query)
        answer = response['answer']
        source = response['sources']

    except Exception as e:
        print("I'm afraid your question failed! This is the error: ")
        print(e)
        return None

    if len(answer) > 0:
        return answer, source

    else:
        return None
# ============================================================

# ========== Section 4\. Display ChatOutside in chatbot style ===========
if 'generated' not in st.session_state:
    st.session_state['generated'] = []

if 'past' not in st.session_state:
    st.session_state['past'] = []

if 'source' not in st.session_state:
    st.session_state['source'] = []

def clear_text():
    st.session_state["input"] = ""

# We will get the user's input by calling the get_text function
def get_text():
    input_text = st.text_input('Chat here! 💬', key="input")
    return input_text

user_input = get_text()

if user_input:
    # source contain urls from Outside
    output, source = chatoutside(user_input)

    # store the output
    st.session_state.past.append(user_input)
    st.session_state.generated.append(output)
    st.session_state.source.append(source)

    # Display source urls
    st.write(source)

if st.session_state['generated']:
    for i in range(len(st.session_state['generated'])-1, -1, -1):
        message(st.session_state["generated"][i],  key=str(i))
        message(st.session_state['past'][i], is_user=True,
                avatar_style="big-ears",  key=str(i) + '_user')

3. 生产考虑

好了,够了!

应用实际上已经很不错了。但是如果我们想进入生产环境,还有一些额外的事项需要考虑:

  1. 在 Pinecone 中摄取新的和更新的数据:我们对文章数据进行了单次批量插入。实际上,每天都有新的文章添加到我们的网站中,而且一些字段可能会更新已经摄取到 Pinecone 的数据。这不是机器学习的问题,而是对媒体公司而言常常存在的问题:如何保持每个服务中的数据更新。潜在的解决方案是设置一个定时任务来定期执行插入和更新任务。有关如何并行发送插入操作的指令,如果我们可以使用 Django 和 Celery 的异步任务,这可能会非常有用。

  2. Pinecone pod 存储的限制:当前应用使用的是 p1 pod,能够存储最多 1M 个 768 维向量,或者如果我们使用 OpenAI 的 ada-002 嵌入模型(其维度为 1536),则大约能存储 50 万个向量。

  3. 流式处理功能以提高响应速度:为了减少用户感知的延迟,可能有助于向应用添加流式处理功能。这将模拟 chatGPT 按 token 返回生成的输出,而不是一次性显示整个响应。虽然这种功能在使用 LangChain 函数的 REST API 中有效,但对于我们来说,因为我们使用 GraphQL 而不是 REST,这带来了独特的挑战。

4. 常见误解和问题

  1. chatGPT 记住了截至 2021 年 9 月的互联网数据,并且基于记忆检索答案。

    • 这并不是它的工作方式。训练之后,chatGPT 从内存中删除数据,并使用其 1750 亿个参数(权重)来预测最可能的 token(文本)。它不会基于记忆检索答案。这就是为什么如果你只是复制 chatGPT 生成的答案,你不太可能找到来自互联网的任何来源。
  2. 我们可以训练/微调/提示工程 chatGPT。

    • 训练和微调大型语言模型意味着实际更改模型参数。你需要访问实际的模型文件,并根据你的具体使用情况指导模型。在大多数情况下,我们不会训练或微调 chatGPT。我们所需要的只是提示工程:为 chatGPT 提供额外的上下文,并让它根据这些上下文回答问题。
  3. token 和词有什么区别?

    -** Token 是一个词片段。100 个 token 大约等于 75 个词。例如,“Unbelievable” 是一个词,但包含 3 个 token(un, belie, able)。**

  4. 4000-token 限制意味着什么?

    -** OpenAI gpt-3.5 的 token 限制为 4096,用于结合用户输入、上下文和响应。当使用 Langchain 的内存时,(用户问题 + 上下文 + 内存 + chatGPT 响应)使用的总词数需要少于 3000 个词(4000 个 token)。**

    • gpt-4 有更高的 token 限制,但也贵了 20 倍!(gpt-3.5:$0.002/1000 tokens;gpt-4:$0.045/1000 tokens,假设 500 个用于提示和 500 个用于完成)。
  5. 我是否必须使用像 Pinecone 这样的向量存储?

    -** 不,Pinecone 不是唯一的向量存储选项。其他向量存储选项包括 Chroma、FAISS、Redis 等。此外,你并不总是需要向量存储。例如,如果你想为特定网站构建一个问答系统,你可以爬取网页并参考这个 openai-cookbook-recipe

告别的话

感谢你阅读这篇长文!如果你对使用 Langchain 有任何问题或建议,请随时联系我。

此外,我将参加 LLM Bootcamp,希望能学习到更多关于将 LLMs 应用到生产中的最佳实践。如果你对这个话题感兴趣,请关注我未来的帖子!😃

为企业建立机器学习操作

原文:towardsdatascience.com/building-machine-learning-operations-for-businesses-6d0bfbbf2139

支持你的 AI 策略的有效 MLOps 蓝图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 John Adeojo

·发布于 Towards Data Science ·11 分钟阅读·2023 年 6 月 20 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源:由作者生成,使用 Midjourney

背景 — 探索 MLOps

在我的职业生涯中,我发现成功的 AI 策略的关键在于能够将机器学习模型部署到生产中,从而大规模释放其商业潜力。然而,这不是一件容易的事——它涉及各种技术、团队的整合,并且往往需要组织内部的文化转变,这个系统被称为 MLOps。

然而,没有一种通用的 MLOps 策略。在本文中,我提供了一个灵活的 MLOps 蓝图,可以作为起点或用来微调你当前的工作流程。尽管 MLOps 的旅程可能复杂,我强烈建议将其视为将 AI 融入业务的一个不可或缺的初步步骤,而不是一个次要考虑因素。

MLOps 超越了技术

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源:成功 MLOps 策略的组成部分

在深入技术细节之前,我想分享一些我观察到的各种 MLOps 策略中的(非技术性)见解。MLOps 不仅仅是技术——它依赖于三个关键组成部分:投资、文化和技术。那些从一开始就考虑这三方面的公司往往在策略上更成功。我见过的一个常见错误是企业优先投资解决方案,而忽视了必要的文化变革。这种疏忽可能严重破坏你的策略,可能浪费资金并削弱高层管理人员或投资者的信心。

文化

向任何业务引入新文化绝非易事,需要全体员工的全力支持。我常见的一个常见陷阱是,当企业突然用新的、光鲜的工具替代旧工具时,往往忽视了文化变革。这种做法可能引发不满,导致这些工具被忽视或误用。

相反,成功管理文化变革的公司涉及终端用户参与制定 MLOps 策略,并赋予他们推动责任。此外,他们还提供了必要的支持和培训,以提升用户技能,激励他们参与这些举措。

解决方案可能在技术上确实更优,但如果不推动文化变革,它就有可能无效。毕竟,是人们操作技术,而不是技术操作人们。

技术

为了简明起见,我将技术定义为技术基础设施和数据管理服务的组合。

有效的 MLOps 策略建立在成熟的数据生态系统之上。通过利用数据管理工具,数据科学家应能够以安全且符合监管要求的方式访问数据进行模型开发。

从技术基础设施的角度来看,我们应当赋能数据科学家和机器学习工程师,提供所需的硬件和软件,以便促进 AI 产品的开发和交付。对许多公司而言,利用云基础设施是实现这一目标的关键。

投资

在 MLOps 中没有捷径,特别是在投资方面。高效的 MLOps 策略应优先考虑对人力和技术的投资。我遇到的一个常见问题是,由于预算限制,客户往往倾向于围绕单一数据科学家构建 MLOps 策略。在这种情况下,我通常建议重新评估,或至少调整预期。

从一开始,就必须明确你的创新投入的范围及其持续时间。实际上,如果你希望人工智能成为你运营的基础并带来相关好处,持续投资是至关重要的。

有关开发 AI 策略的观点,您可能希望阅读我关于使用 Wardley 图绘制 AI 策略的文章:

## 为企业建立 AI 策略

通过 Wardley 图绘制 AI 策略的艺术

[towardsdatascience.com

MLOps 的高级蓝图

现在我们已经打下了基础,我们将深入探讨一些 MLOps 的技术组件。为了帮助可视化,我设计了一个流程图,说明了各个过程之间的关系。虚线表示数据流动,实线表示从一个活动到另一个活动的过渡。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图像:高层次 MLOps 工作流程

模型开发实验室

模型开发过程本质上是不可预测和反复的。未能认识到这一点的公司将难以建立有效的 AI 策略。实际上,模型开发往往是工作流程中最混乱的部分,充满了实验、重复和频繁的失败。这些元素在探索新解决方案时是必不可少的;创新正是在这里诞生的。那么,数据科学家需要什么?实验、创新和合作的自由。

现在有一种普遍的看法,即数据科学家在编写代码时应遵循软件工程最佳实践。虽然我不反对这种观点,但每件事都有其时间和场所。我不认为模型开发实验室必然是这种实践的场所。与其试图平息这种混乱,我们应该将其视为工作流程的必要部分,并寻求利用能够帮助我们管理混乱的工具 — 一个有效的模型开发实验室应能提供这些。让我们探讨一些潜在的组件。

实验与原型开发 — Jupyter Labs

Jupyter Labs 提供了一个多功能的集成开发环境(IDE),适用于初步模型和概念验证的创建。它提供了对笔记本、脚本和命令行接口的访问,这些都是数据科学家熟悉的功能。

作为一个开源工具,Jupyter Labs 以其与 Python 和 R 的无缝集成,涵盖了当代数据科学模型开发任务的大部分。大多数数据科学工作负载可以在实验室 IDE 中进行。

环境管理 — Anaconda

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图像:Anaconda 虚拟环境及模型共享的示意图

有效的环境管理可以简化后续的 MLOps 工作流程步骤,重点关注安全访问开源库和再现开发环境。Anaconda,作为一个包管理器,允许数据科学家创建虚拟环境,并使用其简单的命令行接口(CLI)安装模型开发所需的库和包。

Anaconda 还提供了代码库镜像,评估开源包的安全商业使用,尽管需要考虑第三方管理的相关风险。使用虚拟环境在管理实验阶段至关重要,本质上为特定实验提供了一个封闭的空间来容纳所有包和依赖项。

版本控制与协作 — GitHub Desktop

协作是成功的模型开发实验室的重要组成部分,利用 GitHub Desktop 是促进这一点的有效方式。数据科学家可以通过 GitHub Desktop 为每个实验室创建一个仓库。每个仓库存储模型开发的笔记本或脚本,以及一个 environment.yml 文件,该文件指示 Anaconda 如何在另一台机器上重现笔记本开发时的环境。

Jupyter Labs、Anaconda 和 GitHub 这三种实验室组件的结合为数据科学家提供了一个安全的空间进行实验、创新和协作。

#An example environment.yml file replicating a conda environment

name: myenv
channels:
  - conda-forge
dependencies:
  - python=3.9
  - pandas
  - scikit-learn
  - seaborn

模型管道开发

在与处于 MLOps 成熟初期的客户讨论时,似乎存在这样一种观点:数据科学家开发模型后,将其“交给”机器学习工程师进行“生产化”。这种做法不可行,而且很可能是最快丢失机器学习工程师的方式。没有人愿意处理他人的混乱代码,坦率地说,期望工程师处理这些代码是不公平的。

相反,组织需要培养一种文化,使数据科学家负责在数据实验室中开发模型,然后将其正式化为端到端模型管道。这是为什么:

  • 数据科学家对他们的模型了解得比任何人都要多。让他们负责创建模型管道将提高效率。

  • 你在每个开发阶段建立一种软件工程最佳实践的文化。

  • 机器学习工程师可以专注于能够增加价值的工作,如资源配置、扩展和自动化,而不是重构他人的笔记本。

构建端到端的管道初看起来可能会令人畏惧,但幸运的是,有针对数据科学家的工具可以帮助他们实现这一目标。

模型管道构建 — Kedro

Kedro 是一个由 麦肯锡量子黑客 提供的开源 Python 框架,用于帮助数据科学家构建模型管道。

# Standard template for Kedro projetcs

{{ cookiecutter.repo_name }}     # Parent directory of the template
├── conf                         # Project configuration files
├── data                         # Local project data
├── docs                         # Project documentation
├── notebooks                    # Project related Jupyter notebooks
├── README.md                    # Project README
├── setup.cfg                    # Configuration options for tools 
└── src                          # Project source code
    └── {{ cookiecutter.python_package }}
       ├── __init.py__
       ├── pipelines
       ├── pipeline_registry.py
       ├── __main__.py
       └── settings.py
    ├── requirements.txt
    ├── setup.py
    └── tests

Kedro 提供了一个用于构建端到端模型管道的标准模板,结合了软件工程最佳实践。其背后的理念是鼓励数据科学家构建模块化、可重复和易维护的代码。一旦数据科学家完成了 Kedro 工作流,他们实际上构建了一个可以更容易地部署到生产环境的系统。以下是总体概念:

  • 项目模板:Kedro 提供了一个标准且易于使用的项目模板,提升了结构性、协作性和效率。

  • 数据目录:Kedro 中的数据目录是项目可以使用的所有数据源的注册表。它提供了一种简单的方法来定义数据的存储方式和位置。

数据工程目录由 Kedro 项目定义,详见 docs.kedro.org/en/0.18.1/faq/faq.html

  • 管道: Kedro 将数据处理结构化为一个依赖任务的管道,强制执行清晰的代码结构,并可视化数据流和依赖关系。

  • 节点: 在 Kedro 中,节点是一个 Python 函数的包装器,指定该函数的输入和输出,作为 Kedro 管道的构建块。

  • 配置: Kedro 管理不同环境(开发、生产等)的配置,而不需要将任何配置硬编码到代码中。

  • 输入/输出: 在 Kedro 中,I/O 操作与实际计算相抽象,这增加了代码的可测试性和模块化,并简化了不同数据源之间的切换。

  • 模块化和可重用性: Kedro 提倡模块化编码风格, resulting in reusable, maintainable and testable code.

  • 测试: Kedro 集成了 Python 中的测试框架 PyTest,使得编写管道测试变得简单。

  • 版本控制: Kedro 支持数据和代码的版本控制,使得能够重现管道的任何先前状态。

  • 日志记录: Kedro 提供了标准化的日志记录系统,用于跟踪事件和变更。

  • 钩子和插件: Kedro 支持钩子和插件,根据项目需求扩展框架功能。

  • 与其他工具的集成: Kedro 可以与 Jupyter notebook、Dask、Apache Spark 等各种工具集成,以促进数据科学工作流的不同方面。

所有 Kedro 项目都遵循这一基本模板。对数据科学团队实施这一标准将实现可重复性和可维护性。

若要了解 Kedro 框架的更全面概述,请访问以下资源:

  • Kedro 文档: link

  • 数据工程中分层思维的重要性: link

注册与存储 — 数据版本控制 (DVC)

注册和存储是机器学习可重复性的基础,任何希望融入机器学习的企业都应牢记这一点。机器学习模型本质上由代码、数据、模型制品和环境组成 — 这些都必须可追溯,以实现可重复性。

DVC 是一个为模型和数据提供版本控制和跟踪的工具。虽然 GitHub 可能是一个替代方案,但它在存储大对象的能力上有限,对大量数据集或模型构成问题。DVC 实质上扩展了 Git,提供相同的版本控制能力,同时支持在本地或云端的 DVC 仓库中存储更大的数据集和模型。

在商业环境中,将代码版本控制在 Git 仓库中有明显的安全优势,同时将实际的模型制品和数据单独存储在受控环境中。

记住,随着关于 AI 商业使用的法规日益严格,模型的可重复性将变得越来越重要。可重复性有助于审计。

模型管道部署 — Docker

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片:推理管道的 Docker 部署示意图。请注意,这种方法也可以应用于模型监控和再训练管道

部署不仅仅是一个单一任务,而是一个精心设计的工具、活动和过程的融合;Docker 将这些都联系在一起,用于模型部署。对于具有众多依赖项的复杂 ML 应用程序,Docker 通过将应用程序与其环境封装在一起,确保了在任何机器上的一致性。

这个过程始于一个 Dockerfile;然后,Docker 使用其命令构建一个镜像,这是一个适用于任何启用 Docker 的机器的预打包模型管道。与 Kedro 的管道功能结合使用,Docker 可以高效地部署模型再训练和推理管道,确保 ML 工作流各个阶段的可重复性。

模型监控与再训练管道 — MLflow

随着时间的推移,机器学习模型的性能会受到退化的影响,这可能是由于概念漂移数据漂移。我们希望能够监控模型性能何时开始下降,并在必要时重新训练它们。MLflow 通过其跟踪 API 提供了这种能力。跟踪 API 应该被纳入数据科学家构建的模型训练和推理管道中。虽然我在模型监控和再训练管道中指定了 MLflow 进行跟踪,但跟踪也可以在模型开发实验室进行,特别是用于实验跟踪。

推理端点

鉴于推理管道已经被封装到 Dockerfile 中,我们可以在任何地方创建该管道的 Docker 镜像,以用作任何应用程序的 API 端点。根据使用情况,我们将不得不决定在哪里部署 Docker 镜像。不过,这超出了本文的范围。

角色与职责

在 MLOps 中分配明确的角色和责任对其成功至关重要。MLOps 的多面性跨越了多个学科,因此需要清晰的角色划分。这确保了每项任务的高效执行。此外,它还促进了责任的明确,从而更快地解决问题。最后,明确的委派减少了混乱和重叠,使团队更高效,并有助于维持和谐的工作环境。这就像一台运行良好的机器,每个齿轮都发挥着完美的作用。

数据科学家

  • 角色:数据科学家在 MLOps 策略中的主要功能是专注于模型开发。这包括初步实验、原型设计和为经过验证的模型设置建模管道。

  • 职责:数据科学家确保模型遵循机器学习最佳实践,并与业务案例对齐。除了实验室任务,他们还与业务利益相关者沟通,识别有影响的解决方案。他们对数据实验室负责,一个主数据科学家应该设定操作节奏和实验室设置的最佳实践。

机器学习工程师

  • 角色:ML 工程师负责监督 MLOps 的技术基础设施,探索创新解决方案,与数据科学家共同制定策略,并提升流程效率。

  • 职责:他们确保技术基础设施的功能,监控组件的性能以控制成本,并确认生产模型满足所需规模的需求。

数据治理专业人士

  • 角色:数据治理专业人士维护安全性和数据隐私政策,在 MLOps 框架内安全传输数据方面发挥关键作用。

  • 职责:虽然数据治理是每个人的责任,这些专业人士会制定政策并通过定期检查和审计确保合规。他们跟进法规,确保所有数据使用者的合规。

结论

在 MLOps 领域的导航是一项需要深思熟虑的规划、正确的技术与人才组合,以及支持变革和学习的组织文化的任务。

这个过程可能看起来很复杂,但通过采用精心设计的蓝图,并将 MLOps 视为一个整体的、迭代的过程而不是一次性的项目,你可以从你的 AI 策略中获得巨大的价值。然而,请记住,没有一种方法适用于所有情况。量体裁衣,调整你的策略以适应具体需求,并保持灵活应对变化的环境,这一点至关重要。

LinkedIn 上关注我

订阅 Medium 以获取更多我的见解:

[## 使用我的推荐链接加入 Medium — John Adeojo

我分享数据科学项目、经验和专业知识,以帮助你在旅途中取得成功。你可以通过…

johnadeojo.medium.com

如果你有兴趣将 AI 或数据科学融入到你的业务运营中,我们邀请你预约一次免费的初步咨询:

[## 在线预约 | 数据驱动解决方案

通过免费的咨询发现我们在帮助企业实现雄心目标方面的专业知识。我们的数据科学家和…

www.data-centric-solutions.com

为我儿子打造的 AI 漫画视频生成器

原文:towardsdatascience.com/building-owly-an-ai-comic-story-generator-for-my-son-c99fb695d83b

利用在 Amazon SageMaker JumpStart 上精细调整的 Stable Diffusion 2.1,我开发了一种名为 Owly 的 AI 技术,能够制作带有音乐的个性化漫画视频,以我儿子的玩具作为主角。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Agustinus Nalwan

·发表于 Towards Data Science ·24 分钟阅读·2023 年 4 月 11 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Owly AI 漫画故事讲述者 [AI 生成图像]

每天晚上,与我 4 岁的儿子 Dexie 分享睡前故事已经成为一种珍贵的例行公事,他非常喜欢这些故事。他的书籍收藏相当丰富,但他特别着迷于我从零开始编写的故事。以这种方式创作故事也让我能够融入我希望他学习的道德价值观,这些在商店购买的书籍中可能很难找到。随着时间的推移,我磨练了编写个性化叙事的技巧,点燃了他的想象力——从有裂缝的龙到寻找陪伴的孤独天灯。最近,我编造了像 Slow-Mo Man 和 Fart-Man 这样的虚构超级英雄故事,这些故事已经成为他的最爱。

尽管这段时间对我来说充满了乐趣,但在经过半年的每晚讲故事后,我的创意储备正在经受考验。为了让他持续获得新鲜而有趣的故事而不使自己精疲力竭,我需要一个更可持续的解决方案——一种能够自动生成引人入胜的故事的 AI 技术!我给她起了个名字叫 Owly,以他最喜欢的鸟类——猫头鹰来命名。

Pookie 和通往魔法森林的秘密门——由 AI 漫画生成器生成。

概念

当我开始组装我的愿望清单时,它很快膨胀起来,这种膨胀源于我对测试现代技术前沿的渴望。普通的文本故事是不够的——我设想了一个 AI 制作一个完整的漫画,最多可有 10 个面板。为了增加 Dexie 的兴奋感,我计划使用他熟悉和喜爱的角色,如 Zelda 和 Mario,甚至可以加入他的玩具。坦率地说,个性化的角度源于对漫画条纹的视觉一致性的需求,我稍后会深入讨论。但别急,这还不是全部——我还希望 AI 朗读故事,并配上合适的音乐来营造气氛。完成这个项目对我来说既有趣又具有挑战性,而 Dexie 将会享受到一场量身定制的互动故事盛宴。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Dexie 的玩具作为漫画故事的主要角色 [作者提供的图片]

脑力风暴

为了征服上述要求,我意识到需要组装五个奇妙的模块:

  1. 故事脚本生成器,编写多段故事,每段故事将被转换为漫画条纹部分。此外,它会推荐一种音乐风格,以从我的库中挑选合适的曲调。为了实现这一目标,我请来了强大的 OpenAI GPT3.5 大型语言模型 (LLM)。

  2. 漫画图像生成器,为每个故事片段生成图像。Stable Diffusion 2.1 与 Amazon SageMaker JumpStart、SageMaker Studio 和 Batch Transform 联手,将这一切变为现实。

  3. 文本转语音模块,将书面故事转换为音频叙述。Amazon Polly 的神经引擎跃然而出,提供了救援。

  4. 视频制作器,将漫画条纹、音频叙述和音乐编织成一个自播放的杰作。MoviePy 是这个节目的明星。

  5. 最终,控制器将这四个模块的宏伟交响曲协调起来,建立在强大的 AWS Batch 基础上。

游戏计划?让故事脚本生成器编织一个 7-10 段的叙述,每段转变为漫画条纹部分。然后,漫画图像生成器为每个片段生成图像,而文本转语音模块则制作音频叙述。根据故事生成器的推荐选择一段悦耳的音乐。最后,视频制作器将图像、音频叙述和音乐结合在一起,创造一个异想天开的视频。Dexie 将会在这个独一无二的互动故事冒险中获得极大的享受!

漫画图像生成器

在深入探讨故事脚本生成器之前,让我们首先探索图像生成模块,以便为任何关于图像生成过程的参考提供背景。有许多文本到图像的 AI 模型可供选择,但我选择了 Stable Diffusion 2.1 模型,因为它在使用 Amazon SageMaker 和更广泛的 AWS 生态系统进行构建、微调和部署方面非常受欢迎且容易。

Amazon SageMaker Studio 是一个集成开发环境(IDE),提供了一个统一的基于 Web 的界面,处理所有机器学习(ML)任务,简化数据准备、模型构建、训练和部署。这提高了数据科学团队的生产力,最多可提高 10 倍。在 SageMaker Studio 中,用户可以无缝上传数据、创建笔记本、训练和调整模型、调整实验、与团队协作,并将模型部署到生产环境中。

Amazon SageMaker JumpStart 是 SageMaker Studio 中的一个宝贵功能,提供了大量广泛使用的预训练 AI 模型。一些模型,包括 Stable Diffusion 2.1 base,可以使用你自己的训练集进行微调,并附带一个示例 Jupyter Notebook。这使得你可以快速高效地对模型进行实验。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在 Amazon SageMaker JumpStart 上启动 Stable Diffusion 2.1 Notebook [图片来源:作者]

我导航到 Stable Diffusion 2.1 base 视图模型页面,并通过点击 Open Notebook 按钮启动了 Jupyter Notebook。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Stable Diffusion 2.1 Base 模型卡 [图片来源:作者]

几秒钟内,Amazon SageMaker Studio 提供了示例笔记本,其中包含所有必要的代码,用于从 JumpStart 加载文本到图像模型、部署模型,甚至为个性化图像生成进行微调。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Amazon SageMaker Studio IDE [图片来源:作者]

有许多文本到图像的模型可用,许多模型由其创建者为特定风格量身定制。利用 JumpStart API,我筛选并列出了所有文本到图像的模型,使用 filter_value “task == txt2img” 并在下拉菜单中展示以便于选择。

from ipywidgets import Dropdown
from sagemaker.jumpstart.notebook_utils import list_jumpstart_models

# Retrieves all Text-to-Image generation models.
filter_value = "task == txt2img"
txt2img_models = list_jumpstart_models(filter=filter_value)

# display the model-ids in a dropdown to select a model for inference.
model_dropdown = Dropdown(
    options=txt2img_models,
    value="model-txt2img-stabilityai-stable-diffusion-v2-1-base",
    description="Select a model",
    style={"description_width": "initial"},
    layout={"width": "max-content"},
)
display(model_dropdown)

# Or just hard code the model id and version=*. 
# Eg. if we want the latest 2.1 base model
self._model_id, self._model_version = (
    "model-txt2img-stabilityai-stable-diffusion-v2-1-base",
    "*",
)

我需要的模型是 model-txt2img-stabilityai-stable-diffusion-v2–1-base,它允许微调。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

大量文本到图像的模型 [图片来源:作者]

在不到 5 分钟的时间内,利用提供的代码,我将模型部署到了运行 g4dn.2xlarge GPU 实例的 SageMaker 端点。我迅速从我的文本提示中生成了第一张图片,你可以在下面查看展示效果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我的图像生成器创建了一张海龟在水下游泳的图像 [图片来源:作者]

Amazon SageMaker Studio 简化了我的实验和原型制作过程,使我能够迅速尝试各种图像生成提示,并通过文件浏览器和预览窗口直接在 IDE 中查看生成的图像。此外,我可以在 IDE 中上传图像,利用内置终端启动 AWS CLI 将图像上传到 S3 存储桶或从中下载图像,并执行 SageMaker 批量转换作业以对模型生成大量图像进行大规模测试。

故事脚本生成器

这个模块的任务非常简单:根据故事主题和角色名称制作故事脚本。使用 GPT3.5 API 生成关于特定主题的故事非常简单。

openai.api_key = self._api_key
prompt = "Write me a 1000-word story about Bob the penguin who wants to travel to Europe to see famous landmarks"
response = openai.Completion.create(
    model="text-davinci-003",
    prompt=prompt,
    temperature=0.7,
    max_tokens=2089,
    top_p=1,
    frequency_penalty=0,
    presence_penalty=0
)

例如,使用提示“写一篇关于企鹅 Bob 想要去欧洲看著名地标的 1000 字故事。他发现自己的勇气和好奇心让他体验了许多令人兴奋的事情。” GPT3.5 会轻松地编写出引人入胜的故事,就像由专业讲故事的人撰写的一样,下面就是一个类似的例子。

企鹅 Bob 一直梦想着去欧洲旅行,参观著名的地标。他听朋友们讲过巴黎的埃菲尔铁塔、罗马的斗兽场和伦敦的大本钟。他对南极洲的日常生活感到厌倦,渴望冒险。

一天,Bob 决定勇敢地开始规划他的旅行。他花了几个小时研究最佳的旅行路线和最实惠的住宿。经过仔细考虑,他决定从巴黎开始他的旅程。

船程漫长而疲惫,但他对终于到达欧洲感到兴奋。他登记入住酒店后,立即出发去看埃菲尔铁塔。当他走在巴黎的街道上时,他感到前所未有的惊奇和兴奋。

在接下来的几天里,他参观了像卢浮宫、巴黎圣母院和凡尔赛宫这样的著名地标。他尝试了新的食物,结识了新朋友,每一次经历都为他的冒险增添了色彩。

故事本身很棒,但为了将其转化为漫画,我需要将故事分成若干部分,并为每一部分创建一幅图像。最合逻辑的方法是将每个段落转换为一个部分。然而,正如你所见,从这些段落生成的图像呈现出一些重大挑战。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们的企鹅 Bob 被描绘成不同角色 [AI 生成的图像]

  • 角色混乱出现了!每幅漫画中,Bob 都被描绘成完全不同的角色。在第一幅中,他是一个企鹅玩偶;在第二幅中,他是一个人的腿;在第三幅中,他是一个穿着西装的绅士;在第四幅中,他是一个穿蓝衬衫的男人。这种情况发生是因为只有第一段提到了“企鹅 Bob”,而第二段仅称他为“Bob”,其余段落则用“他”来指代。鉴于这些稀少的信息,难怪图像生成器将 Bob 以多种不同的形象呈现。

  • 这些场景也缺乏焦点。第一幅漫画展示了一个企鹅玩偶坐在桌子上,而不是一个居住在南极的企鹅梦想着欧洲冒险。在第二幅漫画中也出现了类似的问题,展示了某个人的腿高高飞过城市。看来图像生成器将“一天,鲍勃决定冒险”理解成了从飞机跳伞,而该段落的重点应该是鲍勃计划他的欧洲之行。长段落中有多个焦点往往会使图像生成器感到困惑,导致图像脱离上下文。

为了解决场景缺乏焦点的问题并提高角色的一致性,我对提示进行了细化,要求每段包含一个简洁的一句话场景描述,格式为[],以便可以程序化提取。这使我能够提供详细的示例,并指导图像生成器创建更集中和准确的图像。

对于每个部分,请详细描述场景,并始终在一段中包含位置的句子,格式为[a photo of character in the location]、[a photo of character in front of an object]、[a photo of character next to an object]、[a photo of a location]。

使用更新后的提示,这里是生成的故事。

[鲍勃企鹅在南极的照片]

鲍勃企鹅是一只快乐和好奇的企鹅,生活在南极。他对在那里与朋友和家人一起的生活感到满足。但有一天,他决定冒险,探索冰冷大陆之外的世界。他听说了许多美丽而异国情调的地方,并希望亲自体验。

[鲍勃企鹅在读书的照片]

鲍勃企鹅开始研究世界,查看地图并阅读不同国家和文化的信息。他特别被欧洲吸引,那里的许多著名地标和景点让他向往。他决定欧洲是他想要访问的地方,于是开始计划他的旅行。

[鲍勃企鹅在游轮上的照片]

他开始了漫长的船程。他感到兴奋,迫不及待地想要到达那里,并且决心去欧洲。在几周的旅行后,他终于到达了目的地。

[鲍勃企鹅在埃菲尔铁塔的照片]

鲍勃企鹅开始探索欧洲,对他访问的各种不同地方感到惊叹。他去了巴黎的埃菲尔铁塔、罗马的斗兽场和爱尔兰的莫赫悬崖。他去的每个地方都让他充满了敬畏和喜悦。

正如你所观察到的,生成的场景描述要集中得多。它们提到单一场景、位置和/或正在进行的活动,通常以角色的名字开头。这些简洁的提示对于我的图像生成器效果显著,如下所示的改进图像就是证明。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们的 Bob 企鹅的更一致外观 [AI 生成的图像]

Bob 企鹅已成功回归,但他在每个漫画条幅中依然呈现出全新的形象。由于图像生成过程将每张图像视为独立的,而且没有提供有关 Bob 的颜色、大小或企鹅类型的信息,因此一致性仍然难以实现。

我之前考虑过将详细的角色描述作为故事生成的一部分,以保持图像中的角色一致性。然而,这种方法由于两个原因被证明是不切实际的:

  • 有时,很难在不 resorting 到大量文本的情况下详细描述一个角色。虽然企鹅的种类可能不多,但考虑到鸟类的一般情况——有着无数形状、颜色和品种如葵花鹦鹉、鹦鹉、金丝雀、鹈鹕和猫头鹰,这项任务变得令人生畏。

  • 生成的角色不总是符合提示中提供的描述。例如,一个描述绿色鹦鹉带红色喙的提示可能会生成一只绿色鹦鹉带黄色喙的图像。

所以,尽管我们尽了最大努力,我们的企鹅朋友 Bob 仍然经历着某种身份危机。

为图像生成器添加个性化

我们解决企鹅困境的方法在于为 Stable Diffusion 模型提供一个关于我们企鹅角色应有外观的视觉提示,以影响图像生成过程并保持生成图像的一致性。在 Stable Diffusion 的世界中,这一过程被称为微调,你需要提供一些(通常是 5 到 15 张)包含相同对象的图像以及描述它的句子。这些图像将被称为训练图像。

结果是,这种个性化不仅仅是解决方案,还是我漫画生成器的一个非常酷的功能。现在,我可以将 Dexie 的许多玩具作为故事中的主要角色,例如他节日的圣诞企鹅,为 Bob 企鹅注入新生命,使其对我年轻但坚韧的观众更加个性化和易于关联。因此,一致性的追求变成了为定制故事的胜利!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Dexie 的玩具现在是 Bob 企鹅 [作者提供的图像]

在我令人兴奋的实验日子里,我发现了一些智慧的结晶可以分享,以在微调模型时实现最佳结果,减少过拟合的可能性:

  • 保持训练图像中的背景多样化。这样,模型就不会将背景与对象混淆,防止在生成图像中出现不必要的背景配角。

  • 从不同角度捕捉目标对象。这有助于提供更多视觉信息,使模型能够生成具有更大角度范围的对象,从而更好地匹配场景。

  • 混合特写镜头和全身镜头。这确保了模型不会假设特定姿势是必要的,从而为生成的对象与场景的和谐提供了更多灵活性。

为了执行稳定扩散模型的精细调整,我启动了一个 SageMaker Estimator 训练作业,使用 Amazon SageMaker Python SDK 在 ml.g5.2xlarge GPU 实例上,并将训练过程定向到 S3 桶中的训练图像集合。生成的精细调整模型文件将保存在 s3_output_location 中。仅需几行代码,魔力便开始显现!

# [Optional] Override default hyperparameters with custom values
hyperparams["max_steps"] = 400
hyperparams["with_prior_preservation"] = False
hyperparams["train_text_encoder"] = False

training_job_name = name_from_base(f"stable-diffusion-{self._model_id}-transfer-learning")

# Create SageMaker Estimator instance
sd_estimator = Estimator(
    role=self._aws_role,
    image_uri=image_uri,
    source_dir=source_uri,
    model_uri=model_uri,
    entry_point="transfer_learning.py",  # Entry-point file in source_dir and present in train_source_uri.
    instance_count=self._training_instance_count,
    instance_type=self._training_instance_type,
    max_run=360000,
    hyperparameters=hyperparams,
    output_path=s3_output_location,
    base_job_name=training_job_name,
    sagemaker_session=session,
)

# Launch a SageMaker Training job by passing s3 path of the training data
sd_estimator.fit({"training": training_dataset_s3_path}, logs=True)

为准备训练集,确保它包含以下文件:

  1. 一系列名为 instance_image_x.jpg 的图像,其中 x 是从 1 到 N 的数字。在这种情况下,N 代表图像的数量,理想情况下应大于 10。

  2. 一个 dataset_info.json 文件,其中包含一个必需字段,称为 instance_prompt。该字段应提供对象的详细描述,并在对象名称前加上唯一标识符。例如,“Bob 企鹅的照片”,其中‘Bob’充当唯一标识符。通过使用该标识符,你可以引导精细调整后的模型生成标准企鹅(称为“企鹅”)或来自训练集中的企鹅(称为“Bob 企鹅”)。一些来源建议使用像 sks 或 xyz 这样的唯一名称,但我发现这样做并非必要。

dataset_info.json 文件还可以包含一个可选字段,称为 class_prompt,它提供对象的一般描述,而没有唯一标识符(例如,“一只企鹅的照片”)。此字段仅在 prior_preservation 参数设置为 True 时使用;否则,将被忽略。我将在下面的高级精细调整部分进一步讨论。

{"instance_prompt": "a photo of bob penguin",
   "class_prompt": "a photo of a penguin"
}

经过几次使用 Dexie 的玩具进行测试后,图像生成器产生了一些真正令人印象深刻的结果。它将 Dexie 的袋鼠磁性积木创作栩栩如生地呈现出来,使其跃入虚拟世界。生成器还巧妙地描绘了他心爱的淋浴乌龟玩具在水下游泳的场景,周围环绕着色彩斑斓的鱼群。图像生成器确实捕捉到了 Dexie 玩耍时最喜欢的魔力!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Dexie 的玩具栩栩如生 [AI 生成的图像]

针对精细调整的稳定扩散模型进行批量转换

由于我需要为每个漫画条生成一百多张图像,因此部署 SageMaker 端点(可以将其视为 Rest API)并一次生成一张图像并不是最有效的方法。因此,我选择对我的模型进行批量转换,向其提供包含生成图像提示的 S3 桶中的文本文件。

我会提供更多关于这个过程的细节,因为我最初对它感到困惑,我希望我的解释能为你节省一些时间。你需要为每个图像提示准备一个文本文件,文件中包含以下 JSON 内容:{“prompt”: “a photo of Bob the penguin in Antarctica”}。虽然似乎有一种方法可以使用 MultiRecord 策略将多个输入合并到一个文件中,但我未能弄清楚它的工作原理。

我遇到的另一个挑战是对我的微调模型执行批量转换。你不能使用 Estimator.transformer() 返回的转换器对象执行批量转换,这通常在我之前的项目中有效。相反,你需要先创建一个 SageMaker 模型对象,将微调模型的 S3 位置指定为 model_data。之后,你可以使用这个模型对象创建转换器对象。

def _get_model_uris(self, model_id, model_version, scope):
    # Retrieve the inference docker container uri
    image_uri = image_uris.retrieve(
        region=None,
        framework=None,  # automatically inferred from model_id
        image_scope=scope,
        model_id=model_id,
        model_version=model_version,
        instance_type=self._inference_instance_type,
    )
    # Retrieve the inference script uri. This includes scripts for model loading, inference handling etc.
    source_uri = script_uris.retrieve(
        model_id=model_id, model_version=model_version, script_scope=scope
    )
    if scope == "training":
        # Retrieve the pre-trained model tarball to further fine-tune
        model_uri = model_uris.retrieve(
            model_id=model_id, model_version=model_version, model_scope=scope
        )
    else:
        model_uri = None

    return image_uri, source_uri, model_uri

image_uri, source_uri, model_uri = self._get_model_uris(self._model_id, self._model_version, "inference")

# Get model artifact location by estimator.model_data, or give an S3 key directly
model_artifact_s3_location = f"s3://{self._bucket}/output-model/{job_id}/{training_job_name}/output/model.tar.gz"

env = {
    "MMS_MAX_RESPONSE_SIZE": "20000000",
}

# Create model from saved model artifact
sm_model = model.Model(
    model_data=model_artifact_s3_location,
    role=self._aws_role,
    entry_point="inference.py",  # entry point file in source_dir and present in deploy_source_uri
    image_uri=image_uri,
    source_dir=source_uri,
    env=env
)

transformer = sm_model.transformer(instance_count=self._inference_instance_count, instance_type=self._inference_instance_type,
                                output_path=f"s3://{self._bucket}/processing/{job_id}/output-images",
                                accept='application/json')
transformer.transform(data=f"s3://{self._bucket}/processing/{job_id}/batch_transform_input/",
                      content_type='application/json')

就这样,我的定制图像生成器已经准备好了!

高级 Stable Diffusion 模型微调

虽然这对我的漫画生成器项目并非必需,但我想提到一些涉及 max_stepsprior_reservationtrain_text_encoder 超参数的高级微调技术,以防它们对你的项目有用。

Stable Diffusion 模型的微调由于你提供的训练图像数量与基础模型使用的图像数量之间的巨大差异,非常容易过拟合。例如,你可能只提供了 10 张 Bob the penguin 的图像,而基础模型的训练集包含了数千张企鹅图像。图像数量更多会降低过拟合的可能性,并减少目标对象与其他元素之间的错误关联。

当将 prior_reservation 设置为 True 时,Stable Diffusion 使用提供的 class_prompt 生成默认数量的 x 张图像(通常为 100),并在微调过程中将这些图像与 instance_images 结合起来。或者,你可以通过将这些图像放置在 class_data_dir 子文件夹中来手动提供这些图像。根据我的经验,prior_preservation 在对 Stable Diffusion 进行微调以生成真人面孔时通常至关重要。当使用 prior_reservation 时,确保提供一个提到最合适的通用名称或与角色相似的常见对象的 class_prompt。对于 Bob the penguin,这个对象显然是一只企鹅,所以你的类提示应该是 “a photo of a penguin”。这种技术也可以用来生成两个角色之间的混合体,稍后我会讨论。

另一个对高级微调有帮助的参数是 train_text_encoder。将其设置为 True 以启用在微调过程中对文本编码器的训练。结果模型将更好地理解更复杂的提示,并更准确地生成真人面孔。

根据你具体的使用案例,不同的超参数值可能会产生更好的结果。此外,你需要调整 max_steps 参数来控制所需的微调步骤数。请记住,较大的训练集可能会导致过拟合。

文本转语音和视频生成

通过利用 Amazon Polly 的神经文本转语音(NTTS)功能,我能够为故事的每个段落创建音频叙述。音频叙述的质量非常出色,因为它听起来非常自然和像人类发声,使其成为理想的讲故事者。

为了适应年轻观众,如 Dexie,我采用了 SSML 格式,并利用了 标签将说话速度降低到正常速度的 90%,确保内容不会传递得太快,以便他们能够跟上。

self._pollyClient = boto3.Session(
                region_name=aws_region).client('polly')
ftext = f"<speak><prosody rate=\"90%\">{text}</prosody></speak>"
response = self._pollyClient.synthesize_speech(VoiceId=self._speaker,
                OutputFormat='mp3',
                Engine='neural',
                Text=ftext,
                TextType='ssml')

with open(mp3_path, 'wb') as file:
    file.write(response['AudioStream'].read())
    file.close()

在所有辛勤工作之后,我使用了 MoviePy —— 一个非常棒的 Python 框架 —— 神奇地将所有照片、音频叙述和音乐转变成一个令人惊叹的 mp4 视频。说到音乐,我让我的技术选择完美的配乐来匹配视频的氛围。怎么做的呢?好吧,我只是修改了我的故事脚本生成器,使用一些巧妙的提示从预定列表中返回一种音乐风格。这有多酷?

在故事开始时,请从以下列表中建议匹配故事的歌曲风格,并用<>标出。歌曲风格列表包括 action、calm、dramatic、epic、happy 和 touching。

一旦选择了音乐风格,下一步是从相关文件夹中随机挑选一个 MP3 曲目,该文件夹包含了一些 MP3 文件。这有助于为最终产品增添一点不可预测性和兴奋感。

控制模块

为了协调整个系统,我需要一个以 Python 脚本形式存在的控制模块,它能够无缝地运行每个模块。当然,我还需要一个计算环境来执行这个脚本。我有两个选择 — 第一个是我偏好的选择 — 使用 AWS Lambda 的无服务器架构。这涉及使用多个 AWS Lambda,配合 SQS。第一个 Lambda 作为公共 API,使用 API Gateway 作为入口点。这个 API 会接收训练图像的 URL 和故事主题文本,并对数据进行预处理,将其放入 SQS 队列中。另一个 Lambda 会从主题中提取数据并进行数据准备 —— 比如图像调整大小、创建 dataset_info.json,并触发下一个 Lambda 调用 Amazon SageMaker Jumpstart 来准备 Stable Diffusion 模型,并执行 SageMaker 训练作业以微调模型。唷,这真是一口气说完。最后,Amazon EventBridge 将作为事件总线,用于检测训练作业的完成,并触发下一个 Lambda 使用微调后的模型执行 SageMaker Batch Transform 生成图像。

然而,这个选项不可行,因为 AWS Lambda 函数的最大存储限制为 10GB。在对 SageMaker 模型执行批处理转换时,SageMaker Python SDK 会在本地 /tmp 临时下载并提取 model.tar.gzip 文件,然后将其发送到运行批处理转换的管理系统。不幸的是,我的模型压缩后为 5GB,因此 SageMaker Python SDK 抛出一个错误,说“磁盘空间不足。”对于大多数模型尺寸较小的使用场景,这将是最佳和最清洁的解决方案。

因此,我不得不选择第二个选项——AWS Batch。它运行良好,但成本稍高,因为 AWS Batch 计算实例必须在整个过程中运行——即使在对模型进行微调和执行批处理转换时,这些操作都是在 SageMaker 内的独立计算环境中执行的。我本可以将过程拆分为多个 AWS Batch 实例,并通过 Amazon EventBridge 和 SQS 将它们连接起来,就像我之前使用无服务器方法一样。但由于 AWS Batch 启动时间较长(约 5 分钟),这会给整体过程增加过多的延迟。因此,我选择了集成的 AWS Batch 选项。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Owly 系统架构

欣赏 Owly 壮丽的架构图吧!我们的冒险从通过 AWS 控制台启动 AWS Batch 开始,为其配备充满训练图像的 S3 文件夹、一个引人入胜的故事主题和一个令人愉快的角色,这些都通过 AWS Batch 环境变量提供。

# Basic settings
JOB_ID = "penguin-images" # key to S3 folder containing the training images
STORY_TOPIC = "bob the penguin who wants to travel to Europe"
STORY_CHARACTER = "bob the penguin"

# Advanced settings
TRAIN_TEXT_ENCODER = False
PRIOR_RESERVATION = False
MAX_STEPS = 400
NUM_IMAGE_VARIATIONS = 5

AWS Batch 立即启动,从 JOB_ID 指定的 S3 文件夹中检索训练图像,将其调整为 768x768,并在将其放入暂存 S3 桶之前创建 dataset_info.json 文件。

接下来,我们调用 OpenAI GPT3.5 模型 API 来编写一个引人入胜的故事,并与所选主题和角色相协调的补充歌曲风格。然后我们召唤 Amazon SageMaker JumpStart 来释放强大的 Stable Diffusion 2.1 基础模型。使用该模型,我们启动 SageMaker 训练作业,对其进行微调以适应我们精心挑选的训练图像。经过短短 30 分钟的间歇后,我们为每个故事段落制作图像提示,以文本文件的形式,然后将其放入 S3 桶中,作为图像生成盛宴的输入。Amazon SageMaker Batch Transform 被用来在短短 5 分钟内批量生成这些图像。

完成后,我们借助 Amazon Polly 为故事中的每个段落制作音频讲解,仅需 30 秒即可将其保存为 mp3 文件。然后我们从按歌曲风格分类的库中随机挑选一个 mp3 音乐文件,基于我们巧妙的故事生成器的选择。

最终的工作将生成的图像、音频讲述 mp3 文件和音乐.mp3 文件巧妙地编织成一个视频幻灯片,借助 MoviePy 完成。为了增加优雅感,我们添加了平滑的过渡和肯·伯恩斯效果。最后的杰作,完成的视频,随后被上传到输出 S3 存储桶,等待你的急切下载!

测试并添加一些增强功能

我必须说,我对结果感到相当满意!故事脚本生成器真的是超乎预期地表现出色。几乎每一个制作的故事脚本不仅写得很好,而且充满了积极的道德观,展示了大型语言模型(LLM)的令人惊叹的能力。至于图像生成,嘛,这就有点参差不齐了。

根据我之前描述的所有改进,一五分之一的故事可以直接用于最终视频。其余的四分之一,通常有一到两张图像存在常见问题。

  • 首先,我们仍然遇到不一致的角色。有时模型会生成一个与训练集中原始角色略有不同的角色,通常选择现实主义版本而不是玩具版本。但不要担心!在文本提示中添加期望的照片风格,如“一个卡通风格的海龟雷克斯在海里游泳”,有助于缓解这个问题。然而,这确实需要人工干预,因为某些角色可能需要现实主义风格。

  • 然后就是缺失身体部位的奇特情况了。偶尔,我们生成的角色会出现四肢或头部缺失的情况。哎呀!为了解决这个问题,我们在稳定扩散模型中添加了负面提示,比如“缺失四肢,缺失头部”,以鼓励生成避免这些奇特属性的图像。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

雷克斯海龟的不同风格(右下角的图像为现实主义风格,右上角的图像为混合风格,其余为玩具风格)和缺少头部(左上角的图像)[AI 生成的图像]

  • 当处理对象之间不常见的互动时,会出现奇异的图像。生成角色在特定位置的图像通常会得到令人满意的结果。然而,当涉及到描绘角色与其他对象互动时,尤其是以不寻常的方式,结果往往不尽如人意。例如,尝试描绘刺猬汤姆挤奶牛时,会产生刺猬与奶牛的奇特混合体。同时,制作汤姆·刺猬拿着花束的图像则会变成一个人同时抓着刺猬和花束。不幸的是,我尚未制定出解决这一问题的策略,这使我得出结论,这是当前图像生成技术的局限。如果你试图生成的图像中的对象或活动非常不寻常,模型缺乏先验知识,因为训练数据中从未出现过这样的场景或活动。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从“汤姆刺猬在挤奶牛”提示生成的混合体(顶部图像)是刺猬和奶牛的混合。 从“汤姆刺猬拿着花”生成的图像(底部左侧图像)则是一个人同时抱着刺猬和花束 [AI 生成的图像]

最终,为了提高故事生成的成功率,我巧妙地调整了我的故事生成器,使其每段生成三个不同的场景。此外,对于每个场景,我指示我的图像生成器创建五个图像变体。通过这种方法,我增加了从十五个可用图像中获得至少一个优质图像的可能性。拥有三种不同的提示变体也有助于生成完全独特的场景,尤其是当一个场景过于稀有或复杂以至于无法创建时。以下是我更新后的故事生成提示。

"Write me a {max_words} words story about a given character and a topic.\nPlease break the story down into " \
"seven to ten short sections with 30 maximum words per section. For each section please describe the scene in " \
"details and always include the location in one sentence within [] with the following format " \
"[a photo of character in the location], [a photo of character in front of an object], " \
"[a photo of character next to an object], [a photo of a location]. Please provide three different variations " \
"of the scene details separated by |\\nAt the start of the story please suggest song style from the following " \
"list only which matches the story and put it within <>. Song style list are action, calm, dramatic, epic, " \
"happy and touching."

唯一的额外成本是在图像生成步骤完成后进行一点手动干预,我会挑选每个场景的最佳图像,然后继续进行漫画生成过程。尽管有这一小小的不便,我现在在制作精彩漫画方面的成功率达到了 9/10!

上演时刻

随着 Owly 系统的全面组装,我决定在一个美好的星期六下午对这项技术奇迹进行测试。我从他的玩具收藏中生成了一些故事,准备利用我购买的巧妙便携式投影仪来提升 Dexie 的睡前故事体验。那天晚上,当我看到 Dexie 的脸上绽放出兴奋的光芒和眼睛睁大的神情,漫画在他的卧室墙上播放时,我知道我的努力都值得了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Dexie 正在看着他卧室墙上的漫画 [图片来源于作者]

最棒的是,现在我只需不到两分钟就能利用我已经拍摄的玩具角色照片创作一个新故事。此外,我可以无缝地将我希望他从每个故事中学到的宝贵道德融入其中,比如不与陌生人交谈、勇敢冒险或对他人友善和乐于助人。以下是这个神奇系统生成的一些令人愉快的故事。

《超级刺猬汤姆拯救他的城市免受龙的威胁》 — 由 AI 漫画生成器生成。

《勇敢的企鹅鲍勃:欧洲历险记》 — 由 AI 漫画生成器生成。

一些有趣的实验

作为一个好奇的发明家,我忍不住尝试图像生成模块,以推动 Stable Diffusion 的边界,并将两个角色合并成一个宏伟的混合体。我用科瓦兹·奥克诺特的图像对模型进行了微调,但我加了一点变化,把塞尔达设定为既独特又经典的角色名。设置prior_preservation为 True,我确保 Stable Diffusion 会“奥克诺特化”塞尔达,同时保持她的独特本质。

我巧妙地利用了适度的max_step为 400,刚好能够保留塞尔达的原始魅力,同时避免她被科瓦兹·奥克诺特的无法抗拒的魅力完全吞噬。请看塞尔达与科瓦兹的辉煌融合,合而为一!

Dexie 兴奋不已地见证了他最喜欢的两个角色在他的睡前故事中大展拳脚。他踏上了激动人心的冒险,打击外星生物并寻找隐藏的宝藏!

不幸的是,为了保护知识产权,我不能展示生成的图像。

生成型 AI,特别是大型语言模型(LLMs),将长期存在,并成为不仅仅是软件开发,还包括许多其他行业的强大工具。我在一些项目中亲身体验了 LLMs 的真正力量。就在去年,我构建了一个名为 Ellie 的机器人泰迪熊,能够移动头部并像真正的人类一样进行对话。虽然这项技术无疑强大,但重要的是要谨慎使用,以确保生成结果的安全性和质量,因为它可能是一把双刃剑。

各位,这就是全部了!希望你们觉得这个博客有趣。如果是的话,请给我多多点赞。随时在LinkedIn与我联系,或查看我在Medium 个人主页上的其他 AI 项目。敬请关注,我将在接下来的几周内分享完整的源代码!

最后,我要感谢来自 AWS 的Mike Chambers,他帮助我排查了微调后的 Stable Diffusion 模型批量转换代码的问题。

从基础构建 PCA

原文:towardsdatascience.com/building-pca-from-the-ground-up-434ac88b03ef

通过一步步的推导,超级提升你对主成分分析的理解

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 哈里森·霍夫曼

·发表于数据科学前沿 ·12 分钟阅读·2023 年 8 月 7 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

热气球。图像由作者提供。

主成分分析(PCA)是一种常用于降维的旧技术。尽管在数据科学家中这是一个广为人知的话题,但 PCA 的推导通常被忽视,留下了关于数据本质和微积分、统计学以及线性代数之间关系的宝贵见解。

在本文中,我们将通过思想实验推导 PCA,从二维开始,扩展到任意维度。随着我们逐步推进每一项推导,我们将看到看似不同的数学分支之间的和谐互动,最终达到优雅的坐标变换。这一推导将揭示 PCA 的机制,并揭示数学概念的迷人相互联系。让我们开始这段启发性的 PCA 探索之旅。

在二维中热身

作为生活在三维世界中的人类,我们通常理解二维概念,这也是本文的起点。从二维开始将简化我们的首次思想实验,并使我们更好地理解问题的本质。

理论

我们有一个数据集,长得像这样(请注意,每个特征应该被缩放到均值为 0 和方差为 1):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(1) 相关数据。图像由作者提供。

我们立即注意到这些数据位于由x1x2描述的坐标系中,这些变量是相关的。我们的目标是找到一个由数据的协方差结构决定的新坐标系。 特别是,坐标系中的第一个基向量应解释将原始数据投影到其上的大部分方差。

我们的首要任务是找到一个向量,使得当我们将原始数据投影到这个向量上时,保留最大量的方差。换句话说,理想的向量指向方差最大化的方向,正如数据所定义的那样。

这个向量可以通过它与 x 轴逆时针方向所成的角度来定义:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(2) 通过旋转向量来寻找最大方差的方向。图像由作者提供。

在上面的动画中,我们通过与 x 轴的角度来定义一个向量,我们可以看到向量在 0 到 180 度之间的每个角度指向的方向。从视觉上看,我们可以看到一个接近 45 度的θ值指向数据方差的最大方向。

为了重新阐述我们的目标,我们希望找到一个角度,使得向量指向方差最大化的方向。从数学上讲,我们希望找到一个最大化这个方程的θ

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(3) 最佳的θ将最大化这个目标函数。图像由作者提供。

在这里,N是数据中的观察次数。我们将每个观察值投影到由***[cos(θ) sin(θ)]定义的轴上,这是一个由角度θ***定义的单位向量,并对结果进行平方。

当我们改变θ时,这个方程给出了当数据投影到由θ定义的轴上的方差。让我们计算方程中的点积并重写这个表达式,使其更易于处理:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(4) 重写后的方差方程。图像由作者提供。

幸运的是,这是一个凸函数,我们可以通过计算它的导数并将其设为 0 来最大化它。我们还需要计算方差方程的二阶导数,以确定我们是否找到了最小值或最大值。***var(θ)***的一阶和二阶导数如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(5) var(θ)的一阶和二阶导数。图像由作者提供。

接下来,我们可以将一阶导数设为 0,并重排方程以隔离θ

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(6) 将 var(θ)的一阶导数设为 0 并重排。图像由作者提供。

最后,利用常见的三角恒等式和一些代数,我们得到了一个使θ最小化或最大化***var(θ)***的封闭形式解:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(7) 计算θ的方程,用于找到最大或最小方差的方向。图像由作者提供。

值得注意的是,这个方程是我们在二维空间进行 PCA 所需的全部。二阶导数将告诉我们θ是否对应于局部最小值或最大值。由于只有另一个主成分,它必须通过将θ偏移 90 度来定义。因此,两个主成分角度是:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(8) 定义二维主成分的角度。图像由作者提供。

如前所述,我们可以使用var(θ)的二阶导数来确定哪个θ属于主成分 1(最大方差的方向),哪个θ属于主成分 2(最小方差的方向)。另外,我们也可以将两个θs代入***var(θ)***中,看看哪个结果更高。

一旦我们知道哪个θ对应于每个主成分,我们就将每一个代入二维单位向量的三角函数方程([cos(θ) sin(θ)])。具体来说,我们做如下操作:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(9) 从最大化或最小化 var(θ) 的 θs 确定两个主成分。图像由作者提供。

就是这样——pc1pc2是主成分向量。通过思考主成分分析的目标,我们能够在二维空间中从零开始推导出主成分。为了验证这个结果是否正确,让我们编写一些 Python 代码来实现我们的策略。

代码

第一个函数找到从方差方程的导数中推导出的主角度之一。由于这是一个封闭形式的方程,实现起来非常简单:

import numpy as np

def find_principal_angle(x1: np.ndarray, x2: np.ndarray) -> float:
    """
    Find the angle corresponding to one of the principal components
    in two dimensions.

    Parameters
    ----------
    x1 : numpy.ndarray
        First input vector with shape (n,).
    x2 : numpy.ndarray
        Second input vector with shape (n,).

    Returns
    -------
    float
        The principal angle in radians.
    """

    cov = -2 * np.sum(x1 * x2)
    var_diff = np.sum(x2**2 - x1**2)

    return 0.5 * np.arctan(cov / var_diff)

根据方差方程的性质,find_principal_angle() 恢复一个主角度,另一个主角度则相差 90 度。为了确定 find_principal_angle() 返回的是哪个主角度,我们可以使用方差方程的二阶导数或 Hessian 矩阵:

def compute_pca_cost_hessian(x1: np.ndarray,
                             x2: np.ndarray,
                             theta: float) -> float:
    """
    Compute the Hessian of the cost function for Principal Component
    Analysis (PCA) in two dimensions.

    Parameters
    ----------
    x1 : numpy.ndarray
        First input vector with shape (n,).
    x2 : numpy.ndarray
        Second input vector with shape (n,).
    theta : float
        An angle in radians for which the cost function is evaluated.

    Returns
    -------
    float
        The Hessian of the PCA cost function evaluated at the given theta.
    """

    return np.sum(
        2 * (x2**2 - x1**2) * np.cos(2 * theta) -
        4 * x1 * x2 * np.sin(2 * theta)
    )

这个函数的逻辑直接来源于图 (5)。我们需要的最后一个函数是从 find_principal_angle()compute_pca_cost_hessian() 确定两个主成分:

def find_principal_components_2d(x1: np.ndarray, x2: np.ndarray) -> tuple:
    """
    Find the principal components of a two-dimensional dataset.

    Parameters
    ----------
    x1 : numpy.ndarray
        First input vector with shape (n,).
    x2 : numpy.ndarray
        Second input vector with shape (n,).

    Returns
    -------
    tuple
        A tuple containing the two principal components represented as
        numpy arrays.
    """

    theta0 = find_principal_angle(x1, x2)
    theta0_hessian = compute_pca_cost_hessian(x1, x2, theta0)

    if theta0_hessian > 0:
        pc1 = np.array([np.cos(theta0 + (np.pi / 2)),
                        np.sin(theta0 + (np.pi / 2))])
        pc2 = np.array([np.cos(theta0), np.sin(theta0)])
    else:
        pc1 = np.array([np.cos(theta0), np.sin(theta0)])
        pc2 = np.array([np.cos(theta0 + (np.pi / 2)),
              np.sin(theta0 + (np.pi / 2))])

    return pc1, pc2

find_principal_components_2d() 中,theta0 是其中一个主角度,theta0_hessian 是方差方程的二阶导数。因为 theta0 是方差方程的极值点,theta0_hessian 告诉我们 theta0 是最小值还是最大值。特别地,如果 theta0_hessian 为正,则 theta0 必定是最小值,对应于第二主成分。否则,如果 theta0_hessian 为负,theta0 则是最大值,对应于第一主成分。

为了验证find_principal_components_2d()是否做了我们想要的事情,让我们找到一个二维数据集的主成分,并将其与scikit-learn 中的 PCA 实现的结果进行比较。以下是代码:

import numpy as np
from sklearn.decomposition import PCA

# Generate two random correlated arrays
rng = np.random.default_rng(seed=80)
x1 = rng.normal(size=1000)
x2 = x1 + rng.normal(size=len(x1))

# Normalize the data
x1 = (x1 - x1.mean()) / x1.std()
x2 = (x2 - x2.mean()) / x2.std()

# Find the principal components using the 2D logic
pc1, pc2 = find_principal_components_2d(x1, x2)

# Find the principal components using sklearn PCA
model = PCA(n_components=2)
model.fit(np.array([x1, x2]).T)
pc1_sklearn = model.components_[0, :]
pc2_sklearn = model.components_[1, :]

print(f"Derived PC1: {pc1}")
print(f"Sklearn PC1: {pc1_sklearn} \n")
print(f"Derived PC2: {pc2}")
print(f"Sklearn PC2: {pc2_sklearn}")

"""   
Derived PC1: [0.70710678 0.70710678]
Sklearn PC1: [0.70710678 0.70710678] 

Derived PC2: [ 0.70710678 -0.70710678]
Sklearn PC2: [ 0.70710678 -0.70710678]
"""

# Visualize the results
fig, ax = plt.subplots(figsize=(10, 6))
ax.scatter(x1, x2, alpha=0.5)
ax.quiver(0, 0, pc1[0], pc1[1],  scale_units='xy', scale=1, color='red')
ax.quiver(0, 0, pc2[0], pc2[1],  scale_units='xy', scale=1, color='red', label="Custom PCs")
ax.quiver(0, 0, pc1_sklearn[0], pc1_sklearn[1],  scale_units='xy', scale=1, color='green')
ax.quiver(0, 0, pc2_sklearn[0], pc2_sklearn[1],  scale_units='xy', scale=1, color='green', label="Sklearn PCs")
ax.set_xlabel("$x_1$")
ax.set_ylabel("$x_2$")
ax.set_title("Custom 2D PCA vs Sklearn PCA")
ax.legend()

这段代码创建了二维相关数据,进行了标准化,并使用我们的自定义逻辑和scikit-learn PCA 实现找到了主成分。我们可以看到,我们得到了与 scikit-learn 完全相同的结果。得到的主成分如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(10) 我们的二维 PCA 方法与 scikit-learn 实现完全匹配。图像来源:作者。

如预期的那样,两组主成分完全重叠。然而,我们方法的问题是它无法推广到二维以外的情况。在下一节中,我们将推导出任意维度的类似结果。

推导一般结果

在二维中,我们利用方差定义、微积分和一点三角学推导出了一个封闭形式的方程来找到主成分。不幸的是,这种方法对于超过二维的数据集很快会变得不可行。为此,我们必须依赖于数学中最强大的分支——线性代数来进行计算。

理论

PCA 在更高维度的推导在许多方面类似于二维情况。根本的区别在于,我们必须利用一些线性代数工具来帮助我们考虑主成分与数据原点可能形成的所有角度。

首先,假设我们的数据集有d个特征和n个观察值。如前所述,每个特征应缩放到均值为 0 且方差为 1。我们将数据排列成如下矩阵:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(11) PCA 的数据矩阵。图像来源:作者。

如前所述,我们的目标是找到一个单位向量,p,它指向数据方差最大方向。为此,我们首先需要陈述两个有用的事实。第一个是我们数据的协方差矩阵,在更高维度中的方差的类似物,可以通过将 X 与其转置相乘找到:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(12) 计算 X 的协方差矩阵的方程。图像来源:作者。

尽管这很有用,我们并不关心数据本身的方差。我们想知道数据在投影到新轴上的方差。 为此,我们回顾一个关于标量量的方差的结果。即,当标量乘以常数时,得到的方差是该常数的平方乘以原始方差:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(13) 乘以常数的标量方差。图片由作者提供。

类似地,当我们用数据矩阵乘以一个向量(即,当我们将一个向量投影到矩阵上)时,得到的方差可以按如下方式计算:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(14) 向量投影到数据矩阵上的方差。图片由作者提供。

这给了我们定义 PCA 问题所需的一切。也就是说,我们想找到向量p(主成分),使下列方程最大化:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(15) 在d维度下,PCA 问题的陈述是找到向量p,使其在投影到数据时的方差最大。图片由作者提供。

d 维度下,PCA 问题的陈述是找到向量p,使其在投影到数据时的方差最大。我们还规定p 是一个单位向量,这使得这是一个约束优化问题。因此,就像在二维情况下一样,我们需要使用微积分。

为了解决这个问题,我们可以利用 拉格朗日乘子,它允许我们在满足指定约束的同时最小化目标函数。这个问题的拉格朗日表达式如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(16) PCA 拉格朗日表达式。图片由作者提供。

为了优化这个问题,我们对其求导并将其设为 0:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(17) PCA 拉格朗日表达式的导数。图片由作者提供。

一些重新排列给出了以下表达式——这是线性代数中一个非常熟悉的结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(18) PCA 特征方程。图片由作者提供。

这告诉我们什么?令人惊讶的是,最佳主成分 (p) 乘以数据的协方差矩阵 (S),实际上就是那个相同的 p 乘以一个标量 (λ)。换句话说,p 必须是协方差矩阵的** 特征向量**,我们可以通过计算 S 的特征值分解来找到 p。我们还观察到:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当数据投影到p上时,数据的方差等于p的特征值。图片由作者提供。

当数据投影到p上时,数据的方差等于对应于***p.***的特征值。我们还知道,S 最多有 d 个特征值/特征向量,特征向量是正交的,特征值从大到小排序。这意味着,通过对 S 进行特征值分解,我们可以恢复所有的主成分:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(20) 所有的 d 个主成分都来自于 S 的特征值分解。图片由作者提供。

这是一种统计学、微积分和线性代数的美丽交汇,给我们提供了一种优雅的方法来找到高维数据集的主成分。真是一个美丽的结果!

代码

如果我们使用 NumPy,PCA 的一般形式实际上比我们推导出的二维版本更容易编码。以下是使用三维数据集的 PCA 示例:

import numpy as np
from sklearn.decomposition import PCA

# Generate three random correlated arrays
rng = np.random.default_rng(seed=3)
x1 = rng.normal(size=1000)
x2 = x1 + rng.normal(size=len(x1))
x3 = x1 + x2 + rng.normal(size=len(x1))

# Create the data matrix (X)
X = np.array([x1, x2, x3]).T

# Normalize X
X = (X - X.mean(axis=0)) / X.std(axis=0)

# Compute the covariance matrix of X (S)
S = np.cov(X.T)

# Compute the eigenvalue decomposition of S
variances, pcs_derived = np.linalg.eig(S)

# Sort the pcs according to their variance
sorted_idx = np.argsort(variances)[::-1]
pcs_derived = pcs_derived.T[sorted_idx, :]

# Find the pcs using sklearn PCA
model = PCA(n_components=3)
model.fit(X)
pcs_sklearn = model.components_

# Compute the element-wise difference between the pcs
pc_diff = np.round(pcs_derived - pcs_sklearn, 10)

print("Derived Principal Components: \n", pcs_derived)
print("Sklearn Principal Components: \n", pcs_derived)
print("Difference: \n", pc_diff)

"""
Derived Principal Components: 
 [[-0.56530668 -0.57124206 -0.59507215]
 [-0.74248305  0.66666719  0.06537407]
 [-0.35937066 -0.47878738  0.80100897]]
Sklearn Principal Components: 
 [[-0.56530668 -0.57124206 -0.59507215]
 [-0.74248305  0.66666719  0.06537407]
 [-0.35937066 -0.47878738  0.80100897]]
Difference: 
 [[ 0\.  0\.  0.]
 [ 0\.  0\. -0.]
 [ 0\.  0\.  0.]]
""" 

正如我们所见,scikit-learn 的 PCA 实现给出的主成分正是协方差矩阵的特征向量。

最后的思考

主成分分析(PCA)是一种强大的技术,广泛应用于数据科学和机器学习中,用于减少高维数据集的维度,同时保留最重要的信息。本文探讨了 PCA 的基础原理,首先在二维情况下进行了分析,并使用线性代数将其扩展到任意维度。

在二维情况下,我们通过最大化数据投影到新轴上的方差,推导出了一个闭式解来找到主成分。我们使用了三角学和微积分来确定定义主成分的角度。随后,我们在 Python 中实现了我们的策略,并通过将结果与 scikit-learn 的 PCA 实现进行比较来验证其正确性。

在扩展到任意维度时,我们利用线性代数找到了一种通用的解决方案。通过将 PCA 表达为特征值问题,我们展示了主成分是协方差矩阵的特征向量。相应的特征值表示数据在投影到每个主成分上的方差。

正如我们在数学中常常看到的,那些看似无关的概念,经过不同数学家在不同时间段的发展,最终会汇聚成一个美丽的结果。PCA 是这种复杂交汇的一个典型例子,还有许多类似的例子值得探索!

成为会员: https://harrisonfhoffman.medium.com/membership

请给我买杯咖啡: https://www.buymeacoffee.com/HarrisonfhU

参考文献

  1. Ali Ghodsi, Lec 1: 主成分分析 — www.youtube.com/watch?v=L-pQtGm3VS8&t=3129s

  2. 独立成分分析 2www.youtube.com/watch?v=olKgmOuAvrc

在 Apache Airflow 中构建管道 - 初学者指南

原文:towardsdatascience.com/building-pipelines-in-apache-airflow-for-beginners-58f87a1512d5

一个快速简单的演示,展示如何在 Airflow 上运行 DAG

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Aashish Nair

·发表于 Towards Data Science ·阅读时间 9 分钟 ·2023 年 3 月 15 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 Kelly Sikkema 提供,Unsplash

Apache Airflow 在数据科学和数据工程领域非常受欢迎。它拥有许多功能,使用户能够以编程方式创建、管理和监控复杂的工作流。

然而,该平台的功能范围可能会无意中成为初学者的负担。新用户在浏览 Apache Airflow 的文档和教程时,可能会被新术语、工具和概念淹没。

为了提供一个更易于理解的入门介绍,我们提供了一个 Apache Airflow 演示的简化版本,该演示涉及编码和运行 Airflow 管道。

术语

熟悉以下 Airflow 术语后,将更容易跟随演示。

  1. DAG: DAG,意为有向无环图,是 Airflow 用来表示工作流的工具。DAG 由表示任务的节点和表示任务之间关系的箭头组成。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

示例 DAG(作者创建)

2. 任务: 任务是 DAG 中的一个工作单元。DAG 中的任务可以通过依赖关系连接在一起,确保按照特定顺序执行。

3. 操作符: 操作符是用于实例化 DAG 中任务的工具。Airflow 提供了大量操作符,能够执行基本的工作,如执行 Python 代码、bash 命令和 SQL 查询。

4. 元数据数据库: 元数据数据库存储用户创建和运行的 Airflow 管道的元数据。

5. Webserver: Webserver 是一个方便的用户界面,使用户能够运行、监视和调试管道。

6. 调度器: 调度器负责监控 DAG 并在其依赖关系满足时运行任务。

演示

演示的目标是使用 Python 创建一个 DAG 并在 Airflow 上运行。由于我们优先考虑简洁性,我们将创建一个仅包含两个任务的 DAG。

第一个任务运行一个名为 pull_jokes 的 Python 函数,它从 官方笑话 API 获取随机笑话并将其存储在文本文件中。

第二个任务运行一个 bash 命令,打印一句话:

echo "The random jokes have been pulled"

最后,我们希望第二个任务(即 bash 命令)在第一个任务(即 Python 函数)之后立即执行。如果我们可视化这个 DAG,它会像这样:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

演示 DAG(由作者创建)

很简单,对吧?我们开始吧。

第一部分 — 设置 Airflow

在开始创建任何 DAG 之前,我们需要在机器上设置 Airflow,这将需要使用命令行界面。

有几种方法可以安装 Apache Airflow,如 文档 中所述。在这里,我们将使用 PyPI 方式进行安装。

  1. 激活 Windows 子系统 Linux(适用于 Windows 用户)

如果你使用的是 Windows,你需要激活 Windows 子系统 Linux(WSL),这将需要先安装 Ubuntu。

要激活 WSL,请在 PowerShell 中输入 wsl 命令。

2. 创建虚拟环境

接下来,创建我们将工作的虚拟环境。输入以下命令以安装 python-venv。

sudo apt install python3-venv 

对于演示,我们将创建一个名为“airflow_venv”的虚拟环境并激活它。

python -m venv airflow_venv
source airflow_venv/bin/activate

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

激活虚拟环境(由作者创建)

3. 选择 Airflow 的主目录(可选)

重要的是要知道 Airflow 项目将存储在哪里,因为创建的工作流应存储和配置在这里。

默认情况下,Airflow 的主目录将是 ~/airflow 目录。然而,用户可以使用以下命令更改 Airflow 的主目录:

export AIRFLOW_HOME=<directory>

对于案例研究,项目将存储在 ~/airflowhome 目录中。

4. 安装 Apache Airflow

确认主目录后,使用以下命令通过 pip 安装 Apache Airflow:

pip install apache-airflow 

此安装将在主目录中创建一个具有以下结构的项目:

└── airflowhome/
    ├── airflow.cfg
    ├── logs/
    │   └── ...
    └── webserver_config.py

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Airflow 主目录(由作者创建)

名为 airflow.cfg 的文件包含了所有 Airflow 的配置。它包含一个名为 dags_folder 的参数,显示所有创建的 DAG 必须 存放的文件夹路径。

通过输入以下命令,你可以查看当前分配的目录:

vim airflow.cfg

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

代码输出(由作者创建)

如输出所示,所有用于此演示的 airflow DAG 必须位于airflowhome目录中的dags子目录中。

因此,我们将在同一目录中创建一个名为dags的文件夹,其中将包含随后创建的管道。

mkdir dags

注意:您也可以更改 airflow.cfg 文件中分配给*dags_folder*参数的路径,选择您喜欢的路径。

项目结构应更新为如下:

└── airflowhome/
    ├── airflow.cfg
    ├── logs/
    │   └── ...
    ├── dags/
    └── webserver_config.py

5. 初始化元数据数据库

接下来,使用以下命令初始化数据库:

airflow db init

初始化数据库后,airflow 主目录中现在应该有一个名为airflow.db的文件。

└── airflowhome/
    ├── airflow.cfg
    ├── airflow.db
    ├── logs/
    │   └── ...
    ├── dags/
    └── webserver_config.py

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Airflow 主目录(作者创建)

6. 创建用户。

需要一个用户账户来访问网络服务器,因此我们可以使用以下命令创建一个。

airflow users create --username <username> \ 
                     --password <password> \
                     --firstname <firstname> \
                     --lastname <lastname> \
                     --role <role> \
                     --email <email>

对于案例研究,我们将创建一个管理员账户。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

创建用户(作者创建)

一旦创建了用户账户,可以使用以下命令进行验证:

airflow users list

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

代码输出(作者创建)

7. 启动 Web 服务器和调度器

可以通过一行命令打开 Airflow web 服务器。

airflow webserver -p 8080

注意:默认端口为 8080,但您可以选择不同的端口。

在新的终端中,使用以下命令启动调度器:

airflow scheduler

9. 登录到 Web 服务器

启动 web 服务器后,在网页浏览器中访问“localhost:8080/”(或您选择的任何端口),您将被定向到登录页面,在那里输入您新创建的用户账户的登录凭据。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

登录页面(作者创建)

输入详细信息后,您将被定向到主页。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Web 服务器(作者创建)

目前,只会有 Airflow 提供的示例 DAG,但一旦创建了您的 DAG,它们也会显示在 web 服务器上。

第二部分 — 创建 DAG

既然我们已经设置好了 Airflow,我们可以使用 Python 构建 DAG。此演示中创建的 DAG 将被称为“pulling_jokes_dag”。

以下名为pull_jokes.py的 Python 脚本创建了一个 DAG 实例及其任务:

这是一个小代码片段,但有很多内容。让我们一步步详细解读。

  1. 创建 DAG 实例

首先,我们创建一个 DAG 实例,在其中确定 DAG 的配置。

一个 DAG 实例必须为两个参数分配值:dag_idstart_datedag_id参数是 DAG 的唯一标识符,而start_date参数是 DAG 计划开始的日期。

为了简化操作,我们只会指定几个参数。schedule 参数定义 DAG 应运行的规则。end_date 参数表示 DAG 运行应停止的时间。catchup 参数表示调度程序是否应为自上次数据间隔以来未运行的任何数据间隔启动 DAG 运行。tag 参数为 DAG 分配标签,这将使其在 UI 中更易于找到。

对于那些对 DAG 实例所有可用参数感兴趣的人,可以访问 Airflow 的 文档

2. 创建第一个任务

创建 DAG 实例后,我们可以创建第一个任务,该任务运行 pull_jokes 函数,使用 Python Operator

task_id 参数是任务的唯一标识符,而 python_callable 参数包含应执行的函数。

3. 创建第二个任务

接下来,我们创建第二个任务,该任务执行 bash 命令,使用Bash Operator

再次地,task_id 参数是任务的唯一标识符,而 bash_command 参数包含应执行的 bash 脚本。

4. 建立依赖关系

创建任务后,需要设置依赖关系以确定任务的执行顺序。

我们可以使用 >> 操作符将任务 2 设置为在任务 1 之后 执行。

第三部分 — 在 Airflow 上运行 DAG

一旦创建了 DAG 的 Python 脚本,下一步是让 DAG 在 Airflow 上运行。

提醒一下,DAG 脚本 必须 放置在 airflow.cfg 文件中指定的位置。用于此案例研究的 pull_jokes.py 文件应位于此位置。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

DAGs 目录(作者创建)

pull_jokes.py 文件移动到正确的位置后,项目目录结构应如下所示:

└── airflowhome/
    ├── airflow.cfg
    ├── airflow.db
    ├── dags/
    │   └── pull_jokes.py
    ├── logs/
    │   └── ...
    └── webserver_config.py

现在,Airflow 应该可以访问 DAG。要确认这一点,只需输入以下命令:

airflow dags list

我们现在应该能够在 Web 服务器中查看新添加的 DAG。使用以下命令启动 Web 服务器:

airflow webserver -p 8080

在一个单独的终端中,启动调度程序

airflow scheduler

你应该能够在 UI 中看到名为“pulling_jokes_dag”的 DAG。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Web 服务器中的 DAG(作者创建)

专业提示:虽然这是可选的,但最好为你的 DAG 分配标签。这将使其在 Web 服务器中更易于查找。

从技术上讲,DAG 可以仅通过命令行界面进行管理,但 Web 服务器中的功能使得访问和监控创建的管道变得更加容易。

一些功能包括但不限于:

  • DAG 以网格形式的详细说明:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

网格视图(作者创建)

  • DAG 的图形形式分析

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图形视图(作者创建)

  • DAG 的底层 Python 代码

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

代码(作者创建)

  • 审计日志

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

审计日志(作者创建)

一旦 Airflow 访问到 DAG,它将根据提供的时间表执行 DAG 运行。

也可以通过点击网页服务器右上角的“播放按钮”手动运行 DAG。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

运行 DAG(作者创建)

或者,可以使用 CLI 手动触发 DAG:

airflow dags trigger <dag_id>

就这样,我们的 DAG 在 Airflow 上运行起来了!

结论

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由Prateek Katyal提供,来源于Unsplash

希望这对那些希望亲身体验 Apache Airflow 的人来说是一个有用的入门指南。

这个演示绝不是 Airflow 功能和能力的全面展示(我们只是初步了解),但它应该能帮助人们入门,这通常是最困难的部分。

在构建并运行第一个 DAG 后,你将建立一个坚实的基础,这将使你更容易设计和运行更复杂的工作流,体验更加顺畅。

祝你在数据科学的努力中好运!

使用深度学习构建强大的推荐系统

原文:towardsdatascience.com/building-powerful-recommender-systems-with-deep-learning-d8a919c52119

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者插图

使用 PyTorch 库 TorchRec 的逐步实现

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Lina Faik

·发布在 Towards Data Science ·阅读时长 7 分钟·2023 年 7 月 3 日

在正确的时间向客户推荐合适的产品是各行各业普遍面临的挑战。例如,银行家不断寻找向现有或潜在客户推荐高度相关服务的机会。零售商努力推荐符合客户口味的吸引人产品。类似地,社交网络旨在构建引人入胜的动态,以促进用户采纳。

尽管这是一个被广泛研究的用例,但由于问题的独特性,取得令人满意的性能结果仍然困难重重。主要原因包括存在大量分类数据,通常会导致稀缺问题,以及用例的计算方面,带来扩展性问题。直到最近,推荐模型才开始利用神经网络。

在这种背景下,Meta 开发并公开了一个深度学习推荐模型(DRLM)。该模型因结合了协同过滤和预测分析的原理,并适用于大规模生产而特别出色。

目标

本文的目标是通过使用 PyTorch 库 TorchRec 的逐步实现,帮助你有效解决自己的推荐系统问题。

阅读本文后,你将理解:

  1. DLRM 模型如何工作?

  2. DLRM 模型有什么独特之处,使其强大而具有可扩展性?

  3. 你如何从头到尾实现自己的推荐系统?

本文需要对推荐系统问题有一般性的了解,并熟悉 pytorch 库。本文所述的实验使用了库 TorchRec 和 PyTorch。你可以在 GitHub 上找到代码 这里

[## GitHub - linafaik08/recommender_systems_dlrm

通过在 GitHub 上创建账户来为 linafaik08/recommender_systems_dlrm 的开发做出贡献。

github.co](https://github.com/linafaik08/recommender_systems_dlrm?source=post_page-----d8a919c52119--------------------------------)

1. 解码 DLRM 模型

让我们首先深入了解 DLRM 模型的复杂性,并探索其基本原理和机制。

1.1. 模型设计概述

为了提供一个更具体的例子,我们来考虑一个在线零售商希望为每个访问其网站的客户创建个性化推荐的场景。

为了实现这一点,零售商可以训练一个模型,该模型预测客户购买特定产品的概率。该模型根据各种因素为每个客户的每个产品分配一个分数。推荐流通过对这些分数进行排序来构建。

在这种情况下,模型可以从涵盖每个客户和产品一系列信息的历史数据中学习。这包括诸如客户年龄和产品价格等数值变量,以及产品类型、颜色等类别特征。

DLRM 模型的优势在于:它具有利用数值变量和类别变量的非凡能力,即使在处理大量独特类别时也是如此。这使得模型能够全面分析和理解特征之间的复杂关系。要理解原因,我们来看一下图 1 中的架构模型。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1 — DLRM 模型架构,由作者绘制,灵感来自 [5]

类别特征

DLRM 为每个类别特征学习一个嵌入表,并使用这些表将这些变量映射到稠密表示。因此,每个类别特征都表示为相同长度的向量。

数值特征

DLRM 通过一个称为底部 MLP 的多层感知器(MLP)处理数值特征。该 MLP 的输出维度与之前的嵌入向量相同。

成对交互

DLRM 计算所有嵌入向量与处理过的数值特征之间的点积。这使得模型能够包括二阶特征交互。

连接与最终输出

DLRM 将这些点积与处理过的数值特征进行拼接,并将结果输入到另一个 MLP 中,称为顶部 MLP。最终的概率通过将此 MLP 的输出传递到 sigmoid 函数获得。

1.2. 模型实现

尽管模型在理论上具有良好的潜力,但其实际实施仍然存在计算难题。

通常,推荐系统涉及处理大量数据。特别是使用 DLRM 模型会引入非常多的参数,超过了常见深度学习模型。因此,这增加了其实现所需的计算需求。

  1. DLRM 中大多数参数可归因于嵌入,因为它们由多个表组成,每个表都需要大量内存。这使得 DLRM 在内存容量和带宽方面都具有较高的计算需求。

  2. 尽管 MLP 参数的内存占用较小,但仍然需要大量计算资源。

为了缓解内存瓶颈,DLRM 依赖于一种独特的模型并行(用于嵌入)和数据并行(用于 MLP)的组合。

2. 从概念到实现:构建自定义推荐系统的逐步指南

本节提供了从头到尾实现自己推荐系统的详细逐步指南。

2.1. 数据转换和批处理构建

第一步是将数据转换为张量并将其组织成批次,以输入到模型中。

为了说明这个过程,让我们以这个数据框为例。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于稀疏特征,我们需要将值拼接成一个向量并计算长度。这可以使用 KeyedJaggedTensor.from_lengths_sync 函数来完成,该函数接受两个元素作为输入。以下是 Python 脚本的一个示例:

values = sample[cols_sparse].sum(axis=0).sum(axis=0)
values = torch.tensor(values).to(device)
# values = tensor([1, 0, 2, 0, 2, 2, 0, 2, 0, 1, 0, 1, 2, 0], device='cuda:0')

lengths = torch.tensor(
    pd.concat([sample[feat].apply(lambda x: len(x)) for feat in cols_sparse],
              axis=0).values,
    dtype=torch.int32
).to(self.device)
# lengths = tensor([1, 1, 1, 1, 1, 2, 3, 2, 2, 0], device='cuda:0', dtype=torch.int32)

sparse_features = KeyedJaggedTensor.from_lengths_sync(
  keys=cols_sparse,
  values=values,
  lengths=lengths
)

对于密集特征和标签,过程更为简单。这是 Python 脚本的一个示例:

dense_features = torch.tensor(sample[cols_dense].values, dtype=torch.float32).to(device)
labels = torch.tensor(sample[col_label].values, dtype=torch.int32).to(device)

通过使用前一步骤的输出,可以构建一个批次。这是 Python 脚本的一个示例:

batch = Batch(
  dense_features=dense_features,
  sparse_features=sparse_features,
  labels=labels,
).to(device)

要获取更全面的实现,可以参考 GitHub batch.py 文件及相关 GitHub 仓库

2.2. 模型初始化和优化设置

下一步涉及初始化模型,如以下 Python 代码所示:

# Initialize the model and set up optimization

# Define the dimensionality of the embeddings used in the model
embedding_dim = 10

# Calculate the number of embeddings per feature
num_embeddings_per_feature = {c: len(v) for c, v in map_sparse.items()}

# Define the layer sizes for the dense architecture
dense_arch_layer_sizes = [512, 256, embedding_dim]

# Define the layer sizes for the overall architecture
over_arch_layer_sizes = [512, 512, 256, 1]

# Specify whether to use Adagrad optimizer or SGD optimizer
adagrad = False

# Set the epsilon value for Adagrad optimizer
eps = 1e-8

# Set the learning rate for optimization
learning_rate = 0.01

# Create a list of EmbeddingBagConfig objects for each sparse feature
eb_configs = [
    EmbeddingBagConfig(
        name=f"t_{feature_name}",
        embedding_dim=embedding_dim,
        num_embeddings=num_embeddings_per_feature[feature_name + '_enc'],
        feature_names=[feature_name + '_enc'],
    )
    for feature_idx, feature_name in enumerate(cols_sparse)
]

# Initialize the DLRM model with the embedding bag collection and architecture specifications
dlrm_model = DLRM(
    embedding_bag_collection=EmbeddingBagCollection(
        tables=eb_configs, device=device
    ),
    dense_in_features=len(cols_dense),
    dense_arch_layer_sizes=dense_arch_layer_sizes,
    over_arch_layer_sizes=over_arch_layer_sizes,
    dense_device=device,
)

# Create a DLRMTrain instance for handling training operations
train_model = DLRMTrain(dlrm_model).to(device)

# Choose the appropriate optimizer class for the embedding parameters
embedding_optimizer = torch.optim.Adagrad if adagrad else torch.optim.SGD

# Set the optimizer keyword arguments
optimizer_kwargs = {"lr": learning_rate}
if adagrad:
    optimizer_kwargs["eps"] = eps

# Apply the optimizer to the sparse architecture parameters
apply_optimizer_in_backward(
    optimizer_class=embedding_optimizer,
    params=train_model.model.sparse_arch.parameters(),
    optimizer_kwargs=optimizer_kwargs,
)

# Initialize the dense optimizer with the appropriate parameters
dense_optimizer = KeyedOptimizerWrapper(
    dict(in_backward_optimizer_filter(train_model.named_parameters())),
    optimizer_with_params(adagrad, learning_rate, eps),
)

# Create a CombinedOptimizer instance to handle optimization
optimizer = CombinedOptimizer([dense_optimizer])

然后可以使用以下代码训练和评估模型:

loss, (loss2, logits, labels) = train_model(batch)

要获取更全面的实现,可以参考 GitHub model.py 文件及相关 GitHub 仓库

关键要点

✔ DLRM 模型提供了一种有效结合数值和类别特征的引人注目的方法,使用嵌入技术,使得模型能够捕捉复杂的模式和关系。

✔ 尽管其架构需要相当的计算资源,但其实现结合了模型并行和数据并行的独特组合,使得模型在生产环境中具有可扩展性。

✔ 然而,由于数据的有限性,该模型的性能尚未在各种真实世界的数据集上进行广泛测试。这引发了对其在实际场景中有效性的担忧。

✔ 此外,该模型需要调整大量参数,这进一步使得过程复杂化。

✔ 考虑到这一点,像 LGBM 这样更简单的模型可能提供类似的性能,并且具有更简单的实现、调优和长期维护,而不会有相同的计算开销。

参考文献

[1] M Naumov 等,用于个性化和推荐系统的深度学习推荐模型,2019 年 5 月

[2] Facebook 团队的 DLRM 模型初步实现的 Github 仓库,开放源代码

[3] DLRM:一种先进的开源深度学习推荐模型,Meta AI 博客,2019 年 7 月

[4] 现代生产推荐系统的 Pytorch 库,torchec

[5] Vinh Nguyen, Tomasz Grel 和 Mengdi Huang,优化 NVIDIA GPU 上的深度学习推荐模型,2020 年 6 月

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值