TowardsDataScience-博客中文翻译-2022-十二-

TowardsDataScience 博客中文翻译 2022(十二)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

使用 PyTorch 构建一个简单的神经网络

原文:https://towardsdatascience.com/build-a-simple-neural-network-using-pytorch-38c55158028d

使用 PyTorch 构建您的第一个人工智能模型的快速简单的步骤

UnsplashMichal Parzuchowski 拍摄

之前的帖子中,我们讨论了如何使用 NumPy 制作一个简单的神经网络。在本帖中,我们将讨论如何使用内置 PyTorch 函数制作一个简单的神经网络,而不是自己编写每个函数。

PyTorch 是由脸书人工智能实验室开发和维护的用于深度学习的开源 Python 库。PyTorch 使用张量(Torch。张量)来存储和操作数字的矩形数组。张量类似于 NumPy 数组,但它们也可以在 GPU 中运行。torch.nn 包可用于构建神经网络。我们将创建一个具有单个隐藏层和单个输出单元的神经网络。

  1. 导入库

PyTorch 的安装指南可以在 PyTorch 的官方网站上找到。首先,我们需要导入 PyTorch 库。

**import** torch
**import** torch.nn **as** nn

2.数据准备

现在,我们将定义变量,如输入大小、隐藏单元、输出大小、批量大小和学习速率。

n_input, n_hidden, n_out, batch_size, learning_rate = 10, 15, 1, 100, 0.01

我们现在将如下随机初始化虚拟输入和输出目标数据(或张量):

data_x = torch.randn(batch_size, n_input)
data_y = (torch.rand(size=(batch_size, 1)) < 0.5).float()

我们用 100 个数据样本初始化输入数据,每个样本有 10 个特征,并分别用 100 个数据点初始化输出数据。

print(data_x.size())
print(data_y.size())

3。定义神经网络模型

使用内置函数,我们将创建带有输出 sigmoid 层的简单序列模型,如下所示:

model = nn.Sequential(nn.Linear(n_input, n_hidden),
                      nn.ReLU(),
                      nn.Linear(n_hidden, n_out),
                      nn.Sigmoid())
print(model)

接下来,我们将为梯度下降定义损失函数和优化器。在 nn 包下,有几种不同的损失函数。这里我们将使用nn.MSELoss作为模型的损失函数,计算输入和目标之间的均方误差。同样,[torch.optim](https://pytorch.org/docs/stable/optim.html#module-torch.optim)包提供了各种优化算法。我们将使用随机梯度下降(SGD)优化器。

loss_function = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

4。训练循环

这里,我们将通过以下步骤定义训练循环:

  • 正向传播—计算预测的 y 并计算当前损耗
  • 反向传播——在每个时期之后,我们在开始反向传播之前将梯度设置为零
  • 梯度下降—最后,我们将通过调用 optimizer.step() 函数来更新模型参数
losses = []
**for** epoch **in** range(5000):
    pred_y = model(data_x)
    loss = loss_function(pred_y, data_y)
    losses.append(loss.item())

    model.zero_grad()
    loss.backward()

    optimizer.step()

5。输出

我们可以绘制损失图,并查看模型在每个时期的训练情况。

**import** matplotlib.pyplot **as** plt
plt.plot(losses)
plt.ylabel(**'loss'**)
plt.xlabel(**'epoch'**)
plt.title(**"Learning rate %f"**%(learning_rate))
plt.show()

我们可以看到,损耗在每个时期都在减少,这表明参数正在被学习。

在本教程中,您学习了在 PyTorch 中逐步开发简单神经网络模型的方法。具体来说,您学习了如何初始化随机数据、定义神经网络模型并训练它们。使用 PyTorch 的主要优势有两方面:

  1. 在图形处理单元(GPU)中使用张量来提供操作类似数字的数组的能力。
  2. 基于磁带自动微分系统的深度神经网络的可用性。

恭喜你用 PyTorch 构建并训练了你的第一个神经网络!

成为 这里 的媒介会员,支持独立写作,每月 5 美元,获得媒介上每个故事的全部权限。

用 GPT-3 和 Dash 构建一个简单的 Web 应用程序,在 10 分钟内将自然语言转换成源代码

原文:https://towardsdatascience.com/build-a-simple-web-application-that-converts-natural-language-into-source-code-in-10-minutes-7172df51b0aa

图片经由iStockphoto.com授权提供萨德

1.欢迎来到变形金刚的时代

未来几年,变形金刚可能会深刻改变应用程序的开发方式。在不久的将来,大多数由开发人员产生的低价值代码可能由人工智能和变形金刚产生。

一个转换器是一个深度学习模型,依靠自关注机制,使用序列对齐递归神经网络来计算表示而没有

最*,OpenAI 发布了其生成式预训练变压器的最大模型,称为 GPT-3 ,具有大约 1750 亿个机器学习参数的巨大容量。它有一种非凡的能力,可以利用深度学习,生成人类可以理解的文本,可以是故事、诗歌,也可以是源代码。

该模型的一个主要优点是,它可以与少量发射零发射学习一起使用,这大大缩短和简化了事情,不再需要长时间的数据准备和训练阶段。

本文主要关注的是源代码生成,其中一些额外的内容展示了强大的文本生成能力。它附带了用于构建这个 playground web 应用程序的完整 Python 源代码。

截至今天, OpenAI GPT-3 达芬奇 API 仍处于测试版,因此代码生成功能可能会在未来几个月取得一些进展。

2.web 应用程序功能概述

通过本文,您将能够构建并运行一个简单的 web 应用程序,该应用程序只需几行 Python 代码,就能实现基于 GPT-3 的强大代码生成功能。基本上,该应用程序是一个游乐场,允许用户用自然语言键入一个简单的请求,然后查看生成的源代码。该应用程序与管理代码生成或文本完成的 GPT-3 API 集成在一起。

如果可以提出以下问题并获得生成的代码,这可能是每个开发人员的梦想:

“请生成一个 Python 代码,使用随机森林算法,根据年龄、职位、经验等标准预测工资”

或者如下:“请生成一个名为 VIPPerson 的 Java 类,用来表示一个具有姓名、年龄和性别属性的人”。

为了帮助用户,已经准备了典型请求的预设列表。每一个都可以修改,所以用户可以点击一个按钮来生成代码并看到结果。

使用的技术堆栈如下:

  • 计算机编程语言
  • 破折号(用于 Python web 应用程序)
  • GPT-3 API (OpenAI)

以下是您将能够在接下来的部分中构建的应用程序的一些功能。

用例非常简单:用户可以从下拉菜单中选择一个请求类型的预置。然后,用自然语言编写的标准请求填充文本区域字段。用户可以修改该请求。完成后,用户可以点击生成按钮,相应的源代码就会生成并显示出来。

代码生成:生成一个 SQL 查询

下面是用自然语言描述字段和表格以及数据选择的简单请求。结果是一个符合 PostgreSQL 语法的 SQL 查询。

用户可以在文本区域字段中更改提议的请求,使其适应自己的需要。

来源:作者

代码生成:创建一个具有特定阈值的 AWS CloudWatch 警报

你可能知道,AWS Cloud 可以完全用代码来配置。这里使用了一个名为 Boto 的 SDK 来生成 CloudWatch 警报,当 CPU 超过 70%时触发该警报。

来源:作者

代码生成:使用随机森林算法根据年龄、职位、经验等标准预测工资

这个请求允许生成一个完整的代码来根据几个标准预测工资。结果是使用 RandomForestRegressor 的 Python 代码实现了这一点。还使用 Pyplot 库绘制了一个图形。

来源:作者

代码生成:生成一个 Java 类,用姓名、年龄和性别属性表示一个人

这是一个用很少的属性生成的非常基本的 Java 类。事实上,描述以 Java 注释和类名开始,这有助于 GPT-3 引擎更准确地完成代码。

来源:作者

代码生成:开发者任务列表

该请求可以采取任务列表的形式。默认生成的语言是 Python,但是也可以指定将其转换成任何其他通用语言。

来源:作者

文本处理:总结一篇文本

接下来的两个预置展示了一些强大的文本生成功能。这一个允许总结一篇文章。可以调整代码中的一些参数来确定结果有多短。

来源:作者

文本处理:为一个 10 岁的孩子改写文本

另一个强大的文本生成功能。该请求要求 GPT-3 引擎重新表述文本,以便 10 岁的孩子能够理解。

来源:作者

3.快速启动

下面将允许您编辑和运行我的 GitLab 上的代码。在这之前,你需要在 OpenAI 注册来获得你的 API 密匙。

  • OpenAI 网站注册获取你的 API 密匙
  • 去我的 GitLab 项目命名为 GPT-三短跑-操场
  • 使用 Google Colab 编辑 Jupyter Python 笔记本
  • 用您的 api 密钥替换代码中的 openai.api_key
  • 在“执行”菜单中选择“全部运行”
  • WebApp 应该在几秒钟后启动。单击页面底部生成的 URL 以显示它

应用程序应该如下所示:

来源:作者

4.理解代码

4.1。GPT 3 号初始化

代码的第一部分安装 OpenAI GPT-3 依赖项。API 密钥需要替换为您在他们的网站上注册时收到的密钥。

来源:作者

4.2。预置

这个应用程序用自然语言提供了 10 个预设的请求。8 个用于代码源生成,2 个与文本完成相关。

现在让我们来描述生成 SQL 查询的第一个预置。基本上,下面的函数接收一个自然语言的请求,用特定的参数调用 GPT-3 API,然后返回将显示在屏幕上的结果。(这里是一个 SQL 查询)其他预设是类似的。

此预置使用以下参数:

  • 引擎:GPT-3 引擎,或模型,将生成完成。一些引擎适用于自然语言任务,另一些则专门用于代码。code-davinci-001 表示将使用最先进的代码生成引擎。您可以在此找到更多信息
  • 提示:要用自然语言完成的查询。即###生成一个 SQL 查询,列出一家公司年龄在 20 到 30 岁之间的雇员。(这是一个非常简单的问题,但是预置本身要复杂得多)
  • 温度:温度控制答案的随机性。0.0 是最具确定性和重复性的值,仍然是代码生成的最佳选择。
  • max_tokens :生成的最大令牌数
  • top_p :通过细胞核取样控制多样性。0.5 表示考虑了所有可能的加权选项。将其设置为 1.0 可能会为代码生成提供更好的结果。
  • 频率 _ 惩罚:介于-2.0 和 2.0 之间的数字。正值会根据新单词在文本中的出现频率对其进行惩罚,从而降低模型逐字重复同一行的可能性。根据我们的需要,该参数设置为 0.0。
  • presence_penality :介于-2.0 和 2.0 之间的数字。正值根据新单词是否出现在文本中来惩罚它们,增加了模型谈论新话题的可能性。根据我们的需要,该参数设置为 0.0。
  • 停止:最多 4 个序列,API 将在这些序列中停止生成更多的令牌。返回的文本将不包含停止序列。在此预设中,停止序列被设置为“#”、“”。

来源:作者

响应(生成的代码或完整的文本)被返回给调用者。

每个预设都有自己的参数设置。该代码为每个预置复制,以确保更好的清晰度和参数解耦。

4.3。操场网络应用

这个 web 应用程序基于 Dash 框架,它允许您用 Python 构建和运行 web 应用程序。更具体地说, Jupyter-dash library 的优势在于可以直接从你的 Jupyter Colab 笔记本上运行网络应用。

Dash 文档可以在这里找到。

4.3.1。依赖项导入

此部分导入所有 Dash 依赖项。请注意,dash 的 2.0.0 版本是由于上一版本的 bug 而导入的。这个问题可能已经解决了,所以请不要犹豫,在将来删除对 2.0.0 版本的引用。

Dash 提供两种类型的组件:

  • 核心(内置)组件
  • HTML 组件

输入输出状态依赖关系将允许我们管理由显示字段上的事件触发的回调。

来源:作者

4.3.2。操场网页

在本节中,构建了一个简单的 web 页面布局,主要包含一个标题和 4 个元素:

  • 下拉预设:10 个预设列表的下拉字段
  • textarea-query :默认情况下,根据选择的预置请求设置的文本区域字段。一旦选择了预置,用户将能够改变提议的请求。
  • 按钮-生成:一个 HTML 按钮,将触发对 GPT-3 API 的调用并获得响应
  • div-output-result2 :一个预先格式化的 HTML 输出字段,用于显示可能是生成的代码或完整文本的结果。

来源:作者

4.3.3。预设下拉回调

当用户从下拉字段中选择一个预设时,调用这部分代码。基本上,该函数从下拉预设字段接收预设 ID 作为输入,并向文本区域查询字段返回一个预先格式化的请求作为输出。

第二个函数返回一个名为 query 的字段,这是一个根据所选预设 ID 预先格式化的请求。

同样,目的是尽可能地简化代码,这样读者可以更好地理解它,但是我相信,一旦您理解了逻辑,您将能够应用一些代码最佳实践。

来源:作者

4.3.4.“生成按钮回调

当用户按下“生成按钮时,这部分代码被调用。基本上,要调用的预置功能是用预置 ID预置号动态构建的。然后,使用来自文本区域字段的请求和为此预设设置的特定参数来调用 GPT-3 API。最后,生成的代码或完整的文本会显示在特定的输出字段中。( div 输出结果 2 )

请注意,在这次回调中还有 2 个额外的状态。这意味着如果 textarea-querydropdown-preset 值发生变化,将不会触发该回调。但是它们的值将被传递给这个回调函数。

来源:作者

5.最后一句话

谢谢你把我的文章看完,希望你喜欢!

如您所见,GPT 非常有前途,只需几行 Python 代码就可以轻松实现。短跑可能有助于达到 10 分钟的目标。

你可以在我的 GitLab 上找到完整的源代码: GPT-3-Dash-Playground

使用 Streamlit 构建歌曲推荐系统并在 Heroku 上部署

原文:https://towardsdatascience.com/build-a-song-recommendation-system-using-streamlit-and-deploy-on-heroku-375a57ce5e85

了解如何使用 Spotify 音乐数据集的 K *邻构建一个简单的歌曲推荐引擎

韦斯·希克斯在 Unsplash 上的照片

介绍

像你我这样的音乐爱好者可能会不断渴望迎合我们个人音乐品味的新歌。无论是最*令人兴奋的 hip-pop,充满活力和活力的 k-pop,还是轻松柔和的渐进式爵士乐,我们都可以从歌曲推荐系统中受益,该系统可以推荐我们喜欢的新歌。

由于音乐数据的可用性和深度学习的进步,我们能够建立一个简单的歌曲推荐系统,可以推荐与我们个人偏好密切匹配的歌曲。这些偏好或对我们推荐系统的输入包括音乐流派、发行年份范围和几种不同的音频特征,如能量、乐器性、音质等。在本文中,我们将用Streamlit构建这个推荐引擎,用Scikit-learn构建 k-*邻机器学习模型,并用Heroku部署我们的网站。

为了了解我们正在建设的东西,你可以在这里查看最终的应用程序:song-recommendation-streamlit.herokuapp.com

资料组

在开始构建我们的应用程序之前,我们需要一个音乐数据集。对于我们的数据集,我们将使用来自 Kaggle 的 Spotify 和 Genius Track 数据集。该数据集包含使用其 API 从 Spotify *台收集的数千张专辑、艺术家和歌曲的信息。此外,数据集还包含歌曲的低级音频特征,以及它们的歌词。

数据集统计

数据集预处理

我们的数据预处理的目标是我们想要一个联合数据集,该数据集由每首歌曲及其各自的流派信息、发行年份和音频特征组成,因为这些将是我们对系统的输入。目前,数据集主要分为三个csv 文件:spotify_artists.csvspotify_albums.csvspotify_tracks.csvspotify_artists.csv包含每个艺术家的流派信息,spotify_albums.csv包含每个专辑的发行日期,而spotify_tracks.csv包含每首歌曲的音频特征。

为了组合这三个数据集,我们可以从使用 Pandas 加载这三个数据集开始。

读出数据

这是三个数据集的样子:

Spotify 专辑数据(75511 行)

Spotify 艺术家数据(56129 行)

Spotify 跟踪数据(101939 行)

现在,我们可以用tracks数据连接albumsartists。我们需要将专辑发行年份和艺术家流派信息与曲目数据结合起来。

## join artist genre information and album release date with track dataset
# drop irrelevant columns
# get only tracks after 1990
def join_genre_and_date(artist_df, album_df, track_df):
    album = album_df.rename(columns={'id':"album_id"}).set_index('album_id')
    artist = artist_df.rename(columns={'id':"artists_id",'name':"artists_name"}).set_index('artists_id')
    track = track_df.set_index('album_id').join(album['release_date'], on='album_id' )
    track.artists_id = track.artists_id.apply(lambda x: x[2:-2])
    track = track.set_index('artists_id').join(artist[['artists_name','genres']], on='artists_id' )
    track.reset_index(drop=False, inplace=True)
    track['release_year'] = pd.to_datetime(track.release_date).dt.year
    track.drop(columns = ['Unnamed: 0','country','track_name_prev','track_number','type'], inplace = True)

    return track[track.release_year >= 1990]

请注意,我们还从我们的曲目数据帧中删除了不相关的列,只保留了最*的歌曲(1990 年以后出版的歌曲),以保持我们的数据集较小,这将允许我们在构建应用程序时加快加载和处理时间。

我们可以通过只包含属于我们选择的特定流派的歌曲来进一步缩小数据集的大小。

def get_filtered_track_df(df, genres_to_include):
    df['genres'] = df.genres.apply(lambda x: [i[1:-1] for i in str(x)[1:-1].split(", ")])
    df_exploded = df.explode("genres")[df.explode("genres")["genres"].isin(genres_to_include)]
    df_exploded.loc[df_exploded["genres"]=="korean pop", "genres"] = "k-pop"
    df_exploded_indices = list(df_exploded.index.unique())
    df = df[df.index.isin(df_exploded_indices)]
    df = df.reset_index(drop=True)
    return df

get_filtered_track_df返回的 dataframe 将删除genres_to_include中不属于任何流派的歌曲。然后,我们运行以下程序:

genres_to_include = genres = ['dance pop', 'electronic', 'electropop', 'hip hop', 'jazz', 'k-pop', 'latin', 'pop', 'pop rap', 'r&b', 'rock']
track_with_year_and_genre = join_genre_and_date(artists_data, albums_data, tracks_data)
filtered_track_df = get_filtered_track_df(track_with_year_and_genre, genres_to_include)

之后,我们对稍后将使用的uri列做一些预处理,并进一步删除不相关的列:

filtered_track_df["uri"] = filtered_track_df["uri"].str.replace("spotify:track:", "")
filtered_track_df = filtered_track_df.drop(columns=['analysis_url', 'available_markets'])

预处理后的数据如下所示:

预处理数据集

最后,我们将其保存到一个csv文件中:

filtered_track_df.to_csv("filtered_track_df.csv", index=False)

主应用程序

现在我们有了预处理过的数据集,我们可以开始构建我们的主应用程序了。我们将使用 Streamlit,这是一个 Python web 框架,用于构建机器学习和数据科学的 web 应用程序。如果这是你第一次听说它,请随意查看它的文档!

安装库

开始构建我们的应用程序的第一步是安装库,如果你还没有这样做的话。

pip install streamlit
pip install pandas
pip install plotly
pip install scikit-learn

除了 Streamlit,我们还将使用 Pandas 来加载我们的数据,plotly 用于可视化,scikit-learn 用于实现我们的机器学习模型。

在我们的环境中安装了库之后,我们可以创建一个名为app.py的文件,并开始编写 Streamlit 代码。

首先,我们首先导入我们需要的库。

import streamlit as st
st.set_page_config(page_title="Song Recommendation", layout="wide")import pandas as pd
from sklearn.neighbors import NearestNeighbors
import plotly.express as px
import streamlit.components.v1 as components

请注意,我们在导入 Streamlit 后设置页面配置,以便利用整个布局空间。

加载数据

导入所需的库后,我们编写一个函数来加载之前保存的预处理过的.csv数据。我们还使用 Streamlit 内置函数@st.cache保存缓存以加快加载速度。

[@st](http://twitter.com/st).cache(allow_output_mutation=True)
def load_data():
    df = pd.read_csv("data/filtered_track_df.csv")
    df['genres'] = df.genres.apply(lambda x: [i[1:-1] for i in str(x)[1:-1].split(", ")])
    exploded_track_df = df.explode("genres")
    return exploded_track_df

然后,我们定义用户可以从中选择的风格列表,以及用户可以定制的音频特性列表。请注意,这里使用的流派列表与我们预处理数据时使用的相同。我们也称上面定义的load_data

genre_names = ['Dance Pop', 'Electronic', 'Electropop', 'Hip Hop', 'Jazz', 'K-pop', 'Latin', 'Pop', 'Pop Rap', 'R&B', 'Rock']
audio_feats = ["acousticness", "danceability", "energy", "instrumentalness", "valence", "tempo"]exploded_track_df = load_data()

k-NN 机器学习模型

加载数据后,我们可以开始构建我们的机器学习模型来推荐歌曲。有许多方法可以解决这个问题。一种简单的方法是使用 k 最*邻模型来获得在距离上与用户选择的特征输入集最接*的热门歌曲。这些“特征输入”包括感兴趣的流派、发行年份范围(开始年份和结束年份)和一组音频特征(声音、可跳舞性、能量、乐器性、效价、速度)。

我们可以使用 Sklearn 来构建 k-NN 模型,并返回给定测试点的 top-k 结果。为了执行上述功能,我们编写了函数n_neighbors_uri_audio,它将按照排名的升序返回顶部邻居的 Spotify URIs 和音频特征值(最接*输入特征的点排在第一位)。

def n_neighbors_uri_audio(genre, start_year, end_year, test_feat):
    genre = genre.lower()
    genre_data = exploded_track_df[(exploded_track_df["genres"]==genre) & (exploded_track_df["release_year"]>=start_year) & (exploded_track_df["release_year"]<=end_year)]
    genre_data = genre_data.sort_values(by='popularity', ascending=False)[:500] neigh = NearestNeighbors()
    neigh.fit(genre_data[audio_feats].to_numpy()) n_neighbors = neigh.kneighbors([test_feat],       n_neighbors=len(genre_data), return_distance=False)[0] uris = genre_data.iloc[n_neighbors]["uri"].tolist()
    audios = genre_data.iloc[n_neighbors][audio_feats].to_numpy()
    return uris, audios

请注意,我们只使用属于某个流派的前 500 首最受欢迎的歌曲,以便我们的系统推荐的歌曲会更受欢迎。如果你不介意被推荐不太受欢迎的歌曲,请随意使用这个数字或删除它。

应用布局和集成

现在,终于到了构建应用程序最激动人心的部分:前端布局的时候了!在我们的主页中,我们可以有一个巨大的标题,上面写着“歌曲推荐引擎”和一个仪表板,允许用户定制他们想听的歌曲。

title = "Song Recommendation Engine"
st.title(title)st.write("First of all, welcome! This is the place where you can customize what you want to listen to based on genre and several key audio features. Try playing around with different settings and listen to the songs recommended by our system!")
st.markdown("##")with st.container():
    col1, col2,col3,col4 = st.columns((2,0.5,0.5,0.5))
    with col3:
        st.markdown("***Choose your genre:***")
        genre = st.radio(
            "",
            genre_names, index=genre_names.index("Pop"))
    with col1:
        st.markdown("***Choose features to customize:***")
        start_year, end_year = st.slider(
            'Select the year range',
            1990, 2019, (2015, 2019)
        )
        acousticness = st.slider(
            'Acousticness',
            0.0, 1.0, 0.5)
        danceability = st.slider(
            'Danceability',
            0.0, 1.0, 0.5)
        energy = st.slider(
            'Energy',
            0.0, 1.0, 0.5)
        instrumentalness = st.slider(
            'Instrumentalness',
            0.0, 1.0, 0.0)
        valence = st.slider(
            'Valence',
            0.0, 1.0, 0.45)
        tempo = st.slider(
            'Tempo',
            0.0, 244.0, 118.0)

要在 localhost 上运行 streamlit 应用程序,可以运行:

streamlit run app.py

这将呈现网页:

仪表板布局

看起来不错!你可以定制你想要的仪表盘,改变你喜欢的输入风格。

在构建仪表板之后,我们可以使用用户选择的功能来显示要推荐的歌曲。为此,我们可以调用前面定义的n_neighbors_uri_audio函数。为了显示推荐的歌曲,我们可以使用 Spotify 开发者小部件来显示一个使用经典 HTML 的 iframe。

tracks_per_page = 6
test_feat = [acousticness, danceability, energy, instrumentalness, valence, tempo]
uris, audios = n_neighbors_uri_audio(genre, start_year, end_year, test_feat)tracks = []
for uri in uris:
    track = """<iframe src="[https://open.spotify.com/embed/track/{](https://open.spotify.com/embed/track/{)}" width="260" height="380" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>""".format(uri)
    tracks.append(track)

给定一组输入,我们希望能够推荐更多的歌曲,这样用户就有更多的选择,而不是只显示前 6 首歌曲。因此,我们可以添加一个“推荐更多歌曲”按钮。为了支持这一点,我们需要使用 Streamlit 的会话状态来在每个用户会话的重新运行之间共享变量。我们可以使用会话状态来检查用户是否更改了任何输入。如果用户改变任何输入,系统需要从(顶部邻居的)开始推荐。如果用户继续按“推荐更多歌曲”按钮而不改变任何输入,则将遍历顶部邻居,直到顶部邻居列表的末尾。请记住,推荐的歌曲按其排名的升序排列。

if 'previous_inputs' not in st.session_state:
    st.session_state['previous_inputs'] = [genre, start_year, end_year] + test_featcurrent_inputs = [genre, start_year, end_year] + test_feat
if current_inputs != st.session_state['previous_inputs']:
    if 'start_track_i' in st.session_state:
        st.session_state['start_track_i'] = 0
    st.session_state['previous_inputs'] = current_inputsif 'start_track_i' not in st.session_state:
    st.session_state['start_track_i'] = 0

设置会话状态后,我们可以在布局中显示推荐的歌曲,并实现“推荐更多歌曲”按钮。我们还可以显示每首歌曲的雷达图,显示其音频特征的值。

with st.container():
    col1, col2, col3 = st.columns([2,1,2])
    if st.button("Recommend More Songs"):
        if st.session_state['start_track_i'] < len(tracks):
            st.session_state['start_track_i'] += tracks_per_pagecurrent_tracks = tracks[st.session_state['start_track_i']: st.session_state['start_track_i'] + tracks_per_page]
    current_audios = audios[st.session_state['start_track_i']: st.session_state['start_track_i'] + tracks_per_page]
    if st.session_state['start_track_i'] < len(tracks):
        for i, (track, audio) in enumerate(zip(current_tracks, current_audios)):
            if i%2==0:
                with col1:
                    components.html(
                        track,
                        height=400,
                    )
                    with st.expander("See more details"):
                        df = pd.DataFrame(dict(
                        r=audio[:5],
                        theta=audio_feats[:5]))
                        fig = px.line_polar(df, r='r', theta='theta', line_close=True)
                        fig.update_layout(height=400, width=340)
                        st.plotly_chart(fig)

            else:
                with col3:
                    components.html(
                        track,
                        height=400,
                    )
                    with st.expander("See more details"):
                        df = pd.DataFrame(dict(
                            r=audio[:5],
                            theta=audio_feats[:5]))
                        fig = px.line_polar(df, r='r', theta='theta', line_close=True)
                        fig.update_layout(height=400, width=340)
                        st.plotly_chart(fig)else:
        st.write("No songs left to recommend")

当您再次运行整个时,您将得到以下应用程序!

歌曲显示布局

歌曲显示布局

新航吊灯雷达图

您可以验证歌曲是否与用户选择的输入匹配,以及“推荐更多歌曲”按钮是否有效!这个应用程序最棒的地方在于,你不仅可以探索新歌,还可以直接播放歌曲预览(如果你有 Spotify,甚至可以播放整首歌)。现在你可以坐下来享受你最喜欢的音乐了!

为了方便起见,下面是app.py的完整代码:

部署

在我让你进入自己的音乐世界之前,这里有一个额外的部分来教你如何部署这个应用程序,让世界看到。

说到 web 部署,有大量选项可供选择。 Streamlit Cloud 支持自己的部署服务,但是我个人觉得 Heroku 过去非常好用,所以我就用这个。

创建必要的文件

以下是您需要在 Heroku 上部署的文件:

  • requirements.txt :一个文本文件,包含应用程序所需的所有依赖项及其版本的列表
  • setup.sh :在 Heroku 上设置 app 的 Shell 文件
  • proc file:Heroku 用来运行和启动 app 的入口点

在应用程序的根目录中创建这三个文件,并将以下内容复制粘贴到您自己的目录中。

requirements.txt

streamlit==1.0.0
pandas==1.3.3
plotly==5.3.1
scikit-learn==0.23.2

setup.sh

mkdir -p ~/.streamlit/
echo "\
[server]\n\
headless = true\n\
port = $PORT\n\
enableCORS = false\n\
" > ~/.streamlit/config.toml

过程文件

web: sh setup.sh && streamlit run app.py

创建一个 Github 存储库(如果还没有的话)

现在,您应该创建一个 github repo。然后,您可以使用以下代码将您的代码推送到 repo。

echo "# song_recommendation" >> README.md
git init
git add .
git commit -m "first commit"
git branch -M main
git remote add origin [git@github.com](mailto:git@github.com):[username]/[repo_name].git
git push -u origin main

运行上述命令后,您应该看到您的代码被推送到 repo。

创建 Heroku 帐户并安装 Heroku CLI

在收集了必要的文件和 Github repo 之后,我们现在可以创建一个 Heroku 帐户了。导航到 Heroku 的主要网站创建帐户。

https://www.heroku.com/

创建帐户后,安装 Heroku CLI:

https://devcenter.heroku.com/articles/heroku-cli

部署到 Heroku

现在,我们有了部署到 Heroku 的所有工具!最后一步是运行以下命令登录到您的帐户,构建您的应用程序,并进行部署。

heroku login
heroku create [app_name]
git push heroku main

注意:无论何时修改代码,都要记得在推送到 heroku main 之前进行 git add 和 commit。

恭喜你!您现在应该可以在[app_name].herokuapp.com上看到您的应用正在运行。

我已经在 Heroku 上部署了这个应用程序的一个版本,你可以在这里访问:song-recommendation-streamlit.herokuapp.com。本文中显示的代码可以在这个 github 库中找到。

结论

我希望你们都从这篇教程中学到了一些东西!简而言之,我们建立了一个歌曲推荐引擎,它可以接受用户的音乐偏好,并找到与他们最匹配的歌曲。我们通过使用 k-NN 模型来实现这一点。为了演示推荐系统,我们使用 Streamlit 构建了 web 应用程序,并将其部署在 Heroku 上。

我希望你们都喜欢这篇文章。请随意使用推荐系统,并让我知道您的想法!以后有更多精彩的帖子可以随时关注我。下期再见,注意安全!

另外,请随意查看我的其他一些有趣的帖子:

https://medium.datadriveninvestor.com/beginners-guide-to-web-scraping-using-beautifulsoup-and-python-999a927f20e6

参考

用 Python 编写一个拼写检查程序

原文:https://towardsdatascience.com/build-a-spelling-checker-program-in-python-a2c852330d70

在这篇文章中,我们将探索如何使用 Python 来检查单词和句子的拼写

真诚媒体Unsplash 上拍摄的照片

目录

  • 介绍
  • 使用 Python 检查单词的拼写
  • 使用 Python 检查句子的拼写
  • 结论

介绍

在编程中,我们经常会用到大量的文本对象。

这些包括我们添加到项目存储库中的简单自述文件,或者用于发送电子邮件的自动化,或者其他东西。

我们在这些对象中使用的文本容易出错、拼写错误等等。

使用 Python 我们可以快速有效地检查不同单词和句子的拼写。

使用本文中的模块,您还可以用 Python 构建一个拼写纠正程序。

要继续学习本教程,我们需要以下 Python 库:textblob。

如果您没有安装它,请打开“命令提示符”(在 Windows 上)并使用以下代码安装它:

pip install textblob

使用 Python 检查单词的拼写

在这一节中,我们将探索如何使用 Python 来检查单词的拼写。

步骤 1:导入所需的依赖项

Word() 是一个简单的单词表示,它有许多有用的方法,尤其是检查拼写。

第二步:定义一个单词来检查拼写

让我们使用一些有拼写错误的单词,如“appple ”,并对其进行拼写检查。

第三步:检查单词的拼写

您应该得到:

[('apple', 1.0)]

。spellcheck() 方法返回一个元组,其中第一个元素是正确的拼写建议,第二个元素是置信度。

在我们的例子中,拼写检查器 100%确信正确的拼写是“apple”。

注:

您可能会收到多个元组(正确拼写的多个建议)。

例如,如果您对单词“aple”运行相同的代码,您将得到:

[('able', 0.5140664961636828),
 ('pale', 0.4219948849104859),
 ('apple', 0.028132992327365727),
 ('ample', 0.023017902813299233),
 ('ape', 0.010230179028132993),
 ('ale', 0.0025575447570332483)]

因为上述拼写错误可能是许多拼写错误的单词的一部分,所以您可以获得多个正确拼写的选项,按信心排序。

使用 Python 检查单词拼写的程序

结合上述所有步骤并添加一些功能,我们可以使用 Python 创建一个检查单词拼写的程序:

使用示例单词“appple”运行此程序应返回:

Spelling of "appple" is not correct!
Correct spelling of "appple": "apple" (with 1.0 confidence).

使用 Python 检查句子的拼写

为了使用 Python 检查一个句子的拼写,我们将在上一节构建的程序的基础上进行构建。

不幸的是,我们不能将整个句子传递到 checked 中,这意味着我们将把句子分成单个单词,并执行拼写检查。

步骤 1:导入所需的依赖项

第二步:定义一个句子来检查拼写

让我们用一个有两个拼写错误的简单句子:“sentence”和“checkk”。

第三步:把句子分成单词

您应该得到:

['This', 'is', 'a', 'sentencee', 'to', 'checkk!']

第四步:将每个单词转换成小写

您应该得到:

['this', 'is', 'a', 'sentencee', 'to', 'checkk!']

我们转换成小写的原因是因为它会影响拼写检查的性能。

第五步:去掉标点符号

您应该得到:

['this', 'is', 'a', 'sentencee', 'to', 'checkk']

我们删除标点符号的原因是因为它会影响拼写检查的性能,因为它将标点符号视为单词的一部分。

第六步:检查句子中每个单词的拼写

使用我们早先创建的函数 check_spelling()

您应该得到:

Spelling of "this" is correct!
Spelling of "is" is correct!
Spelling of "a" is correct!
Spelling of "sentencee" is not correct!
Correct spelling of "sentencee": "sentence" (with 0.7027027027027027 confidence).
Spelling of "to" is correct!
Spelling of "checkk" is not correct!
Correct spelling of "checkk": "check" (with 0.8636363636363636 confidence).

我们看到代码正确地识别了拼写错误的单词,并提供了正确拼写的建议以及置信度。

使用 Python 检查句子拼写的程序

结合以上所有步骤,使用我们之前创建的函数 check_spelling() ,并添加一些功能,我们可以使用 Python 创建一个纠正单词拼写的程序:

用例句“这是一个要检查的句子!”应该返回:

Spelling of "this" is correct!
Spelling of "is" is correct!
Spelling of "a" is correct!
Spelling of "sentencee" is not correct!
Correct spelling of "sentencee": "sentence" (with 0.7027027027027027 confidence).
Spelling of "to" is correct!
Spelling of "checkk" is not correct!
Correct spelling of "checkk": "check" (with 0.8636363636363636 confidence).

结论

在本文中,我们探讨了如何使用 Python 检查单词和句子的拼写。

如果你有任何问题或对编辑有任何建议,请随时在下面留下评论,并查看我的更多 Python 编程教程。

原载于 2022 年 2 月 25 日 https://pyshark.comhttps://pyshark.com/spelling-checker-program-in-python/

用 Python 编写一个拼写纠正程序

原文:https://towardsdatascience.com/build-a-spelling-corrector-program-in-python-46bc427cf57f

在本文中,我们将探索如何使用 Python 来纠正单词和句子的拼写

布雷特·乔丹在 Unsplash 上的照片

在这篇文章中,我们将探索如何使用 Python 来纠正单词和句子的拼写。

目录

  • 介绍
  • 使用 Python 正确拼写单词
  • 使用 Python 正确拼写句子
  • 结论

介绍

我们已经在之前的一篇文章中介绍了 Python 中的拼写检查的主题。

虽然简单地检查拼写错误是一个有用的工具,但项目中真正使用的更适用的例子是执行拼写纠正的程序。

使用 Python 我们可以快速有效地纠正不同单词和句子的拼写。

要继续学习本教程,我们需要以下 Python 库:textblob。

如果您没有安装它,请打开“命令提示符”(在 Windows 上)并使用以下代码安装它:

pip install textblob

使用 Python 正确拼写单词

在这一节中,我们将探索如何使用 Python 来纠正单词的拼写。

步骤 1:导入所需的依赖项

Word() 是一个来自 textblob 库的简单单词表示,它有许多有用的方法,尤其是用于纠正拼写。

第二步:定义一个单词来纠正拼写

让我们使用我们在拼写检查示例中使用的同一个单词,它有一个拼写错误:“appple”。

第三步:单词的正确拼写

您应该得到:

apple

使用 Python 纠正单词拼写的程序

结合上述所有步骤并添加一些功能,我们可以使用 Python 创建一个程序来纠正单词的拼写:

使用示例单词“appple”运行此程序应返回:

'apple'

使用 Python 正确拼写句子

在这一节中,我们将探索如何使用 Python 来纠正单词的拼写。

步骤 1:导入所需的依赖项

TextBlob() 是库中一个简单的文本块表示,它有许多有用的方法,尤其是用于纠正拼写。

第二步:定义一个句子来纠正拼写

第三步:句子的正确拼写

您应该得到:

A sentence to check!

使用 Python 修改句子拼写的程序

结合上述所有步骤并添加一些功能,我们可以使用 Python 创建一个程序来纠正句子的拼写:

用例句“要检查的句子!”运行这个程序应该返回:

A sentence to check!

结论

在本文中,我们探讨了如何使用 Python 来纠正单词和句子的拼写。

如果你有任何问题或对一些编辑有建议,请随时在下面留下评论,并查看我的更多 Python 编程教程。

原载于 2022 年 2 月 27 日【https://pyshark.com】

使用 Vue.js、FastAPI 和 WebSockets 在浏览器中构建基于人工智能的自动完成功能

原文:https://towardsdatascience.com/build-an-ai-based-autocomplete-in-the-browser-using-vue-js-fastapi-and-websockets-1eb7ae19bfd8

一个智能自动完成类似谷歌使用递归神经网络

我们将会建造什么

介绍

我喜欢谷歌搜索。它以某种方式读取我们的思想,知道我们接下来要键入什么。我必须承认,我不知道它是如何工作的,但在本文中,让我们尝试使用深度学习和递归神经网络来建立类似的东西。

整体直觉

作者图片

这个想法是,一旦用户开始在搜索栏中输入,我们就将输入的文本一个字符一个字符地输入到递归神经网络中,以生成自动完成的预测。

本教程分为 3 个部分:

  • 建立和训练性格 RNN 模型
  • 构建前端和后端
  • 通过 WebSockets 建立通信

让我们开始吧。

准备模型

首先,让我们构建 charRNN 模型,它是应用程序的主干。

上面的笔记本包含了构建模型的代码。让我们一步一步来看:

数据集

现在,我们希望训练 charRNN 模型来预测用户将要提出的问题,因此让我们在问题数据集上训练该模型。

我从 Kaggle 中找到了下面的数据集(请参见最后关于数据集使用的致谢):

这个数据集是专门为问答相关任务设计的,但是,我们可以只过滤掉问题。

>> questions_all[10:15]
>> array(['Did Lincoln start his political career in 1832?',
       'Did Lincoln ever represent Alton & Sangamon Railroad?',
       'Which county was Lincoln born in?',
       'When did Lincoln first serve as President?',
       'Who assassinated Lincoln?'], dtype=object)

上面的代码片段显示了数据集中的一个小问题示例。该数据集包含来自各种主题的问题,它可以作为我们应用程序的一个良好起点,因为这些问题很短,有点像用户问 Google 的问题。

数据预处理

现在我们已经过滤掉了问题,下一步是清理它们并执行预处理,包括将文本转换成小写字母;以及删除标点符号、空值、制表符、换行符和多个空格。

上面的代码片段执行预处理。

数据统计

我们还没有完成预处理。让我们看看问题长度的分布图:

问题长度分布图

从上面的情节中,我们可以看到一个普通的问题有大约 50 个字符,但也有超过 200 个字符的问题。想想看,用户很少搜索这么长的问题,所以让我们完全删除它们,这样做是安全的,因为*均问题只有 50 个字符长。现在,我们可以将问题保留在*均值的一个标准偏差内,以包括数据集的大部分,并删除其他所有内容。

在上面的代码片段中,我们计算了分布的*均值和标准偏差,并将它们相加得到最佳长度,即距离*均值一个标准偏差的长度。最后,我们删除超过最佳长度的问题。

准备数据集

既然我们已经准备好了问题,我们就必须对它们进行编码和准备,以便输入到模型中。

下面的动画概述了我们将详细讨论的编码过程。

作者制作的动画

在 Pytorch 中,我们通常创建一个从 Pytorch 的Dataset类继承的自定义数据集类,下面是这样做的一些优点:

  • 我们将对数据有更多的控制。
  • 这有助于保持代码的模块化。
  • 我们可以从这个 dataset 实例中创建一个 Pytorch Dataloader,它会自动处理数据的批处理、混排和采样,我们将在后面看到。

上面的代码片段显示了一个名为QuestionsDataset的自定义数据集类。让我们走一遍:

  • 首先,在__init__方法中,我们通过从数据集中提取独特的字符来加载问题和准备好的词汇。我们还添加了一个额外的开始和结束标记,因为每当我们处理有限序列时,模型应该知道何时开始和结束句子。
  • encode_questions方法用于对单个问题进行编码。这里,我们首先使用我们在__init__方法中创建的词汇字典将问题中的字符编码为索引,然后我们执行一键编码。请注意,我们在 one-hot 编码之前,分别在句子的开头和结尾添加了开始和结束标记。您也可以使用嵌入来代替一次性编码,但是由于这里的词汇表很小,我们可以继续使用一次性编码。
  • 回到__init__方法,我们对所有的问题进行编码,并用零填充,这样所有的问题都有相同的长度。填充允许我们批量处理问题,这可以在不影响模型性能的情况下提高训练时间。
  • 我们还需要定义另外两个方法:应该返回数据集中数据点总数的__len__方法,以及应该返回基于索引的数据点的__getitem__方法。Pytorch 的Dataloader使用这些方法来批处理和混洗数据,我们将在下面看到。

编码过程如下图所示:

对问题进行编码

执行训练、验证分割

接下来,让我们将数据分成训练集和验证集,并创建数据加载器。

在上面的代码片段中,首先我们通过从所有问题中提取唯一字符来创建词汇表,定义批量大小,以及可以是词汇表中不存在的任何唯一字符的开始和结束标记。

接下来,我们以 9:1 的比率执行训练验证分割,并使用我们之前定义的QuestionsDataset类创建数据集。Pytorch 的Dataloader构造函数允许我们从这些数据集实例中创建数据加载器,它可以自动对数据进行批处理、采样和混排。

最后,我们准备好了输入模型的数据。接下来让我们建立模型。

构建 charRNN 模型

charRNN 模型在每个时间步长接收一个输入字符,并输出下一个适当字符的概率分布,如下所示:

作者插图

这个想法是,我们将一个编码的问题传递给递归神经网络,它在每个时间步输出一个隐藏状态。接下来,我们通过完全连接的网络传递这些隐藏状态中的每一个,以获得 logits。然后,我们从逻辑和目标计算交叉熵损失。我们计算每个时间步长的损耗,并将所有时间步长的损耗相加,得到总损耗,用于整个网络的反向传播。

这个模型的代码如下:

请注意,在输出通过完全连接的网络之前,我们对其进行了*坦化处理。这是因为每当我们计算损耗时,我们是对所有批次一起计算,而不是一批一批地计算。因此,我们沿着批次维度进行扁*化,以合并所有批次,如下所示:

拉*输出

训练模型

好了,让我们来训练我们的模型:

这里,我定义了一个三层 LSTM 网络,各层相互堆叠,隐藏状态大小为 512,丢失概率为 0.4。

我使用了一个 Adam 优化器,默认学习速率为 1e-3,交叉熵作为损失函数。训练和验证循环运行 100 个时期,并且每 10 个时期保存一次模型。这些超参数可以根据模型的性能进行调整。

此外,这里还有一些需要注意的事项:

  • 我们在批次之间传递隐藏状态,这意味着一个批次的最终状态将是下一个批次的初始状态。
  • 我们在更新模型参数之前剪切梯度,因为 rnn 面临着由于随时间反向传播而引起的梯度爆炸问题。因此,我们将幅度较大的梯度“裁剪”到特定的阈值。

训练后,我们可以绘制训练和验证曲线:

乍一看,这似乎是一个很好的情节,但如果你仔细观察,火车和验证损失彼此接*——这是一个不好的迹象。这通常意味着模型无法捕捉数据中的表示。这是有意义的,因为我们用的是一个简单的 charRNN 模型。如果我们使用基于 transformer 的模型,我们可能会提高性能,但现在让我们继续使用这个模型。

生成问题

现在我们的模型已经训练好了,让我们了解如何进行推理,在我们的例子中,推理是根据用户的输入来预测问题。

给这个想法一个输入字符,我们对它进行编码,并通过网络获得 logits 作为输出。然后,我们对 logits 应用 softmax 函数,以获得下一个字符的概率分布。现在,我们可以直接从这个分布中挑选最上面的字符,但是这样会导致过拟合,所以我们从分布中挑选最上面的 k 个字符,随机选择其中一个作为下一个字符。

下面的代码片段中的GenerateText类中的predict_next_char方法说明了这个过程。

predict_next_char方法接受一个输入字符和隐藏状态来预测下一个合适的字符。

现在,我们的目标是根据用户给出的上下文来预测问题。上下文也称为质数,是一组初始字符。这个想法是将这些初始字符一个一个地输入到模型中,并构建隐藏状态。接下来,使用隐藏状态和上下文中的最后一个字符,我们使用predict_next_char方法预测下一个字符,然后我们将这个预测的字符作为输入来预测下一个字符。我们重复这个过程,直到到达结束标记,这表示我们已经到达问题的结尾。

这个过程在generate_text法中有所说明。

让我们看看下面的一些例子:

准备前端

我们的前端将是简单的,它只是一个搜索栏,用户输入显示在前面,自动完成显示在后面,不透明度降低,如下图所示:

为了获得这种效果,想法是将两个span元素放在同一个div容器中,这可以通过将div容器的位置设置为相对位置并将span元素的位置设置为绝对位置来实现。本质上,我们相对于div容器放置span元素,这样它们可以被放在相同的位置。

代码片段如下所示:

现在,可以通过将contenteditable属性设置为 true 来使span元素在浏览器中可编辑。此外,因为我们希望在用户输入之后自动完成,所以它的 z 索引被设置为-1。最后,我们加入一些 CSS 来得到一个漂亮的自动完成的搜索栏。

通过 WebSockets 进行通信

这个想法是,一旦用户开始在浏览器中输入,文本需要作为输入发送到后端的 charRNN 模型,以生成预测。然后,预测将被发送回前端,并显示在搜索栏中用户输入后面的自动完成占位符中。

由于我们希望在前端和后端之间建立实时双向通信,WebSockets 将是理想的。

让我们构建前端、后端,并使用 WebSockets 建立通信。

构建后端

对于后端,我们只需围绕 charRNN 模型创建一个 API 包装器来服务请求。我在这里使用 FastAPI 来定义 WebSocket API。

让我们浏览一下代码:

首先,我们定义并加载预训练的 charRNN 模型,并将其设置为评估模式。接下来,我们定义一个使用 WebSocket 协议的路由和一个函数predict_question,每当请求到达这个路由时,这个函数就会被调用。

predict_question函数是一个异步函数,本质上意味着它不是等待一个特定的任务完成,而是执行其他部分,并在任务完成时返回。async关键字用于定义一个异步函数,而await关键字告诉而不是等待函数中特定任务的完成。

在这个函数中,首先我们接受来自前端服务器的握手并建立通信。然后,一旦我们从前端接收到输入文本,我们就通过 charRNN 模型运行它,以生成自动完成预测,并使用 WebSocket 协议将其发送回前端。

接下来让我们在前端添加 WebSocket 通信机制。

构建前端

在前端,我们需要一种机制来检测用户输入,这样我们就可以在用户开始输入时将文本发送到后端。为此,我们可以使用 Vue 指令和事件处理程序。v-on指令(也用@符号表示)监听元素上的特定事件,并在事件被触发时调用处理函数。

让我们浏览一下代码:

首先,我们在mounted生命周期钩子中创建 WebSocket 对象。生命周期钩子本质上是在组件创建的不同阶段被调用的函数。一旦组件被呈现到 DOM 中,就会调用mounted生命周期钩子,这是实例化 WebSocket 对象并与后端建立通信的好阶段。

接下来,在搜索栏元素中,我们添加了行@input="sendText”来监听输入事件并触发函数sendText,该函数获取搜索栏中的文本并将其发送到后端。一旦我们从后端接收到自动完成的预测,函数receiveText通过 WebSocket 对象的onmessage回调方法被触发,该函数填充autoComplete占位符,该占位符显示在搜索栏中用户输入的后面。

注意搜索栏元素中的第@keypress=”preventInput”行。这是一个事件处理程序,防止用户在搜索栏中键入数值或标点符号,因为记住在我们的 charRNN 模型的预处理步骤中,我们已经删除了所有内容,只保留了字母字符。因此,为了防止未识别的输入进入我们的 charRNN 模型,我们使用这个事件处理程序。

就是这样。我们已经完成了申请。让我们看看它的实际效果。

运行应用程序

代码可以在这个 GitHub 库中找到。要设置环境,请将此存储库克隆到您的工作区中:

git clone [https://github.com/wingedrasengan927/deep-autocomplete.git](https://github.com/wingedrasengan927/deep-autocomplete.git)
cd deep-autocomplete

假设您已经安装了 Kubernetes,按顺序运行以下命令:

kubectl apply -f ./backend/backend-deployment.yaml
kubectl apply -f ./backend/backend-service.yaml
kubectl apply -f ./frontend/frontend-deployment.yaml
kubectl apply -f ./frontend/frontend-service.yaml

您应该看到应用程序在localhost:8080运行

应用程序

虽然这是一个有趣的、初学者友好的项目,但我能想到的一个关键应用是构建个性化的搜索栏,使用某种模糊匹配算法来搜索想要的结果。然而,我们可能希望使用基于 Transformer 的模型来获得更好的性能。

我希望你喜欢这篇文章。让我们在 LinkedInTwitter 上连线。

致谢:

这里使用的数据集是由诺亚·史密斯、迈克尔·海尔曼、丽贝卡·华、谢伊·科恩、凯文·金佩尔以及卡耐基·梅隆大学和匹兹堡大学的许多学生在 2008 年至 2010 年间收集的。这里用的是 CC BY_SA 3.0 下的。如果您撰写任何涉及使用上述数据的论文,请引用该论文:

N. A .史密斯,m .海尔曼和 r .华(2008 年 9 月)。作为竞争性本科项目的问题生成。美国国家科学基金会关于问题生成、共享任务和评估挑战的研讨会论文集。

使用隔离林和 Kedro 构建异常检测管道

原文:https://towardsdatascience.com/build-an-anomaly-detection-pipeline-with-isolation-forest-and-kedro-db5f4437bfab

开发和管理用于检测欺诈性信用卡交易的数据科学管道

照片由 Unsplash 上的 timea dombi 拍摄

通过机器学习的强大功能,数据科学有望在所有行业产生巨大的商业价值。

然而, Gartner 最*的一份报告显示,尽管有足够的数据和意图,大多数数据科学项目仍无法超越实验。

为了释放数据科学的全部潜力,机器学习模型需要作为可扩展的端到端系统部署在现实世界中,并自动管理。

本文探讨了数据科学管道背后的概念,以及如何利用 Kedro 创建金融数据异常检测管道。

本文首发于 海王。艾

内容

(1)什么是数据科学管道?(2)什么是 Kedro?(3)为什么要 Kedro?(4)分步指南—异常检测的数据科学管道

(1)什么是数据科学管道?

顾名思义,数据科学管道包括各种组件的无缝链接,以促进数据按预期顺利移动。

如果我们在网上搜索数据科学管道,我们会看到令人眼花缭乱的管道设计。好消息是,我们可以将这些管道归结为六个核心要素:

  1. 数据检索和摄取
  2. 数据准备
  3. 模特培训
  4. 模型评估和调整
  5. 模型部署
  6. 监控

作者图片

上图说明了这六个组件是如何连接起来形成一个管道的,在这个管道中,机器学习模型可以在生产环境中提供最佳结果。

(1)数据检索和摄取

数据是所有数据科学项目的生命线,因此第一步是从各种数据源中识别相关的原始数据。

这一步比听起来更具挑战性,因为数据通常以不同的格式存储在不同的孤岛中(例如,第三方来源、内部数据库)。

一旦所需的数据集被正确识别,它们就被提取和整合以用于下游处理。

(2)数据准备

数据洞察的质量取决于数据质量。因此,数据准备花费最多的时间和精力也就不足为奇了。

用于数据准备的技术基于手边的任务(例如,分类、回归等)。)并包括数据清理、数据变换、特征选择、特征工程等类别。

(3)模型训练

模型训练是模型在数据中潜行,学习潜在的模式。经过训练的模型将被表示为从数据中捕获模式信息的统计函数。

要实现的机器学习模型的选择取决于实际任务、数据的性质和业务需求。

(4)模型评估和调整

一旦模型训练完成,评估其表现是至关重要的。评估是通过让模型对以前没有见过的数据进行预测来完成的。它代表了它在现实世界中的表现。

评估指标有助于指导优化模型性能所需的更改(例如,选择不同的模型、调整超参数配置等)。).

机器学习开发周期是高度迭代的,因为有许多方法可以基于度量和错误分析来调整模型。

(5)部署

一旦我们确信我们的模型可以提供出色的预测,我们就通过将它部署到生产环境中来将模型暴露给实际的操作。

模型部署是将模型集成到生产环境中的关键步骤,在生产环境中,模型接收实际数据并为数据驱动的业务决策生成输出。

(6)监测

为了保持强大且持续运行的数据科学管道,我们必须监控其在部署后的表现。

除了模型性能和数据质量,监控指标还可以包括操作方面,如资源利用率和模型延迟。

在成熟的 MLOps 设置中,我们可以根据预测性能或新数据的可用性触发新的模型训练迭代。

模型监控仪表板示例| 来源(经许可使用)

(2)什么是 Kedro?

数据科学管道的重要性刺激了许多有效构建和管理它们的框架的开发。一个这样的框架是 Kedro ,这是本文的重点。

Kedro 是一个开源的 Python 框架,用于创建可复制的、可维护的、模块化的数据科学代码。它有助于加速数据管道,增强数据科学原型,并促进管道的再现性。

Kedro 将软件工程概念应用于开发生产就绪的机器学习代码,以减少成功部署模型所需的时间和精力。

它的影响是通过消除低质量代码的重新设计工作和无缝协作的项目模板的标准化来实现的。

让我们看看 Kedro 中的应用概念:

  • ****再现性:准确一致地跨不同管道运行和环境重新创建工作流程步骤的能力。
  • ****模块化:将大的代码块分解成更小的、独立的、易于理解的单元,这些单元易于测试和修改。
  • ****可维护性:使用标准代码模板,允许团队成员容易地理解和维护任何项目的设置,从而促进协作开发的标准化方法
  • ****版本控制:精确跟踪每个管道运行中使用的数据、配置和机器学习模型。
  • ****文档:清晰的结构化信息,便于理解
  • ****无缝打包:允许数据科学项目被记录并有效地运送到生产中(使用像 Airflow 或 Docker 这样的工具)。

(3)为什么是 Kedro?

将数据科学项目从试点开发带入生产的过程充满了挑战。一些重大困难包括:

  • 需要为生产环境重写代码,导致项目严重延迟
  • 混乱的项目结构使得协作充满挑战
  • 难以追踪的数据流
  • 过于冗长且难以测试或重用的功能
  • 难以理解的函数之间的关系

QuantumBlack 团队开发了 Kedro 来应对上述挑战。它诞生于这样一种信念,即数据科学代码应该从一开始就为生产做好准备。

(4)分步指南:构建用于异常检测的数据科学管道

让我们进入激动人心的部分,通过一个实际动手的项目来学习。

项目用例围绕着金融欺诈检测。我们将使用隔离森林作为主要的机器学习模型,构建一个异常检测管道来识别信用卡交易中的异常。

信用卡交易数据由 Worldline 和机器学习小组合作获得。这是对真实世界信用卡交易的真实模拟,旨在包括复杂的欺诈检测问题。

以下可视化显示了我们最终的异常检测管道,并作为我们将在以下部分构建的蓝图

项目结构|作者图片

您可以随时查看这个项目的 GitHub repo

步骤 1 —安装 Kedro 和 Kedro-Viz

建议创建一个虚拟环境,以便每个项目都有其独立的环境和相关的依赖项。要使用 Kedro,官方文档建议用户安装 Anaconda

因为我的 Python 版本是 3.10 以上,Anaconda 使得在兼容 Kedro 需求的版本上创建环境(使用 conda 而不是 venv )变得很容易(也就是说,在撰写本文时,Python 是 3.6-3.8)。****

特别是,这是生成我们的 Kedro 环境的命令(在 Anaconda Powershell 提示符中):

**conda create --name kedro-env python=3.7 -y**

一旦用conda activate kedro-env设置并激活了虚拟环境,我们就可以使用 pip 来安装 Kedro 和 Kedro-Viz 插件:

**pip install kedro kedro-viz** 

我们可以通过将目录切换到我们的项目文件夹并输入**kedro info**来检查 Kedro 是否正确安装。如果安装正确,我们应该看到以下内容:

作者图片

此时,我们可以安装项目所需的其他包了。

**pip install scikit-learn matplotlib**

如果我们希望将这个项目初始化为 Git 存储库,我们可以使用:

**git init**

步骤 2 —项目设置

Kedro 的一个关键特性是创建标准的、可修改的和易于使用的项目模板。我们可以用以下代码初始化一个新的 Kedro 项目:

**kedro new**

在为一系列提示提供相关名称之后,我们将得到一个高度组织化的项目目录,我们可以在这个目录上进行构建:

作者图片

项目结构可以分为六个主文件夹:

/conf: 包含指定数据源(即数据目录)、模型参数、凭证和日志信息等细节的配置文件。

/数据: 包含输入、中间和输出数据。它被组织成一个八层的数据工程约定,以清晰地分类和构建数据的处理方式。****

/docs: 包含与项目文档相关的文件。

/logs: 包含管道运行时生成的日志文件。

/笔记本:包含项目中使用的 Jupyter 笔记本,例如用于实验或初步探索性数据分析。**

/src: 包含项目的源代码,如流水线步骤、数据处理、模型训练的 Python 脚本。

步骤 3 —数据设置

数据先于科学,所以让我们从数据设置开始。原始数据(每日交易的 70 个 CSV 文件)首先放在 data/01_raw 中。

根据我们之前的项目蓝图,我们知道在整个过程中将会生成和利用哪些数据。因此,我们可以将这些信息翻译成 数据目录 ,这是一个项目可用的数据源注册表。

数据目录提供了一种定义如何存储和解析数据的一致方式,使得从管道中的任何位置加载和保存数据集变得容易。

我们可以在中找到数据目录。yml 文件—conf/base/catalog . yml

作者图片

上图是数据目录中定义的数据源的一个片段。例如,我们首先期望我们的原始 CSV 文件被读取并合并到一个名为merged_data.csv的中间 CSV 数据集。

Kedro 有内置数据连接器(比如熊猫。CSVDataSet,matplotlib。MatplotlibWriter)来适应不同的数据类型。

步骤 4-创建管道

一旦定义了我们的数据目录,我们就可以构建我们的管道。首先要理解两个关键概念: 节点管道

  • ****节点是管道的构建块。它们是代表数据转换的 Python 函数,例如数据预处理、建模。
  • ****管道是连接在一起交付工作流的节点序列。它组织节点的依赖关系和执行顺序,连接输入和输出,同时保持代码模块化。

用于异常检测的完整管线可分为三个较小的模块化管线,我们将最终连接这些管线:

  1. 数据工程管道
  2. 数据科学管道
  3. 车型评估管道

我们可以根据指定的名称,使用以下命令实例化这些模块化管道:

**kedro pipeline create data_engineering
kedro pipeline create data_science
kedro pipeline create model_evaluation**

虽然管道在这个阶段是空的,但是它们的结构已经在 /src 文件夹中很好地生成了。

作者图片

每个管道文件夹都有相同的文件,包括 nodes.py (节点代码)和 pipeline.py (管道代码)。

步骤 5 —构建数据工程管道

让我们先来看看数据工程管道,我们在这里处理数据,用于下游的机器学习。更具体地说,有三个预处理任务要执行:

  1. 将原始数据集合并到中间合并数据集中
  2. 通过仅保留预测值列并为后续训练测试拆分创建新的日期列来处理合并的数据集
  3. 按时间顺序执行 80:20 列车测试,拆分并删除不必要的列

我们首先将任务脚本化为三个独立的** 节点函数nodes.py 中:**

然后,我们将这些节点函数导入到 pipeline.py 中,以正确的顺序链接它们。

注意在每个节点的包装器节点(..)** ,我们指定一个名称,函数(从 node.py 导入),以及数据目录中定义的输入输出数据集(参见 步骤 3 )。**

节点包装器中的参数应该与数据目录中的数据集名称和节点函数的参数相匹配。

对于节点node_process_data,预测列的列表存储在 conf/base/parameters.yml 中的参数文件中。

我们的数据工程管道设置已经完成,但是还没有准备好,因为它还没有注册。我们将在稍后的步骤 8** 中探讨这一点,所以让我们继续构建剩下的两条管道。**

步骤 6 —构建数据科学管道

我们管道的异常检测模型是隔离林隔离森林是一个无监督算法,使用决策树构建。

它通过随机选择一个特征,然后在最大值和最小值之间选择一个分割值来“隔离”观察值。由于异常现象很少且不同,因此预计它们比正常观察更容易分离。

我们将使用 scikit-learn 实现进行隔离林建模。有两个任务(和节点)要创建— (i) 模型训练和 (ii) 模型预测(也称为推理)。

模型的污染值设置为 0.009 ,与原始数据集中观察到的欺诈案例比例(即 0.9%)相匹配。

像以前一样,我们在 pipeline.py 中的管道函数中将节点链接在一起。

正如在数据目录中看到的,我们将把我们训练过的隔离森林模型作为 pickle 文件保存在Data/06 _ models中。

第 7 步—构建模型评估管道

虽然隔离森林是一个无监督的模型,但如果我们有地面真实标签,我们仍然可以评估它的性能。

在原始数据集中,有一个 TX_FRAUD 变量作为欺诈交易的指示器。

有了基础事实标签和预测的异常分数,我们可以获得评估指标并将其表示为 AUC 和 AUCPR 图。

下面是运行模型评估节点的 pipeline.py 脚本。

这个模型评估步骤与步骤 6 中的数据科学管道是分开的。这种分离是因为我们使用的是无监督异常检测算法,我们并不期望总是获得真实数据。

步骤 8 —在管道注册表中注册所有管道

至此,创建管道的所有艰苦工作都已完成。我们现在需要通过在管道注册表中导入和注册所有三个模块化管道来结束。

return 语句中的__default__行表示模块化管道的默认运行顺序,在我们的例子中是所有三个模块化管道— data_engineeringdata_sciencemodel_evaluation

Kedro 的美妙之处在于,它的模块化结构让我们能够灵活地构建我们的管道。例如,如果我们没有基本事实标签,我们可以从默认管线运行中排除model_evaluation

步骤 9 —可视化管道

在运行管道之前,最好检查一下我们到目前为止已经构建了什么。奇妙的 Kedro-Viz 插件让我们可以很容易地可视化整个管道结构和依赖关系。

鉴于其易用性、清晰度和美观的显示,许多 QuantumBlack 客户对这一功能表达了他们的喜悦也就不足为奇了。

我们可以使用以下命令轻松生成可视化效果:

**kedro viz**

我们的浏览器中将会打开一个新的选项卡,我们将会看到一个漂亮的可视化工具来探索我们的管道结构。这种可视化也可以很容易地导出为一个. png 图像文件。

作者图片

步骤 10 —运行管道

我们终于准备好运行我们的管道。下面的命令将执行我们之前注册的默认管道。

**kedro run**

运行时,管道将使用生成的数据填充各自的目录,包括异常预测和模型评估图。

我们还可以运行在管道注册表中注册的特定管道。例如,如果我们只想运行数据工程模块化管道(de),我们可以在命令中添加--pipeline=<NAME>:

**kedro run --pipeline de**

步骤 11 —评估管道输出

最后,是时候评估我们异常检测管道的输出了。特别是,让我们回顾一下评估图(保存在 数据/08 _ 报告 )来看看模型的性能如何。

作者图片

这些图表明隔离森林模型 AUC 是 0.8486 ,这是一个相当好的基线机器学习模型性能。

附加功能

恭喜你走到这一步,并成功地用 Kedro 创建了一个异常检测管道!

除了基本的功能之外,Kedro 还有其他用于管理数据科学项目的有用特性。这里有几个值得一提的功能:

(1)实验跟踪

Kedro 使设置实验跟踪和访问每个管道运行的记录指标变得容易。除了它的内部实验跟踪能力,Kedro 与其他 MLOps 服务整合得很好。

例如, Kedro-Neptune 插件 让用户享受组织良好的管道和强大的 Neptune 元数据管理用户界面的好处。

Neptune UI 仪表板示例|作者图片

(2)管道切片

Kedro 的管道切片功能允许我们根据需要执行管道的特定部分。例如,我们可以在希望运行的管道切片中定义开始和结束节点:

**kedro run --from-nodes train-test-split --to-nodes train_model**

(3)项目文档、打包和部署

我们可以通过在项目的根目录下运行这个命令来生成特定于项目的文档(构建在 Sphinx 框架上)。

**kedro build-docs**

接下来,为了将项目的打包为 Python 库,我们运行以下命令:

**kedro package**

最后,我们可以通过第三方插件 DockerAirflow部署这些打包的数据科学管道。

结论

还有其他有趣的 Kedro 特性和教程可用,所以请查看官方文档进行进一步的探索。另外,继续前进,看看包含这个项目所有代码的 GitHub repo

如果您想了解 Kedro 与市场上其他流水线工具的比较,请查看下面这篇全面的 NeptuneAI 文章:

**https://neptune.ai/blog/kedro-vs-zenml-vs-metaflow [## Kedro vs ZenML vs Metaflow:应该选择哪个管道编排工具?- neptune.ai

海王星. ai](https://neptune.ai/blog/kedro-vs-zenml-vs-metaflow)**

在你走之前

我欢迎您加入我的数据科学学习之旅。跟随此媒体页面并查看我的 GitHub 以了解实用和教育数据科学内容。同时,与 Kedro 一起享受构建数据科学管道的乐趣!

** https://kennethleungty.medium.com/membership [## 通过我的推荐链接加入媒体-梁建华

作为一个媒体会员,你的会员费的一部分会给你阅读的作家,你可以完全接触到每一个故事…](https://kennethleungty.medium.com/membership)**

使用 AWS Lambda 构建一个事件驱动的神经风格传输应用程序

原文:https://towardsdatascience.com/build-an-event-driven-neural-style-transfer-application-using-aws-lambda-18fa8145ef5b

在 5 个时期和每个时期 100 个步骤之后,生成神经类型转移图像(在右侧)

为了构建一个生产就绪的 ML 应用程序并确保它的长期稳定性,我们需要考虑一长串的需求,包括模型迭代的容易程度、可再现性、基础设施、自动化、资源、内存等等。最重要的是,我们需要一个无缝的开发者体验。会有多难呢?

Flyte 可以处理前一组问题,因为:

  • 这是一个工作流自动化*台,有助于维护和复制管道。
  • 它提供了基础设施、资源和内存的控制旋钮。

此外,Flyte 简化了开发人员的体验。在这篇博文中,我们将看到如何使用 Flyte 和 AWS Lambda 构建一个神经风格的转换应用程序。我们将对端到端管道进行编码,并分配所需的计算来运行代码。此外,我们将设计一个事件驱动的机制来触发管道,并在用户上传图像时输出风格化的图像。从用户的角度来看,上传图像时必须生成风格化的输出图像。

由于应用程序必须在一个事件上被触发,例如,图像上传,对 Flyte 来说更合适的选择是使用 AWS Lambda。它是无服务器和事件驱动的计算服务。我们的神经风格传输应用程序将利用 AWS Lambda 的“事件驱动特性”。

让我们看看如何使用 Flyte 和 AWS Lambda 将管道自动化和事件驱动的服务结合在一起。

图一。应用程序概述(图片由作者提供)

应用代码

神经风格转移是将风格图像的风格应用到内容图像上。输出图像将是内容和样式图像的混合。

要开始使用代码,首先要导入并配置依赖项。

注意:此代码改编自 TensorFlow 文档 中的 神经样式传递示例。运行代码,确保 *tensorflow* *flytekit* *Pillow* 库通过 *pip* 安装。

content_layersstyle_layers是 VGG19 模型的层,我们将使用它们来构建我们的模型,tensor_to_image任务将张量转换为图像。

模型建立过程的第一步是获取图像并对其进行预处理。定义一个[@task](https://docs.flyte.org/projects/flytekit/en/latest/generated/flytekit.task.html)来加载图像,并将其最大尺寸限制为 512 像素。

preprocess_img任务下载内容和样式图像文件,并使用load_img函数调整它们的大小。

准备好模型使用的数据后,定义一个返回样式和内容张量的 VGG19 模型。

class **StyleContentModel**(tf.keras.models.Model):
    def **__init__**(self, style_layers, content_layers):
        super(StyleContentModel, self).__init__()
        self.vgg = vgg_layers(style_layers + content_layers)
        self.style_layers = style_layers
        self.content_layers = content_layers
        self.num_style_layers = len(style_layers)
        self.vgg.trainable = False def **call**(self, inputs):
        "Expects float input in [0,1]"
        inputs = inputs * 255.0
        preprocessed_input = tf.keras.applications.vgg19.preprocess_input(inputs)
        outputs = self.vgg(preprocessed_input)
        style_outputs, content_outputs = (
            outputs[: self.num_style_layers],
            outputs[self.num_style_layers :],
        )
        style_outputs = [gram_matrix(style_output) for style_output in style_outputs]
        **content_dict** = {
            content_name: value
            for content_name, value in zip(self.content_layers, content_outputs)
        }
        **style_dict** = {
            style_name: value
            for style_name, value in zip(self.style_layers,    style_outputs)
        }
        return {"content": content_dict, "style": style_dict}

vgg_layers函数返回中间层输出的列表,在该列表之上构建模型(注意,我们使用的是预训练的 VGG 网络),而gram_matrix函数字面上的描述图像的风格。当在图像上调用模型时,它返回style_layers的 gram 矩阵和content_layers的内容。

接下来是样式转换算法的实现。通过考虑两个损失的加权组合,计算总损失(样式+内容)。

def **style_content_loss**(outputs, content_targets, style_targets):
    style_outputs = outputs["style"]
    content_outputs = outputs["content"]
    style_loss = tf.add_n(
        [
            tf.reduce_mean((style_outputs[name] - style_targets[name]) ** 2)
            for name in style_outputs.keys()
        ]
    )
    **style_loss** *= style_weight / len(style_layers)
    content_loss = tf.add_n(
        [
            tf.reduce_mean((content_outputs[name] - content_targets[name]) ** 2)
            for name in content_outputs.keys()
        ]
    )
    **content_loss** *= content_weight / len(content_layers)
    **loss = style_loss + content_loss**
    return loss

tf.GradientTape内调用style_content_loss更新图像。

[**@task**](http://twitter.com/task)**(requests=Resources(cpu="1", mem="5Gi", storage="5Gi", ephemeral_storage="5Gi"))**
def **train_step**(
    image: tf.Variable, content_image: tf.Tensor, style_image: tf.Tensor
) -> tf.Variable:
    opt = tf.optimizers.Adam(learning_rate=0.02, beta_1=0.99, epsilon=1e-1)
    extractor = StyleContentModel(style_layers, content_layers)
    style_targets = extractor(style_image)["style"]
    content_targets = extractor(content_image)["content"]
    **with tf.GradientTape() as tape:
        outputs = extractor(image)
        loss = style_content_loss(outputs, content_targets, style_targets)
        loss += total_variation_weight * tf.image.total_variation(image)**
    grad = tape.gradient(loss, image)
    opt.apply_gradients([(grad, image)])
    image.assign(clip_0_1(image))
    return image

train_step任务初始化样式和内容目标值(张量),计算总变化损失,运行梯度下降,应用处理过的梯度,并在 0 和 1 之间裁剪图像的像素值。如下定义clip_0_1功能:

def **clip_0_1**(image):
    return tf.clip_by_value(image, clip_value_min=0.0, clip_value_max=1.0)

创建一个[@dynamic](https://docs.flyte.org/projects/flytekit/en/latest/generated/flytekit.dynamic.html#flytekit-dynamic)工作流,为指定数量的epochssteps_per_epoch触发train_step任务。

tf.Variable存储内容图像。当从tf.GradientTape内调用时,image,一个tf.Variable被观察,操作被记录,用于自动微分。

最后,定义一个[@workflow](https://docs.flyte.org/projects/flytekit/en/latest/generated/flytekit.workflow.html)来封装任务并生成风格化的图像。

一旦管道被部署,接下来的步骤将是设置 S3 桶和配置 Lambda。

配置 AWS S3 桶和 Lambda

图像将被上传到 S3 桶,一旦图像被上传,Lambda 将被用来触发 Flyte 工作流。

S3 水桶

要配置 S3 存储桶,

  1. 打开亚马逊 S3 控制台
  2. 选择
  3. 选择创建桶
  4. 为存储桶命名,例如,“神经类型转移”。
  5. 选择适当的 AWS 区域(确保 Lambda 在同一个 AWS 区域中创建)。
  6. 阻止或解除阻止公共访问(本教程假设授予了公共访问)。
  7. 选择创建桶

希腊字母的第 11 个

Lambda 函数可以通过蓝图、容器映像或无服务器应用程序库从头开始创建。可以选择 Blueprint 来获取样本 lambda 代码,在我们的例子中,是一个 S3 blueprint。然而,由于我们需要从 Lambda 内部连接到 FlyteRemote,我们必须安装flytekit库。Lambda 内的库安装可以通过 zip 文件容器镜像 的方式实现。

Zip 文件是让flytekit进入 Lambda 的最简单的方法,但是由于它对 zip 文件的大小限制,更可行的方法是使用容器映像方法。

容器图像

要在您的机器上创建容器映像:

1.创建一个项目目录(例如 lambda)来容纳 lambda 函数。

2.在目录中创建 4 个文件:lambda_function.pyDockerfilerequirements.txtflyte.config

lambda
 ├── Dockerfile
 ├── flyte.config
 ├── lambda_function.py
 └── requirements.txt

3.lambda_function.py:封装代码获取上传的图片,实例化一个 FlyteRemote 对象,触发 Flyte 工作流。

ℹ️ FlyteRemote 提供了一个编程接口来与 Flyte 后端进行交互。

务必填写endpointdefault_project(如flytesnacks)、default_domain(如development),以及投放计划名称(如neural_style_transfer.example.neural_style_transfer_wf)。

  1. flyte.config :添加通过 flyteRemote 连接 Flyte 的配置。

  2. requirements.txt

flytekit>=1.0.0
  1. Dockerfile :将lambda_function.pyflyte.configrequirements.txt复制到根目录。将CMD实例化为在lambda_function.py文件中使用的处理程序。

7.使用以下命令在项目目录中构建 Docker 映像:

docker build -t neural-style-transfer .

8.向 Amazon ECR 注册表验证 Docker CLI。

aws ecr get-login-password --region <us-east-1> | docker login --username AWS --password-stdin <123456789012>.dkr.ecr.<us-east-1>.amazonaws.com

确保替换<>中的文本。

9.在 ECR 中创建一个存储库。

10.标记您的 Docker 映像,并将映像推送到新创建的存储库中。

docker tag neural-style-transfer:latest <123456789012>.dkr.ecr.<us-east-1>.amazonaws.com/lambda:neural-style-transfer-latestdocker push <123456789012>.dkr.ecr.<us-east-1>.amazonaws.com/lambda:neural-style-transfer-latest

确保替换注册表详细信息中的<>中的文本。

就是这样!您现在在 ECR 中有了自己的图像。

λ配置

要配置 Lambda,

  1. 打开 Lambda 控制台的功能页面
  2. 选择创建功能
  3. 选择容器图像
  4. 输入函数名(如 s3-lambda)。
  5. 容器镜像 URI (应该可以在亚马逊 ECR 控制台->-仓库仪表盘中找到)。
  6. 选择创建功能

您现在已经配置了 lambda!

许可

S3 水桶和拉姆达目前是分开的实体。为了在图像上传到 S3 桶时立即触发 Lambda,我们必须在它们之间建立一个连接。

连接它们还需要设置所需的权限。但是在配置权限之前,复制 bucket 和 Lambda ARNs。

斗 ARN :

  1. 打开亚马逊 S3 控制台
  2. 选择木桶
  3. 选择你的桶。
  4. 选择属性
  5. 复制 ARN。

λARN:

  1. 打开 Lambda 控制台的功能页面
  2. 选择功能
  3. 选择你的λ。
  4. 选择配置然后选择权限
  5. 点击中的角色执行角色。
  6. 复制 ARN。

S3 水桶

要设置 S3 存储桶的权限,请执行以下操作:

  1. 转到您创建的 S3 存储桶。
  2. 选择权限
  3. 斗策中,选择编辑
  4. 添加以下策略:

确保填写 Lambda 执行角色 ARN 和 S3 桶 ARN。

希腊字母的第 11 个

要为 Lambda 设置权限,请执行以下操作:

1.遵循λARN一节中概述的步骤 1- 4。

2.在权限下,选择添加权限

3.在下拉列表中,选择创建内嵌策略

4.在 JSON 选项卡下,粘贴以下内容:

请务必填写 S3 桶 ARN。

5.选择审核策略

6.对于名称,输入策略的名称。

7.选择创建策略

您可以将FLYTE_CREDENTIALS_CLIENT_SECRET添加到 lambda 的环境变量中,作为初始化 FlyteRemote 的一部分。为此:

  1. 遵循λARN一节中概述的步骤 1–3。
  2. 选择配置然后选择环境变量
  3. 设置密钥为FLYTE_CREDENTIALS_CLIENT_SECRET,值应该是你的秘密。

现在有趣的部分来了——将 lambda 与 S3 桶联系起来!

引发

要设置触发器,请执行以下操作:

  1. 遵循λARN一节中概述的步骤 1–3。
  2. 选择配置然后选择触发
  3. 点击添加触发器
  4. 选择触发器下拉菜单中,选择 S3。
  5. 铲斗下选择您的 S3 铲斗。
  6. 选择添加

图二。S3 和拉姆达之间应该有联系。(图片由作者提供)

测试应用程序

为了测试应用程序,上传一张图片到 S3 桶。在 Flyte 控制台上,在 neural style transfer 工作流下,检查执行是否被触发。执行的输出应该是你的风格化图像!

后续步骤

总的来说,我们构建了一个事件驱动的应用程序,每当有新数据时,它就动态地触发和执行 ML 管道。用 Flyte 和 AWS Lambda 生产管道非常容易,如本教程所示。我们还可以在这个流程之上有一个前端应用程序,使应用程序更容易访问。

如果你想对本教程给出反馈或者对实现有疑问,请在评论中发表!

使用客户端 JavaScript 通过 Tesseract OCR 构建图像和 PDF 文本提取工具

原文:https://towardsdatascience.com/build-an-image-pdf-text-extraction-tool-with-tesseract-ocr-using-client-side-javascript-6126031001

pdf . js+tessera CT . js——OCR&Web 技术的融合。包括完整的代码实现。

大约十年前,光学字符识别(OCR)工具,如 Tesseract OCR 引擎,只能通过 C/C+等二进制格式执行,或者打包成包装类,包括 Windows 可执行文件(。exe)、Python 包或 Java 软件开发工具包(JDK)。

随着 Web Assembly ( WASM )编译器的出现,Tesseract OCR 后来被编译成了 JavaScript 插件 Tesseract.js (衷心感谢同为媒介作家的 Jerome Wu )。这反过来通过组合另一个 JavaScript 插件 PDF.js 的功能,实现了一个 PDF 转文本应用的完整客户端 JavaScript 实现。

作者插图

总共有 2 个 与 OCR 相关的附带项目 作为我对 OCR 实现的自我探索之旅的一部分得以实施。它们如下所示:

第 1 部分:图像到文本

https://javascript.plainenglish.io/build-a-text-to-speech-app-using-client-side-javascript-98cd72df73bb

第二部分:PDF-to-Text✶

✶类似于第一部分:使用客户端 JavaScript 构建一个文本到语音的应用,文本提取的主要原理保持不变。唯一需要的额外中间步骤是将上传的 PDF 文档页面转换成图像,这将在后面的实施步骤中详细说明。

使用 Tesseract OCR 构建 PDF 转文本应用程序

对于该应用,需要实现一个 自托管 版本的 Tesseract.js v2,以支持 离线使用*可移植 *

第一步。检索 Tesseract.js v2 的以下 4 个文件

-tessera CT . min . js
-worker . min . js
-tessera CT-core . wasm . js
-eng.traineddata.gz
*

为简单起见,所有要提取的文本都假定为英文***

  • 导入插件
*<script src='js/tesseract/tesseract.min.js'></script>*
  • 继续将相应的工作属性指定为常量
  • 将 worker 实例化封装到一个async function
*const tesseractWorkerPath='js/tesseract/worker.min.js';
const tesseractLangPath='js/tesseract/lang-data/4.0.0_best';
const tesseractCorePath='js/tesseract/tesseract-core.wasm.js';
var worker;
async function initTesseractWorker() {
  worker = Tesseract.createWorker({
    workerPath: tesseractWorkerPath,
    langPath:  tesseractLangPath,
    corePath: tesseractCorePath
  });    
  await worker.load();
  await worker.loadLanguage('eng');
  await worker.initialize('eng');
  return new Promise((resolve) => resolve('worker initialised.'));
}*

注意:由于 app 是自托管的,相对路径需要重新定义为本地相对路径。

第二步。检索 PDF.js 的以下 2 个文件

注意: PDF 插件最初是由 Mozilla 开发的,用于通过 JavaScript 渲染 PDF。原始文件可以在这里找到。

在浏览器中导入插件:

*<script src='js/pdf/pdf.min.js'></script>*

第三步。为 PDF 上传创建用户界面

  • HTML 文件输入和 PDF 页码显示
*<input id='uploadPDF' type='file' />
<hr>
Pg <span id='currentPageNo'></span> of <span id='totalPages'></span>*
  • JavaScript 代码片段
*const pdfWorkerPath='js/pdf/pdf.worker.min.js';
const pixelRatio=window.devicePixelRatio*2;
var uploadPDF=document.getElementById('uploadPDF');
var currentPageNo=document.getElementById('currentPageNo');
var totalPages=document.getElementById('totalPages');
var _PDF_DOC, _PAGE, noOfPages, currentPage=1;
var _CANVAS=document.createElement('canvas');
function readFileAsDataURL(file) {
  return new Promise((resolve,reject) => {
    let fileredr = new FileReader();
    fileredr.onload = () => resolve(fileredr.result);
    fileredr.onerror = () => reject(fileredr);
    fileredr.readAsDataURL(file);
  });
}
const loadImage = (url) => new Promise((resolve, reject) => {
  const img = new Image();
  img.addEventListener('load', () => resolve(img));
  img.addEventListener('error', (err) => reject(err));
  img.src = url;
});
uploadPDF.addEventListener('change', function(evt) {
  let file = evt.currentTarget.files[0];
  if(!file) return;
  readFileAsDataURL(file).then((pdf_url) => {
    pdfjsLib.GlobalWorkerOptions.workerSrc=pdfWorkerPath;
    (async () => {
      _PDF_DOC = await pdfjsLib.getDocument({ url: pdf_url });
      noOfPages = _PDF_DOC.numPages;
      totalPages.innerHTML = noOfPages;
      while(currentPage<=noOfPages) {
        await initPdfTesseractWorker();
        currentPageNo.innerHTML=currentPage;
        _PAGE = await _PDF_DOC.getPage(pageNo);
        let pdfOriginalWidth = _PAGE.getViewport(1).width;
        let viewport = _PAGE.getViewport(1);
        let viewpointHeight=viewport.height;
        _CANVAS.width=pdfOriginalWidth*pixelRatio;
        _CANVAS.height=viewpointHeight*pixelRatio;
        _CANVAS['style']['width'] = `${pdfOriginalWidth}px`;
        _CANVAS['style']['height'] = `${viewpointHeight}px`;
        _CANVAS.getContext('2d').scale(pixelRatio, pixelRatio);
        var renderContext = {
          canvasContext: _CANVAS.getContext('2d'),
          viewport: viewport
        };
        await _PAGE.render(renderContext);
        let b64str=_CANVAS.toDataURL();
        let loadedImg = await loadImage(b64str);
        let result=await worker.recognize(loadedImg);
        let extractedData=result.data;

        let wordsArr=extractedData.words;
        let combinedText='';
        for(let w of wordsArr) {
          combinedText+=(w.text)+' ';
        }
        inputTxt.insertAdjacentText('beginend', combinedText);
        await worker.terminate();
        currentPage++;
      }
    })();
  }, false);
});*

解释:

  • pdfjsLib.GlobalWorkerOptions.workerSrc=pdfWorkerPath;将 PDF 插件的工作路径分配给其全局名称空间
  • 变量_CANVAS是以编程方式创建的,因为 PDF.js 插件将每个页面呈现在一个 HTML 画布元素上
  • 上传 PDF 文档时,文件以 base64 字符串的形式作为变量pdf_url读取,以检索_PDF_DOC对象
  • 编写 while 循环是为了处理上传的 PDF 文档的各个页面。对于呈现在画布元素上的每个页面,图像数据被提取为变量b64Str,然后被解析为实用函数loadImage()。这将返回一个Image()元素,供 Tesseract 的工作人员提取嵌入的文本。
  • 对于处理的每一页图像,inputTxt.insertAdjacentText('beginend', combinedText)将所有提取的文本添加到输入域inputText中,直到 PDF 的所有页面都被处理。

要点:在每个 while 循环中,单个页面图像由单个实例化的 worker 处理。因此,对于后续的单个页面,需要再次实例化单个工作人员来提取嵌入的文本内容。

实施预览:

作者截屏|上传 sample.pdf 文件时,会提取每页的文本,并相应地附加到下面的文本字段。

完整的源代码可以在我的 GitHub repo: 文本到语音转换应用或者在演示中试用!

  • 请注意,从第一部分添加了额外的功能。它们是:

作者图片|带**的按钮(👆)** 可供用户选择,了解更多实施细节*

作者图片|选择【┏🠋┓下载文本】使用户能够下载文本字段中所有提取的文本内容

非常感谢你坚持到这篇文章的结尾!❤希望这个实现对您有所帮助。

如果您对更多 GIS、数据分析和 Web 应用相关的内容感兴趣,请随时关注我的 Medium 。会非常感激—😀

— 🌮请给我买一份玉米卷🎀˶❛◡❛)

*https://geek-cc.medium.com/membership *

用 sktime 构建复杂的时间序列回归管道

原文:https://towardsdatascience.com/build-complex-time-series-regression-pipelines-with-sktime-910bc25c96b6

如何通过 sktime 使用 scikit-learn 和 XGBoost 模型预测

马库斯·温克勒在 Unsplash 上的照片

停止使用 scikit-learn 进行预测。您可以使用现有的回归管道并不意味着您应该这样做。或者,你不厌倦使用同样的老技术,如指数*滑和 ARIMA 预测吗?用更高级的算法比如梯度提升树不是更好玩吗?

使用 sktime ,你可以做到这一点(以及更多)。 Sktime 是一个库,可以让你安全地使用任何 scikit-learn 兼容的回归模型进行时间序列预测。本教程将讨论如何使用 sktime 将时间序列预测问题转化为回归问题。我还将向您展示如何用流行的库 XGBoost 构建一个复杂的时间序列预测器。

背景和动机

什么是时间序列预测?

时间序列预测是一种预测一个或多个未来值的技术。像回归建模一样,数据从业者可以基于历史数据拟合模型,并使用该模型预测未来的观察结果。

预测中使用的一些最流行的模型是单变量模型。单变量模型仅使用以前的观测值进行预测。指数*滑模型使用过去观察值的加权*均值来预测未来值,最*的数据点被赋予更大的权重。 ARIMA 模型基于数据中的自相关进行预测。自相关测量时间序列的时移(即滞后)值之间的关系。

如果时间序列预测听起来类似于回归建模,那么你是正确的。我们可以使用回归模型进行时间序列预测。

为什么我们要使用回归进行时间序列预测?

您应该使用回归模型的原因之一是为了提高模型性能。回归技术是灵活的,你可以超越过去观察的单变量模型。包含时间步长要素(如星期几或节假日)可以丰富您的数据,并有可能发现数据中隐藏的趋势。

其次,你可能已经使用了流行的机器学习框架,比如 scikit-learnXGBoost 。如果您已经熟悉这些库,将时间序列预测任务转换为回归问题可以节省您的时间。

然而,现代预测库,如脸书的 预言家 ,也提供了多变量分析的灵活性和简单的 API。尽管如此,使用回归技术进行预测应该是您的数据科学工具包中的另一个工具。

在时间序列回归中,我们需要注意什么?

时间序列回归并非没有问题。自相关观测违反了线性回归模型的假设。然而,更复杂的回归模型,如梯度树提升,通常对多重共线性具有鲁棒性。

此外,如果我们盲目地拟合和调整我们的回归模型,我们可能会引入数据泄漏。数据泄漏是指在模型训练和验证阶段使用的信息,否则这些信息将无法用于预测。例如,使用 K-fold 交叉验证对数据进行随机洗牌可以让你的模型预测未来。相反,你应该使用时态交叉验证

您可以使用 sktime 框架,而不是从头开始实现时间序列回归模型。

为什么要用 sktime?

Sktime 是一个开源的框架,用于对时间序列进行建模的各种机器学习任务,包括时间序列回归、分类、聚类和标注。该框架结合了几个库的特性,用户体验类似于 scikit-learn

我们将使用 sktime 完成预测任务,因为它提供了功能,例如:

  • 归约:使用与 scikit-learn API 兼容的估计器构建时间序列预测器
  • 调整:使用带有时间交叉验证的网格搜索策略确定超参数的值
  • 评估 : sktime 包括几个性能指标(如 MAPE、梅斯),并为定制评分器和回溯测试提供了一个简单的实现
  • 流水线:scikit-learn 流水线 的扩展,它是一系列级联的转换器,用来构建一个预测器
  • AutoML :自动调整策略,在一系列模型和超参数中确定最佳预测器

Sktime 还提供了其他几类预测模型,包括指数*滑、ARIMA、BATS 和 Prophet。我们不会在本教程中讨论这些预测模型,但我鼓励您在模型选择阶段实现这些预测器。

代码示例

资料组

我们将在本教程中使用的数据集是澳大利亚墨尔本[1]每小时的行人计数。我们将我们的分析限制在 2010 年 1 月 1 日至 2019 年 12 月 31 日。我每周都汇总数据。

如果我们可视化我们的数据,我们可以看到大约从 2013 年年中开始的总体上升趋势。我们还可以在 12 月份看到更多的行人,可能是因为圣诞购物,在 7 月份是因为财政年度末的销售。我们的数据看起来是不稳定的,当我们运行一个扩展的 Dickey-Fuller 测试时得到了证实。

作者图片

线性回归的简单预测器

我们将运行的第一个示例是一个使用线性回归的简单预测器。我们先用 sktime 的temporal _ train _ test _ split把最后 26 周的数据作为我们的测试集。该函数不会打乱数据。因此,它适用于预测。

from sktime.forecasting.model_selection import temporal_train_test_splity_train, y_test = temporal_train_test_split(y, test_size=26)

我们还指定了我们的预测范围,即我们将预测的 26 周的测试数据。我们使用 预测地*线 对象来实现这一点。

from sktime.forecasting.base import ForecastingHorizonfh = ForecastingHorizon(y_test.index, is_relative=False)

准备工作完成后,我们可以继续实例化我们的scikit-learnlinear regression对象,它将成为我们预测器的估计器。或者,您可以使用其他 scikit-learn 估算器或 scikit-learn API 兼容估算器,例如 XGBoost 。我将在下面的例子中演示这一点。

from sklearn.linear_model import LinearRegressionregressor = LinearRegression()

Sktime 的make _ reduction函数将时间序列转换成与我们的 scikit-learn 估计器兼容的表格数据。参数“window_length”控制滑动窗口变换中的滞后数。

考虑下面这个简单的时间序列,用【y】表示。如果我们应用我们的 make_reduction 函数,在窗口长度等于 3 的情况下,我们生成一个具有三个输入变量的表格数据集,分别表示为‘lag _ 1’、‘lag _ 2’和‘lag _ 3。该函数将一维时间序列数据集转换为与回归估计器兼容的格式。

作者图片

此外, make_reduction 函数有一个输入“策略”,它控制预测器如何生成其预测。当我们进行不止一步的预测时,该参数是相关的。在我们的例子中,我们预测未来 26 周的行人数量(多步),而不仅仅是下一周(一步)。

我们的多步预测策略有三个选项可供选择:

  • 直接:我们为我们预测的每个时期创建一个单独的模型。在我们的例子中,我们拟合了 26 个模型,每个模型进行一次预测。
  • 递归:我们拟合单个一步先行模型。然而,我们使用前一个时间步的输出作为后面的输入。例如,我们使用下周的预测作为两周后预测的输入,等等。
  • 多输出:一个模型用于在一次预测中预测整个时间序列范围。此选项的使用取决于是否有一个能够预测序列的模型。

在我们的例子中,我们将使用递归策略。

from sktime.forecasting.compose import make_reductionforecaster = make_reduction(regressor, window_length=52, strategy="recursive")

我们现在可以拟合我们的线性回归预测器,并根据我们的测试数据预测 26 周的行人数量。

forecaster.fit(y_train)
y_pred = forecaster.predict(fh)

然后,我们相对于训练和测试数据绘制我们的预测,以确定模型的适用性。 Sktime 使用 plot_series 实用函数使这变得简单。

from sktime.utils.plotting import plot_seriesplot_series(y_train['2018-07-01':], y_test, y_pred, labels=["y_train", "y_test", "y_pred"], x_label='Date', y_label='Count pedestrians');

作者图片

我们的线性回归预测器似乎给了我们一个合理的拟合。然而,这是一个保守的预测,错过了测试数据的波峰和波谷。

我们使用mean _ absolute _ percentage _ error(MAPE)来评估我们的模型,结果为 4.8%。**

**from sktime.performance_metrics.forecasting import mean_absolute_percentage_errorprint('MAPE: %.4f' % mean_absolute_percentage_error(y_test, y_pred, symmetric=False))**

如果我们把所有这些放在一起,我们的代码看起来像这样:

让我们看看是否可以使用更复杂的算法,比如 XGBoost ,来解决这个问题。

使用 XGBoost 和外部输入进行时间序列预测

XGBoost 是一个梯度推进机器的实现,因其速度和性能而流行于表格机器学习任务。我们可以使用 XGBoost 进行时间序列预测,因为它有一个与 sktime 的 make_reduction 函数兼容的 scikit-learn 包装器

**from xgboost import XGBRegressorregressor = XGBRegressor(objective='reg:squarederror', random_state=42)
forecaster = make_reduction(regressor, window_length=52, strategy="recursive")**

我们还将包括外生数据,这是另一个有助于预测的时间序列。让我们包括几个虚拟变量来表示一年中的月份,分成我们的训练集和测试集。

**# Create an exogenous dataframe indicating the month
X = pd.DataFrame({'month': y.index.month}, index=y.index)
X = pd.get_dummies(X.astype(str), drop_first=True)# Split into train and test
X_train, X_test = temporal_train_test_split(X, test_size=26)**

当我们调用 fit 和 predict 方法时,我们包括了外部数据。您可以在recursivetabularregressionpredictor文档中找到更多信息。

**forecaster.fit(y=y_train, X=X_train)
y_pred = forecaster.predict(fh=fh, X=X_test)**

从视觉上看,我们的预测似乎比我们的线性回归预测更差。当我们看看 MAPE 时,我们的预期得到了证实,该国经济增长率已升至 7.1%。因为我们使用了默认的超参数值,所以 XGBoost 预测器可能是欠拟合的。

下面的例子将演示我们如何为我们的 XGBoost 预测器调整超参数。

作者图片

您可以在下面找到这个例子的代码。

调整我们预测器的超参数

我们的 XGBRegressor 有几个我们可以调优的超参数,如这篇文章中所述。我们希望调整预测器的超参数,看看是否可以改善性能。

在调整超参数之前,我们必须向数据中添加一个验证集。我们有几个由 sktime 实现的策略,包括一个single window splittersliding window splitterexpanding window splitter。为了简单起见,我们将创建一个单一的验证集,其大小与我们的测试集相同。然而,这篇文章解释了各种策略之间的差异。

**from sktime.forecasting.model_selection import SingleWindowSplittervalidation_size = 26
cv = SingleWindowSplitter(window_length=len(y)-validation_size, fh=validation_size)**

Sktime 实现了两种超参数调优策略:随机搜索网格搜索。我们将使用 100 次迭代的随机搜索。

**from sktime.forecasting.model_selection import ForecastingRandomizedSearchCVparam_grid = {
    'estimator__max_depth': [3, 5, 6, 10, 15, 20],
    'estimator__learning_rate': [0.01, 0.1, 0.2, 0.3],
    'estimator__subsample': np.arange(0.5, 1.0, 0.1),
    'estimator__colsample_bytree': np.arange(0.4, 1.0, 0.1),
    'estimator__colsample_bylevel': np.arange(0.4, 1.0, 0.1),
    'estimator__n_estimators': [100, 500, 1000]
}regressor = XGBRegressor(objective='reg:squarederror', random_state=42)
forecaster = make_reduction(regressor, window_length=52, strategy="recursive")gscv = ForecastingRandomizedSearchCV(forecaster, cv=cv, param_distributions=param_grid, n_iter=100, random_state=42)**

同样,我们适应并预测。

**gscv.fit(y=y_train, X=X_train)
y_pred = gscv.predict(fh=fh, X=X_test)**

但是这一次,我们可以检查我们的随机搜索对象,看看预测器如何使用超参数的不同组合来执行。

**gscv.cv_results_**

作者图片

有趣的是,我们的预测者在我们的测试数据上表现更差,正如我们的可视化和 MAPE 增加到 7.8%所示。

作者图片

您可以在下面的示例中找到完整的代码。

向我们的预测渠道添加组件

我们以前在非*稳数据上训练我们的模型,导致预测不佳。使用 statsmodels ,我们可以分解我们的行人时间序列来观察趋势和季节性。

**from statsmodels.tsa.seasonal import seasonal_decomposeresult = seasonal_decompose(y_train, model='multiplicative')
result.plot()
plt.show()**

我们假设我们的时间序列是乘法的,而不是加法的,因为我们的时间序列的振荡幅度似乎随着时间的推移而增加。查看下面的分解图,我们看到我们的趋势(第二个子曲线)自 2013 年年中以来有所增加,并且有一个季节性模式,圣诞节和年中行人流量增加,而 1 月的第一周流量减少。

作者图片

因此,我们的预测者的表现可以通过去除时间序列的季节性和趋势性,产生一个更接**稳的时间序列来提高。 Sktime 包括两个类, 去季节器去渲染 ,我们可以将它们纳入我们的预测管道。

**from sktime.forecasting.compose import TransformedTargetForecaster
from sktime.transformations.series.detrend import Deseasonalizer, Detrender
from sktime.forecasting.trend import PolynomialTrendForecasterregressor = XGBRegressor(objective='reg:squarederror', random_state=42)forecaster = TransformedTargetForecaster(
    [
        ("deseasonalize", Deseasonalizer(model="multiplicative", sp=52)),
        ("detrend", Detrender(forecaster=PolynomialTrendForecaster(degree=1))),
        ("forecast", make_reduction(regressor, window_length=52, strategy="recursive"),
        ),
    ]
)**

我们可以使用随机网格搜索来调整去季节化器去渲染的参数。例如,我们可以看到加法或乘法模型是否是最好的,或者我们希望使用什么次数的多项式来模拟趋势。

**param_grid = {
    'deseasonalize__model': ['multiplicative', 'additive'],
    'detrend__forecaster__degree': [1, 2, 3],
    'forecast__estimator__max_depth': [3, 5, 6, 10, 15, 20],
    'forecast__estimator__learning_rate': [0.01, 0.1, 0.2, 0.3],
    'forecast__estimator__subsample': np.arange(0.5, 1.0, 0.1),
    'forecast__estimator__colsample_bytree': np.arange(0.4, 1.0, 0.1),
    'forecast__estimator__colsample_bylevel': np.arange(0.4, 1.0, 0.1),
    'forecast__estimator__n_estimators': [100, 500, 1000]
}gscv = ForecastingRandomizedSearchCV(forecaster, cv=cv, param_distributions=param_grid, n_iter=100, random_state=42) gscv.fit(y=y_train, X=X_train)
y_pred = gscv.predict(fh=fh, X=X_test)**

调整我们的预测管道后,我们可以可视化我们的预测。我们观察到我们的预测更接*我们的趋势数据,这表明从我们的时间序列中去除季节性和趋势提高了我们模型的性能。MAPE 降至 4.7%,是我们测试的所有车型中表现最好的。

作者图片

下面是这个例子的完整代码。

Sktime 是一个多功能的库,可以让你使用与 scikit-learn 兼容的回归模型进行时间序列预测。您可以构建复杂的多步管道,甚至可以与最先进的预测算法相媲美。下次你做预测项目时,不要只使用 ARIMA。试一试。

喜欢你读的书吗?跟着我上。否则,发微博给我或者在 LinkedIn 上加我。

你可以在 GitHub 上找到这篇文章中用到的所有代码。

[1]墨尔本市,行人计数系统—每月(每小时计数) (2022),墨尔本市公开数据,根据知识共享署名 3.0 澳大利亚发布(CC BY 3.0 AU)

为 Jupyter 笔记本构建 Docker 映像,并在 Cloud 的 VertexAI 上运行

原文:https://towardsdatascience.com/build-docker-image-for-jupyter-notebook-and-run-on-clouds-vertexai-9bd97c48c52d

使用 Docker、Bash、Scheduler、Pub/Sub 和 Function 在 Google 云*台中自动运行任何 Python 脚本

用码头集装箱装运你的应用程序

您是否已经成功地在本地编写了一个 Python 应用程序,现在想要将其带到云中?这是一个简单易懂的详尽的一步一步的教程,讲述了如何将 Python 脚本转换成 Docker 映像并将其推送到 Google Cloud Registry。在 Google 云*台中,可以通过 Pub/Sub 在 VertexAI 中自动调用这个 Docker 镜像。本教程是在 Windows 计算机上创建的,但是对于 Linux 或 Mac,基本步骤是相同的。在本文的最后,您将能够在您的操作系统上创建自己的 Docker 映像,并在 VertexAI 中自动触发 Python 脚本。

你将经历什么:

  • 安装“Docker 桌面”
  • Docker 映像和容器:构建、标记、运行和推送到 GCloud
  • 通过 Bash、Scheduler、Pub/Sub 和 Function 自动运行 GCloud 的 VertexAI 中的任何 Python 脚本

初期状况

此目录结构中有以下文件:

Sourcecode 文件夹包含 Jupyter 笔记本“dockerVertexai”和“input.csv”文件:

Jupyter 笔记本本身只是一个很小的 Python 应用程序:

import pandas as pd
df=pd.read_csv('input.csv')
df['Output'] ='Now With Output added'
df.to_csv('output.csv')

这个脚本所做的只是导入一个名为“input”的 csv 文件:

..向该数据帧添加新列“Output”,并将结果导出为新文件“output.csv”:

尽管这可能不是世界上最令人印象深刻的脚本,但它对您以后在 VertexAI 中轻松确认功能非常有用。此外,希望这个脚本为常见的“hello world Flask Docker”示例提供一个有用的扩展。尽管这个脚本非常简单,但它向您展示了如何不仅使用源代码文件,而且还使用 cloud 的 notebook 实例中的其他输入输出文件。有了这些知识,在云中使用 Python 就没有任何限制。

准备 :

如果还没有,您需要在开始构建 Docker 映像之前下载 Docker Desktop:

https://www.docker.com/products/docker-desktop/

一旦下载了(在本例中是 Windows)版本,您只需打开应用程序即可启动 Docker Desktop:

等待 Docker 桌面启动(图片由作者提供)

对于我们的目的,我们不需要做任何事情,只是启动 Docker 桌面。

下一个准备步骤是 Dockerfile 文件。

Dockerfile 文件

先看看 docker 文件:

FROM gcr.io/deeplearning-platform-release/base-cpu:latest

# RUN pip install torch # you can pip install any needed package like this

RUN mkdir -p /Sourcecode 

COPY /Sourcecode /Sourcecode 

Dockerfile 定义稍后运行实例时将使用哪个 vm(虚拟机)映像。在这种情况下,你可以选择深度学习-*台-发布。

如果需要任何额外的 Python 包,也可以在这里安装 pip(例如,运行 pip install python-dotenv)。因为您将只使用 Pandas(它已经随 deeplearning-platform-release 一起提供),所以没有必要 pip 安装它。

一旦实例稍后启动并运行,命令“run mkdir -p /Sourcecode”将创建一个新文件夹“Sourcecode”。

dockerfile 中的最后一个命令将把所有文件从 Sourcecode 文件夹(这是您本地机器上的路径中的文件夹,稍后您将从这里构建 docker 映像)复制到正在运行的实例中新创建的 Sourcecode 文件夹中(您将在几个步骤中再次看到这一点)。

Dockerignore

此外,您还可以选择存储一个 Dockerignore 文件。这是为了排除执行 Python 代码不需要的包。这样做的目的是使图像尽可能小,而不是构建不必要的包。

__pycache__
Sourcecode/temp

Dockerignore 与本教程的后续步骤无关。

要构建 Docker 映像,您还可以使用 Requirement.txt 文件。在本文中,您不会使用这种方法,但是如果您感兴趣的话,您可能想阅读这篇文章(链接)。

作为最后的准备(在开始 Docker 构建之前),您需要在 Google 云*台中部署一个项目。

谷歌云项目

我想你已经是这样了。如果你不知道怎么做,你可能会发现这是一个有用的链接

在这一点上,我的建议是复制 ProjectId,因为在本教程中你会多次用到它。本例中 GCP 的项目 ID 是:social sensition-XYZ

命令行界面(CLI)

gcloud (Google Cloud)命令行界面是创建和管理 Google Cloud 资源的主要 CLI 工具。CLI 工具从命令行以及脚本和其他自动化中执行许多常见的*台任务。如果你还没有下载 CLI,你可以在这里找到说明

您将首先初始化一个配置,以便能够在命令行中使用您的 Google Cloud 项目:

gcloud init

使用您现有的帐户登记:

..最后选择合适的项目编号(如果有的话)。

为项目启用工件注册

默认情况下,工件注册对于新项目是不可用的。所以必须先启用它(否则无法发布到 GCloud Artifact Registry)。

要启用工件注册,请执行以下命令:

gcloud services enable artifactregistry.googleapis.com

并添加一个存储库主机名列表,如 europe-west3-docker.pkg.dev:

gcloud auth configure-docker europe-west3-docker.pkg.dev

很好,您已经完成了所有准备工作,所以您终于可以开始构建 Docker 了。

Docker 映像构建

在您的终端中,移动到存储本地文件的文件夹,并输入:

docker build --tag python-docker .

请注意圆点前的空格。

如果你收到一个错误信息,如“Docker 守护进程没有运行”,这可能是因为你忘记启动 Docker 桌面应用程序。只需启动 Docker-Desktop(就像开头提到的那样),然后重新运行 docker build 命令。

大楼后..:

..已完成:

如果您没有在 docker 文件中添加命令,将不会有“运行 pip 安装”命令

..Docker 映像已经启动并运行。您还可以通过查看 Docker 桌面应用程序获得额外的保证:

或者您也可以在您的终端中交叉检查:

docker images

您将需要在下一步中标记 ImageID(按作者标记图像)

Docker 图像标签

从上面的 Docker images 命令中复制并粘贴 ImageID,并将其用作 Docker 标签。其次,您需要为您的 Google Cloud 项目复制路径。这包括 Google Cloud 的项目 ID(social senement-XYZ)和存储库(工件设置中的名称):

用你的项目 id(图片由作者提供)替换社会感知空白(社会感知-XYZ)

码头运行

尽管这一步与我们没有进一步的关联,但为了完整起见,还是简单地提一下。

只需使用 run 将您的映像作为容器运行:

docker run python-docker

工件储存库

很抱歉,在这一点上还有另一个交叉引用。但到目前为止,我们还没有在 GCloud *台中创建工件库。但是在你推广 Docker 形象之前,这是非常重要的。幸好这是儿戏:

您必须创建一个存储库来上传工件。在您的案例 Docker 中,每个存储库可以包含受支持格式的工件。

我将这个存储库称为“tweetimentrep”(图片由作者提供)

选择一个适合自己的地区。对我来说,那就是法兰克福:

谷歌推荐工件而不是容器注册表,这就是我们选择它们的原因

注意:在继续之前,请确保您确实已经在您的 GCloud 工件中创建了一个存储库名称:

如果存储库存在,您只能标记 python-docker 图像(按作者分类的图像)

很抱歉出现不一致:当下面提到 tweetiment-xyz 时,指的是同一个项目 social sensition-XYZ。

Docker 图像标签

现在继续并相应地标记您的设置:

docker tag 4f0eac4b3b65 europe-west3-docker.pkg.dev/tweetiment-xyz/tweetimentrep/python-docker

这就是你标记 Docker 图像的简单方式。

现在您已经标记了 Docker 图像,您可以将它推入 GCP 的注册表中:

docker push europe-west3-docker.pkg.dev/tweetiment-xyz/tweetimentrep/python-docker

如果您收到类似以下内容的消息:

..这仅仅意味着您必须激活工件注册 API(并且不要忘记给它一个存储库名称,否则您将不能正确地标记和推送您的 Docker 映像)。

以可伸缩和集成的方式存储和管理工件(作者图片)

现在,您可以重新运行 push 命令。如果 API 仍然没有激活,只需等待几分钟,直到它激活。

推送准备就绪后:

您将能够在您的工件注册表中看到它:

很好,您已经将 Python 代码及其文件夹结构和内容作为 Docker 映像传输到 Google Cloud 了!这意味着你可以在谷歌云中创建一个笔记本,在那里你可以手动运行 Jupyter 笔记本(或者 Python。py 代码)。

韦尔泰赛

了解如何手动创建笔记本实例是很有帮助的。如果不知道如何,这个链接应该是支持。但是学习如何自动运行这样的实例更令人兴奋。要做到这一点,首先看一下一旦创建了实例,如何在 VertexAI 实例中自动运行 Python 脚本。为此,使用一个 bash 脚本(一个. sh 文件)。

Bash 脚本

#!/bin/bash
# copy sourcode files from docker container to vm directory
sudo docker cp -a payload-container:/Sourcecode/ /home/jupyter
sudo -i
cd /home/jupyter/Sourcecode
# Execute notebook
ipython -c “%run dockerVertexai.ipynb”

现在将 startup script.sh 上传到您的云存储桶中:

云存储是一种托管服务,用于存储(非)结构化数据(图片由作者提供)

转到 VertexAI 并激活 VertexAI API 和笔记本 API(如果还没有激活的话)。

在工作台中,单击“新笔记本”,并从顶部选择“定制”作为第一个选项:

在下一个表单中,从工件注册表中选择您的容器映像。

通常你会选择最常用的版本(图片由作者提供)

另外,不要忘记选择一个脚本在创建后运行。这将是你。sh 文件,这是您几分钟前刚刚保存在存储桶中的文件:

现在你可以走了:

现在,您已经准备好创建 Jupyter 实验室,它还支持 Jupyter 笔记本(图片由作者提供)

多亏了启动脚本,Jupyter 笔记本在 Jupyter 实验室实例启动时被自动执行。因为存在 output.csv(以前不存在),所以您可以确保脚本运行:

瞧,output.csv 已经自动生成(图片由作者提供)

很好,脚本被自动执行并创建了一个输出文件。但是不幸的是,这个输出文件没有被持久化。所以最好把这个文件保存在云存储中。也就是在我们之前创造的桶里。

from google.cloud import storage # Imports the Google Cloud client library
storage_client = storage.Client() # Instantiates a client
BUCKET_NAME =’socialsenti’
blob_name=’output.csv’
storage_client = storage.Client()
bucket = storage_client.bucket(BUCKET_NAME)
blob = bucket.blob(blob_name)
with blob.open(“w”) as f:
 f.write(“Output file has been saved into your bucket”)
with blob.open(“r”) as f:
 print(f.read())

将此文件添加到 Jupyter 笔记本的一个新单元中,您将看到输出文件随后会保存在您的存储桶中。

如果您想保存结果,请将其保存到存储器中(图片由作者提供)

那更好。但是仍然有一个可能的优化。因为直到现在,实例一直在运行,即使结果(输出文件)已经创建。因此,您需要一种方法在脚本结束时关闭虚拟机(最终 VertexAI 只使用 Google 计算引擎 GCE ),这样您就不会产生更多的备用成本。

现在让我们进入自动化的下一步。现在,您将从终端通过命令创建实例,而不是手动启动实例:

gcloud notebooks instances create instancetweetiment - container-repository=europe-west3-docker.pkg.dev/tweetiment-xyz/tweetimentrep/python-docker - container-tag=latest - machine-type=n1-standard-4 - location=us-central1-b - post-startup-script="gs://socialsenti/default-startup-script.sh"

或者,如果您愿意,您可以直接从 Jupyter 笔记本上运行它。

from google.cloud.notebooks_v1.types import Instance, VmImage, ContainerImage
from google.cloud import notebooks_v1
client = notebooks_v1.NotebookServiceClient()
notebook_instance = Instance(container_image=ContainerImage(repository="europe-west3-docker.pkg.dev/tweetiment-xyz/tweetimentrep/python-docker",),machine_type="n1-standard-8",post_startup_script="gs://socialsenti/default-startup-script.sh")
parent = "projects/tweetiment-xyz/locations/us-central1-a"
request = notebooks_v1.CreateInstanceRequest(parent=parent,instance_id="notetweeti",instance=notebook_instance,)
op = client.create_instance(request=request)
op.result()

为了防止第一次运行时由于缺少身份验证而出现错误,请运行以下命令。这将创建凭据文件:

gcloud auth application-default login

该凭据文件通常存储在以下路径下:

% APPDATA % \ g cloud \ application _ default _ credentials . JSON

现在,您可以通过编程方式访问 GCP。如果您已经创建了一个笔记本(无论是手动的还是编程的),那么您可以使用这个 Python 代码来读取它。Instance_Name 是笔记本本身的名称。这段摘录来自 GCP 文档:

from google.cloud import notebooks_v1

def sample_get_instance():
    # Create a client
    client = notebooks_v1.NotebookServiceClient()
    # Initialize request argument(s)
    request = notebooks_v1.GetInstanceRequest(
        name="projects/tweetiment-xyz/locations/us-central1-a/instances/test", 
    )
    # Make the request
    response = client.get_instance(request=request)
    # Handle the response
    print(response)
sample_get_instance()

如果你想安排一个重复的 Python 脚本,你可以使用谷歌的云调度器,结合云功能和发布/订阅。

云调度程序

选择发布/订阅作为目标类型和适当的发布/订阅主题:

您不需要输入邮件正文(作者图片)

您可以等待,直到调度程序变为活动状态。您也可以随时手动启动它进行测试。

计划者很好。但是有时您也希望在网页上执行一个动作后立即执行 Python 脚本。为此,云函数是一个不错的选择。

云函数

选择发布/订阅作为函数的触发器(图片由作者提供)

选择 Python 作为运行时,并输入“TweetiNoteStartTopic”作为入口点。那个入口点也将是你的函数名。所以最终云函数看起来是这样的:

在 requirements.txt 中,您可以根据需要放置所有依赖项:

# Function dependencies, for example:
google-cloud-notebooks>=1.4.4
google-cloud>=0.34.0

您的 main.py 可能类似于:

import base64
from google.cloud.notebooks_v1.types import Instance,  ContainerImage
from google.cloud import notebooks_v1

def TweetiNoteStartTopic(event, context):
  client = notebooks_v1.NotebookServiceClient()
  notebook_instance = Instance(container_image=ContainerImage(repository="europe-west3-docker.pkg.dev/tweetiment-xyz/tweetimentrep/python-docker",),machine_type="n1-standard-8",post_startup_script="gs://socialsenti/default-startup-script.sh")
  parent = "projects/tweetiment-xyz/locations/us-central1-a"
  request = notebooks_v1.CreateInstanceRequest(parent=parent,instance_id="notetweeti",instance=notebook_instance,)
  op = client.create_instance(request=request)
  op.result()
  print("finished")

发布/订阅

请注意,您也可以随时触发您的云功能。只需转到 test 选项卡并输入以下 json:

{ " data ":" TweetiNoteStartTopic " }

恭喜

您现在可以在云中以编程方式运行任何 Python 脚本了!要么直接通过云函数作为纯 Python 脚本,要么作为虚拟机上的 Docker 容器。是 VertexAI 让你在 Jupyter 实验室环境中运行 Jupyter 笔记本,而 Jupyter 实验室环境本身运行在谷歌云引擎(GCE)上的虚拟机上。感谢谷歌硬件——你可以在 VertexAI 上编程设置——几乎没有限制。或者你以前用过 96vCPU 和 360GB Ram 吗?我认为现在自豪地穿上你的谷歌云衬衫是合理的:-)

非常感谢您的阅读!希望这篇文章对你有帮助。请随时在 LinkedInTwitter工作室与我联系。

使用 Jupyter 笔记本电脑构建高度互动的项目

原文:https://towardsdatascience.com/build-highly-interactive-projects-with-jupyter-notebooks-c7fcf7f87633

利用小部件为构建交互式 Python 和数据科学项目创建用户友好的环境(奖金 2 项目!)

米格尔·托马斯在 Unsplash 上拍摄的照片

大多数数据科学和 Python 项目通常是很少或没有交互性的代码行。对于初级程序员来说,这可能是一个相当大的问题,因为他们需要不断地使用 print 语句来理解他们当前正在进行的项目的逻辑和工作流。

即使使用图形用户界面(GUI)工具,如 Tkinter、Kivy 和其他类似的库,编程要求也更多。然而,如果有一种方法可以让用户在 Jupyter 笔记本中利用小部件和单行代码开发高度交互性,那会怎么样呢?

我们将在本文中探索这样的场景,在这里我们将理解一些交互式小部件特性的基础。然后,我们还将继续用这些介绍性的小部件构建几个简单、有趣和交互式的项目。

在我们深入 IPython 小部件的概念之前,有必要对 Jupyter 笔记本有一个基本的了解。在我以前的一篇文章中,我已经介绍了与 Jupyter 笔记本相关的大部分基本概念。你可以从下面的链接中找到这个主题。

注: GitHub 链接基础知识和项目在文末提供。

IPython 小部件入门:

Ipywidgets,也称为 Jupyter 小部件或简称为“小部件”,是用于 Jupyter 笔记本和 IPython 内核的交互式 HTML 小部件。一般来说,小部件的作用是创建一个交互式 GUI 对象,允许用户开发高度紧凑的项目。它们还可以用于在 Python 和 JavaScript 之间同步有状态和无状态信息。有关更多信息,请点击这些参考文档的链接。

安装这些小部件的过程非常方便,可以用下面的命令来完成。在安装期间,ipywidgets 库自动配置 Jupyter 笔记本的所有必要要求,而不需要任何额外的配置。

pip install ipywidgets

一旦我们成功安装了 ipywidgets 库,我们就可以根据需要在每个 Jupyter 笔记本中创建一个交互式项目。第一步在开始使用库中可用的不同类型的小部件之前,我们需要导入 ipywidgets 库和 IPython display 命令,以便向用户显示这些交互式小部件。

# Importing the ipywidgets library and IPython display moduleimport ipywidgets as widgets
from IPython.display import display

一旦导入了必要的库,用户就可以开始实现 ipywidgets 工具包中提供的众多小部件。在本文的这一部分,我们将探索程序员和开发人员最常用的一些小部件选项的基础。

1.滑块:

widgets.IntSlider(value=50,
                  min=0,
                  max=100)

作者图片

我们要看的第一个小部件是整数滑块。它为用户提供了一个有用的选项,可以在特定范围内使用滑块来覆盖任何特定任务的各种操作。但是,您只能使用整数滑块来检索整数值,在这里我们指定默认值以及滑块的最小和最大范围。

显示的滑块的默认方向是水*的。开发人员也可以使方向垂直。滑块还有其他选项,包括浮动滑块和浮动日志滑块,以及许多其他类似的选项。

2.下拉列表:

widgets.Dropdown(
    options=['1', '2', '3'],
    value='1',
    description='Number:',
    disabled=False,
)

作者图片

我们将在本节中看到的下一个交互式小部件是下拉小部件。这个小部件显示一个值列表,允许用户从可用选项中进行选择。在上面的代码片段中,我将下拉框的值包括为 1、2 和 3,默认值为“1”提供的描述是数字。我们可以使用下拉工具来执行操作,为执行特定任务选择任何所需的值。

3.文本框:

widgets.IntText(
    value=3,
    description='Numbers:',
    disabled=False
)

作者图片

我们接下来要看的 ipywidget 是 textbox 小部件。顾名思义,这个小部件对于在 GUI 框中输入文本数据非常有用。在上面的代码片段中,我们可以注意到我们使用了整数文本小部件,允许用户输入一些整数类型的数据。文本框输入还有其他几个选项,允许用户输入字符串数据,包括用于执行以下操作的文本和文本区域小部件。下面是一个示例代码,显示了输入字符串类型数据的替代选项。

备选代码:

widgets.Text(
    value='Hello World',
    placeholder='Type something',
    description='String:',
    disabled=**False**
)

4.单选按钮:

widgets.RadioButtons(
    options=['1', '2', '3'],
    description='Numbers:',
    disabled=False
)

作者图片

我们将讨论的下一个 ipywidget 是单选按钮小部件,它允许用户选择一组选项。如上面的代码片段所示,我们可以为单选按钮提供一个简短的描述,然后相应地声明选项。有大量的实验和项目可以使用这些单选按钮,尤其是只允许选择一个选项的情况,比如多项选择题。

5.复选框:

widgets.Checkbox(
    value=False,
    description='I Agree to the terms and conditions',
    disabled=False,
    indent=False
)

作者图片

我们将探索的倒数第二个基本小部件是复选框 ipywidget。在 checkbox 小部件的帮助下,我们可以让用户选择“是”或“否”。该复选框最初是未选中的。一旦用户点击复选框,我们可以激活进一步的命令和操作。这些复选框对各种项目都很有用,包括条款和条件的协议,如上所示。

6.切换:

widgets.ToggleButtons(
    options=['1', '2', '3'],
    description='Numbers:',
    disabled=False,
    button_style=''
)

作者图片

我们要看的最后一个基本部件是切换按钮。使用这些小部件,我们可以在一堆按钮之间切换,每个按钮的角色和功能都相应地改变。在上面的代码片段中,我们可以注意到,我们有一个显示给用户的选项列表,以及一个我们可以根据需求添加的附加描述。

在接下来的部分中,我们将看几个简单的项目,我们可以在 Jupyter 笔记本中的 ipywidgets 的帮助下构建这些项目。在我们继续之前,我建议对其他类似的 GUI 技术感兴趣的读者阅读我以前的一篇文章,在这篇文章中,我介绍了 Python 开发人员可以利用的七个最佳图形工具,以及下面提供的链接中的一些入门代码。

</7-best-ui-graphics-tools-for-python-developers-with-starter-codes-2e46c248b47c>

注: 在 Jupyter 笔记本的菜单栏上的 widget 图标中,我们可以点击保存 widget 选项我们临时保存图标的 widget 状态。如果您有兴趣了解更多关于不同类型的小部件选项和 ipywidget 库的信息,我建议您查看下面的文档

项目 1:温度转换器

有了对 Jupyter 笔记本小部件概念的基本理解,我们将尝试用这些小部件开发几个有趣而简单的交互项目。在第一个项目中,我们将关注温度从摄氏到华氏的转换。我们将使用滑块小部件来完成这项任务。让我们通过导入必要的库来开始这个项目,如下面的代码片段所示。

# Importing the ipywidgets library and IPython display moduleimport ipywidgets as widgets
from IPython.display import display

在下一步中,我们将创建用于执行温度转换过程的函数。我们将定义 temp 函数,它将参数作为 slider 小部件的值。下面是将摄氏温度转换成华氏温度的公式。您还可以使用 1.8 来代替代码片段中公式中使用的(9/5)分数值。

# Define the function for temperature conversiondef temp(slider_value):
    # Celsius to Fahrenheit conversion
    F = (slider_value *(9/5)) + 32
    return F

一旦我们完成了温度转换函数的定义,我们就可以继续定义 slider 小部件,通过它我们可以控制摄氏度数。我们可以将这个小部件存储在一个变量中,如下所示。

slider = widgets.IntSlider()

在定义了 slider 小部件之后,我们可以继续显示它,这样用户就可以根据需要相应地改变值。

display(slider)

作者图片

当用户在显示的刻度上移动滑块时,滑块值相应地更新,表示摄氏刻度。我们可以通过之前定义的函数传递这些值,以获得所需的华氏输出。

value = slider.value
temp(value)

输出:

77.0

当我们将滑块值作为 25 摄氏度传递时,我们得到了预期的 77.0 华氏度的响应。这样的构建也可以用于其他项目,如货币转换器等等!

项目 2:测验

在本文的第二个项目中,我们将学习如何在这些 ipywidgets 的帮助下开发一个简单的交互式测验项目。让我们从导入基本的库需求开始,如下面的代码片段所示。

# Importing the ipywidgets library and IPython display moduleimport ipywidgets as widgets
from IPython.display import display
import time

一旦导入了所有必需的库,我们就可以为这个项目创建接下来的两个基本函数了。首先,我们将定义一个函数来检查相关问题的答案。这个函数接受两个参数,即用户输入的问题编号和问题答案。答案被验证,并且用户被通知他们的答案是正确的还是不正确的。

在下一个函数中,我们将从 toggle button 小部件获取一个输入问题编号,并在单选按钮的帮助下显示问题及其各自的多选答案。我们将返回由用户(或玩家)选择并输出作为答案的值。

# Creating the function for storing the Questions and Answers for the mini-quizdef answer_check(question, question_input):
    if question_input == "Question-1":
        if question.value == "7":
            print("Correct Answer!")

        else:
            print("Incorrect Answer!")

    elif question_input == "Question-2":
        if question.value == "366":
            print("Correct Answer!")

        else:
            print("Incorrect Answer!")def mini_quiz(question):
    if question == 'Question-1':
#         q1 = widgets.IntText(value=0, description='How many continents are there in the world?')
        q = widgets.RadioButtons(options=['4', '5', '6', '7'], description='How many continents are there in the world?:')
        display(q)

    elif question == 'Question-2':
        q = widgets.RadioButtons(options=['364', '365', '366', '367'], description='How many days are there in a leap year?:')
        display(q)

    return q

在下一个代码块中,我们将为用户提供一个选项,从他们希望查看和回答的不同类型的问题中进行选择。我只给出了两个切换按钮选项,但是一旦您更好地掌握了 ipywidgets 库,这个项目就可以得到极大的改进。

question = widgets.ToggleButtons(options=['Question-1', 'Question-2'], description='Quiz:')
display(question)

作者图片

现在让我们存储切换按钮的值,如下面的代码片段所示。

question_input = question.value
question_input

最后,我们可以开始调用我们的函数来相应地计算问题和答案。首先,我们将调用第一个函数来传递并显示问题及其各自的答案。

question = mini_quiz(question_input)

最后,我们将调用函数来检查正确的答案。如果用户提供的解决方案是对的,那么一个“正确答案!”将显示注释。否则,答案不正确!”显示注释。

answer_check(question, question_input)

作者图片

上面的截图显示了当用户选择一个不正确的选项时会发生什么。

作者图片

上面的截图显示了当用户选择正确的选项时会发生什么。有几个额外的改进和不同种类的项目,您可以用这个库来构造。我非常鼓励观众尝试各种不同类型的项目。

结论:

普通技术人员在 Unsplash 上拍摄的照片

“在某些方面,编程就像绘画。你从一块空白的画布和一些基本的原材料开始。你综合运用科学、艺术和工艺来决定如何处理它们。”
安德鲁·亨特

交互性在开发用户友好的环境方面发挥着巨大的作用,同时也使初学数据科学和编程的爱好者能够通过更直观的理解探索众多概念。添加各种附加小部件和交互式功能有助于简化项目的导航和探索,因为您可以轻松控制多个参数。

在本文中,我们探索了 ipywidgets 库以及它授予用户访问权限的众多特性。我们首先了解了一些可以在 Jupyter 笔记本中使用的出色的基本小部件。然后,我们构建了几个简单、有趣、交互式的 Python 项目,借助这个工具,您可以轻松地构建这些项目。

如果你想在我的文章发表后第一时间得到通知,请点击下面的链接订阅邮件推荐。如果你希望支持其他作者和我,请订阅下面的链接。

https://bharath-k1297.medium.com/membership

如果你对这篇文章中提到的各点有任何疑问,请在下面的评论中告诉我。我会尽快给你回复。您可以从下面的 GitHub 链接访问基础知识和项目。

看看我的一些与本文主题相关的文章,你可能也会喜欢阅读!

</7-python-programming-tips-to-improve-your-productivity-a57802f225b6>

谢谢你们坚持到最后。我希望你们都喜欢这篇文章。祝大家有美好的一天!

利用 Scala、Spark 和 Hadoop 构建推荐系统

原文:https://towardsdatascience.com/build-recommendation-system-using-scala-spark-and-hadoop-d2ee35c97d3c

在分布式 Hadoop 集群上使用 Scala 和 Spark 构建的电影推荐器

照片由泰森·莫尔特里Unsplash 拍摄

推荐系统是一种广泛使用的机器学习技术,在电子商务(亚马逊,阿里巴巴),视频流(网飞,迪士尼+),社交网络(脸书,Linkedin)和其他许多领域都有很多应用。由于这些服务中的大量数据,现在大多数行业级推荐系统都构建在 Spark 和 Hadoop 这样的大数据框架中。所以在这篇博客中,我想向你展示我是如何使用 Scala、Spark 和 Hadoop 构建一个电影推荐系统的。

1.推荐系统介绍

1.1 不同的推荐系统算法

推荐系统算法可以分为两种主要类型:基于内容的推荐和协同过滤。下面是一个描述它们之间差异的汇总表。

推荐算法:基于内容与协同过滤(图片由作者提供)

1.2 协同过滤和 Spark ALS

在这篇文章中,我们将使用协同过滤作为推荐算法。协同过滤的工作原理是这样的:首先,我们把所有用户对所有项目的评分看作一个矩阵,这个矩阵可以分解为两个独立的矩阵,一个是用户矩阵,其中行代表用户,列是潜在因素;另一种是项目矩阵,其中行是潜在因素,列代表项目(见下图)。在这个因式分解过程中,评分矩阵中缺失的值可以被填充,作为用户对项目评分的预测,然后我们可以使用它们向用户提供推荐。

协同过滤中的矩阵分解(图片由作者提供)

ALS(交替最小二乘法)是一种协作过滤的数学优化实现,它使用交替最小二乘法(ALS)和加权λ正则化(ALS-WR)来寻找最佳因子权重,以最小化预测和实际评级之间的最小二乘法。 Spark 的 MLLib 包有一个内置的 ALS 函数,我们将在本帖中使用。

2.系统设置

  • 20.04.3
  • JDK 11.0.13
  • Scala 2.12.11
  • 火花 3.2.0
  • Hadoop 3.2.2
  • IntelliJ IDEA (2021.3.1)

关于系统先决条件的详细设置,请跟随我在上的帖子前往 AI 。

3.资料组

在这个项目中,我们将使用来自明尼苏达大学双城分校的 CC0 许可证MovieLens 数据集。可以从 Kaggle 下载 ml-100k (16M)。

运行以下命令解压缩 zip 文件:

unzip ml-100k.zip

你会看到解压后的ml-100k文件夹包含多个文件。

作者图片

我们主要使用两个数据文件:

  • u.data:用户评分数据,包括用户 id物品 id评分时间戳
  • u.item:电影数据,包括项目 id电影名称上映日期imdb url 等。

4.火花运转

4.1 从 Github 克隆代码

在 Spark 中运行之前,使用以下命令将代码从我的 Github 库克隆到您的本地目录:

git clone [https://github.com/haocai1992/MovieRecommender.git](https://github.com/haocai1992/MovieRecommender.git)

在 IntelliJ IDEA 中打开文件夹。您的项目结构应该如下所示:

作者图片

4.2 在 HDFS 准备数据

在我们开始之前,我们需要在终端中启动 hadoop HDFS 和 YARN 服务(参见本文中的)。

$ hadoop namenode -format
$ start-all.sh

然后我们需要将 ml-100k 数据集上传到 Hadoop HDFS:

$ hadoop fs -put ~/Downloads/ml-100k /user/caihao/movie

4.3 Spark 中的培训推荐模型

使用以下工具在 Spark 中训练推荐模型:

$ spark-submit --driver-memory 512m --executor-cores 2 --class RecommenderTrain --master yarn --deploy-mode client ~/Desktop/spark_test/MovieRecommender/out/artifacts/MovieRecommender_jar/MovieRecommender.jar

使用以下工具查看您在 HDFS 训练的模特:

$ hadoop fs -ls -h /user/caihao/movie

您将在这里看到您的模型:

作者图片

4.4 在 Spark 中生成建议

为 Spark 中的userID=100推荐电影使用:

$ spark-submit --driver-memory 512m --executor-cores 2 --class Recommend --master yarn --deploy-mode client ~/Desktop/spark_test/MovieRecommender/out/artifacts/MovieRecommender_jar2/MovieRecommender.jar --U 100

您将看到以下输出:

作者图片

或者在 Spark 中为movieID=200推荐用户使用:

./bin/spark-submit --driver-memory 512m --executor-cores 2 --class Recommend --master yarn --deploy-mode client ~/Desktop/spark_test/MovieRecommender/out/artifacts/MovieRecommender_jar2/MovieRecommender.jar --M 200

您将看到以下输出:

作者图片

5.在数据块中运行 PySpark 版本

如果你不了解 Scala,我还创建了一个 Python 版本的推荐系统!它使用 PySpark 并运行在 Databricks 上。

作者图片

在这里检查我的代码:我的数据块笔记本

要了解更多关于如何在数据块上创建集群和运行 Spark 的信息,请查看本教程。

6.推荐系统设计

我们的系统设计如下。

作者图片

总之,有两个 Scala 对象:

  • RecommenderTrain.scala:读取评级文件(u.data),准备数据,训练 ALS 模型,保存模型。
  • Recommender.scala:读取电影文件(u.item),加载 ALS 模型,生成电影推荐。

7.履行

7.1 培训 ALS 模型— RecommenderTrain.scala

RecommenderTrain.scala是一个 Scala 对象,包含三个主要方法。

7.1.1 prepareData

prepareData从 path 中读取 ratings 数据,解析有用字段并返回ratingsRDD

def PrepareData(sc: SparkContext, dataPath:String): RDD[Rating] = {
    // reads data from dataPath into Spark RDD.
    val file: RDD[String] = sc.textFile(dataPath)
    // only takes in first three fields (userID, itemID, rating).
    val ratingsRDD: RDD[Rating] = file.map(line => line.split("\t") match {
      case Array(user, item, rate, _) => Rating(user.toInt, item.toInt, rate.toDouble)
    })
    println(ratingsRDD.first()) // Rating(196,242,3.0)
    // return processed data as Spark RDD
    ratingsRDD
}

7.1.2 ALS.train

ALS.trainratingsRDD进行显式评级训练,并返回一个MatrixFactorizationModel对象。

val model: MatrixFactorizationModel = ALS.train(ratings=ratingsRDD, rank=5, iterations=20, lambda=0.1)

关于训练参数的信息:

ALS.train (Image by Author)中的参数

7.1.3 saveModel

saveModel将模型保存到路径。

def saveModel(context: SparkContext, model:MatrixFactorizationModel, modelPath: String): Unit ={
    try {
      model.save(context, modelPath)
    }
    catch {
      case e: Exception => println("Error Happened when saving model!!!")
    }
  finally {
  }
  }
}

7.2 生成建议— Recommend.scala

Recommend.scala是一个 Scala 对象,包含四个主要方法。

7.2.1 prepareData

prepareData从 path 中读取 movies 数据,解析有用字段并返回movieTitle

def prepareData(sc: SparkContext, dataPath:String): RDD[(Int, String)] ={
    println("Loading Data......")
    // reads data from dataPath into Spark RDD.
    val itemRDD: RDD[String] = sc.textFile(dataPath)
    // only takes in first two fields (movieID, movieName).
    val movieTitle: RDD[(Int, String)] = itemRDD.map(line => line.split("\\|")).map(x => (x(0).toInt, x(1)))
    // return movieID->movieName map as Spark RDD
    movieTitle
}

7.2.2 MatrixFactorizationModel.load

MatrixFactorizationModel.load从路径加载 ALS 模型。

val model: MatrixFactorizationModel = MatrixFactorizationModel.load(sc=sc, path=modelPath)

7.2.3 model.recommendProducts

model.recommendProducts为给定的用户 ID 推荐电影。

val recommendP = model.recommendProducts(user=inputUserID, num=10)

7.2.4 model.recommendUsers

model.recommendUsers为给定的 itemID 推荐用户。

val recommendU = model.recommendUsers(product=inputMovieID, num=10)

8.摘要

好了,我们已经用 Scala + Spark + Hadoop(用 PySpark + Databricks)构建了一个推荐系统,祝贺你!我希望这篇文章对你有用。

Github 回购:https://github.com/haocai1992/MovieRecommender

https://github.com/haocai1992/MovieRecommender

9.引文

接触

构建您的第一个 AWS CDK 项目

原文:https://towardsdatascience.com/build-your-first-aws-cdk-project-18b1fee2ed2d

Python 使基础设施代码变得简单

图片来自泰佐斯Unsplash

不久前,我写了一篇关于 AWS CloudFormation 的文章,这是一个代码为的基础设施工具,它使得在一个中央 yaml 或 json 文件中定义和部署基础设施资源变得很容易。这使得开发人员可以轻松地跟踪和监控基础设施的任何更改,并且还可以与大多数 AWS 服务集成。虽然 CloudFormation 功能强大,但语法需要一点时间来掌握,而且肯定有一个小的学习曲线。

为了使这个过程更加容易,我们将研究 AWS 云开发工具包( AWS CDK )。AWS CDK 也作为代码工具的基础设施,但它支持许多语言,如 Python、TypeScript 和 Java,您可以使用它们来定义您的资源。这使得部署和管理您的资源更加简单,因为您可以使用自己选择的语言和适当的 SDK,而不是试图从头构建一个 YAML 文件或您自己的云形成集。AWS CDK 从本质上抽象出了 CloudFormation 模板构建过程,并在幕后为您处理这一部分。

在今天的文章中,我们将探索一个简单的 Python 示例,并观察我们通过 AWS CDK 定义、部署和管理资源的速度。

注意:对于那些刚接触 AWS 的人来说(有一些经验来完全理解这篇文章是很好的),如果你想继续下去,请确保在下面的 链接 中做一个帐户。确保还安装了 AWS CLI 以使用示例。我还将提供一个我们将使用的服务列表,以及更深入的定义。如果您已经熟悉这些服务,请随意跳到您感兴趣的部分。

目录

  1. 先决条件/设置
  2. CDK 命令
  3. 构建 S3 桶和 Lambda 函数的 Python 示例
  4. 其他资源和结论

1.先决条件/设置

在你开始工作之前,你需要一些依赖。如前所述,确保安装了 AWS CLI 并用您的安全凭证配置了。确保也安装了 pip,对于这个例子,我们将使用 Python 3.7。要安装 CDK,你需要安装NPM。要安装 CDK,请在安装 npm 后运行以下命令。

安装 cdk

要验证 CDK 已经安装并访问文档,在您的终端中运行以下命令,它会将您重定向到 CDK 文档。

访问文档

CDK 文件(作者截图)

现在我们可以专注于创建我们的 Python CDK 项目了。我创建了一个名为“cdk-samples”的目录,并在 CLI 中为该目录运行以下命令。

创建 Python CDK 应用程序

这将在您的环境中安装以下文件和目录。

已安装的目录/文件(作者截图)

首先,我们需要激活虚拟环境,之后我们可以安装任何其他依赖项。

激活 virtualenv

激活虚拟环境(作者截图)

既然已经安装了虚拟环境,请确保安装 requirements.txt。对于我们的示例,我们将向预先创建的 requirements.txt 添加 S3 和 Lambda 模块,如下所示。

通过 CLI 安装

requirements.txt

这里我们将处理两个主要文件。如果你去 cdk_samples 目录,会有一个名为的文件 cdk_samples_stack.py 。这是您可以在模块中定义资源的地方。对于这个例子,我们将创建一个 S3 桶和一个 Lambda 函数,我们将在这里使用 Python SDK 定义这两个资源。根目录中的 app.py 文件将合成并执行我们用资源定义的这个模块。

2.CDK 命令

在我们继续之前,我只想介绍一些我们将使用的常见 CDK CLI 命令,这些命令对您也很有用。

cdk synth :这将创建由您的代码定义的 CloudFormation 模板/堆栈。

cdk deploy :这将获取从 synth 命令创建的 CloudFormation 堆栈,并部署定义的资源。

cdk bootstrap :这与我们的例子相关,因为我们正在处理资产。每当您有本地代码、Docker 映像等时,都需要在部署模板之前进行引导。这与我们的用例相关,因为我们在本地文件中定义了 Lambda 代码。

要查看所有 cdk 命令的列表,请查看文档这里是

3.构建 S3 桶和 Lambda 函数的 Python 示例

导航到 cdk_samples_stack 目录和其中对应的 Python 文件。首先,我们将构建一个样本 S3 桶。我们首先通过 CDK 导入将要使用的服务。

CDK 堆栈

在这里,我们现在可以定义我们想要创建的 S3 存储桶。要做到这一点,访问 S3 CDK Python 文档创建一个桶。确保您的存储桶名称是全球唯一的。

创建 S3 存储桶

现在返回到根目录并在 CLI 中运行 cdk synth,您应该看到终端中显示的 CloudFormation 模板和 S3 bucket 资源。

cdk synth(作者截图)

模板部署完成后,运行 cdk deploy,资源将被部署,您可以在 AWS 控制台中看到 CloudFormation Stack ARN。

cdk 部署(作者截图)

CDK 堆栈控制台(作者截图)

如果我们导航到 S3 控制台,您应该能够看到我们刚刚创建的 S3 桶。

S3 桶创建(作者截图)

现在我们可以按照一个非常简单的过程来创建我们的 Lambda 函数。回到 cdk samples 目录,为模拟 Lambda 函数创建一个 Lambda 目录。您的目录应该如下所示。

Lambda 目录(作者截图)

对于 lambda_handler 文件,我们将添加一个存根 lambda 函数。

λ处理器

我们现在可以返回到我们的 cdk_example_stack.py 文件,并使用下面的文档定义这个 Lambda 函数。

定义λ函数

现在,我们可以运行相同的 CDK CLI 命令,只是您可能需要在合成和部署之间进行引导,以考虑我们已经定义的本地 Lambda 函数。

更新的堆栈(作者截图)

如果我们查看 CloudFormation 控制台,我们现在应该会看到添加了一个 Lambda 函数,这在 Lambda 控制台中也可以看到。

使用 Lambda 资源更新堆栈(作者截图)

控制台中的 Lambda 函数(作者截图)

要清理所有资源并删除 CloudFormation 堆栈,请运行 cdk destroy

4.其他资源和结论

完整代码举例:https://github.com/RamVegiraju/CDK-Examples

https://github.com/aws-samples/aws-cdk-examples CDK 样本库:

在一个小时内,我们设法使用裸 Python 轻松定义和部署了一个简单的 AWS 应用程序。CDK 是一个功能强大的工具,可用于集中跟踪您的资源,并极大地简化基础架构管理。

如果你喜欢这篇文章,请在 LinkedIn 上与我联系,并订阅我的媒体 简讯 。如果你是新手,使用我的 会员推荐 报名。

用 Tensorflow 构建您的第一个 CNN

原文:https://towardsdatascience.com/build-your-first-cnn-with-tensorflow-a9d7394eaa2e

创建第一个神经网络的分步指南

照片由亚历山大·格雷像素上拍摄

计算机视觉是当今的热门话题之一。它为涉及图像的不同问题提供了可靠的解决方案。在本文中,我们将使用 Tensorflow 从头开始创建一个卷积神经网络(CNN)。然后,我们将使用它对猫和狗的图像进行分类,这是最流行的图像分类数据集之一。

本文旨在帮助该领域的新手(尤其是 Tensorflow 新手)学习获取数据、处理数据、构建模型、训练模型以及最终使用数据进行预测的基本原则。

什么是分类?

我们要回答的第一个问题是什么是分类。我将尽可能缩短这些原则,并提供其他有价值的文章来更深入地介绍它们。

分类是给给定的输入数据分配一个标签的过程。例如,我们可以对电子邮件是否是垃圾邮件进行分类。在这种情况下,对于给定的图像,我们将预测图像是猫还是狗。

关于什么是分类和最流行的算法的更详细的解释,我强烈推荐阅读下面的文章:

卷积神经网络(CNN)

下面的关键概念是 CNN(或 ConvNet)。CNN 是一类神经网络,它将图像作为输入,对其应用一系列操作并返回输出。该输出可以是概率、图像类别的预测或另一个新图像。这取决于网络架构和我们试图解决的问题。

在这种情况下,我们将使用一个简单的 CNN 来将输入图像分类为狗或猫类。它将返回给定图像是狗还是猫的概率。

为了更多更好地理解这种神经网络在图像中执行的操作,我建议这篇文章:

构建我们的网络

(你可以在这个 repo 中找到所有代码)

是时候开始工作并构建我们的分类器了。如前所述,我们将使用 Tensorflow 和 Keras,使用简单的 CNN 架构制作一个猫/狗分类器。

加载数据集

我们将使用牛津-IIIT 宠物数据集,包含超过 7000 张猫和狗的图片。该数据集拥有 CC-BY-SA 4.0 许可证,这意味着我们可以为任何目的共享和调整数据。

首先,我们需要下面的库。

接下来,我们将加载数据集。你可以从 Kaggle 下载,并将文件夹复制到你的工作目录中。另外,你可以直接从数据集的网站获取数据。

一旦我们有了数据集,它将包含一个名为‘images’的文件夹,里面有所有的宠物图像。

每个图像将在文件名中包含其标签。正如我们在数据集的信息中看到的,数据被分成不同的品种。

数据集信息,链接

有 12 个品种的猫和 25 只狗。我们将遵循的策略是使用所有的狗品种作为狗图像,所有的猫品种作为猫图像。我们不考虑品种,因为我们想建立一个二元分类器。

看看我们有多少数据。

There are 7390 images in the dataset

我们可以看到,总共有 7390 张图片。现在是时候除掉这些品种了。我们将创建两个列表,一个包含所有狗的照片,另一个包含所有猫的图片。

现在我们可以看一下,看看我们有多少张每种动物的图片。

There are 2400 images of cats
There are 4990 images of dogs

该数据集有 2400 只猫和 4990 只狗。

数据分区

对于我们的网络,我们希望使用三个不同的集合:

  • 训练:训练模型的图像。
  • 验证:在训练过程中测试模型的图像。
  • test:在模型完成训练后对其进行测试的图像。

我们将使用 70%的图像进行训练,10%用于验证,剩下的 20%用于测试。

由于我们只有两个图像列表(狗和猫),我们需要将这些列表分成不同的集合。我们将用熊猫来做这件事。

首先,我们将数据混洗并分成三组,其对应的数据分布为 70/10/20。

接下来,我们必须使用熊猫创建数据帧

它们只有两列,一列是图像名称,另一列是标签“猫”或“狗”。

最后,我们可以连接数据帧。

There are 5173 images for training
There are 739 images for validation
There are 1478 images for testingThere are 7000 images for training
There are 1000 images for validation
There are 2000 images for testing

完美!我们已经从数据集创建了三个分区。

数据预处理

接下来的步骤是预处理所有的图像。我们希望它们具有相同的维度(因为 CNN 将需要具有相同维度的所有输入数据),并且它们的像素被归一化。这样做的原因是为了更快地训练我们的神经网络模型。

我们将使用 Keras 中的 ImageDataGenerator 类。

我们刚刚创建了 3 个新的数据集,每个数据集都有经过预处理的图像。此外,我们应用了 shuffle 属性来随机重组图像的顺序,并应用了 batch_size 来将图像加入 32 个元素的组中。这意味着我们将一次给 CNN 32 张图片。每一步中的图像越少,模型的学习效果就越好,但完成训练过程需要的时间就越长。这是一个参数,我们可以用它来检查性能如何变化。

形象化

如果我们想检查数据集的结果,我们可以做以下比较。

Batch shape:  (32, 224, 224, 3)
Label shape:  (32,)

我们有 32 张图片及其相关标签。此外,我们可以绘制这些图像(例如,第四个)。

该批次的第四个图像,作者

Label:  1.0

狗的标签是 1,猫的标签是 0。

模型

现在我们已经准备好了所有的数据,是时候构建模型了。

Model: "sequential" _________________________________________________________________  Layer (type)                    Output Shape            Param #    =================================================================  conv2d (Conv2D)                 (None, 222, 222, 64)    1792                                                                          max_pooling2d (MaxPooling2D)    (None, 111, 111, 64)    0                                                                                                                                             conv2d_1 (Conv2D)               (None, 109, 109, 128)   73856                                                                         max_pooling2d_1 (MaxPooling2D)  (None, 54, 54, 128)     0                                                                                                                                          conv2d_2 (Conv2D)               (None, 52, 52, 256)     295168                                                                        max_pooling2d_2 (MaxPooling2D)  (None, 26, 26, 256)     0                                                                                                                                        conv2d_3 (Conv2D)               (None, 24, 24, 512)     1180160                                                                       global_average_pooling2d (G     (None, 512)             0           lobalAveragePooling2D)                                                                                                              dense (Dense)                   (None, 1)               513
=================================================================
Total params: 1,551,489
Trainable params: 1,551,489
Non-trainable params: 0 _________________________________________________________________

这是一个简单的 CNN 模型,有四个卷积层和一个输出层,输出层由一个密集层和 1 个输出神经元组成。

让我们进入训练阶段。我们需要编译和拟合模型。我们将使用二进制交叉熵作为损失函数,因为我们正在处理带有整数标签的类(0 代表猫,1 代表狗)。优化器将是 adam 算法,我们将使用精度作为流程的度量。

我们将训练 15 个纪元的网络。

现在我们已经训练出了第一个 CNN!我们可以在训练过程中查看网络的精度损耗值。

培训指标,按作者

最后一步是评估这个模型。为此,我们将使用测试数据集。

47/47 [==============================] - 10s 204ms/step - loss: 0.4277 - accuracy: 0.8051Loss: 0.4276582598686218
Accuracy: 0.8051421046257019

该模型的准确率为 80%。

你已经训练了你的第一个卷积神经网络,祝贺你!既然我们已经训练了新模型,我们可以用它来预测猫和狗的真实图像。这是我们可以使用模型进行预测的部分。这些图像在任何过程阶段都是前所未见的。

预言

预测阶段是与现实世界问题最相似的场景,在这一阶段,一旦模型被定型,它就必须对看不见的数据进行分类。

在我们的例子中,我们将预测下图的类。

Pixabay像素上制作的一只猫的图像

对于人类来说,这显然是一只可爱的猫的图像,但让我们看看我们的模型对此有什么看法。

array([[0.08732735]], dtype=float32)

在这种情况下,我们必须将影像整形为网络的输入格式,并像对所有数据集影像一样对其进行归一化。

然后我们给模型,输出是 0.08 。该输出可以从 0 到 1 变化,因为网络的输出层具有 sigmoid 激活函数。因为我们的猫的类是 0,狗的类是 1,所以我们的模型同意这个图像是一只猫。

你可以在这个回购中看到完整的代码。

后续步骤

我们已经看到了一个简单的从头开始 CNN 的方法。它的性能很好,但仍有很大的改进空间。在这里,我将列举一些可能的修改,您可以研究以增强网络。

  • 改变网络架构。您可以尝试新的层,编辑现有的层或更改激活功能。你尝试的越多,你在 CNN 获得的知识就越多。
  • 增加漏失批量归一化图层。有非常有用的,尤其是在更大的模型中。这些层也有助于避免模型中的过度拟合。
  • 尝试新的优化训练过程。有很多不同的优化器可以用来训练网络。你可以改变它们,看看是否有改进。
  • 做图像增强,这是一种从现有图像中创建新图像的技术。您可以尝试缩放、旋转或裁剪部分图像,以生成更多数据。

一旦你掌握了所有这些要点(以及你能想到的任何其他要点),我建议着手于一个多类分类模型。例如,考虑到这次的品种,你可以使用相同的数据集。创建一个分类器,不仅区分猫和狗,而且区分数据集中可用的品种。这将是一个有趣的挑战!

我希望本教程对你有所帮助。如果您有任何问题,请随时发表评论!👋🏻​

在 Julia 中用 Flux.jl 构建你的第一个神经网络

原文:https://towardsdatascience.com/build-your-first-neural-network-with-flux-jl-in-julia-10ebdfcf2fa3

没有任何外部数据的初学者教程

时代是你不应该预测的——胡尔基·奥莰·塔巴克在 Unsplash 上拍摄的照片

机器学习的第一条规则应该是你不要用机器学习。我的意思是,如果你有一个新项目,试着不用机器学习来解决。ML 很贵,很难监控、维护和运行,更不用说数据科学家可能很贵。如果你真的不能用启发式规则解决问题,那么你可以去 ML。

本教程将把上面的陈述颠倒过来。我们将使用神经网络从给定的日期时间中预测 Unix 纪元。这是为了练习,没别的。求求你,求求你不要为了学习之外的任何事情而这样做!🙏

这一整篇教程作为冥王星笔记本提供:

https://github.com/niczky12/medium/blob/master/julia/epoch_pluto.jl

不知道冥王星是什么?请看这篇 文章

要获得所有媒体文章的完整信息——包括我的——请点击这里订阅。

你说纪元?

澄清一下,一个纪元时间是从 1970 年 1 月 1 日午夜起经过的 。我们可以通过调用datetime2unix函数从 Julia 的 DateTime 中获得一个 epoch(或 unix)时间:

**julia>** using Dates**julia>** dt = DateTime("2021-09-01T12:45:00")2021-09-01T12:45:00**julia>** datetime2unix(dt)1.6305003e9

我们的目标是获取一个日期时间,并预测它在 Unix 纪元中的值。系好安全带,我们有工作要做!

那可是好多秒啊!

我打赌今天的日期和这张图上的沙子一样多,证明我错了!—吉姆·盖德Unsplash 上的照片

这是一个教程——而且很傻——所以我会设定一些界限。假设我们的训练输入在 2010 年到 2020 年之间,我们只对预测到 2030 年的值感兴趣:

现在我们知道了 y 的变化范围,我们可以取几个样本并创建我们的数据。注意,我们不需要从任何地方下载任何东西,我发现很多 ML 教程都要求你下载数据或者在不同格式之间转换。这可能会很困难,困难是不好的,所以我们将避免所有的麻烦,并制作我们自己的数据。

因为我们正在建立一个神经网络,标准化我们的输入是必须的。但是由于我们的输出是如此之大——在 1,000,000,000 的规模上——我们也将需要标准化我们的输出。这样,我们可以避免等待神经网络达到它需要的巨大权重。

以下是一些将 x 和 y 归一化的辅助函数:

我们也可以使用小批量来加速训练。IterTools 的partition函数使这变得简单:

如果你觉得以上令人困惑,看看我在 Julia 发表的关于广播的文章。

建立一个网络,训练一个网络

为了容易开始,我们将只使用一个单层和identity作为我们的激活函数。非常欢迎您下载源文件并在 Pluto 中使用它。

上面有 3 个组件:

  • 模型:这是我们的 NN 架构。
  • 损失:这告诉 Flux 我们的损失函数是什么。上面我用的是 Flux 内置的均方误差。
  • 优化器:告诉 Flux 如何更新权重的函数。

为了训练我们的网络,我们将这些数据连同输入和输出数据对一起传入Flux.train!,并对我们的数据进行几次迭代。令人困惑的是,每次迭代也被称为一个纪元😃。

随着时间的推移,我们的训练损失——作者制作的图表

为了预测值,我们需要将预测值重新调整到各个时期:

我还用 2020 年至 2030 年间的例子创建了一个测试集,以下是预测样本:

Input -> Predicted epochs as datetime | difference
2030-02-13T03:33:28 -> 2030-02-11T17:01:22.811 | -35 hours
2021-02-18T12:15:50 -> 2021-02-16T17:24:46.530 | -43 hours
2022-06-26T02:55:35 -> 2022-06-26T08:19:32.420 | 5 hours
2023-11-16T05:54:15 -> 2023-11-16T01:11:17.980 | -5 hours
2030-06-24T05:38:26 -> 2030-06-24T13:47:39.302 | 8 hours
2026-04-19T21:39:56 -> 2026-04-20T07:08:37.777 | 9 hours
2028-01-16T20:26:00 -> 2028-01-16T12:52:12.389 | -8 hours
2029-12-21T02:00:31 -> 2029-12-20T21:20:49.342 | -5 hours
2027-03-05T14:17:19 -> 2027-03-06T21:43:38.841 | 31 hours
2026-07-01T05:54:13 -> 2026-07-02T01:32:57.773 | 20 hours

如你所见,我们的预测并不完美,还有改进的空间,但我们的 NN 正在收敛并产生合理的结果。当你预测 10 年后的未来时,20 个小时的时差对于时代来说并不算太差。

现在轮到你了

去把他们都按下来!—腾雅特Unsplash 上的照片

我希望你读这篇文章的时候和我写作的时候一样开心。这仅仅是开始。你可以用上面的来开始你的 NNs 和 Flux.jl 的旅程冥王星笔记本在这里供你消遣。欢迎您更改输入样本大小、日期、优化器、损失函数、激活函数、神经网络结构等。去野外按几个按钮!

https://github.com/niczky12/medium/blob/master/julia/epoch_pluto.jl

我写朱莉娅和其他很酷的东西。如果你喜欢这样的文章,请考虑关注我。

</jupyter-notebooks-can-be-a-pain-but-i-️-pluto-f47913c5c16d> https://blog.devgenius.io/make-a-command-line-game-with-julia-a408057adcfe

建立你自己的人工智能语音助手来控制你的电脑

原文:https://towardsdatascience.com/build-your-own-ai-voice-assistant-to-control-your-pc-f4112a664db2

一个简单的指南,告诉你如何构建你自己的人工智能助手来控制你电脑上的各种动作

亚历山大·奈特Unsplash 上拍照

最*,使用虚拟助手来控制你的周围环境正在成为一种常见的做法。我们利用谷歌人工智能、Siri、Alexa、Cortana 和许多其他类似的虚拟助手,通过简单的语音或音频命令为我们完成任务。你可以要求他们播放音乐或打开一个特定的文件或任何其他类似的任务,他们会轻松地执行这些动作。

虽然这些设备很酷,但开发自己的人工智能语音自动化助理也很有趣,你可以利用它来控制你的桌面,只需借助你的声音。我们可以使用这样的人工智能与你聊天,打开视频,播放音乐,等等。

在这篇文章中,我们将致力于开发一个人工智能助手的介绍性项目,你可以利用它来控制你的 PC 或任何其他类似的设备。我们将从介绍构建这个项目所需的一些基本依赖关系开始,并继续将其全部放入一个 Python 文件中,通过该文件构建 AI 语音助手来执行您的命令。

在阅读这篇文章之前,如果你对我们从零开始构建东西的其他这样的酷项目感兴趣,我建议看看我以前的一个作品。下面提供了一个链接,您可以在这里用 Python 开发自己的天气应用程序指示器,只需不到十行代码。

基础知识入门:

第 1 部分:桌面控件

照片由本斯·博罗斯Unsplash 拍摄

在文章的这一部分,我们将学习如何控制我们的电脑。我们将学习如何管理和处理物理屏幕上的一些基本操作。在 PyAutoGUI 的帮助下,我们可以执行这个项目所需的许多功能。这个自动化库工具允许用户以编程方式控制鼠标和键盘。

您可以使用一个简单的 pip 命令安装 PyAutoGUI 库来处理所有与光标、鼠标和键盘相关的任务,如下所示。

pip install PyAutoGUI

让我们从这个库中的一些基本命令开始,我们将需要这些命令来开发我们的语音辅助人工智能 Python 项目。几分钟后,安装应该在各自的环境中顺利完成。

首先,让我们导入 PyAutoGUI 库,如下面的代码片段所示。下一个关键步骤是知道你的工作屏幕的分辨率。我们可以使用最*安装的库中可用的尺寸功能打印默认的屏幕尺寸和屏幕高度。

import pyautogui# Printing the default screen width and height
screenWidth, screenHeight = pyautogui.size()print(screenWidth, screenHeight)

输出: 1920 1080

你可以注意到我的屏幕分辨率是 1920 x 1080,这应该是大多数电脑的默认屏幕尺寸。但是,如果您的显示器屏幕分辨率较高或较低,您仍然可以轻松地跟随指南。这些命令可以互换使用,以在任何分辨率下获得所需的坐标。如果你的屏幕显示分辨率和我的不匹配,一定要相应地改变一些参数。

我们将在本节中介绍的另一个基本命令是发现鼠标指针当前位置的命令。库的 position()函数将定位鼠标指针所在的当前坐标。我们可以使用这些位置在您的桌面屏幕上定位文件夹和其他重要目录。下面是执行以下操作的代码片段。

# Showing the current cursor position
currentMouseX, currentMouseY = pyautogui.position() # Get the XY position of the mouse.
print(currentMouseX, currentMouseY)

该库的另一个有趣的功能是,您可以使用下面提供的代码片段来定位当前工作屏幕上某些图像的位置以及相应的坐标。

# Locating on the Screen by getting the co-ordinates
x, y = pyautogui.locateCenterOnScreen("image.png")

我们将在本节中看到的最后一个基本命令是允许我们打开所需目录的函数。通过将光标放在左上角,我能够算出我的管理文件夹的坐标。我们可以使用 moveTo()函数将光标移动到相应的位置,以及文件夹的相应位置。然后,我们可以使用 click()命令,只需输入鼠标左键或右键的点击次数以及您想要的点击次数。

# Open The Admin Directory
pyautogui.moveTo(37, 35, 1)
pyautogui.click(button='left', clicks=2)

使用上面的代码片段,您应该能够打开 admin 文件夹,因为光标会自动移动到 admin 目录并双击它来打开它。如果您的屏幕左上角没有类似的图标,或者您有不同的屏幕分辨率,请随意尝试相应的位置和坐标。

第 2 部分:语音命令控制

照片由托马斯·勒Unsplash 上拍摄

在本文的这一部分,我们将了解语音识别的一些基本要求,这是这个项目的第二个最核心的部分。我们将需要一个麦克风来通过声音传递我们的命令,并相应地解释信息。建议使用语音识别库,以及您选择的文本到语音转换器。还要确保您的工作环境中安装了 PyAudio。

如果观众对文本到语音转换不太熟悉,我强烈推荐查看我以前的一篇文章,其中我用 Python 介绍了 Google 文本到语音转换,并提供了初学者代码来帮助您入门。下面提供了相同内容的链接。

首先,我们可以导入必要的库,如下面的代码块所示。语音识别库将使我们能够检测必要的语音命令。此外,我们还可以利用文本到语音库来传递文本命令,并将它们转换为语音,然后传递给系统来执行所需的操作。我们可以为语音识别器创建一个变量。

import speech_recognition as sr
import pyttsx3r = sr.Recognizer()

在下一步中,我们将读取用户的麦克风输入作为源,并相应地解释语音。一旦音频被识别为期望的,语音输出就显示在终端输出上。但是,如果语音未被检测到,我们可以通过必要的异常来确保用户可以相应地验证他们的设置。下面是简单语音识别的代码片段。

with sr.Microphone() as source:
    r.adjust_for_ambient_noise(source)
    print ("Say Something")
    audio = r.listen(source)

    try:
        text = r.recognize_google(audio)
        print("you said: ", text)

    except sr.UnknownValueError:
        print("Google Speech Recognition could not understand audio")

    except sr.RequestError as e:
        print("Could not request results from Google Speech Recognition service; {0}".format(e))

下一步,我们将构建 AI 语音助手的最终版本,在这里我们可以将本节中讨论的两个功能组合成一个实体来执行所需的操作。

开发人工智能语音助手的最终版本:

附身摄影Unsplash 上拍照

既然我们对本文中设备控制和语音识别这两个核心组件有了基本的理解,我们就可以开始结合这两个元素来开发我们的项目了。让我们从必要的库导入开始,如下所示。

import pyautogui
import speech_recognition as srr = sr.Recognizer()

在下一个片段中,我们将定义命令的功能,在这里我们将解释许多动作。在下面的代码块中,我只定义了几个功能,即打开我的管理目录或开始菜单。该函数接受用户提供的文本输入。我们可以添加几个其他必要的命令来进一步改进这个项目。

def commands(text):
    if text == "open admin":
        # Open The Admin Directory
        pyautogui.moveTo(37, 35, 1)
        pyautogui.click(button='left', clicks=2) elif text == "open start menu":
        # Open The start menu
        pyautogui.moveTo(18, 1057, 1)
        pyautogui.click(button='left', clicks=1)

在下一个代码块中,我们将定义接收来自用户的音频输入并相应地识别语音的功能。一旦听到音频,在将文本输入传递到我们的命令功能之前,请确保将其转换为小写。一旦构建了下面的代码,您就可以自由地测试和运行这个项目了。

with sr.Microphone() as source:
    r.adjust_for_ambient_noise(source)
    print ("Say Something")
    audio = r.listen(source)

    try:
        text = r.recognize_google(audio)
        print("you said: ", text)
        commands(text.lower())

    except sr.UnknownValueError:
        print("Google Speech Recognition could not understand audio")

    except sr.RequestError as e:
        print("Could not request results from Google Speech Recognition service; {0}".format(e))

运行项目的首选方法是最小化所有选项卡并打开终端来运行 Python 代码。您可以发出命令“open admin”来观察光标从默认位置移动到指定位置,并根据需要打开它。我的 GitHub 资源库中提供了以下项目所需的所有文件。从下面的链接中查看。

下面这个项目只是一个入门项目,让你从零开始拥有一个自己的 AI 语音助手。我们可以对下面的项目进行大量的改进和提高,我会推荐用户去尝试。我还将查看本文的第 2 部分扩展,在那里我们可以为更好的特性和性能做一些重要的改进。

结论:

维卡·斯特劳贝里卡Unsplash 上的照片

“任何可以产生比人类更聪明的智能的东西——以人工智能、脑机接口或基于神经科学的人类智能增强的形式——都毫无疑问地成为最能改变世界的东西。其他的甚至都不在一个联盟里。”
—埃利泽·尤科夫斯基

对语音和声音的识别是人类理解的原始任务。通过听和读不同类型的声音和比喻,我们能够感知和回应大多数人类情感。然而,机器还没有完全理解语言背后的情感的能力。

虽然我们还没有完全能够开发出完全理解人类情感的机器,但我们已经成功地开发出了多种可以检测和理解语音的设备。当前编程时,AI 可以识别语音并创建网络映射来解释对话并执行相应的任务。

在本文中,我们开发了一个关于语音自动化系统的项目,它可以用来控制桌面上的许多操作。我们介绍了 PyAutoGUI 库处理所有光标、鼠标和键盘相关任务的基础知识。然后,我们研究了用于语音检测和处理的语音识别库。最后,我们构建了一个人工智能语音助手来控制你的电脑。

如果你想在我的文章发表后第一时间得到通知,请点击下面的链接订阅邮件推荐。如果你希望支持其他作者和我,请订阅下面的链接。

https://bharath-k1297.medium.com/membership

如果你对这篇文章中提到的各点有任何疑问,请在下面的评论中告诉我。我会尽快给你回复。

给所有喜欢看我内容的观众一个快速更新。我很抱歉博客的延迟,因为我工作有点忙。从下个月开始,我会试着每月至少发布三到五篇文章。感谢大家一直以来的支持。

看看我的一些与本文主题相关的文章,你可能也会喜欢阅读!

</7-python-programming-tips-to-improve-your-productivity-a57802f225b6>

谢谢你们坚持到最后。我希望你们都喜欢这篇文章。祝大家有美好的一天!

构建气候变化迷因生成器

原文:https://towardsdatascience.com/building-a-climate-change-meme-generator-7b4476b585f7

使用 GPT-3 和 DALLE 以及一系列的提示和反应来创造迷因

模因是当今社会最高效的信使。有没有其他载体能像好的模因一样快速有效地将强有力的声明传播到世界各地?这个思考过程启发了我的团队参加 OpenAI 最*的气候黑客马拉松:气候变化迷因引擎。在我们讨论迷因之前,让我们先退后一步,提供一些更深入的背景。

上周,OpenAI 举办了一场关于气候变化的黑客马拉松。不,这不需要我们“侵入”任何人的电脑。目标有两个:

  1. 开发一种创新工具,帮助减轻全球变暖的影响
  2. 将 OpenAI 的预训练算法整合到该工具中(例如 Dalle、GPT-3)

这些预先训练的人工智能(AI)算法是一些最先进的模型,机器能够理解人类的文本/符号,并从中生成文本和图像。“理解”这个词,有点误导。这些模型在成千上万的文本和图像上进行训练,以找到人类通过文本交流的固有模式。训练过程利用了嵌套的数学函数,这些函数被告知要为特定的目标进行优化。输出是强大的、预先训练的自然语言处理模型。当我说“强大”的时候,我并不是指拥有任何类似人类良知的人工智能。

因为这些模型是预先训练好的,所以黑客马拉松的大部分工作转移到了定义问题、促进工程、微调模型和任何应用程序开发工作上。*这些模型需要一系列文本作为输入(即提示),输出是与提示相关的文本或图像。提示工程是指使用提示来获得您想要的输出的实践。微调模型是输入新数据以改进这些模型的过程,以便使它们向您想要的输出倾斜。例如,如果我想用 GPT-3 写类似我风格的文章,我会用我以前文章中的文字对它进行微调。事实上,我在的另一篇文章中就是这么做的。

对于气候黑客马拉松,我们的目标如下:

  • 激发基层的热情,将全球变暖的风险放在公众思想的首位
  • 让全球变暖成为谈论的“酷”话题(双关语:)
  • 利用网络效应在网上传播思想

为了生成与气候变化相关的迷因,我们的过程应该类似于下面的流程图。

作者图片

借助 GPT-3,将“生成气候变化概念”转换成 python 代码相当简单。看起来是这样的:

import openai

api_key = <insert api key>
openai.api_key = api_key
concept = openai.Completion.create(
    model='text-davinci-002',
    prompt="Generate a concept related to climate change:\n",
    temperature=1,
    max_tokens=20,
    top_p=1,
    frequency_penalty=1,
    presence_penalty=0
    )

print(concept.choices[0].text)

在上面的代码中,我们选择了将输出中的字符数限制为 20(即 max_tokens)并最大化随机性(即温度)的参数。你会注意到 GPT-3 的提示是用简单的英语写的。任何在计算机代码中清晰可辨的东西都是一个惊喜。这个代码的输出是一个与气候变化相关的随机概念,比如“可持续交通”、“极地冰盖融化导致海*面上升”,或者“全球变暖”。

使用这个概念,我们要求 GPT-3“描述一个有趣的图像代表这个[插入上面的概念]”。延续“全球变暖”的概念,一个结果是“一个融化的极地冰帽,一只企鹅懒洋洋地躺在上面,享受着温暖的天气。”

按作者分类的表格

使用一些额外的 python 代码来组合图像和标题,输出是一个迷因,由上面标题中提到的企鹅主演。

DALLE 创建的图像

这个迷因可能看起来不多,但我保证它在互联网上传播的速度比科学家谈论全球变暖的危险还要快。它将企鹅人性化,因为它经历了家园被融化的可怕后果带来的悲伤和孤独。当人类在数字世界中遇到这种气候变化迷因时,他们会与企鹅产生共鸣。记住,一张图胜过千言万语。

~数据通才
数据科学职业顾问

*预先训练的模型可能会改变数据科学家的预期技能组合。我两年前在之前的一篇文章中预测了这种类型的变化,在这篇文章中,数据科学家分成了更多的技术与更多的商业/决策科学类型。

热图生成器的代码发布在 Github 上。

用 Python 构建颜色选择器

原文:https://towardsdatascience.com/building-a-color-picker-with-python-55e8357539e7

创建一个从图像像素中选择 RGB 通道的工具

(来源: kaggle )

我知道,我知道…外面有很多颜色选择工具。然而,我想你会发现在笔记本上直接使用它的好处。这个工具也是完全可定制的。

我们将构建两个颜色选择器:

  • 简单选取器—从单一图像中选择一种颜色
  • 复杂拾色器—从多个图像中选择颜色列表并显示颜色

最后,我们讨论了数据科学中的一些应用。这些是我使用拾色器的方式。

进口

让我们深入研究代码。你也可以在 GitHub 上找到完整的项目。

首先,我们有一些进口货。我们有标准包装(第 2-3 行)。 mpimg 用于加载图像, pyperclip 用于将字符串保存到剪贴板,glob 用于处理文件路径。确保你已经安装了所有这些。

#Imports
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

import pyperclip

import random
import glob

我们将从不同的鸟类图像中挑选颜色。你可以在文章的封面图片中看到一些例子。您也可以在 Kaggle 上找到完整的数据集。我们在给定的目录(第 2 行)中加载所有的图像路径(第 3 行)。

#Dataset
read_path = "../../data/birds/"
img_path = glob.glob(read_path + "*.jpg")

简单的颜色选择器

图 1 中,您可以看到我们的第一个拾取器正在工作。每当我们点击图像中的某个位置,该像素的 RGB 通道就会保存到我们的剪贴板中。然后,我们可以将该值直接粘贴到笔记本中。

图 1:简单颜色选择器的演示(来源:作者)

为了创建这个选择器,我们从 onclick 函数开始。这是每次点击图像时运行。我们得到点击的 x 和 y 坐标(第 5-6 行)。然后我们得到该坐标像素的 RGB 通道(第 10 行)。最后,我们将这些通道作为字符串保存到剪贴板中(第 13 行)。

def onclick(event):
    global img

    # get x,y of click
    x = round(event.xdata)
    y = round(event.ydata)

    # get RGB values
    rgb = img[y][x]

    # save to clip board
    pyperclip.copy(str(rgb))

为了使用这个函数,我们首先用 matplotlib 创建一个图形(第 4 行)。然后,我们使用 mpl_connect 函数向该图添加交互功能(第 7 行)。您可以看到我们已经将 onclick 函数作为参数传入。最后,我们加载一个鸟的图像(第 10–11 行)并显示它(第 13 行)。

%matplotlib notebook
global img

fig = plt.figure(figsize=(5,5))

#Add an interactive widget to figure 
cid = fig.canvas.mpl_connect('button_press_event', onclick)

#Load image and add count
path = img_path[0]
img = mpimg.imread(path)

plt.imshow(img)
plt.show()

另一件要注意的事情是全局变量的使用(第 2 行)。这允许这些变量在 onclick 函数中更新。这是将图像作为参数传递的替代方法。我们还有线 %matplotlib 笔记本(线 1)。这样可以将数字保留在笔记本中。

复杂颜色选择器

现在让我们加强一下。在图 2 中,我们现在有一个图像框(左)和颜色框(右)。我们现在可以看到我们点击的像素的颜色,并遍历多个图像。另外,请注意彩色框左上角的红色数字。我们可以将颜色保存到一个列表中,这个数字会在我们每次这样做的时候更新。

图 2:复杂颜色选择器的演示(来源:作者)

同样,我们从我们的 onclick 函数开始。这与之前的拾色器类似。主要区别是我们现在运行函数 change_choice ,而不是保存 RGB 通道。我们还更新了一个全局 rgb 变量。这是因为它可以被下面的其他函数访问。

def onclick(event):
    global img
    global rgb

    # get x,y of click
    x = round(event.xdata)
    y = round(event.ydata)

    # get RGB values
    rgb = img[y][x]

    #Update second plot with colour
    change_choice()

我们有一个函数 onpress ,它将在键盘被按下时运行。我们从获取密钥开始(第 6 行)。接下来会发生什么取决于按下了哪个键:

  • n (下一步):我们运行 change_image 函数
  • c (复制):我们将 RGB 通道保存到剪贴板(第 13 行)和颜色列表(第 16 行)。我们还运行了 change_choice 函数。

记住,当 onclick 函数运行时,全局 rgb 变量被更新。这意味着当我们按下“c”时,将保存最*点击的 RGB 通道。

def onpress(event):
    global rgb
    global colours

    #Get key 
    key = event.key

    if key == 'n':
        change_image()

    elif key == 'c':
         # save to clip board
        pyperclip.copy(str(rgb))

        # add to list of colours
        colours.append(rgb)

        change_choice()

change_choice 用于更新彩色盒。为了创建这个框,我们使用与图像框相同的尺寸(第 13 行)。颜色框中的每个像素将具有当前全局 rgb 值的 RGB 通道(第 14 行)。我们还删除当前计数(第 9–10 行),然后更新它(第 18 行)。为此,我们使用保存的颜色列表的长度。

def change_choice():
    global img
    global ax
    global colours
    global rgb

    # remove previous count
    for txt in ax[1].texts:
        txt.set_visible(False)

    # create array of colour choice
    dims = np.shape(img)
    col = np.array([[rgb]*dims[0]]*dims[1])
    ax[1].imshow(col)

    # update colour count
    ax[1].text(0, 15, len(colours),color='r',size=20)

    plt.show()

change_choice 功能通过两个动作运行:

  • 通过 onclick 功能当我们点击图像时。在这种情况下,全局 rgb 被更新,框中的颜色将改变。
  • 当我们按下“c”时,通过 onpress 功能。这里颜色列表的长度增加了,红色计数将会改变。

最后,我们有 change_image 函数。每当我们按下“n”时,这用于更新图像框。我们从关闭所有现有地块开始(第 8 行)。然后我们创建一个新的绘图(第 10 行)并添加点击(第 13 行)和按压(第 14 行)功能。我们加载并显示一个随机的鸟图像(第 17–20 行)。然后我们更新颜色框(第 24 行)。首先将全局 rgb 变量设置为[255,255,255],我们将盒子颜色设置为白色。

def change_image():
    global img_path
    global img
    global ax
    global rgb

    # close all open plots
    plt.close('all')

    fig,ax = plt.subplots(1,2,figsize=(10,5))

    # add an interactive widget to figure 
    cid = fig.canvas.mpl_connect('button_press_event', onclick)
    cid2 = fig.canvas.mpl_connect('key_press_event', onpress)

    # load random image
    path = random.choice(img_path)
    img = mpimg.imread(path)

    ax[0].imshow(img)

    # reset the colour window
    rgb = [255,255,255]
    change_choice()

我们可以通过运行 change_image 函数来启动拾色器(第 12 行)。注意,我们现在在第 1 行中有了 %matplotlib tk 。这将在笔记本外部的窗口中打开拾色器。如果你试图直接在笔记本上运行它,它实际上是不起作用的。如果有人能解决这个问题,请在评论中告诉我:)

%matplotlib tk
global img_path
global colours
colours = []

# load image paths
read_path = "../../data/birds/"
img_path = glob.glob(read_path + "*.jpg")

# start widget
change_image()

当您遍历图像并保存颜色时,颜色列表将会更新。图 3 给出了这样一个列表的例子。这来自于我们在图 2 中看到的鸟类图像。

图 3:颜色列表示例

数据科学应用

我想与您分享这段代码,因为我发现它在我的数据科学之旅中非常有用。在本文的其余部分,我们将讨论一些应用程序。

巩固图表颜色

你可以说我是一个完美主义者,但是在展示作品时,我喜欢我所有的图表都有一个共享的配色方案。问题是我倾向于使用多个 python 包。在图 4 中,你可以看到 matplotlib(左)和 SHAP (右)使用的默认颜色的差异。

图 4:不同的图表颜色(来源:作者)

有了第一个拾色器,我能够解决这个问题。我可以用 Python 保存这些图表,加载它们,选择它们的颜色。更新 matplotlib 图表很简单。或者, Leonie Monigatti 有一个很好的关于如何定制 SHAP 剧情的教程

您可能也会发现下面的代码很有用。它会将 RGB 通道转换成十六进制字符串。我发现有些包只接受这个颜色参数。

#convert RGB to hexidecimal 
from colormap import rgb2hex
rgb2hex(134,  94,  58)

使用图像数据的特征工程

第二个应用是为什么我决定建立更复杂的颜色选择器。我用它来创建机器学习的功能。在图 5 中,你可以看到橙色轨迹是如何从图像的其余部分中分离出来的。轨迹像素的颜色是使用拾色器获得的。

图 5:使用图像数据的特征工程(来源:作者)

下周,我将更详细地介绍这个应用程序。我将发表一篇关于图像数据特征工程的文章。除了上面的方法,它还包括灰度、裁剪和边缘检测。

我希望你喜欢这篇文章!你可以成为我的 推荐会员 😃 来支持我

https://conorosullyds.medium.com/membership

| 推特 | YouTube | 时事通讯 —注册免费参加 Python SHAP 课程

资料组

鸟类 450 种 -图像分类( CC0:公共领域)https://www.kaggle.com/datasets/gpiosenka/100-bird-species

使用 Numpy 从头构建卷积神经网络

原文:https://towardsdatascience.com/building-a-convolutional-neural-network-from-scratch-using-numpy-a22808a00a40

随着计算机视觉应用在我们的生活中变得无处不在,理解卷积神经网络的工作原理对于每个数据科学从业者来说都是至关重要的

来源:canva.com

我之前的文章中,我在没有使用 Tensorflow、Pytorch、Keras 等流行的现代深度学习库的情况下,构建了一个深度神经网络。我后来用那个网络来分类手写数字。所获得的结果不是最先进的水*,但它们仍然是令人满意的。现在我想更进一步,我的目标是只用 Numpy 开发一个卷积神经网络(CNN)。

这项任务背后的动机与创建完全连接的网络的动机相同:Python 深度学习库尽管是强大的工具,但却阻止了从业者理解底层正在发生的事情。对于 CNN 来说尤其如此,因为这个过程比经典深度网络进行的过程更不直观。唯一的解决办法就是亲自动手,尝试自己实现这些网络。

我打算把这篇文章作为一个实用的实践指南,而不是一个关于 CNN 运作原理的全面指南。因此,理论部分是狭窄的,主要服务于实践部分的理解。对于需要更好地理解卷积网络工作原理的读者,我留下了一些很好的参考资料。查看本视频来自的独立代码本完整指南

什么是卷积神经网络?

卷积神经网络使用特定的架构和操作,这使它们非常适合于图像相关的任务,如图像分类、对象定位、图像分割和许多其他任务。它们大致模拟了人类的视觉皮层,其中每个生物神经元只对视野的一小部分做出反应。此外,高级神经元对其他低级神经元的输出做出反应【1】

正如我在之前的文章中所展示的,即使是经典的神经网络也可以用于像图像分类这样的任务。问题是,它们只适用于小尺寸的图像,当应用于中等或大尺寸的图像时,它们变得非常低效。原因是经典神经网络需要大量的参数。例如,一个 200x200 像素的图像有 40000 个像素,如果网络的第一层有 1000 个单元,那么仅第一层就有 4000 万个权重。由于 CNN 实现了部分连接的层和重量共享,这个问题得到了极大的缓解。

卷积神经网络的主要组件包括:

  • 卷积层
  • 池层

卷积层

卷积层由一组过滤器(也称为内核)组成,当应用于该层的输入时,会对原始图像进行某种修改。滤镜是一个矩阵,其元素值定义了对原始图像执行的修改类型。如下所示的 3x3 内核具有突出显示图像垂直边缘的效果:

不同的是,这个内核强调水*边缘:

卷积的效果。来源:维基百科

核中元素的值不是手动选择的,而是网络在训练期间学习的参数。

卷积的作用是隔离图像中存在的不同特征。密集层稍后会使用这些功能。

池层

池层非常简单。池层的任务是缩小输入图像,以减少网络的计算负载和内存消耗。减少图像尺寸,实际上意味着减少参数的数量。

池层所做的是使用一个内核(通常是 2x2 的维度)并将输入图像的一部分聚合成一个值。例如,一个 2x2 max 池内核取输入图像的 4 个像素,并只返回具有最大值的像素。

Python 实现

所有的代码都可以在这个 GitHub 资源库中找到。

https://github.com/andreoniriccardo/CNN-from-scratch [## GitHub-andreoniriccardo/CNN-从零开始:从零开始的卷积神经网络

github.com](https://github.com/andreoniriccardo/CNN-from-scratch)

这个实现背后的想法是创建 Python 类来表示卷积和最大池层。此外,由于这段代码后来被应用于 MNIST 分类问题,我为 softmax 层创建了一个类。

每个类都包含实现正向传播和反向传播的方法。

这些层随后被连接成一个列表,以生成实际的 CNN。

卷积层实现

构造器将卷积层的核的数量及其大小作为输入。我假设只使用大小为**kernel_size****kernel_size** 的*方核。

在第 5 行,我生成了形状为**(kernel_num, kernel_size, kernel_size)** 的随机过滤器,并将每个元素除以内核大小的*方进行归一化。

**patches_generator()** 方法是一个生成器。它产生执行每个卷积步骤的图像部分。

**forward_prop()** 方法对上述方法生成的每个面片进行卷积。

最后,**back_prop()** 方法负责计算损失函数相对于层的每个权重的梯度,并相应地更新权重值。注意,这里所说的损失函数并不是网络的全局损失。取而代之的是由最大池层传递给前一卷积层的损失函数。

为了展示这个类的实际效果,我用 32 个 3x3 滤镜实例化了一个**ConvolutionLayer** 对象,并对一个图像应用了正向传播方法。输出由 32 个稍小的图像组成。

原始输入图像的大小为 28x28 像素,如下所示:

来源:作者。

在应用卷积层的前向传播方法之后,我获得了 32 个大小为 26×26 的图像。这里我画了其中一个:

来源:作者。

正如你所看到的,图像稍微变小了,手写数字也变得不那么清晰了。考虑到这个操作是由一个填充了随机值的过滤器执行的,所以它不代表一个经过训练的 CNN 实际执行的操作。尽管如此,你可以得到这样的想法,这些卷积提供了更小的图像,其中对象特征是孤立的。

最大池层实施

构造函数方法只分配内核大小值。以下方法与卷积层的方法类似,主要区别在于反向传播函数不更新任何权重。事实上,池层并不依赖于权重来执行聚合。

Sigmoid 层实现

softmax 层使由 max 池提供的输出体积变*,并输出 10 个值。它们可以被解释为对应于数字 0-9 的图像的概率。

结论

你可以克隆包含代码的 GitHub 库,用**main.py** 脚本玩。该网络当然没有达到最先进的性能,但在几个时期后达到 96%的准确率。

参考

[1]: 使用 Scikit-Learn、Keras 和 TensorFlow 进行机器实践学习,第二版——aurélien géRon

[2]: 深度学习 54: CNN_6 —用 Python 从零开始实现 CNN

利用 River ML 和 Apache Flink 构建信用卡欺诈检测在线培训渠道

原文:https://towardsdatascience.com/building-a-credit-card-fraud-detection-online-training-pipeline-with-river-ml-and-apache-flink-25549b89583d

来自的照片

在本教程中,我们将使用的主要框架是:

  • Flink :全分布式实时批处理框架
  • RiverML :在线学习图书馆
  • Flask :用于构建 RESTful 微服务的开源 python 包

TL;DR:代码在GitHub上。

通常我们在处理 ML 管道时至少有两个独立的过程。在第一阶段,我们用一段时间内收集的一些数据训练一个新的 ML 模型。这通常称为批量训练,根据数据量的不同,这可能是一个更慢、计算更密集的过程。在第二阶段,我们采用我们在训练中产生的模型,并在新数据上使用它来标记它,这一过程称为推理。*年来,一种新的范式出现了,它试图将训练和推理结合起来,它被称为在线训练。这有两个主要的好处。首先,我们不需要那么多的计算能力来进行训练,因此成本较低,并且 ML 流水线被简化。其次,用更多数据训练的改进的 ML 模型立即可用,并且在某些情况下,例如欺诈检测,这是非常重要的,因为它可以减少假阳性的情况,从而更快更好地检测欺诈。

我们的 ML 管道将有两个组件:使用 Apache Flink 完成的实时摄取部分,以及使用 Flask 和 RiverML 的 ML 服务部分,后者负责在线培训。我们将使用 Apache Flink 来读取数据,因为它是一个低延迟、高度可伸缩的*台,可用于大数据应用程序。Apache Flink 最初是为 JVM 语言开发的,现在对 python 有了很好的支持,这就是我们将在本教程中使用的。所以让我们开始吧。

步骤 1:为所有依赖项设置 python 环境

我们将使用pipenv在一个单独的环境中安装我们需要的所有 python 包。在我提供的 github 链接中,有一个我们将使用的 Pipfile:

[[source]]
url = "[https://pypi.org/simple](https://pypi.org/simple)"
verify_ssl = true
name = "pypi"[packages]
apache-flink = "*"
river = "*"
flask = "*"[dev-packages][requires]
python_version = "3.6"

正如你所看到的,我们在 python 版本中使用了flink, river and flask,这是一个很好的选择,可以让所有的依赖项相互兼容。

要安装pipenv只需运行:

pip install --user pipenv

然后在我们项目的根目录中,我们可以安装所有的库:

pipenv install

为了激活环境,我们使用 shell 命令:

pipenv shell

如果一切运行成功,您应该会看到环境被激活,如下图所示:

我们将为这一步创建一个简单的 python 脚本,它将读取输入的信用卡交易,并将调用 RiverML 欺诈检测系统,算法的结果将存储在一个文件中。对于输入数据,我们将使用包含 2013 年 9 月欧洲持卡人信用卡交易的数据集。该数据集显示了两天内发生的交易,其中 284,807 笔交易中有 492 笔欺诈。数据集高度不*衡,正类(欺诈)占所有交易的 0.172%。这些数据可以在 RiverML 库中找到。

我们首先创建一个 Apache Flink 环境,这是我们摄取应用程序的入口点。这适用于我们想要创建的任何 Apache Flink 应用程序:

env = StreamExecutionEnvironment.get_execution_environment()
# write all the data to one file
env.set_parallelism(1)

作为一个完全分布式的框架,处理可以在多个线程上完成,但对于本教程的范围,我们将只使用一个线程,作为直接结果,只创建一个文件作为输出。

接下来,我们将从信用卡数据中读取一些行,并将它们存储在一个列表中。我们将使用env.from_collection将列表读入一个 Flink 数据流。对于本教程来说,这已经足够好了,但在生产环境中,我们可能会从 Apache Kafka 或 AWS Kinesis 等事件存储中读取这些数据,这将确保我们获得连续的记录流:

# get the credit card data
dataset = datasets.CreditCard()# create a small collection of items
i = 0
num_of_items = 2000
items = []
for x, y in dataset:
  if i == num_of_items:
    break
  i+=1
  items.append((json.dumps(x), y))credit_stream = env.from_collection(
        collection=items,
        type_info=Types.ROW([Types.STRING(), Types.STRING()]))

您还会注意到,当我们创建 Flink 数据流时,我们还需要定义一个模式。在我们的例子中,我们使用代表两个字符串的 Types.ROW([Types.STRING(), Types.STRING()]),第一个包含交易值,第二个是标签,可以是 0(无欺诈)和 1(欺诈)。一个交易记录的示例:

'{Time=0.0, V21=-0.018306777944153, V20=0.251412098239705, V23=-0.110473910188767, V22=0.277837575558899, V25=0.128539358273528, V24=0.0669280749146731, V27=0.133558376740387, V26=-0.189114843888824, V1=-1.3598071336738, V2=-0.0727811733098497, V28=-0.0210530534538215, V3=2.53634673796914, V4=1.37815522427443, V5=-0.338320769942518, V6=0.462387777762292, V7=0.239598554061257, V8=0.0986979012610507, V9=0.363786969611213, Amount=149.62, V10=0.0907941719789316, V12=-0.617800855762348, V11=-0.551599533260813, V14=-0.311169353699879, V13=-0.991389847235408, V16=-0.470400525259478, V15=1.46817697209427, V18=0.0257905801985591, V17=0.207971241929242, V19=0.403992960255733}'

然后我们使用数据流中的map方法来调用欺诈服务。在本教程的后面,我们将回顾微服务的创建和启动,但现在我们需要知道端点是[http://localhost:9000/predict](http://localhost:9000/predict'),我们发送的有效负载是{x: feature, y:label}:

# detect fraud in transactions
fraud_data = credit_stream.map(lambda data: \
        json.dumps(requests.post('[http://localhost:9000/predict'](http://localhost:9000/predict'), \
                   json={'x': data[0], 'y': data[1]}).json()),\ 
                   output_type=Types.STRING())

最后我们写出结果。我们可以注意到,我们使用fraud_data.sink_to来写入文件。最后,我们还告诉 Flink,我们已经准备好使用env.execute()来执行流水线:

# save the results to a file
fraud_data.sink_to(
  sink=FileSink.for_row_format(
            base_path=output_path,
            encoder=Encoder.simple_string_encoder())
        .build())# submit for execution
env.execute()

该文件应包含ROCAUC指标和结果,false 表示没有检测到欺诈:

{"performance": {"ROCAUC": 0.4934945788156797}, "result": false}

第三步:编写 Flask 在线培训微服务

我们将构建一个标准的 REST 服务来包装 ML 欺诈模型。这样做通常是为了让算法与摄取层松散耦合。如果我们需要部署另一个版本的模型,我们只需要在不干扰 Flink 消费者的情况下更新微服务。

最初,我们创建一个包含所有 ML 对象的类,我们将使用这些对象与发送到服务的新数据进行交互。model由一个标准定标器和一个逻辑回归分类器组成,前者将数据转换为零均值和单位方差,后者是检测欺诈等二元任务的最佳选择。我们还使用ROCAUC度量来确定算法在当前迭代中的表现。

class RiverML:
    # fraud detection model
    model = compose.Pipeline(
        preprocessing.StandardScaler(),
        linear_model.LogisticRegression()
    ) # ROCAUC metric to score the model as it trains
    metric = metrics.ROCAUC()
fraud_model = RiverML()

接下来,我们实际编写预测端点。这将是一个POST,因为我们需要从用户那里检索数据。这里有几个重要的步骤。fraud_model.model.predict_one(x_data)将对新交易进行预测。正如我们稍后将看到的,最初的预测不会很好,但是随着越来越多的数据被输入到模型中,它将给出更好的结果。然后,我们使用fraud_model.metric.update(y_data, y_pred)来计算ROCAUC指标,使用fraud_model.model.learn_one(x_data, y_data)来更新带有正确标签的模型。如你所见,预测和学习都在一个应用程序中完成。

[@app](http://twitter.com/app).route('/predict', methods=['POST'])
def predict():
    # convert into dict
    request_data = request.get_json()
    x_data = json.loads(request_data['x'])
    y_data = json.loads(request_data['y']) # do the prediction and score it
    y_pred = fraud_model.model.predict_one(x_data)
    metric = fraud_model.metric.update(y_data, y_pred) # update the model
    model = fraud_model.model.learn_one(x_data, y_data) return jsonify({'result': y_pred, 'performance': {'ROCAUC': fraud_model.metric.get()}})

最后,我们发回一个 JSON,其中包含实际的预测和模型的表现。

第四步:全部运行

要运行我们刚刚编写的 ML 管道,首先我们需要运行 flask 应用程序。要在单独的终端运行中这样做:

python fraud_river_ml.py

您应该会看到类似下图的内容,flask 告诉我们该服务可用:

现在,我们还可以在单独的终端窗口中运行 Apache Flink 消费者:

python flink_consumer.py --output data

这个 python 脚本将使用--output参数来定义我们存储结果的位置。这应该需要一分钟左右的时间,但是在这之后,脚本将完成运行,我们应该会在我们的位置找到一个文件。再次请注意,在生产环境中,flink 消费者脚本不应该停止运行,因为它会消耗无限的数据。

如果我们查看输出文件,我们将会看到模型是如何随时间演变的。第一次迭代将有ROCAUC -0.0:

{"performance": {"ROCAUC": -0.0}, "result": false}
{"performance": {"ROCAUC": -0.0}, "result": false}
{"performance": {"ROCAUC": -0.0}, "result": true}
{"performance": {"ROCAUC": -0.0}, "result": false}
{"performance": {"ROCAUC": -0.0}, "result": true}
{"performance": {"ROCAUC": -0.0}, "result": false}
{"performance": {"ROCAUC": -0.0}, "result": false}
{"performance": {"ROCAUC": -0.0}, "result": false}
{"performance": {"ROCAUC": -0.0}, "result": false}
{"performance": {"ROCAUC": -0.0}, "result": false}

但是,随着我们将越来越多的数据输入逻辑回归算法,这种情况将会得到改善:

{"performance": {"ROCAUC": 0.4992462311557789}, "result": false}
{"performance": {"ROCAUC": 0.4992466097438473}, "result": false}
{"performance": {"ROCAUC": 0.4992469879518072}, "result": false}
{"performance": {"ROCAUC": 0.4992473657802308}, "result": false}
{"performance": {"ROCAUC": 0.49924774322968907}, "result": false}
{"performance": {"ROCAUC": 0.4992481203007519}, "result": false}
{"performance": {"ROCAUC": 0.499248496993988}, "result": false}
{"performance": {"ROCAUC": 0.4992488733099649}, "result": false}
{"performance": {"ROCAUC": 0.49924924924924924}, "result": false}

就是这样!我希望你喜欢这个教程,并发现它很有用!我们看到了如何使用 Apache Flink 编写 python 应用程序,如何使用 River ML 训练在线分类器,以及如何通过结合训练层和推理层来降低成本。这是可靠和可扩展的大数据 ML 应用程序的主干,我们可以在众多提供可扩展基础设施的云提供商(如 AWS、GCP 或 Azure)之一的生产中部署这些应用程序。

用 Flask 和 SQLAlchemy 构建 CRUD 应用程序

原文:https://towardsdatascience.com/building-a-crud-app-with-flask-and-sqlalchemy-1d082741bc2b

关于构建书店 CRUD 应用程序后端的深入教程

汤姆·赫曼斯在 Unsplash 上的照片

CRUD 应用程序是一个网络应用程序,允许你创建、读取、更新和删除东西。这是 web 开发中非常常见的任务,对于学习如何构建 web 应用程序非常有用。

在本教程中,你将学习如何在 Flask 中构建一个 CRUD 应用程序,结果将是一个书店 web 应用程序的工作后端。我们将定义服务来处理 CRUD 操作;RESTful bookshop API 的获取、发布、上传和删除请求。

构建书店应用程序有助于学习,因为这是一个真实的例子,而不是一个玩具项目。

本教程基于 FlaskFlask-SQLAlchemy 扩展

Flask 是一个使用 Python 构建 web 应用的微框架。这是一个非常轻量级的框架,易于学习和使用。

轻量级并不意味着 Flask 不强大。每当您想在应用程序中使用类似于 ORM(对象关系映射)的东西时,您可以使用 Flask 提供的扩展。在本教程中,我使用 Flask-SQLAlchemy 扩展创建了一个数据库和一个存储书籍的表。

SQLAlchemy 是一个 Python ORM(对象关系映射)库,它使得使用数据库变得容易。

目录:

设计数据库

在创建数据库之前,我们需要定义数据库模式和表。模式是数据库的元数据结构,而表是我们想要存储的实际数据。

这个项目的设计很简单:我们有一个名为books的表来存储图书字段:isbn(图书的 ISBN 号)、标题、作者和价格。

这些字段将通过 SQLAlchemy ORM 存储在数据库中。Flask API 将使用这些字段作为 CRUD 操作的数据模型。

下面是一个 UML 图,显示了依赖于数据库模式的 API 中使用的函数:

Flask 应用程序调用了依赖于 Book 表的函数。(作者在 Plantuml 上设计的)

正如我们所见,Flask API 有 5 个依赖于 Book 表的函数。您将看到这些函数如何从 SQLAlchemy 调用适当的方法。让我们首先看看我们是如何构造 API 函数的。这些函数将被 Flask API 调用,并用@app.route装饰器进行装饰。每个的映射如下所示:

  • get_books()使用 GET 请求列出所有映射到/book/list URL 的书籍。
  • get_book(isbn)获取由我们传递给函数的 URL 参数isbn定义的指定书籍。这个函数使用 GET 请求映射到/book/<isbn> URL。
  • create_book()向数据库添加新书。这个函数使用 POST 请求映射到/book URL。
  • update_book(isbn)使用 PUT 请求更新映射到/book/<isbn> URL 的指定图书。
  • delete_book(isbn)使用删除请求删除映射到/book/<isbn> URL 的指定图书。

构建 API

为了跟进这个项目,您可以创建一个文件并将代码转储到其中。您还可以创建多个文件,并从这些文件中导入代码来分离关注点。

我更喜欢将 API 代码组织在多个文件中。原因是它有助于你保持代码的组织性,也有助于你保持代码的整洁。

让我们创建一个名为bookshop的文件夹。在该文件夹中,该项目的结构如下所示:

**.**
**├──** app
**│  ** ├── __init__.py
**│  ** ├── models.py
**│  ** └── routes.py
**├──** bookshop.py
**├──** config.py

我试图在不使用蓝图的情况下将结构做得尽可能的小,我认为这对这个小应用来说太过了。以下是每个文件的分类:

  • bookshop.py是包含 Flask API 的主文件。
  • config.py包含 API 的配置。
  • app/__init__.py是包含数据库和应用程序实例的文件。
  • app/models.py包含数据库模式和 ORM。
  • app/routes.py包含将由 API 调用的 API 函数。

让我们从app/models.py文件开始创建数据库。

安装依赖项

在创建 db 模型之前,让我们安装 Flask 和 Flask-SQLAlchemy 扩展。

让我们也安装数据库引擎。在本教程中,您将使用 MySQL,但也可以随意使用任何其他数据库引擎。SQLAlchemy 支持 MySQL、SQLite、Postgres 等等。

对于 MySQL,安装 PyMySQL 库。

让我们全部安装:

**$** pip install flask flask-sqlalchemy PyMySQL

现在是时候创建数据库了。

创建数据库模型

让我们如下定义app/models.py文件中的数据库模型:

from . import db **class** Book(db.Model):
    __tablename__ = 'books'
    isbn = db.Column(db.Integer, primary_key=True)
    author = db.Column(db.String(100), nullable=False)
    title = db.Column(db.String(100), nullable=False)
    price = db.Column(db.Float) **def** to_json(self):
        **return** {
            'isbn': self.isbn,
            'author': self.author,
            'title': self.title,
            'price': self.price
        }

在该文件中,我们将表名定义为books,将字段定义为:

  • isbn:表格的主键。
  • author:图书作者,字符串字段,不可为空。它的长度限制在 100 个字符以内。
  • title:书名,为必填字段,长度为 100 个字符。
  • price:书的价格,是一个浮点字段,可以为空。

这里使用的to_json()函数将 Book 对象转换为 JSON 对象,该对象可以在浏览器上返回给客户机。我们将在接下来的章节中看到更好的方法。

注意,Book类是db.Model类的子类。该db实例在app/__init__.py文件中定义如下:

from flask_sqlalchemy import SQLAlchemydb = SQLAlchemy()

配置您的烧瓶应用程序

现在我们有了数据库模型,让我们为 SQLAlchemy 扩展配置 Flask 应用程序。

配置应用程序的最佳实践是创建并定义一个父配置类,该类将保存所有环境的通用配置。然后为您的环境创建子配置类的实例。在我们的例子中,我们将创建三个环境:开发、测试和生产。

让我们看看实际情况:

import os **class** Config:
    SQLALCHEMY_TRACK_MODIFICATIONS = False @staticmethod
    **def** init_app(app):
        **pass** **class** DevelopmentConfig(Config):
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = os.getenv("DEV_DATABASE_URL") **class** TestingConfig(Config):
    TESTING = True
    SQLALCHEMY_DATABASE_URI = os.getenv("TEST_DATABASE_URL") **class** ProductionConfig(Config):
    SQLALCHEMY_DATABASE_URI = os.getenv("DATABASE_URL") config = {
    "development": DevelopmentConfig,
    "testing": TestingConfig,
    "production": ProductionConfig,
    "default": DevelopmentConfig
}

因此Config类保存了应用程序的全局配置,它们是:

  • SQLALCHEMY_TRACK_MODIFICATIONS设置为 False,禁用修改跟踪系统。这是避免跟踪从 Flask-SQLAlchemy 到 SQLAlchemy 库的变化的开销的好方法。
  • init_app()是一个静态方法,用于初始化应用程序配置。

在这个Config父类之后,我们为每个环境创建了三个子类。每个环境都定义了适合该环境的配置。

最后,我们有了config字典,它将环境名映射到配置类。默认环境是我们将在本教程中使用的开发环境。

DevelopmentConfig类将DEBUG属性设置为 True,因为如果 API 中有任何错误,我们希望在浏览器中看到调试消息。

此外,它的SQLALCHEMY_DATABASE_URI属性设置了数据库 URL,这是我们为连接到数据库而定义的。

在我们的例子中,我们将数据库 URL 设置为环境变量DEV_DATABASE_URL,这是 MySQL 数据库的 URL。在终端上运行以下命令来定义 env 变量:

**$** export DEV_DATABASE_URL=mysql+pymysql://<username>:<password>@localhost:3306/flaskapp

其中<username><password>是 MySQL 数据库的凭证,flaskapp是数据库的名称。请随意用您自己的值替换任何值。

如果您正在使用另一个数据库引擎,您可以将DEV_DATABASE_URL更改为该数据库的适当 URL。例如,如果您正在使用 sqlite,您可以将其设置为sqlite:///<path_to_db>

现在让我们导入配置字典并开始创建 Flask 应用程序。现在,app/__init__.py文件如下所示:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from config import configdb = SQLAlchemy() **def** create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app) db.init_app(app)
    **return** app

create_app()函数用于创建基于环境的应用实例,该环境通过config_name参数作为变量传递给函数。

app.config.from_object()方法用于从config字典加载配置。然后,该配置用于初始化应用程序。

最后,用 app 实例初始化 SQLAlchemy 实例db

让我们为 API 设置第一个端点,它们是 GET 请求。

设置获取请求

让我们将 GET 请求函数添加到app/routes.py文件中:

import os
from . import create_app
from .models import Book
from flask import jsonifyapp = create_app(os.getenv('FLASK_CONFIG') **or** 'default') @app.route("/book/list", methods=["GET"])
**def** get_books():
    books = Book.query.**all**()
    **return** jsonify([book.to_json() **for** book **in** books]) @app.route("/book/<int:isbn>", methods=["GET"])
**def** get_book(isbn):
    book = Book.query.get(isbn)
    **if** book **is** None:
        abort(404)
    **return** jsonify(book.to_json())

create_app()函数实例化应用程序实例,然后使用app.route()装饰器注册端点。在get_books()方法中,我们使用Book.query.all()查询所有书籍的数据库,然后使用jsonify()函数返回所有书籍的 JSON 表示;它是一个帮助器函数,将 Python 对象序列化为 JSON。

现在,让我们在运行应用程序之前通过 SQLAlchemy 添加一些数据。

向 SQLAlchemy 添加数据

向数据库添加数据的一种方法是打开一个 Flask 交互式 shell,然后创建一个新的Book模型实例。

让我们通过运行:

$ flask shell

此命令为您打开一个交互式会话来执行 Python 命令。这对调试和测试您的代码很有帮助。

我们现在在壳里面。让我们导入db实例和Book模型:

>>> from app import db
>>> db
<SQLAlchemy engine=mysql+pymysql://root:***@localhost:3306/flaskapp?charset=utf8>
>>> from app.models import Book
>>> Book
<**class** 'app.models.Book'>

假设已经在您的机器上创建了flaskapp数据库,让我们在数据库中创建Book表,然后定义一个新的 book:

>>> db.create_all()
>>> book = Book(author="Ezz", title="Cleaner Python", price=0.0)
>>> book
<app.models.Book **object** at 0x7f404a052e50>
>>> db.session.add(book)
>>> db.session.commit()

所以现在已经用db.create_all()创建了Book表,它创建了所有属于db.Model子类的表。

使用db.session.add()book变量添加到数据库中。注意,将 book 对象添加到数据库中并不意味着您可以查询它。它还没有提交到数据库。这就是为什么我们需要运行db.session.commit()来提交我们对数据库所做的更改。

让我们再创作一本书:

>>> book2 = Book(author="Ahmed", title="Python", price=10.99)
>>> db.session.add(book2)
>>> db.session.commit()

所以现在我们书店里有两本书。这将足以演示如何列出和使用 API。

让我们用Ctrl+C(或CMD+C)关闭外壳,返回我们的终端运行 app。

运行烧瓶应用程序

您可以通过在终端上运行以下命令来运行该应用程序:

**$** export FLASK_APP=bookshop.py
**$** flask run

第一个命令定义了FLASK_APP环境变量指向bookshop.py文件。但是当你做flask run的时候,你会发现一个预期的错误。那个bookshop.py文件是空的。让我们解决这个问题,从app/routes.py文件导入app变量:

from app.routes import app

现在,您可以运行应用程序并在[http://localhost:5000/book/list](http://localhost:5000/book/list.) 公开 API 端点。

在这种情况下,您会发现以下 JSON 响应:

[
  {
    "author": "Ezz", 
    "isbn": 1, 
    "price": 0.0, 
    "title": "Cleaner Python"
  }, 
  {
    "author": "Ahmed", 
    "isbn": 2, 
    "price": 10.99, 
    "title": "Python"
  }
]

当您调用这个端点http://localhost:5000/book/1时,您将得到第一本书:

{
  "author": "Ezz", 
  "isbn": 1, 
  "price": 0.0, 
  "title": "Cleaner Python"
}

1替换为图书的 ISBN,您将得到与您查询的图书相关的响应。

删除图书

删除请求类似于我们对 GET 请求所做的。

让我们打开app/routes.py文件并添加以下代码片段:

from . import db
...@app.route("/book/<int:isbn>", methods=["DELETE"])
**def** delete_book(isbn):
    book = Book.query.get(isbn)
    **if** book **is** None:
        abort(404)
    db.session.delete(book)
    db.session.commit()
    **return** jsonify({'result': True})

这里,我们使用db.session.delete(book)从数据库中删除这本书,然后使用db.session.commit()提交修改。

你可能想知道如何测试,特别是因为DELETE路线与GET路线相同。要删除一本书,您需要使用curl并选择DELETE方法,因为默认情况下它会将请求视为GET请求。

例如,如果要删除第二本书,可以使用以下命令:

**$** curl http://localhost:5000/book/2 -X DELETE
**{**
  "result"**:** true
**}**

它成功地返回了一个 JSON 响应,其中的result键被设置为True

您可以再次调用 GET 端点,通过查看 URL: http://localhost:5000/book/list或使用curl命令来检查第二本书是否已经完成:

**$** curl http://localhost:5000/book/list
**[**
  {
    "author": "Ezz", 
    "isbn": **1,** 
    "price"**:** 0.0, 
    "title"**:** "Cleaner Python"
  }
**]**

它只给出了一本书的列表。第一个。

添加新书

我们还可以通过使用POST方法调用一个函数来向数据库添加一本新书。

from flask import request
...@app.route('/book', methods=['POST'])
**def** create_book():
    **if** **not** request.json:
        abort(400)
    book = Book(
        title=request.json.get('title'),
        author=request.json.get('author'),
        price=request.json.get('price')
    )
    db.session.add(book)
    db.session.commit()
    **return** jsonify(book.to_json()), 201

为了测试添加一本新书,让我们使用curl程序:

**$** curl -H "Content-Type: application/json" -X POST -d '{"title": "Learning", "author": "Ibrahim", "price": "3.44"}' http://localhost:5000/book
**{**
  "author"**:** "Ibrahim", 
  "isbn"**:** 3, 
  "price"**:** 3.44, 
  "title"**:** "Learning"
**}**

当然,我们也可以使用flask shell添加新书。这种方法的问题是您必须导入dbBook实例。为了避免这种情况,现在的bookshop.py文件应该如下所示(在添加了一个代码片段之后):

from app import db
from app.routes import app
from app.models import Book @app.shell_context_processor
**def** make_shell_context():
    **return** **dict**(db=db, Book=Book)

@app.shell_context_processor装饰器用于注册一个函数,该函数将被调用来向 shell 会话注入变量。

make_shell_context()函数返回一个字典,其中包含我们需要添加到 shell 会话中的dbBook实例,这样我们就可以在 shell 中使用它们,而不必导入它们。

更新图书

更新一本书类似于增加一本新书,除了我们使用PUT方法而不是POST

让我们将以下内容添加到app/routes.py文件中:

@app.route('/book/<int:isbn>', methods=['PUT'])
**def** update_book(isbn):
    **if** **not** request.json:
        abort(400)
    book = Book.query.get(isbn)
    **if** book **is** None:
        abort(404)
    book.title = request.json.get('title', book.title)
    book.author = request.json.get('author', book.author)
    book.price = request.json.get('price', book.price)
    db.session.commit()
    **return** jsonify(book.to_json())

为了测试图书更新,让我们再次使用curl:

**$** curl http://localhost:5000/book/3 -X PUT -H "Content-Type: application/json" -d '{"author": "Ahmed", "title": "Python for Beginners", "price": 12.99}'
**{**
  "author"**:** "Ahmed", 
  "isbn"**:** 3, 
  "price"**:** 12.99, 
  "title"**:** "Python for Beginners"
**}**

结论

本教程介绍了如何通过真实应用程序使用 Flask 创建 RESTful API 的基础知识。

我们已经看到了如何使用 SQLAlchemy 连接到数据库,如何创建模型,如何将模型映射到表,如何创建路由,如何使用curl程序调用和测试 API,以及如何使用flask shell调试应用程序。

我们还讲述了如何从 CRUD bookshop 应用程序中读取、创建、更新和删除一本书。

你可以从这个链接获得本教程的完整代码。

想用 Python 写出干净的代码? 下载免费清理器 Python 电子书 。它提供了 5 种方法来编写高效的 Python 代码。

通过 Presidio 简化 PII 匿名化

原文:https://towardsdatascience.com/building-a-customized-pii-anonymizer-with-microsoft-presidio-b5c2ddfe523b

匿名化的背景以及如何构建匿名器

马库斯·斯皮斯克在 Unsplash 上的照片

随着欧洲 GDPR 法案的实施,各种法规纷纷出台,正确处理敏感信息,特别是个人身份信息(PII) 成为许多公司的一项要求。在本文中,我们将讨论什么是 PII,以及如何在非结构化数据(尤其是文本)中匿名化 PII。我们还将使用 Microsoft Presidio 演示一个文本匿名器的示例实现,这是一个提供快速 PII 识别和匿名化模块的开源库。本文分为以下几个部分:

  1. 背景:隐私和匿名
  2. 现有的匿名技术
  3. 用微软 Presidio 定制 PII 匿名器
  4. 结论、链接和参考文献

跳到任何你觉得最有趣的部分!

背景

早在 19 世纪 50 年代,当美国人口普查局开始从公开的美国公民人口普查数据中删除个人数据时,就已经研究并应用了数据保护和隐私保护技术。自从早期使用诸如添加随机噪声或聚集的简单技术以来,已经提出并改进了各种模型。隐私是一项基本人权。根据字典的定义,它是

个人或团体封闭自己或关于自己的信息,从而有选择地表达自己的能力。

哪些个人信息被视为敏感信息,可能会损害个人隐私?欧盟在 2018 年出台了最严厉的隐私与安全法 【通用数据保护条例 (GDPR) 。GDPR 将个人数据定义为

“与可直接或间接识别的个人相关的任何信息。姓名和电子邮件地址显然是个人数据。位置信息、种族、性别、生物特征数据、宗教信仰、网络 cookies 和政治观点也可以是个人数据。”

根据一条信息是否可以直接或间接用于重新识别个人,人们可以将上述信息分为直接识别符准识别符。<#61d8>

-直接标识符:个人独有的一组变量(姓名、地址、电话号码或银行账户),可用于直接识别主体。

-准识别信息:单独使用时无法重新识别的信息(如性别、国籍或居住城市),但与其他准识别信息和背景知识结合使用时可能会重新识别。

现有的匿名技术

数据保护法规让公司和个人在利用数据获得洞察力和保护隐私之间不断斗争。但是,匿名化技术可以应用于数据集,因此不可能识别特定的个人。这样,这些数据集将不再受数据保护法规的保护。

匿名结构化数据

当涉及到结构化数据的匿名化时,有一些已建立的隐私数学模型,如 K-匿名或差分隐私。

K-anonymous 于 1998 年由 Pierangela Samarati 和 Latanya Sweeney 提出。如果在数据集中,一个人包含的每个信息不能与至少 k-1 个其他个体区分开来,则屏蔽数据集具有k-匿名性属性。有两种方法可以用来实现 k-匿名。一个是抑制,从数据集中完全删除一个属性的值;还有一种是泛化,用一个更一般的值替换一个属性的特定值。

L-多样性k-匿名的进一步扩展。如果我们将数据集中具有相同准标识符的多组行放在一起,那么每个敏感属性至少有 l 个不同的值,那么我们可以说这个数据集具有 l 多样性。

微软研究院的 Cynthia Dwork 于 2006 年引入了差分隐私。换句话说,仅仅通过观察差分私有分析的输出,人们不能肯定地说一个特定的个体是否在数据集中。有了差分隐私,我们可以了解关于一个群体的有用信息,而不知道关于一个个体的信息。

计算数据集统计数据(如*均值、方差)或执行某些聚合的算法可以被视为差分私有算法。也可以通过在数据中添加噪声来实现。差分隐私使用参数ε来调节隐私和数据集有用性之间的权衡。

还有一些众所周知的技术可应用于结构化数据库的匿名化:

  • 屏蔽:删除、加密或隐藏私有标识符
  • 假名化:用假名或假值替换私有标识符
  • 泛化:用一个更通用的值替换一个特定的标识符值
  • 交换:交换数据集的属性值,使它们与原来的不同
  • 扰动:通过引入随机噪声或使用随机方法改变数据

匿名化非结构化数据

对文本或图像等非结构化数据进行匿名化的过程更具挑战性。它需要检测非结构化数据中敏感信息的位置,然后对其应用匿名技术。由于非结构化数据的性质,直接使用简单的基于规则的模型可能不会有很好的性能。例外情况是电子邮件或电话号码,它们可以通过基于模式的方法(如 RegEx)非常有效地检测和匿名。其他挑战包括不同国家的识别资料类型不同。因此,很难设计一个适用于所有用例的通用系统。典型的标识符类型包括上面提到的内容—姓名、电子邮件、驾驶执照等。除此之外,你可以在这里找到一个关于敏感信息类型的详尽列表

除了基于规则的方法,自然语言处理(NLP)技术也被应用于文本匿名化。特别是用于命名实体识别(NER)任务的技术。这是一种序列标记任务。在这种情况下,标签指示令牌(如单词)是否对应于命名实体,如人、 地点、日期

命名实体识别(图片由作者提供)

几个神经模型已经在具有一般命名实体的数据集上的 NER 任务上实现了最先进的性能。当他们在包含各种类型的个人信息的医疗领域数据上接受培训时,他们在这些数据上也表现出了最先进的性能。这些模型架构包括带有字符嵌入的递归神经网络 或双向变压器 SpaCy 也有一款 NER 车型。它是一个在 Ontonotes 数据集上微调的 RoBERTa 语言模型,有 18 个命名的实体类别,如 PERSONGPECARDINALLOCATION、等(实体类型的完整列表见此处)。另一个开箱即用的工具是 Presidio 。这是一个由微软开发的匿名化 SDK,依靠基于规则和基于 NER 的模型来检测和屏蔽 PII。支持实体的列表可以在这里找到

认出 PII 后会发生什么?

一种方法是用虚拟值替换标识符。例如,如果在类似“我的名字是 Amy,我的号码是 85562333 ”的句子中识别一个人的名字或电话号码,那么匿名化的文本可以是“我的名字是< PERSON_NAME >,我的号码是< PHONE_NUMBER > ”。这种方法简单明了,但也有一些缺点。当对所有相同的实体类型使用一个值时,我们会丢失重要的信息。在这个例子中,我们不知道这个人的性别。根据匿名文本的用途,可能需要保留此类信息。或者当涉及到地址时,人们希望将地理分布保持在一定水*。

另一种方法是用代理值替换检测到的实体。可以从每个实体类型的预定列表中随机抽取,也可以根据某种规则抽取。还有一些 Python 包,如 Faker ,可以生成合成地址或名称作为代理值。

用微软 Presidio 定制 PII 匿名器

当我们将 PII 匿名化应用于现实世界的应用时,可能会有不同的业务需求,这使得直接使用预训练模型具有挑战性。例如,想象一下,挪威的一家公司找到你,希望你为他们开发一个文本匿名器。他们希望它支持在英语和挪威语文本中匿名化 PII。除了常见的 PII 实体,您还需要检测遵循某些校验和规则的挪威国民 ID 号。预先训练的 NER 模型是很好的,但是如果不使用额外的标记数据来微调模型以获得良好的性能,您就不能轻松地添加新的实体类型。因此,拥有一个能够利用预训练模型并且易于定制和扩展功能的工具是很好的。

微软 Presidio 是一个开源的 SDK,它就是这样做的。据他们说,

Presidio 支持针对特定业务需求的可扩展性和可定制性,允许组织通过民主化去身份识别技术和引入决策透明度,以更简单的方式保护隐私。

它有两个主要部分——分析器和匿名器。分析器是一个基于 Python 的服务,用于检测文本中的 PII 实体。它利用命名实体识别、正则表达式、基于规则的逻辑以及多语言相关上下文的校验和。例如,它为电子邮件和 IP 地址使用预定义的基于 Regex 模式的识别器,并使用 SpaCy 自然语言处理模型为命名实体构建识别器。

Presidio 分析仪(图片来自 Presidio 文档

匿名器也是一个基于 python 的服务。它通过应用某些操作符(如 replace、mask 和 redact)将检测到的 PII 实体匿名化。默认情况下,它在文本中直接用实体类型替换检测 PII,如<EMAIL><PHONE_NUMBER>。但是可以定制它,为不同类型的实体提供不同的匿名逻辑。

Presidio 匿名器(图片来自 Presidio 文档

首先,按照此处的说明安装 Presidio 后,使用它就像调用默认AnalyzerEngineAnonymizerEngine一样简单:

AnalyzerEngine策划了对 PII 的侦查。它的一些最重要的属性包括:

  • registry : RecognizerRegistry它包含了所有要在文本上使用的预定义或定制的识别器
  • nlp_engine:NLP 模块的抽象,包括对文本中的标记的处理功能
  • supported_languages:支持的语言列表

对于我们前面提到的场景,使用默认的分析器是不够的,我们需要通过添加以下定制来定制我们自己的分析器引擎。

1。附加语言支持

默认情况下,Presidio 使用英文文本。但是因为在我们的例子中我们也将处理挪威语文本,我们需要将挪威语添加到 Presidio 的NlpEngineProvider的配置中。

这是 SpaCy 上的挪威语模型管道。在运行分析器之前,还需要下载语言模型。

2。添加挪威电话号码识别器

Presidio 中有一组预定义的识别器。此处见详细实体列表。我们可以选择加载部分或全部。Presidio 的一个优点是很容易添加适合自己用例的识别器。在下面的示例中,我们为以+47 国家代码开头的挪威电话号码添加了一个识别器,因为默认的电话号码识别器不支持所有的国家代码。

3。添加挪威国家 ID 识别器

挪威身份证号码包含一个人的生日和一些遵循一定规则的数字(参见详细规则此处)。我们可以为挪威语 ID 识别器创建一个类,它以如下方式从 Presidio 扩展了EntityRecognizer:

最后,将所有东西放在一起,我们就有了我们定制的分析仪:

当我们运行分析器来分析一段文本时(通过调用analyzer.analyze(text),可选地给出要识别的实体类型列表),文本将通过我们定义的识别器管道。我们可以添加三种主要类型的识别器——基于模式的识别器、基于规则的识别器和外部识别器。在这个例子中,我们需要添加基于规则的识别器。基于规则的识别器利用 NLP 工件,这些工件是由 NLP 引擎产生的用于识别 PII 的令牌相关信息,例如词条、POS 标签或实体。

对于已识别的 PII,它将返回包含详细信息的字典:

{
  'entity_type': 'NORWEGIAN_ID',
  'start': 74,
  'end': 85,
  'score': 0.95
}

在匿名化方面,我们可以通过文本中的类型<NORWEGIAN_ID>直接屏蔽 PII 实体,得到类似“我的 id 是< NORWEGIAN_ID >”的内容,或者我们可以向Anonymizer提供一个自定义操作符的字典。例如,我们可以创建一个随机的 11 位数字来替换 ID。此外,我们还可以使用faker之类的包来生成合成名称。

用一些例子测试匿名器

为了测试匿名器的性能,我们从 LinkedIn 上找到了挪威语和英语的招聘信息。他们通常有许多 PII 实体。下面显示了一些示例文本(注意:这里发布的是经过编辑的版本,所以它们不包含任何真实的标识符)。

将样本文本提交到我们的匿名管道,我们获得了以下结果:

正如我们所见,对于英文文本,匿名器具有非常好的性能,可以检测所有的姓名、电子邮件和电话号码。对于文本语法合适的挪威语文本,检测性能也很好。然而,当谈到不是正确句子的挪威语文本时,例如第 4 个例子,我们可以看到它有点纠结,把电话号码和日期搞混了。对于那些对更详细的性能评估感兴趣的人,请查看这篇论文,其中他们进行了一项研究,将 Presidio 应用于一个来自维基百科文本的小型人工标注数据集。

结论

在本文中,我们讨论了隐私和 PII 的背景,并展示了如何使用Presidio来构建一个定制的 PII 匿名器。在真实文本上的结果是有希望的。我们还可以进一步改进。我们可以用一个更强大的语言模型来替换底层语言模型,以便在 NER 上获得更好的性能。为了识别更多由不同国家定义的 PII 实体,我们还可以添加更多基于规则的识别器。

最终备注:

我在蜡笔小组Inmeta 做数据科学顾问。我们看到越来越多的客户对正确处理 PII 感兴趣,所以我们非常感谢像 Presidio 这样的好的开源库,它使我们能够利用最先进的技术来解决这个问题。

在我的 Github 库中查看定制匿名器的完整实现。它还包括将它作为 Docker 应用程序运行并将其部署到 AWS 的脚本。感谢您阅读文章!我很想听听你对这个话题的看法!请随时在 LinkedIn 上与我联系。

参考

  1. ^ 《文本数据的匿名化模型:现状、挑战和未来方向》(2021 年)。计算语言学协会第 59 届年会暨第 11 届国际自然语言处理联合会议论文集
  2. ^ 皮耶安吉拉·萨马拉蒂和拉坦娅·斯威尼,《披露信息时保护隐私:k-匿名及其通过推广和抑制的实施》(1998 年)。技术报告,SRI 国际
  3. ^ 辛西娅·德沃克、弗兰克·麦克雪莉、科比·尼斯姆和亚当·斯密,《在私人数据分析中校准噪声与灵敏度》(2006 年)。密码学理论
  4. ^ Franck Dernoncourt,Ji Young Lee,Ozlem Uzuner 和 Peter Szolovits,使用递归神经网络对患者病历进行去识别(2017)。美国医学信息学协会杂志
  5. ^ Alistair EW Johnson、Lucas Bulgarelli 和 tom j pollard,使用预先训练的双向转换器对自由文本医疗记录进行去识别(2020)。美国计算机学会健康、推理和学习会议录

使用 Tensorflow 2 构建带有自定义数据集的 CycleGAN 模型

原文:https://towardsdatascience.com/building-a-cyclegan-model-with-custom-dataset-using-tensorflow-2-12d66be16378

展开拥抱脸空间

图片来源:作者

生成对抗网络是机器学习中的一种生成建模技术。在 GAN 中,两个不同的神经网络(生成器和鉴别器)相互竞争。生成器的输出虽然是合成的,但可能接*真实。

有许多不同的甘架构。今天,我们将关注 CycleGAN。cycleGAN 的有趣之处在于,它是一种不成对的图像到图像翻译技术。

在这项工作中,我们将研究一个在自定义数据集上训练和部署 cycleGAN 模型的端到端示例。你可以在这里找到可用的 CycleGAN 代码(使用 Tensorflow 2) ,所以我不会重复我们已经有的。相反,我想重点关注几个重要的缺失部分,这些部分是你在现实生活的深度学习项目中需要的——使用定制数据。评估 GAN 模型,使用已经训练好的模型进行预测,最后创建一个有趣的演示!!

因此,这项工作的贡献可以概括为—

  • 使用自定义图像数据创建张量流数据集
  • 计算 FID 分数以评估 GAN 模型
  • 保存和加载模型
  • 在拥抱面空间展开模型

玩得开心点。

使用自定义图像数据创建张量流数据集:

为了在 Tensorflow 中训练我们的模型,我们需要 Tensorflow 数据集格式的训练数据集——公开为tf.data.Datasetstf.data.Datasets中的每个元素可以由一个或多个元素组成。例如,图像管道中的单个元素可以是表示图像及其标签的一对张量。在我们的示例中,我们将以 TFRecord 格式表示图像,这是 Tensorflow 自己的二进制记录格式。使用 TFRecord 格式有几个好处

  • 这是一种二进制格式,因此占用的磁盘空间和内存的读/写时间更少
  • 使用 TFRecord,可以将多个数据集组合成一个数据集。例如,在这项工作中,我们将多个图像组合成一个 TFRecord。这个组合记录很好地集成到了tf.data.Datasets的数据加载和预处理功能中。这在加载非常大的数据集时特别有用,数据集库可以只加载 TFRecord 的必要部分进行处理,而不是将整个数据集加载到内存中。
  • 您可以在 TFRecord 中存储序列数据,例如单词嵌入或时间序列数据。

TFRecord 的一个缺点是——创建 TFRecord 并不简单——至少对我来说是这样:)

首先,我们需要定义一个描述 TFRecord 组件的字典。例如,对于一个图像分类问题(有监督的),您可能有一个image和一个label。因为我们正在研究一个 cycleGAN 模型,我们不需要一个label,因为它本质上是无人监管的。

feature = {
 ‘image’: _bytes_feature(image),
 }

这里,_bytes_feature是一个私有方法,它从一个字符串/字节返回一个字节列表

def _bytes_feature(value):
 “””Returns a bytes_list from a string / byte.”””
 if isinstance(value, type(tf.constant(0))):
 value = value.numpy() 
 return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))

一旦我们有了 TFRecord,我们如何知道记录实际上是正确创建的呢?我们可以计算每个 TFRecord 中的项目数(记住——我们在一个 TF record 中有多个图像)。我们还可以可视化整个数据集。

FILENAMES = tf.io.gfile.glob('cat*.tfrec')
print(f'TFRecords files: {FILENAMES}')
print(f'Created image samples: {count_data_items(FILENAMES)}')display_samples(load_dataset(FILENAMES, *IMAGE_SIZE).batch(1), 2, 5)

注意display_samples函数调用中的25这两个数字。这些数字表示行数和列数。将行数乘以列数得到数据集中图像的总数。因此,它需要与您的数据集大小相匹配。

关于自定义数据集创建的端到端代码,请参见这里的

评估 GAN 模型

没有单一的指标来评估 GAN 模型。根据用例,您可能想要使用定量和定性指标的组合。

在我们的工作中,我们将使用 FID 评分。Frechet 初始距离(FID)度量生成图像和真实图像的特征之间的距离。FID 越低越好。如果 FID 为 0,则表示两幅图像相同

那么我们如何计算 FID 分数呢?tldr

  • 使用预训练的 Inception V3 模型,删除最后一层
  • 生成两幅图像(生成的和真实的)的特征向量。向量大小将是 2,048
  • 然后使用论文中描述的等式 1 计算 FID 分数

FID 的一些缺点要记住

  • FID 使用一个预训练的初始模型,它的特征向量可能不能捕获你的用例的必要特征。
  • 为了更好地工作,FID 需要大的样本量。建议的最小样本量为 10,000

如果您想了解更多关于如何计算 FID 分数的信息,请参考这里的

保存并加载模型:

训练完模型后,我们想要保存它(假设我们对训练/验证损失和评估满意)。在保存模型时,我们希望确保保存了整个模型。

我们所说的整个模型是什么意思?

  • 模型的架构/配置、重量
  • 模型的优化器状态和
  • 模型的编译信息(如果。调用了 compile()

我们想要保存整个模型,因为—

  • 在推理过程中,您不需要模型架构代码。这样你会有一个干净的推理逻辑,更快的开发周期。
  • 如果您想要转换用于边缘设备(TFLite)的模型,或者想要使用 Tensorflow.js 在浏览器中运行模型,您需要拥有整个模型

有几种方法可以保存整个模型。对于 Tensorflow 2,我更喜欢 SavedModel 格式。可以用 Keras 的model.save方法,也可以用 Tensorflow 的tf.keras.models.save_model方法。在model.save()函数中,如果你使用一个字符串作为参数,你的模型将被保存为SavedModel格式。

model.save('art_generator')

在拥抱面部空间展开:

现在我们已经保存了模型,我们可以编写推理逻辑并部署它供人们使用。对于这个项目,我已经在拥抱面部空间这里部署了模型。如果你访问那个链接,你会看到这样的东西——

拥抱面部空间的示例应用程序

在那里,你可以上传你的cat照片(或任何其他宠物),按下submit按钮,等待 10 秒钟就可以看到猫art了,如下图所示

照片到艺术

你可能会意识到,我需要更多的迭代来提高艺术的质量。但尽管如此,这是一个有趣的应用程序玩!

关于如何在拥抱面部空间部署模型的细节可以在这里找到。

参考资料:

  1. https://developers.google.com/machine-learning/gan
  2. 关于 TF recordhttps://medium . com/mosely-ai/tensor flow-records-what-them-and-how-to-use-them-c 46 BC 4 BBB 564的详细信息
  3. 如何评价甘模式https://machine learning mastery . com/how-to-evaluate-generative-adversarial-networks/
  4. 卡格尔·周期根的例子https://www.kaggle.com/amyjang/monet-cyclegan-tutorial
  5. 如何计算 FID 分数https://machine learning mastery . com/how-to-implementing-the-frechet-inception-distance-FID-from-scratch/
  6. 如何保存和加载 Tensorflow 模型https://medium . com/deep-learning-with-keras/save-load-keras-models-with-custom-layers-8f 55 ba 9183 D2
  7. 关于评估 GAN 模型的详细信息https://wandb . ai/ayush-tha kur/GAN-evaluation/reports/How-to-evaluation-GANs-using-frech et-Inception-Distance-FID-vmlldzo 0 mtaxoti
  8. 在拥抱面部空间展开你的模型https://huggingface.co/docs/hub/spaces

在 Plotly Dash 中构建仪表板

原文:https://towardsdatascience.com/building-a-dashboard-in-plotly-dash-c748588e2920

在生理网上浏览数千份心电图记录

Shutterstock: ID1855544260

在这篇文章中,我将分享在 Plotly Dash 中构建交互式仪表盘(app)的过程,以探索海量的在线数据集。我们将使用来自 Physionet [1,2]上的 Icentia11k 数据库的心电图(ECG)记录,经许可使用。这篇文章的目的是帮助初学者建立他们的第一个仪表板,同时激励更有经验的读者为像 Physionet 这样的大规模在线数据集建立仪表板。

我们将建立的应用程序目前托管在这里。这个应用的源代码可以在这个 Github 仓库中找到,还有本文中显示的应用的后续版本的代码。

本帖组织如下:

  1. 动机:心电图是大数据
  2. 第一步:加载和打印数据的函数
  3. Dash 样板代码:运行我们的第一个应用程序
  4. 添加组件:图形
  5. 添加组件:下拉菜单
  6. 仪表板布局
  7. 回调函数:让应用程序组件相互交流
  8. 向全世界展示我们的应用程序!

动机:心电图是大数据

亚历山大·辛恩在 Unsplash 上拍摄的照片

心电图(ECG)是监测心脏健康的一种方便、无创的方法。随着可穿戴设备的出现,心电图可以一次持续几天,从而可以收集前所未有的大量数据。

具体有多少数据?典型的 ECG 记录频率约为 250Hz(即每秒 250 次测量)。如果患者连续 7 天佩戴监护仪,监护仪将产生 7246060250=151,200,000 个数据点!

Physionet 有一个公共数据库,其中有来自 11,000 名患者的多日心电图记录,由 Icentia 公司提供。现在我们大约有 10 个数据点。我认为公*地说,这使我们进入了“大数据”的领域。

直接绘制如此大量的数据是不切实际的。一个小时内绘制的心电图,更不用说一整周了,显示的是一整面墙的墨迹:

Icentia11k 生理网数据库中 0 号患者的第一小时心电图。单个图的心电图太多了!

这就是交互式可视化派上用场的地方。它们允许我们无缝地缩放和*移记录的不同部分。通过这种方式,我们可以在记录中的任何地方查看 ECG 行为,但以秒为单位的时间比以小时为单位的时间更合适。将它集成到仪表板中,我们可以添加其他功能,如下拉框,以便在不同的患者之间进行选择。此外,我们可以添加其他面板来查看除 ECG 之外的患者特征。如果这听起来很有趣,请继续阅读,因为我们在 Physionet 数据库中构建了一个(最小)仪表板来探索患者的心脏动力学。

第一步——编写加载和可视化数据的基本函数

在我们创建仪表板之前,我们应该写下函数来加载和显示我们希望在仪表板上看到的数据。如果你想跟随代码,在一个新的目录中建立一个虚拟环境(例如使用 venv )并安装 Python 包 numpy、pandas、plotly、dashwfdb 。这可以通过以下方式实现

现在创建一个名为app.py的 Python 脚本,并导入包,如下所示

Physionet 数据库包含两种形式的 ECG 数据:1)原始信号和 2)心跳注释。我们将在仪表板上显示这两种情况。

加载原始 ECG 信号。该数据库包含 11000 名患者,每个患者有大约 50 个心电图段。利用 Python 包 wfdb ,我们可以编写一个函数来加载特定患者、时间段和时间间隔的 ECG 数据:

例如,让我们在 2 分钟和 3 分钟之间加载患者 18 的心电图,分段 0:

这将使用一行程序从 Physionet 中检索 ECG 数据!我们现在需要一个函数来创建这些数据的交互式可视化:

fig = make_ecg_plot(df_ecg)执行这个函数现在产生一个对象,它将成为我们仪表板的 组件 。要查看该图,您可以使用fig.write_html('my_fig.html')保存并在网络浏览器中打开。您应该会看到类似这样的内容:

患者 18 从第 2 分钟到第 3 分钟的交互式心电图图形可视化。

正如我们所见,当我们想要查看较长时间间隔的心电图时,心电图会变得非常密集。只关注节拍注释会更方便:

加载节拍注释。这些数据包含记录中每次心跳的发生时间和类型。我们将把重点限制在正常的窦性搏动(标为“N”)和室性早搏(标为“V”)。和以前一样,我们编写一个函数来加载这些数据:

让我们像以前一样加载相同的心电图。这次接收出现在特定样本号的节拍类型:

我们将通过绘制连续心跳之间经过的时间来可视化这些数据:

现在运行make_beat_interval_plot(df_beats)返回下图,对应于之前显示的原始 ECG 图:

患者 18 从第 2 分钟到第 3 分钟的心搏间隔图。

请注意,该图包含的数据要少得多。因此,我们将使用这种类型的图来扫描长时间内的心电图,并使用之前显示的原始心电图图来研究较短时间内发生的心脏动力学。

现在我们有了提取和绘制数据的函数,是时候开始实际构建应用程序了!

破折号样板代码

我们将使用以下样板代码作为起点:

属性允许我们使用预定义的 CSS 配置。我们将使用 Chris Parmer 提供的 Dash Styleguide

如果您将这个 Python 脚本保存为app.py,并使用python app.py运行它,您应该会看到以下消息

Dash is running on http://127.0.0.1:8050/

现在,在您的网络浏览器中访问http://127 . 0 . 0 . 1:8050/,您应该会看到文本“我的仪表板”。如果是这样,您已经有了一个可以运行的仪表板!现在是时候开始构建它了。

添加组件:图形

我们将从把我们早先做的数字相加开始。现在,我们将为一个默认的患者和段号制作数字(最终我们将能够用回调函数和来改变它,我们将在后面定义)。当用户首次启动应用程序时,将显示该默认患者。

我们现在已经制作了图形对象,但是为了在 Dash 应用程序上显示图形,我们必须将它放在app.layout的内容分割器(Div)中。我们列出组件的顺序就是它们在应用程序中出现的顺序。我们还为每个图形分配了一个id,这在我们使用回调函数时会变得很重要。

到目前为止,将代码放在一起([app_v1.py](https://github.com/ThomasMBury/ecg-dashboard/blob/main/medium/app_v1.py))并运行它,应该会产生一个如下所示的应用程序:

应用程序版本 1:显示交互式图形。

添加组件:下拉菜单

Dash 中的下拉菜单有很多选项。需要指定的关键是id、默认的value,当然还有options

以下代码将创建一个下拉框,选项范围从 0 到 10999,可用于选择数据库中的患者编号。

请注意,我们将默认值设为患者 0,这与我们之前的默认值一致。

就像图一样,下拉菜单被认为是应用程序的组件,必须放在Div中。我们将使用额外的html组件为下拉菜单添加一个标题。完整的 Div 如下所示

我们可以创建一个类似的 Div,其中包含一个 ECG 段号的下拉菜单。在我们的app.layout中列出这些 div,给出了应用程序的更新版本([app_v2.py](https://github.com/ThomasMBury/ecg-dashboard/blob/main/medium/app_v2.py)),如下所示:

应用程序版本 2:包含下拉菜单(仍然没有连接到应用程序的其余部分)

仪表板布局

我们 app 目前的布局并不理想。下拉菜单不必要的宽,而且放置不当。幸运的是,我们可以使用style属性修改每个 Div 的布局属性。它接受任意数量的 CSS 属性/值对,其中有多个

对于这个应用程序,我们将调整 div 的高度、宽度和填充,以重新组织布局。它们可以被指定为绝对值,或基于用户屏幕尺寸的百分比。

让我们将标题和两个下拉菜单放在应用程序顶部的同一层。我们将标题 Div 的宽度设置为 200px,这足以容纳文本,下拉菜单的宽度设置为用户屏幕宽度的 20%。我们将设置 60px 的固定高度。选项'display’:’inline-block’将分隔线置于直线上。

我们的app.layout现在看起来是这样的:

对代码([app_v3.py](https://github.com/ThomasMBury/ecg-dashboard/blob/main/medium/app_v3.py))进行这样的调整会产生一个类似于下面这样的应用程序:

应用程序版本 3:下拉菜单和标题的新布局

现在我们已经对布局进行了排序,让我们开始连接应用程序的不同组件。目前下拉菜单与应用程序的其余部分是断开的。是时候介绍一下回调函数了。

回调函数:让应用程序组件相互交流

回调函数是仪表板最强大的功能之一。它们允许用户在与仪表板交互时获得仪表板的实时更新。

照片由 Quino AlUnsplash 上拍摄

编写回调函数时,需要仔细考虑函数的InputOutput应该是什么。例如,我们的下拉菜单中的一个值的变化应该反映在我们的图中。这里,Input是下拉菜单中的新值,Output是绘图。

回调函数列在app.layout之后。它们的基本结构如下:

@app.callback内部的部分被称为函数装饰器。它使用相关的idproperty将函数的输入和输出链接到应用程序的组件。

output_idinput_id是我们已经为应用程序的每个组件设置的 id。例如,患者下拉菜单的 ID 是'dropdown_patient_id'

input_propertyoutput_property是我们希望捕获的那个组件的属性。常见的例子包括用于选择下拉菜单值的'value'和用于选择绘图图形对象的'figure'。我们还将看到'relayoutData'专门捕捉绘图图形的当前布局。

关于回调函数的更多细节,请查看 Dash 教程。

对于我们的应用程序,我们将编写两个回调函数。第一个将连接下拉菜单节拍间隔绘图。第二个将把心搏间期图的布局连接到 ECG 图。

I)下拉菜单中变化的回调函数

当用户改变我们的下拉菜单中的一个值时,我们希望节拍间隔图相应地更新。这将包括从 Physionet 加载适当的数据,然后创建绘图。这就是回调函数要做的事情。

这个回调函数的输入和输出是什么?我们的输入是来自 ID 为'dropdown_record_id'或 ID 为'dropdown_segment_id'的下拉菜单的'value'。输出将是 ID 为'fig_intervals'的人物的'figure'属性。

因此,回调装饰器(函数前的位)是

现在来写函数——这基本上是实现我们已经写好的函数。给定一个患者 ID,我们希望从生理网加载适当的数据,然后绘制搏动间隔图:

现在运行应用程序([app_v4.py](https://github.com/ThomasMBury/ecg-dashboard/blob/main/medium/app_v4.py)),我们看到更新下拉菜单产生了预期的效果——我们现在可以查看数据库中 11,000 名患者中任何一名患者的搏动间隔图!

应用程序版本 4:下拉菜单现在连接到节拍间隔图

ii)绘图布局变化的回调函数

最后,我们将把心电图图放入图片中。我们希望当用户在“心跳间隔”图中选择足够小的时间窗口时,显示该图。因此我们将编写一个回调函数来检测节拍间隔图的 布局数据 的变化。

该布局数据从绘图图形的属性'relayoutData'获得。该回调函数还需要下拉菜单中的当前值,以便加载适当的 ECG 数据。因此,这个函数的装饰器是

当此功能被触发时,我们希望它加载并绘制所提供的 ECG 数据:

  1. 触发是由于fig_intervals的 x 轴边界发生变化。
  2. 新的界限导致时间窗口长度小于 1 分钟

如果触发是由于其他点击(如改变下拉菜单或其他布局选项),我们可以使用exceptions.PreventUpdate()来“取消”回调函数。这包含在下面的函数中(有一些细微差别,我就不细说了):

使用这个新的回调函数([app_v5.py](https://github.com/ThomasMBury/ecg-dashboard/blob/main/medium/app_v5.py))运行应用程序,我们现在能够查看短于 1 分钟的时间窗的 ECG 图:

应用程序版本 ECG 图现已连接到搏动间隔图的布局

通过这一回调功能,应用程序仅在实际观察有用时加载高分辨率 ECG 数据(短时间内),从而节省处理时间。

托管我们的应用

我们可以在本地运行我们的应用程序供个人使用,没有问题。然而,就其现状而言,与合作者共享并不容易,除非他们愿意建立自己的虚拟环境并通过 Python 运行。这些 Dash 应用程序的美妙之处在于,我们可以在托管它们,让任何有互联网连接的人都可以使用它们!

照片由 NASAUnsplash 拍摄

我们将与 Heroku 合作托管我们的应用,Heroku 是一个提供免费托管非商业应用的云*台。在这里,我将带你通过这些步骤,让这个应用程序托管遵循 Dash 提供的这些指令

更新 2023 年 1 月 2 日: Heroku 不再有自由层,所以我用 铁路 代替。您可以遵循下面的步骤 3–8,然后将存储库推送到 Github。从那里,我有一篇 文章 关于如何建立铁路。

  1. 注册 成为 Heroku 的帐户

2.安装Heroku CLI。这允许我们从命令行与 Heroku 交互。

3.为你的app.py文件创建一个干净的目录,并用

git init

4.创建一个虚拟环境并激活它。我们将使用 venv 模块创建一个名为 venv 的虚拟环境。

python -m venv venv
source venv/bin/activate

5.安装应用依赖项。最好先用以下代码更新 pip

pip install --upgrade pip

正如我们所见,我们的应用程序需要以下 Python 包:

pip install numpy
pip install pandas
pip install plotly
pip install dash
pip install wfdb

我们还需要 gunicorn 包,它使所有这些 Python 进程能够在 Heroku 服务器上运行。

pip install gunicorn

6.创建 requirements.txt 文件。我们刚刚安装的 Python 包和它们的依赖项需要在一个requirements.txt文件中列出。这可以通过以下命令实现

pip freeze > requirements.txt

7.创建一个. gitignore 文件。这表明我们不希望 git 跟踪哪些文件。我们可以创建并打开文件

touch .gitignore
open .gitignore

现在,为我们将不跟踪的文件输入以下行:

venv 
*.pyc 
.DS_Store 
.env

8.创建一个过程文件。这是部署所必需的。创建文件并使用打开它

touch Procfile
open Procfile

并输入以下文本

web: gunicorn app:server

您现在应该有一个包含app.pyrequirements.txt.gitignoreProcfilevenv的目录。如果是这样,你已经准备好发送这个应用程序到 Heroku!

安妮·斯普拉特在 Unsplash 上拍摄的照片

9.创建 Heroku app。使用 Heroku CLI 创建一个新应用程序,命令如下

heroku create ecg-dashboard-medium

其中ecg-dashboard-medium是您的应用程序的名称。如果你打开你的 Heroku 应用,你应该会看到你的应用被列出来了。

10.提交更改并将 git 存储库推送到 Heroku。这是通过以下方式实现的

git add .
git commit -m "Our first deployment to Heroku"
git push heroku main

在终端输出的末尾,您应该会看到

https://ecg-dashboard-medium.herokuapp.com/ deployed to Heroku

这是您的应用程序的 URL!如果你访问它,你应该看到你的应用程序启动和运行。

照片由阿莱西奥·索格蒂Unsplash 上拍摄

结论

恭喜你坚持到了最后!在这篇文章中,我们从头到尾看到了如何在 Plotly Dash 中构建和部署一个仪表板。这包括编写函数来加载和绘制我们的数据,将我们的图形作为应用程序的组件,设置布局,编写回调函数,最后,将我们的应用程序部署到公共服务器。我鼓励你现在就去构建你自己的应用——祝你好运!

承认

感谢 Icentia 允许本出版物使用 Icentia11k 数据集。除非另有说明,所有图片均为作者所有。

[1]谭,s .,奥尔蒂斯-加涅,s .,博登-加尼翁,n .,费克图,p .,库维尔,a .,本吉奥,y .,&科恩,J. P. (2022)。Icentia11k 单导联连续原始心电图数据集(1.0 版)。生理网

[2] Goldberger,a .,Amaral,l .,Glass,l .,Hausdorff,j .,Ivanov,P. C .,Mark,r .,…,和 Stanley,H. E. (2000 年)。生理银行、生理工具包和生理网:复杂生理信号新研究资源的组成部分。循环[在线]。101 (23),第 e215–e220 页。

使用 Streamlit 在 5 分钟内构建一个仪表板

原文:https://towardsdatascience.com/building-a-dashboard-in-under-5-minutes-with-streamlit-fd0c906ff886

用 Python 构建数据科学应用程序的分步指南

Streamlit 是一个开源框架,允许数据科学家在几分钟内将 python 脚本转化为可共享的 web 应用程序

最终产品—作者的 GIF

本文概述了如何使用 Streamlit 创建一个交互式仪表板的简单示例,如上所示。Streamlit 与 Plotly 图形库集成得非常好,因此使用它的经验是一个额外的奖励,并使向 Streamlit 的过渡非常无缝。

页眉和使用图像

在本节中,我们来看看插入 Streamlit 应用程序的标题和图像所涉及的代码。为此,我们需要使用特性 st.columns() ,它允许我们插入并排排列的容器。在这些列中,我们使用 st.image()st.title() 来插入所需的内容。插入不同样式的文本还有许多其他选项。

由下面的代码生成的仪表板—图片由作者提供

插入图并根据用户输入过滤数据

在 Streamlit 中插入交互式绘图的最简单方法是使用 Plotly ,这是 Python(和其他语言)中的数据可视化库。当在 Plotly 中创建了一个图时,使用ST . plot ly _ chart()在 Streamlit 中显示它是非常容易的。

在本例中,我们将使用 Plotly 内置的预期寿命与 GDP 数据集,因为它有许多变量可以很好地展示 Streamlit 功能。

作者提供的数据图像

示例 plottly Graph—作者提供的图片

根据这些数据,我们可以使用 Plotly 创建一个非常好的绘图,但是我们希望使用 Streamlit 来允许用户更改数据的显示方式。特别是:

  • 使用 st.slider() 允许用户选择要显示哪一年的数据

作者图片

  • 使用ST . select box()允许用户选择显示哪个洲

作者图片

  • 使用ST . checkbox()允许用户选择是否记录 x 轴

作者图片

过滤数据

为了过滤数据,让我们首先包括如上所示的滑块、选择框和复选框。同样,我们将使用 st.columns() 使元素并排出现,而不是垂直出现,这是 Streamlit 中的默认设置。

这里要注意的是,与前面看到的 st.image()st.title() 不同,这里的 st.slider()ST . select box()ST . checkbox()所有的返回值都是基于用户输入的内容,需要赋值给变量,如

因此,到目前为止的所有代码,我们的仪表板现在看起来像这样👇

作者图片

因此,下一步是加载数据,获取这些用户输入并生成一个图。

读入数据并应用过滤器

读入数据后,我们可以应用用户给我们的过滤器。

每次用户更新其中一个输入,应用程序都会刷新以反映更新。更多关于如何优化这个稍后!

产生和显示图

为了生成一个绘图,我们可以像在 Jupyter 笔记本中一样使用 Plotly,只是不再使用 fig.show() 来显示图像,而是使用ST . plot ly _ chart()来显示仪表板中的绘图。

最终产品

在大约 50 行代码中,我们有了一个全功能的仪表板🎉

作者 GIF

完整的代码可以在我的 GitHub 上查看这里👈

运行您的 Streamlit 应用

已经编写了构建一个可爱的仪表板的所有代码,现在让我们看看如何部署它。部署 Streamlit 应用程序最简单的方法是在您的本地机器上。

为此,只需打开您的命令行(终端等。),导航至保存您的的目录。py 文件并运行如下所示的命令(用您的文件名替换 my_file_name.py )

streamlit run my_file_name.py

您的应用程序现在应该在默认的 web 浏览器中打开,作为一个完全交互式的页面,您可以随意摆弄🤸‍♀️

一些额外的提示

  • 在代码的开头使用这个命令,我们可以让仪表板填满屏幕的整个宽度。有时,如果没有这一点,如果你在一个宽显示器上观看,你就不能充分利用空间。
st.set_page_config(layout="wide")
  • 在 Streamlit 中,您可能有多个功能,其中一些不受某些用户输入的影响。当然,如果输入没有改变,就必须重新运行一个函数,这并不是最佳选择! @st.cache ,如下所示,告诉 Streamlit 在重新运行那个特定的函数之前检查输入是否已经改变,这样可以节省你很多时间。文档可在这里获得。
@st.cache
def my_function(i1, i2, ...):
     x = ...
     return x

如果你从这些文章中获得了价值,并且还不是 medium 会员,考虑使用下面的链接注册 Medium!👇

https://medium.com/@riandolphin/membership

构建跟踪数据科学术语的仪表板

原文:https://towardsdatascience.com/building-a-dashboard-to-track-data-science-buzzwords-fcfdc21dc84a

一种数据驱动的方式来优先关注社交媒体

作者图片

仪表板应用程序*年来在公共场所和组织内越来越受欢迎。与书面报告相比,在面对高度复杂的主题时,仪表板为用户提供了开放式的、通常是更具吸引力的体验。它将受众的角色从被动的接受者转变为主动的探索者,从而促使他们做出明智的决策。

数据科学以其复杂性和快速的技术进步而闻名。对于数据爱好者来说,建立一个仪表板来帮助人们了解数据世界的最新趋势是一个很酷的想法。对于数据科学初学者来说,这样的仪表板也可以用于教育目的(例如,“从受欢迎的角度来看,我应该选择学习哪个深度学习框架/云*台?”).在这篇博文中,我将根据我通过 Twitter API 收集的数据集,浏览我制作的一个仪表板应用程序,以可视化 Twitter 上的数据科学术语。我跳过了数据收集部分,但是如果你感兴趣,你可以看看我之前关于学术推文分析的文章。

对于这个项目,我使用 Dash 和 Plotly。Dash 是一个用于快速构建数据应用的低代码框架。它通过抽象出所有底层技术和协议,使得构建具有交互式数据可视化的全栈 web 应用程序变得极其简单。Plotly 是一个绘图库,用于创建基于 web 的交互式可视化,通常在 Dash 应用程序中使用(尽管它也可以在 Jupyter notebook 中以独立的方式使用)。在反思创建应用程序的过程时,我发现将整个项目简化为三个相对独立的步骤是一个很有帮助的策略,每个步骤对应一个代码块。

  1. 定义 web 应用中的元素。在这一步中,我们确定了应用程序的目标,并定义了基本元素,具体来说:1)应用程序向用户呈现了什么信息(例如,图形、文本),以及 2)应用程序从用户那里收到了什么指示(例如,下拉菜单、单选按钮)。换句话说,我们关注应用程序的输入和输出。
  2. 定义网页的布局和风格。在第二步中,我们将元素组织成有意义且用户友好的部分。事实上,第 1 步和第 2 步都是应用程序的“前端”,可以压缩到一个巨大的代码块中,但将它们分开处理可以大大简化代码的结构,并使未来的调整更容易。
  3. 定义元素的行为。在这一步中,我们定义了 web 应用程序在用户触发事件时的动作。Dash 提供了回调函数机制,作为应用程序中可见元素(输入和输出)和后台计算(接收输入和返回输出的函数)之间的接口。

数据集。

该数据集由 144 万条包含关键词“数据科学”或“数据科学家”的英文推文组成。数据集中的记录已被匿名化;可能导致识别 twitter 用户身份的信息(例如,tweet_id、user_id、用户句柄、时间戳、地理数据、链接)已被删除。

代码实现。

以下是我在这个仪表板项目中使用的包:

作者图片

第一步。

我的目标是在仪表板中实现两个功能块。用户可以在第一个块中探索标签的流行程度,第一个块由一个条形图和一个下拉菜单组成,条形图显示最常用标签的数量,下拉菜单指定年份。

作者图片

第二块允许用户从三个方面探索感兴趣的特定标签: 1) 与感兴趣的特定标签同时出现的最频繁的标签; 2) 一段时间内带有该标签的推文数量; 3) 随着时间的推移,该标签的每条推文参与次数(即*均点赞数+转发数+回复数)。我还包含了一组单选按钮,允许用户搜索与给定 hashtag 同时出现的最常见的非 hashtag 单词。

作者图片

这些元素可以用破折号来定义。 dcc 和仪表板。 html 子模块:

作者图片

步骤二。

在这一步,我们将上面定义的元素捆绑到一个视觉上有意义的布局中。首先,让我们通过调用 dash 来初始化仪表板应用程序。破折号构造器。布局可以定义为一个树状的层次结构(更多信息,见官方教程)。为了控制垂直和水*排列,我使用了 dash_bootstrap_components 模块下的 Row()和 Col()类(参见文档这里的)。external_stylesheets 参数是可选的,它告诉 app 对象以现代的深色外观呈现网页。第二行也是可选的,设置图片的配色方案以与网页保持一致。

作者图片

顺便提一下,代码中的“dcc.loading()”提供了一个很酷的用户友好特性,允许您在应用程序执行一些计算时显示动画加载图标,而相关的可见元素尚未刷新。这在计算耗时(> 0.5 秒)时很有用,因为它可以向用户提供视觉反馈,表明正在进行的计算的状态。

第三步。

现在让我们来定义应用程序的动态行为。下面的屏幕截图显示了仪表板第一个功能块的代码:

作者图片

分解一下:

  • @app.callback 是一个包装器,将下面的函数修改(“包装”)成一个回调函数。回调函数监听输入(下拉菜单,由其 HTML id 指定)。当下拉值发生变化时,包装器中的函数(hashcount_update)将被自动调用,下拉菜单的新值将作为内部函数的输入参数传递。
  • 内部函数执行一些计算(创建 hashtag 计数的新数据帧),然后将两个输出传递回包装器。请注意,返回变量的顺序与@app.callback 装饰器中的输出顺序相匹配。
  • 看官方教程这里

在第二个块中,我想创建一个回调函数,它可以使用单选按钮和文本框中的信息,但只在单击“更新”按钮时执行。这里,我使用 State() 而不是 Input() 来传递来自文本框和单选按钮的值。当需要在搜索设置中进行多次更改时,这可以避免不必要的代码执行。

作者图片

运行应用

最后,要运行应用程序,只需在代码末尾包含“app.run_server()”:

作者图片

当执行整个脚本时,您会在控制台窗口中发现一些消息,其中应该有这样一行:

作者图片

这是此仪表板应用程序的本地主机地址。如果您将此地址复制并粘贴到浏览器,您将能够打开您的仪表板。就是这样!

我正在我的 github 上分享这个应用的代码。不幸的是,由于 Twitter 的再分配政策,我无法直接分享数据集,但幸运的是,使用 Twitter 的 API 很容易获得这些数据。如果您有其他意见/想法,请随时留言。

感谢阅读!

建立卓越的数据工程中心

原文:https://towardsdatascience.com/building-a-data-engineering-center-of-excellence-b83d51cedb6a

构建与当前规模相关的有效数据工程实践的基本组件。

作者图片

随着数据的重要性不断增长,变得越来越复杂,对熟练数据工程师的需求从未像现在这样大。但是什么是数据工程,为什么它如此重要?在这篇博文中,我们将讨论一个有效的数据工程实践的基本组成部分,为什么数据工程对今天的企业越来越重要,以及如何建立你自己的数据工程卓越中心!

多年来,我有幸建立、管理、领导和培养了一支规模可观的高绩效数据仓库和 ELT 工程师团队。在我的团队的帮助下,我每年都花大量时间有意识地计划和准备管理我们的数据逐月增长,并满足我们的 20000 多名全球数据消费者不断变化的报告和分析需求。我们构建了许多数据仓库来存储和集中从许多 OLTP 源生成的大量数据。我们通过在本地数据仓库和云中创建星型模式来实施 Kimball 方法。

目标是让我们的用户群能够对数据进行快速分析和报告;因此,我们的分析师社区和业务用户可以做出准确的数据驱动型决策。

我花了大约三年时间将数据仓库和 ETL 程序员的团队 ( 复数)转变成一个有凝聚力的数据工程团队。

我在这篇文章中总结了我在建立全球数据工程团队过程中的一些经验,希望数据专业人员和各种技术水*的领导者都能从中受益。

数据工程师的发展

现在是成为数据工程师的最佳时机。在过去的十年中,我们看到企业的觉醒,现在认识到他们的数据是公司的心跳,使数据工程的工作职能,确保准确,当前和高质量的数据流向依赖它的解决方案。

从历史上看,数据工程师的角色是从 数据仓库开发人员ETL/ELT 开发人员 (提取、转换和加载)演变而来的。

数据仓库开发人员负责设计、构建、开发、管理和维护数据仓库,以满足企业的报告需求。这主要是通过从运营和交易系统中提取数据,并使用提取转换加载方法(ETL/ ELT)将其传输到数据仓库或数据湖等存储层来实现的。数据仓库或数据湖是数据分析师、数据科学家和业务用户消费数据的地方。开发人员还执行转换,以使摄取的数据符合带有聚合数据的数据模型,从而便于分析。

数据工程师的主要职责是为多个用户生成并安全地提供数据。

数据工程师通过组织的每个部分监督数据的接收、转换、建模、交付和移动。数据提取发生在许多不同的数据源和应用程序中。数据工程师将数据加载到数据仓库和数据湖中,这些数据仓库和数据湖不仅是为了数据科学和预测分析计划(正如每个人都喜欢谈论的那样),而且主要是为了数据分析师。数据分析师和数据科学家执行运营报告、探索性分析、基于服务级别协议(SLA)的商业智能报告和针对迎合数据的仪表板。在本书中,我们将讨论所有这些工作职能。

数据工程师的角色是通过数据建模和可行的数据架构,从云和本地、新的和现有的系统中获取、存储和聚合数据。没有数据工程师,分析师和数据科学家就没有有价值的数据可以使用,因此,在每个新的数据团队成立时,数据工程师是最先被雇佣的。根据企业内可用的数据和分析工具,数据工程团队的角色配置文件、结构和方法有几个选项,可以选择他们的职责应该包括哪些内容,我们将在本章中讨论。

数据工程小组

软件越来越多地将数据工程师传统的手工和繁琐的任务自动化。数据处理工具和技术在过去几年里有了巨大的发展,并将继续增长。例如,基于云的数据仓库(比如雪花)让数据存储和处理变得既经济又快速。数据管道服务(如IICS 信息阿帕奇气流百万富翁五川)将数据提取转化为可以快速高效完成的工作。数据工程团队应该利用这些技术作为力量倍增器,采用一致和内聚的方法来集成和管理企业数据,而不仅仅是依靠遗留的孤立方法来构建具有脆弱、性能不佳、难以维护的代码的定制数据管道。继续采用后一种方法将会抑制上述企业的创新步伐,并迫使未来的重点放在管理数据基础架构问题上,而不是如何帮助您的企业创造价值。

企业数据工程团队的主要角色应该是 将原始数据 转换成可供分析的形状,为现实世界的分析和数据科学应用奠定基础。

数据工程团队应充当企业级数据的 管理员 ,负责管理组织的数据,并为那些想要利用数据的人充当资源,例如报告&分析团队、数据科学团队和其他利用企业数据*台进行更多自助服务或业务组驱动分析的团队。这个团队应该充当组织知识的 管家 ,管理和细化目录,以便更有效地进行分析。让我们看看一个运行良好的数据工程团队的基本职责。

数据工程团队的职责

数据工程团队应在企业内提供一个共享功能,该功能可跨越以支持报告/分析和数据科学功能,从而提供对干净、转换、格式化、可扩展和安全的数据的访问,以供分析。数据工程团队的核心职责应该包括:

构建、管理和优化核心数据*台基础设施

从各种结构化和非结构化来源构建和维护定制和现成的数据集成和接收管道

管理整体数据管道编排

通过技术流程和业务逻辑,在加载原始数据之前或之后管理数据转换

支持分析团队优化数据仓库的设计和性能

数据是企业的资产。

数据作为一种资产应该被共享和保护。

数据应被视为企业资产,在所有业务部门中加以利用,通过加快决策制定,并借助数据提高竞争优势,来提升公司对各自客户群的价值。良好的数据管理、法律和法规要求要求我们保护所拥有的数据免遭未经授权的访问和泄露。

换句话说,管理安全是一项至关重要的职责。

为什么要创建一个集中的数据工程团队?

将数据工程视为支撑分析和数据科学能力的标准和核心能力,将有助于企业发展如何处理数据和分析。企业需要停止像我们经常看到的那样,基于所涉及的技术堆栈来纵向处理数据,而转向更多的管理 数据结构网格层 的横向方法,该网格层跨越整个组织,可以根据需要连接到各种技术,以推动分析计划。这是一种新的思维和工作方式,但随着各种数据组织寻求规模化,它可以提高效率。此外,为数据工程资源创建专门的结构和职业发展道路也有价值。数据工程技能集在市场上需求量很大;因此,在公司之外招聘可能成本很高。公司必须使程序员、数据库管理员和软件开发人员在职业道路上能够通过跨技术工作来获得上述技能组合所需的经验。通常,建立一个卓越的数据工程中心或能力中心是实现这种进步的第一步。

创建集中式数据工程团队的挑战

数据工程团队的集中化即服务方法不同于报告和分析以及数据科学团队的运营方式。原则上,这确实意味着 放弃对资源的某种程度的控制 并为这些团队如何合作和一起交付计划建立新的流程。

数据工程团队将需要证明它能够有效地支持报告和分析以及数据科学团队的需求,无论这些团队有多大。数据工程团队必须 有效地对工作负载 进行优先级排序,同时确保他们能够为分配的项目带来正确的技能和经验。

数据工程至关重要,因为它是数据驱动型公司的支柱。它使分析师能够处理干净且组织有序的数据,这是获得洞察力和做出合理决策所必需的。要构建一个有效的数据工程实践,您需要以下关键组件:

数据工程卓越中心

数据工程团队应该是企业内部的核心能力,但它应该有效地充当几乎所有与数据相关的事情的支持功能。它应该以协作支持的角色与报告、分析和数据科学团队互动,以使整个团队取得成功。

数据工程团队不创造直接的商业价值 —但价值应该来自于使报告和分析以及数据科学团队更有生产力和效率,以确保通过数据分析计划向业务利益相关方交付最大价值&。为实现这一目标,数据工程能力中心的六项主要职责如下-

卓越数据工程中心-作者图片。

让我们回顾一下 6 个职责支柱

1。确定整理和争论的中心数据位置

了解并拥有一个数据湖战略。 ( 用于大量消费数据进行分析的集中式数据储存库或数据仓库)。定义必要的数据表以及它们在数据工程环境中的连接位置,随后将原始数据转换为可消化且有价值的格式。

2。数据摄取和转换

将数据从一个或多个源移动到一个新的目的地(您的数据湖或云数据仓库)在那里可以存储和进一步分析数据,然后将数据从源系统的格式转换为目的地的格式

3。ETL/ELT 操作

从一个或多个源中提取、转换数据并将其加载到目标系统中,以新的上下文或样式表示数据。

4。数据建模

数据建模是数据工程团队的一项基本功能,当然并不是所有的数据工程师都擅长这项功能。通过理解信息系统工作流、建模所需的查询、设计表格、确定主键以及有效地利用数据来创建有根据的输出,将数据对象和业务规则之间的关系形式化为概念表示。

我见过面试中的工程师在这方面比在技术讨论中编码搞得更乱。理解维度、事实和聚合表之间的区别是很重要的。

5。安全和访问

确保敏感数据得到保护,并实施适当的身份验证和授权,以降低数据泄露的风险

6。架构和管理

定义模型、策略和标准,管理收集的数据、存储的位置和方式,以及如何将这些数据集成到各种分析系统中。

数据工程能力的六大职责重点是确定用于整理和辩论的中央数据位置、接收和转换数据、执行 ETL/ELT 操作、数据建模、安全访问和管理架构。虽然所有公司在这些职能方面都有自己的特定需求,但确保您的团队拥有必要的技能组合以奠定大数据成功的基础非常重要。

除了数据工程之外,以下是企业中需要考虑的其他能力中心:

分析能力中心

分析能力中心在整个公司范围内实现一致、有效和高效的 BI、分析和高级分析能力。通过报告、分析和仪表板解决方案,帮助业务职能部门进行分类、确定优先级并实现其目标,同时提供运营报告和可视化、自助服务分析以及自动生成此类见解所需的工具。

数据科学能力中心

数据科学能力中心旨在探索前沿技术和概念,以释放新的见解和机遇,更好地通知员工,并使用自动化人工智能和自动化人工智能解决方案(如 H2O.aiDataikuAible 、DataRobot、 C3.ai )创建规范的信息使用文化

数据治理

数据治理办公室为用户提供可信、可理解和及时的数据以提高效率,同时将数据的完整性和神圣性保留在合适的人手里以供大众消费。

随着公司的发展,您会希望确保数据工程能力能够支持六大职责。通过这样做,您将能够确保涵盖数据管理和分析的所有方面,并且您的数据是安全的,可以被需要它的人访问。你开始思考你的公司将如何发展了吗?您采取了哪些步骤来组建集中的数据工程团队?

感谢您的阅读!

https://twitter.com/richiebachala

在数据块上构建数据网格—快速

原文:https://towardsdatascience.com/building-a-data-mesh-on-databricks-fast-25bf9a9bf0b2

如何在 Databricks 内部构建数据网格*台

(照片由 皮埃特罗 )

Databricks 使团队能够以极快的速度和较低的维护需求构建数据网格*台。

但是构建一个基于数据块的数据网格*台也有意想不到的缺点。

在本文中,我将使用一个假想的团队结构概述一个可能的设置,并帮助您理解 databricks 上的数据网格是如何工作的。

本文节选自我在 MEAP 曼宁合著的《数据网在行动》 一书的草稿。

为什么将 Databricks 作为数据网格*台?

与 AWS 和 GCP 版本的数据网格不同,Databricks 上的数据网格看起来很像一个集中式数据设置。至少在技术方面。这带来了明显的好处,即易于设置和典型的集中化好处,以及两个不太明显的缺点:

  1. 通过集中大部分基础设施,我们又一次受制于中心瓶颈。使用托管共享服务时,这种影响要小得多,但它仍然存在。此外,我们在物理上将数据集中在一个地方。
  2. 通过集中部分流程,我们让“data domain 所有权”变得更加难以理解。在这样的技术架构中,将焦点放在过程、人员和组织上是最重要的。

另一方面,这种数据网格可能不需要一个以上的数据工程师来维护中小型公司内部的技术基础设施。在域内部,您应该能够用每个域一名数据科学家/工程师来满足需求。

8.3.1 自助式数据*台架构

我们的数据网格有几个参与者。

  • 团队 & 都是开发团队,团队内部有一名数据科学家。
  • 团队灰色只是一个独自维护*台的单人团队。
  • C 消费者:我们有一些商业分析师和一个推荐系统,它们都主要消费数据。

我们的业务分析师精通 SQL,可以在笔记本中使用 Python 做一些工作。

下图描绘了我们这次的架构。注意上下文变得更加模糊了。你当然可以“分离出”黑白团队的上下文,但是在这种情况下我们没有这样做。

小团队如何利用 Databricks 快速创建自助式数据*台——作者。

让我们再次确定数据网格的不同组件,首先是*台的组件,然后是数据产品部分。

确定*台的组件

在我们的架构中,*台接口和内核主要由数据块和相关服务组成。一个人的团队可以开始为 Kinesis 和 Fargate 组件的供应提供模板或某种服务,但这可能会与拥有一个非常精简的*台团队的想法相冲突。

*台团队需要做的是提供指南,一页纸的关于如何在数据网格中使用数据块的指南。如何使用访问权限,在哪里存储所有数据产品等等。

确定数据产品的组成部分

所有的数据产品都是建立在数据基础之上的。下面详细描述一下。

Black 团队拥有的数据产品更复杂一些。上游数据来自运行中的 RDS 实例和一些其他系统。为此,该团队使用定制的 AWS Fargate 任务定期将数据加载到 S3 桶中。从那里开始,Databricks spark 批处理作业拾取数据,并将它们放入 Delta Lake。然后,使用 Databricks 笔记本将数据转换为数据产品,并将其再次放入三角洲湖形式,在那里向其他人公开以供使用。在这种情况下,三角洲湖数据集形成了数据输出端口

白队拥有的数据产品使用两个上游数据集。用户定期将数据输入到 AWS S3 存储桶中,该存储桶由批处理作业拾取并交付到三角洲湖中进行临时存储。这形成了数据产品的推送界面。另一方面,该团队提供了一个定制的 AWS Kinesis 数据流。操作组件将数据实时推送到数据流中,然后由 Databricks 流作业拾取。同样,笔记本用于转换和产生数据输出。

【white 团队如何利用 Databricks 来获取流数据和基于推送的数据,以构建一种新的数据产品——image by the author。

white 团队的第二个数据产品展示了使用 Databricks 构建额外的派生数据产品是多么简单。White 团队只需拿起另一个笔记本,找到已经提供的两个数据产品,并将其转换为 Databricks 中的第三个新数据产品。

业务分析师可以轻松地使用 Databricks SQL 来访问和浏览数据产品。他们还可以在该框架内创建报告和仪表板。

评论

数据块上的工作流是特定于数据块的。团队可以使用基础设施来管理数据产品的访问权限,分析师可以使用所谓的 unity 数据目录来浏览数据。

如果我们采用这样的体系结构,数据网格原理会有所不同。以数据块为中心的架构限制了团队的自由,并且没有简单的选择来“退出”*台。因此,这种设置只有在您解决这些限制或者通过治理措施确保团队在工具选择上保持一致的情况下才能很好地工作。因此,这种体系结构确实会带来退回到非数据网状操作模式的风险。

这使得这种架构更适合年轻的公司。这个架构也更针对数据工程师和数据科学家。这意味着它的目标是内部数据人员比例较高的公司,而 GCP 和 AWS 选项也适用于软件工程师。

变化

Databricks 本身可以被其他更大的数据*台取代,如雪花生态系统,它已经提供了几乎所有需要从上面实施架构的组件。

要扩展这种数据网格架构,您需要为每个参与者启用“退出”选项。这可以通过添加一个外部数据目录并减少转换工具中的数据块来实现,转换工具仍然是一个强大的工具集。您的设置可能如下所示:

  1. 团队将数据存储在他们自己的数据存储中
  2. 摄取工具获取这些数据,并将其放入 Databricks 可访问的数据存储中。
  3. Databricks 用于对这些数据集进行转换,并生成数据产品。
  4. trinoDB 这样的查询引擎用于将不同的数据产品连接在一起。
  5. 中央数据目录确保数据产品可以存储在任何地方,并且仍然可以访问和查找。

Databricks 架构摘要

本节描述的架构非常注重最大限度地利用 Databricks 的数据转换能力。因此,它与 Databricks *台紧密相连。我们还看到,我们需要一些帮助来将数据放入中央三角洲湖,以释放数据块的力量。

这种设置的一大优势是最少的维护工作,并为公司通常希望承担的所有分析工作负载提供了强大的支持。

另一方面,我们看到了对开发团队的有限支持和对 Databricks *台的锁定效应。

看看下面的概要简介。

作者对 databricks 数据网格*台设置的总结。

对如何建立伟大的数据公司、伟大的数据密集型产品、成为伟大的数据团队感兴趣,还是对如何利用开源构建任何伟大的东西感兴趣?然后考虑加入我的 免费简讯《三个数据点周四》 。它已经成为数据初创企业、风投和数据领导者值得信赖的资源。

在卡夫卡生态系统上构建数据网格

原文:https://towardsdatascience.com/building-a-data-mesh-on-the-kafka-ecosystem-399a5bd8799c

面向数据密集型产品公司的数据网格。

(这就是我对卡夫卡的数据网格的想象。美国宇航局在 Unsplash 拍摄的照片

F 为数据网格找到一个好的技术架构是很困难的,并且是一个非常公司化的过程。基于 Kafka 的数据网格对于已经在构建数据密集型产品的公司来说是一个很好的选择。

如果您的软件工程师不愿意处理大量数据,并且无法进入 Kafka 生态系统,那么这种数据网格技术架构不适合您。

但是如果你有先决条件,那么这个架构对你的公司来说是非常强大的。

我们描述的设置支持在整个公司范围内快速构建数据密集型应用程序。它将支持基于 SQL 的转换工具和遗留数据源的集成,同时仍然提供标准化的输入和输出端口。

本文节选自《 数据网格在行动 》由曼宁所著,第一本关于数据网格范式的实施的书,由 亚采克斯文玛丽安 马里乌斯

自助式数据*台架构

为了解释数据网格*台如何在 Kafka 上工作,我选择使用一家公司内部的三个虚拟团队。

  • 两个软件工程团队黑与白,
  • 和一个*台团队,负责操作一些附加组件,这些组件集合了基于 Kafka 的数据网格。

下面描述了设置。花些时间来看看数据是如何在不同的数据产品之间流动的。请注意,这个数字有所减少。这里的 Kafka 集群是一个物理中心的基础设施,在称为“流&主题”的层次上有逻辑分离,这是 Kafka 内部的组织单元。

该架构使用两种关键的 Kafka 技术:

  1. 使用以 Kafka 主题为载体的事件流作为数据输出端口,或者使用 Kafka Connect“sinks”将主题直接映射到云存储中提供的输出格式,如雪花表或 CSV 文件。
  2. 默认使用 ksqlDB 作为数据产品中基于 SQL 语法的转换工具。此外,ksqlDB 被用作从 Kafka 主题携带的事件流数据输出端口获取数据的工具。

两个团队如何在技术上实现利用卡夫卡的数据产品,作者图片。

您应该能够快速找出该架构中的几个重要亮点:

  1. 卡夫卡生态系统内部发生了很多事情。
  2. 数据输入使用 Kafka 源进行标准化,这在大多数其他设置中是不存在的。
  3. 数据输出端口使用 Kafka 接收器和 Kafka 主题携带的事件流进行标准化。
  4. Ksql 用于生态系统内部的转换。
  5. 其他功能,如利用融合模式注册中心的中央数据目录、trino 和一些 BI 工具,是业务分析师工作所必需的。这在其他设置中更简单。

现在让我们来看看*台本身,然后详细了解数据产品。

识别组件

*台的界面将主要由文档组成,其余的界面是 Kafka 特有的界面。该团队可能会创建一个自述文件,以便轻松导航特定于公司的 Kafka 设置。

Black team 的数据产品从上游基于 SQL 的数据库获取数据。它使用 Kafka Connect 内置的 JDBC 源连接器将数据传输到 Kafka 主题中。KsqlDB 用于转换数据,并将数据集写入新的 Kafka 流和 AWS S3 桶。对于后者,再次使用 Kafka Connect AWS S3 连接器。Kafka 流可通过 Kafka 流 API 或 ksqlDB 访问,如下所示。因此,Kafka streams 端口与 AWS S3 存储桶一起成为数据输出端口

【black 团队如何在 Kafka 上实施他们的数据产品,图片由作者提供。

Team White 的数据产品从 AWS S3 桶中获取数据,这种获取是通过定制的数字处理应用程序完成的。该应用程序使用 Kafka Producer API 将数据集写入 Kafka 流。Kafka Connect sink 被配置为也将这些数据转储到雪花表中。这两个组件,雪花表以及 Kafka 流端口成为数据输出端口

白队的第二个数据产品的架构非常简单。它使用 ksqlDB 从 White 的第一个数据产品使用 ksqlDB 连接到现有的事件流,使用 SQL 转换数据,然后再次将其写入充当数据输出端口的事件流。

【white 团队如何利用 ksqlDB(图片由作者提供)获取另一个团队的数据以制作第二个数据产品。

在这种情况下,推荐引擎可以只获取 Kafka 流,但在我们的情况下,它更容易获取批量数据,这些数据通过标准 Kafka 接收器放入 AWS S3 存储桶。

您可能会看到,业务分析师在我们的案例中有点特殊。他们没有消费数据产品的直接方式,在 Kafka 生态系统中没有。为此,我们利用 trinoDB 作为查询引擎来查询所有数据产品,并允许在原生 SQL 中连接它们。在 trinoDB 之上,我们可以放置一个标准的商业智能工具,如 Tableau、Looker 或 Metabase,也可以以仪表板等形式提供数据。

和上一节一样,这个架构是一个比本章中解释的前两个更具体的工具。因此,我们不解释工作流和数据网格原理,而是讨论为什么这个架构更像是一个特殊用途的工具。

更多想法

如果您的公司专注于构建数据密集型应用程序,尤其是实时应用程序,这种架构将非常强大,因为 Kafka 提供的原生集成使得构建和组合这些应用程序非常容易。

这种架构是为开发团队大量使用而设计的。为了容纳数据工程师和数据科学家,我们需要添加一些额外的东西,比如 trinoDB。我们也没有像 databricks 或 AWS 原生设置那样,在类似笔记本的环境中进行任何原生集成。

我们可以做到这一切,只是需要额外的关注。

另一个考虑因素是模式注册中心和数据目录。Kafka 需要自己的模式注册表才能充分发挥作用。然后,需要将模式注册中心集成到外部数据目录中。所有其他体系结构也是如此,但是在所有其他体系结构中,数据目录也可以充当模式注册表,在这种情况下,我们确实需要 Kafka 特定的注册表。

其他架构中使用的大多数变体在这里也是可能的。特别是,安装更多数据工程密集型组件是最有意义的。

卡夫卡建筑概述

这种体系结构侧重于构建数据密集型应用程序,这些应用程序本质上不一定需要是“分析型”的。

它的目标是那些希望从产品几乎每个部分的数据中提取价值,从而构建大部分数据密集型应用程序的公司。因此,主要的焦点变成了开发团队,让他们的生活变得更加轻松。这也意味着数据工程师的生活更加艰难,或者至少需要额外的关注。

这些都是很难但不得不做的权衡。下表给出了 Kafka 数据网格的概要。

表作者。

对如何建立伟大的数据公司、伟大的数据密集型产品、成为伟大的数据团队感兴趣,还是对如何利用开源构建任何伟大的东西感兴趣?然后考虑加入我的 免费简讯《三个数据点周四》 。它已经成为数据初创企业、风投和数据领导者值得信赖的资源。

为我自己的 Strava 数据构建数据管道

原文:https://towardsdatascience.com/building-a-data-pipeline-for-my-strava-data-98ea8b2b0767

使用 Strava API、MySQL、Python、S3、Redshift 和 Airflow 对我自己的 Strava 数据进行 EtLT

马库斯·温克勒在 Unsplash 上的照片

我构建了一个 EtLT 管道,从 Strava API 获取我的 Strava 数据,并将其加载到一个 红移 数据仓库中。然后,使用 气流 每周运行一次该管道,以提取任何新的活动数据。最终目标是使用这个数据仓库在 Tableau 中构建一个自动更新的仪表板,并触发我的 Strava Kudos 预测模型的自动重新训练。

系统架构(图片由作者提供)

数据提取

我的个人 Strava 活动数据首先使用 Strava API 被增量摄取并被加载到 S3 桶中。在每次摄取运行时,我们查询 MySQL 数据库以获得最后一次提取的日期:

然后,我们使用requests库重复调用 REST API,直到获得从现在到last_updated_warehosue之间的所有活动数据。我们包含了一个time.sleep()命令来遵守 Strava 设置的 100 个请求/15 分钟的速率限制。我们还包含了try: except:块,以防止
丢失某些活动的数据。

在将数据本地导出到由竖线分隔的.csv文件之前,我们执行一些小的转换,比如格式化日期和时区列。因此 EtLT 中的小 t!在我们保存数据之后,它将被上传到一个 S3 存储桶中,以便以后加载到数据仓库中。

最后,我们执行一个查询,在最后的提取日期更新 MySQL 数据库。

数据加载

一旦数据被加载到 S3 数据湖,它就会被加载到我们的红移数据仓库。我们分两部分加载数据:

  • 我们首先将数据从 S3 存储桶装载到一个临时表中,该表的模式与我们的生产表相同
  • 然后,我们在临时表和生产表之间执行验证测试(参见数据验证部分)。如果所有关键测试都通过了,那么我们首先从生产表中删除两个表之间的所有重复项。然后,临时表中的数据被完全插入到生产表中。

数据验证

我们用 python 实现了一个简单的框架,用于在我们的数据管道中执行基于 SQL 的数据验证检查。尽管它缺少我们期望在生产环境中看到的许多功能,但这是一个良好的开端,并为我们如何改进基础设施提供了一些见解。

validator.py脚本对红移执行一对 SQL 脚本,并基于比较操作符(>、<、=)比较这两个脚本。然后,根据两个执行脚本的结果,测试通过或失败。在将新获取的数据上传到 staging 表之后,但在将该表插入到 production 表之前,我们执行这个验证步骤。

首先,我实现了检查重复项的检查,将 staging 表中总活动的分布(Airflow 设置为在每周结束时执行)与*均历史周活动计数进行比较,并使用 z 分数将 Kudos 计数指标的分布与历史分布进行比较。换句话说,最后两个查询检查这些值是否在基于历史的预期值的 90%置信区间内。例如,以下查询计算给定周内上载的所有活动的 z 得分(在 staging 表中找到)。

通过跑步

python src/validator.py sql/validation/weekly_activity_count_zscore.sql sql/validation/zscore_90_twosided.sql greater_equals warn

在终端中,我们将在之前的查询中找到的 z 分数与 90%置信区间 z 分数SELECT 1.645;进行比较。命令末尾的“warn”告诉脚本不要出错退出,而是警告我们。另一方面,如果我们在结尾加上' halt ',脚本将会退出并显示一个错误代码,并暂停所有后续任务。

我们还实现了一个系统,通过验证测试结果向给定的松弛通道发送通知,这个验证系统是受 James Densmore 的优秀数据管道一书的管道中的数据验证一章的启发。

然后,我们将所有的测试组合到一个 shell 脚本validate_load_data.sh中,在将数据从 S3 存储桶加载到暂存表之后,在将数据插入到生产表之前,运行这个脚本。对上周的数据运行此管道会得到以下输出:

松弛消息(图片由作者提供)

很高兴看到我们的第二次测试失败了,因为上周我没有像往常一样跑那么多!

虽然这个验证框架非常基础,但是它是一个很好的基础,可以在以后的日子里进行构建。

数据转换

现在数据已经被接收到数据仓库中,管道中的下一步是数据转换。在这种情况下,数据转换既包括数据的非上下文操作,也包括考虑上下文和逻辑的数据建模。在这种情况下,使用 ELT 方法而不是 ETL 框架的好处是,它给我们,即最终用户,以我们需要的方式转换数据的自由,而不是拥有一个我们不能改变的固定数据模型(或者至少不能没有麻烦地改变)。在我的例子中,我将我的红移数据仓库连接到 Tableau,构建一个仪表板。例如,我们可以构建一个数据模型来提取每月统计数据:

我们还可以建立更复杂的数据模型。例如,我们可以获得按锻炼类型细分的每周总 kudos 的每周百分比变化:

更进一步的方向是利用第三方工具,如 dbt 来实现数据建模。

用气流把它们组合在一起

我们创建一个 DAG 来协调我们的数据管道。我们将管道设置为每周运行,这意味着它将在每周周日上午的午夜运行一次。如下图所示,我们的 DAG 将:

  • 首先,使用 Strava API 提取最*的数据,并将其上传到 S3 存储桶
  • 然后,它会将这些数据加载到我们的红移集群中的临时表中
  • 然后将执行 3 个验证测试,并将结果通知我们的 Slack channel
  • 然后,临时表将被插入到生产表中,删除流程中的任何重复项
  • 最后,将在一个新表activity_summary_monthly中创建一个月度聚合数据模型

DAG 图(图片由作者提供)

数据可视化

完成数据转换后,我们就能够使用 Tableau 构建一个交互式仪表板,当新数据被接收到数据仓库时,它会自动更新,每周更新一次。我创建的仪表板是为了调查我的 Strava 活动的声望如何随着时间和地点的变化而变化。在建立这个项目后,我关闭了红移服务器,以避免任何费用,但下面可以看到仪表板的截图。

Kudos 仪表板(图片由作者提供)

互动地图(图片由作者提供)

单元测试

单元测试是使用 PyTest 执行的,所有测试都可以在 tests 目录中找到。例如,下面我们看到一个测试make_strava_api_request函数的单元测试。它断言收到了一个字典响应,并且该响应包含一个整数的“id”键。

进一步的方向和考虑

  • 用 docker 改善气流:我本可以使用气流的 Docker 图像来运行 Docker 容器中的管道,这会使事情更加健壮。这也将使大规模部署管道更加容易!
  • 实现更多的验证测试:对于一个真实的生产管道,我会在整个管道中实现更多的验证测试。例如,我可以使用像远大前程这样的开源工具。
  • 简化流程:管道可能会以更简单的方式运行。另一种方法是使用 Cron 进行编排,使用 PostgreSQL 或 SQLite 进行存储。
  • 数据流:为了保持仪表盘持续更新,我们可以从类似 Kafka 的东西中受益。

结束语

最后,我构建了一个数据管道来自动提取、加载和转换我的 Strava 数据。该管道每周运行一次,并自动更新交互式仪表板。

我希望你喜欢它!

构建数据*台:投资数据质量的 7 个标志

原文:https://towardsdatascience.com/building-a-data-platform-7-signs-its-time-to-invest-in-data-quality-c0822a9c2177

以及何时开始有意义。

图片由 Unsplash 上的弗兰基·洛佩兹提供。

我从客户那里得到的最常见的问题之一是:“什么时候投资于数据质量和可观察性是有意义的?”

我的回答是,“看情况。

事实是,构建数据*台是一个多阶段的旅程,数据团队必须兼顾几十个相互竞争的优先事项。对于一个只有几个连接到内部数据库的仪表板的公司来说,数据可观察性可能没有意义。

另一方面,与我交谈过的许多组织都增加了开发其 数据*台 的投资,却没有看到数据采用率和信任度的相应提高。如果你的公司不使用或不信任你的数据,那么你统治数据*台的最好计划都是白日梦。

为了回答这个问题,我列出了七个主要指标,表明是时候为您的数据*台投资数据质量了。

7 表示您有“好的管道,但坏的数据”

根据多年来与数百名客户的交谈,我发现了七个迹象,表明您的数据团队应该优先考虑数据质量。

您最*迁移到了云

无论您的组织是在迁移到 数据湖 还是在云*台之间(例如亚马逊红移到雪花),维护数据质量都应该是您的数据团队的首要任务。

毕竟,您可能出于以下三个原因之一进行迁移:

  • 你现在的数据*台已经过时,结果就是数据可靠性低,没人信任数据。
  • 如果没有大量的手动干预,您当前的*台无法随您的业务一起扩展,也无法支持复杂的数据需求。
  • 您当前的*台运营成本很高,而新*台在维护得当的情况下会更便宜。

无论您为何迁移,在保持速度的同时,向您的数据*台灌输信任是至关重要的。

您应该花更多的时间构建您的数据管道,花更少的时间编写测试来防止问题发生。

对于 AutoTrader UK 来说,投资于数据可观测性是其初始云数据库迁移的关键组成部分。

AutoTrader UK 的首席开发人员 Edward Kent 表示:“随着我们将可信的内部系统迁移到云,这些旧系统的用户需要相信新的基于云的技术与他们过去使用的旧系统一样可靠。

您的数据堆栈随着更多数据源、更多表和更多复杂性而扩展

数据产品的规模不是投资数据质量的唯一标准,但却是一个重要的标准。像任何机器一样,移动部件越多,就越容易损坏,除非对可靠性工程给予适当的关注。

虽然在投资可观察性之前,没有硬性规定您的组织应该有多少数据源、管道或表,但是一个好的经验法则是超过 50 个表。也就是说,如果您有较少的表,但是您的组织的数据停机的严重性很大,那么数据可观测性仍然是一项非常明智的投资。

另一个重要的考虑因素是数据堆栈的增长速度。例如,广告*台 Choozle 知道投资于数据可观察性,因为预计他们的新*台升级会导致桌子蔓延。

“当我们的广告客户连接到谷歌、必应、脸书或其他外部*台时,Fivetran 会进入数据仓库,并完全自动地将其放入报告堆栈。我不知道广告商什么时候创造了一个连接器,”Choozle 首席技术官 Adam Woods 说。

“这导致了桌子的蔓延、扩散和碎片化。我们需要数据监控和警报,以确保所有这些表格都是同步和最新的,否则我们会开始收到客户的来信。”

您的数据团队正在壮大

好消息是您的组织重视数据,这意味着您正在雇用更多的数据人员,并对您的数据堆栈采用现代工具。然而,这通常会导致数据团队结构的变化(从集中到分散),新流程的采用,以及数据团队少数早期成员对数据集的了解。

如果您的数据团队正在经历这些问题中的任何一个,这是一个投资于主动方法以保持数据质量的好时机。否则,随着时间的推移,技术债务将慢慢堆积,您的数据团队将投入大量时间来清理数据问题。

例如,我们的一位客户被我们称为“您正在使用那张桌子?!"问题。随着数据*台的扩展,数据分析师越来越难以辨别哪些表是正在被有效管理的,哪些表是过时的。

数据认证 程序和端到端数据沿袭可以帮助解决这些问题。

值得注意的是,虽然不断增长的数据团队是投资数据质量的标志,但即使是一个人的数据团队也可以从更自动化的方法中受益匪浅。例如, Clearcover 的单人数据工程团队能够消除瓶颈,并通过投资自动化为业务增加更多价值。

您的团队至少花费 30%的时间来解决数据质量问题

当我们开始蒙特卡洛时,我采访了 50 多名数据主管,以了解他们在管理数据系统方面的痛点。超过 60%的人表示他们正处于数据可靠性之旅的早期阶段,他们的团队花了半天时间来解决数据质量问题。

例如,Blinkist 的工程总监 Gopi Krishnamurthy估计在实施数据可观测性之前,他的团队将 50%的工作时间用于消防数据演练。

多个行业研究已经证实:数据工程师花费了太多的(宝贵的!)时间固定而不是创新。在我的研究过程中,我还发现数据工程团队花费 30–50%的时间来处理破裂的管道、错误的模型和陈旧的仪表板。

即使是已经开发了自己的数据质量*台的组织,也发现他们的团队在构建和升级*台上花费了太多的时间,并且差距仍然存在。

与一年前相比,您的团队拥有更多的数据消费者

你的组织成长速度很快,很牛逼。数据推动着您的招聘决策、产品特性和预测分析。

但是,在大多数情况下,快速增长会导致依赖数据的业务利益相关者的增加、更多样化的数据需求以及最终更多的数据。此外,随着大量数据进入您的数据生态系统的可能性增加,随之而来的是更多的责任。

本叔给彼得·帕克的建议可以应用到你的数据团队中。图片由蒙特卡洛提供。

具有讽刺意味的是:最受数据驱动的组织会有更多的数据消费者来发现任何出现的错误。例如,在 AutoTrader UK,超过 50%的员工每月都会登录并使用 Looker 中的数据,包括财务报告等复杂、备受瞩目的数据产品。

通常,利益相关方数据需求的增加是一个很好的指标,表明您需要主动领先于数据质量问题,以确保最终用户的数据可靠性

您的公司正在转向自助服务分析模式

您正在转向自助服务分析模式,以节省数据工程时间,并允许每个业务用户直接访问数据并与之交互。

您的数据团队很兴奋,因为他们不再需要满足业务用户的临时请求。同样,您的利益相关者也很高兴,因为访问数据的瓶颈消除了。

虽然这对您的数据团队来说是令人兴奋的,但您的利益相关者需要信任这些数据。如果他们不信任它,他们就不会用它来做决策。最终,如果您的终端用户不信任这些数据,那么迁移到自助服务分析模型的目的就落空了。

存在于任何数据生态系统中的数据质量问题 有 两种:你可以预测的( 已知的未知 )和你不能预测的( 未知的未知 )。随着数据在数据驱动型组织的日常运营中变得越来越不可或缺,对可靠数据的需求只会增加。

数据是客户价值主张的关键部分

任何时候你的组织为它的数据找到一个新的用例,特别是当它是面向客户的时候,你的头脑中就会响起一个警报。慢慢的,每个应用都在变成数据应用。

Toast 是一家领先的餐厅销售点提供商,基于其为客户提供的业务洞察力,使其与竞争对手区分开来。通过 Toast,餐厅可以获得数百个数据点,例如他们的业务在一段时间内的表现如何,他们的销售额与昨天相比如何,以及他们的顶级客户是谁。

“我们说我们的客户都是 Toast 公司的员工,”Toast 公司的数据工程经理诺亚·艾布拉姆森说。

“我们的团队为所有内部数据请求提供服务,从产品到上市,从客户支持到硬件运营。”

虽然这是一个巨大的增值,但它也使他们的数据堆栈面向客户。这意味着它必须像其核心产品一样,以同样的可靠性和正常运行时间标准来对待。

构建更可靠的数据*台

就像天然气公司需要相信他们的石油钻塔可以每天向消费者提供天然气和石油一样,组织也需要相信他们的数据可以向利益相关者提供干净可靠的数据。

通过采用主动的数据质量方法,您的团队可以在疯狂的松弛消息、简洁的电子邮件和其他数据停机迹象出现之前,率先了解数据质量问题。

否则,宝贵的工程时间将被浪费在应对数据宕机上,您成为数据驱动型公司的努力将随着时间的推移而受阻,业务用户也将失去对数据的信任。

虽然每种情况因您的业务而异,但我们发现最好尽早将数据质量融入您的数据*台。

毕竟,不是每个人的快速拨号上都有蜘蛛侠。

有兴趣了解有关为您的数据*台采取主动数据质量方法的更多信息吗?伸出手去 巴尔摩西 和剩下的 蒙特卡洛团队

用 Tweepy 建立一个你被提到的推特数据集

原文:https://towardsdatascience.com/building-a-dataset-of-tweets-where-youre-mentioned-using-tweepy-182106d5dcef

使用 Twitter API 构建数据集的简单方法

克劳迪奥·施瓦兹在 Unsplash 上的照片

对于许多公司来说,分析人们提到你公司手柄的推文是很有趣的。这些数据可用于识别客户经常向公司询问的反馈主题和问题。当我们理解了关键主题,我们就可以制定行动计划,从一开始就防止问题的发生。

Tweepy 是一个允许更容易与 Twitter API 交互的包。在这篇文章中,我将介绍如何在 Twitter 开发者*台上设置自己,并使用 Tweepy 来抓取任何提到你的句柄的推文。

首先,Twitter 发布了不同术语的定义,理解这一点很重要,因为它规定了我们如何调用 API。

定义(推特,2022 年)

Tweets: 发布到 Twitter 上的消息,包含文本、照片、GIF 和/或视频。

提及:包含另一个账户的 Twitter 用户名的推文,前面有“@”符号。比如:“Hello @TwitterSupport!”

回复:回复是指你回复另一个人的推文。

我们希望被提及,因为我们希望了解客户对我们的评价。

在 Twitter developer 上设置

最大的挑战是在 Twitter 开发者*台上建立和验证你自己。在这之后——非常简单。

去 twitter 开发者门户:https://developer.twitter.com/en注册。

你需要填写一些基本的细节,并验证你的电子邮件。

作者图片

然后你将建立一个项目

作者图片

你有了项目之后,Twitter 会给你提供 API_key 和 API_key_secret。这两个都很重要,所以一定要保存好。

作者图片

接下来,您需要设置高架通道。这在你的项目页面上。

作者图片

他们会要求您确认一些基本的个人信息,然后您必须回答为什么要增强访问权限。

作者图片

最后,你必须同意条款和条件,然后你的申请将被审查。我的是即时的,但我听说可能需要几天。

现在您已获得提升的访问权限,您需要对您的应用程序启用身份验证。

作者图片

你的 app 需要有读写权限,我选了 native app。

作者图片

Twitter 请求一个回拨 URI 和一个网站 URL——这些对于您在本地运行的分析来说似乎是有问题的。对于回拨 URI,您可以使用: http://127.0.0.1/return 并使用任何网站作为网站 URL。

作者图片

然后我们需要得到我们需要的最后两组凭证;访问令牌和访问令牌密码。

转到应用程序中的密钥和令牌,生成访问令牌并将其保存在本地。

作者图片

我们需要用四样东西来证明自己:

  1. api_key
  2. api _ 密钥 _ 机密
  3. 访问令牌
  4. 访问令牌密码

我们现在有了所有四个项目,所以我们可以开始使用 Tweepy 来收集我们的提及。

使用 Tweepy 连接到 Twitter API

首先,确保安装了 tweepy 包。

然后导入包:

接下来,我们需要使用我们之前生成的令牌来验证我们自己。我制作了虚拟密钥,以避免让你们访问我的 twitter 账户。

我们使用以下代码初始化身份验证对象,然后设置访问令牌。最后,我们使用认证对象初始化 API。

要获得提到我们句柄的 tweets,我们需要使用 API 中的‘mentions _ timeline’。“count”参数决定返回多少条推文。

它的输出是一个 JSON 文件。我们只对 tweet 的文本感兴趣,所以我们遍历包含数据的 JSON 对象,只打印出文本元素。

完整的代码在我的 github 里。

结论

Twitter API 是自然语言处理(NLP)数据集的优秀资源,然而直接调用它可能具有挑战性。Tweepy 提供了一个软件包,可以更容易地获取您需要的数据,因此您可以继续为业务提供见解。在这个例子中,我想获得提及,但 Tweepy 允许您获得自己的推文,特定用户的推文,甚至通过关注/阻止用户来控制您的帐户。功能的完整列表可以在文档中找到。

作为替代,如果您的公司拥有 Sprinklr(这是一个众所周知的社交媒体管理*台)的许可证,这提供了一个很好的前端来访问 Twitter API,而不需要任何代码。

参考

Twitter (2022) 关于不同类型的推文。可在:https://help.twitter.com/en/using-twitter/types-of-tweets(访问时间:2022 年 10 月 26 日)。


我喜欢为商业用户写关于数据科学的文章,我热衷于使用数据来提供切实的商业利益。

您可以通过 LinkedIn 与我联系,并通过 Medium 关注我,了解我的最新文章。

使用 R Shiny 和 Tensorflow 构建基于深度学习的对象检测应用程序

原文:https://towardsdatascience.com/building-a-deep-learning-based-object-detection-app-using-r-shiny-and-tensorflow-5c17e93301d0

了解如何基于您自己的数据集微调用于对象检测的定制深度学习模型

为对象检测训练一个像样的深度学习模型需要很多努力,当在最终用户的 web 应用程序中部署和嵌入该模型时,这变得更加复杂。在本教程中,我们打算通过提供一个如何使用 Python 和 Tensorflow 框架开发准确的深度学习模型的实际示例来解决这个看似艰巨的任务,并使用 R Shiny 框架构建一个支持动态对象检测的工作 web 应用程序。在本教程结束时,您将能够为杂货项目构建一个全面的对象识别应用程序,如下所示:

物体检测应用。图片由作者提供。

训练用于对象检测的深度学习模型

训练用于对象检测的执行深度学习模型需要大量数据和计算能力。为了促进开发,我们可以通过细化基于其他相关数据集预训练的调整模型来使用迁移学习。在本例中,我们选择由 Tensorflow Hub 提供的 sdd mobilenet,它在速度和准确性之间提供了良好的*衡。

由于在训练全面的深度学习模型时,需要考虑多个后端逻辑,如路径和超级参数,因此我们可以创建一个中央字典来存储这些配置参数,包括设置不同的路径,安装相关的库,以及下载预训练的模型。

# Configuration parameters
CUSTOM_MODEL_NAME **=** 'my_ssd_mobnet' 

*# SSD has good tradeoff between speed and accuracy; can switch to other pretrained model*
PRETRAINED_MODEL_NAME **=** 'ssd_mobilenet_v2_fpnlite_320x320_coco17_tpu-8'
PRETRAINED_MODEL_URL **=** 'http://download.tensorflow.org/models/object_detection/tf2/20200711/ssd_mobilenet_v2_fpnlite_320x320_coco17_tpu-8.tar.gz'

*# TF official script to encode training data to tf record format*
TF_RECORD_SCRIPT_NAME **=** 'generate_tfrecord.py'

*# Mapping dictionary between label and integer id*
LABEL_MAP_NAME **=** 'label_map.pbtxt'

*# Define a list of folder paths to be created (if needed) and used later*
paths **=** {
    'WORKSPACE_PATH': os**.**path**.**join('Tensorflow', 'workspace'),
    'SCRIPTS_PATH': os**.**path**.**join('Tensorflow','scripts'),
    'APIMODEL_PATH': os**.**path**.**join('Tensorflow','models'),
    *# bounding box annotation*
    'ANNOTATION_PATH': os**.**path**.**join('Tensorflow', 'workspace','annotations'),
    'IMAGE_PATH': os**.**path**.**join('Tensorflow', 'workspace','images'),
    'MODEL_PATH': os**.**path**.**join('Tensorflow', 'workspace','models'),
    'PRETRAINED_MODEL_PATH': os**.**path**.**join('Tensorflow', 'workspace','pre-trained-models'),
    'CHECKPOINT_PATH': os**.**path**.**join('Tensorflow', 'workspace','models',CUSTOM_MODEL_NAME), 
    'OUTPUT_PATH': os**.**path**.**join('Tensorflow', 'workspace','models',CUSTOM_MODEL_NAME, 'export'), 
    'PROTOC_PATH':os**.**path**.**join('Tensorflow','protoc')
}

files **=** {
    'PIPELINE_CONFIG':os**.**path**.**join('Tensorflow', 'workspace','models', CUSTOM_MODEL_NAME, 'pipeline.config'),
    'TF_RECORD_SCRIPT': os**.**path**.**join(paths['SCRIPTS_PATH'], TF_RECORD_SCRIPT_NAME), 
    'LABELMAP': os**.**path**.**join(paths['ANNOTATION_PATH'], LABEL_MAP_NAME)
}# Download TF model training utility scripts from TF model zoo
**if** **not** os**.**path**.**exists(os**.**path**.**join(paths['APIMODEL_PATH'], 'research', 'objection_detection')):
    !git clone https:**//**github**.**com**/**tensorflow**/**models {paths['APIMODEL_PATH']}# Install TF object detection library
**if** os**.**name**==**'posix':  
    !apt**-**get install protobuf**-**compiler
    !cd Tensorflow**/**models**/**research **&&** protoc object_detection**/**protos**/*.**proto **--**python_out**=.** **&&** cp object_detection**/**packages**/**tf2**/**setup**.**py **.** **&&** python **-**m pip install **.**

我们还需要为杂货项目识别的特定任务提供训练图像和边界框。这些数据将用于通过将默认输出更改为特定设置来微调预训练模型,即识别字典中描述的六种杂货项目(苹果、鳄梨、香蕉、卷心菜、胡萝卜和土豆)。

**#** Download training images **import** shutil

**if** os**.**path**.**exists('object_detection_using_tensorflow'):
    shutil**.**rmtree('object_detection_using_tensorflow')

!git clone [https:**//**github**.**com**/**jackliu333**/**object_detection_using_tensorflow**.**git](https://github.com/jackliu333/object_detection_using_tensorflow.git)# Create label map
labels **=** [{'name':'Apple', 'id':1},
          {'name':'Avocado', 'id':2},
          {'name':'Banana', 'id':3},
          {'name':'Cabbage', 'id':4},
          {'name':'Carrot', 'id':5},
          {'name':'Potato', 'id':6}]

**with** open(files['LABELMAP'], 'w') **as** f:
    **for** label **in** labels:
        f**.**write('item { \n')
        f**.**write('\tname:\'{}\'\n'**.**format(label['name']))
        f**.**write('\tid:{}\n'**.**format(label['id']))
        f**.**write('}\n')

我们还将把数据分成训练集和测试集。请注意,这种划分需要按照类别索引进行,以便特定类别的项目不会完全分配到训练集或测试集中。

# Split into train test folders
tmp_folders **=** ['train', 'test']

**for** i **in** tmp_folders:
    **if** os**.**path**.**exists(os**.**path**.**join(paths['IMAGE_PATH'], i)):
        shutil**.**rmtree(os**.**path**.**join(paths['IMAGE_PATH'], i))
        !mkdir **-**p {os**.**path**.**join(paths['IMAGE_PATH'], i)}
    **else**:
        !mkdir **-**p {os**.**path**.**join(paths['IMAGE_PATH'], i)}**import** shutil

**for** i **in** range(len(labels)):
    *# print(labels[i]['name'])*
    from_path **=** os**.**path**.**join('object_detection_using_tensorflow','images',labels[i]['name'])
    *# print(from_path)*

    *# get unique file names*
    tmp_files **=** os**.**listdir(from_path)
    tmp_names **=** []
    tmp_file_types **=** []
    **for** tmp_file **in** tmp_files:
        tmp_name **=** os**.**path**.**splitext(tmp_file)[0]
        tmp_file_type **=** os**.**path**.**splitext(tmp_file)[1]
        tmp_names**.**append(tmp_name)
        tmp_file_types**.**append(tmp_file_type)
    tmp_names **=** list(set(tmp_names))
    tmp_names **=** [i **for** i **in** tmp_names **if** i **!=** '.DS_Store']
    tmp_file_types **=** list(set(tmp_file_types))
    tmp_file_types **=** [i **for** i **in** tmp_file_types **if** len(i) **!=** 0]
    *# random shuffle the files*
    random**.**shuffle(tmp_names)

    *# training and test files*
    tmp_names_train **=** tmp_names[0:int(len(tmp_names)*****0.9)]
    tmp_names_test **=** [i **for** i **in** tmp_names **if** i **not** **in** tmp_names_train]

    *# move into respective target folders*
    **for** tmp_name **in** tmp_names_train:
        **for** tmp_file_type **in** tmp_file_types:
            tmp_name_full **=** tmp_name **+** tmp_file_type
            shutil**.**copy(os**.**path**.**join(from_path, tmp_name_full), \
                        os**.**path**.**join(paths['IMAGE_PATH'], "train"))

    **for** tmp_name **in** tmp_names_test:
        **for** tmp_file_type **in** tmp_file_types:
            tmp_name_full **=** tmp_name **+** tmp_file_type
            shutil**.**copy(os**.**path**.**join(from_path, tmp_name_full), \
                        os**.**path**.**join(paths['IMAGE_PATH'], "test"))

结果图像数据然后被转换成 TF 记录格式,以便更快地处理。

*#* Create TF Record *# download conversion script*
**if** **not** os**.**path**.**exists(files['TF_RECORD_SCRIPT']):
    !git clone https:**//**github**.**com**/**nicknochnack**/**GenerateTFRecord {paths['SCRIPTS_PATH']}!python {files['TF_RECORD_SCRIPT']} **-**x {os**.**path**.**join(paths['IMAGE_PATH'], 'train')} **-**l {files['LABELMAP']} **-**o {os**.**path**.**join(paths['ANNOTATION_PATH'], 'train.record')} 
!python {files['TF_RECORD_SCRIPT']} **-**x {os**.**path**.**join(paths['IMAGE_PATH'], 'test')} **-**l {files['LABELMAP']} **-**o {os**.**path**.**join(paths['ANNOTATION_PATH'], 'test.record')}

在开始训练模型之前,我们需要更新一些将偏离默认设置的配置参数,以便训练管道知道我们正在基于六个类别执行对象识别。

**#** Update configuration file for transfer learning **import** tensorflow **as** tf
**from** object_detection.utils **import** config_util
**from** object_detection.protos **import** pipeline_pb2
**from** google.protobuf **import** text_format

*# Read current configuration file*
pipeline_config **=** pipeline_pb2**.**TrainEvalPipelineConfig()
**with** tf**.**io**.**gfile**.**GFile(files['PIPELINE_CONFIG'], "r") **as** f:                                                                                                                                                                                                                     
    proto_str **=** f**.**read()                                                                                                                                                                                                                                          
    text_format**.**Merge(proto_str, pipeline_config)  

*# Update based on new labels*
pipeline_config**.**model**.**ssd**.**num_classes **=** len(labels)
pipeline_config**.**train_config**.**batch_size **=** 4
pipeline_config**.**train_config**.**fine_tune_checkpoint **=** os**.**path**.**join(paths['PRETRAINED_MODEL_PATH'], PRETRAINED_MODEL_NAME, 'checkpoint', 'ckpt-0')
pipeline_config**.**train_config**.**fine_tune_checkpoint_type **=** "detection"
pipeline_config**.**train_input_reader**.**label_map_path**=** files['LABELMAP']
pipeline_config**.**train_input_reader**.**tf_record_input_reader**.**input_path[:] **=** [os**.**path**.**join(paths['ANNOTATION_PATH'], 'train.record')]
pipeline_config**.**eval_input_reader[0]**.**label_map_path **=** files['LABELMAP']
pipeline_config**.**eval_input_reader[0]**.**tf_record_input_reader**.**input_path[:] **=** [os**.**path**.**join(paths['ANNOTATION_PATH'], 'test.record')]

*# Write to configuration file*
config_text **=** text_format**.**MessageToString(pipeline_config)                                                                                                                                                                                                        
**with** tf**.**io**.**gfile**.**GFile(files['PIPELINE_CONFIG'], "wb") **as** f:                                                                                                                                                                                                                     
    f**.**write(config_text)

使用 Tensorflow 提供的训练脚本可以简化模型训练。

TRAINING_SCRIPT **=** os**.**path**.**join(paths['APIMODEL_PATH'], 'research', 'object_detection', 'model_main_tf2.py')
command **=** "python {} --model_dir={} --pipeline_config_path={} --num_train_steps=2000"**.**format(TRAINING_SCRIPT, paths['CHECKPOINT_PATH'],files['PIPELINE_CONFIG'])
!{command}

由于训练过程将保存中间检查点,即模型权重,因此我们可以选择加载哪个模型检查点并将其用于检测。

**#** Load trained model from checkpoint **import** os
**import** tensorflow **as** tf
**from** object_detection.utils **import** label_map_util
**from** object_detection.utils **import** visualization_utils **as** viz_utils
**from** object_detection.builders **import** model_builder
**from** object_detection.utils **import** config_util

*# Load pipeline config and build a detection model*
configs **=** config_util**.**get_configs_from_pipeline_file(files['PIPELINE_CONFIG'])
detection_model **=** model_builder**.**build(model_config**=**configs['model'], is_training**=False**)

*# Restore checkpoint*
ckpt **=** tf**.**compat**.**v2**.**train**.**Checkpoint(model**=**detection_model)
ckpt**.**restore(os**.**path**.**join(paths['CHECKPOINT_PATH'], 'ckpt-3'))**.**expect_partial()

*# @tf.function*
**def** detect_fn(image):
    image, shapes **=** detection_model**.**preprocess(image)
    prediction_dict **=** detection_model**.**predict(image, shapes)
    detections **=** detection_model**.**postprocess(prediction_dict, shapes)
    **return** detections

现在,我们可以通过传递从前面定义的图像文件夹中随机选择的图像来测试微调后的模型。

**import** cv2 
**from** matplotlib **import** pyplot **as** plt
**%**matplotlib inline

*# Randomly select an image to be detected*
tmp_img **=** random**.**choice([file **for** file **in** os**.**listdir(os**.**path**.**join(paths['IMAGE_PATH'], 
                                              'test')) **if** file**.**endswith(".jpg")])
IMAGE_PATH **=** os**.**path**.**join(paths['IMAGE_PATH'], 'test', tmp_img)

category_index **=** label_map_util**.**create_category_index_from_labelmap(files['LABELMAP'])

img **=** cv2**.**imread(IMAGE_PATH)
image_np **=** np**.**array(img)

input_tensor **=** tf**.**convert_to_tensor(np**.**expand_dims(image_np, 0), dtype**=**tf**.**float32)
detections **=** detect_fn(input_tensor)

num_detections **=** int(detections**.**pop('num_detections'))
detections **=** {key: value[0, :num_detections]**.**numpy()
              **for** key, value **in** detections**.**items()}
detections['num_detections'] **=** num_detections

*# detection_classes should be ints.*
detections['detection_classes'] **=** detections['detection_classes']**.**astype(np**.**int64)

label_id_offset **=** 1
image_np_with_detections **=** image_np**.**copy()

viz_utils**.**visualize_boxes_and_labels_on_image_array(
            image_np_with_detections,
            detections['detection_boxes'],
            detections['detection_classes']**+**label_id_offset,
            detections['detection_scores'],
            category_index,
            use_normalized_coordinates**=True**,
            max_boxes_to_draw**=**5,
            min_score_thresh**=**.5,
            agnostic_mode**=False**)

plt**.**imshow(cv2**.**cvtColor(image_np_with_detections, cv2**.**COLOR_BGR2RGB))
plt**.**show()

检测图像中的对象。图片由作者提供。

使用 R Shiny 构建 Web 应用程序

R Shiny 是构建现代 web 应用程序的一个很好的工具,不需要深入了解 HTML、CSS 或 Javascript。我们可以通过选择特定的应用程序框架并填充前端(ui)来快速启动应用程序。r)和后端(服务器。r)脚本,以及可选的全局文件(global。r)处理整个应用程序的环境。

R Shiny 中用户界面的设计可以遵循如下的网格系统,这样可以很容易地决定在特定的行或列中添加什么组件。

ui <- dashboardPage(
 skin=”blue”,
 #(1) Header
 dashboardHeader(title=”Object Recognition App”,#,style=”font-size: 120%; font-weight: bold; color: white”),
 titleWidth = 250,
 tags$li(class = “dropdown”),
 dropdownMenu(
 type = “notifications”, 
 icon = icon(“question-circle”),
 badgeStatus = NULL,
 headerText = “Feedback”,
 notificationItem(“Send email to developer”, icon = icon(“file”),
 href = “[liu.peng@u.nus.edu](mailto:liu.peng@u.nus.edu)”)
 )),
 #(2) Sidebar
 dashboardSidebar(
 width=250,
 fileInput(“input_image_upload”,”Upload image”, accept = c(‘.jpg’,’.jpeg’)),
 tags$br(),
 sliderInput(“min_score_threshold”,”Confidence threshold”,0,1,0.5),
 # tags$p(“Upload the image here.”)
 selectInput(inputId = “product_type”,label = “Choose product”,
 choices = c(“Flour”,”Baby Food”),
 selected = NA),
 selectInput(inputId = “halal_status”,label = “Halal status”,
 choices = c(“H”,”NH”),
 selected = NA),
 selectInput(inputId = “weight”,label = “Choose weight”,
 choices = c(“50g”,”100g”),
 selected = NA),
 actionButton(“submit”,”Submit”,icon(“paper-plane”), 
 style=”color: #fff; background-color: #337ab7; border-color: #2e6da4")
 ),

 #(3) Body

 dashboardBody(
 box(
 title = “Object Recognition”, width = 12, solidHeader = TRUE, status = “primary”,
 collapsible = T, collapsed = F,
 fluidRow(
 column(6,
 h4(“Instruction:”),
 # tags$br(),
 tags$p(“1\. Upload image to be classified and set confidence threshold.”),
 tags$p(“2\. Check prediction results.”),
 tags$p(“3\. Select specific product category.”),
 tags$p(“4\. Click submit to record in the system.”)
 ),
 column(6,
 h4(“Predicted Category:”),
 tableOutput(“text”)
 )
 ),

 fluidRow(
 column(h4(“Image:”),imageOutput(“output_image”), width=6),
 column(h4(“Predicted Image:”),imageOutput(“output_image2”), width=6)
 )
 ),
 box(
 title = “Image Gallery”, width = 12, solidHeader = TRUE, status = “success”,
 collapsible = T, collapsed = F,
 fluidRow(
 column(3,
 h3(“All categories”),
 verbatimTextOutput(“all_cats”)
 ),
 column(3, 
 selectInput(“input_image_select”, “Select image”,c(“”,ALL_IMGS),selected = “”),
 ),
 column(6,
 column(h4(“Image:”),imageOutput(“output_image_selected”), width=6),
 )
 )
 )

 # box(
 # title = “Product Recording”, width = 12, solidHeader = TRUE, status = “success”,
 # collapsible = T, collapsed = T,
 # “test”
 # )

 ))

服务器处理所有后端处理,例如使用预先训练的模型对上传的图像进行预测,并将预测输出返回到前端 UI。

服务器文件。图片由作者提供。

我们还在全局文件中定义了一些实用函数和环境设置。注意,R 使用 reticulate 库来处理 Python 脚本。在这种情况下,我们首先构建一个新的虚拟环境,为对象检测 conda 设置必要的背景环境,然后加载核心的图像识别 Python 函数,这些函数将以 API 的形式进行转换,与 R Shiny 生态系统进行交互。

全局文件。图片由作者提供。

结论

在本教程中,我们介绍了如何通过迁移学习在 Tensorflow 中微调预训练的深度学习模型以进行杂货项目识别,以及如何通过 R Shiny 构建现代 web 应用程序来为最终用户托管模型。最终产品是 R & Python 脚本的组合,其中核心 Python 功能(如对象检测)被无缝包装,并作为 API 向 R 公开,由 R 处理应用程序开发。我们希望本教程将是一个很好的起点,让您可以部署自己的模型,并以一种有效且吸引人的方式与他人分享。

所有支持数据和代码都可以在附带的 github 以及下面的 YouTube 浏览中找到。

使用 Numpy 从头构建深度神经网络

原文:https://towardsdatascience.com/building-a-deep-neural-network-from-scratch-using-numpy-4f28a1df157a

现代深度学习库是强大的工具,但它们可能会导致从业者想当然地认为神经网络的功能原理是理所当然的

来源:unsplash.com

在这个项目中,我在不借助任何深度学习库(Tensorflow,Keras,Pytorch)的情况下,构建了一个深度神经网络。我之所以将自己强加于这项任务,是因为如今,使用多个 python 库提供的高级工具来构建深度和复杂的神经网络是毫不费力的。毫无疑问,这对机器学习专业人员来说是一个巨大的优势:我们只需要几行代码就可以创建强大的模型。然而,这种方法有一个很大的缺点,就是让这些网络的功能不清楚,因为它们是在“引擎盖下”发生的。

对于任何想要巩固对这些神奇工具的理解的人来说,从头开始构建深度神经网络是一个很好的练习。

这篇文章将涵盖理论和实践两部分。理论部分是理解实现的必修课。对于理论,我们需要代数和微积分的基础知识,而对于编码部分,只会用到内置的 Python 函数和 Numpy。

这种方法在存储缓存值的策略上不同于其他实现。此外,与大多数实现不同,该代码允许我们比较无限可能的网络架构,因为层和激活单元的数量是由用户定义的。

问题定式化

在这个应用程序中,我创建了一个深度神经网络来解决著名的 MNIST 分类问题。

MNIST 数据集是一个手写数字的大型数据库。该数据集包含 70,000 个小图像(28 x 28 像素),每个图像都被标记。

来自 MNIST 数据集的手写数字

理论

在这一节中,我将概述应用程序的理论部分。我将为正向传播和反向传播的每一步定义所有矩阵,特别注意阐明所有矩阵的维数。

投入

输入包括形状为 28×28 像素的 m 个训练图像。因此,每个图像由大小为 784 的一维数组表示。为了加快计算速度,我将利用矢量化技术。我将整个训练集存储在一个矩阵 X 中。X 的每一列代表一个训练示例:

尺寸为:

正向传播

为了澄清解释,让我们假设建立一个神经网络,包括:

  • 输入层
  • 1 个大小为 10 的隐藏层,具有 ReLu 激活功能
  • 1 个大小为 10 的隐藏层,具有 Softmax 激活功能
  • 输出层

所有矩阵和计算可以很容易地扩展到任何架构的全连接网络。

对于每一层,正向传播由 2 个步骤组成:

  • 权重和偏差的应用
  • 激活函数的计算

对于隐藏层 1,我们使用矩阵乘法和矩阵加法来应用权重和偏差:

然后,我们需要计算选定的激活函数:

遵循矩阵乘法规则,维数为:

第 2 层也是如此:

矩阵维数为:

一般来说,对于任何层 l,这两个步骤都是通过这些简单的方程进行的:

在前向传播结束时,到达层 L,我们计算预测:

反向传播

反向传播的目的是计算损失函数相对于网络每层权重的偏导数。一旦我们知道了导数,我们就可以应用梯度下降优化来调整它们的值。

反向传播的第一步是计算预测的误差。将第 2 层视为最后一层,我们有:

其中:

现在,我们可以计算损失函数相对于第 2 层的权重和偏差的导数:

尺寸如下:

一旦我们知道了最终层的所有导数,反向传播过程就包括通过网络的各层反向传播,并如下计算偏导数:

参数更新

知道了损失函数的梯度,我们就知道向哪个方向移动才能达到最优。因此,我们更新参数:

编码

本节介绍了用于实现深度神经网络的所有功能。完整的代码可以在我的 GitHub 库中找到。

在上面链接的 GitHub 存储库中,您会找到 5 个文件:

  • “README.md”:这是一个展示项目的降价文件
  • “train.csv”:这是一个包含 MNIST 数据集训练集的 csv 文件
  • “test.csv”:这是一个包含 MNIST 数据集测试集的 csv 文件
  • “main.py”:这是一个 Python 脚本,我们将从这里运行神经网络
  • “utils.py”:这是一个 Python 文件,我们在其中定义了构建神经网络所需的函数

我们将主要关注“utils.py”文件,因为它是大部分网络实现所在的位置。

第一个功能是**init_params**。它将层的尺寸作为输入,并返回包含所有随机初始化的权重和偏差的字典:

接下来,我定义所有的激活函数及其导数。在这个应用程序中,我们将使用 ReLu 和 Softmax 激活。

现在好戏开始了。**forward_prop** 函数将输入 X 和网络参数(权重和偏差)作为输入,并返回包含每层激活的字典。输出字典还包含各图层的 Z 矩阵,作为一种缓存。这是因为在反向传播阶段需要 Z 矩阵。

**back_prop**函数可能是整个实现的核心。它从最后一层到第一层扫描网络,并计算关于每层的每个权重和偏差的损失函数的梯度。

在反向传播步骤期间计算的梯度稍后用于更新权重和偏差。**update_params** 函数处理这个任务。

接下来的两个辅助功能**get_predictions****get_accuracy** 分别用于从最终层选择预测(即选择具有最高 Softmax 分数的类别)和计算预测的准确度。

最后,我将上面的所有函数总结在**gradient_descent_optimization** 函数中:

模型评估

我想尝试的第一个网络架构包括:

  • 大小为 784 的输入层
  • 大小为 10 的隐藏层#1 和 ReLu 激活
  • 大小为 10 的隐藏层#2 和 Softmax 激活
  • 大小为 1 的输出层

该层在我的 Python 代码中用列表**[784, 10, 10]**表示。没有必要在列表中包含输出图层,因为它没有关联的权重。从现在开始,所有的网络都将由描述其架构的 python 列表来表示。

在训练网络 1000 次迭代后,精确度收敛到大约 88%的值。

作者图片

考虑到网络的规模,这并不可怕,但是这与我们在这个任务中想要达到的结果相差甚远。一个更深更广的网络**[784, 256, 128, 64, 10]** ,经过 500 次迭代的训练,达到了 97%的准确率。

作者图片

对于第二个网络,每次迭代的训练时间显著增加,因为额外的层和神经元使其计算要求更高。

为了达到更好的图像分类效果,通常使用不同类型的网络:卷积神经网络。

请随意修改我的 GitHub 库中的代码,并探索当层数和单元数改变时结果如何变化。

使用 Azure Kubernetes 服务、Seldon Core 和 Azure Cognitive 构建健康实体标签服务

原文:https://towardsdatascience.com/building-a-health-entity-labelling-service-using-azure-kubernetes-service-seldon-core-and-azure-5dd6871a338

在本教程中,我们将在 Azure 生态系统中完全用 Kubernetes 构建一个推理服务

照片由像素皮克斯拜拍摄

在本教程中,我们将使用以下技术:

  • AKS : Azure 云上的 Azure Kubernetes 服务作为我们部署 ML 模型的*台
  • Seldon core :开源*台,用于在 Kubernetes 上快速部署机器学习模型
  • cdktf : 将允许我们用 python 在 Azure cloud 中构建基础设施
  • 一个 zure 认知服务:Azure 中现成的 AI 服务,可以通过 API 访问
  • s2i :一个用于从源代码创建 docker 图像的命令行工具
  • Istio:一个开源项目,Istio 使组织能够保护、连接和监控微服务

TL;DR:代码在GitHub上。

在本文中,我们将在 Azure 中部署一个健康搜索实体推理服务。我们将使用 cdktf 部署基础设施,并将编写一个 seldon 核心模型,该模型将与 Azure 认知服务 API 进行交互,以分析健康数据。我们还将使用 helm 在 AKS 和其他各种依赖中安装 seldon 核心。我们假设您已经在您的环境中安装了 kubectl、helm 和 azure CLI,并且您能够在基于 linux 的终端中运行这里显示的大多数命令。

步骤 1:编写 azure 基础设施

为了构建和部署基础设施,我们需要访问 azure 帐户(免费试用注册可用此处)和安装在我们环境中的 cdktf。我们可以使用 brew 实现这一目的:

brew install cdktf

好了,现在我们可以开始编码了。在 cdktf 中,我们需要做的第一件事是创建包含所有 azure 组件的主类,我们称之为 MLAzureStack:

正如我们所看到的,我们需要从 TerraformStack 继承,还需要创建一个azuremprovider来帮助与 azure cloud APIs 进行通信,以便构建所需的基础设施。

接下来,我们将创建一个资源组。这是一个逻辑容器,将容纳我们部署的所有基础架构。这是一个将你所有的资源组合在一起的好方法。

我们使用一个类 StackVariables 的 vars 实例,它将保存我们基础设施的所有自定义值。请访问 github repo 了解有关这方面的详细信息。

在下一部分中,我们将创建 Kubernetes 集群。我们将创建一个包含 2 个节点和 D2 V4 虚拟机的基本集群,足以托管 Seldon Core 和 Istio 安装。

我们还需要创建负责文本分析 API 的认知帐户,该 API 将运行用于标记新数据的健康实体服务。

我们必须用我们的代码创建一个 docker 容器,这个容器必须托管在 Azure 中。为此,我们将创建一个容器注册表。

我们需要的最后一项基础设施是创建一个密钥库。基本上,我们有敏感的信息,我们希望安全地存储。为此,我们创建了一个密钥库。

在 KeyVault 中,我们可以定义我们希望给予新的秘密和密钥的权限。我们还创建了两个存储库秘密,用于保存认知访问密钥和端点。访问健康实体服务 API 需要这些值。

我们现在已经准备好部署 Azure 堆栈了。我们需要从终端运行 cdktf deploy 命令:

cdktf deploy

这将初始化 terraform 提供程序并部署声明的资源:

作者图片

如果我们导航到 azure 门户,我们应该看到在资源组中创建的所有资源:

作者图片

步骤 2:编写谢顿核心模型

在这一节中,我们将编写谢顿模型,该模型将接收新的请求,如果遇到新的请求,将发回带标签的健康数据。

我们定义了一个health_entity类,它有一个__init__方法和一个predict方法,基本上负责处理请求。

作者图片

在构造函数中,我们连接到 KeyVault,得到我们之前定义的秘密。我们使用这些参数来创建一个负责处理 ML 请求的TextAnalyticsClient

predict方法中,我们将数据请求传递给医疗 azure 客户端。我们从 API 中取回一些entities和一些在它们之间定义的relationships。我们创建一个 JSON 响应,将所有信息返回给用户。

在我们定义了health_entity.py的文件夹中,我们还必须创建另一个文件.s2i/environment,它将包含 docker 映像中 Seldon 使用的所有环境变量。

MODEL_NAME=HealthModel
API_TYPE=REST
SERVICE_TYPE=MODEL
PERSISTENCE=0

我们可以看到,我们将创建一个标准模型,并为 REST 请求提供服务。GRPC 也得到支持。

步骤 3:在 AKS 中安装 Seldon 核心

为了能够在 AKS 中部署谢顿模型,我们需要在集群中安装一些东西。我们将需要 azure cli、helm 和 kubectl 来运行一些安装脚本。

要将 kubectl 连接到 Azure 集群,我们需要在 kube 配置文件中添加所需的凭证:

az aks get-credentials --resource-group aksseldonml-rg --name aksseldonml-aks --admin

我们可以测试这是否可行,使用kubectl get ns检查集群中可用的名称空间:

作者图片

谢顿核心需要的下一个组件是 istio。首先,我们需要使用以下方式下载它:

curl -L [https://istio.io/downloadIstio](https://istio.io/downloadIstio) | sh -

移动到 istio 包目录并将其添加到您的路径中,以便能够使用 istio 命令行实用程序istioctl:

cd istio-1.11.4 
export PATH=$PWD/bin:$PATH

现在,我们可以在集群上安装 istio:

istioctl install --set profile=demo -y

名称空间标签istio-injection=enabled指示 Istio 自动注入代理以及我们在该名称空间中部署的任何东西。我们将为包含推理模型的seldon名称空间设置它:

kubectl create namespace seldon
kubectl label namespace seldon istio-injection=enabled

为了让 Seldon Core 使用 Istio mesh,我们需要创建一个 Istio 网关,帮助将流量导入 mesh:

kubectl apply -f - << END
apiVersion: networking.istio.io/v1alpha3
**kind**: Gateway
**metadata**:
  **name**: seldon-gateway
  **namespace**: istio-system
**spec**:
  **selector**:
    **istio**: ingressgateway
  **servers**:
  - **port**:
      **number**: 80
      **name**: http
      **protocol**: HTTP
    **hosts**:
    - "*"
END

如果我们想检查是否所有的 Istio 组件安装正确,我们可以运行kubectl -n istio-system get all,我们应该有类似的东西:

作者图片

在安装 Seldon 之前,我们需要创建一个命名空间来托管它:

kubectl create namespace seldon-system

接下来,我们可以使用 helm 安装谢顿核心:

helm install seldon-core seldon-core-operator \
    --repo [https://storage.googleapis.com/seldon-charts](https://storage.googleapis.com/seldon-charts) \
    --set usageMetrics.enabled=true \
    --set istio.enabled=true \
    --namespace seldon-system

我们可以使用kubectl -n seldon-system get pods:检查 Seldon 控制器是否正在运行

作者图片

步骤 4:部署 Seldon health 服务

不需要。我们已经设置好了环境,可以进行部署。我们将使用s2i来构建 docker 映像。要安装它,我们可以运行:

brew install source-to-image

我们将使用seldonio/seldon-core-s2i-python36:1.14.0-dev作为基础构建一个 docker 映像。我们将把我们的新图像命名为seldon-health:0.1

s2i build . seldonio/seldon-core-s2i-python36:1.14.0-dev seldon-health:0.1

接下来,我们需要登录到我们在步骤 1 中创建的 Azure 容器。这将允许我们在注册表中推送新的映像。

az acr login --name seldonservice

我们需要标记图像,然后才能将其推送到 Azure:

docker tag seldon-health:0.1 seldoncontainerregistry.azurecr.io/seldon-health:0.1

最后,我们可以将图像发送到容器注册表中:

docker push seldoncontainerregistry.azurecr.io/seldon-health:0.1

一旦图像在 azure 容器中,我们就可以使用 kubectl 部署 seldon 模型。kubernetes 清单包含我们刚刚部署的图像:

kubectl apply -f - << END
apiVersion: machinelearning.seldon.io/v1
kind: SeldonDeployment
metadata:
  name: seldon-health
  namespace: seldon
spec:
  name: seldon-health
  predictors:
  - componentSpecs:
    - spec:
        containers:
        - name: classifier
          image: seldoncontainerregistry.azurecr.io/seldon-health:0.1
    graph:
      name: classifier
    name: default
    replicas: 1
END

我们可以使用kubectl -n seldon get pods:来验证部署是否成功

作者图片

步骤 5:测试健康服务

我们可以用两种方式来测试健康服务。首先,我们将使用seldon-core-microservice CLI 在本地测试代码,我们还将在 AKS 上测试部署。

我们需要安装带有 pip 的 seldon core CLI:

pip install seldon-core

然后在一个单独的终端上我们可以运行一个本地的 seldon 核心微服务服务器:

cd model/ && seldon-core-microservice --service-type MODEL health_entity

您应该能够看到一个 gunicorn 服务器开始监听端口 9000:

作者图片

在单独的终端上,我们可以运行 curl 命令来检查我们的服务:

curl -X POST [http://localhost:9000/api/v1.0/predictions](http://localhost:9000/api/v1.0/predictions) -H 'Content-Type: application/json' -d '{ "data": { "ndarray": ["Patient needs to take 50 mg of ibuprofen every day."] } }'

正如我们所看到的,我们已经在 curl 请求中发送了一些医疗信息。我们应该得到一个带有标签信息的 JSON:

[
   {
      "entity":{
         "name":"50 mg",
         "category":"Dosage",
         "confidence_score":0.99
      }
   },
   {
      "entity":{
         "name":"ibuprofen",
         "category":"MedicationName",
         "confidence_score":1.0
      }
   },
   {
      "entity":{
         "name":"every day",
         "category":"Frequency",
         "confidence_score":1.0
      }
   },
   {
      "relation_type":"DosageOfMedication",
      "roles":[
         {
            "role":"Dosage",
            "entity":"50 mg"
         },
         {
            "role":"Medication",
            "entity":"ibuprofen"
         }
      ]
   },
   {
      "relation_type":"FrequencyOfMedication",
      "roles":[
         {
            "role":"Medication",
            "entity":"ibuprofen"
         },
         {
            "role":"Frequency",
            "entity":"every day"
         }
      ]
   }
]

我们可以看到,我们得到了标记的医学术语,例如布洛芬被认为是MedicationName类型的实体,我们还可以看到不同术语之间的关系,如 50 mg(剂量)和布洛芬(药物),关系类型为DosageOfMedication.

我们也可以在 AKS 中使用类似的 curl 命令。我们需要为我们部署的 seldon health 服务获取集群 ip 和端口。为此,我们需要运行几个 kubectl 命令:

export INGRESS_HOST=**$(**kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}'**)**
export INGRESS_PORT=**$(**kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].port}'**)**
export INGRESS_URL=$INGRESS_HOST:$INGRESS_PORT
echo $INGRESS_URL

一旦我们有了INGRESS_URL,我们可以在 curl 命令中替换它:

curl -X POST [http://$](http://localhost:9000/api/v1.0/predictions)INGRESS_URL[/api/v1.0/predictions](http://localhost:9000/api/v1.0/predictions) -H 'Content-Type: application/json' -d '{ "data": { "ndarray": ["Patient needs to take 50 mg of ibuprofen every day."] } }'

我们应该得到与本地请求相似的响应。

好了,我们终于到了教程的结尾。这是一个复杂的问题,但是我们已经设法在 Azure 中创建了一个端到端的 Kubernetes ML 解决方案。我们已经看到了如何使用 CDKTF 在 python 中创建复杂的基础设施,如何使用 helm 和 kubectl 在 Kubernetes 中安装各种框架,以及如何使用 Seldon Core 创建和部署 ML RESTful 服务,这种服务非常可扩展、安全且易于配置。

构建高棉语拼写检查器

原文:https://towardsdatascience.com/building-a-khmer-spelling-checker-7e3356677335

了解 BK 树和编辑距离(又名 Levenshtein 距离)

图片作者。

一.动机

最*,我有机会开发一个高棉键盘应用程序。像任何其他键盘应用程序一样,拼写检查和拼写建议是用户体验的核心功能。在尝试实现这些特性时,我遇到了一种流行的方法,它依赖于两个概念来进行拼写检查:编辑距离(又名 Levenshtein 距离)和 BK 树。因此,这篇文章将讨论这些概念及其缺点,并分享一个依赖于它们的高棉语拼写检查器的 Python 实现。

二。编辑距离(又名莱文斯坦距离)

编辑距离是将一个字符串转换成另一个字符串所需的最小步骤/操作数。有多种技术可以计算编辑距离。然而,最流行的是由苏联数学家弗拉基米尔·莱文斯坦提出的莱文斯坦距离。他的方法允许在编辑距离中考虑三种类型的字符串操作:插入、删除和替换/替换。在拼写检查的上下文中,编辑距离允许我们测量两个单词的差异。

算法

我们可以通过动态规划有效地计算编辑距离。它使用由 2D 阵列表示的矩阵来存储子串之间的临时距离,以节省计算成本。让我们看看如何计算“កាល”和“ក្បាល".”之间的编辑距离

步骤 1: 初始化一个大小为(len(word1) + 1,len(word2) +1)的空 2D 数组

图 1:步骤 1-初始化 2D 阵列。图片作者。

****第二步:用列索引填充第一行的单元格。

图 2:步骤 2-填写第一行。图片作者。

第三步:在第一列的单元格中填入它们的行索引。

图 3:步骤 3-填写第一列。图片作者。

步骤 4: 在位置(1,1),我们查看该位置的两个单词的字符:单词 1 (ក)和单词 2 (ក).因为它们是相同的,所以我们将当前单元格的值设置为其左上邻居的值。因此,cell (1,1)的值变为 0。然后,我们移动到第 1 行的下一列。

图 4:步骤 4——当 word1 和 word2 的字符相同时。图片作者。

步骤 5: 在位置(1,2),我们再次比较该位置的两个单词的特征:单词 1 (ក)和单词 2 (្).这一次两个人物是不同的。因此,当前单元格的值是 min([left,top,top-left]) + 1。然后,我们对剩余的单元格重复步骤 4 和 5。

图 5:步骤 5——当 word1 和 word2 的字符不同时。图片作者。

****第六步:我们填充完所有单元格后,最小编辑距离就是 2D 矩阵的右下角单元格。因此,单词“កាល”和“ក្បាល”之间的最小编辑距离是 2。

图 6:步骤 6-产生的 2D 矩阵和最小编辑距离。图片作者。

此外,为了知道哪种编辑操作组合导致最小的编辑距离,我们需要从左上角到右下角的单元格执行最短路径搜索。当然,可以有多种组合,因为可以有多条最短路径通向右下角的单元。

三。BK 树

构建拼写检查器的一个简单方法是找到用户提供的单词与字典中所有其他单词之间的编辑距离,并将编辑距离最小的单词返回给该单词。然而,如果字典很大,这种方法需要时间和计算。BK 树允许我们通过使用利用编辑距离的树数据结构来限制所需的比较次数,从而更快地执行搜索。树中的每个节点都是一个单词。连接不同节点的每条边是该节点与其父节点之间的编辑距离。为了更好地理解 BK 树,我们可以看一个例子,看看如何为一个 4 个单词的字典构建树:{"ស្គម、ស្អាត、កាល、ក្បាល"}.然后,我们将进一步研究如何在树中搜索匹配的节点。

BK 树构造算法

****第一步:添加第一个单词“ស្គម”作为树的根节点。然后继续下一个词,“ស្អាត".

图 7:步骤 1-添加第一个单词作为根节点。图片作者。

****第二步:设置根节点为当前节点。然后计算“ស្អាត”到当前节点之间的编辑距离。所以 dist(ស្អាត,ស្គម) = 3。之后,我们查看当前节点的子节点。由于当前节点没有距离等于ស្គមdist(ស្អាត的子节点,我们插入“ស្អាត”作为当前节点的子节点。然后我们转到下一个词,“កាល".”

图 8:步骤 2-添加第二个单词。图片作者。

****第三步:设置根节点为当前节点。然后计算“កាល”到当前节点之间的编辑距离。所以我们得到 dist(កាល,ស្គម) = 4。类似于步骤 2,当前节点没有距离等于ស្គម).dist(កាល的子节点因此,我们插入“កាល”作为当前节点的子节点。然后我们转到下一个词,“ក្បាល".”

图 8:步骤 3-添加第三个单词。作者图片

****第四步:设置根节点为当前节点。然后计算“ក្បាល”到当前节点之间的编辑距离,这给我们 dist(ក្បាល,ស្គម) = 4。这一次,当前节点有一个距离为 4 的子节点,即ស្គម).的 dist(កាល在这个场景中,我们通过将子节点设置为当前节点,将插入过程下推到该子节点。之后,我们重复插入过程,当前节点为“កាល”,得到 dist(ក្បាល,កាល) = 2。在这个阶段,没有一个“កាល”的孩子的距离与កាល).的 dist(ក្បាល相等因此,我们插入“ក្បាល”作为“កាល".”的子代

图 9:步骤 4-添加第四个单词(当一个孩子有相同的编辑距离时)。作者图片

在构建树之后,我们可以搜索它来找到匹配的节点作为单词更正。

BK 树搜索

我们可以应用任何树搜索算法来搜索 BK-树。在我们的例子中,我选择了深度优先搜索。然而,BK-tree 给我们的一个特殊功能是能够修剪/跳过树中的大部分节点。它通过允许我们只查看编辑距离在特定容差阈值内的节点来实现这一点。更具体地说,我们只查看当前节点的子节点,其编辑距离在[dist(word,current_node)-N,dist(word,current_node) + N]之间,其中 N 是容差级别。

让我们看看,如果用户输入单词“កាក”,并且我们的容忍级别 N = 2,将会返回什么。

步骤 1: 初始化一个空栈和一个空匹配列表。

图 10:步骤 1-初始化空堆栈和匹配。图片作者。

步骤 2: 将根节点(ស្គម)添加到堆栈中。

图 11:步骤 2——将第一个根节点添加到堆栈中。图片作者。

****第三步:获取堆栈顶部的节点(ស្គម).之后,我们发现 dist(ស្គម,កាក) = 4 > N。因此,我们没有将它添加到匹配列表中。然后看看它的孩子“ស្អាត”和“កាល".”对于每个孩子,如果 dist(ស្គម,កាក) - 2 ≤ dist(孩子,ស្គម) ≤ dist(ស្គម,កាក) + 2,那么我们把它加到堆栈中。在这种情况下,“ស្អាត”和“កាល”都被添加到堆栈中,因为 2 ≤ dist(ស្អាត,ស្គម) = 3 ≤ 6,2 ≤ dist(កាល,ស្គម) = 4 ≤ 6。

图 12:步骤 3-遍历树 1。图片作者。

****第四步:获取栈顶节点(កាល).然后,dist(កាល,កាក) = 1 ≤ N。所以我们把它添加到匹配列表中。我们再次找到可以添加到堆栈中的“កាល”的孩子。这次只能添加“ក្បាល”,因为-1 ≤ dist(ក្បាល,កាល) = 2 ≤ 3。

图 13:步骤 3-遍历树 2。图片作者。

步骤 5: 获取栈顶节点(ក្បាល).然后,dist(ក្បាល,កាក) = 3 > N。因此,我们没有将它添加到匹配列表中。由于“ក្បាល”没有任何子节点,因此我们不再向堆栈中添加任何节点。

图 14:步骤 3-遍历树 3。图片作者。

****第五步:获取栈顶节点(ស្អាត).然后,dist(ស្អាត,កាក) = 4 > N。因此,我们没有将它添加到匹配列表中。由于“ស្អាត”没有任何子节点,因此我们不再向堆栈中添加任何节点。

图 15:步骤 4-遍历树 3。图片作者。

步骤 7: 由于堆栈中不再有节点,算法结束。

图 16:搜索的结果。图片作者。

在上面的例子中,搜索遍历了所有节点。但是,当字典中的单词量很大时,这种方法会让我们跳过很多不必要的节点。

四。缺点

这种提供可接受结果的拼写检查和建议方法有一些缺点。

  1. 树构建过程可能需要很长时间才能完成,因此需要预先计算并保存到文件中。
  2. 建议的质量很大程度上取决于字典中的单词。如果这个单词不在字典中,它就不能被建议。
  3. 它没有任何先前的上下文单词,因此建议可能不适合整个句子结构。

动词 (verb 的缩写)代码

在了解了这些概念之后,我有机会编写一个利用它们的 python 包。我做的第一件事是准备一本高棉语词典。为了做到这一点,我从 SBBIC 下载了一个开源(麻省理工学院许可)高棉语单词数据集,并进行了一些预处理,最终得到了一个大约 4 万个独特单词的列表。然后,我使用下面的代码来构建 BK 树,将树保存到 XML 文件中,并使用它来提出拼写建议。此外,我还将它打包成一个 python 库,可以通过 pip 安装。

编辑距离

BK 树结构

BK 树搜索/拼写检查和建议

不及物动词结论

学习编辑距离和 BK 树让我实现了一个简单而有效的拼写检查和建议功能。我希望这篇文章能为你提供足够的理解,让你在其他编程语言中实现这些概念,并把它们扩展到不仅仅是拼写检查。快乐学习!

参考

背靠背瑞典。(2019).编辑两根弦之间的距离 Levenshtein 距离(LeetCode 上的“编辑距离”)【YouTube 频道】。YouTube。从 https://www.youtube.com/watch?v=MiqoA-yF-0M取回& t=5s

EpicFactFind。(2019).用 Levenshtein 距离和 Bk 树进行拼写检查【YouTube 频道】。YouTube。从 https://www.youtube.com/watch?v=oIsPB2pqq_8取回

茹拉夫斯基博士。最小编辑距离【PowerPoint 幻灯片】。斯坦福大学。从 https://web.stanford.edu/class/cs124/lec/med.pdf取回

北卡罗来纳州库马尔(2022 年)。 BK-Tree |简介&实现。极客 forGeeks。检索自https://www . geeks forgeeks . org/bk-tree-introduction-implementation/

米什拉,B . h .(未注明)。布尔夏德·凯勒树(BK 树)。 OpenGenus。从 https://iq.opengenus.org/burkhard-keller-tree/取回

SBBIC。(2017). SBBIC 高棉语词表【数据集】。SBBIC。从 https://sbbic.org/2010/07/29/sbbic-khmer-word-list/取回

辛格(2020)。BK 树:未探索的数据结构。走向数据科学。检索自https://medium . com/future-vision/bk-trees-unexplored-data-structure-EC 234 f 39052d

图沙尔·罗伊——让编码变得简单。(2015).最小编辑距离动态编程【YouTube 频道】。YouTube。从 https://www.youtube.com/watch?v=We3YDTzNXEk取回

为在线学习构建一个有状态的 ML 应用程序

原文:https://towardsdatascience.com/building-a-lil-stateful-ml-application-for-online-learning-66624d62afae

使用 River、Flask、Gunicorn 和多重处理构建在线学习应用程序

我今天看到的大多数实时 ML 系统都是无状态的——它们训练和服务一个固定的模型工件,直到被另一个在更新数据窗口上训练的工件完全取代。如果模型被频繁地重新训练,那么无状态的模型重新训练可能是昂贵的——而如果模型没有被足够地重新训练,那么模型漂移就会成为一个问题。

另一方面,s tateful 再培训和部署建立在最初的模型工件之上。我们不是执行大批量的训练任务,而是进行增量训练——更频繁地更新模型权重。这样做的优点是既节省成本,又避免了模型漂移。

无状态与有状态再训练——来自 Chip Huyen

在部署方面,这也带来了一系列独特的挑战。大多数 web 服务器+ docker 图像+模型工件的模型服务方法都假设工件和模型权重是静态的。以这种方式部署模型的新版本将意味着从像 S3 这样的 blob 存储中进行大量读取。出于对明智的系统设计的兴趣(也因为它很酷),我想建立一个小的部署,既能在某种规模水*上进行实时预测又能从地面实况中学习

建筑

对于这种方法,我在普遍接受的应用程序设计中找不到太多,我认为真正的设计在很大程度上取决于用例。然而,我发现将有状态 ML 应用程序与由数据库支持的有状态 web 应用程序进行比较是一个简单的类比。具体来说,我们希望一个数据库实例针对写入进行优化,另一个针对读取进行优化。

具有读取副本的有状态数据库支持的应用程序——按作者排序的图像

如果我们把 ML 模型看得更像一个有状态的、数据库支持的应用程序——因为它基本上允许读(预测)和写(用基本事实进行增量训练),会怎么样?

带有“读取副本”的有状态 ML 应用程序—由作者创建的图像

极度简化的架构。在模型服务器中,我们本质上想要一个高读取的对象/副本,以及另一个我们可以执行写入的对象/副本,这样我们的模型就可以随着时间的推移而学习。

模型

我想用一个“经典”的模型架构来演示这个架构(与某种 RL 相反,基于奖励的代理可能更适合)。因此,我需要使用一个库,在这个库中我可以很容易地对一个只有单一观察值的模型进行连续的训练。

一个同行最*把我放到了 River 上——一个在线机器学习的 Python 库。虽然它有点类似于更熟悉的 API,如 scikit-learn,但它允许我们轻松地将有用的方法用于我们的用例。

作者代码

上面将返回类似如下的内容:

ROCAUC: 95.04%

如果您在每次迭代期间打印度量,您可以实时观察模型性能的提高,并且模型从 50–50 随机猜测机器变成训练有素的分类器。

应用程序

此处查看代码 。对于散文,请继续阅读…

对于我的第一次尝试,我想为一个 flask 应用程序的单个实例实现这个功能,该应用程序运行在多个 gunicorn 工作进程中。这意味着如果我想更新一个变量,我需要在我的应用程序运行的每个进程中更新它。与使用 Flask 服务无状态模型部署不同,我们实际上关心 fork 后发生了什么(在我们的初始线程分裂成多个工作进程之后),因为我们需要更新变量。

感谢互联网的魔力,这已经相当可行了——JG 有一个关于这个主题的可爱的帖子以及一个 Github 回购,我能够派生并适应我的用例。我选择使用multiprocessing.Manager类在我的应用程序中跨进程共享数据。这允许我们在任何地方都可以访问的 Python 字典中存储 2 个河流模型(一个写一个读)和我们的度量。

基本应用程序本身很简单(源自 JG 的代码),用 5 个工人运行一个 flask+gunicorn 应用程序:

import argparse
import os
from multiprocessing import Manager

import gunicorn.app.base
from flask import Flask, request
from river import compose, linear_model, metrics, preprocessing

metric = metrics.ROCAUC()
model = compose.Pipeline(
    preprocessing.StandardScaler(), linear_model.LogisticRegression()
)
app = Flask(__name__)

...

def initialize():
    global data
    data = {}
    data["main_pid"] = os.getpid()
    manager_dict = Manager().dict()
    manager_dict["read_only_model"] = model
    manager_dict["writable_model"] = model
    manager_dict["metric"] = metric
    data["multiprocess_manager"] = manager_dict

class HttpServer(gunicorn.app.base.BaseApplication):
    def __init__(self, app, options=None):
        self.options = options or {}
        self.application = app
        super().__init__()

    def load_config(self):
        config = {
            key: value
            for key, value in self.options.items()
            if key in self.cfg.settings and value is not None
        }
        for key, value in config.items():
            self.cfg.set(key.lower(), value)

    def load(self):
        return self.application

if __name__ == "__main__":
    global data
    parser = argparse.ArgumentParser()
    parser.add_argument("--num-workers", type=int, default=5)
    parser.add_argument("--port", type=str, default="8080")
    args = parser.parse_args()
    options = {
        "bind": "%s:%s" % ("0.0.0.0", args.port),
        "workers": args.num_workers,
    }
    initialize()
    HttpServer(app, options).run()

这里最关键的是在initialize()函数中,我们定义了一个全局变量data——我们在其中存储了我们的multiprocessing.Manager对象。在该对象中,我们存储了一个可以更新的模型,一个假设不可变的“只读”模型,以及我们的度量。

之后,我们只需要添加所需的预测和模型更新路线!使用非常熟悉的 flask 语法,我们可以定义执行所需操作的端点。

@app.route("/predict", methods=["POST"])
def predict():
    json_request = request.json
    x = json_request["x"]
    return str(data["multiprocess_manager"]["model"].predict_one(x)), 200

@app.route("/update_model", methods=["PUT"])
def update_model():
    json_request = request.json
    x, y = json_request["x"], json_request["y"]
    model = data["multiprocess_manager"]["writable_model"]
    y_pred = model.predict_proba_one(x)
    model.learn_one(x, y)

    metric = data["multiprocess_manager"]["metric"]
    metric.update(y, y_pred)

    data["multiprocess_manager"]["metric"] = metric
    data["multiprocess_manager"]["writable_model"] = model
    data["multiprocess_manager"]["read_only_model"] = model
    return str(data["multiprocess_manager"]["metric"]), 200

/predict端点是一个 post 请求,它只获得预测。然而,/update_model端点将地面实况作为请求并按顺序:

  1. 获取给定观察的预测概率
  2. 用新的观察更新可写模型
  3. 使用预测和基本事实更新指标
  4. 替换我们的多处理管理器中的度量、可写模型和只读模型的值

完整代码可以参考 Github 库。如果您想运行它,请使用自述文件中的docker命令。应用程序启动看起来会像这样:

[2022-12-21 13:44:07 +0000] [8] [INFO] Starting gunicorn 20.1.0
[2022-12-21 13:44:07 +0000] [8] [INFO] Listening at: http://0.0.0.0:8080 (8)
[2022-12-21 13:44:07 +0000] [8] [INFO] Using worker: sync
[2022-12-21 13:44:07 +0000] [27] [INFO] Booting worker with pid: 27
[2022-12-21 13:44:07 +0000] [28] [INFO] Booting worker with pid: 28
[2022-12-21 13:44:07 +0000] [29] [INFO] Booting worker with pid: 29
[2022-12-21 13:44:07 +0000] [30] [INFO] Booting worker with pid: 30
[2022-12-21 13:44:07 +0000] [31] [INFO] Booting worker with pid: 31

为了确认它的工作,我们需要确定我们的模型确实在学习,并且这种学习在所有 gunicorn 工人中是持续的。为此,我们可以运行 repo 中的send_requests.py脚本。这将向/update_model端点发送单个示例,以便模型增量学习,并在每次传递时返回更新的度量。

当您这样做时,您将看到我的可怕的格式化的字节串响应被打印到终端,并且观看模型实时学习!

...
b'ROCAUC: 81.90%'
b'ROCAUC: 82.44%'
b'ROCAUC: 82.92%'
b'ROCAUC: 83.41%'
b'ROCAUC: 83.86%'
b'ROCAUC: 84.29%'
b'ROCAUC: 84.72%'
b'ROCAUC: 85.11%'
b'ROCAUC: 85.52%'
b'ROCAUC: 85.90%'
...

现在,我们还可以发送单个预测请求来对给定网页是否为网络钓鱼网页进行分类:

curl -X POST -H 'Content-Type: application/json' \
  localhost:8080/predict -d \
  '{"x": {"empty_server_form_handler": 1.0, "popup_window": 0.0, "https": 1.0, "request_from_other_domain": 0.0, "anchor_from_other_domain": 1.0, "is_popular": 0.0,"long_url": 0.0, "age_of_domain": 1, "ip_in_url": 0}}'

False

😱

未来的工作

好的,所以当我开始总结这个的时候,我开始有额外的想法和问题…

这个会缩放吗?

没有可能。如果我们要同时服务于从多个应用程序运行时更新我们的模型的,我们可能想要部署类似 Redis 的东西来存储我们的模型(或者甚至用作实时参数服务器),而不是将我们所有的模型和度量存储在多处理管理器中。如果我将它部署到 Kubernetes,我可能会将培训/服务部署完全分离到单独的应用程序中,对 GCS 进行频繁的模型备份,在更新后添加检查点和测试,等等……但我现在不会这样做。

替代方法?

这么多。理论上,即使我们执行有状态的再训练,我们仍然可以有无状态的部署(部署将会用新的模型工件更频繁地更新)。这将减少人们重用现有模型部署的范例,而不会增加有状态服务的复杂性。尽管如此,将分布式离线培训技术(如联合学习或使用参数服务器)应用到在线领域还是很酷的,对吗?

这有必要吗?

取决于我想象的用例。对于学习边缘设备和案例来说,如果我们(1)有现成的基础知识,并且(2)最新的模型很重要,那就太好了。然而,在我们不知道或不能快速接收地面真相的情况下…可能不值得复杂和开销。没有什么比将一个陈旧的模型工件投入生产,然后让它坐以待毙更好的了。

不管怎样,这很有趣,我希望你喜欢它!期待更多。

来自斯凯岛的愚蠢美丽的照片用作封面照片——图片由作者提供

感谢 JG 的代码,感谢 Nick Lowry 对 River 的提醒,感谢 Cole Ingraham 的一些深思熟虑的来回帮助我塑造未来的工作,感谢 Chip Huyen 一贯的一针见血!

使用 Flask 构建机器学习 Web 应用程序

原文:https://towardsdatascience.com/building-a-machine-learning-web-application-using-flask-29fa9ea11dac

使用 Python 和 Flask 实现机器学习 API 的简单教程

作者图片

如果你是数据科学家或者数据科学专业的学生,你应该知道机器学习是如何工作的。您知道算法的细节,使用哪个库,并执行诊断。假设在一个商业环境中,你手里有一个完美的模型,结果很优秀,一切都很理想。然后呢?建议我们将代码交给相关的涉众,如果他们想看到结果,请他们运行代码,这确实很诱人。但这不是商业环境的运作方式。这个问题的一个解决方案是创建一个机器学习 web 应用程序。有了这个解决方案,利益相关者就可以通过一个网站访问和运行你的机器学习模型,而不是一些他们看不懂的代码。本教程将向您展示如何使用 Python 为机器学习模型、Flask 为后端引擎、HTML 为前端创建机器学习 web 应用程序。

顺便说一下,我正在 Mac 电脑上运行本教程。据我所知,如果你使用的是其他操作系统,教程的任何部分都应该没有区别。但是如果有,请告诉我。

好吧,我们开始吧。

机器学习模型

在这一节中,我将把 Python 文件命名为model.py。出于本教程的考虑,我将使用以下假数据构建一个简单的二进制分类器。

给定身高和体重,我使用高斯朴素贝叶斯分类器来预测物种。如果你注意到,我没有费心做训练测试分割,因为在本教程中,我只需要分类器。为了开发一个好的机器学习模型,请不要跳过训练-测试的分离。

接下来,我们将把这个机器学习模型扔到一个泡菜里。在 Python 中,pickle 是指将一个变量存储到一个.pkl文件中。为了提取模型,将下面的代码添加到model.py中。稍后我会解释为什么我们应该酸洗我们的模型。

您应该注意到您的目录现在应该存储文件clf.pkl

当前目录

请记住,您可以在这个过程中使用您的模型。请记住,对于本教程,我使用的是sklearn模型。使用其他机器学习库可能与本教程不兼容。

前端

在这一节中,我将 HTML 文件命名为website.html

这是一个简单的 HTML,由两个输入栏组成,一个输入身高,一个输入体重。仔细查看这段代码,您可能会注意到第 16 行有些奇怪。的确{{ output }}不是 HTML 语法,你是对的。看看如果我们照原样运行上面的 HTML 会发生什么。

HTML 输出

这个{{ }}语法属于 Flask,我们将使用的 web 框架。稍后用 Flask 运行这个 HTML,会产生不同的输出。

显然,这种设计不适合部署,因为它太普通了。您可以在本教程中使用自己的设计,最好是适合您的模型目的和您的组织的设计。如果你想使用你自己的设计,这里有一些要注意的事情

  • 确保您有一个带有method = "POST"的表单。
  • 记住输入栏的名称。为了我们之间的一致性,我建议使用name = "height"name = "weight"作为输入。

请注意,您必须在目录中创建一个名为templates的文件夹,并将 HTML 文件放在那里以供 Flask 应用程序使用。否则,你不会得到你想要的结果。

当前目录

同样,文件website.html位于templates文件夹中。

后端

在这一节中,我将把 Python 文件命名为app.py。如果你是初学者,使用 Flask 有点棘手。我会演示给你看怎么用,但不会泄露每行代码的推理过程。

记录导入的库。这里,第 7 行是 Flask 应用程序的声明,第 13 行只是运行应用程序。第 10 行是我们放置主要函数的地方。

我会解释一些函数和方法,所以一旦我给出了主要函数,就更容易理解了。

main 函数应该只在我们收到来自 HTML 的 POST 请求时运行。我们可以通过看条件request.method == "POST"来强加这个条件。

如果我们收到一个 POST 请求,我们希望从输入中获取值。我们可以通过代码request.form.get("name")来实现。其中"name"是 HTML 中输入栏的名称。对于我们的例子,输入栏名称是name = "height"name = "weight"

为了调用我们之前腌制的模型,我们使用函数joblib.load("clf.pkl")

为了使用 Flask 语法呈现 HTML 文件,我们使用函数render_template("file.HTML")

让我们把这些拼图拼在一起。

简单重申一下我的解释,

  • 第 5 行是我们从 HTML 接收 POST 请求的地方
  • 8 号线在调用我们之前训练腌制的模型。
  • 第 11 & 12 行从 HTML 获取输入。
  • 第 15 行将这些输入放入 Pandas 数据帧。
  • 第 18 行预测给定输入的输出。
  • 第 21 行,如果没有 POST 请求,HTML 中的输出字段将保持为空。
  • 第 23 行显示了带有 Flask 语法的 HTML。我将很快解释这些论点。

使用 Flask,您可以发送一个 Python 变量以出现在 HTML 文件中。该变量可以是任何类别,甚至是数据框。方法是在render_template()函数中指定它。

在 Python 中

render_template("file.HTML", name_in_HTML = name_in_Python)

file.HTML

{{ name_in_HTML }}

当然,name_in_Python只是你的 Python 变量,name_in_HTML是你想在 HTML 中使用的名字。

因此,在我们的主函数中,我们将机器学习预测的结果发送到 HTML。在实践中,您可以出于多种原因使用它,例如,

  • 如果您正在使用会话,请留言“你好,姓名”。
  • 使用datetime库显示今天的日期。
  • 打印所有输入,以确保用户的确定性。

为了实现我提到的一切,render_template()函数可以接受如下多个参数,

render_template("file.HTML", 
                 name_in_HTML_1 = name_in_Python_1,
                 name_in_HTML_2 = name_in_Python_2,
                 name_in_HTML_3 = name_in_Python_3)

您的目录现在应该看起来像这样

当前目录

这几乎是我们创建 web 应用程序所需要做的全部工作。我希望你能为你的目的调整这个。

在教授本教程时,通常会出现的一个问题是,“我们为什么要腌制模型?”。我们确实可以在每次用户提交 POST 请求时训练模型。事实上,对于我们这里的例子来说,运行时的差异是微不足道的。然而,存在具有非常长的训练时间复杂度的机器学习算法。在这种情况下,对每个 POST 请求训练模型对用户体验没有好处,因为用户将不得不等待(对于大数据和严格的模型,有时长达数小时)。无论您使用哪种算法,您都应该忽略模型的复杂性,并将其作为一种标准实践。

运行应用程序

要运行我们刚刚创建的应用程序,请转到您的终端或命令提示符。您必须将目录设置为您的项目目录。在我的例子中,文件夹名是Machine-Learning-Web-App。所以我应该在终端中写的命令是,

cd path/to/my/directory/Machine-Learning-Web-App

如果您不知道目录的路径,请运行model.py中的代码

import os
print(os.path.dirname(os.path.abspath(__file__)))

这段代码的输出应该是path/to/your/directory/Machine-Learning-Web-App或您正在使用的任何文件夹名称。因此,将输出复制到您的终端。别忘了加上cd prior。

然后,您应该使用以下命令运行您的 Python 文件app.py

python app.py

如果您做的一切都正确,您应该在终端中看到这个输出,

复制输出中的http链接,并在浏览器中运行。

烧瓶产量

注意按钮下方不再有{{ output }}文本。这是因为我们现在使用 Flask 渲染 HTML。

在输入栏中输入一些数字,然后按 run 来测试您的机器学习模型是否已经成功实现。

最终应用

我们可以看到,对于条目height = 90, weight = 50,我的机器学习模型预测这个实例是一只狗。对于条目height = 70, weight = 40,模型预测这个实例是一只猫。

这就是一切。web 应用程序现在可以部署了。

恭喜

我们已经成功地创建了一个可以部署的机器学习 web 应用程序。请为您的应用程序创建一个更好的用户界面,这样用户就不必与单调的黑白网站交互。有一些方法可以让用户体验更加无缝。在我之前的工作中,我使用 AJAX 自动填充输入栏。这是你可以进一步探索的,或者与你的 UI/UX 团队合作以获得更好的体验。你可以在 GitHub 上找到完整的代码和数据集。

如果你想在不需要 HTML 和 CSS 的情况下创建 web 应用的另一种选择,请查看我关于使用 Streamlit 构建机器学习 web 应用的文章。

如果你喜欢这篇文章,请考虑鼓掌并关注。请在评论中分享你的反馈和想法。感谢您的阅读!

使用 Streamlit 构建机器学习 Web 应用程序

原文:https://towardsdatascience.com/building-a-machine-learning-web-application-using-streamlit-8c3d942f7b35

关于构建 Streamlit 机器学习应用程序的简单教程

作者图片

如果你是数据科学家或者数据科学专业的学生,你应该知道机器学习是如何工作的。您知道算法的细节,使用哪个库,并执行诊断。假设在一个商业环境中,你手里有一个完美的模型,结果很优秀,一切都很理想。然后呢?建议我们将代码交给相关的涉众,如果他们想看到结果,请他们运行代码,这确实很诱人。但这不是商业环境的运作方式。这个问题的一个解决方案是创建一个机器学习 web 应用程序。有了这个解决方案,利益相关者就可以通过一个网站访问和运行你的机器学习模型,而不是一些他们看不懂的代码。

在我之前的教程中,我演示了使用 Flask 创建 ML 应用程序,Flask 是 Python 中常用的 web 框架。

不幸的是,我意识到许多数据科学家缺乏必要的网页设计和工程知识来完成这样的项目。本文将介绍一种更友好的替代方法,使用 Streamlit 创建 ML 应用程序。

Streamlit 是一个 app 框架,不需要 HTML、CSS 等网页设计工具的知识(不像 Flask)。如果你完成了 Flask 教程,你会惊讶于创建一个 Streamlit 应用程序是多么简单。当然,Streamlit 也有一些限制,比如你不能控制域路由,你的设计选择也是有限的。但是我相信,作为一名数据科学家,你会发现这些限制对于你的目的来说是微不足道的。

机器学习模型

本节中的 Python 文件将被命名为model.py

在构建 ML 应用程序之前,我们需要一个机器学习模型。我们将使用这个简单的数据来创建一个分类模型,

简单的数据带来简单的模型。我确信我不必向像你这样的机器学习专家解释下面的代码。

这是一个简单的逻辑回归模型来预测二元结果。是的,我没有做训练测试分割,也没有预测任何事情。因为对于本教程,我只需要最终的分类器。如果你跟着我,确保clf是你想要使用的型号。

下一步是腌制模型。酸洗仅仅意味着将模型存储到一个.pkl文件中。这样做的目的是将训练过程和用户体验分开(即用户不必忍受模型训练时间)。为此,将以下代码添加到model.py

检查文件clf.pkl现在是否在你的目录中。

应用程序

本节中的 Python 文件将被命名为app.py

Streamlit 让生活变得简单,因为它结合了应用程序的后端和前端。我将给出一个 Streamlit 如何取代 HTML 的例子。

在 HTML 中,要制作一个数字输入栏,您必须编写,

<input type="number", name="", placeholder=""/>

获取该输入的值可以通过发送到后端脚本的 POST 请求来完成。在 Streamlit 中,这可以在一个文件中通过简单地编写x = st.number_input("placeholder")来完成,输入值直接存储在变量x中。

我会给出我是如何创建这个应用程序的。这应该是不言自明的。

让我简单解释一下每一行。

  • 第 6 行打印出标题。这相当于 HTML 中的<h1><h2>标签。
  • 第 9 和 12 行是输入栏。输入值存储在变量heightweight中。这相当于 HTML 中的<input type = "number">
  • 第 15 行是下拉输入。该选择将被记录在变量eyes中。该下拉菜单的选项为BlueBrown。这相当于 HTML 中下拉菜单的<select>标签和选项的<option>标签。
  • 第 18 行是一个按钮。我把它放在一个 if 语句中,因为如果按下它会简单地返回True。这相当于 HTML 中的<button>标签。
  • 21 号线正在调用我们之前训练和腌制的模型。
  • 第 24 行将输入存储到数据帧中,随后是第 26 行中的简单处理。这个过程正是我们在model.py中所做的。
  • 第 29 行使用模型预测给定输入的输出。
  • 第 32 行打印输出。这相当于 HTML 中的<p>标签。注意print()不会出现在 Streamlit 应用上。

运行app.py会给我们这个页面。

网页结果

此应用程序现已准备就绪。您可以通过使用其他 streamlit 对象来试验设计。查看文档[https://docs.streamlit.io/](https://docs.streamlit.io/)并在左侧栏中查找章节API reference了解更多可能性。

运行应用程序

要运行我们刚刚创建的应用程序,请转到您的终端或命令提示符。您必须将目录设置为您的项目目录。在我的例子中,文件夹名是Streamlit-ML-App。所以我应该在终端中写的命令是,

cd path/to/my/directory/Streamlit-ML-App

如果您不知道目录的路径,请在model.py中运行这段代码

import os
print(os.path.dirname(os.path.abspath(__file__)))

这段代码的输出应该是path/to/your/directory/Streamlit-ML-App或者你正在使用的任何文件夹名。因此,将输出复制到您的终端。别忘了加上cd prior。

然后你应该使用下面的命令运行你的 Python 文件app.py

streamlit run app.py

如果您做的一切都正确,您应该在终端中看到这个输出,

如果本地主机页面没有自动打开,请复制其中一个 URL 并将其粘贴到您的浏览器中。

放入一些输入,测试你的机器学习模型是否已经成功实现。

最终应用

我们可以看到,对于条目height = 90, weight = 40, eyes = 'Brown',我的机器学习模型预测这个实例是一只狗。对于条目,height = 70, weight = 30, eyes = 'Blue'模型预测这个实例是一只猫。

恭喜

我们已经成功创建了一个可供部署的 Streamlit 机器学习应用程序。我希望您能清楚地看到,与典型的 web 框架相比,这是多么简单。我的应用程序一点也不好看,因为它非常简单。请进一步研究 Streamlit 文档,使您的应用程序更加美观。你可以在 GitHub 上找到我的完整代码和数据集。感谢您的阅读!

使用 R 建立一个简单的营销组合模型(MMM)并进行预测

原文:https://towardsdatascience.com/building-a-marketing-mix-model-in-r-3a7004d21239

我们回顾了营销组合建模(MMM)多点接触归因(MTA) 之间的区别,然后我们继续在 R

Unsplash 上由 LekoArts 拍摄的照片

TLDR

在之前的一篇文章中,我们讨论了如何在分析或建模之前使用 R 清理和准备杂乱的营销数据。在这里,我们将深入研究营销组合模型。具体来说,我们将了解:

  • 营销组合建模概述
  • 营销组合模型多接触归因模型的区别。两者看似相似,但应用于非常不同的场景。也可以一起用!
  • 使用 R 建立一个基本的营销组合模型
  • 定义股票
  • 解释营销组合模型的结果
  • 做出预测和建议

就像在上一篇文章中一样,我用一个非常基本的例子以一种容易理解的方式写了这篇文章。R 代码已经被广泛地注释并一步一步地介绍,以帮助新的 R 用户尝试这种技术。

什么是营销组合模型,为什么有用

营销组合建模(MMM)是一种计量经济学方法,用于估计广告渠道对关键转化结果(如销售、客户激活、销售线索等)的影响。一个品牌可以使用不同类型的营销策略来提高对其产品的认知度并推动销售。例如:

  • 使用电子邮件营销,用首次购买的折扣代码吸引新客户
  • 通过付费媒体活动,如点击付费或 Youtube 上的数字显示广告
  • 通过脸书这样的社交媒体
  • 或者通过离线的传统户外媒体,如电视、印刷品或广播

这就是为什么它被称为营销“组合”,因为在任何给定的时间都有一个以上的渠道进行营销活动。不同渠道的营销预算会有所不同,每个渠道推动认知和销售的能力也会有所不同。

例如:作为一个渠道,电视黄金时段的广告很可能比展示广告贵得多。虽然社交媒体*台非常适合提高知名度和发展社区,但它并不总是被视为转化渠道,不像点击付费(PPC)那样直接,用户点击 PPC 广告的同一天就可以实现销售。显然,这些例子因品牌、行业或营销产品而异。

通过执行营销组合建模,品牌可以确定特定营销活动对销售等关键结果的影响程度。它可以用于估计给定渠道的投资回报,甚至可以用作优化支出和预测/基准未来绩效的规划工具。

营销组合建模(MMM)和多点接触归因(MTA)之间有什么区别

这些建模技术听起来相似,但它们适用于不同的场景。

营销组合建模
尽可能简单地解释一下:营销组合建模数据集通常包括预测因素,如每个渠道的支出,以及给定期间的销售或收入等结果变量。我们正试图根据我们关心的结果(在这种情况下是销售额,或总查询量,无论哪个都是品牌的关键转化 KPI)来建立每个渠道的支出之间的关系模型。这样,我们就能发现每个渠道对销售的影响,并利用这种关系来预测未来的销售额。

冒着过于简化的风险:它有助于回答“花费 x ,获得 y 类型的问题。它可以用来估计收益递减,了解投资回报率,并作为预算规划的一部分。

因此,在营销组合建模中普遍应用多元回归等回归方法也就不足为奇了。

同样值得注意的是,很难衡量电视、广播和印刷品等离线传统 OOH 媒体的表现(不像在线广告,我们可以获得点击量、点击率和印象等指标)。所以在这种情况下使用 spend。

多点接触归因建模 在多点接触归因中,我们承认客户旅程是复杂的,客户可能会在其旅程中遇到不止一个(甚至所有)我们的营销渠道活动。

客户的购买途径可能始于在网上看到展示广告→接触到社交媒体活动→后来在谷歌中进行关键词搜索→然后在登陆网站之前点击 PPC 广告,最终完成购买。

与 MMM 不同,在 MMM 中,我们倾向于查看支出的顶层视图,在多接触归因中,我们关心这些细微的接触点,因为它们都在促成最终销售中发挥作用。

我们根据这些渠道的接触点为其分配权重,而不是花费,以确定每个渠道对销售的贡献/参与。

根据我们对每个接触点的评估,权重会有所不同。有几种归因模型可用于确定权重。比较常见的有:

最后点击属性:这将 100%的权重分配给最后点击的频道。所以在上面的例子中,PPC 将得到所有的信用。在此之前,任何频道都不会获得任何积分。这是大多数分析*台使用的默认模型。这是最容易解释的加权,因为它只说“”这个渠道是用户在购买前遇到的最后一个渠道,因此它是最重要的,因为它是推动最终决策的渠道。“Google Analytics 使用这种方法已经很多年了,直到最*数据驱动归因在 Google Analytics 4 中广泛使用。

首次点击归属:这与上次点击归属相反。这里,我们将 100%的权重分配给第一个通道。使用上面的例子,这将是显示广告。这种方法的论据通常是"如果显示器不是第一个通道,后续的接触点无论如何都不会发生,所以第一个通道是最重要的。

第一次和最后一次点击的问题是,他们忽略了其他渠道发挥的贡献。他们太简单了。

然后我们有这样的模型:

时间衰减:根据转换前每个通道的新*度分配权重。转换前的最后一个通道(PPC)将获得最大的权重。最小权重被分配给第一通道。

基于位置的:第一个和最后一个通道各获得 40%的权重,剩余的 20%*均分配给中间的通道。

数据驱动归因(DDA) :这可能是最好的方法(与第一、最后、时间衰减和基于位置的方法相比),因为它不是使用预先确定的规则来分配权重,而是在给定接触点的情况下,通过基于转换概率的算法客观地分配权重。像马尔可夫链这样的方法,使用 Shapley 值的博弈论方法在像 Google Search Ads 360 这样的媒体*台中使用,甚至像 Random Forest 这样的集成方法都可以在 DDA 中使用。

在大多数分析*台,如 Google Analytics 中,将这些模型应用于多点触摸归因非常简单,只需点击并选择您想要的模型,然后在您的报告中使用即可。

但是请注意,对于多点触摸属性,频道需要能够报告点击、点击率、印象等指标,以便可以有效地测量触摸点及其序列。这意味着多点触摸属性非常适合数字渠道。衡量收音机的“点击率”这样的指标几乎是不可能的。

我们可以一起使用吗?
是的 MMM 和 MTA 可以用来互补。MMM 可用于在线和线下营销活动支出效果的顶层视图,然后 MTA 可用于深入了解在线渠道,以获得更深入的见解。

使用 R 建立一个基本的营销组合模型

这篇文章的范围是提供一个简单的例子,在 r。

👉重要!你需要确保你的营销数据集是干净的,包括每期的渠道支出(最好是每周)和每周的销售额。关于如何在 R 中执行建模之前清理和准备营销数据的指南,请看我以前的文章这里

在本例中,我们将使用已经在 r 中的 datarium 包中提供的示例营销数据集。首先安装它,然后让我们加载它并查看一下:

这是一个有 200 个观察值(行)的数据框架,有 4 个变量(Youtube、脸书、报纸和销售),都是数字,以千计的。支出以 s 为单位,销售额以售出的单位数为单位。出于这个练习的目的,我们假设这些是过去 200 周脸书、Youtube 和报纸的营销支出。

图片作者自己的

检查相关性

使用图表。PerformanceAnalytics 包中的 Correlation() 函数,让我们在一个漂亮的图表中可视化变量之间的相关性:

图片作者自己的

Youtube 与销售呈正相关——显著性高,相关性强(0.78)。脸书也与销售有积极的关系,这是显着的,但关系是中等的(0.58)。报纸,与销售有积极的关系,虽然这是相当小的(0.23)。

定义存货

在营销组合建模中,我们估计随着时间的推移,广告的效果会变得不那么有影响力。Adstock 理论(布罗德本特,1979 年)认为,当广告最*被观看时,广告产品的知名度最高。当广告曝光减少时,认知度最终会下降。

类似于前面讨论的时间衰减多点触摸属性,adstock 被表示为衰减模型,其值在 0 和 1 之间,0 表示最低意识,1 表示 100%意识。注意典型的 adstock 变换假设了一个无限的衰减,Gabriel Mohanna 在他的文章中清楚地解释了为什么这并不总是现实的。

要计算信道的 adstock,您需要 adstock rate。大多数 MMM 建模由代理方完成,可以访问 adstock 基准,这些基准可以简单地应用。电视上有影响力的创意广告更令人难忘,因此与在线 PPC 广告相比(例如 0.1 甚至 0!)通常只是文本,本质上是静态的。

在我们的 youtube、脸书和报纸的示例数据集中,我们将假设脸书的活动通常是简单的文本帖子,Youtube 上的广告是动画显示横幅,报纸上的广告是彩色的半页创意接管。因此,脸书将拥有最低的 adstock 率(0.1),我们假设报纸收购的 adstock 率为 0.25。

应用 Gabriel 对 adstock 变换的建议,我们得到下面的结果。

👉提示:别忘了卸载 dyplr 和 tidyr,这样它们就不会弄乱下面的 filter() 函数。或者,明确指定 stats::filter(),以便避免 dplyr 版本的。在 dplyr::filter()中, x 需要作为 tible/data frame。

图片作者自己的

图片作者自己的

图片作者自己的

使用多元回归建立营销组合模型,检查多重共线性和异方差

以销售额为因变量,我们现在可以用 Youtube、脸书和报纸的广告库存值建立一个多元回归 MMM 模型。

我们将把它放入 lm() 函数中。然后我们使用 mctest 包中的 imcdiag() 函数应用 VIFs(方差膨胀因子)来检查多重共线性,最后我们执行一个测试来检查异方差性。

多重共线性和异方差问题

  • 👎多重共线性是有问题的,特别是在回归中,因为它会在系数估计中产生大量的方差,并且它们对模型中的微小变化非常敏感,从而使模型不稳定。
  • 👎异方差也是有问题的,尤其是在线性回归中,因为这种回归假设模型中的随机变量在最佳拟合线周围具有相等的方差,换句话说,残差中不应有异方差。相对于因变量的拟合值,残差的方差不应有任何明显的模式。
  • 👎异方差的存在是估计值不可信的标志。该模型可能会生成不准确的 t- 统计数据和 p 值,并可能导致非常怪异的预测。

简而言之,多重共线性和异方差的存在会使我们的模型不准确和不可用。

在之前应用的 VIF 方法中没有检测到多重共线性——很好🤗!

图片作者自己的

异方差可以通过“眼球运动”来检查👀情节:

图片作者自己的

我们对左边的两张图最感兴趣。如果完全没有异方差,红线将是完全*坦的,在 x 轴上应该有随机和相等的分布点。在我们的残差和拟合图中,如果你仔细观察,在 0 处有一条虚线,表示残值= 0。我们的红线稍有弯曲,但不会偏离虚线太多。残差中似乎也没有明显的模式。这很好。
对于比例-位置图也是如此,我们希望红线尽可能*坦,散点图中的点没有明显的图案。似乎是这样的。

更客观的方法是使用来自 lmtest 包的 bptest() 和来自 car 包的 ncvtest() 来使用 Breusch Pagan 测试。两种测试都检查异方差性。

H 表示模型中的方差是同方差的。

图片作者自己的

由于我们的p-值来自布鲁希帕甘和 NCV 测试,返回的值高于 0.05 的显著性水*,我们不能拒绝零假设(耶!)🙌。因此,我们可以说在我们的模型中不存在异方差的主要问题:

解释我们第一个模型的总结结果

图片作者自己的

我们来解读一下模型的总结结果:

观察系数估计值,回想一下数字都在 1000 左右** — 是的,我也不得不提醒自己!🤦‍♂(渠道预算以 s 为单位,销售额以售出的数量为单位)**

  • 在 Youtube 上每花 1000 英镑,我们可以预计销售额会增加 45 (0.045*1000)单位。在脸书上每花 1000 英镑,我们可以预期销售会增加 188 (0.188 * 1000)个单位。每在报纸上花费 1000 英镑,我们可以预期 的销售额会减少6(0.006 * 1000)个单位,尽管这并不显著。
  • t 统计表明我们是否可以对我们的特征(渠道)和因变量(销售额)之间的关系有信心。Youtube 和脸书的高(> 1.96 ) t )统计值以及它们在统计上的显著性p-值意味着我们可以对它们系数的精确度有信心。对报纸来说不算多——但这多少有些道理,因为很难准确地将报纸广告的销售归因于此(除非创意中包含一个特殊的 url 或二维码,这有助于更好地收集数据)。
  • R 表示拟合度,即模型与数据的拟合程度。0.88 的 R 表示该模型可以用 88%的数据来解释。

还不错!😸让我们看看能否在下面的下一次迭代中做得更好。

将趋势和季节性纳入模型 我们可以将趋势和季节性作为附加特征添加到模型中。

请注意,我们的数据是每周一次的,因此频率被设置为 52(尽管这在闰年会很棘手)。我们还需要最少 2 个周期(52 x 2 = 104 周),我们的数据包含过去 200 周的观察结果,因此这应该足够了。

为了给 lm()添加一个基本的线性销售趋势,我们可以使用 seq_along() 来生成一个序列整数的向量:

basic_trend <- seq_along(sampledf$sales)

一个更好的方法是使用 tslm() 函数,它很大程度上只是 lm()的一个 timeseries 包装器,工作方式基本相同,只是它允许在模型中包含“趋势”和“季节”。我们只需要调用“趋势”和“季节”, tslm()就能够自动为我们生成这些内容。

一旦创建了时间序列,我们使用 decompose() 来绘制单个组件,如趋势、季节性等。这一点在下文中有所体现:

图片作者自己的

有趣的是,销售似乎有普遍下降的趋势。观察季节性垂直轴的较小范围,我们可以看到,与趋势等其他成分相比,每周的季节性影响相当小。

现在让我们来拟合我们的营销组合模型,这次使用 tslm()而不是 lm(),并包括趋势和季节性,看看我们是否可以改进该模型。

图片作者自己的

是的,这款车型略有改进!

Youtube 和脸书的系数估计值变化非常小,t-统计值仍然很高(超过 2),具有统计显著性p-值,这意味着我们可以对我们的推断保持信心(💡再次提醒我们自己数字在 1000 左右!!)也就是说,在 Youtube 上每花费 1000 英镑,我们可以预期销售额会增加 45 (0.045*1000)单位。在脸书上每花 1000 英镑,我们可以预期销售会增加 185 (0.185 * 1000)个单位。

请注意,tslm()使用了针对季节性的虚拟变量,第 1 周没有包括在内,这是因为第 1 周被用作与我们的季节性系数估计值进行比较的,这使我们可以这样解释:与第 1 周相比,第 2 周的销售额减少了 2,430 (2.431000)个单位。与第 1 周相比,第 3 周的销量减少了 3,840 (3.841000)台。诸如此类。

季节性的大多数系数估计没有得到足够大的 t 统计量(大于 2),p 值也不显著。只有第 23 周和第 27 周的 t 统计值高于 2,具有非常显著的 p 值,两者都表示与第 1 周相比,销售额下降

模型的拟合优度也略有提高,从以前模型的 0.88 提高到目前模型的 0.89。

其他注意事项

然而,总体而言,这方面的结果并不乐观。销售额总体上呈下降趋势。我们知道,Youtube 和脸书似乎带来了不错的回报,但报纸并非如此。也许下次我们可以试着减少报纸广告?

预测

让我们想象我们已经展示了这些早期的发现。客户意识到模型不是 100%完美的,这是一个持续改进的项目。
如果广告预算和渠道没有变化,并且我们假设下一阶段的趋势和季节性保持相似,使用我们的模型,我们预计表现如下:

图片作者自己的

但是,如果客户接受我们的建议,撤回报纸广告,并将其重新分配为 Youtube 和脸书的额外预算:40%分配给 Youtube,60%分配给脸书(这是在他们当前预算的基础上应用的,我们将假设他们的预算与以前相同),使用该模型,销售业绩预测如下:

图片作者自己的

将两张图表叠加在一起,似乎从报纸到 Youtube 和脸书的预算重新分配可能会提高销售业绩🤑🤑!

图片作者自己的

需要指出的另一点是,通过数字渠道,我们可以几乎实时或至少每天跟踪当前的运行率与预测,这意味着实验、广告测试、改变方向(如果我们需要),性能监控和优化可以无缝完成。

要从模型中获得拟合值,只需调用:

forecast_new_spends$fitted

更进一步

这里显然还有其他因素没有包括在内,这些因素也可能影响模型的有效性。例如,我们只回顾了 3 个渠道,我们是否错过了其他营销活动?我们没有包括其他变量,如天气或是否打折,也许我们还应该包括特定周是否有假期。考虑探索其他频率选项,如每季度或每月。

我们还可能希望考虑其他模型,并将其与我们在此使用的简单多元回归方法进行比较。

由于这篇文章已经很长了,而且时间不够,这一次我还不能涵盖训练和测试模型的过程。如果时间允许的话,也许我们可以在以后的文章中讨论这个问题以及与其他模型的比较。!🐱‍🏍

结局

感谢你读到这里,如果有我遗漏的地方,或者有更好的方法来解决我之前讨论的问题,请在评论中分享。一起学习真好:)

全 R 码

👉从我的 Github repo 这里获取完整的 R 代码。

参考

南布罗德本特,1979 年。电视广告起作用的一种方式,市场研究学会杂志,第 23 卷第 3 期。

这篇文章中使用的营销数据样本来自根据 GPL-2 通用公共许可证授权的 R 的 Datarium 包 https://www . rdocumentation . org/packages/Datarium/versions/0 . 1 . 0

r 是一个免费的开源统计软件https://www.r-project.org/

为新冠肺炎风险因素构建命名实体提取器

原文:https://towardsdatascience.com/building-a-named-entity-extractor-for-covid-19-risk-factors-cb9bf9022b5e

如何从大量数据中提取信息

照片由龙之介·菊野Unsplash 上拍摄

如何从成千上万的文档中提取包含在几个句子中的信息?你如何标注一个巨大的文本语料库?

在本教程中,我们训练并构建了一个命名实体识别器(NER ),使用最大的可用医疗数据集之一来检测新冠肺炎风险因素。

数据集:cord-19

cord-19 数据集汇集了超过 50 万篇关于新冠肺炎和其他冠状病毒相关疾病的研究论文,并由 AllenAI 研究所定期更新。本教程使用的数据遵循了与 github 存储库中显示的数据集相似的处理:https://github.com/allenai/cord19

我们的目标是找到医学论文中提到的导致严重 COVID 的主要危险因素。

我们如何继续注释这个?

使用像这样的大型语料库的一个问题是,信息只会出现在几十万个文档的几个段落中。这就像大海捞针。我们如何标注这样一个语料库?唯一的方法是将问题缩小到可能包含我们需要的信息的较小的文档子集。为此,我们可以使用正则表达式或其他类似的规则,首先搜索可能拥有我们需要的信息的候选对象。

为了探索和注释这个数据集,我们使用开源库 DataQA ,这是一个基于 Python 和 React 的库,用于数据探索和标记。这个 python 包包括 elasticsearch 文本搜索引擎,可以很容易地与 pip 一起安装。

我们首先为命名实体识别创建一个项目并上传数据。

在 DataQA 上为 cord-19 数据集创建一个新的 NER 项目。

在我们的文档被上传和索引后,我们需要缩小可能包含我们想要的信息的一些段落的范围。为了实现这一点,我们可以创建我们的第一个规则。规则如下:文档必须包含带有“风险因素”一词的句子,以及多个已知风险因素中的一个,如“年龄”、“肥胖”等。

缩小包含“风险因素”和多个已知风险因素之一的句子的文档范围的规则。

此规则将查找包含总结了迄今已知的所有新冠肺炎风险因素的句子的文档。这样的搜索对于主要关注关键词搜索的标准搜索引擎来说是不可能的。DataQA 还将使用规则预先标记所有已知的风险因素,使手动过程更加高效。

预先标记的示例:该规则自动提取风险因素,从而更快地标记文档。

我们包括了另一个规则,这是一个稍微不同的方式来表达同样的想法:文件必须包含一个句子,要么“风险”,然后是“住院/住院”(或以相反的顺序),然后是一个已知的风险因素。

一旦我们有了这些规则,我们就可以用规则中的前标签来注释文档,或者进行搜索。在搜索中,我们应用规则作为过滤器,并搜索 covid 的提及,因为有许多论文讨论了其他不相关条件的风险因素。

同时有效搜索和标记文本的例子。

这样做了一段时间后,我们得到了一个由 42 个文档组成的小标签集,我们用它来训练命名实体提取器。这是一个小的训练数据集,我们的目标是看看我们是否可以快速训练 NER 来检测更多的风险因素,而不需要做任何额外的手动标记。

用空间建造 NER

我们使用 Spacy 来基于我们刚刚注释的数据构建命名实体提取器。我们首先需要准备训练数据。

准备来自 DataQA 的标记数据以用作训练数据。

然后,训练我们的 NER 的代码如下。我们使用一个 Spacy 管道,增加一个句子分割器来检测句子边界,并用一个新的只有“风险因素”作为实体的 NER 步骤来代替默认的步骤。然后以小批量训练管道,以避免过度配合。

发现新提及的风险因素

一旦我们训练了我们的 NER,我们就可以在一组未标记的文档样本上运行它,看看我们是否能找到一组在手动标记过程中没有识别出的新的风险因素。这里值得一提的是,我们正在对上述规则挑选出的文档运行我们的模型,以避免得到太多的误报。

以下是一些由 NER 确定的风险因素的例子,这些因素没有被人工标记:神经肌肉疾病、种族/民族、孕前肥胖等。

总而言之,将我们所有的发现放在一起,文献中提到的严重 covid 的前 20 个风险因素按样本中提到它们的论文数量排列如下:

[('diabetes', 87),
 ('hypertension', 84),
 ('age', 79),
 ('obesity', 67),
 ('smoking', 36),
 ('sex', 27),
 ('cardiovascular disease', 27),
 ('gender', 27),
 ('older age', 20),
 ('copd', 19),
 ('asthma', 18),
 ('male sex', 18),
 ('chronic kidney disease', 16),
 ('chronic obstructive pulmonary disease', 14),
 ('bmi', 13),
 ('pneumonia', 13),
 ('therapy', 12),
 ('cardiovascular', 11),
 ('cancer', 11),
 ('diabetes mellitus', 11)]

一些风险因素指向相同的潜在实体(例如,“年龄”和“老年”是相同的风险因素),并且“治疗”是不属于该列表的错误位置。然而,这个列表让我们对文献中讨论的关键因素有了一些了解。

长 covid 呢?

鉴于数据的缺乏,很少有论文讨论长 COVID 的风险因素。因此,搜索这些提及具有挑战性,因为信息很少。

我们使用与之前相同的方法来筛选可能的候选人。这一次,论文的数量少了很多,所以我们可以直接获得信息。

经过快速搜索,我们发现了大量引用女性、年龄增长、身体质量指数增加以及第一周报告的症状数量的论文,作为新冠肺炎患者是否会发展为长期 COVID 的预测特征。

如果您想了解更多关于 DataQA 的数据探索,请前往我们的 资源库 获取更多教程和示例。我们还提供该产品的企业版,可以直接处理 pdf 文件(查看我们的 网站 )。别忘了留下一颗星来支持这个开源项目:-)

如何从零开始构建神经网络

原文:https://towardsdatascience.com/building-a-neural-network-from-scratch-8f03c5c50adc

没有框架,只有 Python

布雷特·乔丹在 Unsplash 上的照片

“我不能创造的东西,我不理解”(理查德·费曼)

这句话是在理查德·费曼去世时的黑板上发现的。我一直认为引用费曼的话有些老套。然而,当一句引语完美地表达了你的感受时,诉诸陈词滥调可能是合理的。

让我解释一下:我知道有几十篇关于本文主题的优秀文章、教程和视频。我们需要另一个吗?简单说说为什么我认为我的观点与众不同。本人 51 岁,无论是编程,微积分,线性代数,还是工科背景都是。大约 12 个月前,我开始自学编程和人工神经网络。在大量关于深度学习的教程和课程之后,其中大多数都利用了现有的框架,如 PyTorch、sci-kit learn 和 TensorFlow,我仍然觉得我对某些概念的理解有些不对劲。当我对一件事理解不透彻的时候,我总是做同样的事情:从头开始构建。因此,我决定不借助任何框架来实现一个简单的人工神经网络(ANN 或 NN)。这篇文章说明了我的尝试的结果。也许,有些人会发现这是徒劳的。希望其他人会发现它很有帮助——甚至可能学到一些东西。

(请注意,这不是对神经网络的介绍,我也不会解释支撑算法的数学。我假设我的读者熟悉深度神经网络背后的基本概念,例如反向传播。如果你不是,你可以做得比在 YouTube 上看 3blue1brown 的系列更糟

最后,请注意,我欢迎批评和建议。如果你发现了一个错误,请随时联系

有兴趣的可以在这个 GitHub repo 里找代码。)

入门指南

首先,我们导入一些库。由于我们不会使用任何深度学习或机器学习框架,所以导入的数量有限。

导入库

我决定使用 MNIST_784 数据集来训练和测试这个网络。如果你正在阅读这篇文章,你可能知道, MNIST 被认为是深度学习的“你好,世界”。这是一个由 70.000 幅手写数字灰度图像组成的数据集;用作者的话说:

“对于那些希望在真实世界数据上尝试学习技术和模式识别方法,同时在预处理和格式化方面花费最少精力的人来说,这是一个很好的数据库。”

我们可以下载 MNIST 作为一个python字典(使用例如scikit-learn),如下所示(我们将数据集本身存储在data,将标签存储在labels:

装载 MNIST

让我们来探索数据集。我们随机选取一个数据点来了解图像及其形状:

探索 MNIST 数据点

所以我们知道我们的图像是一个形状为 784 的向量:一个扁*的 28 x 28 的矩阵。如果我们将数据集分成具有例如 60.000 个数据点的训练集和具有 10.000 个数据点的测试集,我们将得到如下结果:输入层大小= mn,*其中 m 是样本数, n 是特征数。

因此,网络的输入图层大小为(60.000 x 784)。假设我们将训练数据集表示为以行表示要素,以列表示示例,那么我们将以类似如下的内容结束:

让我们首先考虑一个简单的全连接神经网络,只有一个隐藏层。假设隐藏层有 512 个节点。第一个问题是:我们如何控制从输入层到隐藏层的映射?换句话说,权重矩阵的大小应该是多少?输入层中的每个节点都将连接到隐藏层中的每个输入。因此,我们将需要 784 x 512 = 401,408 个权重或可训练参数。并且由于到隐藏层的每个连接将向每个加权和添加一个偏差,我们将需要 512 个偏差。作为一般规则:

权重矩阵的形状规则

(获得维度权限总是让我抓狂:这个,吴恩达提供的,是我迷路时用的。)最后,这是我们得到的具有由 4 个神经元组成的单一深层的 NN:

图层的形状

(但是记住:我们需要给隐藏层的每个节点加上偏差。)

搭建舞台

先来定义一些常用的 激活函数 :

激活

在本文中,我将只使用 整流线性单元(ReLU) 函数,但是 NN 应该足够灵活以适应其他选择。当我们实现神经网络时,我们会记住这一点。

对于输出层,我们将使用 softmax 鉴于这是一个多类分类问题。

Softmax

在训练我们的网络之前,我们需要一些预处理函数:

  1. 归一化功能将输入缩放至[0,1]范围,并且
  2. 一个 one-hot-encode 函数,它将把标签数组从一个 n 大小的向量(其中 n 是样本数)变成一个 n x m 数组(其中 m 是可能的输出数)。

缩放特性有多种方式:在我们的例子中,我们将使用最小-最大缩放(我们也可以将每个特性除以一个特性可能取值的数量——在我们的例子中是 255):

缩放比例

为了对标签执行一次热编码,我们可以创建一个单位矩阵(即对角线上由 1 和其他地方的 0 组成的矩阵),其形状与我们的标签向量相同,然后用标签向量本身对其进行索引,如下所示:

label                            encoded
-------  -------------------------------
      5  [0\. 0\. 0\. 0\. 0\. 1\. 0\. 0\. 0\. 0.]
      0  [1\. 0\. 0\. 0\. 0\. 0\. 0\. 0\. 0\. 0.]
      4  [0\. 0\. 0\. 0\. 1\. 0\. 0\. 0\. 0\. 0.]
      1  [0\. 1\. 0\. 0\. 0\. 0\. 0\. 0\. 0\. 0.]
      9  [0\. 0\. 0\. 0\. 0\. 0\. 0\. 0\. 0\. 1.]
      2  [0\. 0\. 1\. 0\. 0\. 0\. 0\. 0\. 0\. 0.]
      1  [0\. 1\. 0\. 0\. 0\. 0\. 0\. 0\. 0\. 0.]
      3  [0\. 0\. 0\. 1\. 0\. 0\. 0\. 0\. 0\. 0.]
      1  [0\. 1\. 0\. 0\. 0\. 0\. 0\. 0\. 0\. 0.]
      4  [0\. 0\. 0\. 0\. 1\. 0\. 0\. 0\. 0\. 0.]

标签的一键编码

因为原始数据可以以不同的方式组织,所以我决定,作为一种设计选择,将适当的整形和一次性编码留给用户:也就是说,将由他们来预处理训练数据集和测试数据集,以便将特性作为行传递,将示例作为列传递,并将标签传递给one_hot_encode函数。相反,缩放将由网络本身来执行。

最后,我们将需要激活函数的导数来执行梯度下降:

派生物

实现神经网络

我们现在已经拥有了深度神经网络所需的一切,我们将把它实现为一个class。所以我们需要思考:

  • 一个**init**的方法;
  • 一种正向传播方法;
  • 如何实现反向传播
  • 如何训练网络;
  • 如何计算成本函数
  • 方法进行预测,,最后:
  • 一种实现准确性等指标的方法。

我的思考过程大致如下。我们可以将以下参数传递给构造函数:

  • 带有相应标签的训练集
  • 带有标签的测试装置
  • 激活功能选项(字符串形式)
  • 我们需要预测的班级数量
  • 架构——我们用列表来表示,其中列表的每个元素对应于一个深层,并指示该层中神经元的数量。

然后,我们使用字典来存储这些层,并用字符串来命名它们,以便于跟踪(这也是我从吴恩达的机器学习课程中学到的技巧)。

神经网络 init 方法。

注意一堆assert语句。我再强调这一点也不为过:如果你不想花时间去调试地狱,考虑一下如何测试你的网络,并在你的方法中随意加入需要满足的条件。

当构造函数被调用来实例化一个 NN 时,它会执行一系列操作:

  1. 它对训练集 X 中的特征进行归一化;
  2. 它在architecture列表的开头添加了一个大小为 m 的层,其中 m 是特征的数量(它从第一个训练示例的形状中推断出来的),并在末尾添加了另一个大小为 n (类的数量)的层;
  3. 它初始化一个名为参数的空dict来存储偏差和权重。

一旦我们有了init,我们就可以考虑初始化这些参数的方法。为此,我们进行如下操作:

  • 我们迭代层的数量(因此,从 1 开始,到 L -1 结束,其中 Larchitecture列表的长度)。
  • 对于每一层,我们向parameters字典中添加一个用于权重的键值对和另一个用于偏差的键值对
  • 为了简单起见,我们通过从形状的正态分布样本中随机选取来初始化权重:I 层中的节点数,I 层中的节点数, i+1) 并将它们缩放 100 倍。更好的选择可能是使权重初始化依赖于所选择的激活函数(例如,参见本文)
  • 偏差是零向量:层中的每个节点一个。我们使用关键字 w{i} 和带有关键字 b{i}的每个偏置向量将每个权重矩阵存储在字典中。

参数初始化

前馈、反向传播和拟合

前馈

现在我们来看看正向传播反向传播的方法。

我希望这两种方法都不接受任何参数作为输入(当然,除了类本身),并且就地修改模型。为什么?因为我们将把数据传递给 fit 方法,并且对向前和向后传递的调用将在那里发生。(然而,我们确实需要每次向前传递返回一个成本值,我们将在梯度下降期间拟合模型时使用该值。)

因此,下面是我们如何实现forward方法。您可能记得我们初始化了两个字典:一个用于层,一个用于参数(我们使用上面的initialize_parameters方法填充)。对于除输入和输出层之外的每个层中的每个节点,我们将需要一个输入(输入的加权和)和一个激活输出。我们可以在我们的layers字典中为模型架构中的每一个 i 深层分别存储为 z{i}a{i} 。因此:

  • 我们迭代范围(1,架构的长度-1);
  • 为了获得该范围内每个 ilayers[zi],我们计算parameters[wi]layers[a-i]的点积,再加上parameters[bi];
  • 最后,为了获得激活输出layers[ai],我们将选择的激活函数应用于layers[zi]。我们存储层的值,因为当我们执行反向传播时需要它们。

正向传播

作为一个实现说明:我被成本函数计算中的溢出逼疯了,直到我意识到这是由于可能被零除。因此在成本计算中为 0.00000001。

反向传播

反向传播算法的实现在结构上相对简单。它与前向传递的算法非常相似(这里我不打算讨论反向传播算法背后的数学,因为这已经超出了本文的范围)。我们初始化一个空的derivatives字典来存储渐变,并从右到左遍历各层。对于每一层,我们计算dZ,并使用它来计算该层的dWdB(分别更新权重和偏差)。这就是我们之前定义的layers字典派上用场的地方,因为我们需要访问每一层的输入z和激活a。首先,让我们计算最后(输出)层的梯度。

对于最外层的层来说,dz仅仅是output — ground truths的结果。(注:dz是什么形状?有多少标签就有多少行,有多少训练示例就有多少列——因此,当我们用存储在layers中的值打点它时,我们需要转置它。请记住这一点。)

然后我们可以如下计算dWdb:

  1. 权重的梯度是dz的点积和先前层转置的激活(见上述注释)(实例数量的*均值)
  2. 偏差的梯度只是*均的dz(注意,我们需要keepdims=True,否则,数组将被挤压成形状(1),作为秩 1 数组,这可能导致不明确的结果
  3. 我们将该层激活的渐变存储在变量dAPrev中。

然后我们在剩余的层上循环,除了这次我们将dz计算为层的激活函数与n+1th层的激活梯度dAPrev的乘积。

最后,我们归还derivatives字典。

反向投影

拟合、精确度和预测

我们现在把它们放在一起训练网络。我们将一个学习速率和多个时期传递给fit方法。这将通过调用initialize_parameters来训练模型,并且对于每个时期运行一个正向传递,随后是返回梯度的反向传播。这些将依次用于更新可训练参数。在培训期间,费用将存储在一个列表中,以供将来参考。训练集和测试集(这里定义为accuracy)上的性能也将被存储和显示。

适合模型。

最后两部分是predictaccuracies方法。

预测和准确性。

我还定义了一堆绘图函数,我在这里省略了,但是如果你感兴趣,你可以在 Github repo 上找到它们。

训练网络和结果

最后,我们可以训练和测试我们的网络!让我们将数据分成一个训练集和一个测试集,并用 ReLu 执行训练(但是我们可以使用任何其他已定义的激活函数)。

然后,我们可以用适当的初始化参数实例化一个 NN:在这里,我选择了一个分别具有 128 个和 32 个神经元的 2 层架构,我将以 0.03 的学习率跨 200 个时期对其进行训练。

让我们看看结果!

作者图片

作者图片

作者图片

这个好像一点都不差!

我希望你在路上学到了一些东西。

参考

[1]许可:Yann LeCun 和 Corinna Cortes 拥有 MNIST 数据集的版权,该数据集是原始 NIST 数据集的衍生作品。MNIST 数据集是根据知识共享署名-同样分享 3.0 许可条款提供的。

用 Snowpark 为 Python 构建面板仪表盘

原文:https://towardsdatascience.com/building-a-panel-dashboard-with-snowpark-for-python-fe1b16e7bd75

数据科学家的雪花

数据科学家通常使用 SQL 与数据仓库进行交互,但通常依赖 Python 进行数据发现、可视化和建模。如果我们可以用我们喜欢的 Python 工具直接与数据仓库交互,那该有多好?Snowflake 现在原生支持 Python 和 Snowpark for Python。它使我们的数据科学家能够用 Python 编码,同时享受 Snowflake 提供的相同的安全性、性能、治理和可管理性优势。有了这个工具,我可以与我的数据仓库交互,可视化数据,甚至直接用 Python 构建和部署模型到我的数据仓库。要了解数据库中有什么,首先要做的一件事就是可视化您的数据。在本文中,我将向您展示如何创建这个面板仪表板,以便有意义地可视化雪花数据集中的 500 万个数据点。

作者图片

什么是 Python 的 Snowpark?

Snowpark for Python 允许数据科学家编写我们熟悉的 Python 代码,并在 Snowflake 中将 Python 翻译回 SQL。通过与 Anaconda 的合作,我们可以为 Snowpark 使用所有安全且精心管理的 Python 包。雪花甚至在 Anaconda 中有自己的 Python 包存储库:【https://repo.anaconda.com/pkgs/snowflake。

什么是面板?

Panel 构建交互式仪表盘和应用。就像 R 闪亮,但更强大。它是由我的 Anaconda 同事 Philipp Rudiger、Jean-Luc Stevens 和 Jim Bednar 开发的。Panel 是 HoloViz 生态系统中的七个库之一。如果你想了解更多关于 HoloViz 和 Panel 的知识,可以看看我之前的博文《为什么我喜欢 HoloViz、panel.holoviz.org 和 awesome-panel.org》。

材料

为了这篇文章,请查看我在 Github 上的 Jupyter 笔记本

设置

本文使用了来自雪花市场的“OpenStreetMap — Nodes (USA)”数据。这些数据可以免费使用。在市场中找到这些数据,点击“获取数据”,然后你应该会看到它出现在你的“数据”中。

要访问 Snowpark for Python,您可以在命令行中运行以下命令来创建一个新的 Conda 环境,激活该环境,安装 Snowpark for Python,安装所需的 viz 包,并启动一个 Jupyter 笔记本。

现在我们可以开始在 Jupyter 笔记本上编码了。

进口所需模块

首先,我们需要从 Snowpark 导入所需的模块。我将我的所有凭证保存在一个单独的文件中,并将其导入到这里,但是您应该使用自己的凭证。

与雪花建立连接

我们创建一个连接到我们雪花帐户的会话。

或者,您可以使用外部浏览器进行身份验证:

获取数据

接下来,我们从 OpenStreetMap 数据库中获取数据。这个数据库中有两个独立的表:数据表和地理表。Geography 表包含与数据表相关联的几何信息,如经度和纬度。下面的代码显示了从雪花中查询数据的两种方法:

  • session.table返回整个表格的内容。
  • session.sql允许我们编写 SQL 查询并返回 SQL 结果。几何数据被定义为一个地理对象,这是一个包含经度和纬度的字典。我使用 st_x 和 st_y 将经度和纬度作为两个独立的列提取出来。

数据处理

这里实际上没有多少数据处理步骤。我使用来自 Snowpark for Python 的[.join](https://docs.snowflake.com/en/LIMITEDACCESS/snowpark-python.html#joining-dataframes)函数连接了两个表。然后我们可以将这个 Snowpark 数据框转换成我们熟悉的熊猫数据框。

用 Datashader 绘制 500 万个数据点

这个 OpenStreetMap 数据包含 5 百万个数据点,我想在地图上绘制它的经度和纬度信息。

Datashader 是 HoloViz 家族中的大数据可视化工具。使用 Numba(实时编译器)和 Dask(并行计算),Datashader 可以在一台机器上非常快速地绘制出数百万甚至数十亿个数据点。如果你想了解更多关于 Datashader 如何工作的信息,请查看我之前关于 Datashader 的文章。

好的,回到我们的例子。首先,我们导入用于绘图的模块。我们需要将我们的经度和纬度转换为 Web 墨卡托坐标,以便它们能够正确地显示在地图上。然后我写了这个函数datashader_plot来绘制这 500 万个数据点,并用地图覆盖它们。在这个函数中,我们首先创建一个地图map_tiles,然后使用hvplot配合rasterize=True使用 Datashader 进行栅格化,这样就可以快速而有意义的可视化大数据。

结果显示了一个包含所有 500 万个数据点的交互图!

作者图片

创建交互式仪表板

如果我们想要选择设施,并根据我们选择的设施展示我们的地块,会怎么样?在这里,我创建了一个面板小部件来选择数据中的前 10 个便利设施,然后我创建了一个面板仪表板,其中的绘图对应于我们选择的便利设施。我用了 hvPlot。iteractive 创建此仪表板,了解有关 hvPlot 的更多信息。互动,查看我之前的博文

作者图片

最后,我们可以使用一个模板来使我们的仪表板看起来更好。运行template.show()将自动打开一个标签,显示我们的最终仪表板。

作者图片

部署仪表板

我希望雪花可以提供一种方法,直接在雪花上部署我们的面板应用程序,这样我所有的数据和仪表板都可以在一个地方访问——雪花。

要将这个仪表板作为 web 服务器启动,我们可以取消对template.servable()上面最后一行的注释,只需运行panel serve snowflake_plot.ipynb。有关如何将仪表板部署到服务器的详细信息,请查看面板文档,或者我之前关于将仪表板部署到 Google Cloud App EngineGoogle Cloud Run 的文章。

我真的很喜欢使用 Snowpark for Python,我对它的许多特性感到兴奋,包括 Python UDFs、模型构建和部署。我想写的东西太多了。敬请关注我的下一篇文章!

承认

感谢您的反馈和支持!

参考文献:

我是 Anaconda 的高级数据科学家 Sophia Yang。请随时在 TwitterLinkedinYouTube :)上与我联系

构建随机森林分类器来预测神经尖峰

原文:https://towardsdatascience.com/building-a-random-forest-classifier-for-neural-spike-data-8e523f3639e1

在 Python 中构建随机森林分类器以预测真实神经细胞外尖峰的子类型的分步指南。

未喷涂上的 Fakurian 设计的“Braintree”

G 考虑到人脑本身神经元的异质性,分类工具通常被用来将电活动与不同的细胞类型和/或形态相关联。这是神经科学界的一个长期问题,在不同物种、病理、大脑区域和层次之间可能有很大差异。幸运的是,随着快速增长的计算能力允许机器学习和深度学习算法的改进,神经科学家获得了进一步探究这些重要问题的工具。然而,正如 Juavinett 等人所述,在大多数情况下,编程技能在社区中的代表性不足,教授这些技能的新资源对于解决人类大脑的复杂性至关重要。

因此,为了提供一个相关的用例,在本文中,我们将为神经细胞外锋电位波形数据集构建一个随机森林分类器算法。随机森林分类器是广泛应用的监督学习模型,是解决分类问题的有效而强大的算法。可以说,它们位于分类器层次结构的顶端,旁边还有其他算法,如:逻辑回归、支持向量机、朴素贝叶斯分类器和决策树。

在此过程中,我们还将完成多维降维和聚类步骤(带有示例代码),以建立一个有监督的学习问题。一旦完成,任何级别的程序员将能够(1)识别和绘制独特的细胞外波形,( 2)实施随机森林算法对它们进行分类。

什么是决策树?

返璞归真!首先,为了理解随机森林图,掌握决策树是很重要的(这将很快有意义)。简而言之,决策树是一种由“节点”和“分支”组成的树状模型,是一种可视化展示决策及其结果的工具。

Angel Dasz_ai 所总结的,并且如下图所示,节点可以分类如下:

  • 根节点:起始节点。在决策树中,它通常评估最能分割数据的变量。
  • 决策节点:根据条件分支的子节点。
  • 叶节点:树的最终节点,不能再分支。

从根节点开始,按照 if-else 结构在决策节点对一个独立变量(连续变量或分类变量)进行拆分。沿着树向下移动,数据不断被“分割”,直到到达可以进行分类的叶节点。如果不是这种情况,则重复该过程,直到达到结果。

带有定义的决策树插图。图片作者。

决定分裂

决策树使用多种算法来决定问题的最佳分割,但“基尼指数”和信息增益的“熵”是最常见的标准。两者都确定了节点处标签的杂质,这是在判定节点处异质或混合值程度的度量。这是至关重要的,因为决策树算法找到了最佳标准,使集合成为更同质的子集(即↓杂质)而不是异质的子集(即↑杂质)。

是对一组数据的杂质的一种度量,可以通过以下公式表示:

熵公式。其中“ Pi 表示数据集中类别“I”的概率。

通过使用熵计算,可以计算节点处的随机性或无序度。信息增益使用熵来量化哪个特征通过减少熵来提供关于分类的最大信息,并因此做出相应的分割:

信息增益公式。从类“ X ”上的“ Y ”的熵中减去类“ Y 的熵。

与熵相似,基尼系数(也称为基尼系数或基尼系数)在 0 和 1 之间变化。它通过测量目标属性值的概率分布之间的差异来计算分类的杂质。0 的输出是同质的,而 1 是不纯的,这表明新的随机数据被错误分类的可能性很大。一旦实现,就进行节点分裂,这减少了这种计算的杂质。对于“C”类不同阶层,基尼公式将为:

基尼系数公式,其中" C" 表示数据子集中的总类别数,而" i" 是从 C 中选择的类别。

决策树的衰落

虽然决策树可以为分类问题提供一个可视化的解决方案,但是使用该算法的缺点应该被考虑在内:

  • 它们将继续形成分支,直到每个节点都是同质的。如果测试一个小样本,这就产生了一个过度拟合的问题(鲁棒性)。
  • 一次仅使用一个独立变量来确定分类,这可能会导致性能和准确性问题。
  • 不处理缺少的值和数值属性。

随机森林分类算法是如何工作的?

理解分类和回归树(CART)的基本概念的重要性在这里发挥了作用,因为随机森林使用许多决策树的集合而不是一个来做出最终决策(集合技术)。这是特别强大的,因为模型的集合将胜过单个模型。最终,这解决了与决策树相关的一些缺点,因为它提高了性能和健壮性。值得注意的是,集合中的每棵树都是相对不相关的,这很重要,因为正如饶彤彤所概述的:

这些树保护彼此免受各自错误的影响(只要它们不总是在同一个方向出错)。虽然有些树可能是错误的,但许多其他的树将是正确的,因此作为一个群体,这些树能够朝着正确的方向移动。

一种简化的随机森林分类算法的图示。图片作者。

随机森林算法如何构建多棵树?

随机森林算法实现了 (1)引导聚合和(2)特征随机性的组合,以使用相同的数据集构建许多决策树。总体而言,它们在这样做的同时保持相对不相关,这是一个重要的特性:

  1. bootstrap 聚合(又名 Bootstrap 或 bagging) 是一种技术,涉及在给定迭代次数和变量(Bootstrap 样本)的基础上随机抽样数据子集,并进行替换。来自所有迭代和样本的预测通常被*均以获得最可能的结果。重要的是要理解,它不是将数据“分块”成小的大小,并在其上训练单个树,而是仍然保持初始数据大小。这是一个应用集合模型的例子。
  2. 特征随机性主要作用是降低决策树模型之间的相关性。与可以利用所有特征来辨别最佳节点分裂的决策树相比,随机森林算法将随机选择这些来进行决策。最终,这允许训练也在不同的特征上发生。

总的来说,通过这些方法,随机森林算法可以在相同数据的不同集合上进行训练(bootstrapping ),同时还可以利用不同的特征来生成预测结果。这是一个比决策树更强大的分类工具。

特征重要性

最后,随机森林算法还将使用基尼系数(或*均减少杂质)来评估数据集中每个特征在人工分类问题上的重要性。现在知道了随机森林算法是使用决策树的集合来构建的,直观地,每个内部节点是使用基尼不纯或信息增益来选择的(如上所述)。对于每个特征,计算杂质的减少,并在集合中的所有决策树上*均,以确定特征重要性。该方法在随机森林的[scikit-learn](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html#sklearn.ensemble.RandomForestClassifier.feature_importances_) 实现中可用(分类器和回归器都适用)。

将随机森林分类器应用于细胞外神经记录:

现在,申请!我们将使用一个公开可用的数据集,这个数据集是在授权Attribution 4.0 International(CC by 4.0)提供的。这是一组取自哺乳动物大脑的细胞外记录。

就上下文而言,细胞外记录是由细胞产生的电位的记录,或者在感兴趣的细胞附*的细胞外液中,或者无创地。

加载库和数据集

首先,我们将导入以下库和 load required 数据集。

# Install the following libraries
import pandas as pd
import numpy as np
from umap import UMAP
import seaborn as sns
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.anchored_artists import AnchoredSizeBarfrom sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
from sklearn.cluster import AgglomerativeClustering
from sklearn.neighbors import kneighbors_graph
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_samples, silhouette_score
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix

为了熟悉数据集,首先可视化数据类是很重要的。这使我们能够更好地理解数据集和组织数据的可能方式。

# Load waveforms
mypath = '*insert file path/waveforms.csv'
data = pd.read_csv(mypath, index_col = 'uid')# Get an overview of the data
print(f'{data.shape[0]} unique experiment identifiers (uid), recorded with a sampling frequency (KHz) of {((data.shape[1]-1)/5)}')# Class breakdown
data.organoid.value_counts()

看到我们有多个类,如value_counts()所示,我们将用唯一的颜色代码标记每个类。当在进一步的数据分析中可视化这些类时,这将被证明是有用的。

# Define custom colors for visualization
mycolors = {'Data_D':     '#FFA500', # orange
           'Data_G':       '#4169E1', # royalblue
           'Data_F':       '#FF4500', # orange red
           'Data_C':       '#9400D3', # darkviolet
           'Data_A':       '#32CD32', # limegreen 
           'Data_E':       '#228B22', # forestgreen
           'Data_G_V2' :   '#006400', # darkgreen 
           'Data_H':       '#00BFFF', # deepskyblue 
           'Data_E_V2':    '#DC143C', # crimson
           'Data_F_V2':    '#0000FF', # blue
           'Data_B':       '#000000', # black
           }# Add color to the DataFrame
data['color'] = data['organoid'].apply(lambda orgID: mycolors[orgID])

现在,我们可以想象!我在下面展示了一个简单的柱状图,总结了每节课的录音数量。然而,更多的 Python 数据可视化工具可以在这里的上一篇文章中找到。

# Visualizing the unique experiment identifiers
fig, ax = plt.subplots(figsize=(15, 8))
sns.barplot(x=data.organoid.value_counts().index, y=data.organoid.value_counts(), palette=mycolors)# Customizing the graph
plt.xticks(rotation=30,fontsize=14)
plt.yticks(fontsize=14)
ax.set_xlabel('Class type', fontsize=16)
ax.set_ylabel('Number of waveforms', fontsize=16)
plt.rcParams["font.family"] = "Arial"# Despine
right_side = ax.spines["right"]
right_side.set_visible(False)
top_side = ax.spines["top"]
top_side.set_visible(False)plt.savefig('Figures/barplot.png', dpi = 300, bbox_inches="tight")
plt.show()

图片作者。一个条形图,突出显示了每种类型记录的波形数量的变化。

可视化细胞外波形

假设我们正在分析细胞外记录,我们将可视化每个数据集类产生的*均波形。这将为手头的分类问题提供进一步的见解。下面,我通过计算每个数据集类的*均轨迹,并叠加到它们的输入轨迹上来实现这一点。

# Isolating the waveforms for each class 
class_names = data['organoid'].unique()# Plotting mean traces for each organoid class
fig, ax = plt.subplots(1,9, figsize=(24,4.5))
for unique_class in class_names:    
        df_new = data[data['organoid'] == unique_class] #isolating # np.array conversion
        df_new = df_new.iloc[:,:-2].to_numpy() #dropped last column# Averaging across samples per organoid class 
        data_mean_perclass = np.mean(df_new, axis=0)# Sampling frequency for plot generation
        sampling_freq = np.linspace(0, 5, 150) #recording length is 5ms per 150 samplesfor i in range(class_names.shape[0]): 
            if unique_class == class_names[i]:

                # Plotting all traces behind in light color
                for row_num in range(df_new.shape[0]): 
                    ax[i].plot(sampling_freq, df_new[row_num,:], color = 'lightgray')

                # Plotting each mean waveform into a separate subplot 
                ax[i].plot(sampling_freq,data_mean_perclass, color=mycolors[unique_class], linewidth=3)
                ax[i].set_ylim([-1.8, 1.8])
                ax[i].grid()
                ax[i].axis('off')
                ax[i].title.set_text(unique_class)  
                plt.rcParams["font.family"] = "Arial"

            else: 
                continue

        # Scale bar
        scalebar = AnchoredSizeBar(ax[8].transData, 1, "1 ms", 'lower right', frameon=False, size_vertical=0.02, pad=0.1)
        ax[8].add_artist(scalebar)

plt.savefig('Figures/spikes.png', dpi = 300)

每一类的*均细胞外波形以彩色显示,并与其子图标题相对应。浅灰色表示用于产生*均输出阵列的输入信号。作者提供的图片,显示尺寸已重新格式化。

多维缩减

从视觉上看,波形轨迹突出显示了每个数据集类都有许多来自不同神经元细胞类型的单个尖峰波形。*均起来,可以说这些类中有些看起来很像,有些则不像。

为了更好地验证这一点,我们将使用统一流形*似和降维投影(UMAP)对细胞外波形数据进行多维降维。最后,还可以进行主成分分析或 t 分布随机邻居嵌入(t-SNE)。西瓦卡尔·西瓦拉贾 在这里概述了这些方法之间的区别。出于本文的考虑,我们将只关注前两个部分,以获取大部分方差。

# UMAP calculation
umap_2d = UMAP(n_components=2, random_state = 43)
projections = umap_2d.fit_transform(data.iloc[:,:-2])# Concat dataframes for seaborn scatter plot
UMAP_df = pd.DataFrame(projections, columns = ['UMAP 1','UMAP 2'])
UMAP_df_concat = pd.concat([UMAP_df,data['organoid'].reset_index(),data['color'].reset_index()], axis=1)# Figure plotting
sns.set(font_scale=1.2)
sns.set_style("whitegrid", {'axes.grid' : False})
fig1 = sns.relplot(data = UMAP_df_concat, x = "UMAP 1", y = "UMAP 2", hue="organoid", kind="scatter", palette=mycolors, height=5, aspect=1.5)
fig2 = sns.relplot(data = UMAP_df_concat, x = "UMAP 1", y = "UMAP 2", hue="organoid", kind="scatter", palette=mycolors, height=3, aspect=1.5, col='organoid', col_wrap=3)# Figure export
fig1.savefig('Figures/UMAP.png', dpi = 300)
fig2.savefig('Figures/UMAP_2.png', dpi = 300)

细胞外波形时间序列数据的 UMAP 图,n_components = 2。颜色对应于每个数据集类。图片作者。

从空中俯瞰,UMAP 上正在形成不同的星团。每个聚类没有同质的类别类型(由颜色变化显示),而是来自不同类别的不同细胞外记录的组合。因此,很可能每个有机类类型都具有与其他类相似的波形比例。

为了更好地查看每个数据集对 UMAP 的贡献以及它们是如何聚类的,我们将隔离它们的数据:

代表来自每个数据集类的独特细胞外波形时间序列数据的一系列 UMAP 子图。图片作者。

的确,来自特定数据集类的一些记录看起来更相似地聚类,而其他记录则不相似。这与我们的波形轨迹一致。接下来,我们将对数据进行聚类,为我们的随机森林算法建立一个分类问题。

k 均值聚类

为了使用前两个分量对我们的数据进行无偏聚类,我们将利用 K-means。这种聚类方法属于聚类的划分类,用于最小化总的类内变化。为了选择最佳聚类数,我们将利用(1)肘和(2)剪影方法:

  1. Elbow 方法:计算总的类内*方和(wss),它衡量聚类的紧密性。目的是尽可能减少这种情况。
  2. 剪影法:我们将使用剪影法回测肘法。这种方法通过确定对象在其中的位置来测量群集的质量。高轮廓值表示“好”的聚类,而低轮廓值表示“差”的聚类。

这是这两种方法之间的微妙*衡,可以为您的数据确定最佳的聚类数。所以,让我们一步一步来:

# Set range of possible clusters
range_n_clusters = range(2, 11) #set as 2 to 10# Elbow method & Silhouette scores for optimal number of cluster
wcss = [] #within cluster sum of square 
silhouette_values = []
for i in range_n_clusters:

    kmeans = KMeans(n_clusters = i, random_state = 42)
    kmeans.fit(data.iloc[:,:-2])
    wcss.append(kmeans.inertia_)

    cluster_labels = kmeans.fit_predict(data.iloc[:,:-2])
    silhouette_avg = silhouette_score(data.iloc[:,:-2], cluster_labels)
    silhouette_values.append(silhouette_avg)# Isolating highest Silhouette value calculation
max_silhouette = max(silhouette_values)
max_index = silhouette_values.index(max_silhouette)
print(f'Optimum number of clusters is {range_n_clusters[max_index]}')

# Figure plotting
fig, ax = plt.subplots(1,2, figsize=(18,4.5))
ax[0].plot(range_n_clusters, wcss)
ax[1].plot(range_n_clusters, silhouette_values)
ax[0].title.set_text('The Elbow Method')
ax[1].title.set_text('Optimal Cluster Number')
ax[0].set(xlabel='Number of Clusters', ylabel='WCSS')
ax[1].set(xlabel='Number of Clusters', ylabel='Silhouette score')# Line to indicate optimum cluster number
ax[0].axvline(x=range_n_clusters[max_index], color='black', label='axvline - full height', ls='--')
ax[1].axvline(x=range_n_clusters[max_index], color='black', label='axvline - full height', ls='--')# Export figures
fig.savefig('Figures/Silhouette_elbow_method.png', dpi = 300, bbox_inches="tight")

肘(左)和侧影方法(右)对神经尖峰数据集的结果。虚线突出显示了使用 K 均值聚类的最佳聚类数。图片作者。

正如图中虚线所示,我们数据集的最佳聚类数是 3 。原因是对于这个数字,轮廓得分最高,而组内*方和(WCSS)减少。我们将通过使用以下代码在我们的组合 UMAP 图上可视化聚类来确认这一点:

# Agglomerative clustering with optimal cluster number on UMAP
def clustering_agglo(df, n_clusters):
    X = df.to_numpy()
    connectivity = kneighbors_graph(X, int(len(df.index)/10), include_self=False)
    agc = AgglomerativeClustering(linkage='ward', connectivity=connectivity, n_clusters=n_clusters)
    agc.fit(X)
    print(f'Labelling {len(np.unique(agc.labels_))} clusters on original UMAP projection')
    return agc.labels_labelsAgglo = clustering_agglo(data.iloc[:,:-2],range_n_clusters[max_index])# UMAP plotting
sns.set(font_scale=1.2)
sns.set_style("whitegrid", {'axes.grid' : False})
fig = sns.relplot(data = UMAP_df_concat, x = "UMAP 1", y = "UMAP 2", hue=labelsAgglo, kind="scatter", palette=['red','blue','green'], height=5, aspect=1.5)# Add labels to original dataframe: 
data['cluster_group'] = labelsAgglo# Figure export
fig.savefig('Figures/UMAP_clusters.png', dpi = 300)

跨数据集类别记录的时序细胞外数据的 UMAP 图。颜色对应于使用 K-means 聚类确定的聚类,并使用剪影和肘方法进行验证。图片作者。

从这个图中,我们可以看到(在很大程度上)K-means 聚类有一个合理的工作!值得注意的是,UMAP 图上的聚类之间有一些重叠,但看到这是一个使用神经尖峰数据的真实数据集,这是可以预期的。现在,让我们进一步调查这些?

每个集群的类别和波形细分

如前所述,从特定数据集类记录的波形聚类更相似,而其他的则不相似。为了更好地理解这些差异,让我们来看看每个聚类的*均波形以及组成它们的数据集类别:

#Cluster group colour
cluster_colors=['red','blue','green']#Breakdown of each waveform cluster type by organoid class
for cluster_group in range(0,3):
    df_group = data[data['cluster_group'] == cluster_group] #isolating each cluster group
    breakdown_perclust = ((df_group.organoid.value_counts())/(df_group.shape[0]))*100

    # Getting idx for each cluster for color matching
    breakdown_perclust_df = breakdown_perclust.to_frame()
    breakdown_perclust_df['index'] = breakdown_perclust_df.index
    breakdown_perclust_df['color'] = breakdown_perclust_df['index'].apply(lambda orgID: mycolors[orgID])
    breakdown_perclust_df = breakdown_perclust_df.rename(columns = {'organoid' : 'class_breakdown_percentage'})

    # Computing mean waveforms for each cluster
    df_group_new = df_group.iloc[:,:-3].to_numpy() #dropped last columns
    data_mean_group_percluster = np.mean(df_group_new, axis=0)# Sampling frequency for plot generation
    sampling_freq = np.linspace(0, 5, 150) #recording length is 5ms per 150 samples

    # Piechart plotting
    fig, ax = plt.subplots(1,2, figsize=(16,4), gridspec_kw={'width_ratios': [2.5, 1]})
    ax[0].pie(breakdown_perclust_df['class_breakdown_percentage'], colors = breakdown_perclust_df['color'], labels = breakdown_perclust_df['index'],\
              autopct='%1.0f%%', pctdistance=0.5, labeldistance=1.1)
    title_pie = ("Cluster number: " + str(cluster_group))
    ax[0].title.set_text(title_pie)

    # Draw inner circle
    centre_circle = plt.Circle((0,0),0.70,fc='white')

    # Equal aspect ratio ensures that pie is drawn as a circle
    ax[0].add_patch(centre_circle)  
    ax[0].axis('equal')  

    # Mean waveform plotting
    ax[1].plot(sampling_freq,data_mean_group_percluster, linewidth=3, color = cluster_colors[cluster_group])
    title_waveform = ("Mean waveform for cluster number: " + str(cluster_group))
    ax[1].title.set_text(title_waveform)
    ax[1].set_ylim([-1.3, 1.3])
    ax[1].grid()
    ax[1].axis('off')
    plt.rcParams["font.family"] = "Arial"

    # Scale bar
    scalebar = AnchoredSizeBar(ax[1].transData, 1, "1 ms", 'lower right', frameon=False, size_vertical=0.02, pad=0.1)
    ax[1].add_artist(scalebar)

    # N-values
    n_value = ("N-value: " + str(df_group.shape[0]))
    plt.figtext(0.36, -0.05, n_value, ha="center", fontsize=12, bbox={"facecolor":"orange", "alpha":0.2, "pad":5})

    # Export figures
    fig_name = ("Figures/Piechart_waveform_clust" + str(cluster_group) + ".png")
    plt.savefig(fig_name, dpi = 300, bbox_inches="tight")
    plt.tight_layout()
    plt.show()

饼图(左)突出显示了每个数据集类的波形百分比及其对应的*均波形(右)。图片作者。

乍一看,可以看出不同数据集聚类之间的*均波形存在相当大的差异。此外,来自不同数据集类别的波形对聚类的贡献百分比似乎也不同。

现在,我们的随机森林分类器有了一个明确的分类问题!我们可以验证集群之间的这些差异。

将数据分成单独的训练集和测试集

为了有效地训练随机森林算法,我们将把数据分成训练集和测试集。我们预测的期望结果y是聚类类型,而我们的细胞外尖峰时间序列数据是X

测试和训练的原因将其设置为不允许训练数据集中有足够的数据供模型学习输入到输出的有效映射。测试集中也没有足够的数据来有效地评估模型性能。这可以使用以下代码来完成:

# Split data into training and testing sets
X = data.iloc[:,:-3] #drop last 3 columns
y = data['cluster_group']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.33, random_state = 42)# check the shape of X_train and X_test
print(f'Number of entries for X_train is {X_train.shape[0]}, and the number of entries for X_test is {X_test.shape[0]}')

打印输出。图片作者。

随机森林分类

现在,让我们训练我们的随机森林分类器!我们将在许多决策树(迭代)上这样做,并测试它们的预测准确性。这里, y_test 是真实类标签, y_pred 是测试集中的预测类标签:

# Instantiate the Random Forest classifier 
number_int = [1,2,3,4,5,10,20,50,70,100,200,400,500]
accuracy = []
for i in number_int: 
    rfc = RandomForestClassifier(n_estimators=i, random_state=41)# Fit the model
    rfc.fit(X_train, y_train)# Predict the test set results
    y_pred = rfc.predict(X_test)

    # Append to list
    accuracy.append('{0:0.4f}'.format(accuracy_score(y_test, y_pred)))# Check accuracy score 
    print(f'Model accuracy score with {i} decision-trees is {format(accuracy_score(y_test, y_pred))}')# Figure plotting
fig, ax = plt.subplots(figsize=(7,4))
ax.plot(number_int, accuracy)
ax.title.set_text('Number of interations vs accuracy')
ax.set(xlabel='Number of iterations', ylabel='Accuracy')
plt.savefig('Figures/Iterations_accuracy.png', dpi = 300, bbox_inches="tight")

随机森林分类器的“精确度”和“迭代次数”之间的关系。图片作者。

尽管决策树的数量更多,但是在达到稳定状态之前,模型的准确度分数确实在 1 到 100 之间增加。对于 2 个决策树,它是 0.93877,而对于 500 个决策树,它是 0.9632。因此,随着模型中决策树数量的增加,预期精度也会增加。

混淆矩阵

最后,让我们用分类报告构建一个混淆矩阵来总结我们的随机森林分类算法的性能,并评估准确性、精确度和召回指标。

混淆矩阵将通过总结正确和不正确的预测提供我们的模型性能和产生的错误类型的指示。这些可以分为以下四类:(1)(TP)【2】真阳性(TN)(3)****假阳性(FP)(4)假阴性(FN)。 Giulio Laurenti 博士在这里更详细地解释了所述读数

# Confusion matrix
cm = confusion_matrix(y_test, y_pred)
print(classification_report(y_test, y_pred))# Figure plotting
cmap = sns.color_palette("Greys", as_cmap=True) # color map
fig, ax = plt.subplots(figsize=(8, 8))
cm_percent = ((cm/cm.sum(axis=0))*100)
cm_percent = np.nan_to_num(cm_percent, copy=True, nan=0.0, posinf=None, neginf=None)
sns.heatmap(cm_percent, cmap = cmap, annot=True, fmt=".2f", linewidth=1, cbar_kws={"label": "Cluster group classified (%)", "shrink": .8}, annot_kws={"size": 16}) #selecting for percentage only column 
ax.set_title('Confusion matrix')
plt.show()# Figure export
fig.savefig("Figures/Confusion_matrix.png", dpi=300, bbox_inches='tight') 

输出的混淆矩阵分类报告(截图)。图片作者。

0-2 群组的混淆矩阵。不同的阴影突出显示分类为 TP、TN、FN 或 FP 的预测数相对于每个分类的总可能分类数(%)。图片作者。

结论

我们做到了!我们现在已经建立了一个随机森林算法来准确和精确地分类神经尖峰数据的子集。

在这篇文章中,我们不仅介绍了一个涉及神经细胞外数据的真实例子,还介绍了决策树和随机森林分类器背后的理论,以及在我们的工作流程中实现的多维缩减和 K-means 聚类。

虽然我们在这里讨论了使用随机森林解决分类问题,但重要的是要认识到有许多方法可以解决这些问题。因此,在未来的文章中,我们将通过覆盖使用深度学习算法和逻辑回归的分类来进一步讨论这一点。

你可以通过 LinkedIn 联系我,通过 Twitter 关注我,或者在 GitHubKaggle 上加入我。

用 Python 构建亚马逊产品推荐系统

原文:https://towardsdatascience.com/building-a-recommender-system-for-amazon-products-with-python-8e0010ec772c

我为亚马逊的电子产品类别建立了一个推荐系统

马克斯·托马斯Unsplash 上拍摄的照片

介绍

T 这个项目的目标是电子产品类别部分重建亚马逊产品推荐系统

现在是十一月,黑色星期五到了!你是哪种类型的购物者?你是把当天想买的所有商品都存起来,还是宁愿打开网站,看看现场打折促销

尽管网上商店在过去十年中取得了令人难以置信的成功,显示出巨大的潜力和增长,但实体店和网上商店的根本区别之一是消费者的冲动购买。

如果顾客面前摆着各种各样的产品,他们更有可能购买原本不打算购买的东西。冲动购买现象居然被网上店铺的配置限制。同样的不会发生对于他们的物理同行。最大的实体零售连锁店让他们的顾客通过一条精确的路径,以确保他们在离开商店之前参观了每一个通道。

像亚马逊这样的网上商店认为可以重现冲动购买现象的方法是通过推荐系统。推荐系统识别客户刚刚购买或查看的最相似的互补的产品。其目的是最大化网上商店通常缺乏的随机购买现象。

在亚马逊上购物让我对其中的机制非常感兴趣,我想重现(甚至是部分)他们推荐系统的结果。

根据博客“Recostream”的说法,亚马逊产品推荐系统有三种依赖关系,其中一种是产品对产品推荐。当用户实际上没有搜索历史时,该算法将产品聚集在一起,并根据商品的元数据向同一用户推荐它们。

数据

项目的第一步是收集数据。幸运的是,圣地亚哥加利福尼亚大学的研究人员有一个存储库,让学生和组织外的个人使用这些数据进行研究和项目。可以通过下面的链接以及许多其他与推荐系统相关的有趣数据集来访问数据【2】【3】。产品元数据最后更新于 2014 年;许多产品今天可能无法买到。

电子类元数据包含 498,196 条记录,总共有 8 列:

  • asin —与每个产品相关联的唯一 ID
  • imUrl —与每个产品相关联的图像的 URL 链接
  • description —产品的描述
  • categories —每个产品所属的所有类别的 python 列表
  • title —产品的名称
  • price —产品的价格
  • salesRank —每个产品在特定类别中的排名
  • related —与每个产品相关的客户查看和购买的产品
  • brand —产品的品牌。

您会注意到该文件是一种“松散的”JSON格式,其中每一行都是一个JSON ,包含前面提到的作为一个字段的所有列。我们将在代码部署部分看到如何处理这个问题。

电子设计自动化(Electronic Design Automation)

让我们从一个快速的探索性数据分析开始。在清除了其中一列中至少包含一个NaN值的所有记录之后,我为电子产品类别创建了可视化效果。

有异常值的价格箱线图-作者图片

第一个图表是一个箱线图,显示了每种产品的最高价、最低价、第 25 个百分点、第 75 个百分点和*均价格。例如,我们知道一件产品的最高价值是 1000 美元,而最低大约是 1 美元。160 美元上方的线由组成,每个点都代表一个异常值。离群值表示在整个数据集中只出现一次的记录。因此,我们知道只有一种产品的价格在 1000 美元左右。

T2 的*均 T3 价格似乎在 25 美元左右。值得注意的是,库matplotlib通过选项showfliers=False自动排除异常值。为了让我们的箱线图看起来更清晰,我们可以将参数设置为 false。

价格箱线图—作者图片

结果是一个没有异常值的更加清晰的箱线图。图表还显示,绝大多数电子产品的价格都在 1 美元到 160 美元之间。

按列出的产品数量排名的前 10 大品牌——按作者排序的图片

图表显示了在亚马逊上销售的电子类产品的数量排名的十大品牌。其中有惠普、索尼、戴尔、三星。

十大零售商定价箱线图—作者图片

最后,我们可以看到前 10 名卖家价格分布。索尼和三星无疑提供了广泛的产品,从几美元一直到 500 美元和 600 美元,因此,它们的*均价格高于大多数顶级竞争对手。有趣的是, SIB 和 SIB-CORP 提供更多的产品,但*均价格更实惠。

图表还告诉我们,索尼提供的产品大约是数据集中最高价格产品的 60%。

余弦相似性

根据产品的特征将产品聚集在一起的一个可能的解决方案是余弦相似度。我们需要彻底理解这个概念,然后建立我们的推荐系统。

****余弦相似度衡量两个数字序列的“接*”程度。它如何适用于我们的情况?令人惊奇的是,句子可以被转换成数字,或者更好地,转换成向量。

余弦相似度可以取-1 和 1 之间的值,其中 1 表示两个向量在形式上相同,而 -1 表示它们尽可能不同。****

数学上,余弦相似度是两个多维向量的点积除以它们的大小的乘积【4】。我知道这里有很多不好的词语,但是让我们试着用一个实际的例子来分解它。

假设我们正在分析文档 A** 和文档 B 。文档 A 具有三个最常见的术语:“今天”、“好的”和“阳光”,它们分别出现 4 次、2 次和 3 次。文档 B 中相同的三个术语出现了 3 次、2 次和 2 次。因此,我们可以将它们写成如下形式:**

A = (2,2,3);B = (3,2,2)

两个向量的点积的公式可以写成:

他们的矢量点积不外乎 2x3 + 2x2 + 3x2 = 16

另一方面,单矢量幅度计算如下:

如果我应用我得到的公式

| | A | | = 4.12||B|| = 4.12

因此,它们的余弦相似度为

16 / 17 = 0.94 = 19.74°

这两个向量非常相似。

到目前为止,我们只计算了两个矢量与三维之间的的分数。一个单词向量实际上可以有无限多的维度(取决于它包含多少单词),但是这个过程背后的逻辑在数学上是相同的。在下一节中,我们将看到如何在实践中应用所有的概念。****

代码部署

让我们进入代码部署阶段,在数据集上构建我们的推荐系统。

导入库

每个数据科学笔记本的第一个单元应该导入库,我们项目需要的库是:

#Importing libraries for data management
import gzip
import json
import pandas as pd
from tqdm import tqdm_notebook as tqdm

#Importing libraries for feature engineering
import nltk
import re
from nltk.corpus import stopwords
from sklearn.feature_extraction.text import CountVectorizer 
from sklearn.metrics.pairwise import cosine_similarity
  • 解压缩数据文件
  • json解码它们
  • pandas将 JSON 数据转换成更易管理的数据帧格式
  • tqdm创建进度条
  • nltk处理文本字符串
  • re提供正则表达式支持
  • 最后,需要使用sklearn进行文本预处理

读取数据

如前所述,数据已经以松散 JSON** 格式上传。这个问题的解决方案是首先用命令json.dumps将文件转换成 JSON 可读格式行。然后,我们可以通过将\n设置为换行符,将这个文件转换成由 JSON 行组成的 python 列表。最后,我们可以将每一行添加到data空列表中,同时用命令json.loads将它作为 JSON 读取**。****

使用命令pd.DataFramedata列表作为数据帧读取,我们现在可以用它来构建我们的推荐器。

#Creating an empty list
data = []

#Decoding the gzip file
def parse(path):
  g = gzip.open(path, 'r')
  for l in g:
    yield json.dumps(eval(l))

#Defining f as the file that will contain json data
f = open("output_strict.json", 'w')

#Defining linebreak as '\n' and writing one at the end of each line
for l in parse("meta_Electronics.json.gz"):
  f.write(l + '\n')

#Appending each json element to the empty 'data' list
with open('output_strict.json', 'r') as f:
    for l in tqdm(f):
        data.append(json.loads(l))

#Reading 'data' as a pandas dataframe
full = pd.DataFrame(data)

为了让您了解data列表的每一行看起来如何,我们可以运行一个简单的命令** print(data[0]),控制台打印索引为 0 的那一行。**

print(data[0])

output: 
{
'asin': '0132793040', 
'imUrl': 'http://ecx.images-amazon.com/images/I/31JIPhp%2BGIL.jpg', 
'description': 'The Kelby Training DVD Mastering Blend Modes in Adobe Photoshop CS5 with Corey Barker is a useful tool for...and confidence you need.', 
'categories': [['Electronics', 'Computers & Accessories', 'Cables & Accessories', 'Monitor Accessories']], 
'title': 'Kelby Training DVD: Mastering Blend Modes in Adobe Photoshop CS5 By Corey Barker'
}

如您所见,输出是一个 JSON 文件,它用{}来打开和关闭字符串,每个列名后面都跟有:和相应的字符串。你可以注意到第一个产品缺少了pricesalesRankrelatedbrand information。这些列自动填充有NaN值。

当我们以数据框架的形式阅读整个列表后,电子产品显示出以下 8 个特征:

| asin   | imUrl   | description   | categories   |
|--------|---------|---------------|--------------|| price   | salesRank   | related   | brand   |
|---------|-------------|-----------|---------|

特征工程

****特征工程负责数据清理并创建列,我们将在其中计算余弦相似度得分。由于 RAM 内存的限制,我不希望专栏特别长,因为评论或产品描述可能会特别长。相反,我决定创建一个包含categoriestitlebrand 列的“数据汤”。但在此之前,我们需要消除这三列中包含 NaN 值的每一行。

所选的栏目包含了我们推荐者所需要的有价值的、本质的文本形式的信息。description列也可能是一个潜在的候选列,但是该字符串通常太长,并且在整个数据集中没有标准化。对于我们要完成的任务来说,它并不代表足够可靠的信息。

#Dropping each row containing a NaN value within selected columns
df = full.dropna(subset=['categories', 'title', 'brand'])

#Resetting index count
df = df.reset_index()

运行第一部分代码后,行数从 498,196 急剧减少到大约 142,000 ,这是一个很大的变化。只有在这一点上,我们才能创建所谓的数据汤:

#Creating datasoup made of selected columns
df['ensemble'] = df['title'] + ' ' + 
df['categories'].astype(str) + ' ' + 
df['brand']

#Printing record at index 0
df['ensemble'].iloc[0]

output: 
"Barnes &amp; Noble NOOK Power Kit in Carbon BNADPN31 
[['Electronics', 'eBook Readers & Accessories', 'Power Adapters']] 
Barnes &amp; Noble"

需要包括品牌的名称,因为标题并不总是包含它。

现在我可以继续进行清洁部分了。函数text_cleaning负责从集合列中删除每个amp字符串。除此之外,字符串[^A-Za-z0–9]过滤掉每个特殊字符。最后,函数的最后一行删除字符串包含的每个停用词

#Defining text cleaning function
def text_cleaning(text):
    forbidden_words = set(stopwords.words('english'))
    text = re.sub(r'amp','',text)
    text = re.sub(r'\s+', ' ', re.sub('[^A-Za-z0-9]', ' ', 
           text.strip().lower())).strip()
    text = [word for word in text.split() if word not in forbidden_words]
    return ' '.join(text)

使用λ函数,我们可以将text_cleaning应用于名为ensemble的整个列,我们可以通过调用iloc并指示随机记录的索引来随机选择随机产品的数据汤。

#Applying text cleaning function to each row
df['ensemble'] = df['ensemble'].apply(lambda text: text_cleaning(text))

#Printing line at Index 10000
df['ensemble'].iloc[10000]

output:
'vcool vga cooler electronics computers accessories 
computer components fans cooling case fans antec'

10001 行**上的记录(索引从 0 开始)是 Antec 的 vcool VGA 冷却器。这是一个品牌名称不在标题中的场景。**

余弦计算和推荐功能

余弦相似度的计算始于构建一个矩阵,该矩阵包含所有出现在集合列中的单词。我们要用的方法叫做“计数矢量化,或者更通俗的说法是“单词包”。如果你想了解更多关于计数矢量化的内容,你可以在下面的链接中阅读我以前的一篇文章。

由于 RAM 的限制,余弦相似性分数将仅在预处理阶段后可用的 142,000 条记录中的前 35,000 条记录上进行计算。这很可能影响推荐器的最终性能。

#Selecting first 35000 rows
df = df.head(35000)

#creating count_vect object
count_vect = CountVectorizer()

#Create Matrix
count_matrix = count_vect.fit_transform(df['ensemble'])

# Compute the cosine similarity matrix
cosine_sim = cosine_similarity(count_matrix, count_matrix)

命令cosine_similarity,顾名思义,计算count_matrix中每一行的余弦相似度。count_matrix上的每一行都是一个向量,包含出现在集合列中的每个单词的字数。

#Creating a Pandas Series from df's index
indices = pd.Series(df.index, index=df['title']).drop_duplicates()

在运行实际推荐系统之前,我们需要确保创建一个索引,并且这个索引没有重复。

只有在这一点上,我们才能定义content_recommender函数。它有 4 个参数:titlecosine_simdfindices。调用函数时,标题将是唯一要输入的元素。

content_recommender工作方式如下:

  • 它找到与用户提供的标题相关联的产品的索引****
  • 它在余弦相似矩阵中搜索产品的索引,并收集所有产品的所有分数
  • 将所有分数从最相似产品(接* 1)到最不相似(接* 0)进行排序****
  • 它只选择前 30 个最相似的产品
  • 它添加一个索引,返回一个熊猫系列的结果
# Function that takes in product title as input and gives recommendations
def content_recommender(title, cosine_sim=cosine_sim, df=df,
indices=indices):

    # Obtain the index of the product that matches the title
    idx = indices[title]

    # Get the pairwsie similarity scores of all products with that product
    # And convert it into a list of tuples as described above
    sim_scores = list(enumerate(cosine_sim[idx]))

    # Sort the products based on the cosine similarity scores
    sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)

    # Get the scores of the 30 most similar products. Ignore the first product.
    sim_scores = sim_scores[1:30]

    # Get the product indices
    product_indices = [i[0] for i in sim_scores]

    # Return the top 30 most similar products
    return df['title'].iloc[product_indices]

现在让我们在“Vcool VGA Cooler”上测试一下。我们想要 30 种类似的产品,客户会有兴趣购买。通过运行命令content_recommender(product_title),函数返回 30 条建议的列表

#Define the product we want to recommend other items from
product_title = 'Vcool VGA Cooler'

#Launching the content_recommender function
recommendations = content_recommender(product_title)

#Associating titles to recommendations
asin_recommendations = df[df['title'].isin(recommendations)]

#Merging datasets
recommendations = pd.merge(recommendations, 
                           asin_recommendations, 
                           on='title', 
                           how='left')

#Showing top 5 recommended products
recommendations['title'].head()

5 个最相似的产品中,我们发现了其他 Antec 产品,如 Tricool 电脑机箱风扇、扩展槽冷却风扇等等。

1    Antec Big Boy 200 - 200mm Tricool Computer Case Fan                                                            
2    Antec Cyclone Blower, Expansion Slot Cooling Fan                                                               
3    StarTech.com 90x25mm High Air Flow Dual Ball Bearing Computer Case Fan with TX3 Cooling Fan FAN9X25TX3H (Black)
4    Antec 120MM BLUE LED FAN Case Fan (Clear)                                                                      
5    Antec PRO 80MM 80mm Case Fan Pro with 3-Pin &amp; 4-Pin Connector (Discontinued by Manufacturer)

原始数据集中的related列包含消费者也购买、一起购买以及在查看 VGA 冷却器后购买的产品列表。

#Selecting the 'related' column of the product we computed recommendations for
related = pd.DataFrame.from_dict(df['related'].iloc[10000], orient='index').transpose()

#Printing first 10 records of the dataset
related.head(10)

通过在该列中打印 python 字典的头,控制台返回以下数据集。

|    | also_bought   | bought_together   | buy_after_viewing   |
|---:|:--------------|:------------------|:--------------------|
|  0 | B000051299    | B000233ZMU        | B000051299          |
|  1 | B000233ZMU    | B000051299        | B00552Q7SC          |
|  2 | B000I5KSNQ    |                   | B000233ZMU          |
|  3 | B00552Q7SC    |                   | B004X90SE2          |
|  4 | B000HVHCKS    |                   |                     |
|  5 | B0026ZPFCK    |                   |                     |
|  6 | B009SJR3GS    |                   |                     |
|  7 | B004X90SE2    |                   |                     |
|  8 | B001NPEBEC    |                   |                     |
|  9 | B002DUKPN2    |                   |                     |
| 10 | B00066FH1U    |                   |                     |

让我们测试一下我们的推荐者是否做得好。让我们看看also_bought列表中的一些asinid 是否出现在推荐中。

#Checking if recommended products are in the 'also_bought' column for
#final evaluation of the recommender

related['also_bought'].isin(recommendations['asin'])

我们的推荐人正确推荐了 44 种产品中的 5 种。

[**True** False  **True** False False False False False False False  **True** False False False False False False  **True** False False False False False False False False  **True** False False False False False False False False False False False False False False False False False]

我同意不是最佳结果,但是考虑到我们只使用了完整数据集中可用的 498,196 行中的 35,000 ,这是可以接受的。它当然有很大的改进空间。如果目标列的 NaN 值不那么频繁,甚至不存在,那么推荐可能会更准确,更接*实际的 Amazon 值。第二,使用更大的 RAM 存储器,或者甚至分布式计算,可以允许从业者计算更大的矩阵。

结论

我希望你喜欢这个项目,并希望它对未来的使用有用。

如文中所述,通过将数据集的所有行包含在余弦相似度矩阵中,可以进一步改善最终结果。最重要的是,我们可以通过将元数据数据集与存储库中其他可用的数据集合并,来添加每个产品的评审*均分。我们可以在余弦相似度的计算中包含价格。另一个可能的改进是建立一个完全基于每个产品描述图片的推荐系统。****

列出了进一步改进的主要解决方案。从未来实施到实际生产的角度来看,它们中的大多数甚至是值得追求的。

最后,我想以感谢 Medium 实现了如此有用的功能,让程序员可以在*台上共享内容来结束这篇文章。

print('Thank you Medium!')

最后,如果您喜欢该内容,请考虑添加一个关注,以便在新文章发表时得到通知。如果你对这篇文章有任何意见,请写在评论里!我很想读读它们:)谢谢你的阅读!

PS:如果你喜欢我写的东西,如果你能通过 这个链接 订阅一个中等会员,那对我来说就是全世界。有了会员资格,你就获得了媒体文章提供的惊人价值,这是支持我的内容的一种间接方式!

[1]亚马逊 2021 年的产品推荐系统:这家电子商务巨头的算法是如何工作的?—恢复数据流。(2021).检索于 2022 年 11 月 1 日,来自 Recostream.com 网站:https://recostream.com/blog/amazon-recommendation-system

[2]何,r .,&麦考利,J. (2016 年 4 月).起伏:用一类协同过滤对流行趋势的视觉演变进行建模。第 25 届国际万维网会议论文集(第 507-517 页)。

3麦考利,j .,塔吉特,c .,施,q .,,范登亨格尔,A. (2015 年 8 月)。基于图像的风格和替代品建议。在第 38 届国际 ACM SIGIR 信息检索研究与发展会议记录中(第 43–52 页)。

[4]f . Rahutomo,t . kita suka 和 m . arit sugi(2012 年 10 月)。语义余弦相似度。在ICAST 第七届高科技国际学生大会(第 4 卷第 1 页)。

[5]鲁纳克·巴尼克。2018.用 Python 实践推荐系统:开始用 Python 构建强大的个性化推荐引擎。包装出版。

如果你想看看我以前的一些文章

** **

构建一个语义分割的计算机视觉算法部署在边缘

原文:https://towardsdatascience.com/building-a-semantics-segmentation-computer-vision-algorithm-for-deployment-on-the-edge-3ad1a8922fd1

生产计算机视觉项目中的技术挑战和学习

照片由美国国家癌症研究所通过 Unsplash 提供

作者:雅道

在本文中,我们将讨论在边缘设备上部署语义分割算法的挑战和技术。特别是,我们将介绍在我们的项目中面临的技术挑战,这是一个部署在 iOS 设备上的自动伤口分割模型。

我们将首先简要介绍项目背景,然后介绍我们面临的技术挑战以及克服这些挑战的方法。

技术挑战 1 —小且不*衡的数据集

  1. 数据扩充
  2. 迁移学习
  3. 选择正确的指标
  4. 定制损失函数

技术挑战 2—边缘部署

  1. 性能与尺寸的权衡
  2. 量子化

技术挑战 3——创建灵活的再培训渠道

  1. 实验跟踪— WandB
  2. 配置管理—九头蛇
  3. 超参数搜索—贝叶斯优化

项目背景

今天,世界上大约有 10%的成年人患有糖尿病。在所有患有糖尿病的成年人中,25%的人将在其一生中发展为糖尿病足溃疡。如果不加以控制,糖尿病足溃疡可能恶化,导致组织和骨骼的严重损伤,这可能需要手术切除(截肢)脚趾、脚或腿的一部分。今天,85%的主要截肢手术之前都有糖尿病足溃疡。通过适当的伤口护理和干预,可以预防和治愈糖尿病足溃疡。

目前,糖尿病足溃疡的评估是由熟练的医生手工完成的。怀疑他/她患有糖尿病足溃疡的患者将不得不前往医院,在那里熟练的医生将观察伤口并手动测量和分类伤口。该过程不仅耗时,而且由于是手动过程,也可能不完全准确。此外,当使用尺子和探针测量伤口时,患者可能不得不承受疼痛和感染的风险。

图 1:糖尿病伤口的人工伤口评估。这个过程通常由护士在糖尿病伤口上的描图纸上完成。图片参考:https://commons . wikimedia . org/wiki/File:Diabetic _ Wound _ 121 . jpg

技术—语义分割

在这个问题中使用的计算机视觉技术被称为语义分割。这种技术通常用于自主导航。分割被认为是计算机视觉领域中最困难的任务之一,因为它必须根据类别对图像的每个像素进行分类。对我们来说幸运的是,在过去的几年里,这个领域已经有了令人难以置信的研究。在 PASCAL VOC 语义分割基准测试中,(https://papers with code . com/sota/semantic-segmentation-on-PASCAL-VOC-2012),我们可以找到许多对街道图像和物体相当准确的开源模型。虽然我们可以尝试许多开源语义分割模型,但没有一个模型是在与我们相似的图像(医学图像)上训练的。

图 2 :语义分割的例子,图像的每一个像素都根据其类别进行分类。图片参考:https://commons . wikimedia . org/wiki/File:Image-segmentation-example-segmented . png

技术挑战

在这个项目中。我们必须解决众多的技术挑战,即 1 号。小而不*衡的数据集2。边缘展开3。创建灵活的再培训渠道。在本文接下来的部分中,我们将详细讨论这些挑战以及我们如何克服它们。

技术挑战 1 —小且不*衡的数据集

我们面临的第一个技术挑战是拥有一个数据集,这个数据集不*衡。在医学领域,数据收集和注释非常昂贵。在数据收集方面,由于数据(个人数据)的敏感性,网上很少有糖尿病足溃疡的图像,收集这些图像通常需要用户许可。在数据注释上,伤口的识别通常由熟练的医生来完成。数据必须在图像注释工具(例如 CVAT)上手动注释,并由医生检查一致性。

幸运的是,我们与一家客户公司合作,该公司了解数据对人工智能项目的重要性。我们的客户与新加坡的多家医院和疗养院合作,收集糖尿病足溃疡的图像,甚至从该地区的其他医院购买数据。为了标注数据,该公司组建了一个标注工作组,由医生和训练有素的专业人员组成,他们以高度的一致性标注图像。

即使我们的客户付出了巨大的努力,与通常用于训练语义分割算法的数据集相比,数据集的大小仍然很小。此外,我们还面临着不*衡的数据集问题。在收集的所有伤口图像中,图像中超过 90%的像素是皮肤或背景图像。剩余的伤口像素被不均匀地分成 7 个以上的伤口类别,每个伤口类别约占所有像素计数的 0.2%至 2.7%。

我们将讨论一些克服小数据和不*衡数据挑战的方法。

1.数据扩充

克服小数据量挑战的第一个策略是通过扩充来人为增加数据集的大小。数据扩充是一种通过对现有数据执行几何(旋转、翻转、裁剪)和光度(改变对比度、亮度、模糊等)变换来人工增加训练数据集的大小和多样性的技术。这有助于打击过度拟合,从而使我们的模型更好地推广。

图 3: 数据增强技术。

我们还使用增强来绕过数据的其他特定限制 t。我们创造性地增强的一个例子是肤色。由于数据图像是从新加坡医院收集的,大多数糖尿病伤口图像来自亚洲患者,并且不包含许多具有不同肤色的图像。这降低了该模型对不同肤色患者的性能。为了克服这个问题,我们创建了增强功能,专门改变图像的肤色,以产生更大的图像多样性,并创建一个更公*的人工智能模型。

2.迁移学习

我们用来克服小数据集的第二个策略是使用迁移学习,即使用预先训练的模型参数来初始化我们要训练的模型的参数。这样做使我们能够利用使用更大数据集训练的预训练较低级特征,并从我们自己的数据集学习较高级特征。这不仅加快了训练时间,而且有助于在性能下降最小的情况下克服小数据集的问题。

然而,必须注意的是,迁移学习只有在用于训练预训练参数的数据集与我们的数据集有些相似时才有效,从而允许学习的低级特征可转移到学习我们自己的数据集中的高级特征。为了达到最佳的迁移学习效果,我们实验了冻结不同层的权重定制不同层的不同学习速率。对于我们的情况,我们的模型在使用预先训练的权重进行训练时比从头开始训练时表现得相对更好。

3.选择正确的指标

由于超过 90%的像素是皮肤/背景类,使用简单的度量标准,如准确度,仅仅通过将所有像素分类为皮肤/背景类,将给出不合理的高数字。此外,出于医学诊断的目的,在确定糖尿病足溃疡的严重程度时,一些伤口类别比其他类别更重要。为了解决这个问题,我们使用了特定于类的加权指标,比如 IOU 和 F1,来确定模型的准确性。

与客户讨论业绩指标至关重要。不同的利益相关者带来了不同的观点。例如,虽然管理团队要求模型在低于特定延迟的情况下运行,但医疗顾问更喜欢模型为几个伤口类别提供更高的优先级,工程师需要模型低于特定的文件大小,以便将整个应用程序放入 iOS 应用程序商店。最终,我们确定了一组度量标准,其中包括特定于类的加权度量标准,如 IOU 和 F1,以及与量化模型的推理速度和文件大小相关的度量标准。

4.定制损失函数

诸如交叉熵损失的标准损失函数使模型训练期间的总体预测误差最小化。但是,在不*衡数据集的情况下,交叉熵损失通常更重视多数类。理想情况下,我们希望有一个损失函数,它更加强调少数职业,同时我们也可以灵活地改变它的性能,以优化某一个创伤职业。尝试的一些损失函数包括分类病灶损失、病灶 Tversky 损失、敏感性特异性损失和具有定制权重的稀疏分类病灶损失。我们最终得到了一个焦点损失的变体,它对某些少数类的错误分类加重了模型的惩罚。

通过对损失函数的实验,我们能够大幅提高对更小但更重要的伤口类别的预测精度。

技术挑战 2 —边缘部署

克服了小型和不*衡数据集的问题后,等待我们的下一个挑战是边缘部署。对于用于医疗诊断的模型,该模型必须由医疗保健法定委员会监管和批准。此外,由于伤口图像是个人数据,所有图像在部署期间必须保存在本地。这两个因素使得云部署不太可能,并将我们推向了边缘部署。

1.性能与尺寸的权衡

边缘部署的特征之一是对模型大小的限制。因为我们的模型需要部署在 iOS 的应用程序中,所以模型的大小必须足够小,以便可以托管在苹果应用程序商店中。

通常,人工智能模型的性能与模型的大小直接相关;尺寸越大,越准确。PASCAL VOC 性能基准测试中的大多数先进算法都太大,无法安装在边缘设备上。因此,我们必须变得有创造性,以实现良好的性能与尺寸的权衡。

为了在实现良好性能的同时减小模型的尺寸,我们将最先进模型的主干换成了更小、更适合移动的主干,如 MobileNet 或 EfficientNet。从那里,我们能够调整阿尔法——网络的宽度,以减少我们的模型的大小。之后,我们执行量化和修剪,以进一步减少模型的大小,我们将在下一节讨论。

2.量子化

量化是一种转换技术,可以减少模型大小,同时改善 CPU 和硬件加速器延迟。大多数深度学习框架,如 Tensorflow,本身就支持量化,在 Tensorflow 中量化模型非常简单。量化的挑战在于选择正确的量化技术,它可以在给定硬件的情况下产生最快的推理速度,同时保持其准确性。

我们试验了许多量化技术,从量化感知训练到训练后量化,再到动态范围、全整数和浮点 16 量化。我们从实验中学到的是,量化理论可能不总是与实际部署观察完全一致。例如,虽然 int8 量化应该在装有机器学习加速器芯片的 iPhone 上产生最快的推理速度,但当我们部署到 iPhone 12 时,它的执行速度没有 float16 量化快。此外,修剪等技术并不适用于所有架构,使用权重聚类只会减少压缩后的模型大小,而不会减少 TensorFlow Lite 模型大小,因此它对我们的用例没有用处。

技术挑战 3 —创建灵活的再培训渠道

除了生成一个表现相当好的模型之外,我们还需要建立一个保留管道,这样我们的客户就可以在初始部署之后持续收集更多的数据并改进模型。因此,我们的首要任务之一是让源代码易于阅读,被适当地记录,并且实验能够高效且易于跟踪。为了实现这一点,我们实现了一些工具。

1.实验跟踪— WandB

我们发现在进行深度学习项目时,拥有一个实验跟踪*台非常有用。在我们的案例中,我们选择了 WandB,因为它具有视觉吸引力,易于使用,并且有一个免费版本,我们可以在项目结束后交给我们的客户。鉴于我们必须调整的参数数量很大,在整个项目过程中,我们进行了一千多次实验。如果没有实验跟踪*台,就不可能跟踪我们已经进行的实验,也不可能比较不同的实验。

图 5:WandB 上的实验跟踪。图像取自项目的 WandB 实例。

2.配置管理—九头蛇

深度学习项目中的可配置参数数量巨大。在训练管道的几乎每个部分都有可配置的参数,从数据管道到扩充、模型架构、损失函数,一直到细节,例如调整耐心以尽早停止。为了管理大量的参数,我们让所有的配置远离源代码,以提高效率和减少错误。

我们组织配置的方式是使用 Hydra 库。Hydra 是一个用于优雅地配置复杂应用程序的框架。它允许我们轻松地换入和改变配置。例如,当我们尝试不同的损失函数时,我们简单地将每个损失函数保存在一个配置文件中,当我们尝试不同的损失函数时,就换出一个配置文件。

3.超参数搜索—贝叶斯优化

最后,深度学习实验需要在许多参数的大型搜索空间中迭代,每个参数的大小不同。事实证明,网格搜索在这方面效率相当低。贝叶斯优化使用贝叶斯定理来指导搜索,以便找到目标函数的最小值或最大值。这让我们能够用更少的运行次数搜索更大的参数空间,这极大地加快了我们的实验速度。

结论

经过半年多的代码库构建和紧张的实验,我们能够创建一个相当准确的语义分割模型,能够对糖尿病足溃疡图像进行分类和分割。

在接下来的几个月里,客户公司将部署该模型进行测试。随着实际部署,他们将能够在反馈循环中收集更多数据,并不断提高数据质量和数量,不断改进模型。有了这个,我们希望这个算法将有助于改善和拯救生命。

我们要感谢 AI Singapore ,我们的项目经理和导师给了我们这个参与这个项目的机会。在所有关键的收获和经验教训中,我们认为决定人工智能项目成功的最重要因素是管理层的支持和领导。因此,我们真诚地感谢客户公司对我们的支持和信任,使这个项目成为可能。

感谢您抽出时间阅读。我们祝你的人工智能项目一切顺利!

人工智能工程师:桑托什亚道

项目技术导师::潘·蜀汉·达里尔,张俊彦

项目经理:杨迪强,吴金福

关于作者

陈— 陈毕业于新加坡国立大学,获得统计学学位。他一直热衷于数字,并很高兴能够将他的技术能力应用于人工智能解决方案。

孟勇·李— 孟勇毕业于新加坡管理大学,获得人工智能专业硕士学位。今天,孟勇建立了可扩展的生产 ML 系统,专注于计算机视觉。他对人工智能的应用方面最感兴趣。

Santosh Yadaw — Santosh 毕业于南洋理工大学,获得物理学学位。在加入 AI 新加坡的学徒计划之前,他在一个政府法定委员会担任高级工程师,管理复杂的国防项目。Santosh 本质上是一名修补工程师,热衷于构建可持续的人工智能应用程序,以帮助带来积极而有意义的影响。

使用 Cognitive 和 CDKTF 构建无服务器 Azure ML 服务

原文:https://towardsdatascience.com/building-a-serverless-azure-ml-service-using-cognitive-and-cdktf-8f1217f39bbb

在本教程中,我们将回顾使用云服务,如 Azure Functions 和 Cognitive 来构建情感分析服务

亚历山大·帕萨里克的照片来自 Pexels

为了构建我们的服务,我们将使用以下内容:

  • cdktf : 将允许我们用 python 在 Azure cloud 中构建基础设施
  • azure 功能 : 用于运行应用的无服务器云服务
  • azure 认知服务 : 可通过 API 访问的现成 AI 服务
  • vs 代码 : 代码编辑器用于编写我们的应用和部署 azure 功能

TL;DR:代码在GitHub上。

在本文中,我们将在 Azure 中部署和开发一个 ML 情感分析服务。我们将使用 cdktf 部署基础设施,并编写一个 rest 服务,将情感分析逻辑应用于即将到来的请求。整个栈都是用 python 写的。

步骤 1:编写 azure 基础设施

为了构建和部署基础设施,我们需要访问 azure 帐户(此处提供免费试用注册)和安装在我们环境中的 cdktf。我们可以使用 brew 实现这一目的:

brew install cdktf

好了,现在我们可以开始编码了。在 cdktf 中,我们需要做的第一件事是创建包含所有 azure 组件的主类,我们称之为 MLAzureStack:

正如我们所看到的,我们需要从 TerraformStack 继承,还需要创建一个azuremprovider,它将帮助与 azure 云 API 进行通信,以便构建所需的基础设施。

接下来,我们将创建一个资源组。这是一个逻辑容器,将容纳我们部署的所有基础架构。这是一个将你所有的资源组合在一起的好方法。

我们使用一个类 StackVariables 的 vars 实例,它将保存我们基础设施的所有自定义值。请访问 github repo 了解有关这方面的详细信息。

在下一个代码片段中,我们将创建一组在部署 Azure 功能时使用的资源:

存储帐户将托管我们的功能应用程序所使用的容器的文件系统。这是代码上传的地方,也是日志和临时文件写入的地方。

application insights 是一个监控组件,它将帮助我们从 azure 功能中收集指标和日志。

服务计划定义了可用于该功能的计算资源,还定义了其扩展方式。

我们可以定义 azure 函数基础设施。我们可以看到我们引用了资源组、服务计划和存储帐户。我们正在创建一个运行 python 代码的 linux 函数。我们还定义了另外两个应用程序设置 AZURE_LANGUAGE_ENDPOINTAZURE_LANGUAGE_KEY ,它们将指向我们接下来创建的认知资源。它们将在实际的应用程序中作为环境变量被读取。

最后一块基础设施是认知账户,它将运行情感分析 ML 预训练算法。我们使用认知服务端点作为输出变量,因此我们可以在实际的 python 函数中使用它。

为了能够运行 cdktf cli,我们必须使用az login登录 azure cloud。微软有很好的关于如何安装 azure cli 的文档。

然后我们可以检查哪些资源将被部署到 azure 中,使用:

cdktf diff

我们可以看到提供者正在初始化,类似于我们通常在 terraform 中看到的情况:

作者图片

最后,我们可以部署堆栈:

cdktf deploy

如果一切都运行成功,我们应该看到已经添加了 9 个资源:

作者图片

如果我们导航到 azure 门户,我们应该看到在资源组中创建的所有资源:

作者图片

第二步:编写 azure 函数代码

这一步将包括编写 azure 函数 python 代码。如果你在 AWS 或 GCP 使用过其他无服务器服务,你会注意到类似的模式。

我们为 azure 功能和认知服务导入所有必要的库,并创建文本分析服务,这将帮助我们使用情感分析 API。正如我们在上一步中看到的,一旦在 cdktf 中定义了 AZURE_LANGUAGE_ENDPOINT 和 AZURE_LANGUAGE_KEY,我们就可以在这里读取它们,因为需要它们来读取认知 API。

在函数的主体中,我们尝试读取请求中传递的句子字段,如果我们没有找到它,我们将向用户发送一个 404 无效请求。如果我们有了必填字段,我们就调用文本分析服务,获取情感标签(正面、负面、中性),然后将其发送回客户端。

该请求具有以下格式:

{
   "sentence": "This is an amazing day!"
}

步骤 3:使用 vscode 部署和测试服务

我们可以下载本教程的源代码,并使用 vscode 来编辑和部署代码。可以从这里下载 Vscode。

我们还需要在 IDE 的扩展选项卡中安装 Azure Functions 扩展:

作者图片

如果我们安装了所有东西并从 GitHub 加载了项目,我们可以进入 vscode 中的 azure 选项卡,我们应该会看到 azure 函数(在我们登录后)和本地项目:

作者图片

接下来我们可以将该功能部署到 azure。我们可以通过点击“功能”选项卡中的云图标来实现:

作者图片

将弹出一个菜单,我们可以在其中选择已经部署的功能:

作者图片

几分钟后,我们将收到部署成功的消息:

作者图片

部署后,我们可以直接从 vscode 测试功能。在函数选项卡中,我们可以右键单击部署在 azure 中的函数,然后运行立即执行函数:

作者图片

这将打开另一个消息框,我们可以在其中编写将发送给 azure 函数的请求:

作者图片

我们应该收到来自服务的带有我们请求的情感标签的回答:

作者图片

如果我们对函数的响应有任何问题,检查任何问题的最简单方法是查看日志。为此,我们需要登录 azure 门户,然后进入资源组-> cdktfsentml-rg-> cdktfsentml-function。在函数窗口中,我们需要进入函数选项卡并选择我们的 cdktfsentml 函数:

作者图片

进入 Monitor 选项卡,您将获得关于该函数每次调用的许多详细信息:

作者图片

如果我们想从 azure 中移除我们的基础设施,我们可以再次运行 cdktf,几分钟后它应该会移除所有内容:

cdktf destroy

就是这样!我希望你喜欢这个教程,并发现它很有用!我们已经着手在 Azure 中创建一个完整的服务,从构建基础设施到构建和测试 ML 服务。我们看到了如何在 python 中端到端地构建我们的产品,以及使用预构建模型来运行众所周知的 ML 问题(如情感分析)是多么容易。当在生产中使用像 Azure Cognitive 这样的服务时,我们需要评估与构建和训练我们自己的算法相比的成本。此外,我们还看到了如何利用 Azure Functions 这样的无服务器产品来保持低成本,如果我们的服务不经常使用,并且没有必要建立一个 24/7 服务器解决方案。

建立一个简单的人工智能驱动的人在回路系统来管理野生动物相机陷阱图像和注释

原文:https://towardsdatascience.com/building-a-simple-ai-powered-human-in-the-loop-system-to-manage-wildlife-camera-trap-images-caec966d7b59

带着目标和同理心为非营利组织寻求可持续的技术解决方案

猫科动物保护基金会资助的普里西马溪红杉保护区中的美洲狮

在这篇文章中,我简要记录了我设计和构建人工智能驱动的人在回路系统的旅程,以管理猫科动物保护基金的相机陷阱图像&注释。如果你对技术在野生动物保护、构建计算机视觉应用或与非营利组织合作中的作用感兴趣,你可能会发现这篇文章很有用。如果你想在进入这篇文章之前有一个高层次的概述,你也可以看看这个幻灯片

猫科动物保护基金会是一个非营利的野生动物研究和保护组织,总部位于旧金山湾区,专门研究野猫,尤其是美洲狮。为了促进他们的研究,他们主要使用相机陷阱,到 2021 年,他们的业务已经发展到超过 100 个相机陷阱,每年生成超过 100 万张图像。然而,这种增长也意味着他们的数据管道开始淹没他们的底层软件工具,促使他们寻找更具可扩展性的解决方案。

他们知道将基于云的解决方案与人工智能/人工智能结合使用是一条出路,为了帮助他们建立这种解决方案,他们在 LinkedIn 上发布了一个招募志愿者的帖子,我碰巧发现了这个帖子。对于在大卫·爱登堡长大的人来说,单是“保护”这个词就足以引发兴奋感,但当我得知他们需要一个基于云的人工智能解决方案来改善他们的数据管道时,我被说服了!

1.笑一笑,美洲狮,你上镜头了!

在我们开始之前,让我们快速了解一下什么是相机陷阱,以及它们如何帮助野生动物研究。这里有一个 TL;来自本 WWF 帖子的博士——

  • 相机陷阱是一种带有传感器的相机,可以检测前方的活动,并自动触发图像或视频捕捉。
  • 他们让野生动物研究人员以不干扰它们自然行为的方式收集关于我们同类的数据。

现在,你可能在想,“但是为什么我们不能像我们做人口普查一样,穿过森林采访这些动物,并在最后击掌庆祝呢?”。对此,我显然会说,“好主意!我喜欢和五只美洲狮击掌,但我认为生态学家会翻白眼。然而,严肃地说,“在森林里漫步并收集数据”实际上是一种有效的技术,十多年前我曾经自愿这样做,但这是一个昂贵的、劳动密集型的过程,需要人们在森林里踮着脚尖,冒着被毫无防备、受到惊吓的大象踩成煎饼的风险,就我而言!

虽然收集数据的不同技术有不同的权衡,但在猫科动物的情况下,相机陷阱是一个很好的来源,因为猫通常很害羞,喜欢夜间活动。因此,为了支持他们的研究,Felidae 操作了 100 多个照相机陷阱,主要在旧金山湾区,每年产生成千上万的图像!

2.数据供应链

虽然相机陷阱很棒,但它们生成的原始图像在变成我们都爱的甜蜜科学之前还有一段路要走。通常,研究人员需要知道

  • 照片拍摄的地点——这是通过标注纬度&经度设置相机陷阱时收集的。
  • 图像拍摄时间—相机陷阱为每张图像生成一个时间戳,指示图像拍摄时间。
  • 图像中的动物是什么——一旦收集了图像,注释者就会查看每张图像,并记下其中的动物。

正如您可能猜到的那样,手工注释这些图像是这个流程中最耗时的过程。但是当你考虑到相机失灵、错误、遮挡、吹树叶、月球漫步的小妖精(好吧,这可能是我编的)等等因素时。,你会得到很多甚至没有任何动物的图片。如果这还不够,只是为了迷惑研究人员,这些相机陷阱偶尔会决定将他们的内部时钟重置为 1970 年的某个时间,从而打乱所有的时间戳。他们似乎就是不能抓住机会!因此,为了管理这种错综复杂的数据管道,研究人员/组织根据其规模和对资源的访问权限设置了不同的工作流。

为了更好地理解这一点,让我们仔细看看 Felidae 的数据管道,它们遇到的不同问题,以及用于解决这些问题的工具套件。大体上,他们的工作流程可以分为三个任务

  1. 设置相机陷阱 —相机陷阱被布置在预定的位置,为了跟踪它们,Felidae 使用 Google Sheets 将相机陷阱的 id 与其纬度&经度关联起来。
  2. 收集图像——每隔一段时间,来自这些相机陷阱的存储卡就会被收集起来,通过收存箱或者通过物理邮件传送到办公室。Felidae 的工作人员随后将这些图像以及有关源摄像机陷阱的信息保存到物理计算机上。
  3. 注释图像 — Felidae 使用一个基于微软 Access 的工具 Camera Base 来处理注释。因为它运行在一台物理计算机上,所以在任何给定的时间,访问都仅限于单个注释器。因此,注释者使用共享的 Google 日历安排访问,然后使用 TeamViewer 远程登录来注释图像。

一旦对图像进行了注释和清理,数据就从相机库中导出,并根据需要使用 R/Excel 进行提取,以回答他们的研究问题。整个过程是由一群敬业、勤奋的人推动的,他们包括数百名志愿者和一小部分实习生&全职员工。

猫科动物的数据供应链(图片来自作者)

3.向上,向上,远离云

当我第一次了解到猫科动物的工作流程时,这是我的反应—😳😧💀我的猜测是,既然你也已经知道了,你可能已经不远了。我记得当时想,“微软 Access?TeamViewer?共享日历?我刚才是不是穿越时空了?”。但是他们已经知道了!他们痛苦地意识到他们工作流程中的低效率,尤其是他们的一次一个注释者的瓶颈和遗留下来的大量未注释图像。更不用说硬盘故障和数据丢失的潜在风险。然而,当时他们知道迁移到云是解决之道,并且已经尝试过,但是却没有明确的前进道路。

3.1.圣杯

让我们后退一步,再次查看工作流,找出潜在的改进领域。第一个,也是最明显的一个,是远离作为注释工具的相机底座,公*地说,它可能没有考虑到同时的用户访问。通过构建一个 web 应用程序来代替它,可以通过允许多个注释器同时独立工作来消除注释瓶颈。第二种,不太明显的一种,是使用机器学习,特别是计算机视觉,来自动识别图像中的动物。这项技术在最*几年取得了重大进展,并且有可能极大地提高注释器的吞吐量,它被野生动物研究人员广泛认为是“游戏规则的改变者”。因此,从猫科动物的角度来看,这些方向的进展将大大改善他们的工作流程,并腾出时间来做更多的科学工作。但是,正如莫斐斯的名言所说,“知道道路和走在道路上是有区别的”,这种区别在非营利组织中更加危险。

3.2.非营利难题

非营利组织资源匮乏不足为奇,尤其是规模较小、更具地方性的非营利组织。他们很难为其核心职能提供资金,因此在投资新技术时,他们倾向于采取更保守的方法。即使他们雄心勃勃,但他们往往缺乏内部专业知识,不知道从哪里开始。因此,他们期待志愿者的到来并伸出援手。虽然这一开始看起来像是获得了“免费工作”,但随之而来的成本并不明显。不幸的是,现实并没有免费的午餐!

那么,这些成本是什么?虽然与盈利性组织可能面临的情况没有什么不同,如减员、入职、决策失误等。然而,幅度被显著放大。为什么?因为志愿者倾向于白天工作,他们的可用性和承诺水*是高度可变的。此外,可用的志愿者资源通常很少,在经验、专业知识和期望方面差异很大。因此,当非营利组织经历志愿者的流失或重影时,他们的项目往往会停滞不前,改变方向,或者在最糟糕的情况下,彻底失败。

3.3.通往一体化地狱的高速公路

Felidae 的技术努力始于 2020 年,此后出现了一系列志愿者,他们致力于他们更广阔愿景的不同方面。结果是不同成熟度的多线程工作跨越了一系列技术,这些技术密切反映了构建它们的人的背景。让我们快速看一下它们是什么—

  • 一个数据库模式连接来自相机陷阱、志愿者、图像、注释等的信息。起草为原始 SQL 查询。它是经过深思熟虑的,但是还没有投入生产,并且没有数据,在一个 60 美元/月的 Google CloudSQL 实例上。
  • 一个部分建成的节点应用程序与这个数据库接口。然而,大部分代码在少数几个表上实现了基本的 CRUD(创建、读取、更新、删除)操作,并且没有部署到任何地方。
  • 一个部分构建的基于 React 的 UI,还没有与后端集成。
  • 一个经过内部训练的计算机视觉模型,可以区分有和没有动物的图像。作为原始 PyTorch 代码分发,工作人员必须在办公室计算机上用一系列命令执行这些代码,这是唯一一个被积极集成并提供大量价值的组件。

到 2021 年夏天,当我第一次与他们交谈时,大多数从事这些线程工作的人都不在了。其余的人在相当长的一段时间内试图将这些线完成并缝合成一个有凝聚力的产品。他们当时不知道的是,他们正沿着高速公路直奔被人们亲切地称为“融合地狱”的地方。

4.有时候,后退一步就是前进一步

虽然按照现有的轨迹直接进入一个项目是合理的,但重要的是要记住,作为一个新人,你会带来一个相对不受主流思想泡沫影响的全新视角。虽然这能让你找到更好的解决方案,但也让你容易重复过去的错误。不用说,有很多作业要做。

4.1.家庭作业

从没有任何领域知识开始,我花了前几周时间与 Felidae 的一些工作人员和过去的志愿者交谈,他们非常亲切地帮助我对他们的工作流、痛点和乌托邦有一个强烈的感觉。他们还详细记录了他们当前的现代化之旅、过程中的决策点及其背后的基本原理。由于这样的对话往往遵循叙事结构,我需要将他们的问题是什么他们如何试图解决问题或者认为应该解决的问题分开。实现这一点的最佳方式是制作一份结构化的文件,不仅作为他们口述历史的书面版本,还作为未来对话的有用锚。简单来说就是一篇复习论文!

为了补充这一点,我也有相当多的关于相机陷阱数据管道的内容,这让我发现了来自丹·莫瑞斯的这篇奇妙的博客文章。很快就变得很明显,Felidae 并不是唯一一个在看似支离破碎的软件工具领域导航的人,但是激起我兴趣的是社区*年来为解决这个问题所做的重大努力。最值得注意的是,从 2018 年开始,使用机器学习自动检测相机陷阱图像中的动物已经正式成为在 CVPR 的一个研讨会举行的竞赛,这是一个受欢迎的计算机视觉会议。此外,微软的 AI for Earth 已经开源了一个这样的模型,名为 MegaDetector 供大家使用。另一项合作项目 Wildlife Insights ,旨在通过提供一种基于云的人工智能解决方案来标准化相机陷阱图像工作流程,这正是 Felidae 试图打造的东西!

4.2.先买,后买,最后建

从头开始构建东西的冲动是很难抗拒的,尤其是在灵活性和定制的诱惑下。毕竟,创造的欲望很高,来自外部解决方案强加的约束的不适也很高。但是,每一个决定都是一种权衡,而且,灵活性带来了前期和维护成本。正如我前面提到的,这些决定与营利性组织不得不做出的决定没有什么不同,但在技术快速发展、人员流失加剧和缺乏内部专业知识的背景下,非营利组织不得不在更大的不确定性下做出这些决定,这使得这些成本更有可能被低估。

换句话说,非营利组织面临着与营利组织相同的构建与购买困境,尽管约束条件略有不同。一方面,他们需要建设或购买的资本和劳动力较少。另一方面,由于开源社区,他们有了更多的免费选择。在这种情况下,一个很好的经验法则是首先接受一个强烈的偏向,支持免费的解决方案,同时放松质量门槛。接下来是寻找可购买的选择,特别是来自大型成熟组织的选择,这些组织提供低成本、稳定的产品,并可能为非营利组织提供折扣。最后,只有当(1)让工作流适应现有解决方案的效率太低和/或(2)有机会被其他非营利组织重用时,才求助于构建。

在他们的旅程中,Felidae 严重倾向于建造太多的东西,投入了超过 18 个月的时间和精力,开始屈服于臭名昭著的沉没成本陷阱。虽然向前迈进显然意味着剔除大部分工作线索并后退一步,但对我来说,认识到这样一个事实也很重要,即激进变革的提议,尤其是来自一名新志愿者的提议,可能并理所当然地会引发恐惧和怀疑。因此,当我让时间建立信任时,我致力于解决他们的问题,同时将他们推向外部解决方案,即使这意味着在他们的工作流程中增加额外的一两步。

5.少即是多

在很高的层面上,Felidae 需要一个集成了以下功能组件的单一系统——

  1. 库存管理对相机陷阱、盒子、内存卡等实物库存进行编目。跟踪他们在战场上的部署。
  2. 摄像头陷阱管理追踪这些摄像头在研究期间监控的物理位置。
  3. 图像管理以简单、结构化的方式存储、浏览和共享他们收集的成千上万张图像。
  4. 注释管理给这些图片加上它们所包含的动物标签。
  5. 收集、上传和注释这些图片的志愿者和工作人员的用户管理

如前所述,Google sheets 用于跟踪库存、相机陷阱和用户;Dropbox/Windows 文件系统管理的图像;最后,摄像机基础处理注释。然而,这些都没有在下面相互连接,这样做需要大量的体力劳动!当时,虽然没有万能的灵丹妙药(Wildlife Insights 已经很接*了),但是有大型活跃的工作组支持的相当成熟的子组件。因此,最合理的选择是简单地采用这些来代替内部线程,并将事情削减到只有一个薄薄的粘合层,即 web 应用程序。

我在这里的指导原则,由无数黑客马拉松塑造,是一个偏爱速度的原则。因此,当我开始时,我的首要任务是尽快部署一个基本但功能齐全的端到端产品。在给定的环境下,这种方法是理想的,因为在开发过程中,必须尽早将最终用户纳入进来。此外,作为一个内部应用程序,对错误和故障的容忍度更高。但是,也许更重要的是,它提供了当时非常需要的东西——18 个多月后的切实进步感,这将鼓舞士气,并在最*建立信任。

5.1.被解放的姜戈

核心粘合组件,即 web 应用程序,是以遍布 SQL、Node & React 的数千行部分功能代码的形式存在的。但令我惊讶的是,大多数(如果不是全部的话)只是在基表上实现了 CRUD 操作。虽然这些框架中可能有提供相同功能的第三方包,但我和其他任何人都不太了解这个生态系统,无法继续这些思路。然而,我对另一个流行的框架有相当多的经验, Django

现在,添加新的技术依赖不是一件容易的事情,特别是当这种依赖会渗透到单点故障时,在这种情况下,就是我。但是,我对自己的承诺很有把握,在这种情况下,考虑到姜戈提供的众多好处,冒这个风险似乎是合情合理的

  1. 一个用于基本 CRUD 操作的内置 GUI, Django Admin 接口,以及一组可供选择的皮肤(我使用 Jazzmin )。
  2. 令人惊叹的 django-allauth 软件包增强了内置的用户管理系统。
  3. ORMs 超过原始 SQL,我发现它更简单、更干净(个人偏见)。
  4. 一种统一的语言,Python,将跨越 web 应用程序、数据库和 AI/ML 模型,为志愿者在系统的不同领域做出贡献降低了障碍。

带着对 Django 永恒的爱,我重温了我的 web 开发技能,开始着手做这件事。幸运的是,正如我所希望的那样,我不仅能够替换现有的数千行代码,还能够用大约一百行代码添加更多的功能!就这样,在几个喝着咖啡的晚上,我能够建立一个 web 应用程序,消除五个功能组件中的三个——库存、相机陷阱和用户管理——这都要归功于 Django 及其周围生态系统的惊人力量。

5.2.请再来点收纳箱。

下一个稍微棘手的组件是图像管理。这里的棘手之处不在于图像上传的处理,而在于它的庞大数量!直接从存储卡中取出的图像会以成千上万的形式出现,在某些情况下,会以数万的形式出现,这意味着 web 应用程序需要暂停/恢复上传、删除重复图像、验证图像完整性等功能。接下来是浏览、分享和操作这些图像的用户界面。不幸的是,Felidae 严重低估了这方面的努力水*,他们不仅制定了这个崇高的计划,还在浏览器内的 ML 模型中分层,以在上传之前过滤掉空图像。一个小型非营利组织的白日梦!

根据主题,我的目标是少建,而不是多建。幸运的是,几乎所有提供云存储的公司都解决了这个问题,而且锦上添花的是,Felidae 已经使用 Dropbox 来传输他们的图像。因此,更多的 Dropbox 实际上意味着更少的工作,抵消了他们将图像下载到办公室电脑上的需求。更重要的是,Dropbox 有两个主要优势——( 1)简单、直观的 Python SDK,便于与 Django 集成;( 2)易于使用的“文件请求”功能,甚至允许非 Dropbox 用户上传文件——这使得它明显优于其他替代品,如 Google Drive。

从用户的角度来看,一旦从相机陷阱中收集到存储卡,他们只需登录网站并提交在线表格(以前在纸上完成)。接下来,他们会收到一个上传图片的 Dropbox 链接。就是这样!在幕后,Django 会自动浏览上传的图像,并在数据库中添加一层必要的元数据。在这一阶段,Dropbox 通过其 API 展示了一套功能,再次证明了它非常有用。具体来说,

  1. 它使识别文件类型(图像、视频、文档等)变得容易。)特别是对于媒体,它还暴露了 Exif 属性,如时间戳和位置(纬度/经度)——这对相机陷阱至关重要!
  2. 它自动生成内容哈希来唯一识别图像。
  3. 最后也是最重要的,它提供了一个简单的缩略图 API。这意味着 Django 只能提取更大的源图像的更小的压缩版本(~5MB)进行额外的处理&渲染。

少就是多!

5.3.MegaDetector & Annotorious,动态二人组

最后,也是最关键的,是时候用更高效的东西替换相机底座了。概括地说,这需要两个组件——( 1)用户添加注释的用户界面,以及(2)可以学习和复制这些注释器的 AI/ML 模型。在这里,Felidae 确实有一个胜利,那就是一个训练有素的模型,它可以以合理的准确度检测图像中动物的存在。但是,他们完全错过了 MegaDetector ,一个由大型社区支持的类似模型,并根据数量级以上的数据进行训练。作为一个额外的奖励,虽然内部模型将“空白”/“非空白”标签贴在整个图像上(图像分类器),但 MegaDetector 可以检测人、动物和车辆以及它们在图像中的位置(对象检测器)。不用说,斧头必须砍在内部模型上,随着最*发布的新一代 MegaDetector】,这个决定显然已经得到了很多倍的回报!

AI/ML 模型是狡猾的小生物,它们使一切都变得明显随机,MegaDetector 也不例外。没有它,每个图像都需要人工检查,有了它,图像就有可能需要它。不幸的是,解释这些数字并不是一件容易的事情,在这个阶段未被发现的错误模式有可能严重扭曲下游分析。公*地说,即使获得了更多的信任,人类注释者也是随机的,他们有自己的任期相关的错误率。因此,一个健壮的系统应该是一个协作的系统,其中注释者不仅覆盖 AI 模型的盲点,还覆盖彼此——同时随着时间的推移,将手工劳动的量最小化。

实现这一点的第一步意味着一个用户界面将呈现出 MegaDetector 预测的边界框,同时还允许注释者编辑或添加他们自己的边界框。在计算机视觉应用中,这是一项常见但重要的任务,整个公司都围绕着提供这样的功能而建立,在检查我的选项时,我偶然发现了一个免费但非常棒的库,名为注释,它似乎是量身定制的!凭借简单、直观的界面,Annotorious 使 Django 网站成为一个简单的注释*台,不仅取代了相机库,还支持收集更细微的对象级数据,MegaDetector 和其他人工智能模型可以从中学习。

5.4.FaaS、PaaS 和 IaaS

因为我的首要任务是让 Felidae 的员工参与进来,所以让应用程序实时可用非常重要,即使是以准系统的形式。因此,关于底层软件基础设施的决策必须从一开始就做出。但是在开始之前,让我们后退一步,看看系统的不同组件,只是这次是作为软件的一部分。大体上,我们有—

  1. 网络应用,Django,作为用户与系统交互的主要界面。
  2. 一个关系数据库用来存放关于库存、相机陷阱、用户、图像和注释的结构化数据。
  3. 媒体资产主要包括要在网站上呈现的摄像机捕捉图像的压缩版本。
  4. AI/ML 模型像 MegaDetector 一样检测和识别图像中的对象。

十年前,我的直觉是租用一个裸机实例,将所有这些组件竖立起来,但现在,随着成熟云服务的出现,有了更多选项可供选择。随着 Felidae 已经在谷歌云*台(GCP)上开始,任务归结为找出正确的服务集来使用,同时强烈倾向于速度和易于维护。

首先,为 Django 使用应用引擎几乎是一件容易的事情,因为它有慷慨的免费层、内置的自动缩放和优秀的入门文档。用云 SQL它的代理建立 Postgres 数据库很容易,使得本地开发变得轻而易举。对于媒体资产, django-storages 使得存储到云存储和从云存储提供服务变得很简单。而且,在这个过程中,云日志增加了大量的可见性,并使调试成为一种乐趣!这转化为 Django 应用程序一建立就投入使用,并使员工在开发过程的早期就可以轻松浏览、搜索并给出他们有价值的反馈。

剩下的部分,MegaDetector,有点复杂。首先,由于它只有在大批量图片上传时才会被触发,而这种情况每个月都会发生几次,因此将其与 Django 应用捆绑在一起是没有意义的。此外,它有很大的内存占用量(约 6GB),远远超过了应用引擎的限制。考虑到这一点,虽然我最初考虑了云运行,但我最终还是选择了使用云函数,主要是因为它的简单性。只要有一个简单的 http 触发器,Django 就可以在上传图像时调用这些云函数,并且与它分离,模型开发和部署变得更加模块化和独立。有了它,网站的功能就完整了,我完全没有大喊“它还活着!它还活着!!!"疯狂地。

6.景色

到 2022 年初,经过前几个月零星的晚上和周末黑客会议,网站已经准备好了!经过几个月由工作人员领导的用户测试和解决 UX 的问题后,该网站在 2022 年春天开始收集注释。拉远,这里是对 Felidae 的工作流程意味着什么的快速浏览。

Felidae 的新旧工作流程(图片由作者提供)

该网站目前位于 WildePod.org,目前正在向经过审查的志愿者开放,计划在明年开放。现在,这里有一些截图来展示它的样子。

wildepod.org 截图(经许可使用)

7.前方的小路

到目前为止的旅程实际上只是未来有趣而有影响力的旅程的基础。有了基本的工具,这里有一些广泛的工作领域,我认为它们不仅可以为猫科动物,也可以为野生动物研究和保护创造很多价值!

  • 数据金矿——多年来,猫科动物的相机陷阱已经收集了*百万张图像,价值* 5TB!更好的是,他们还有一群热情的志愿者,他们使用 Camerabase,煞费苦心地标注了这些图像的一大部分,包括其中动物的种类和数量。因此,第一步是将所有这些图像迁移到新的范式中,当与 MegaDetector 结合时,有可能生成成千上万的物种级注释,这是 ML 模型的无价资源!
  • 物种检测 —目前,应用程序中 AI/ML 模型的使用仅限于检测一般的动物。然而,由于数据稀少,准确识别其物种是一项非常困难的任务,并且非常处于研究的前沿。目前这种方法的分支是一个两步注释过程——一个 MegaDetector 辅助的动物检测阶段,然后是一个纯手动的物种注释阶段,用户必须从一长串物种中进行选择,这造成了缓慢而糟糕的用户体验。但是,由于猫科动物的大量内部数据,这不仅是改善猫科动物物种检测模型的大好机会,也有助于更大规模的研究工作。此外,在典型的人在回路中的方式中,经过训练的模型可以通过简单地对物种列表进行排序来极大地增强用户体验,作为回报,这些用户注释可以帮助模型在*乎连续的协作回路中随着时间的推移而变得更好。
  • 洞察和分析 —一旦图像被收集和注释,这个管道的最后一个阶段通常是处理数据以回答研究问题。目前,这是通过下载注释并将其加载到 Excel 或 r 等专用软件上来完成的。虽然这些任务通常更难抽象,但有机会将高层次的见解直接集成到网站中,使研究人员能够专注于更深入、更微妙的问题。

强调这条道路的是向更广泛的社区开放源代码数据、模型和软件资源的机会,这可以更好地帮助理解和帮助我们在野外的生物同胞。

如果您有兴趣提供帮助,或者您有任何问题、评论或反馈,请随时在 LinkedIn 上给我留言或在下面添加评论。

用 Neo4j 构建一个简单的推荐引擎

原文:https://towardsdatascience.com/building-a-simple-recommendation-engine-in-neo4j-45770d8747eb

让我们用 plain Cypher 构建一个简单的推荐引擎

像 Neo4j 这样的图形数据库是创建推荐引擎的优秀工具。它们允许我们检查可能包含各种数据源的数据点的大环境。他们强大的存储模型非常适合我们希望分析节点周围环境的应用。如果你想了解是什么让图比关系模型更强大,请阅读这里的。

在本文中,我描述了我们如何只用 Cypher 直接在 Neo4j 中实现一个简单的推荐引擎。该方法基于基本的自然语言处理和简单的条件概率来寻找最可能的匹配项。这个实现可以在一个查询中用几行代码来完成。当用户与应用程序交互时,我们实时运行查询。这个简单的方法产生了非常令人满意的结果,是一个很好的第一个版本。这为我们节省了很多供应和维护额外外部系统的麻烦,我们需要这些系统来进行更复杂的方法。尽管它工作得很好,但是这个解决方案也有一些限制。

领域:队长,任务,事件,领域和项目

DayCaptain 是一个个人时间规划应用程序,允许用户创建由任务和事件组成的一天计划。任务和事件的主要属性是它们的标题,这是一个指定它的短字符串。组织这些对象的一种方法是将它们分配给(生活)区域和项目。一个领域是一个更大且持久的主题,而一个项目是有时间限制的。项目本身可以被分配到一个区域,在这种情况下,它们继承该区域。在本文的其余部分,我们不写“任务和事件”,现在我们只关注任务。

现在,目标是当用户开始在前端输入时,检测新任务的区域分配。例如:用户创建一个任务,开始在 DayCaptain 中编写“部署 ETL 管道”。当他打字时,我们想通过分析使用这些单词的其他任务来检测单词最可能出现的区域。

标记化和词干化—准备工作

目前,我们只考虑任务的标题属性。为了将它转化为可工作的特性,我们需要提取它的表征并对它们进行词干处理。我们使用 StanfordNLP 框架在用户创建新任务和事件时处理每个输入字符串,并将它们的令牌用法作为关系存储在图中。

数据模型:任务的主要属性是标题。它链接到词干标记,并被分配给一个区域。(图片由作者提供)

集合、面积和条件概率

我们的目标是为用户当前正在键入的一组单词找到最可能的区域分配。所以,我们要找到概率P(A|T)最大的区域。换句话说:给定一个记号 T,我们想找到包含这个单词的概率最大的区域 A。

让我们从一个简单的例子开始:

任务与令牌和区域有关系——介于两者之间。(图片由作者提供)

现在,正如我们所看到的,任务(或事件)位于区域和令牌之间,并且基本上形成了它们的分配。当我们创建由标记组成的新任务和事件,并将它们与区域相关联时,我们在区域和标记之间获得更多这样的间接关系。这些间接关系正是我们要分析来寻找建议的。

当任务位于区域和令牌之间时,它们在它们之间形成间接关系。(图片由作者提供)

现在,根据上图,我们还可以在区域和令牌之间构建一个分配矩阵。

利用这些间接关系,我们可以构造一个面积和记号的频率矩阵。(图片由作者提供)

手头有了所有这些数字,我们可以很容易地计算出P(A|T) = P(A & T) / P(T)的条件概率。以下是一些例子:

使用我们的频率矩阵,我们可以很容易地计算条件概率看到一个地区给定一个令牌。(图片由作者提供)

说明这种概率的一个非常直观的方法是将它们视为面积。

频率也可以用面积来表示。(图片由作者提供)

我们的推荐查询很简单。然而,这个例子只适用于单个令牌。问题是:我们如何将多个单词组合成这样一个概率计算?

查找多个单词的推荐

每个任务和事件可以,并且可能由多个令牌组成。为了将我们的模型扩展到这种情况,作为区域的图示特别有用。

例如:我们想找到任务“准备 Neo4j 研讨会”的区域分配。然后,我们想找到概率P(A|”Neo4j” & “workshop”)。从微积分,我们可以推断,我们需要找到P(A & “Neo4j” & “workshop”)P(“Neo4j” & “workshop”)。如果我们查看我们的面积图(或我们的矩阵),我们可以得出这些概率是什么。计算如下:

双令牌条件概率的计算。(图片由作者提供)

正如我们已经从例子中看到的,我们不需要赋值的全局计数,因为它总是相互抵消。因此,我们的推荐查询非常简单,只需确定每个区域内的分配数量。以下是查询:

最终区域检测查询。

该查询的一些注释:在 real DayCaptain 中,也有在区域和任务之间建模的项目。Information标签是任务和事件的超类(或者基本上是所有东西,它有一个标题属性)。此外,查询被简化为不考虑用户。在我们的生产案例中,我们实际上将这个查询限制到特定的用户。

这很简单——但是效果如何呢?

行动中的区域检测🚀(图片由作者提供)

我们对推荐引擎进行了定量评估,甚至在相同的数据上将其与深度神经网络模型进行了比较。结果非常令人满意。在这两种情况下,我们将数据集分为训练集和测试集。尽管在我们的简单方法中没有训练阶段,但我们想测试推荐引擎是否能在它以前没有见过的数据点上很好地工作。

我们有大量来自我自己的数据,因为我已经使用 DayCaptain 超过 4 年了。对于这两种方法,我们测量了他们对已知任务或事件预测正确结果的次数。这两种方法都在大约 95%的时间里预测了正确的区域。

伙计,这很酷——但这意味着什么呢?

让我们对此进行简短的讨论:我们的简单统计(或概率)推荐方法使用非常简单的数学和大量的直觉来产生非常有用的结果。它非常简单明了地用普通密码实现,并且直接在我们的 Neo4j 后端运行。没有添加外部系统的开销,没有我们需要管理的培训周期或模型,维护代码的开销也很低。我们实际上能够实时查询结果(在用户输入时多次查询),而不需要对结果做任何准备。与嵌入 word2vec 的深度神经网络模型等更复杂的方法相比,它产生了同样好的结果。

然而,这种方法的主要缺点是:它局限于一个简单的特性集。一旦我们想在我们的推荐中考虑更多的特性,我们必须在我们的查询中显式地建模它们。它可能变得非常复杂,难以理解,甚至无法维护。更不用说寻找正确的方法将多个特性组合成一个合理的结果的复杂开发过程了。

让我们把这个围起来。

Neo4j 强大的查询模型允许我们在数据库中构建强大的推荐。我们创建了一个非常简单但非常强大的预测查询,为我们的用户提供最佳体验。我们的方法实际上是 80/20,我们取得了一些非常好的结果。对我们来说,主要优势是直接在我们的图形数据库中实现,因为它为我们节省了大量供应、培训和监控额外系统的工作。然而,这种方法的应用限于一小部分特征,因为查询可能变得相当复杂,并且需要很大的努力来维护和扩展。这里描述的方法无疑是构建初始工作解决方案的良好起点。

让我们组队吧——让我听听你的意见。

我希望你喜欢阅读这篇文章,并希望你能从中受益。如果您有任何问题或不同意见,我热忱欢迎您留下评论或直接联系我。点击订阅按钮阅读更多类似内容。🚀

使用 AWS 构建简单的 Web 应用程序

原文:https://towardsdatascience.com/building-a-simple-web-application-using-aws-605436d77407

你是 AWS 新手吗?—本教程是为您设计的。

克里斯·斯皮格尔在 Unsplash 上的照片

在本文中,我将向您展示如何在 AWS 上构建一个简单的 web 应用程序。首先,我们将创建一个显示“Hello World”的静态 web 应用程序然后,我们将发现如何将不同的 AWS 特性整合到 web 应用程序中,并了解它们如何协同工作。

在这个项目中,你可以从标题中猜到,我们将使用 AWS cloud,它代表 Amazon Web Services 一个优秀的云*台,为许多不同的用例提供了无尽的服务,从训练机器学习模型到托管网站和应用程序(在撰写本文时,大约有 200 个 AWS 服务可用)。

这个项目很好地介绍了云计算*台。如果你是新手,刚刚进入云服务,不要担心。开始学习新的东西永远不会太晚。

如果你准备好了,让我们开始工作吧。这是我们将遵循的结构。

目录:

  • 第一步——开始
  • 步骤 2 — AWS Lambda 无服务器功能
  • 步骤 3 —将 Lambda 功能连接到网络应用
  • 步骤 4 —创建一个 DynamoDB 表
  • 步骤 5— IAM 策略和权限
  • 最后一步——测试网络应用

步骤 1 —开始

在这一步中,我们将学习如何使用 AWS Amplify 控制台为我们的 web 应用程序部署静态资源。

基本的 web 开发知识对这部分会有帮助。我们将创建我们的 HTML 文件。如前所述,该网站将是直截了当的,一个标题说“你好,世界!”

作为这个项目的代码编辑,我使用了 Atom。随意用你最喜欢的。以下是该页面的代码片段:

有多种方法可以将我们的代码上传到 Amplify 控制台。比如我喜欢用 Git。为了使本文简单,我将向您展示如何通过拖放方法将它直接放入 Amplify 中。为此,我们必须压缩我们的 HTML 文件。

图片由作者提供。

现在,让我们去 AWS 放大器控制台。它看起来会像这样:

当我们点击“开始”时,它会将我们带到以下屏幕(我们将在此屏幕上选择 Amplify Hosting):

图片由作者提供。

图片由作者提供。

我已经创建了一个 Amplify 应用程序项目,并将其命名为“Step1”

然后,我删除了压缩的索引文件。Amplify 部署了代码,并返回了一个我们可以访问网站的域名 URL。

图片由作者提供。

目前,我们的网站是这样的:

网站域名直播。图片由作者提供。

这一步都做完了!多亏了 AWS Amplify,我们的静态 HTML 代码得以部署和运行。

步骤 2 — AWS Lambda 无服务器功能

在这一步中,我们将使用 AWS Lambda 服务创建一个无服务器功能。AWS Lambda 是一种计算服务,让我们在不使用全时运行的计算引擎的情况下完成任务。相反,只有当某些东西调用它时才起作用;非常有效的解决方案。

给你一些想法,现实生活中无服务器计算的一个很好的例子是自动售货机。他们将请求发送到云端,然后处理作业,只是有人开始使用机器。你可以从这里了解更多关于 AWS Lambda 的信息。

让我们转到 AWS 控制台内部的 Lambda 服务。顺便说一下,确保在 Amplify 中部署 web 应用程序代码的同一区域中创建函数。您可以在页面的右上角看到地区名称,就在帐户名称的旁边。

是时候创建一个函数了。对于运行时编程语言参数:我选择了 Python 3.7,但也可以选择一种您更熟悉的语言和版本。

λ函数。图片由作者提供。

创建 lambda 函数后,我们将看到以下屏幕:

Lambda 功能代码。图片由作者提供。

现在,让我们编辑 lambda 函数。下面是一个从事件 JSON 输入中提取名字和姓氏的函数。然后返回一个上下文字典。body 键存储 JSON,这是一个问候字符串。

编辑并保存 lambda_function 之后,让我们继续前进到 Test 选项卡并创建一个事件。

测试 Lambda 函数。图片由作者提供。

以下是我们部署并运行测试后的执行结果:

图片由作者提供。

执行结果包含以下元素:

  • 测试事件名称
  • 反应
  • 功能日志
  • 请求 ID

步骤 3— 将 Lambda 函数连接到 Web 应用程序

在这一步中,我们将把无服务器 lambda 函数部署到我们的 web 应用程序中。我们将使用 API Gateway 来创建一个 REST API,它将允许我们从 web 浏览器发出请求。API Gateway service,从它的名字我们就能理解,就像是一座桥梁,连接着应用的后端和前端。

REST:表象状态转移。

API:应用编程接口。

让我们抓紧时间,从 AWS 控制台打开 API 网关服务,然后创建一个新的 REST API。

API 网关服务主页:

图片由作者提供。

API 创建页面,我们为 REST API 命名、选择协议类型和端点类型。

创建 API 页面。图片由作者提供。

在下一页中,我们从动作按钮创建一个 POST 方法。集成类型将是 lambda 函数,并确保区域与您用来创建 lambda 函数的区域相同。

新的 API 方法设置。图片由作者提供。

接下来,我们将启用 CORS,它代表跨源资源共享。这是一个 HTTP 头。然后单击启用 CORS 并替换现有的 CORS 标题。

API CORS 设置。图片由作者提供。

启用 CORS 头后,从 API actions 部署 API

这将创造一个新的舞台;您将在左边栏的阶段选项卡下看到它。当您查看 stage 时,顶部会有一个名为 Invoke URL 的 URL。请确保复制该 URL 在这个项目的最后一步,我们将使用它来调用我们的 lambda 函数。

是时候测试我们的 REST API 了。

Resources 选项卡下,点击 POST 后,我们将看到方法执行屏幕。当我们点击我在下面圈出的按钮时,就会出现测试页面。

REST API 详细信息。图片由作者提供。

步骤 4— 创建一个 DynamoDB 表

在这一步中,我们将在 Amazon DynamoDB(另一个 AWS 服务)中创建一个数据表。DynamoDB 是一个完全托管的 NoSQL 数据库服务,支持键值数据结构。

DynamoDB 仪表板:

AWS DynamoDB 服务仪表板。图片由作者提供。

让我们点击创建表格并填写一些关于我们数据表的信息:

创建新的 DynamoDB 表。图片由作者提供。

然后,让我们查看表的细节并复制 ARN,它代表亚马逊资源名称:

web app-表格附加信息。图片由作者提供。

在下一步创建 IAM 访问策略时,我们将使用 ARN。

步骤 5-IAM 策略和权限

IAM:身份和访问管理

我知道你在想什么。政策是一个无聊的话题,我也有这种感觉——直到我意识到政策在保护我们的安全方面起着至关重要的作用。AWS 推荐最小特权访问模型,这意味着不给用户超过需要的访问权限。同样的规则也适用于 AWS 服务。

例如,即使对于这个简单的 web 应用程序项目,我们也已经开发了多个 AWS 服务:Amplify、Lambda、DynamoDB 和 API Gateway。了解他们如何相互交流以及他们共享何种信息至关重要。如果您想了解这方面的更多信息,这里的是涵盖 IAM 中的策略和权限的官方文档部分。

回到我们的项目。

我们将在这里定义的策略将允许访问我们的 lambda 函数来写/更新我们使用 DynamoDB 创建的数据表。

我们去 AWS Lambda 控制台。选择λ函数。然后,我们转到“配置”选项卡,我们将看到“执行”角色。点击链接,这将带我们到该功能的配置设置。

Lambda 功能配置。图片由作者提供。

从权限策略中,我们创建一个新的内联策略。

Lambda 函数 IAM 策略。图片由作者提供。

然后,让我们将下面的代码添加到 JSON 部分。

{
"Version": "2022-11-10",
"Statement": [
    {
        "Sid": "VisualEditor0",
        "Effect": "Allow",
        "Action": [
            "dynamodb:PutItem",
            "dynamodb:DeleteItem",
            "dynamodb:GetItem",
            "dynamodb:Scan",
            "dynamodb:Query",
            "dynamodb:UpdateItem"
        ],
        "Resource": "YOUR-DB-TABLE-ARN"
    }
    ]
}

这个策略将允许我们的 Lambda 函数从 DynamoDB 数据表中读取、编辑、删除和更新项目。

最后,我们将更新 lambda 函数 python 代码。在主页上,我们可以在“配置”选项卡中找到“执行”角色。

拉姆达代码。图片由作者提供。

下面是函数内部的 Python 代码:

响应采用 REST API 格式。完成更改后,确保部署代码。部署完成后,我们可以从橙色的测试按钮测试程序。

我们可以检查 DynamoDB 的结果。当我们运行该函数时,它已经更新了我们的数据表。当我们去 AWS DynamoDB,选择你的桌子,点击左边导航栏的探索项目。下面是 lambda 函数返回的对象:

web app-表格。图片由作者提供。

最后一步— 测试 Web 应用程序

恭喜你走到这一步!

在这最后一步中,我们将看到我们刚刚构建的一切都在运行。我们将更新前端,以便能够在 lambda 函数的帮助下调用 REST API 并接收数据。

首先,让我们在代码编辑器中打开一个新文件,并添加以下代码行(我将其保存为 index.html):

在保存之前,请确保更新 api 键文本。您可以在 REST API 细节下的 API 网关服务中找到它,称为 invoke URL。代码在第 34 行,这里我们用 requestOptions 变量获取 URL 链接。

当代码全部设置好后,我们将把它导出为一个压缩文件,就像步骤 1 中一样。然后,使用控制台将文件上传到 AWS Amplify。

用户界面。图片由作者提供。

我们的数据表接收带有输入数据的 post 请求。当点击“调用 API 按钮时,lambda 函数调用 API。然后使用 javascript,我们将 JSON 格式的数据发送给 API。你可以在 callAPI 函数下找到这些步骤。

您可以在下面的“我的数据表”中找到返回的项目:

web app-表返回的项目。图片由作者提供。

结论

恭喜你。我们已经使用 AWS 云*台创建了一个简单的 web 应用程序。云计算像滚雪球一样越来越成为开发新软件和技术的一部分。这里的是一篇我与认证项目分享顶级云计算*台的文章。如果你今天能从这篇文章中学到一些新东西,我会很高兴。

从事动手编程项目是提高技能的最佳方式。如果你对这个项目有任何问题或反馈,请随时联系我。我会尽我所能给你回信。

我是贝希克·居文,我喜欢分享关于编程、教育和生活的故事。 订阅 给我的时事通讯留下灵感。泰,

如果你想知道我写的是什么样的文章,这里有一些:

用 Java 构建智能 Wordle 求解器

原文:https://towardsdatascience.com/building-a-smart-wordle-solver-with-java-cb734d4a9635

一种高效求解单词的高级字符串处理策略

布雷特·乔丹在 Unsplash 上的照片

介绍

Wordle 是 Josh Wardle 最*在 2021 年 10 月创作的视频游戏,用户有六次机会猜测一个通常每天都在变化的单词。在每一次尝试中,你可以输入一个单词,然后以颜色编码的形式接收来自程序的反馈。

这种格式为单词 try 中的每个字符指定三种颜色中的一种。从左到右,如果该字母不包含在您要预测的单词中,它将是灰色色。另一方面,如果它包含在内但放置错误,它将显示为黄色色。此外,如果你正确预测了它在单词中的位置,那么绿色将用于相应的角色,你肯定会知道它在游戏剩余部分的价值。

在出现几个字母的情况下,程序的逻辑会在分析其余字母之前,先分析从左侧开始遍历的第一个字母。这意味着如果你要猜的单词有两个 E 字符,而你输入的另一个单词包含两个放错位置的 E,它们都将是黄色。相反,如果你输入一个单词有三个放错位置的 E, 前两个会直接被认为是定位错误,颜色为黄色。但是,最后一次出现被视为单词“中的一个”字符,以灰色显示。

作为一个主要要求,所有单词必须是五个字母长,才能正确地玩游戏。此外,用户在猜测单词时的所有尝试都必须按照母语使用者的数量列在相应语言(通常是英语或西班牙语)的词典中。虽然没有什么可以阻止任何其他语言被用于 Wordle,但是你可以找到各种各样的版本,从德语到汉语。

然而,如果你之前已经玩过一个单词游戏,你可能会想知道在猜正确的单词时应该遵循什么样的策略。从计算每个字符的出现频率并首先取出最相关的单词到使用数据科学或先进的机器学习技术,任何选择自动(或不自动)产生适当的提示并最大限度地减少完成游戏的必要尝试被证明在处理这个问题时是有帮助的。

最后,在解释破解 Wordle 游戏的几个程序的内部工作原理之前,至少了解一下本文中使用的编程语言的基础知识是很方便的,这些语言主要是 JavaPython。因此,如果您正在开始这一领域的学习过程,并且正在为下面显示的任何步骤而奋斗,因为我们将涉及一些高级主题,如正则表达式多线程,您可以随时访问在线免费学习资源,如freeCodeCampstack overflow**

目标

本文旨在解释一个算法的 Java 实现,该算法以一种“智能”的方式玩文字游戏。也就是说,它必须遵循一种策略,以尽可能少的步骤到达我们想要猜测的单词。此外,我们将通过优化正常运行所需的硬件资源来提高其效率。

选择核心技术

数据是一种重要的工具,如果我们能够从中提取知识,它在处理类似的自然问题时会特别有用。然而,并不是所有我们处理的数据都会简化我们的工作。例如,在缺失条目的数据集中,大小、格式和维度可能会增加我们思考问题的复杂性。因此,我们必须考虑前面的概念来理解每种选择的基本原理、优点和缺点。

通用过程

如果我们分析 Wordle 的基数和每场比赛的结果情况,我们会发现在我们可以使用的所有策略中存在一个共同的过程,它专注于在下一次迭代中缩小所有可能单词的集合。

一开始,我们将集合称为【D】**(对于字典) 包含特定语言的所有现有的五个字母的单词。因此,每当用户抛出一个新的猜测并收到相应的颜色代码时,我们必须使用程序的响应来丢弃字典中那些不满足先前尝试要求的条目。因此,如果确定这个单词在给定的位置有一个特定的字符,或者有一个字母肯定不在我们要寻找的单词中,我们的程序需要使用这些信息从 D 集合中选择正确的单词,同时最小化每次游戏的可能性。**

作者图片

例如,在上面的游戏中, D 集合包含大约 10k 个单词,这意味着它们中的每一个都可以是我们想要遇到的隐藏单词。在用户输入第一个猜测 【治愈】, 之后,根据满足以下条件的单词的数量,集合长度减少到小于其先前大小的一半:以 H 开始,在位置 1、3 或 4 (0 索引)包含字母 A,并且其中没有 E、L 或 S。第二次输入后, 【匆匆】, 集合再次收缩新的条件:以 Y 字符结尾,不包含 U 或 r。

有了最初的想法,我们现在可以开始建模一个完整的策略,稍后用 Java 实现。然而,由于我们可以有各种各样的技术可用于算法的改进和优化,因此值得考虑不同的备选方案并分析每一种方案以获得高质量的结果。

随机猜测

我们能想出的第一个策略就是所谓的 随机猜测。 这是程序化解决一个 Wordle 游戏时最简单直观的方法之一,也是全球大多数玩家最【无意识】使用的方法。

顾名思义,该方法包括每次迭代从集合【D】中随机选择一个单词,假设该单词完全由提供新信息的单词组成,即,它考虑目标单词中存在或不存在的字符,包括它们在目标单词中的出现和位置。乍一看,这似乎既不是一个复杂的算法实现,也不是不正确的。但是,我们可以做得更好!

随机猜测 的主要缺点是,我们没有探索每个单词与字典中其他可能性之间的关系,因此在每个游戏步骤中,除了已经在 D 集合中定义的标准之外,在没有任何总体标准的情况下选择任意一个单词。因此,这种操作原理可能会导致算法部署的性能不佳。尽管如此,它的简单性使它在执行成本方面成为一个高效的选择,因为除了验证之外,它不会对每个单词执行繁重的计算。

机器学习

我们可以用来解决单词的第二个策略主要继承自机器学习技术,这些技术在过去几年中获得了显著的认可,并且其在各种领域中的应用也增加到了相同的程度。

在更进一步之前,我们必须注意到 机器学习 本身并不是一个你可以应用于任何问题的统一或通用的工具。实际上,它有相当多的专门分支,在这些分支中,不同的类问题以一种确定的方式得到处理,从计算机视觉、序列预测,或者更一般的主题如,到 自然语言处理。机器学习的每个子集都是为了正确有效地解决特定的任务,即使它可能会像 GPT-3 那样扩大规模。在这种背景下,我们需要考虑的研究领域就是 强化学习。**

强化学习

你可能听说过最*在游戏自动化方面的非凡进步,它们比世界上最好的玩家玩得都好。从古代的棋类游戏到现代的新游戏,在这些游戏中,一个“人工智能”被电脑控制,扮演敌人的角色。前面提到的所有例子都有相同的基本原理;

作者图片

强化学习中, 我们能创造的每一个“智能”系统都是从上述方案中构建出来的。如你所见,它由两个主要组件组成,分别表示为 、代理、环境 (游戏世界)。第一个是驻留在游戏世界中的玩家,名称“代理人”来自于它与环境的交互。详细地说,一个代理有一组它一次可以执行的特定动作【它们可以是离散的,如国际象棋位置,也可以是连续的,如机器人手臂的移动。**

允许代理学习如何在环境中选择正确行动的关键概念是它在做出决定(执行行动)后收到的 奖励 。如果它是正的,它将 加强 代理在未来状态做出类似的决定。相反,如果它是否定的,它将代表对采取行动的惩罚,代理将学会在未来不采取类似的行动。使用这些工具,如果我们希望代理学习如何玩游戏,我们将必须建立一套完整的动作,并在它执行正确的决策时分配正确的奖励,即,为代理设置一个目标,使其能够在训练过程后学习并赢得游戏。

作者图片

在向上的表达式中,你有了我们想要最大化【R】的总代理报酬的一般计算方式。在求和记数法中,它表示在时间(r)内每个状态所有生成的奖励 的总和。 此外,引入一个折扣因子 γ 来正式避免 R 在报酬增长到无穷大时变得非常关键。在游戏中,我们实际上是在控制代理人的短期和长期报酬有多高。这不是奖励带来的唯一问题,因为它是强化学习问题中最具挑战性的任务之一。如果您想了解更多关于这些问题的信息,请访问以下详细的 文章

培训过程

在讨论代理的学习过程之前,有必要定义几个与它在每个状态下做出决策时所遵循的标准相关的概念。

一方面,代理人需要有一个工具在特定的状态下及时做出决策 (s)。 形式上,这可以通过创建一个函数来计算在时间上紧接的下一个环境状态中的总代理报酬的预期值来实现,已经采取了一个动作 (a)。 简言之,它代表了状态下可能采取的动作 (s)。 由此,如果有一个动作能够对奖励值做出正贡献,那么它将比代理的动作集中的其他动作具有更高的质量值。

作者图片

另一方面,如果我们希望代理在每个状态下选择并执行一个动作,我们必须使用(或不)Q 函数来塑造内核代理的标准,策略函数 π(s)。 简而言之,策略以一个状态作为输入参数,返回该状态内质量值最大的动作。

作者图片

最后,我们有培训代理的基本要素。现在剩下的唯一任务是找到一种算法来“教导”代理。

在这个阶段,强化学习提供了许多可遵循的途径,所有这些途径对于训练不同类型的代理都是有价值的。然而,我们的情况可能需要两个选项之一;一个影评人 马氏决策过程 让代理人探索 环境或一个价值学习 方法。这里我们只讨论 值学习 ,因为它们是广义的算法。

作者图片

如前图所示, 值学习 依赖于 深度神经网络 ,该网络将完全定义给定状态 s 的参数作为输入,并输出代理在这种状态下可以采取的所有可能行动的质量值。因此,将质量作为推理的输出,代理可以选择获得的最大值并执行相关的动作。但是,为了找到网络内部的最佳权重和偏差值,我们可以使用传统方法,如 、反向传播、随机梯度下降、 和损失函数,如 均方误差 ,在这种情况下,损失函数将正式表示如下:

作者图片

如果你进一步研究网络优化过程的内部细节,你会发现总体目标是最小化损失函数,在这种情况下,通过使用均方差内的预测和实际质量项之间的

前面提到的方法只是我们可以在我们的例子中使用的一个例子,但它不是唯一的方法。强化学习是一个广阔的领域,有如此多的选项值得研究,从 汉密尔顿-雅可比-贝尔曼 方程到 萨尔萨 (状态-行动-奖励-状态-行动)算法。因此,您可以为这个 Wordle 项目探索不同的替代方案及其各自的结果。

尽管如此,总结一下我们可以实现的算法,为代理和环境提供一个好的定义是很关键的:

****1-环境:由游戏机制定义,每个状态对应于代理输入的猜测。此外,它必须为每个状态生成正确的输出颜色代码。

****2-奖励:在训练过程之前,代理需要一个适当的奖励系统引导我们的目标,在这种情况下,猜测隐藏的单词。

作者图片

如上所述,体面的奖励将基于放置在正确位置的字符数(黄色和绿色),字典长度的递减比率,以及通过完成游戏达到最终目标。

3-Agent: 它是我们潜在解决方案的精髓,负责通过处理程序的反馈来做出最佳猜测(动作)。为了训练和塑造它的行为以用于以后的推断,最好的选择是使用神经网络算法来模拟 Q 函数值模式。**

信息论

最后,最终 Java 实现背后的算法是建立在与 信息论 相关的概念之上的,因为几个实验已经证明它是高度优化的。如果您想了解关于强化学习、随机猜测或其他基于属性的角色策略为何不能以最高效率工作的更多信息和细节,请访问以下资源:

现在,让我们解释构建算法所需的基础知识。

信息论 是数学知识的一个分支,专门研究作为理论概念的信息,特别是当它被传递、存储或测量时。因此,该理论的用例在涉及信息处理的任何领域都很广泛,如量子计算、统计学,甚至神经学。这一领域的领军人物是 克劳德·香农 ,其创始人在 20 世纪。他在电子工程和密码学方面取得了有价值的进展,但毫无疑问,他对计算机科学早期阶段的贡献是超越性的。由于这项工作,他开始被公认为信息时代之父。

所以,如果我们思考信息的概念以及试图【测量】它所产生的可能性,我们就会认识到,设想一个代表已知环境或系统中信息量的量级的最合适的方式是由我们自己的香农开发的,被称为 【熵】

作者图片

上面的表达式模拟了一个确定的随机变量【x】,** 的熵,但是为了正确理解所涉及的基础数学,当应用该公式时,我们需要可视化我们正在做的事情。**

例如,想象你正在玩一个彩票游戏,有 100 分之一的中奖机会。如果你突然好奇(假设你能找到答案) : “中奖号码是不是小于 50?”;您将通过因子 0.5 来缩小包含您可以为游戏选择的可能中奖号码的集合,并且您将从您的问题(观察)中获得 1 位信息,因为 log2(1/0.5)=-log2(1/2)=1。

作者图片

因此,类似于 二分搜索法算法 在这种情况下,您将需要 log2(100)个观测值来达到获胜数。可以推断,用来量化信息的单位(基数 2)位, 也表示为 香农单位。

作者图片

这就是为什么公式中有对数的原因,根据问题的不同,对数可以在任何底数。从视觉上看,负对数将更高的信息值与最接* 0 的概率相关联。因此,观察后的可能性集合中包含的元素越少,观察中获得的信息就越多。

注意,对数自动抛出 0 作为确定性事件的信息值,因为不存在不确定性。

最后,熵公式将这个负对数乘以概率,作为权重,并将原始集合中所有可能的概率值加在一起,在前面的示例中,原始集合包含 100 个元素。

在 Wordle 游戏的情况下,我们将使用有 7980 个初始单词的西班牙语词典。在开始和每次输入新单词时,我们会使用熵公式计算字典中每个单词潜在提供的信息,并选择具有最大的那个。然后,由于这是我们的一组可能性,我们的算法必须在未来的游戏迭代中使用程序的反馈来丢弃不可能的单词,缩小集合,直到找到隐藏的单词,游戏获胜。

要了解他的策略的更多细节,您可以访问原始的 3blue1brown 指南:

还有,如果你对信息论和底层数学很好奇,可以使用下面的 资源

Java 实现

在下一节中,我们将在 Java 上实现前面描述的策略,并做一些实质性的修改,为用户带来更好的可玩性和交互性。但首先,让我们简要总结一下我们的程序将遵循的整个算法。

  1. 用所有可能的单词初始化字典,并计算它们的分数。由于我们将使用西班牙语,它最初将包含 7980 个单词。
  2. 为程序提供六次机会来猜测玩家正在想的单词。然而,这次输入的不是一个单词,而是游戏本身在最初版本中应该给出的反馈。因此,玩家不是插入颜色代码,而是输入一系列字符,其中 0 代表相应字符的灰色,1 代表黄色,2 代表绿色。
  3. 挑选具有最大分值的单词并显示给用户。
  4. 等待并验证他的字符序列。如果不一致,请再试一次并显示一条错误消息。
  5. 使用正则表达式来检查程序是否猜中了正确的单词并赢得了游戏。
  6. 如果程序没有猜出这个单词,它将在未来的迭代中更新字典以丢弃不可能的单词,并重新计算得分值。
  7. 如果程序用完了尝试次数,它将“输掉”游戏并停止执行,除非用户想再玩一次。

词典创作

如前所述,我们需要一组五个字母长的西班牙语单词来开始编码和测试我们的程序。我们可以从网上下载一个,但是相反,我们将使用 Python 来自动化创建过程,增加我们对结果的控制。

正如你在上面所看到的,我们可以受益于 Python 带来的众多优势,比如用于执行拼写检查的 enchant ,用于组合计算的 itertools ,或者主要用于从字符串 中删除重音的**

简而言之,代码计算字母表的所有可能排列和正确的西班牙语重音元音(长度为 5) 。然后,它使用 enchant 模块只过滤那些在官方西班牙语词典RAE上注册的排列。

如果你想可视化生成的一组单词,你可以在 Github 上访问上传的文件。

此外,即使现在看起来没有必要,我们也可以计算用户在每次迭代中可能提交的所有可能的输入。因此,通过再次使用 itertools ,在两行代码中,我们生成了一个由 0、1 和 2 组成的 5 字符长字符串的列表。

最后,使用 replace() 函数,我们实现了想要的 2D 数组格式,这将有助于后续的 Java 实现。

主要方法

在解释主要的方法功能之前,我们必须导入一些 数据结构 所需的库以及整个程序中使用的技术。

使用 import 关键字,我们的程序实现了各种实用程序来执行输入/输出操作,从 URL 读取数据, 添加多线程支持,或者高效地遍历大型线性数据结构。

然后,在定义了适当的常数来提高代码性能和可读性之后,我们就可以在 main 方法上实现算法的大部分步骤了。

解决方案的内核在上面的 main 方法中进行了编码。首先,定义一个 扫描仪 来捕捉用户输入。然后,一旦一切都准备好运行,do-while 循环控制玩家是否想再玩一次。在循环内部,我们创建字典并将其存储在一个linked hashmap数据结构中,该数据结构利用 Map Java 接口和 LinkedList 在检索和检查字典中是否存在元素时提供一个常数时间复杂度。此外,我们通过为其元素评分来设置用户交互。**

一旦完成,我们就执行一个考虑到尝试次数常量的 for 循环,该循环执行与用户输入验证、随后的字典更新以及检查用户是否成功相关的所有过程。

上面两个函数也是主要方法功能的一部分,它们管理输入格式转换和从 URL 创建字典。

第一个返回一个 LinkedHashMap 数据结构,其中包含字典应该包含的所有初始单词,它们各自的分值在开始时为零。因此,根据 txt 文件格式,它使用 split() 函数按空格分隔单词并构建相应的输出。

同时,stringpointarray()函数解析用户输入的字符,并将其转换成其他代码段所需的格式。它还做一些验证工作,确保输入具有期望的长度,并且没有输入不支持的字符。

生成正则表达式模式

一旦我们实现了整个算法的逻辑,就该定义负责处理输入和输出文本的其余函数了。为此,我们依赖于基于正则表达式的解决方案 (Regex) ,因为除了每个单词中出现的字符之外,这是建模每个字符的正确或错误位置的最紧凑和可靠的方法。

为了理解我们的程序将如何检测一个单词在特定条件下是否可以形成,或者如何处理输入验证,有必要了解在generate pattern()函数中使用的基本Regex模式和特性。

正则表达式 是表示文本内部 模式 的字符序列。例如,如果您想要验证用户是否以正确的格式正确地书写了电话号码、日期或电子邮件,您可以将该格式编码为正则表达式模式,并将其与用户输入进行匹配。因此,如果有匹配,输入就被成功验证,这意味着用户没有拼错任何字符,也没有输入程序要求之外的任何内容。那些 Regex 模式通常被认为是使用紧凑的符号来搜索和替换大文本中的内容,但是在我们的例子中,我们寻求验证和编码 Wordle 规则。因此,要将游戏规则转换成有效的正则表达式模式,我们必须尽可能简化 Wordle 使用的颜色编码(用户输入)

当该函数遍历输入序列并处理每个字符时,它会将四个可能的正则表达式中的一个附加到结果模式中,具体取决于出现的次数以及所分析单词中的相应值:

  • *“(?=[^%s]$)": 如果选择的输入字符是 0,则表示该单词的对应字母不应该出现在目标单词中。
  • 【(?!。{%s}%s)": 如果所选字符为 0,并且在输出中没有出现值为 2 的相同字符,则单词字母不能出现在目标单词中。
  • *”(?!。{%s}%s)(?=.%s)": 这种模式模拟了 Wordle (本版本中的数字 1)中的黄色,确保特定字符在单词的任何位置至少出现一次,除了它所在的位置。
  • 【(?=.{%s}%s)": 最后,最简单的情况是在单词(数字 2) 上找到一个绿色字符,因为表达式只需要保证出现位置上的特定字符。

在设置了generate pattern()函数后,我们现在可以通过之前生成的模式和上面显示的函数来验证用户的输入,如果用户试图作弊或输入不一致的序列,程序将能够做出反应。

得分单词

最后,在用户输入处理之后要实现的唯一剩余元素是字典刷新部分,其中每个游戏迭代负责根据先前生成的 正则表达式 模式丢弃单词。

因此, updateDict() 采用当前的LinkedHashSet字典以及与用户对该迭代的反馈相关联的正则表达式,并返回包含用熵公式评估的所有条目的新数据结构。此外,为了提高代码性能,我们可以使用 多线程 来计算每个单词的分数,因为这是一个计算开销很大的操作。

由于我们是在 Java 中,是使用 可调用 接口实现的,允许我们异步执行不同的任务,充分利用并发编程的优势。在这个程序中,我们将 de dictionary 分成多个块,并将【score function()执行到一个名为parallelmapupdate、*** 的新类中,这个新类是从 Callable 接口“继承”过来的,从而将整个 dictionary 的评分任务并行化。***

然后,每个 线程 任务返回一个 LinkedHashSet 与字典的某一部分,在结束 updateDict() 执行之前必须连接。

现在,您可以使用一个定制的启动字典运行 Java 程序来测试结果。标准版包含的字数很少,适用于 CPU 中 很少的低端 PC。但是,您可以用任何语言或长度的自定义字典来替换字典,以分析程序并试验您自己的算法变体。

可以找到完整的 。java 文件和包含所有 Python 代码的 Colab 笔记本放在下面的 GitHub 资源库中:

**https://github.com/cardstdani/practica-java **

构建最先进的数据科学*台

原文:https://towardsdatascience.com/building-a-state-of-the-art-data-science-platform-c4171286289d

数据科学|工程|*台

规划强大的数据科学架构的构建模块

万花筒Unsplash 上拍摄的照片

在现实世界中,数据科学流程超出了处理某些数据和训练预测模型的步骤。在生产系统中,数据科学过程对数据和模型交付、审计和准备感兴趣。数据科学是高度精细的,正确掌握数据科学是决定企业数据科学项目能否成功的关键因素。

在本文中,我们将讨论构建一个高效且健壮的 DS *台所需的不同组件。

目标

我们为什么需要数据科学*台?

我们想从这个*台中得到什么?

企业在数据科学服务方面失败的主要原因与他们无法正确量化其 DS 需求有关。大多数人无法回答“你为什么需要 DS?”

不幸的是,一些企业跳上了当时流行词汇的宣传列车,觉得有必要做数据科学。归根结底,这才是最重要的,不是吗?这有三种主要的可能情况:

  • 运气好的话,会产生一个成功的项目
  • 惨败

最终经常发生的是无法兑现宣传。因此,未能交付会导致声誉受损,当然,还会损害业务!

现代企业总是有一个数据科学*台的用例。数据是当今世界的关键。毕竟这是世界上最有价值的资产。然而,大多数人没有理解的是数据科学*台和人工智能(AI)*台之间的区别。数据科学超越了人工智能的局限。

数据科学是数据体跳动的心脏。数据科学是任何组织都可以理解其原始数据的媒介。数据科学最简单的形式是一个工具集,用于交付分析智能。

那么,为什么需要数据科学*台?我们想从这个*台中得到什么?

很简单,数据科学*台使您能够真正理解您的数据。

我们都经历过身体上的关系,我相信我们所有人都曾一度希望自己知道对方在想什么。作为一个组织,你与你的客户和利益相关者有关系。数据科学就是这么做的!让你知道并理解你的伙伴在想什么。它为你提供了正确的知识来评估什么有效,什么无效。帮助您决定按哪个按钮。

组件

在我们深入探讨数据科学*台的制胜之道之前,我想指出两个极其重要的注意事项:

  • 思考未来
    与任何技术一样,数据技术在范围和数量上都在快速发展。因此,保持我们对未来可伸缩性机会的选择可能是明智之举。
  • 思考模块化 在构建我们的数据科学*台时,我们必须将它视为另一种产品。作为一个组织,你不应该建立一个数据科学*台来简单地帮助你的工作流程。为您的数据科学*台感到骄傲!展示它。推销它。最重要的是,卖掉它!
    是的,你没听错。卖掉它。以这样一种方式构建您的*台,每个组件都是个性化的,可以作为单独的产品出售。该*台的每一个组件都应该能够完美地独立工作,并且能够轻松地与其他解决方案集成。

因此,让我们开始讨论可靠的数据科学*台的关键组件。

*台组件的数据流图。作者图片

组成部分 1:数据信息系统

第 1 部分:任何与数据相关的产品都需要从处理数据的传输开始。我们如何将数据放入我们的*台?我们有两个主要选择:

  • 实时数据流
  • ETL 或批量加载

我坚信实时数据流是几乎所有场景的必由之路;然而,这在很大程度上依赖于基础设施和用例。如果实时流媒体是你的选择,那么阿帕奇卡夫卡就是你的朋友。卡夫卡只是一个简单的信息传递解决方案。还有各种其他的解决方案,比如亚马逊 KinesisTIBCO SpotfireRabbitMQ 。还是那句话,选你喜欢的毒药。

查看我的另一篇关于使用 Python 处理 Kafka 事件数据流的文章!

第二部分:数据存储方面。选择数据库系统在很大程度上取决于以下特征:

  • 速度 —数据传入的速度有多快?
  • —数据有多大(大小)?
  • 多样化—你有多少种不同的数据结构?
  • 延迟 —您希望数据库多快返回结果?

这是一个很大的讨论领域,并且远远超出了本文的目标,所以我不会深入讨论。

第三部分:数据处理与特征工程。一旦我们开始获取数据流,我们就可以开始动态处理这些数据。其最终目标是创建特征库(即预先计算的特定特征)。随着机器学习解决方案在数据科学*台中的集成,特征存储变得更加相关。在生产中,任何运行的机器学习模型都会不断地从特征存储中读取以检索它们的数据。

对此有各种解决方案。人们可以使用 Spark 之类的东西开发自己的工具来处理数据流,并将结果存储在数据库对象中(这取决于人们选择的数据库风格)。反过来,人们也可以使用现有的功能商店实现,如盛宴泰克顿霍普斯沃斯。这里要记住的重要一点是,我们应该始终有一个真实的来源,特别是对于工程特征。

组件 2:智能

这个组件是关于建立一个框架,允许自动和手动的机器学习模型训练。所有模型培训流程都应遵循标准模型评估策略(适用于所有模型),该策略将执行 A/B 测试和统计假设测试,以评估和比较基础模型。

最终模型应通过自动超参数调整进一步优化。在部署到您的生产环境之前,每个模型都应该首先通过这个框架。

适当的模型版本控制在这里也很关键。我们需要能够回答以下问题:

  • 现在的模特是什么时候训练出来的?
  • 当前模型的性能指标是什么?
  • 那一天生产的是哪种型号?

这个任务的一个很棒的工具是 MLFlow

在这一点上,我们也应该开始考虑数据版本控制(DVC)——主要是因为同样的原因,你想要版本化你的代码库。

对于 DVC,我们可以:

  1. 作为训练时快照的版本数据(例如 MLFlow 中的工件),或
  2. 使用专门的数据版本系统;

这种方法将允许我们分别对数据和代码进行版本控制(数据的任何变化都会影响代码,但并不是所有的代码变化都会影响数据——所以它们应该是独立的单元)。与功能商店类似,人们可能会选择内部构建 DVC 或通过第三方解决方案。一些例子包括厚皮动物,DVC 和飞机库。

如果对 Hangar 感兴趣,可以随意查看我关于这个纯 Python DVC 实现的另一篇文章。

从长远来看,在我们的数据科学*台中正确实施 DVC 将被证明是非常强大的。特别是有了 MLFlow 这样的工具,我们就可以将 MLFlow 上版本化的训练数据模型绑定到该模型使用的训练数据集(在 DVC 版本化)。这也允许我们对我们的数据进行审计跟踪。尽管如此,模型版本控制和 DVC 都应该与一个元数据存储进行通信,以建立一个关于什么、何时以及如何的强大的审计追踪。但稍后会详细介绍。

这个组件的另一个方面是可解释的人工智能(XAI)。建立机器学习模型是一项任务。但是向最终用户提供可解释的预测是另一个层面的任务。现在,有许多 XAI 软件包可以帮助你解释任何机器学习算法的结果。因此,在我们的智能组件中整合这些技术确实有助于将我们的*台提升到一个新的水*。查看我关于这些技术之一的帖子!

这个组件的最后一个功能是模型监控。我们需要一种方法来评估我们的训练模型的性能,并持续监控它们的性能下降。模型监控和评估应分为三种方式:

  1. 培训期间的模型评估/基准测试
  2. 使用 XAI 和假设分析的模型监控
  3. 连续生产监控

组件 3:生产化

我们将我们的模型发布到生产中,供客户使用。这意味着我们的模型根据实时数据进行预测,并将结果传送到各自的目的地。该步骤还应该与元数据存储进行通信,以保持生产化审计跟踪(例如,当一个新模型被推向生产时,简单地保持所做的每一个预测的跟踪记录)。

可以集成到该组件中的另一个有趣机制是反馈环路。反馈回路是一种机制,其中机器学习模型刚刚做出的预测被反馈到相同的所述模型以进行重新训练。如果与可操作的预测相结合,这个方面变得越来越强大。这个反馈循环应该使我们能够尽可能接*实时地评估生产中的模型(取决于手头的任务),并促进向在线学习的过渡。这也将使我们能够在我们的*台中最小化模型衰减和概念漂移的影响。

例如,让我们假设一个预测客户是否会在未来 5 天内流失的数据模型。一旦我们做出预测,我们就可以对该预测进行 5 天的监控,以确定相应的客户实际上是否会流失。然后,该反馈将被反馈到初始模型,以进一步改进和提高性能。

组成部分 4:监测和暴露

这是我们可以炫耀我们冷静的观察和洞察力的部分。这是整个*台最重要的部分。这是经营的脸面;客户将直接与之交互和体验的组件。这是我们交付所有成果并密切监控所产生的商业价值的地方。

这是我们戴上创造性帽子的机会。我们需要构建仪表板来全面展示我们产品的功能,并确保客户能够全面、快速地吸收我们的见解。

我们还将能够持续监控当前的车型生产绩效,使我们能够在车型再培训和交付方面采取积极主动的态度。我们还可以采用多层仪表板,为不同类型的用户使用不同的视图。例如,开发者视图将允许我们深入研究特定模型的技术指标,而用户/监管者视图将加载 XAI 仪表板和假设分析。

组件 5:元数据 存储

它只是一种让我们对工作流程中发生的事情进行可审计跟踪的方式。在这里,我们可以存储查询、预测和模型训练的审计线索。人们还可以增强元数据存储,以包括不同产品的预测跟踪。

TL;速度三角形定位法(dead reckoning)

本质上,我们的数据科学*台架构应该由 5 个主要组件组成:

  1. 获取数据
    -
    实时流处理
    -特征工程
    -特征存储
  2. 智能
    -
    -
    -机器学习
  3. 生产化
    -
    车型调配
  4. 监控
    -
    生产监控
  5. 元数据存储

上述组件应该是任何数据科学框架的主干。可能不同的是我们如何处理集成并允许不同的组件相互交互。

你喜欢这篇文章吗?如果是,请考虑订阅我的电子邮件列表,以便在我发布新内容时得到通知。

https://david-farrugia.medium.com/subscribe

另外,考虑成为一名会员来支持我和你其他喜欢的作家。每月 5 美元,你就可以无限制地阅读 Medium 上的每一篇文章。

https://david-farrugia.medium.com/membership

想给我买杯咖啡吗?

https://paypal.me/itsdavidfarrugia?country.x=MT&locale.x=en_US

想取得联系?

我很想听听你对这个话题的想法,或者任何关于数据和人工智能的东西。如果你想联系我,请发邮件到 davidfarrugia53@gmail.com给我。

Linkedin——Twitter

用 FastAPI 构建文本预处理微服务

原文:https://towardsdatascience.com/building-a-text-preprocessing-microservice-with-fastapi-ca7912050ba

用 Python 创建和分发一个简单的 NLP 应用程序

Timelab ProUnsplash 上拍摄的照片

介绍

预处理是机器学习/数据科学应用中最重要的步骤之一。在现实世界中,大多数数据集都是脏的,有缺失值,并且充满了不正确的列,如字符串、日期和其他类型的非数字特征。

在常规应用程序中,原始数据主要在表格中,标准化、缩放和编码等方法对于机器学习模型正常工作至关重要。

在 NLP(自然语言处理)应用中,预处理是一个更加关键的步骤,因为文本本来就是非数字、非结构化和杂乱的数据。因此,文本预处理通常包括一些常见的步骤,比如清理(删除特殊字符、标点符号、停用词等)和规范化(将文本转换成小写、词干化和词汇化)。

通常,这些步骤在不同的应用程序之间共享,而一个微服务可能是封装、分发和重用预处理功能的好方法。

在这篇文章中,使用 FastAPI 库在 Python 中构建了一个简单的文本预处理 API。在这个例子中,我们将使用 RegEx(正则表达式)来规范化文本“噪声”,但是所提出的概念可以很容易地扩展到更复杂的应用程序。

在 Python 中查看正则表达式

Regex 是一个结构化的字符串,用来表达一个文本通用模式,该模式用于搜索文本中的术语。Regex 是 NLP 应用程序中一个非常强大的工具,绝对值得关注,但是这篇文章不会涉及它的所有实现细节,只涉及它的功能。

如上所述,正则表达式是描述通用模式的字符串。例如,假设您有一个人与人之间交换的纯文本消息的数据库,您的任务是检索对话中引用的所有电子邮件地址。

电子邮件地址是常见模式的一个很好的例子。它们都有一个“@”符号,并以点结束,如 foo@email.com 或 bar_@emailx.edu.br。正则表达式允许我们以结构化的明确方式描述这种模式。对于这个例子,它将是这样的:

r"\w+\@\w+(?:\.\w+)+"

这个正则表达式并不完美,但它适用于大多数情况。

在 Python 中,我们可以使用原生 regex 模块对字符串执行搜索和替换。

正则表达式查找所有电子邮件地址

输出:

> ['[joaozinho@email.com](mailto:joaozinho@email.com)', '[john123@edu.us](mailto:john123@edu.us)']

但是这对文本预处理有什么帮助呢?

通过使用 replace 函数,regex 模块在文本规范化中非常有用。这个函数允许我们找到一个模式并用一些预定义的文本替换它。通过这样做,我们能够以更有意义的独特方式在文本中表示概念(这可以进一步提高 ML 模型的性能)。

正则表达式子示例

输出:

This is a valid email address:  <EMAIL>  
This is another valid email address:  <EMAIL>  
This is not: antunes@@abc.br

什么是微服务?

一个微服务是一个小应用程序,负责系统上一个非常具体的任务。它独立运行,与其他应用程序隔离,并且在资源上也是自给自足的。

微服务一旦运行,就可以通过 API 调用在其公开的端点上使用。我们将使用 FastAPI 来构建我们的微服务端点,FastAPI 是一个 Python web 框架,可以用最少的代码轻松实现 API。

这篇文章也不会深入 API 和 HTTP 请求的所有细节,但是所提出的应用程序非常简单,应该很容易理解。

微服务示例。图片作者。由 Freepik 创作的图标。

提示:Docker 是软件开发中必不可少的工具,并且与微服务的概念密切相关,因此值得一试。

履行

设置环境

首先要做的是创建一个隔离的 python 环境,只包含所需的依赖项。您可以使用 Anaconda 或 PyEnv。

创建环境后,安装以下软件包:

fastapi
uvicorn[standard]
requests

uvicon【standard】:本地服务器托管 API
请求: Python 库进行 HTTP 请求
FastAPI:FastAPI 包

构建我们的第一个 API 端点

我们与 API 通信的方式是通过在其公开的端点上发出请求。在 Web APIs 的上下文中,端点在 URL 上公开。

使用 API 服务的应用程序/个人被称为客户端,它可以通过 HTTP 请求(如 GET、POST 或 PUT)与端点进行交互。这些请求中的每一个都有预定的行为。例如,GET 可用于从数据库中检索一个条目,并提交以供插入。

以下示例基于 FastAPI 官方页面。用下面的代码创建一个文件 main.py :

API 示例

然后,在终端上运行:

uvicorn main:app --reload

“reload”参数仅用于开发,不要用于生产。

http://127.0.0.1:8000/上打开浏览器,您应该会看到以下响应:

{"Hello":"World"}

恭喜你,你已经构建了你的第一个 API!

那么,我们来了解一下是怎么回事。

  • app 变量引用将要运行的 API 应用程序;
  • “uvicon main:app-reload”使用文件 main 中的变量 app 启动服务器。
  • 默认情况下,API 运行在端口为=8000(http://127 . 0 . 0 . 1:8000)本地主机上。这是根,这意味着所有的端点都是从它开始的路径,比如 http://127 . 0 . 0 . 1:8000/do/something/
  • 当发出 get 请求时, decorator app.get("/") 将路径"/"链接到函数 read_root
    这意味着当在 http://127.0.0.1:8000/上发出 GET 请求时(注意末尾的“/”),函数 read_root 被调用,返回作为响应被发送回来。
  • 当您的浏览器打开 http://127.0.0.1:8000/时,它在链接上发出 GET 请求并显示响应。

在端点中接收数据

构建一个端点来接收信息与我们之前所做的并没有太大的不同。主要区别在于该函数还需要接收参数。例如,下面的端点接收两个参数:

@app.get("/example/") 
def read_item(id: int, name: string): 
    return {"your_id": id, "your_name": name}

该信息由客户端在请求中以 JSON 格式发送(进一步详述),如下所示:

{ "id":123, "name": "Joao" }

最酷的是 FastAPI 自动为我们验证数据。因此,如果客户端发送一个带有{"name":123}或根本没有名称的请求,它将返回一个类型/字段错误。除了默认的 python 类型,FastAPI 还支持验证 Pydantic 类。

用 Pydantic 验证输入

Pydantic 是一种更健壮的验证数据的方法。它允许我们使用 python 类描述我们期望的输入(模型),设置必填字段、允许的间隔和值、可选参数以及更多的。例如,下面的类描述了一个人物应该具有什么样的价值观。

Pydantic 示例

然后,可以将该类插入到函数的参数字段中,就像上一个示例中描述的本地 python 类型一样。

构建我们的预处理端点

现在,终于是实现 API 本身的时候了。

我们的微服务的目的是基于一个定义好的需求列表对文本进行预处理。例如,用户可以请求删除所有货币值并返回小写文本。

它将具有以下功能:删除标点符号,替换数字,替换金钱,替换电话号码,替换电子邮件,并把文本小写。

文本预处理微服务。图片作者。由 Freepik 制作的图标。

总结一下,我们需要实现:

  1. 文本预处理功能。
  2. 端点的输入验证模型。
  3. 将接收文本和预处理步骤列表并返回预处理文本的端点。

先说文本预处理函数。如前所述,我们将使用正则表达式来规范化文本。下面的代码展示了几个负责它的类的实现。BaseRegExPreprocessing 类充当抽象类。每个继承的类都用其代表系统功能的值覆盖了 regexreplace_token

文本预处理类

输入验证非常简单,我们只需要创建一个类来描述客户端发送的数据应该是什么样子。下面的代码展示了这个实现。

Pydantic 验证类

StringTransformation 类枚举 PreprocessingRequest 类中变量步骤的所有可接受值,每个值代表一个功能。PreprocessingRequest 是主类,它描述了发送的数据应该是什么样子。

最后,我们可以将它们放在主 FastAPI 应用程序中。

主要 API 代码

然后,用 uvicorn 运行服务器即可。

对一些例子进行测试

为了正确测试我们的 API,我们将使用 python 请求库。

第一个例子是替换字符串中的所有电话号码。

在查看响应的输出之前,让我们先看一下代码。

数据是我们发送给 API 的信息。它的格式如 pydantic 类中所述:“text”字段包含要预处理的文本,而“steps”字段包含预处理步骤的列表。它作为 json 在 get()方法中传递。

requests.get()在 HTTP://localhost/preprocess/上发出 HTTP GET 请求,这是我们端点的 URL。

可以通过调用 response.json() 来检索返回的输出

API 的一个优点是客户端与应用程序完全无关。我们的客户端是用 python 编写的,但它可以是 C++应用程序、web 浏览器或任何可以发出 HTTP 请求的东西。

事实上,测试端点的另一种方法是访问 localhost 上的 http://127.0.0.1:8000/docs 路径。这个路径指向 FastAPI 自动文档,构建一个迭代窗口来运行请求。

让我们以一个更复杂的例子来结束,这个例子探索了系统的许多功能:

输出:

结论

在本文中,我们使用 python regex 和 FastAPI 构建了一个用于文本规范化的 API。我们探索了 regex 规范化文本的能力,这是 NLP 应用程序中的一个基本过程,并描述了如何使用微服务和 API s 的概念封装和分发这个过程。尽管这是一个简单的应用程序,但它展示了可以扩展到更复杂情况的基本概念。

文中讨论的所有主题都进行了简要的探讨,以使帖子更小,并允许我们在主要目标的方向上更快地移动。要建立更坚实的基础,请查看以下参考资料:)

我希望你喜欢这次旅行,如果你喜欢讨论的任何话题,我强烈建议你进行更深入的研究。

感谢您的阅读!;)

参考

Github 上的代码:https://Github . com/jaumpedro 214/posts/tree/main/text _ pre _ fast _ API

[1] CodingEntrepreneurs Youtube 频道, Python & FastAPI 教程:创建一个 ai 微服务,从图像中提取文本
【2】aa shish Nair,正则表达式:文本分析的瑞士刀。在媒体上,走向数据科学。
【3】AmigosCode Youtube 频道, FastAPI 教程——用 Python 构建 RESTful API
【3】什么是 REST API?。在红帽子上。
【4】HTTP 请求方法。关于 MDN Web 文档
【5】Fast API 官方文档。
【6】Pydantic 官方文件

构建一个工具来估计周围地区的人口

原文:https://towardsdatascience.com/building-a-tool-to-estimate-surrounding-area-population-c7d77263468a

一步一步的指南,建立一个免费的周围人口估计工具(加上访问一个已经建成的!)

克里斯多夫·伯恩斯在 Unsplash 上拍摄的照片

人口统计——尤其是人们居住的地方——是许多选址决策的重要工具。有多少 25 岁或 25 岁以上的人住在你想开餐馆的地方(现有市场)?你要建仓库的地方周围住了多少人(可用工人)?

解决此类问题的数据分析师可能会发现自己不得不使用复杂的数据集或学习曲线陡峭的工具来获取和加载适当的数据,计算复杂的方程,并以直观易懂的方式输出数据。许多工具将给出城市周围区域的人口,有些工具给出精确点的估计值。即使你得到了一个数字,你知道并理解给你这个数字的基本数据和方法吗?你能向别人解释吗?

遵循下面的步骤,看看如何自己构建一个人口估计工具——理解数据和计算——或者随意使用我组装的工具(都是用免费数据&工具构建的)。像许多分析工具一样,它涉及假设、变通办法和一些不精确之处,但作为一种估算工具——就其成本而言——它为许多人口统计讨论提供了一个“足够好”的起点。

一个完整的人口估计工具的例子;作者图片

数据和工具

照片由 Unsplash 上的 Enayet Raheem 拍摄

最可靠的人口数据来源通常被认为是美国人口普查局报告的数据。大多数人都知道常见的地理边界,如州、县和邮政编码。您知道人口和人口统计有更详细的视图吗?人口普查区(*均人口约 4000 人)和人口普查区块组(*均人口约 600-3000 人;报告的最小地理位置)是可用于分析的附加地理位置。

对于区域和区块组,我们获得了精度,但失去了可解释性。我们可以很好地了解包含一个县、城市或邮政编码的区域。我们可以在地图上找到它,或者把它写在我们地址的一部分。你永远不会看到一个数据点被报告为“人口普查区域 37183050100”。然而,为了找到周围地区的人口,额外细节的好处超过了可解释性的损失。

人口普查区块组数据集包含超过 170,000 条记录,因此为了提高性能,我们将使用包含大约 73,000 条记录的人口普查区域数据。

在人口普查数据的世界中导航可能是复杂的——不仅要找到数据,还要格式化和准备数据。这就是为什么我去了由林赛·M·佩廷吉尔 &团队开发的 AskIggy 的开放数据门户,并导航到一个已经为我准备好的人口普查区域数据集(你需要注册一个免费账户才能访问),以快速完成该过程的第一步。

该文件以. gz 文件的形式下载,所以我在 R 中运行了一个快速脚本,将它准备成一个可以被 Tableau 接收的. csv 文件。

library(readr)read_csv('acs_census_tract_social_20210716.csv.gz') %>%
  write_csv('acs_census_tract_social.csv')

您还需要下载形状文件,供人口普查区域稍后使用。下载数据后,我们需要通过计算每个普查区域的大致中点来对文件进行一些小的操作。这是一个众所周知的困难的练习,原因超出了本文的范围,但是对于评估工具来说,您可以使用下面的代码和函数来创建一个评估。

函数(归功于这个 StackOverflow 答案)找到多边形形状(人口普查区域的形状)的质心(中心)。如果中心不在多边形内(想象一个类似 r 的不规则形状;顶部/底部和左侧/右侧中点不在形状中),该函数使用st_point_on_surface函数返回多边形中的一个点,这是一种计算开销更大的在多边形中查找点的方法。

library(sf)
library(tidyverse)shapefile <- st_read("cb_2019_us_tract_500k/cb_2019_us_tract_500k.shp")st_centroid_within_poly <- function (poly) {# check if centroid is in polygon
  centroid <- poly %>% st_centroid() 
  in_poly <- st_within(centroid, poly, sparse = F)[[1]]# if it is, return that centroid
  if (in_poly) return(centroid)# if not, calculate a point on the surface and return that
  centroid_in_poly <- st_point_on_surface(poly) 
  return(centroid_in_poly)
}shapefile_midpoitns <- shapefile  %>% 
  mutate(lon = map_dbl(geometry, ~st_centroid_within_poly(.x)[[1]]),
         lat = map_dbl(geometry, ~st_centroid_within_poly(.x)[[2]]))st_write(shapefile_midpoints, 'tracts_w_midpoint.shp')

有了正确格式的数据,我们将继续讨论如何处理数据。我是一个 Tableau 爱好者,所以这将是我的选择工具。如果你没有 Tableau 的工作或个人使用许可,你可以在这里下载他们软件的免费版本。

构建工具

下载完数据和表格后,您就拥有了构建该工具所需的所有资源。我假设你已经对使用 Tableau 有了基本的了解,可以继续学习本教程。

读入并加入数据

首先创建到空间数据集的连接,并选择人口普查区域 shape file(tracts _ w _ midpoint . shp)。添加第二个连接并将。我们从 AskIggy 数据创建的 csv 文件放到工作区。使用 shapefile 中的大地水准面和. csv 中的 id 创建连接。您可能会看到数据类型不匹配的错误-单击进入。csv 文件(通过单击文件名)并将您的 Id 列更改为字符串。

读入和联接数据的例子;作者图片

连接 Geoid = id 上的两个数据集,其中将 Id 数据类型更改为字符串;作者图片

准备数据

我们现在将设置一些计算字段和参数,为后面的分析打下基础。我们工作的基础将是经纬度点之间的计算。关于这些的基本入门,阅读此处

创建新参数以输入纬度值;我把我的名字叫做Lat Param。将它创建为具有所有允许值的浮点型。对经度做同样的操作(我把我的称为Lon Param)

Tableau 中 Lat Param 参数的示例;作者图片

我们现在需要将这两个数字转换成一个地理点,一个纬度/经度对。我们将利用 Tableau 的MAKEPOINT函数,该函数将数字作为输入,并将该字段解释为一个空间点。

Tableau 中 MAKEPOINT 函数的示例;作者图片

现在,我们要设置计算我们点周围的半径。Tableau 有一个方便的函数可以做到这一点,叫做BUFFER。该函数接受一个空间点、一个数字(半径距离)和一个数字度量(如英里、米等)的参数。).我创建了一个名为Radius Buffer的新参数,以便以后选择半径时更加灵活。我用英里作为我的度量。

说白了,函数就是告诉 Tableau:从我选择的位置,创建一个半径为【Radius Buffer】英里的半径,从那个点向各个方向延伸”

创建缓冲区的示例函数;作者图片

让我们暂停一下,看看我们做了什么。

我们现在可以接受任何一组纬度和经度点(由我们/用户输入),并以英里为单位创建该点周围的半径(同样,由我们/用户输入,以获得最大的灵活性)。通过将我们的Inputted Point放在一张新表的细节上,并添加Buffer Point作为附加标记,我们可以看到该点以及从该点开始的周围距离。

下面是北卡罗来纳州罗利市中心附*的州议会 5 英里半径范围。

红点是我们的输入点,灰色半径是我们的缓冲点,这里是 5 英里;作者图片

还记得我们之前必须创建计算来找到每个人口普查区域的大约中点吗?我们这样做是因为我们可以使用 Tableau 的内置DISTANCE函数来计算我们输入的点和每个人口普查区域的中点之间的距离— 这就是我们估计周围地区人口的方式。

我们将创建一个新的计算字段,我将其命名为Distance Function,它从我们输入的点开始,到每个人口普查区域的中点结束(注意在前面的 R 代码中,我们如何为中点创建了一个LatLon列,我已经在MAKEPOINT函数中使用了它们)。我们想用英里来度量距离。该函数表示:计算从输入点到每个大约人口普查区域中点的距离(以英里为单位)。

最后,我们再创建一个计算字段作为过滤器。我们想知道我们计算的距离是否小于我们选择的半径缓冲区。例如,如果我们想要一个 10 英里的半径([半径缓冲区] = 10),该过滤器将只让我们看到中点距离输入点不超过 10 英里的人口普查区域值。

把它放在一起

我们现在已经准备好了构建工具的一切!

在新的工作表中,将您的Inputted Point拖到 detail 上。然后拖动Buffer Point添加一个标记层。然后将你的Geometry字段从 shapefile 拖到第三个标记层。添加距离过滤器=真实过滤器。将我们的 AskIggy 文件中的 Pop 总数添加到您输入的点的标签中。经过一些颜色和不透明度的调整,你可以得到如下的东西。红色点是输入点,绿色圆圈是半径缓冲区,蓝色多边形是人口估计中包含的人口普查区域。我们可以在此视图中显示参数和半径缓冲区,以便随时进行更改。

创建您的人口估计工具与一对夫妇 Tableau 点击。为清晰起见,编辑了一些格式化步骤;作者图片

北卡罗来纳州罗利市地址的人口估计示例;作者图片

对于北卡罗来纳州罗利一个半径为 5 英里的选定点,我们估计人口约为 222,719 人。如果我们想要 10 英里的半径呢?改变半径缓冲参数,我们的视觉将自动更新。

距离我们 10 英里的缓冲区

还记得我们说过这只是一个估计,而且不精确吗?视觉可以帮助我们理解估计有多精确。当半径缓冲区的一部分边缘没有基础蓝色时,这是我们估计中遗漏的人口。当蓝色多边形超出缓冲区时,这是不应该包括在内的额外人口。

这是因为我们计算的是到人口普查区域面的中点的距离,而不是到边界的距离。如果从人口普查区域的Inputted Point中点的距离大于我们的半径缓冲区,即使面的一部分在我们的半径范围内,数据也会被过滤掉。然而,随着半径缓冲区的增加,我们预计误差占总人口的百分比会下降。此外,当考虑到在许多情况下既有重叠区域又有欠重叠区域时,一些误差可能会自我抵消。

谷歌地图上的纬度和经度的例子。单击地图而不是感兴趣的点来获取这些值。

一个常见的问题可能是— 我如何获得地址的纬度和经度?有几种免费的方法可以做到这一点。你可以进入谷歌地图,点击地图上的一个点(不是一个感兴趣的点,而是地图上的一个地方),底部的弹出窗口(如左侧截图所示)会显示该点的纬度(第一个值)和经度(第二个值)。您还可以使用各种免费工具,输入地址来获取纬度和经度值。这里有一个可以试试

如果你把这样的工具放在别人面前,你可以把这些转换器网站嵌入到 Tableau 仪表板中,以帮助别人找到输入的纬度/经度点;在下面的截图中,你可以看到我的方法带有一个嵌入式网站和一些用户在主页上输入的参数。

使用自由文本参数字段嵌入纬度/经度地址转换器的示例;作者图片

概括地说,我们构建工具的步骤是:

  1. 源普查区域形状文件和数据;用编程语言做一些准备
  2. 加载并连接 Tableau 中的数据
  3. 设置参数并输入所选地址的经纬度坐标
  4. 计算从坐标到所有人口普查区域形状中点的距离
  5. 只保留我们期望半径内的人口普查区域
  6. 对步骤(5)中保存的人口普查区域中的人口进行求和

根据分析

除了“美化”地图以使其更加用户友好(这是我所做的),还有其他几个领域可以探索——我相信许多读者已经可以想到对我上面概述的内容进行改进,以使其在统计上更加严谨或在空间上更加精确。

我想到的一些想法是:

  • 您可以使用人口普查区块组而不是区域来获得更精确的估计值(在加载和处理数据时,请耐心使用 Tableau)
  • 您可以使用相同的数据和过程,按收入范围、年龄、有孩子的家庭等对人口进行细分;在普查区域/区块组级别发布的任何内容都可以使用这种方法进行汇总
  • 有了 Tableau 的付费版本,您可以使用代码脚本为使用可用 API 的最终用户自动执行纬度/经度转换

使用空间数据极具挑战性-有许多方法会无意中误用函数、坐标和距离。然而,在许多情况下,不精确是可以的。如果一个半径内实际上有 20 万人,我们说有 19 万或 21.5 万人,这有关系吗?考虑您的用例可接受的误差范围,当方向数据“足够好”时,我们可以快速开发类似的工具,而无需成为 ArcGIS 大师或地理空间数据科学家。

有兴趣讨论位置策略和分析吗? 在 LinkedIn 上联系我或者在 jordan@jordanbean.com 给我发邮件

用 Python 构建一体化音频分析工具包

原文:https://towardsdatascience.com/building-an-all-in-one-audio-analysis-toolkit-in-python-e849a0e2129e

在一个地方分析您的音频文件

凯利·西克玛在 Unsplash 上的照片

语言是人类之间所有对话的基础。因此,自然语言处理(简称 NLP)领域无疑在帮助人类日常生活方面拥有巨大的潜力。

简而言之,NLP 的领域包括一组旨在理解人类语言数据并完成下游任务的技术。

自然语言处理技术涵盖了许多领域,如问题回答(QA)、命名实体识别(NER)、文本摘要、自然语言生成(NLG)等等。

自然语言处理的几个子领域(图片由作者提供)

虽然 NLP 中大多数先前的研究和开发主要集中在应用各种技术上,特别是在“文本”数据上,但最*,社区已经见证了基于语音的交互的巨大采用,引导机器学习工程师在语音领域进行实验和创新。

因此,在这篇博客中,我将在 Streamlit 中演示一个包罗万象的音频分析应用程序,它以一个音频文件作为输入,并且:

1\. Transcribes the audio
2\. Performs sentiment analysis on the audio
3\. Summarizes the audio
4\. Identifies named entities mentioned in the audio
5\. Extracts broad ideas from the audio

为了实现这一点,我们将使用 AssemblyAI API 来转录音频文件,并简化它以用 Python 构建 web 应用程序。

下图描绘了这个应用程序准备就绪后的样子。

音频分析工具包概述(图片由作者提供)

我们开始吧🚀!

应用程序工作流程

在构建应用程序之前,最好强调一下我们的应用程序的工作流以及它将如何工作。

下图描述了该应用程序的高级图形概览:

AssemblyAI 的转录服务工作流程(图片由作者提供)

如上所述,Streamlit web 应用程序首先将一个音频文件作为输入。

接下来,我们将把它上传到 AssemblyAI 的服务器,以获得音频文件的 URL。一旦 URL 可用,我们将向 AssemblyAI 的转录端点创建一个 POST 请求,并指定我们希望对输入音频执行的下游任务。

最后,我们将创建一个 GET 请求来从 AssemblyAI 中检索转录结果,并将它们显示在我们的 streamlit 应用程序上。

项目要求

本节将强调构建音频工具包的一些先决条件/依赖关系。

排名第一的安装简化版

在 Streamlit 中构建 web 应用程序需要在本地安装 Streamlit python 包。

#2 获取 AssemblyAI API 访问令牌

要访问 AssemblyAI 的转录服务,您应该从他们的网站获得一个 API 访问令牌。对于这个项目,我们姑且定义为auth_key

#3 导入依赖关系

最后,我们将导入这个项目中需要的 python 库。

这样,我们就可以构建我们的音频分析 web 应用程序了。

构建 Streamlit 应用

接下来,让我们继续在 Streamlit 中构建 web 应用程序。

如上所述,我们的应用程序将包括四个步骤。这些是:

1\. Uploading the file to AssemblyAI
2\. Sending the Audio for transcription through a POST request
3\. Retrieving the transcription results with a GET request
4\. Displaying the results in the web application

为了实现这一点,我们将定义四种不同的方法,每种方法都致力于上述四个目标中的一个。

然而,在我们继续之前,我们应该为我们的请求声明头,并定义 AssemblyAI 的转录端点。

  • 方法一: **upload_audio(audio_file)**

该方法的目标是接受从用户处获得的音频文件,并将其上传到 AssemblyAI 以获得该文件的 URL。

请注意,只要可以通过 URL 访问音频文件,就没有必要将它上传到 AssemblyAI。因此,如果音频文件已经可以通过 URL 访问,则可以跳过实现此方法。

upload_audio()方法的实现如下所示:

该函数接受audio_file作为参数,并在 AssemblyAI 的upload_endpoint处创建一个 POST 请求。我们从 AssemblyAI 返回的 JSON 响应中获取upload_url

  • 方法二: **transcribe(upload_url)**

顾名思义,这个方法将接受从上面的upload_audio()方法获得的音频文件的 URL,并将其发送给 AssemblyAI 进行转录。

在上面的 JSON 对象中,我们指定了音频的 URL 和我们希望在 AssemblyAI 的转录端点调用的下游服务。

对于这个项目,这些服务包括情感分析、主题检测、摘要、实体识别和识别文件中的所有发言者。

transcription_endpoint创建 POST 请求后,我们返回 AssemblyAI 返回的transcription_id,稍后我们可以用它来获取转录结果。

  • 方法三: **get_transcription_result(transcription_id)**

倒数第二步是从 AssemblyAI 中检索转录结果。为了实现这一点,我们这次必须创建一个 GET 请求,并提供在上一步中从 AssemblyAI 接收到的惟一标识符(transcription_id)。

实现如下所示:

由于转录时间取决于输入音频文件的持续时间,我们定义了一个 while 循环来创建重复的 GET 请求,直到请求的status变为completed或者转录请求指示了error

接收到的特定音频文件的转录响应如下所示:

  • 方法四: **print_results(results)**

这个应用程序中的最后一个方法是在 Streamlit 应用程序上打印从 AssemblyAI 获得的结果。

为了避免应用程序前端的混乱和文本混乱,我们将把每个服务封装在一个 Sreamlit 扩展器中。

与本项目相关的转录响应的关键是:

  • **text**:包含音频的转录文本。

  • **iab_categories_result**:该键对应的值是音频文件中标识的主题列表。

  • **chapters**:该键表示音频文件的摘要为不同的章节。

  • **sentiment_analysis_results**:顾名思义,这个键保存了音频文件的逐句摘要。

  • **entities**:最后,这个键存储音频文件中识别的实体。

集成主方法中的功能

作为构建 Streamlit 应用程序的最后一步,我们将上面定义的函数集成到main()方法中。

首先,我们为用户创建一个文件上传程序来上传音频文件。

一旦音频文件可用,我们就将其发送到方法 1 ( upload_audio),然后转录音频(transcribe)并检索结果(get_transcription_result),最后在 Streamlit 应用程序上向用户显示结果(print_results)。

执行应用程序

我们的音频分析应用程序已经准备好了,现在是运行它的时候了!

为此,打开一个新的终端会话。接下来,导航到您的工作目录,在用您的 python 文件的名称替换file-name.py之后执行以下命令:

streamlit run file-name.py

上传音频到应用程序(图片由作者提供)

演示演练

上面的上传者要求你上传一个音频文件。一旦你这样做了,上面定义的函数将被顺序执行以生成最终结果。

上传文件的转录结果如下所示:

音频分析应用程序的演练(作者提供的 Gif)

结果

在本节中,我们将讨论从 AssemblyAI 转录模型中获得的结果。

音频转录

输入音频的部分转录如下图所示。

完整音频转录(图片由作者提供)

主题

下图显示了演讲者在整个音频中讨论的广泛话题。

在音频中找到的主题(图片由作者提供)

摘要

为了生成摘要,AssemblyAI 的转录服务首先将音频分成不同的章节,然后分别对每一章进行摘要。

输入音频文件的摘要如下所示。

音频摘要(图片由作者提供)

情感分析

AssemblyAI 将每个句子分为三类情绪——PositiveNegativeNeutral

音频中前三句的感悟如下图。它们被转录模块精确地归类为Neutral

音频中句子的情感(图片由作者提供)

实体检测

最后,音频中标识的实体及其对应的实体标签如下所示。

音频中的实体(图片由作者提供)

结论

总之,在这篇文章中,我们使用 AssemblyAI API 和 Streamlit 构建了一个全面的音频应用程序来分析音频文件。

具体来说,我演示了如何对输入音频执行各种下游 NLP 任务,比如转录、摘要、情感分析、实体检测和主题分类。

感谢阅读!

🧑‍💻成为数据科学专业人士!获取包含 450 多个熊猫、NumPy 和 SQL 问题的免费数据科学掌握工具包。

✉️ 注册我的电子邮件列表 不要错过另一篇关于数据科学指南、技巧和提示、机器学习、SQL、Python 等的文章。Medium 会将我的下一篇文章直接发送到你的收件箱。

用 GraphQL 构建分析 API:数据工程的下一个层次?

原文:https://towardsdatascience.com/building-an-analytics-api-with-graphql-the-next-level-of-data-engineering-6a8aea32ba72

拥有一个高性能、安全、可靠的数据端点是实实在在的挑战。尤其是选择每个人都同意的准确的度量和尺寸,对吗?让我们解决这个问题。

图片由穆罕默德·巴盖里·阿迪布·贝鲁斯Unsplash 上拍摄

你可能会问,为什么数据工程师要用 GraphQL?GraphQL 解决了为每个客户端提供不同接口的问题,它为所有客户端(如 web、移动和 web 应用程序)统一了一个 API。我们现在在数据领域面临着同样的挑战,我们将多个客户端与众多后端系统集成在一起。

那么 GraphQL 是什么呢?在微服务和 web 应用程序的世界中,GraphQL 是一种流行的查询语言,并充当数据层。它本质上是 API 的类固醇 SQL。在本文中,我们将介绍如何将来自所有服务的数据组合成一个统一的 API。

为什么为数据工程师选择 GraphQL?

GraphQL 是什么?

先说 GraphQL。在 2015 年公开发布之前,它是由脸书在 2012 年内部开发的。本质上是为了更好地为他们的移动应用服务,所有的 API 都针对网络进行了优化,拥有更大的客户端和更快的数据连接。他们没有为移动设备复制所有现有的 API 端点,而是很快意识到他们需要另一种解决方案。这个解决方案被称为 GraphQL。如果你想知道更多关于它是如何开始的,我推荐你去看网飞式的纪录片

本质上,GraphQL 是 API 的类固醇 SQL。最棒的是,它基于与 REST 相同的技术,支持 GET 或 POST 请求。但是,不是有多个端点,而是有一个能够接受复杂查询的智能端点。假设您的所有用例都有数百个 API 端点。GraphQL 允许您组合它们,但是只选择您需要的列(SELECT columns FROM ...)和行(WHERE ...)。这些选项可以减少 API 调用和数据负载,因为您可以将几个 REST 调用合并成一个。这也增强了前端开发人员的能力,因为他们更少依赖后端开发人员。

(查询)界面

下图展示了使用 GitHubpublic graph QL endpoint的简单查询。你可以自己尝试下面的查询。

query {
  repository(owner: "sspaeti-com", name: "practical-data-engineering") {
    name
    description
    owner {
      login
    }
    homepageUrl
    visibility
    createdAt
  }
}

响应如下所示:

{
  "data": {
    "repository": {
      "name": "practical-data-engineering",
      "description": "Real estate dagster pipeline",
      "owner": {
        "login": "sspaeti-com"
      },
      "homepageUrl": "https://www.sspaeti.com/blog/data-engineering-project-in-twenty-minutes/",
      "visibility": "PUBLIC",
      "createdAt": "2020-11-18T21:15:37Z"
    }
  }
}

为什么 GraphQL 如此适合?

大多数关于 GraphQL 的讨论都集中在数据获取上,但是任何完整的数据*台也需要一种方法来修改服务器端数据。这就是内置mutation发挥作用的地方,我们稍后在分析 API 中利用它们。在查询和突变下找到更多信息。

维护简单。GraphQL 本质上是一个端点/graphql(可以随意命名),而不是通过创建更多端点来增加复杂性。它还增加了它的可扩展性和可维护性。

类型验证内置于 GraphQL 模式语言中,用于预先验证 GraphQL 查询。这些允许服务器和客户端在有无效查询时有效地通知开发者,而不依赖于运行时检查

自证!当您用列和类型构建您的接口时,您记录了代码,通过使用像 GraphQL 这样的工具进行自省,这些代码自动对客户端可用。对于构建 API 的开发者和使用它的人来说,这是一个巨大的时间节省。

一个大问题是你的 API 模式与你的数据库模式的解耦。例如,你今天使用 AWS Lambda 函数在你的后端查询 Google Big Query ,你想要或者必须换成其他的东西比如 Apache Druid 。您可以在不干扰用户的情况下进行切换,因为 GraphQL 充当了您的语义层。想象一下相反的情况,您将 web 应用程序与 Big Query 的 API 紧密集成。迁移和重写所有的前端代码需要付出很大的努力。

💡注意:除了查询和变异,操作**subscription** 对于更新下游事件和防止轮询也很方便。服务器会将更改推送给所有订阅者。这对于基于事件或实时的应用程序来说很方便。订阅本身可以是 GraphQL 使用 Websockets 在服务器和客户端之间保持开放连接的updatedeleteinsert

另一个成功的方面是标准化规格表。无论如何,成为一个规范意味着更好的工具、更容易的使用和服务间更全面的互操作性都更容易实现。我认为仅此一点就让 GraphQL 难以避免。

GraphQL 不是什么

它不是一个能为您提供所有必要服务的成熟解决方案。它是一个框架的一部分,这个框架提供了适当的边界和工具来为您的业务实现合适的逻辑,否则您将不得不构建这些逻辑。但是没有创建查询接口、权限、验证等的麻烦。把所有东西粘在一起。

它不是 YAML ,也不是 JSON,虽然非常接*,但它是它的格式,如上面的接口部分所示。你可以在文章REST vs graph QL API 中找到其他潜在的问题,好的,坏的,丑陋的

什么是分析 API?

现在,我们已经看到了 GraphQL 可以做什么,我们讨论构建一个通向下一级数据工程的 API,在本文中我称之为分析 API 。该 API 将使所有利益相关者能够以一致和分离的语义方式使用单一来源访问分析数据(如果您知道更好的名称,请告诉我!).

作者使用 GraphQL | Image 的单端点分析 API 架构

分析 API 由五个主要组件组成,其中 GraphQL 是网关 API 和查询接口的最佳选择。除此之外,SQL Connector 还可以连接以 SQL 为母语的遗留或传统 BI 系统。指标或业务逻辑,也叫指标存储无头 BI 存储在指标存储中。假设你在一个有很多变化的大型组织中。在这种情况下,有一个数据目录会很有帮助,它可以帮助发现您的数据,并向数据集添加所有者、评论、评级和其他信息,以便在它们之间导航。orchestrator 持续可靠地更新数据存储中的内容。稍后将详细介绍每个组件。

数据团队如何努力构建分析 API

云架构比以往任何时候都更加复杂,尤其是随着工具和技术的最新发展。如今,每个数据团队都希望公司的决策者能够随时获得数据。无论是数据分析师、产品经理、数据科学家、业务人员还是数据分析师,都很难提供一个单一的接口来抽象所有异构数据存储,并让他们查询所有数据。最重要的是,新的原则和架构正在接受旧的想法,例如,数据网格中分散的数据产品和集中的云数据仓库。

ade vinta的 Xavier Gumara Rigol 表示,如果需要更复杂的处理,每个数据集应该至少有两个与 SQL 的接口,作为快速访问和通过笔记本的编程访问。

另一方面,如果您只有一个 Postgres 数据库或任何其他简化的架构,那么通过分析 API 构建和路由它可能没有意义。让我们看一看当今不同的数据团队,以及他们目前面临的挑战:

  • 机器学习人员想要一个 API 来试验 Jupyter 笔记本中的特定数据。
  • 商业智能用户需要报告公司如何使用他们选择的仪表板工具。他们需要一个 SQL 连接器。响应时间必须在几秒钟之内,因为他们希望实时切片并在会议上演示数字。如果可能的话,公司范围内的 KPI 已经预先计算好,随时可以使用。
  • 超级用户想要更新和修复一些不正确的数据。他们需要一个界面或清晰的文档来说明如何操作。更重要的是,他们是否在数据湖、 OLAP 立方体、配置等环境中进行。,应该没关系。
  • 应用具有不同需求的产品逻辑的内部应用和管道包括摄取新数据、修复无效状态、自动维护,例如压缩海量数据源或实现复杂的业务逻辑。
  • 外部客户想要为他们的数据仓库提取数据。
  • 经理们想一目了然地看到整体数字。

由于这些涉众有不同的用例和技能,很难支持他们所有人。通过现场验证的标准化 GraphQL 界面和记录在案的内置界面,我们拥有了当今最好的方法。这也是一个使更新保持一致并保存的机会,而不是直接与人联系🚒。

授权和认证值得注意,而不是在每个系统中创建新的组和用户。必须一次实施。但是如果你没有这样的 API,这是非常困难的。当然,您可以集成您的身份和访问管理解决方案,但是内置在中央 API 和 GraphQL 中是一种实用而优雅的方式。

分析 API 的组件

现在让我们更详细地看看每个组件,以及它们有效地做了什么。

API 和查询引擎

分析 API 的第一个组件是界面和查询引擎。这个接口是所有工具都可以访问的单一 GraphQL 端点。称之为代理、路由器或网关,它将每个querymutationsubscription转发到正确的服务或管道。

如果您有中央计算度量或任何不支持 SQL 的数据存储,查询引擎会有所帮助,您可以将 GraphQL 查询翻译成特定的查询语言。与 SQL 连接器的一个关键区别是使用高级和更通用的模式来查询数据。例如,代替SELECT COUNT(DISTINCT userid) AS distinct_users FROM customers,我们可以更概括地说:

SELECT {{ metrics.distinct_users }} FROM {{ datasources.customers }}
--or
SELECT * FROM {{ metrics.metric('customer', by='day', dims=['country', 'year']) }}

为此,我们需要一个中间层来将通用查询转换为实际的 SQL 查询,即查询引擎。

我希望你注意到这里所有商业智能工程师的利益和 。我们有一个定义,而不是用稍微不同的语法为所有数据存储编写又长又复杂的查询。我们不需要在不同的地方定义像distinctUsers这样的指标,而是将它存储一次,然后应用于所有的系统。没有必要担心你是否得到了最新版本或者是否有人改变了计算。下一章将详细介绍如何集中存储一个指标定义。

我们看到在变换层出现了更多的抽象。度量层(因 Airbnb 的 Minerva 、Transform.coMetriql 而流行),特征工程框架(更接* MLops),A/B 测试框架,以及各种形状和风格的本土计算框架的寒武纪大爆发。称之为“数据中间件”、“参数管道”或“计算框架”,但这个领域正在开始成形。来自现代数据堆栈如何重塑数据工程

如上面的分析 API 图所示,它通过 GraphQL 与其他组件集成,以便从指标和数据目录存储中读取数据,或者通过编排触发更新。除了只实现某些部分的无头 BI 工具之外,没有集成的工具。在最*围绕无头毕的炒作一章,你可以找到更多关于他们的内容。

度量存储|无头 BI

无头 BI 部分,或者我在本文中提到的度量存储,是存放所有度量的重要地方。度量标准,如计算度量、维度等。,这通常是从 Tableau、Power BI、Superset、Looker 等商业智能工具中了解到的。这些工具都有自己的度量存储,大多数情况下,它们都有描述聚合计算或维度的语言。这里最著名的是来自 Looker 的 LookML 。这里的不同之处在于,我们将所有 BI 工具的这一部分分离出来,并将它们集中起来。它允许我们以结构化和源代码控制的方式定义它们,而不是在专有工具中。

将其视为遵循 原则集中实现一次的某些业务逻辑、关键代码或元数据。假设您使用 Apache Druid,您可能有复杂的计算度量,作为 ML-Engineer 或任何客户,您希望使用 BI 工具查询这些度量。不是在每个工具中添加格式化选项、单位、描述来重新创建它们,而是将它们存储在分析 API 的中心。此外,在度量存储的帮助下。

分析 API 中不同组件之间的通信是通过 GraphQL 从查询引擎返回查询,在需要时通过直接查询查询或更新元数据存储和数据 API 配置,并执行来自编排的请求。

目前,大多数工具和人员使用流行的模板化解决方案和 Jinja 和 SQL 并集成到公司选择的 BI 工具中。但是由于 Jinja SQL 逻辑很难跨不同的数据库引擎重用,并且如果您开始嵌套这些查询,很快就会变得混乱,因此度量存储变得流行起来。

正如您现在所理解的,指标存储是分析 API 的一个重要组成部分,而作为分析查询和指标的关键组件几乎是同义词。此外,随着分析工程师越来越受欢迎,他们的领域知识主要是定义和创建这种指标的转换。

在此之上,我将看到类似于 Kafka 的 汇合模式注册表 附加服务集成,例如比较不同版本的度量定义和数据存储模式。这将减少将数据管道和应用程序逻辑与数据存储中不断发展的表和视图模式集成的错误。并允许一致地添加新的度量和维度。****

最*关于无头毕的炒作

如果您关注数据领域,您会注意到围绕元数据的炒作,这些术语被称为度量存储或无头 BI。有趣的开源和闭源公司以及围绕它起步的工具包括cube . jsMetriqlsuper grainTransform.co或者** Minerva API 您还将看到,由于上面提到的许多原因,他们正在使用或开始使用 GraphQL。******

最新的公告发生在 dbt 的公开会议的基调上联合来自德鲁巴宁关于“公制”。他讲述了 5000 年前我们测量的起源,如果你还没看过,那一定要看。如果你想知道更多的细节,请关注已经开始的 Github 问题中的许多信息和令人兴奋的想法。

数据目录

根据联合国欧洲经济委员会(une ce)2003 年的数据,在过去的十年中,数据增长了 40 倍(今天要高得多)。随着时间的推移,很难保持这一数量。数据目录服务解决了处理快速增长的数据的问题。

解决方案不在于数据,而在于跟踪元数据并有效地呈现它们。像 Amundsen 这样的数据目录和发现工具通过向我们显示哪些数据集可用、谁在哪年创建的、多少行的元数据、最小/最大条目来实现这一点。它包含一个评级系统,用户可以对数据集提供反馈,让您感受数据质量以及使用该数据集的有效性。这是谷歌搜索,但元数据有一个方便的界面,你的指标存储

根据您选择或构建的工具,您可以与指标存储和其他组件(如 orchestrator 或 web 应用程序)进行交互。如果组件本身有 GraphQL 接口,这就是最佳解决方案。用户将使用这种工具的 web 界面,分析 API 的其他部分将通过 GraphQL 和 REST 成为可编程接口。例如,编排工具将查询最新的 db-schema 或持续数据源的列表。

管弦乐编曲

编排部分是大部分业务逻辑和转换最终落地的地方。与其直接在 GraphQL 上将所有东西都构建到查询引擎中,不如使用合适的工具来重用代码并更好地集成它

我认为 Dagster 是现代的业务规则引擎,你可以在其中用 python 代码表达逻辑,这使得它与的无代码/少代码方法相比是可测试和可扩展的。Dagster 提供了大量的工具,如资源来捕获可重用的代码,如连接到 Druid,创建一个 delta 表,启动一个 spark 作业,所有这些都在管道中使用。分析 API 中的另一个构件是 Op ,它将您的业务逻辑浓缩为数据管道中的功能任务。它用类型化的输入和输出进行了很好的定义,并使用了诸如上述资源之类的上下文,使得作为 op 的一部分运行 spark 作业变得很容易。

分析 API 中的集成是与 GraphQL 的集成,因为 Dagster 有一个内置的。Dagster 使用这个接口来查询各种元数据,启动管道/传感器(突变),或者订阅特定的信息。旁注:这不是凭空而来的,因为 Dagster 的创始人尼克·施勒克是 GraphQL 的联合创始人😉。我们没有运行和使用 Dagster UI ,而是为开发人员使用该接口,并通过分析 API 对其进行抽象。

SQL 连接器

SQL 是除了 python 之外的数据语言,在前面的 文章 中有详细阐述。这就是为什么我们也需要为此提供一个接口。SQL 连接器集成了所有 BI、SQL speaking 或遗留工具。例如,连接器主要实现一个 ODBC 或 JDBC 驱动程序,Avatica 构建在 Apache Druid 使用的 Apache 方解石上。这样,就有了一种与 ANSI SQL 接口的方法,包括度量存储中的所有度量和维度,如果工具使用 SQL,就无需在访问端做额外的工作。

构建分析 API 的挑战

在写这篇文章的时候,我经常发现自己与现有的架构和工具相冲突。这一章无论如何也不意味着是完整的。它指出了构建这样一个中央界面的挑战,并让我们从它们的优缺点中学习。

  • 具有 OpenAPI 规范的微服务:由于部署应用程序很容易,我们可以在不同的 API 和服务之间进行大量双向通信。有了 OpenAPI 规范(早期称为 Swagger) ,你可以使用工具生成文档、客户端代码和测试。
  • ROAPI :另一个迷人之处是,它自动为静态数据集旋转只读 API,而不需要你写一行代码。它建立在 Apache ArrowDatafusion 之上。
  • 云数据仓库方法:如果您使用一个主要的云数据仓库,这是一种整体架构。它给你一个 API,你可以用普通的 SQL 访问数据。它有与你的网络应用紧密结合的缺点。
  • 无代码/少代码*台 :当你购买一个闭源厂商*台Ascend.io 或类似*台时也是如此。
  • Lakehouse 架构by data bricks:lake house 表明,一切都朝着更小的移动部分发展,将数据接口整合到最低限度或一个单一接口。首席执行官 Ali Ghodsi 最*表示:“尽管我们在所有数据工具和框架方面取得了很大进展,但人们仍在四处奔波,寻找正确的数字,并与曾经正确的数字进行斗争”。这表明需要指标存储和中央分析 API。
  • 数据虚拟化 :数据虚拟化是集中和连接所有移动部分的另一种方式。你不用四处移动和复制数据,也不用预聚合数据,但是你有一个语义中间层来创建你的商业模型(比如立方体)。这些工具,例如 Dremioothers ,使用 Apache Arrow 技术,该技术将缓存并优化大量内存,以便您获得快速响应时间。Apache Arrow 如何工作,以及他们如何集中数据移动,你可以在 Julien Le Dem 的 Spark Summit 演讲中看到。
  • 无服务器功能:更少的架构,但是对于更小的目标,你可以使用无服务器功能(没有基础设施)来运行你的胶合代码,例如,在 AWS Lambda 中,举一个例子。您的函数可以对支持高度事件驱动用例的事件做出反应。
  • **数据仓库自动化 :另一种解决方法是投资自动化。你可以不用购买你的数据仓库,而是用诸如 TimeXTener、WhereScape、BiGenius 和等等工具来构建它。您可以敏捷地工作,将所有内容集成到您的数据仓库中,而不是将所有内容集成到一个单一的接口中。更多关于那个

结论

正如我们所见,GraphQL 是数据工程和构建分析 API 的强大工具。我们了解了分析 API 的用途,以及数据团队如何努力为当今复杂的云架构开发一个 API,主要是为了服务于各种利益相关方和其他业务部门。具有查询引擎、指标存储、数据目录、编排工具和 SQL 连接器的分析 API 的组件。最后,我们看了看其他解决方案和挑战,总的来说,就是我们现在面临的问题。

这篇文章的意思是不要以任何方式成为银弹解决方案。更重要的是,我希望它能帮助你开发出适合自己的最佳解决方案。我相信在接下来的几个月或几年里,这个特定的主题将会有很多进展,我期待着看到围绕 Analytics API 和 GraphQL 的生态系统。我还对这些解决方案的命名感兴趣,因为在我看来分析 API 还不是最终的名称。**

暂时就这样了。我期待你的建议和批评。让我知道你是如何解决构建分析 API 的问题的,以及你对此的想法。

原载于 2022 年 1 月 22 日【https://www.sspaeti.com】

用 Python 构建交互式图像标注器

原文:https://towardsdatascience.com/building-an-interactive-image-labeler-with-python-e1a14c5fc4

使用鼠标或键盘标记您的图像数据

马特·布里内在 Unsplash 上拍摄的照片

给数据贴标签是一件痛苦的事。然而,准确标记的数据是数据科学和机器学习的基础。所以,让我们看看如何用 Python 来加速这个过程。

我们将构建两种类型的贴标机来记录:

  • 鼠标点击的位置
  • 键盘上按下的键

这些可以用来直接在笔记本上标记图像。我们将讨论用于创建这些的代码,你也可以在 GitHub 上找到它。

首先,我们将使用下面的导入。我们有一些标准包装(第 2-3 行)。我们有一些用来处理图像的包(第 5-6 行)。最后一个用于处理文件路径(第 8 行)。

我们将处理用于训练模型的图像,该模型引导自动驾驶汽车在轨道上行驶。你可以在 Kaggle 上找到这些例子。记下图像名称:

181_160_c78164b4-40d2-11ed-a47b-a46bb6070c92.jpg

图像尺寸为 224 x 224。名称中的前两个数字是该图像中的 X 和 Y 坐标。我们希望使用图像作为输入来预测这个坐标。

不清楚?让我们展示其中一张图片。图像存储在第 2 行的目录中。我们加载所有这些图像的路径(第 3 行)。

我们采用列表中的第一条路径(第 1 行),并从图像路径(第 4–6 行)中获取 X 和 Y 坐标。然后我们显示图像和坐标(第 9-11 行)。具体来说,我们使用 cv2 函数在给定的坐标上画一个圆(第 10 行)。

您可以在图 1 中看到输出。自动驾驶汽车要右转了。理想的方向是朝着绿圈给出的坐标走。

图 1:图像数据示例(来源:作者)

假设你花了几个小时收集数据,却发现它被错误地贴上了标签(这确实发生在我们身上)。试图手动更改文件名中的 x/y 坐标将是一场噩梦。相反,我们将使用 Python。

标签 1:鼠标点击

图 2 展示了我们的第一台贴标机。当你点击图片时,它会被保存到一个名为“重新标记”的文件夹中。使用鼠标单击的坐标更新图像名称。你可以看到一个类似的标签如何被用来记录图像中物体的位置。

图 2:贴标机 1 的演示(来源:作者)

要构建这个标签器,我们首先要定义我们的读写路径。第一个是保存现有图像的地方(第 1 行)。第二个是保存更新图像的位置(第 2 行)。

然后我们定义一个函数, onclick ,当我们点击一个图像时,这个函数就会运行。它获取鼠标点击的坐标(第 6 — 7 行)。我们用这些坐标创建一个新名称(第 10-12 行),并用这个名称在新位置保存图像(第 14 行)。

每次迭代后,绘图被清除(第 17 行)。如果不这样做,你会遇到内存问题。然后,我们通过删除第一个实例(第 25 行)来更新路径列表,并显示列表中的下一个图像(第 25–29 行)。

你可以在下面看到我们如何使用这个函数。我们首先获取我们想要重新标记的所有图像的路径(第 6 行)。我们加载第一幅图像(第 9–10 行)并显示它(第 16–17 行)。重要的一步是向图形添加点击功能(第 14 行)。为此,我们将我们的 onclick 函数作为参数传递给了 mpl_connect 函数。

另一件要注意的事情是全局变量的使用(第 2-3 行)。这允许在 onclick 函数中更新这些变量。我们还有行 %matplotlib tk (行 1)。这将在笔记本外的窗口中打开图形。

标签 2:鼠标点击

现在,让我们添加一点香料到这个标签。在图 3 的中,你可以看到我们给图像添加了绿色圆圈。这些给出了先前标签的坐标。他们让我们看到哪些图片的标签是错误的。你也可以看到这个贴标机直接在笔记本上工作。

图 3:贴标机 2 的演示(来源:作者)

代码类似于我们之前看到的。最重要的是,我们将保存的图像(第 9 行)必须不同于我们显示的图像(第 32 行)。否则,我们的图像上都会有亮绿色的圆圈。虽然,这将使模型更容易做出预测!

同样,我们使用这个函数的方式和以前一样。这次我们有 %matplotlib noteboo k(第 1 行)。这将在笔记本中保留交互图像。

当使用这个标签时,一个技巧是点击你想从数据集中删除的图像的左上角。然后,您可以过滤掉 x < 5 和 y < 5 的所有图像。

标签 3:键盘按压

让我们用键盘代替记录鼠标点击。如果您想要对图像进行分组以执行分类任务,这将非常有用。

对于我们的例子,我们将把我们的图像分为左转弯和右转弯。每当我们点击 l (左) r (右)或 d (删除)键时,下图就会发生变化。我们还在左上角添加了一个数字。这让我们知道还有多少图像需要标记。

图 4:贴标机 3 的演示(来源:作者)

我们首先在 press 上定义一个函数,它将在按键时运行。我们获取密钥(第 7 行),并使用图像名称作为 ID(第 10–11 行)。接下来会发生什么取决于按下哪个键:

  • 如果我们没有点击一个有效的键,将显示一条错误消息(第 14-16 行)
  • “l”我们将一个 ID 添加到 ID 列表中,并将“left”添加到标签列表中
  • “r”我们追加 ID 和“right”
  • 我们不附加任何东西

图形被清除(第 27 行),我们更新图像(第 29–39 行)。我们将 img_path 列表的长度作为文本包含在左上角(第 36 行)。

即使按下“d”键,图像也会更新。收集这些数据时,有时会有一只迷路的手挡住去路。我们现在可以轻松地从分类数据集中移除/删除这些杂乱的图像。

最后,我们在下面看看如何使用这个函数。我们将 id 和标签列表定义为全局变量(第 3–6 行)。我们加载图像路径列表(第 9 行)并加载第一个图像作为 matplotlib 图形(第 12–17 行)。类似地,在我们向图中添加按压功能之前(第 20 行)。这次我们传入' key_press_event 和新函数 onpress 作为参数。

一旦你标记完所有的图片,你将会看到类似的列表。对于您的任务,我强烈建议将它们保存为 csv。你不会想再做一遍所有的标记吧!

(来源:作者)

我希望你喜欢这篇文章!你可以成为我的 推荐会员 😃 来支持我

https://conorosullyds.medium.com/membership

|Twitter|YouTube|时事通讯 —注册免费参加 Python SHAP 课程

资料组

JatRacer Images (CC0:公有领域)https://www . ka ggle . com/datasets/conorsully 1/JatRacer-Images?选择=对象 _ 检测

使用几何构建约束条件下线性优化的迭代求解器

原文:https://towardsdatascience.com/building-an-iterative-solver-for-linear-optimization-under-constraints-using-geometry-d8df2a18b37e

digity MarketingUnsplash 上拍摄的照片

数据科学家的强大工具

优化是数据科学中的常用工具,最常用的方法之一是线性方法。

尽管不是每个优化问题都可以用线性的方式来表述,但是在很多情况下,可以用线性的方式来重写。

在这篇文章中,我们将使用迭代方法,为约束条件下的线性优化从头开始编写一个求解器。

配料

在约束条件下形式化优化时,需要考虑三个主要因素:

  • 一组变量:这些是我们在优化过程中想要确定的量。
  • 目标:这是一个公式,它结合了变量,表达了一些我们想要最大化或最小化的值。由于我们把自己限制在线性优化的情况下,这个目标必须是变量的线性组合。
  • 一组约束:这些约束将限制变量的可能值。同样,这些约束必须用线性公式表示。

例如,变量可以是矩形长方体的高度H、重量W, 和长度L 。目标可以是这个立体的维度之和:W + H + L,我们可以对变量添加一些约束,比如W < 3L < 4.

从数学上来说,这可以写成:

作者的系统

解决这个问题相当容易。可以手动完成。解决方案在于最大化WL,并推导出H,即:W = 3L = 4H = 4,因此长方体尺寸之和为S =3 + 4 + 4 = 11.

将问题可视化

在进入数学细节之前,让我们试着用图形形象化我们的问题,以便我们得到一些见解。

由于我们面临一个三维问题,即我们有三个变量:W, L and H,我们可以使用 3D 几何。

前两个约束很容易想象。第一种,将解空间限制在所有 3D 点都在W = 3定义的*面之下的 3D 空间。第二种方法将解空间限制在由L = 4定义的*面下方的所有 3D 点。

第三个问题处理起来有点复杂,但是如果我们使用下面的数学公式重新编写它:<(W, L, H), (1, 1, 0)> <= 8,其中运算符<., .>是点积,看起来这个约束将长方体的维度投影到向量(1, 1,0)上,并且这个投影的分量之和必须小于 8。

用最小距离解释该投影的另一种方式是将该矢量(1, 1, 0)视为*面图的(非标准化)法向矢量。因此,再一次,这第三个约束可以从几何学上解释为一个计划。

下图通过用一个法向量将*面图绘制成一个圆来说明这一点。

解空间,三个约束表示为边界*面。作者图。

上图中,A = (3, 0, O)和法向量u = (1, 0, 0)具体化了第一个约束W <= 3B = (0, 4, 0)v = (0, 1, 0)实现第二个约束,而C = (0, 4, 4)

解决问题

迭代方法

让我们首先想象我们正在尝试无约束地优化目标W + H + L。即使这个目标相对于W, H and L是线性的,我们仍然可以使用最速下降法。

在线性情况下,如果没有约束,使用这种方法是不相关的,尤其是因为系统是无界的,所以没有解。然而,我们将在下一节中看到如何在这个迭代方法中集成约束。

这种最速下降法很简单,而且是纯几何的:想法是计算一个函数的梯度,并在梯度的方向上稍微移动。这里梯度是常数,是矢量(1, 1, 1)。下面的代码说明了这种方法:

简单最速上升代码。作者代码。

在这段代码中,weights包含了渐变,如同在线性公式中一样,渐变只是线性组合的权重。x_0为初始值,delta定义步长。

注意停止准则是基于收敛的。在这种情况下,没有约束,系统永远不会收敛。目标会无限增加。

检测不满足的约束

在这一步,我们需要能够做的是确定一个点是否在一个*面之下。一个*面可以用一个点A和一个法向量n来定义。按照惯例,如果一个点位于半空间中,指向法向量的方向,我们就说这个点在*面之上。相反,如果一个点在另一半空间中,它就被认为在*面下。

下图说明了用W = 3定义的*面:

绿色*面由点 A 和法向量定义。作者的情节。

给定这两条信息,通过涉及点积的计算,就有可能知道一个点是在*面之上还是之下。

让我们考虑下图中的两点AaAb:

相对于法向量,Ab 在*面下方,而 Aa 在*面上方。作者图。

记住,当在一个向量和一个归一化向量之间做点积时,如果两个向量指向相同的方向,那么产生的标量将是正的,而如果它们指向相反的方向,那么它将是负的。

因此,为了知道Ab是否在*面之下,只需要在法向量n和连接AAb的向量AAb之间执行点积。

如上图所示,这两个向量指向相反的方向,因此Ab位于此处定义的*面下方。

对面Aa在飞机上方。

下面的截图解释了如何使用 NumPy 在 python 中实现这一点:

检测一个点是在*面的下面还是上面。作者代码。

强制约束

将线性约束正式定义为一个*面,由一个点和一个法向量定义,我们就有了必要的工具来检测不满足的约束。

我们现在需要的是一种强制约束的方法。从几何学上讲,这意味着我们需要一种方法来将所考虑的点移动到*面上。即我们需要将该点投影到*面上。

再一次,点积可以帮助我们。如上所述,法向量n与连接A和兴趣点P的向量之间的点积符号告知我们P相对于*面的位置。

但更重要的是,如果法向量n已经归一化,这个点积就给了我们*面和点P之间的距离。知道了这一点,将P投影到*面上简单地归结为将P向矢量n的相反方向移动由点积给出的量。

一张图胜过千言万语,我们来看看下图:

在由 A 和 n 定义的*面上投影一个点 P。

P'已经通过在法向量上的投影P获得。投影P以得到它的投影Proj是通过使用向量P’A*移 P 简单完成的。

用 python 写的,给出了:

投影代码。作者代码。

请注意,这个函数project在数学上实际上是一个投影仪,因为project(project(P))等于project(P)。这是投影仪的数学定义。

把所有的放在一起

我们现在有三种工具可供使用:

  • 收敛到最优解的迭代方法
  • 用于检测约束是否得到充分尊重的函数
  • 必要时强制遵守约束的投影器。

在约束条件下最大化目标的算法非常简单:

  1. 我们使用最陡下降法,沿着梯度向最优方向移动
  2. 我们确保约束得到遵守
  3. 如果不是,则实施约束
  4. 迭代,直到我们已经收敛,即,直到该点停止移动。

在 python 中,这给出了:

使用迭代方法优化系统。作者代码。

为了简化问题的描述,我们引入了一个Constraint对象,它用一个向量、一个点和一个间隙定义了一个约束。间隙表示约束相对于*面的距离。

我们还引入了一个函数normalize_constraint,以确保法向量是酉的,即我们确保它的范数是 1.0。

正如您所看到的,这个基本求解器收敛于最优目标,并提出预期的解决方案。

好吧,这在 3D 中是可行的,但是更高维度呢?

到目前为止,我们一直使用 3D 几何,以方便理解。然而,我们上面所做的一切可以立即推广到任何 n 维空间,其中n ≥ 3

这可以通过将约束视为n-dimensional空间的超*面,即n-1维度对象来实现。

例如,如果我们使用一个 4 维的超立方体,而不是我们的 3D 立方体,我们会得到下面的代码,其中只有问题定义发生了变化:

我们增加了另一个维度:Z,并对其进行简单约束,即Z <= 5。因此,最大目标现在是3 + 4 + 4 + 5 = 16,求解器找到了。

结论

在这篇文章中,我们提出了一种几何的和容易理解的方法来解决约束下的线性问题。

如果你看一下关于这个主题的文献,你会发现还有许多其他方法可以解决这类问题。特别是,还有另一类方法,基于工作方式完全不同的单纯形法。

这种方法有一些优点,其中之一是精度,因为与这里提出的方法相反,它不收敛到解,而是找到解。

这也是一类较少受病态问题影响的方法。

然而,迭代方法,如这里介绍的一个,具有更有效的优势,并允许控制精度和时间消耗之间的权衡。

使用 Tess4J 构建 OCR 原生应用程序工具—只需 3 个步骤即可从 PDF 中提取文本

原文:https://towardsdatascience.com/building-an-ocr-native-application-tool-with-tess4j-extract-text-from-pdf-in-just-3-steps-a51d28e16084

探索 Tess4J 的第二部分。完整的源代码(Java SDK 1.8)和应用程序链接包括在内。

没有 Digitisation❞就没有❝Digitilisation

随着越来越多的企业和政府实体接受更高效的信息交换概念,并利用其数据的内在价值进行建设性分析,这一说法变得越来越正确。

作者图片| OCR 是一个文本提取工具,它将图形内容转换成计算机可读的格式

降低数字化管理成本的最关键工具之一是 光学字符识别(OCR)——将图像、扫描文档和 PDF 文档等图形媒体转换为计算机可读格式。因此,我决定使用流行的开源 OCR 引擎 Tesseract-OCR 探索一系列可能性

项目背景信息

我过去在实现 Tesseract OCR 方面的一些尝试是以 TesseractJS 的形式出现的,这是 OCR 引擎的一个纯 JavaScript 端口(请参考下面的文章了解完整的细节):

**https://javascript.plainenglish.io/build-a-text-to-speech-app-using-client-side-javascript-98cd72df73bb

然而,最*我决定使用Tess4J(Java 中的 Tesseract-OCR )作为利用 Tesseract 的 OCR 引擎的替代方法。作为我最*尝试使用 Tess4J 进行图像到文本转换的后续工作,我的学习之旅的第二部分将展示 Tess4J 的内置组件来对 PDF 文档执行文本提取。此外,这也是对第一部分中开发的早期原型的补充:

用 Tess4J 构建 PDF 文本提取功能

先决条件步骤

在深入研究其技术实现之前,请确保已经具备了使用 Tesseract 的 Java 应用程序的最小化设置。所有 JAR 依赖项与第一部分完全相同。

关于如何使用 Tesseract 设置 Java 应用项目的详细信息,请参考第一部分

此后,Java 项目应该如下所示:

作者截图| JAR 依赖项列表在上图中用黄色框突出显示。

为了简单起见,这个 Java 项目的名称被指定为 Tess4jOcrApp 。请注意,在上面的黄色框中,库:pdfbox-2.0.3.jar pdfbox-tools-2.0.3.jar fontbox-2.0.3.jar是由 OCR 引擎读取和处理 PDF 文档页面所需的主要依赖项。

(注意:** flatlaf-2.4.jar 是可选的,因为它是 Java Swing GUI 的实用程序,而不是 OCR 依赖项。)**

技术实施步骤

为了更好地连接第一部分和当前实现之间的点,第二部分将从与第一部分完全相同的代码模板开始,如图所示:

Author | Template 的代码片段类似于第一部分从文本中提取图像的代码片段。|在 main 方法中,应该实现从 pdf 文档中提取文本。

第一部分只展示了从图像中提取文本,而第二部分旨在读取 PDF 文档并输出从页面中提取的所有文本内容。

****步骤一。设置好镶嵌实例的数据路径后,继续初始化类别PDDocumentPDFRenderer:

PDDocument document = PDDocument.load(new File("sample.pdf"));
PDFRenderer pdfRenderer = new PDFRenderer(document);

****第二步。通过调用getNumberOfPages()获取 PDF 中总页数的值,并继续创建一个 for 循环,如下所示:

int totalNoOfPages = document.getNumberOfPages();
int imageDPI = 300;
for (int p = 0; p < totalNoOfPages; p++) { // FOR-EACH PAGE

}

注意要点:

  • Tesseract OCR 实现最佳性能的理想分辨率是 300DPI — imageDPI
  • 由 Tesseract 类调用的doOCR()方法接受不同类型的参数,例如— File(在第一部分中)以及**BufferedImage**。在下一步中,每个 PDF 页面将被检索并转换为类型**BufferedImage**用于数据提取。

第三步。最后,在初始化来自步骤 1步骤 2 的所需变量后,PDFRenderer类继续调用函数renderImageWithDPI()来提取 PDF 文件中位置p处的文档页面。renderImageWithDPI()的返回类型为**BufferedImage**(变量名为**tempPageBimg**)。**tempPageBimg** 然后被解析为doOCR(),文件处理开始。在完成此页面的 OCR 提取后,将对其他页面分别进行相同的迭代,直到 PDF 文档中的所有文本内容都被输出以供显示。

**仅供参考:这是所有 3 个步骤**的完整代码片段(演示输入文件可在 pdf_sample.pdf 中检索) :

作者的代码片段|输入 PDF 文件sample.pdf总共有 2 页,每一页都是通过 Tesseract 实例作为 BufferedImage 提取的

作者提供的图像|文档的第 1 页已由应用程序成功处理|右图:带有红色轮廓的图像指的是原始 PDF 页面内容

作者提供的图像|文档的第 2 页已由应用程序成功处理|右图:带有红色轮廓的图像指的是原始 PDF 页面内容

最后,PDF 转文本提取工具已经成功创建并可以使用了!🤩

(可选)文本提取工具的用户界面

与第一部分类似,我选择用 Java Swing 的 GUI 工具包构建一个图形用户界面(GUI) ,并添加了第二部分的功能:

作者截屏|展示文本提取工具的 GUI 和功能|请注意,此版本的应用程序支持每次上传多个图像。|每次上传只能处理 1 个 PDF 文档。

****参考消息:完整的源代码可以从我在 Tess4JOcrApp 的 Github repo 中检索到。GUI 应用程序的第 1 版和第 2 版都在同一个回购中提供,因此请随时到⭐和🔱它!

现在你知道了!非常感谢你坚持到这篇文章的结尾!❤如果你觉得这篇文章有用,那么请随时关注我的媒体并继续关注我的另一个即将到来的宠物项目!会非常感激—😀

— 🌮请给我买一份玉米卷🎀˶❛◡❛)

**https://geek-cc.medium.com/membership ****

构建开源 ML 管道:第 1 部分

原文:https://towardsdatascience.com/building-an-open-source-ml-pipeline-part-1-5b52d06351d1

开始使用我们的管道——数据采集和存储。

亨特·哈里特在 Unsplash 上的照片

1。简介

在这一系列的文章中,我感兴趣的是尝试将一个考虑到现代 MLOps 实践的基本 ML 管道放在一起。自然地,在这些项目的开始,我们列出了一个需求列表,那么我们希望我们的管道做什么呢?

  • 自动检索用于模型训练和推理的数据。
  • 在模型推断之前验证数据。
  • 为我们的模型自动搜索超参数。
  • 自动化模型存储和指标跟踪。
  • 持续交付训练好的模型。
  • 模型监控。
  • 自动化模型再训练。

我们可以使用哪些工具来完成这些事情?我不敢说这是做事的最佳方式,但我认为这是一个不错的开始。我们将使用 Argo 工作流作为我们系统的主干。有了 Argo,我们可以定义操作的有向无环图(DAGS ),这将有助于我们建立数据管道。为了存储我们的数据,我们将使用 Minio 。为了验证数据,我们将使用 Great Expectations ,一个用于数据验证的 python 工具。我以前写过一篇关于结合 Argo 和远大前程的文章,你可以在这里找到。对于超参数优化,我们将利用 Argo 的能力来运行并行管道步骤和动态扇出/入。对于模型存储和指标跟踪,我们将利用 MLflow 。作为一个特色商店,我们将使用盛宴。对于模型监控和连续交付,我们将使用 Seldon Core 最后,为了自动化模型重新训练和处理其他基于事件的依赖关系,我们将使用 Argo Events 。一切都将部署在 minikube 集群上。我会尽我所能地讲述整个过程,并对工具的选择保持透明。

2。入门

我们管道的一个构件是 Minio 对象存储。为什么是米尼奥?Minio 是免费的,非常容易使用。同样,你也没有理由不能用你的应用程序的任何云对象存储来代替 Minio。

我假设一开始就安装了 minikubekubectlargo 命令行工具helm 。这给了我们一个起点,来生成我们将需要的所有 kubernetes 资源,以形成一个有效的 ML 管道。

让我们开始我们的 minikube 集群:

minikube start --cpus 4

我的实例有 4 个可用的 CPU 和大约 8 Gb 的内存。

接下来,让我们使用 Bitnami helm 图表部署 Minio:

kubectl create ns mlops
helm repo add bitnami [https://charts.bitnami.com/bitnami](https://charts.bitnami.com/bitnami)
helm repo update
helm install minio bitnami/minio --namespace mlops

按照 shell 中显示的说明获取您的凭据,并将它们存储在某个地方!

接下来,让我们设置 Argo 工作流。我们可以使用他们的快速启动安装来实现。

kubectl create ns argo 
kubectl apply -n argo -f https://raw.githubusercontent.com/argoproj/argo-workflows/master/manifests/quick-start-postgres.yaml

现在我们应该有一个 Argo 工作流的工作安装。

按如下方式获取您的登录令牌(我们稍后会用到):

SECRET=$(kubectl -n argo get sa argo -o=jsonpath='{.secrets[0].name}')
ARGO_TOKEN="Bearer $(kubectl -n argo get secret $SECRET -o=jsonpath='{.data.token}' | base64 --decode)"echo $ARGO_TOKEN

3。提取、转换、加载

好了,我们已经准备好了核心工具,可以在非常基本的 ETL 管道上工作了。我们将从 OpenAQ API 获取数据。对于个人项目来说,OpenAQ 是一个很好的资源,API 可以提供世界各地城市的空气质量数据。

我们将把我们的数据检索管道组织成三个部分。

  1. 从 OpenAQ API 获取 JSON 格式的数据。
  2. 将数据转换为 Pandas Dataframe 并估算缺失值。
  3. 将 Pandas Dataframe 保存为拼花文件并加载到 Minio bucket。

下面列出了这些步骤的脚本:

简而言之,我对 OpenAQ 进行 API 调用,以获取奥地利维也纳市的空气质量数据。该调用针对前一天到当天的所有数据。计划是制作一个 Cron 工作流,在晚上执行我们的 ETL 管道。对于转换,我们将 JSON 响应转换为 Pandas 数据帧,并删除所有包含 NaN 值的行。最后,在加载部分,我们将数据作为一个 parquet 文件上传到我们的 Minio 实例。现在是有趣的部分!设置我们的 Argo 工作流 CronJob。

4。阿尔戈

设置 Cron 工作流对我来说是新的,所以我很想听到任何反馈。该工作流应该在每天凌晨 1 点运行。有趣的是,当我提交它时,我收到一条消息说它将在凌晨 3 点运行,因为我的本地时间与 UTC 有偏差。

您可以使用 Argo 命令行工具提交此工作流:

argo -n argo cron create ETL.yaml

现在进入 Argo UI,我们可以提交 Cron 作业来测试它。希望你也看到绿色支票!

图片作者。

并且应该已经在我们的 Minio 对象存储中创建了一个文件。

图片作者。

所以这是 ML 流水线的第一步。获取数据,对其进行简单的转换,并存储起来以备将来使用。在下一篇文章中,我想设置 Argo 事件来触发基于这个 Minio 桶中出现的新文件的工作流!敬请关注。

在这里 找到系列 的第二部。

构建开源 ML 管道:第 2 部分

原文:https://towardsdatascience.com/building-an-open-source-ml-pipeline-part-2-29a8dcbc7620

用 Argo 事件和 Argo 工作流进行事件驱动的数据处理。

1。设置 Argo 事件

为了建立事件驱动的工作流,我们需要向我们的工具包添加另一个工具,即 Argo Events 。设置 Argo 事件可能有点棘手,但是我在 Github 这里提供了必要的 YAML 文件。我们将从他们的例子中的一个例子开始,只是为了确保一切都正确安装。因此,一旦您克隆了存储库,就可以随意查看这些清单的内容。就修改而言,您需要用自己的凭据替换' minio-secret.yaml '中的 base64 编码凭据,以便事件源能够工作。

kubectl apply -f argo-events.yaml -n argokubectl apply -n argo -f https://raw.githubusercontent.com/argoproj/argo-events/stable/examples/eventbus/native.yamlkubectl apply -f minio-secret.yaml -n argokubectl apply -f minio-event-source-example.yaml -n argokubectl apply -f minio-sensor-example.yaml -n argo

您可能已经注意到,在 Argo 工作流中,侧边栏中有一个与事件相关的标签。如果您现在导航到那里,您应该会得到一个关于服务帐户无法列出该类别中的资源的错误。这是因为默认的 Argo 工作流角色不考虑 Argo 事件。要修复此运行,请执行以下操作:

kubectl apply -f argo-role.yaml -n argo

现在,当您导航到 events 选项卡时,您应该会看到与此类似的内容。这是我们的示例事件源->传感器->触发器设置。当我们将一个文件放入“openaq”桶时,它会触发一个 whalesay 工作流,打印上传文件的文件名。

Argo 用户界面中概述了我们的示例事件源。图片作者。

这是一个很大的 YAML,所以让我们后退一步,谈谈 Argo 事件如何工作,每个文件在做什么。Argo events 的工作原理是将'事件源与包含'触发器'的'传感器连接起来。事件源使用来自外部源的事件。在我们的例子中,当一个文件被放入“openaq”桶时,我们的事件源正在消耗由 Minio 生成的通知。传感器侦听特定的事件源,并定义当事件发生时应该发生什么(这是触发器,在我们的情况下,我们部署 Argo 工作流)。Argo Events 确实是一个很酷的框架,我们在这里使用 Minio 作为事件源,但它也可以很容易地成为 Kafka 主题或 Redis pubsub 事件。

图片来自https://argoproj.github.io/argo-events/

我们需要用触发器模板定义事件源和传感器。为了测试,我使用了来自 Argo Events Github 页面的示例事件源和传感器文件。起初有一些问题,但是我打开了一个 Github 问题,几天之内就解决了。团队的响应时间令人印象深刻!现在,如果您向前移植并访问 Minio bucket,您就可以上传一个文件,您应该会看到示例事件在运行。

我们将回到 Argo 事件并定义我们的预处理模板,但是首先我们需要为数据验证生成一个期望套件。为此我们将寄予厚望。我以前写过一篇关于在 Argo 工作流中使用 Great Expectations 的文章,很多内容都是从那里摘录的。

2。在 Argo 工作流中生成期望套件

为了生成我们的期望套件,我们需要从 OpenAQ API 中提取代表性数据。为了做到这一点,我们将尽可能早地获取 API 允许的数据,然后生成我们的期望套件。原则上,这些数据应该与我们用来训练模型的数据相同,所以我会将这些数据保存到我们的 Minio“数据湖”中,以备将来使用。我们可以利用 Hera 为此生成一个工作流,这让生活变得更加美好。我们也可以利用第 1 部分中的提取和转换函数,只需稍加修改。这里未来要做的是参数化提取函数,这样我们就不需要硬编码 API 调用的开始和结束日期。我们需要为数据验证工作再创建两个 Minio 桶,因为我们不想在上传远大前程文件时触发 Argo 事件管道。我创建了一个名为“验证”的桶和一个名为“训练”的桶。我们将在“验证”中存储我们的远大前程套件,在“训练”中存储原始数据。总之,从 OpenAQ 数据中生成期望值的代码如下所示:

在这里,我们利用 Argo Workflow 的能力,通过定义任务在各行中的顺序来运行并行任务:

extract_task >> ge_task
extract_task >> store_task

在运行代码之前,确保您正在本地主机:2746 上端口转发 Argo 服务器,在本地主机:9000 上端口转发 Minio。提交后,您应该能够在 Argo 用户界面中看到成功的管道运行:

图片作者。

3。设置我们的事件驱动管道。

在这个管道的最后一次迭代中,我们希望能够基于模型度量触发模型重新训练。为此,我认为最灵活的选择是创建一个 webhook 事件源。这意味着我们可以使用 http 请求触发 argo 工作流。简而言之,我们的工作流程如下所示:

  • 在 http 触发时,检索历史 OpenAQ 数据。
  • 将数据转换成表格格式。
  • 为检索到的数据生成一个期望套件。
  • 将原始数据存储在我们的“train”Minio 桶中。

从那里,我们可以设置一个 Minio 事件源,它将触发一个模型训练管道。这一点我们将在下一篇文章中介绍!

为了避免在 YAML 编写我们的管道,我们将使用 Argo 工作流 CRD,工作流模板。我们可以在 Python 中使用 Hera 定义工作流模板,然后在 Argo 事件定义中引用工作流模板。

在 Hera 中,从工作流切换到工作流模板非常容易。为了完整起见,下面是完整的代码,但我将重点介绍具体的更改。

更改发生在导入和第 180–181 行。

ws = WorkflowTemplateService(host="https://localhost:2746", verify_ssl=False, token=TOKEN)w = WorkflowTemplate("generate-expectations", ws, namespace="argo")

现在剩下的就是定义我们的 webhook,每当它接收到 post 请求时就提交这个工作流模板。

首先让我们定义我们的 webhook 事件源。

这里我们创建一个名为 retrain 的 webhook,它在端点/retrain 和端口 12000 上公开。接下来,让我们创建我们的传感器:

您可以在这里看到,我们利用了之前通过使用“templateRef”字段创建的工作流模板。如果我们 kubectl 应用这两个文件,那么我们应该有一个准备就绪的 eventsource + sensor 对。

接下来测试设置端口-转发 eventsource pod。

kubectl -n argo port-forward webhook-eventsource-{randomstring} 12000:12000

然后,我们可以通过向/retrain 端点处的 pod 发送 post 请求来简单地测试它。我学到的一件有趣的事情是,为了正确触发工作流,我必须包含虚拟数据,即使它没有参数。

curl -d '{"dummy":"dummy"}' -H "Content-Type: application/json" -X POST http://localhost:12000/retrain

现在我们有了一个设置,可以用事件驱动的方式启动我们的管道。我们将继续以同样的方式建立我们的渠道。在下一篇文章中,我们将重点关注模型训练过程,以及我们如何将 Argo 工作流与 MLflow 结合起来进行模型训练、实验跟踪和模型存储。

使用新一代语言模型构建应用程序

原文:https://towardsdatascience.com/building-apps-with-a-new-generation-of-language-models-6e1e8ae6038c

播客

Amber Teng 在她的 GPT-3 动力求职信发生器上

苹果 | 谷歌 | SPOTIFY | 其他

编者按:TDS 播客由 Jeremie Harris 主持,他是人工智能安全初创公司 Gladstone 的联合创始人。每周,Jeremie 都会与该领域前沿的研究人员和商业领袖聊天,以解开围绕数据科学、机器学习和人工智能的最紧迫问题。

众所周知,新一代强大且高度扩展的语言模型正在席卷全球。OpenAI、AI21Labs 和 Cohere 等公司已经建立了如此通用的模型,以至于它们正在为数百种新应用提供动力,并为人工智能生成的文本打开了全新的市场。

有鉴于此,我认为探索语言建模的应用方面是值得的——深入研究一种特定的语言模型驱动的工具,以理解在规模化的人工智能系统上构建应用程序意味着什么。这些模型在野外使用有多容易?当人们试图构建由大型语言模型支持的应用程序时,会遇到哪些瓶颈和挑战?这就是我想知道的。

我今天的嘉宾是 Amber Teng,她是一名数据科学家,最*发表了一篇博客,引起了相当多的关注,内容是她使用 OpenAI 强大且现在著名的语言模型 GPT-3 创建的简历附函生成器。我认为她的项目将成为一个伟大的插曲,因为它揭示了我们刚刚进入的强大语言模型的新时代带来的许多挑战和机遇。

因此,今天我们将探索这一点:着眼于语言建模和提示工程的应用方面,理解大型语言模型如何使新的应用程序不仅成为可能,而且更容易构建,以及人工智能产品的可能未来。

以下是我在对话中最喜欢的一些观点:

  • Amber 仅用了 5 个小时就构建并部署了端到端的随函生成器。这包括阅读 OpenAI 关于 GPT-3 的文档的时间,以及为该模型进行工程提示的时间。使用当今的语言模型构建新应用的速度之快令人难以置信,这种开发的便利性是如此多新创业公司诞生的一个重要原因,这些公司的核心产品基本上只是 GPT-3 的一个薄薄的包装。
  • Amber 发现,一个对她很有效的提示工程策略是以一个非常笼统和不明确的提示开始(例如,“为亚马逊的一份机器学习工作写一封求职信。”)并看看 GPT-3 如何处理它,然后用更具体的内容来调整模型的响应。这样,她可以以可控的方式增加提示的复杂性,确保她大致了解提示的每个组成部分所扮演的角色。
  • 对于特别复杂和特殊的提示,Amber 发现 GPT-3 经常在完成时重复提示内容。例如,当给出类似“为 Amber Teng 写一封求职信,她是布朗大学的 CS 学位的数据科学家,申请谷歌的机器学习工程师职位。表明 Amber 对这个角色感兴趣,因为它涉及到为金融科技初创公司建立集成,以及她在金融服务公司工作时经历的许多与金融相关的可视化工作,“GPT 3 号可能会直接复制/粘贴它生成的文本中相对较大的提示部分,基本上没有任何好处。Amber 发现,增加模型的温度参数通常会有所帮助,但对于较长的提示来说,这仍然是一个问题。
  • 人工智能的潜在恶意应用越来越难以忽视,Amber 花了一些时间来思考它们可能会成为她的求职信生成器。令人惊讶的是,她至少提出了一个重要的问题:公司可能会选择用数千封人工智能生成的求职信和简历淹没竞争对手的招聘渠道,使他们无法识别和跟踪合法的候选人。也不清楚 OpenAI 如何检测这种恶意策略,因为求职信看起来像是相当无害的内容,即使是大量求职信生成的请求也不会与你可能从一个完全合法的具有重大吸引力的求职信生成初创公司所期待的不一致。

章节:

  • 0:00 介绍
  • 2:30 安柏的背景
  • 5:30 使用 GPT 3 号
  • 14 时 45 分大楼出现提示
  • 18:15 推广最佳实践
  • 21:45 GPT-3 次失误
  • 25:30 上下文窗口
  • 30:00 端到端时间
  • 34:45 一封求职信的费用
  • 37:00 分析
  • 41:45 围绕公司建设的动态
  • 语言模型的商品化
  • 51:00 总结

构建 AWS Lambda 容器映像

原文:https://towardsdatascience.com/building-aws-lambda-container-images-5c4a9a15e8a2

打包 Lambda 代码和依赖项

图片来自弗兰克麦凯纳拍摄的 Unsplash

AWS Lambda 函数非常强大,是无服务器应用程序的核心构件之一。它们可以用来将你的整个应用程序缝合在一起,而且通常在 Lambda 函数上,你需要为你的项目安装额外的依赖项。Lambda 不支持某些现成的包/库,您需要找到一种方法在该环境中安装这些依赖项。

解决这个问题的一个方法是创建一个包含函数依赖项的部署包/zip 文件。这是可行的,但是一个更干净和直观的替代方法是将所有这些打包在一个容器映像中。我们可以使用 AWS 提供的基础图像来进一步简化这个过程。在本文中,我们将研究如何获取其中一个基础映像,并在此基础上为我们的 Python Lambda 函数安装一个包。我们将构建一个简单的 NumPy 示例,您可以根据自己的示例进行调整。

注意:对于那些刚接触 AWS 的人来说,如果你想跟进的话,请确保你在下面的 链接 中做了一个账户。在开始之前,一定要安装 Docker。本文还假设您对 Python 和 AWS 有基本的了解。

1.设置

对于我们的例子,我们需要三个主要文件:Lambda Python 函数、需求文件和 docker 文件。

所需文件(作者截图)

对于我们的 requirements.txt,我们需要的只是 NumPy 包。我们将使用传入的示例负载执行一个简单的 NumPy 操作。

安装数量

接下来我们有我们的 Python Lambda 函数。在这个 Lambda 函数中,我们将它设置为以一个整数作为输入的样本 JSON。使用 NumPy,我们将捕获这个整数的*方根,并在 Lambda 函数被调用时返回它。

进口

带 NumPy 的*方根

实际上,您的问题的用例将会复杂得多,我们只是在这里进行一个简单的 NumPy 操作,以显示我们的 Lambda 函数正在与我们的附加依赖项一起工作。

我们可以在一个docker 文件中捕获我们的代码和依赖关系。首先我们抓取 AWS 基础 Python 图像

基础 Python 图像

基础映像在 LAMBDA_TASK_ROOT 中提供了一个环境变量。我们可以复制我们的 Python 代码,并在这个目录中安装我们的依赖项。

复制 Lambda 代码

在 Lambda_TASK_ROOT 目录中安装依赖项

然后,我们将 CMD 设置为我们的处理函数,这应该是用你们各自的价值观。

替换为您的文件名和函数名

2.容器映像构建

使用 docker 文件,我们可以用下面的命令构建 Lambda 容器映像“numpy-lambda-container”。

构建图像

然后,我们使用 Docker run 命令启动映像。

开始 Docker 图像

现在在同一个目录中打开另一个终端,我们可以 curl 一个样本数据点来本地调用我们的 Lambda 函数。

示例调用

然后我们在另一个 Shell 中看到结果。

样本结果(作者截图)

接下来,您可以将这个本地映像推送到弹性容器注册表 (ECR)。要将所有这些资源推送到 AWS,您可以使用 AWS CloudFormation/SAM 模板来定义和部署您的基础架构。

3.其他资源和结论

https://github.com/RamVegiraju/Lambda-Container

有关示例的代码,请查看上面的链接。Lambda 容器映像使得定制 Lambda 函数以适应您的工作流比以往任何时候都更容易。这是一个非常简单的用例,您可以使用它作为构建自己的复杂函数的基础。如果您想了解如何在 Lambda 上部署 ML 模型进行推理,请查看这个 AWS 样本库。这是另一个伟大的例子,在 AWS Lambda 上部署 BERT 模型用于无服务器推理。

我希望这篇文章很好地介绍了如何利用 AWS Lambda 的众多特性之一。一如既往,欢迎在评论中留下任何问题和反馈。

如果你喜欢这篇文章,请在LinkedIn上与我联系,并订阅我的媒体 简讯 。如果你是新来的中号,用我的 会员推荐 报名吧。

用有偏向的类构建分类器

原文:https://towardsdatascience.com/building-classifiers-with-biased-classes-adasampling-comes-to-the-rescue-8212814264e3

AdaSampling 来拯救我们

离开 Kaggle 的世界,进入现实世界,数据科学家经常( read: always )面临脏数据的问题。除了缺失值、不同单位、重复等等,分类任务的一个相当常见的挑战是数据标签中的噪声。虽然有些噪声问题可以由分析师来解决,但其他问题本质上是有噪声的或不精确的。

考虑以下任务:预测特定蛋白质是否与特定 DNA 序列结合。或者另一个:预测是什么导致了普通感冒。这两个任务有一个共同点,那就是关于负类的知识很少。有数不清的 DNA 序列,只有那些碰巧被分析并作为特定蛋白质的目标发表的序列被认为是阳性的。但其他未发表的序列不一定是否定的。它们可能没有被分析过。普通感冒也是如此:许多病例未被发现,因为只有少数人报告患了感冒。然而,有一种技术可以帮助你应对这一挑战:等人(2019)提出的自适应采样(AdaSampling)。

AdaSampling 算法,示意性示例。图片作者。

简而言之,AdaSampling 算法执行以下步骤:
1 .从数据集中抽取一个子样本。给定的正/负样本被选择的概率等于它是正/负类成员的概率。(在图中,突出显示的样本最有可能是任一类别的成员,因此被选择用于建模。)
2。使用任何底层分类算法(例如 SVM、kNN、ld a 等)构建分类器。).
3。基于该分类模型,预测样本(在完整数据集内)在正/负类中的概率。
4。重复 1-3,直到类别概率没有变化。

基本上,你会得到一张你的类的清晰的图片,并得到关于你的负样本中哪些可能不是负样本的第一个线索。在训练阶段省略这些可以大大提高模型的准确性。

让我们用 r 中的 AdaSampling 包来尝试一下。示例数据集是关于良性和恶性乳腺癌的。

#install.packages("AdaSampling")
library(tidyverse)
library(AdaSampling)
library(caret)# load the example dataset
data(brca)
# some cleanup of the dataset
brca$cla <- as.factor(unlist(brca$cla)) 
brca$nuc <- as.numeric(as.character((brca$nuc))) #run a PCA
brca.pca <- prcomp(brca[,c(1:9)], center = TRUE,scale. = TRUE)
# append PCA components to dataset
brca_withPCA <- cbind(brca, brca.pca$x)
# plot
ggplot(brca_withPCA, aes(x=PC1, y=PC2, color=cla)) +
  geom_point() + 
  ggtitle("PCA of Breast Cancer Dataset") +
  theme_classic()

原始数据集上的主成分分析如下所示:

图片作者。

我们实际上看到两个很好的分离的类。我们可以在此基础上建立一个“基础事实”分类器。我选择了一个 SVM,得到了 0.96 的精确度和 0.91 的灵敏度(代码如下)。

#Separate test set
set.seed(107)
inTrain <- createDataPartition(y = brca$cla,  p = 0.75)
train <- brca[ inTrain$Resample1,]
test  <- brca[-inTrain$Resample1,]ctrl <- trainControl(
  method = "repeatedcv", 
  repeats = 3,
  classProbs = TRUE, 
  summaryFunction = twoClassSummary
)
model_groundTruth <- train(
  cla ~ .,
  data = train,
  method = "svmLinear", # Support Vector Machines with Linear Kernel
  ## Center and scale the predictors for the training
  preProc = c("center", "scale"),
  trControl = ctrl,
  metric = "ROC"
)#predict
predicted_groundTruth <- predict(model_groundTruth, newdata = test)
confusionMatrix(data = predicted_groundTruth, test$cla, positive="malignant")

很好。这是一个相当精确的分类器。现在让我们看看 AdaSampling 的实际应用。我们假装时光倒流,回到了某些恶性肿瘤被诊断为良性的时间点。这是一个更真实的场景,因为并非所有被诊断为良性的癌症都会永远如此。

#get the classes
pos <- which(brca$cla == "malignant")
neg <- which(brca$cla == "benign")
#introduce 40% noise to malignant class
brca.cls <- sapply(X = brca$cla, FUN = function(x) {ifelse(x == "benign", 0, 1)})
brca.cls.noisy <- brca.cls
set.seed(1)
brca.cls.noisy[sample(pos, floor(length(neg) * 0.4))] <- 0brca$noisy_class <- as.factor(brca.cls.noisy)
brca %>% group_by(cla, noisy_class) %>% tally()

图片作者。

我们引入了一个测量误差:95%的癌症会变成恶性的(基本事实),现在被标记为良性的。PCA 看起来极具破坏性:

图片作者。

使用此数据构建分类器会得到一个准确度为 0.81、灵敏度为 0.47、特异性为 0.99 的模型。正如所料,这个分类器漏掉了许多潜在的恶性肿瘤。

现在是采样的时候了。下面的代码显示了细节。程序如下:需要告诉算法哪个是正样本,哪个是负样本。然后,它充当分类算法的包装器(我选择了 kNN,但是这个包提供了许多其他算法)并重新分配类。完成后,我检查了使用主成分分析的样本分布情况,使用与之前相同的特性:

# identify positive and negative examples from the noisy dataset
Ps <- rownames(brca)[which(brca$noisy_class == 1)]
Ns <- rownames(brca)[which(brca$noisy_class == 0)]# apply AdaSampling method on the noisy data. I pick kNN, but other classification methods are available
brca.preds <- adaSample(Ps, Ns, train.mat=brca[,1:9], test.mat=brca[,1:9], classifier = "knn")
brca.preds <- as.data.frame(brca.preds)
head(brca.preds)
brca.preds$Adaclass <- as.factor(ifelse(brca.preds$P > brca.preds$N, "malignant", "benign"))
brca <- cbind(brca, brca.preds["Adaclass"])#check the result with PCA first:
brca.pca_Ada <- prcomp(brca[,c(1:9)], center = TRUE,scale. = TRUE)
# append PCA components to dataset
brca_withPCA_Ada <- cbind(brca, brca.pca_Ada$x)
# plot
ggplot(brca_withPCA_Ada, aes(x=PC1, y=PC2, color=Adaclass)) +
  geom_point() + 
  ggtitle("PCA of Breast Cancer Dataset with Noise removed by AdaSampling")+
  theme_classic()

图片作者。

几乎和地面真相一模一样!为了完整起见,我还使用清理后的数据构建了一个分类器,结果得到一个准确度为 0.96、敏感度为 0.92、特异性为 0.99 的模型。

该分类器与我们在没有噪声的数据集上构建的分类器一样好。我猜这是由于数据集和恶性肿瘤很容易通过提供的特征识别。在真实世界的场景中,它可能看起来不同,并且您永远无法确定您的模型实际执行得有多好,直到您收集了更多真实的正面和负面示例。在此之前,AdaSampling 是您工具箱中增强模型的又一个工具。

参考文献 : P .杨,J. T. Ormerod,W. Liu,C. Ma,A. Y. Zomaya 和 J. Y. H. Yang,“正-未标记和标记噪声学习的自适应采样及其在生物信息学中的应用”,载于《IEEE 控制论汇刊》,第 49 卷,第 5 期,第 1932–1943 页,2019 年 5 月,doi:10.11109/tcyb . 20000005

建立对可解释方法的信心

原文:https://towardsdatascience.com/building-confidence-on-explainability-methods-66b9ee575514

易于评估解释质量的开源指标

(图片由作者提供)

机器学习的兴起,接管了新的行业和应用,在过去几十年里非常强劲。其日益增加的复杂性将模型变成了黑箱,使得输入和输出之间的过程更加不透明。反过来,在其他因素的支持下,解释机器学习决策过程的需求变得至关重要。可解释性(XAI)提供了对模型如何工作的更好理解,增加了对它们的信心,并导致更好的决策。

对于数据科学家来说,可解释性必须是建模不可或缺的一部分。假设我们要开发一个信用评分模型(不管我们是否要给某人贷款);可解释性可以提供许多洞见:

  • 验证预期特征(工资、债务比率……)是否有重大影响,或者反过来理解为什么出乎意料的特征被过度表现
  • 专注于具有强大预测能力的功能,并改进功能工程以提升性能
  • 提供客户顾问可以用来解释拒绝/批准原因的建议
  • 与数据科学家和非数据用户共享模型和结果
  • 等等...

XAI 最*一直是一个热门的研究话题。出现了多种解释方法。但是今天这些方法并不完全令人满意,对结果的解释应该仔细。

因此,在法国兴业银行,我们开发了帮助评估解释质量的指标。我们的贡献现在可以在 Shapash 库的 1.6.1 版本中获得。

(来源)

Shapash 是由 MAIF 开发的关于可解释性的开源 Python 库。它依赖于现有的方法(如 SHAP 和莱姆),并提出了简单的可视化和一个界面,允许在全局和局部的可解释性之间导航。

关于解释质量指标实现的更多信息,Shapash Github 上有一个 jupyter 笔记本教程

局部可解释方法

局部可解释性有多种形式。其中最受欢迎的是基于重量的方法。那些分配给每个特征的权重与其在样本预测中的贡献成比例。例如:

特定实例中与每个特征相关的权重示例

基本上,您将您的初始问题(您可能已经使用任何模型来获得预测)转换为线性问题,其中权重的总和(或多或少)加到模型输出中。

通过查看捕捉最大权重的特征,您将能够评估这样的句子:“您的贷款申请被拒绝是因为您的收入(决定的 70%)和您的年龄(30%)。

SHAP 和石灰是两种最常用的重量法;下面是一个快速回顾。

石灰

LIME 背后的思想如下:我们用代理(线性回归)局部*似黑盒模型,并将其系数解释为特征的贡献。

用石灰解释一个实例

更详细的解释,可以查看 原文 [1]

SHAP

SHAP (SHapley 加法解释)是基于 SHapley 值,一个来自博弈论的概念。SHAP 使用的思想是,应该考虑每个可能的特征组合的结果,以确定单个特征的重要性。

Shapley 值是通过查看每个要素组合以及模型输出如何变化来计算的。

博弈论应用于确定特征的贡献

理想情况下,您需要为每个特征子集训练不同的模型(n 个特征的 2ⁿ模型),这当然是不可能的。

除此之外,SHAP 提出了一些*似值来帮助计算沙普利值。更多信息请参见 论文【2】。

需要开发度量标准来评估质量

如果现有的方法可以在某些场景中带来有趣的见解和帮助,不幸的是,它们并不完美,并受到限制。

其中,在不涉及细节的情况下,当涉及相关变量时,您将无法计算出准确的 SHAP 值:要么计算是基于人工创建的、永远不会出现在现实生活中的实例,要么权重可能被赋予在模型中实际没有使用的要素(因为与实际使用的要素相关)。

有关这些限制的更多信息,您可以查看这篇文章https://arxiv.org/abs/2002.11097【3】。

此外,可解释性方法之间并不总是一致的:一个特性根据一种方法可能有很强的贡献,根据另一种方法可能有很弱的贡献,甚至是相反的符号,这使得获得可信解释的任务更加困难。

SHAP(左)和莱姆(右)对同一实例的贡献

因此,我们需要工具来估计在特定情况下何时与信任解释相关,以及反过来,何时我们应该将它们视为纯粹的见解。

我们开发了三个度量标准,将通过泰坦尼克号数据集上的一个例子来说明(分类问题,根据一个人的一些特征来预测他是否在沉没中幸存)。

我们将假设该模型已经被训练过,并且我们现在想要理解它的内部工作。

这三个指标是:

  • 一致性
  • 稳定
  • 紧密度

一致性度量

如上所述,目前存在几种基于权重的可解释性方法。这些方法在几个方面可能互不相同:理论基础的强度、初始假设、成熟度水*的异质性……简而言之,许多因素可能会影响与每个变量相关的权重值。

当我们在同一个实例上比较可解释性方法时,我们经常会得到不太相似的解释(甚至在某些情况下完全相反)。不幸的是,很难确定选择正确的方法(如果有的话)。

如果可以理解方法之间的差异是因为它们的假设不同,那么从业务角度来看这是不令人满意的。如果我们想了解客户为什么没有获得贷款,我们不能回答他们:“这取决于假设,要么是因为你的收入,要么是因为你的年龄”。

为了突出和量化这些方法之间的差异,我们开发了一个叫做Consistency的度量标准。这个标准回答了下面的问题:*均来说,不同的可解释性方法给出了相似的解释吗?**

因此,我们可以将差异较小的情况与差异较大的情况区分开来,差异较小的情况增加了对这些方法提供的解释的信心,在差异较大的情况下,我们需要仔细解释这些解释。

图形描述

输出分为两个图表:

********

第一张图显示了不同方法提供的解释之间的*均距离。这是一个 2D 表示,给出了方法之间的相似性的概述。例如,KernelSHAP 和 SamplingSHAP 给出的*均解释似乎比 TreeSHAP 更接*。

然而,距离的解释并不容易。比如 0.33 好不好?为了帮助理解其含义,我们展示了第二张图,作为说明第一张图中显示的值的依据。

在第二张图中,从数据集中选择和提取了真实的实例(由于每个图顶部的 Id,我们可以找到它们),并允许更好地理解显示的值。例如,这里 0.14 的距离似乎显示出非常相似的贡献,对于 0.33 的距离,贡献稍微小一些。

密码

声明对象后,下面的compile方法允许您使用 Shapash 支持的默认方法(即 SHAP、莱姆和 ACV)计算给定数据集的贡献。

****from** shapash.explainer.consistency **import** Consistencycns **=** Consistency()
cns**.**compile(x**=**Xtrain, model**=**clf)
cns**.**consistency_plot()**

您也可以使用 Shapash 没有计算的您自己的贡献。确保它们都是相同的 Pandas 数据帧格式(相同的列,相同的索引)并插入到字典中。

**contributions **=** {"treeSHAP": treeSHAP, 
                 "samplingSHAP": samplingSHAP,
                 "kernelSHAP":kernelSHAP}
cns**.**compile(contributions**=**contributions)
cns**.**consistency_plot()**

技术细节

从数学上来说,距离定义如下。对于 M₁和 M₂的两种解释方法,解释之间的*均距离是:

其中N是计算中考虑的实例数量,w1w2是由所有特征的贡献创建的归一化向量(L2 范数)。

稳定性度量

另一种增加可解释方法可信度的方法是研究它的稳定性。直觉上,如果我们选择相似的实例(我们将在下面看到如何定义这个相似性),我们会期望解释也是相似的。换句话说,对于类似的实例,解释是否相似?这是第二个指标Stability要回答的问题。****

相似性的概念基于两个因素:

  • 实例在特征空间中必须是接*的
  • 模型预测必须接*

事实上,仅与特征值相关的相似性不足以得到相同的解释。例如,如果你观察一个模型的决策边界的两边,你会得到不同的预测,从而得到不同的解释。但是特征值可能非常相似。这完全说得通。

稳定性的问题是很重要的,因为在这个水*上的失败不会给解释带来信心。

在当前可解释方法所依赖的理论假设中(例如:莱姆和 SHAP),稳定性问题通常不被提及。因此,它不会自动跟随。因此,有一个度量标准来评估这一方面是很重要的。

图形描述

可以显示几个图形,这取决于是希望查看完整数据集(全局)的稳定性还是特定实例(局部)的稳定性。

此图说明了一组实例的稳定性。图表上的每个点代表一个要素(作为一个绘图图表,您必须将鼠标悬停在它上面才能看到要素的名称)。

  • Y 轴显示每个特性在所有考虑的实例中的*均重要性。越往上走,功能越重要。
  • X 轴显示每个考虑的实例的邻域中的*均稳定性。越靠右,特征越不稳定。

因此,我们看到左上角的两个特性很重要,并且*均来说相对稳定。如果我们想放大一点,看看稳定性的分布而不是它的*均值,我们会得到这个图表:

我们发现同样的两个最重要的特征(“性别”和“票等级”),但是我们现在看到一些差异:如果它们的稳定性*均上接*,我们现在看到它们的分布如何变化;“性别”在所有实例中保持非常稳定,而“机票类别”则具有更高的可变性,并且取决于所研究的实例。

第三个视角,这次是本地的,允许您研究特定实例的稳定性:

例如,在这里,“性别”在其邻域内是稳定的,这使得贡献可以在一定程度上得到解释,而“装载港”则显示出真正的矛盾。

密码

****from** shapash.explainer.smart_explainer **import** SmartExplainerxpl **=** SmartExplainer()
xpl**.**compile(x**=**Xtrain, model**=**clf)
xpl**.**plot**.**stability_plot()**

默认情况下,选择的可解释方法是 SHAP。

技术细节

计算稳定性指标需要两个步骤:

  1. 选择合适的社区
  2. 计算稳定性本身

关于给定实例的邻域,选择如下:

  • 我们将数据归一化,使其具有单位方差(在相同的比例上获取要素)
  • 我们通过 L1 距离选择前 N 个最*的邻居(默认为 10,这足够大以允许后续的过滤标准)
  • 我们拒绝那些模型输出与实例输出相差太大的邻居(T2 差异将在下面解释)
  • 我们拒绝到实例的距离大于某个阈值的邻居,以便移除异常值(该阈值由距离分布定义)

模型输出之间的最大允许差异定义如下:|

  • 对于回归:

  • 对于分类:

一旦选择了邻居,稳定性度量本身的计算通过考虑实例及其所有邻居并计算以下各项之间的比率来完成:

  • 由特征贡献创建的归一化向量(L1 范数)的标准偏差。
  • 由特征贡献创建的归一化向量(L1 范数)的绝对值的*均值。

这个无单位的比率被称为变异系数 (CV)。**

紧凑度量

为了使可解释性成为可解释的,它必须容易被人类理解。即使使用的模型很简单,这也不能保证可解释性也同样简单:一个依赖于 50 个特征的简单线性回归显示了问题;特征的数量极大地影响了可解释性。

一个简单的可解释性最好是基于少量的特征,因此有必要选择足够数量的特征。然而,所选择的特征必须具有足够高的预测能力,以便它们足够好地逼*模型。因此,必须考虑权衡。

不幸的是,这种权衡并不总是可能的。如果模型确实使用大量的特征来作出决定,则不可能令人满意地提取子集。

这里开发的Compacity指标允许确定我们是否处于这样一种情况,即通常很容易根据少数特征获得解释,或者相反,这是不可能的。为此,将包含所有特征的模型的输出与前 N 个贡献进行比较。最后,显示统计数据。

图形描述

下面生成的图表提供了全局统计数据(一组点),而不是特定实例的统计数据。

有两个参数会影响结果:

  • 包含所有特征的模型的期望*似程度
  • 说明中选择的特征数量

通过固定期望的*似水*和改变特征的数量,我们获得左图;相反,通过固定特征的数量并改变所达到的*似值,我们得到了右边的图。

正如我们在这个例子中所看到的(左图),如果我们选择了 4 个特性,我们将在 X%的时间内以 90%接*基本模型。另一方面,如果我们选择 5 个特征(右图),我们将在 X%的时间内达到 90%的*似值。

密码

****from** shapash.explainer.smart_explainer **import** SmartExplainerxpl **=** SmartExplainer()
xpl**.**compile(x**=**Xtrain, model**=**clf)
xpl**.**plot**.**compacity_plot(distance=0.9, nb_features=5)**

默认情况下,选择的可解释方法是 SHAP。

技术细节

这里,只有输出之间的距离用于计算度量。使用的距离与上面所示的距离相同:

  • 对于回归:

  • 对于分类:

结论

这篇文章提出了三个度量标准来评估现有可解释性方法所提供的解释的质量。他们回答了以下问题:

  • D o 不同的解释方法*均给出相似的解释? ( 一致性)
  • 对类似事例的解释是否相似? ( 局部稳定)
  • 几个特性驱动了模型吗? ( 紧密度)

如果可解释性方法帮助了我们,并提供了可以在某些情况下使用的有趣见解,那么对它们的解释必须谨慎,正如度量所指出的那样。

通过开发这些指标,我们可以区分那些我们可以相信解释的情况和那些需要更深入关注的更复杂的情况。

这些指标在 1.6.1 版本的 Shapash 中提供。还提供了一个 笔记本教程 来熟悉它们。

我感谢朱利安·博恩(法国兴业银行)、扬·戈林( MAIF )和托马斯·布奇( MAIF )在这一富有成效的合作中做出的贡献和承诺。**

参考

[1]里贝罗等,“我为什么要相信你?”:解释任何分类器的预测 (2016)

[2] Lundberg 等人,解释模型预测的统一方法 (2017)

3 Kumar 等人,基于 Shapley 值的解释作为特征重要性度量的问题 (2020)

在 PyTorch 中构建自定义影像数据集

原文:https://towardsdatascience.com/building-custom-image-datasets-in-pytorch-15ba855b47cb

带代码的教程

图片作者。包括来自维基百科(知识共享许可)的神经网络可视化。

在本教程中,您将学习如何使用 PyTorch 的内置图像数据集,以及如何使用您想要的任何图像构建您自己的自定义图像数据集。虽然本教程的重点是图像数据,但 PyTorch 中可定制数据集的关键概念适用于任何类型的数据,包括文本和结构化表格数据。

本教程基于我的公开资源库https://github.com/rachellea/pytorch-computer-vision,其中包含了使用定制 PyTorch 数据集的代码。它还包括训练和评估定制神经网络的代码,在这篇文章中有概述。

学完本教程后,您应该能够:

  • 下载并使用 torchvision.datasets (MNIST、CIFAR、ImageNet 等)的公共计算机视觉数据集。);
  • 使用图像数据标准化和数据扩充;
  • 通过子类化 torch.utils.data.Dataset,从任意图像集合(或非图像训练示例)中创建您自己的数据集;
  • 使用 num_workers 并行加载数据。

什么是数据集?

数据集由带标签的示例组成。对于影像数据集,这意味着每个影像都与一个标签相关联。标签可以是:

  • 定义类的向量,如“cat”=[0,1,0]代表[dog,cat,bus]
  • 定义多个类别的向量,如“猫和狗”= [1,1,0]表示[狗,猫,公共汽车]
  • 定义分割图的矩阵,其中矩阵的每个元素对应于图像的单个像素,并指定该像素属于哪一类,例如,“0”代表狗的一部分,“1”代表猫,“2”代表公共汽车,“3”代表椅子,等等。

关于分类任务的更多信息,请看这篇文章。有关分段任务的更多信息,请参见这篇文章

下载内置 PyTorch 图像数据集

在构建自定义数据集之前,了解内置 PyTorch 图像数据集是很有用的。PyTorch 通过 torchvision 提供了许多内置/预先准备/预先烘焙的图像数据集,包括:

  • MNIST,时尚 MNIST,KMNIST,EMNIST,QMNIST
  • COCO 字幕,COCO 检测;
  • LSUN、ImageNet、CIFAR、STL10、SVHN、PhotoTour、SBU、Flickr、VOC、Cityscapes、SBD、USPS、Kinetics-400、HMDB51、UCF101 和 CelebA。

导入 torchvision 后,只需一行代码就可以下载所提供的数据集。下面是一个下载 MNIST 数据集的例子,该数据集由 60,000 个训练和 10,000 个手写数字测试图像组成。每个图像都是灰度和 28 x 28 像素:

import torchvision 
mnist = torchvision.datasets.MNIST('path/to/mnist_root/',download=True)

从 MNIST 数据集采样的图像蒙太奇。图片来源:维基百科,CC by SA 4.0

在上面的代码片段中,您可以将“path/to/mnist_root/”替换为保存 mnist 图像的目录的绝对路径。

以下是如何下载 CIFAR-10 数据集的示例:

cifar10 = torchvision.datasets.CIFAR10('path/to/cifar10_root/',download=True)

CIFAR-10 包括 50,000 个训练图像和 10,000 个测试图像。它们都是自然的彩色图像,大小为 32 x 32 像素。

CIFAR-10 示例图像。图片来源: CIFAR 网站

您可以指定下载数据集的特定子集(例如,训练、val 或测试)。语法很简单,并且根据您使用的数据集的不同而略有不同。在 torchvision datasets 页面上,为每个数据集分别记录了指定下载数据集特定子集的所有必要参数

作为一个例子,为了指定 MNIST 的训练或测试集,提供了一个称为“train”的自变量,它可以被设置为真或假:

若要指定 MNIST 的训练集,请设置 train=True。

mnist_train = torchvision.datasets.MNIST('path/to/mnist_root/', train=True)

要指定 MNIST 的测试集,请设置 train=False。

mnist_test = torchvision.datasets.MNIST('path/to/mnist_root/', train=False)

为了指定 VOC 2012 分割数据集的训练集或 val 集,提供了一个名为“image_set”的参数,它可以设置为“train”或“val”:

vocseg_train = torchvision.datasets.VOCSegmentation('path/to/voc_root/', year='2012',image_set='train') vocseg_val = torchvision.datasets.VOCSegmentation('path/to/voc_root/',year='2012',image_set='val')

避免过度下载

对于一些内置的 PyTorch 数据集,初始下载可能需要很长时间,这取决于数据集的大小和您的互联网速度。幸运的是,如果您已经下载了一次数据集,那么只要您指定了最初下载数据集的目录,就不需要在该计算机上再次下载它。例如,如果您已经将 MNIST 下载到“path/to/mnist_root/”目录,那么只要您提供的路径是“path/to/mnist_root/”,您就可以访问数据集,而无需再次下载。您还可以通过设置 download=False 来明确指定不要再次下载数据集,这意味着如果由于某种原因您提供的路径不正确,您将会得到一个错误。

#The first time we use this training set, we download it to a particular location 
mnist_train = torchvision.datasets.MNIST('path/to/mnist_root/', train=True) #The second time we use this training set, we don't need to download it and we can just load it from the location we specified before: 
mnist_train = torchvision.datasets.MNIST('path/to/mnist_root/', train=True, download=False)

通过 DataLoader 类使用内置 PyTorch 图像数据集

为了实际使用一个数据集,我们需要能够从该数据集中挑选出示例,并创建它们的批处理以提供给我们的模型。PyTorch 数据加载器接收数据集并对其进行批处理。DataLoader 负责批处理,这很好,因为这意味着我们不需要编写任何繁琐的代码来选择数据集的随机子集。

以下是如何使用提供的 data loader 类为 MNIST 创建训练数据加载器的示例:

import torch 
import torchvision mnist_train = torchvision.datasets.MNIST('path/to/mnist_root/',train=True) train_data_loader = torch.utils.data.DataLoader(mnist_train, batch_size=32, shuffle=True, num_workers=16) for batch_idx, batch in enumerate(train_data_loader): 
    #inside this loop we can do stuff with a batch, 
     like use it to train a model

以下是如何为 MNIST 创建测试数据加载器的示例:

mnist_test = torchvision.datasets.MNIST('path/to/mnist_root/',train=False) test_data_loader = torch.utils.data.DataLoader(mnist_test, 
                                               batch_size=32, 
                                               shuffle=False, 
                                               num_workers=16) for batch_idx, batch in enumerate(test_data_loader): 
    #do stuff

为神经网络模型归一化数据

在向神经网络提供图像数据之前,必须对图像进行归一化,以使输入数据在数值上大致在[0,1]或[-1,1]的范围内。当训练神经网络的数据量大约在这个范围内时,神经网络具有更稳定的训练。对于原始 RGB 像素值在 0 到 255 范围内的图像,成功训练神经网络模型的可能性极小。

PyTorch 为规范化数据提供了多种选择。一个选项是 torch vision . transforms . normalize:

来自 torchvision.transforms 文档

您可以看到,上面的 Normalize 函数需要一个“均值”输入和一个“标准差”输入。“*均值”应该是训练集中每个颜色通道的原始像素的*均值。“std”应该是训练集中每个颜色通道的原始像素的标准偏差。如果您有一个大的数据集,您会希望计算这些值一次,然后存储它们,而不是每次都重新计算它们。请注意,您必须仅使用训练集来计算*均值和标准偏差,因为如果您使用整个数据集,您会将测试集的相关信息泄漏到训练过程中,因为它包含在*均值/标准偏差计算中。

为 ImageNet 上预训练的模型预处理数据

PyTorch 提供在 ImageNet 上预先训练的模型。在为这些模型准备数据时,我们必须考虑到所有这些模型都希望它们的输入图像以特定的方式进行预处理。这些图像必须是 3 通道和 RGB,形状(3 x 高 x 宽),其中高和宽预计至少为 224。此外,像素值必须在范围[0,1]内,并且应该使用 mean = [0.485,0.456,0.406]和 std = [0.229,0.224,0.225]进行归一化。这些*均值和标准值是使用上一节描述的过程在 ImageNet 上计算的。以下转换将使用这些 ImageNet 规范进行规范化:

normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], 
                                 std=[0.229, 0.224, 0.225])

数据增强

数据扩充允许您鼓励模型的预测对某些类型的变化保持不变,例如图像的翻转或旋转。PyTorch 在 torchvision.transforms 中为图像数据增强提供了许多变换,包括颜色抖动、灰度、随机仿射变换、随机裁剪、随机翻转、随机旋转和随机擦除。可以用 torch vision . transforms . compose(transforms)聚合多个转换。

请注意,如果您正在执行对象检测或分割任务,其中地面实况与输入图像“类似图像”且形状相同,则需要对地面实况和输入图像应用等效的数据变换。例如,如果您对输入图像应用水*翻转,您还需要水*翻转该图像的分割基础事实。

下面是一些数据扩充的数据转换的例子,使用的是来自维基百科的公共领域狗图片:

图片由作者提供,来自公共领域狗的照片

图片由作者提供,来自公共领域狗的照片

图片由作者提供,来自公共领域狗的照片

图片由作者提供,来自公共领域狗的照片

图片由作者提供,来自公共领域狗的照片

制作自己的数据集:概述

您可以为您想要的任何图像集合创建 PyTorch 数据集,例如医疗数据、从互联网上下载的随机图像或您拍摄的照片。各种机器学习数据集的例子可以在这里找到。

PyTorch 中自定义数据集实现的要求如下:

  • 必须是 torch.utils.data.Dataset 的子类
  • 必须实现 getitem 方法
  • 必须实现 len 方法

实现之后,可以将自定义数据集传递给 torch.utils.data.DataLoader,然后它可以并行加载多个批处理。这真的很好——这意味着您所要做的就是定义在哪里找到您的图像数据以及如何准备它(即定义一个数据集),然后 PyTorch 会处理所有的批处理和并行数据加载,这样您就不必这么做了!

制作自己的数据集:TinyData 示例

本教程的库包括 TinyData,这是一个自定义 PyTorch 数据集的例子,它是由我在 Microsoft Paint 中绘制的一堆微小的多色图像组成的。这张图片展示了数据集中的图像:

作者图片

下面是 CSV(显示在 Excel 中)的屏幕截图,它定义了每个图像的标签:

作者图片

如上所述,TinyData 是一个用于多标签分类任务的数据集,其中每个影像都与一个或多个标签类别相关联,即红色、蓝色或黄色,以确定该特定颜色是否出现在影像中。

tiny data py torch 数据集的代码

现在让我们看一下定义 TinyData PyTorch 数据集的代码。

这段代码可以在的 load_dataset 目录中找到。它分为两个模块,custom_tiny.py 定义 TinyData 数据集,utils.py 定义图像预处理函数。

概括地说,如果我们在 custom_tiny.py 中查看 TinyData 类,我们可以看到 TinyData 满足上面列出的在 PyTorch 中实现自定义数据集的 3 个要求:

作者图片

现在,让我们来考虑这些必需的部分:

torch.utils.data.Dataset 的子类:为了使我们的数据集成为 PyTorch 数据集的子类,我们需要做的就是将 torch.utils.data.Dataset 放在我们的类名后面的括号中,比如如果我们只导入了 torch,就放入 my class name(torch . utils . data . Dataset),或者如果我们使用了更具体的导入,就放入 my class name(Dataset)“from torch . utils . data import Dataset”使我们的数据集成为 PyTorch 数据集的子类意味着我们的自定义数据集继承了 PyTorch 数据集的所有功能,包括进行批处理和并行数据加载的能力。

len 方法:该方法只返回数据集中图像的总数。您可以在 TinyDataset 的代码中看到,我们将 self.all_filenames 定义为包含数据目录中所有图像文件的名称,这样我们就可以简单地将 len 方法实现为 len(self.all_filenames)。对数据集中的图像数量进行硬编码不是一个好主意;最好根据存储图像的目录的内容来计算图像的数量。

getitem method:该方法必须接受一个整数值“idx”。该方法然后使用该整数值从数据集中选择单个例子,例如通过索引到文件名列表中。最后,该方法返回示例,以便将其提供给模型。该示例至少需要包含一个图像及其相应的标签。图像应该已经过完全处理,以便可以直接输入到模型中-在此方法返回图像之前,应该对图像应用所有规范化和数据扩充步骤。

在 TinyData 的示例代码中可以看到,它的 getitem 方法包括几个步骤:
(1)通过 selected _ filename = self . all _ filenames[idx]选择位于索引“idx”的文件;
(2)使用 PIL 库加载存储在该位置的图像;
(3)应用数据处理步骤,在这种情况下由函数 to_tensor_and_normalize()实现,该函数在 utils 模块中定义;
(4)加载该图像的标签;
(5)通过定义包含图像数据、标签以及整数索引的 Python 字典来创建示例(称为“样本”)。从技术上讲,您不需要提供整数索引,但是它对学习有帮助,所以这里包含了它。

保持数据处理代码 分开

最好将执行数据处理步骤的代码放在与数据集定义不同的模块中。为什么?

原因 1:如果您要运行许多不同种类的实验,那么您的图像处理代码很可能会随着时间的推移而增长,并且您不希望用一堆数据处理函数来混淆定义数据集的模块。本教程中的数据处理代码非常简单——只有几行代码——但是原则上我已经将它放入了自己的模块 utils.py 中(实际上,有一个比“utils”更具体的模块名称是个好主意,但是对于本教程来说已经足够了。)

原因 2:您可能希望多个不同的数据集使用相同的数据处理步骤。如果所有的数据处理函数都在某个“处理模块”中定义,那么每个数据集模块都可以从这个“处理模块”导入,并且代码保持有序。作为一个例子,utils.py 被 custom_tiny.py(定义我们的微型自定义数据集)和 custom_pascal.py(定义基于 PASCAL VOC 2012 的数据集,在本文后面讨论)导入和使用。

如果我们正在做大量的定制数据扩充,那么做这些的函数也将在 utils.py 中定义。

定义训练 vs 验证 vs 测试 数据

您不需要为训练、验证和测试数据定义单独的数据集类。事实上,这样做是不可取的,因为这需要您的代码库包含大量冗余代码。相反,要使单个数据集类能够用于定型、验证或测试数据,可以使用参数来确定数据集将在何处查找图像。在 TinyData 示例中,该参数称为“setname ”,它确定 TinyData 类将从哪个目录加载图像。

tiny data 上的训练模型

要在 TinyData 上训练神经网络,可以运行以下命令:

python Demo-1-TinyConvWithoutSequential-TinyData.py 
python Demo-2-TinyConv-TinyData.py

你不需要 GPU 来运行上述命令,因为数据集非常小。

PASCAL VOC 2012 的自定义数据集

正如我们从 TinyData 示例中看到的,当您想要使用自己的图像时,PyTorch 数据集肯定会派上用场。事实证明,如果您想以不同于默认的方式使用现有的 PyTorch 数据集,PyTorch 数据集也很方便。让我们看一下 load_dataset/custom_pascal.py(也在教程库中)来理解为什么以及如何做到这一点。

custom_pascal.py 为 PASCAL VOC 2012 数据集定义了一个数据集。PASCAL 是一个自然图像数据集,标有以下类别的分割图:“飞机”、“自行车”、“鸟”、“船”、“瓶子”、“公共汽车”、“汽车”、“猫”、“椅子”、“奶牛”、“餐桌”、“狗”、“马”、“摩托车”、“人”、“盆栽植物”、“羊”、“沙发”、“火车”和“电视监视器”。每个图像可能有多个类别。

原来 PyTorch 已经提供了一个加载 PASCAL 的类。下面是一个使用内置 PyTorch 类加载 PASCAL VOC 2012 训练集的示例:

pascal_train = torchvision.datasets.VOCSegmentation(voc_dataset_dir, year='2012',image_set='train',download=False)

如果 PyTorch 已经有了一个 PASCAL 的内置类,叫做 VOCSegmentation,那我们为什么还要费心在 custom_pascal.py 中定义一个 PASCAL 的自定义类呢?有两个主要原因:

(1)因此我们可以将帕斯卡数据集与 SBD 相结合,并创建更大的整体数据集;

(2)所以我们可以用分类标签代替分段标签。

合并两个数据集:帕斯卡+ SBD

研究论文中的 PASCAL 数据集经常与 SBD 数据集结合在一起。为了在帕斯卡和 SBD 数据集上训练单个模型,我们需要以某种方式“混合”这些数据集。最简单的方法是在一个自定义数据集类中同时加载帕斯卡和 SBD,我们在自定义类中这样做:

#Define dataset 
if setname == 'train': 
    #In the training set, combine PASCAL VOC 2012 with SBD 
    self.dataset =    
        [torchvision.datasets.VOCSegmentation(voc_dataset_dir,
                                              year='2012',
                                              image_set='train',
                                              download=False), 
        #SBD image set train_noval excludes VOC 2012 val images    
        torchvision.datasets.SBDataset(sbd_dataset_dir, 
                                       image_set='train_noval', 
                                       mode='segmentation',
                                       download=False)] elif setname == 'val': 
    self.dataset = 
        [torchvision.datasets.VOCSegmentation(voc_dataset_dir, 
                                              year='2012',     
                                              image_set='val', 
                                              download=False)]

然后在 getitem 方法的开始,我们简单地检查是否需要从 PASCAL 数据集或 SBD 数据集中选择我们的图像,这取决于整数 idx 有多大:

if idx < len(self.dataset[0]): 
    chosen_dataset = self.dataset[0] 
else: 
    chosen_dataset = self.dataset[1] 
    idx = idx - len(self.dataset[0])

最后要考虑的是适当地定义我们的 len 方法,以便我们考虑两个数据集的大小:

def __len__(self): 
    if self.setname == 'train': 
        return len(self.dataset[0])+len(self.dataset[1])     elif self.setname == 'val': 
        return len(self.dataset[0])

因为 len 方法正确地反映了帕斯卡和 SBD 的组合大小,PyTorch 从我们的数据集中采样时产生的随机整数 idx 有时会导致 getitem 函数返回帕斯卡图像,而其他时候会导致 getitem 返回 SBD 图像。

改变数据集的标签:分段- >分类

我们为 PASCAL 定义自定义数据集的第二个原因是使用不同的标签。PyTorch 定义的 PASCAL 数据集用于训练分割模型。因此,每个图像的基本事实是分割图。

但是,如果不是训练完全监督的分割模型,而是训练分类模型,或者仅依赖于分类标签的弱监督分割模型,会怎么样?在这种情况下,我们需要不同格式的标签,即指示每个类存在或不存在的多热点向量。

我们可以在自定义数据集类中定义这种新的标签。这是在 custom_pascal.py 函数 get_label_vector()中完成的,该函数接受默认分段图标签,并将其转换为多热点存在/不存在向量。然后,getitem 利用 get_label_vector()将分割图标签转换成我们想要使用的分类标签。

模块 custom_pascal.py 还包含其他有用的代码,包括可视化数据集中的图像和可视化地面真实分割图的函数。它还包括从整数类标签到它们相应的描述性名称(如“cat”或“bus ”)的映射。

定制 PASCAL VOC 2012 数据集上的训练模型

要在自定义 PASCAL VOC 2012 数据集(包括 SBD)上训练神经网络,您可以运行以下命令,最好是在具有 GPU 的机器上:

python Demo-3-VGG16-PASCAL.py 
python Demo-4-VGG16Customizable-PASCAL.py

单元测试

为数据处理编写单元测试总是一个好的策略。如果您错误地处理数据,那么您在其上训练的任何模型都将是错误的。

在 src/unit_tests.py 中可以看到一个单元测试的例子。要运行单元测试,您可以使用以下命令:

python Demo-0-Unit-Tests-and-Visualization.py

上述命令还将运行 PASCAL VOC 2012 数据集可视化。

(关于代码组织的附带说明:如果您正在编写大量的单元测试,那么它们实际上都应该放在它们自己的测试目录中,该目录与 src 处于同一级别。那么 src 中的每个模块在测试中可以有一个相应的单元测试模块。)

定制医疗数据集

公共 GitHub 存储库rachella/ct-net-models包含为 CT 体积数据定义 PyTorch 数据集的代码,包括大量的数据预处理步骤和数据扩充。

用 num_workers 并行数据加载

我提到过 PyTorch 负责并行加载多个批次。您可以通过在数据加载器中定义 num_workers 来控制并行加载的批处理数:

作者图片

num_workers 确定将用于加载数据的进程数。每个过程将加载一个批次。

如果您想进行单进程数据加载,并且一次只加载一个批处理,那么您可以将 num_workers 设置为 0。因为这将导致 PyTorch 只启动一个数据加载过程,所以总体上可能会比较慢,并且您的 GPU 可能会有很多空闲时间,因为它在等待 CPU 完成下一个批处理。设置 num_workers = 0 的一个很好的原因是,如果您使用的是 Windows 机器,并且希望使用 Python 调试器。因为 Windows 是如何处理多处理的,所以您需要将 num_workers = 0,以便在 Windows 上使用带有 PyTorch 的 Python 调试器。但是,一旦调试了程序,就可以增加 num_workers 并在没有调试器的情况下运行代码。

如果要进行多进程数据加载,则需要将 num_workers 设置为正整数,指定加载器工作进程的数量。在此设置中,当 GPU 对一个批次进行计算时,会加载其他批次。例如,如果您选择 num_workers=16,那么将有 16 个进程加载您的批次,这意味着大致上您将同时加载 16 个批次。如果您很好地选择了 num_workers,那么 GPU 将根本不必在批处理之间等待——一旦使用一个批处理完成,下一个批处理就已经准备好了。

您需要仔细选择 num_workers,否则您可能会因为试图同时加载太多的批处理而使您的机器过载。如果您正在处理像 CT volumes 这样的海量图像,或者如果您正在进行大量的数据预处理,这一点尤其重要。

关于如何选择 num_workers,没有严格的规定。以下是一些可能有用的一般提示:

  • 请记住,越高并不总是越好。如果你把 num_workers 设置得太高,会使你的机器窒息,导致速度变慢或者内存出错。
  • 一个不错的经验法则是使用与可用 CPU 内核数量相等的 num_workers。
  • 如果您需要最大限度地优化性能,只需用不同的 num_workers 值做实验,记录一个 epoch 需要多长时间,然后选择导致最快时间的值。这不是一个非常“精神上令人满意”的方法(因为感觉上你应该能够很容易地计算出最佳工人数量),但是这个实验性的测量是计算出一个好的工人数量的最快和最可靠的方法。
  • 请记住,如果您突然从一次训练一个模型切换到一次训练多个模型,您可能希望降低 num_workers。

如果您使用的是 NVIDIA GPU,您可以使用 nvidia-smi 检查内存使用情况:

杂项提示

  • 如果有一个遍历历元的训练循环,请确保将数据加载器放在历元循环之外。否则,您将在每个时期初始化一次数据加载器,这(a)是不必要的,(b)会耗尽您的内存使用。
  • 如果您使用 Git 进行版本控制,请将图像数据集存储在 Git 存储库之外。“微小数据”出现在教程回购中的唯一原因是因为这是一个教程,数据小得不切实际。
  • 与前面的要点相似,将结果文件存储在您的 Git 存储库之外也是一个好主意,因为图像模型的结果通常包含大文件(例如可视化)。

总结

  • PyTorch 的 torchvision 库包括许多内置数据集,包括 MNIST 和 ImageNet。
  • PyTorch 的数据加载器接收数据集并对其进行批处理。
  • torchvision.transforms 可用于规范化数据和/或执行数据扩充。
  • PyTorch 中的自定义数据集必须是 torch.utils.data.Dataset 的子类,并且必须实现 _ _ getitem _ _ 和 len 方法。除此之外,细节由你决定!
  • PyTorch 中的自定义数据集也可以利用内置数据集,将它们合并成一个更大的数据集和/或为每个图像计算不同的标签。
  • 将 num_workers DataLoader 参数设置为某个正整数值 n 意味着 n 进程将并行加载批处理。

数据集创建愉快!

原载于 2022 年 1 月 21 日【http://glassboxmedicine.com】**

用 Tensorflow 扩展构建深度学习管道

原文:https://towardsdatascience.com/building-deep-learning-pipelines-with-tensorflow-extended-913869f1f051

你会看到像大公司一样建立深度学习管道是多么容易

来源。2022 年 6 月 27 日获得 Apache 2.0 许可

目标

在本教程中,我的目标是:

  • 解释机器学习管道在生产中的作用
  • 展示如何开始使用 Tensorflow 本地扩展
  • 展示如何将 Tensorflow 扩展管道从本地环境移动到顶点 AI
  • 给你一些代码示例来适应和开始使用 TFx。

你可以点击查看本教程的代码。

介绍

一旦你完成了你的模型实验,是时候把东西投入生产了。将机器学习投入生产不仅仅是用 REST API 包装模型二进制文件并开始为其服务的问题,而是让重新创建(或更新)和重新部署您的模型成为可能。

这意味着从预处理数据到训练模型以将其投入生产的步骤(我们称之为机器学习管道)应该被部署,并能够尽可能容易地运行,同时使跟踪它和参数化它成为可能(例如,使用不同的数据)。

在这篇文章中,我们将看到如何使用 Tensorflow Extended (TFx)为深度学习模型建立机器学习管道,如何运行并部署到 Google Vertex AI 以及我们为什么要使用它。

我们将从 TFx 及其组件、实现和最小工作管道的概述开始,然后展示如何在 Jupyter 实验室(在 Google Colab 上)和 Vertex AI 管道上运行它。这里使用的代码改编自汉尼斯·哈普克的“构建机器学习管道”。

TFx

TFx 是 TensorFlow 的生产框架。TFx 不仅让用户以高性能的方式服务模型,还包装了 Tensorflow 生态系统的所有部分(从 Keras 到数据验证到服务),让用户构建和运行可扩展的高性能机器学习管道。

它被组织成一系列组件,要求您编写更少的代码,但也很灵活。

来源。在 Apache 2.0 下获得许可的(2022–06–27)

管道被编译成一系列蓝图,适用于不同的基础设施。你可以在本地或 Vertex AI 上运行它们。你可以在 Apache Beam 甚至 Google Dataflow 上运行一些昂贵的数据处理步骤。TFx 不会锁定您的供应商,而是会适应您的资源和基础架构。

来源。根据 Apache 2.0 获得许可

在这篇文章中,我们将为我们的管道使用以下组件:

示例 Gen

TFx 管道的入口点是ExampleGen组件。给定一个本地(或类似云存储的)路径,它根据输入规范收集.tfrecords格式的文件。

我们将使用一个较小的数据集,因此一个.csv文件将是一个更好的选择。我们将使用一个指向本地 csv 文件的CsvExampleGen文件。当我们转移到 Vertex AI 时,这个文件将被上传到一个谷歌云存储桶中。

由我生成

改变

并不总是需要Transform组件,但是当需要进行昂贵的预处理时,它很有用。为此,我们在module file上创建了一个名为preprocessing_fn的纯张量流函数。TFx 将把这个转换应用到由ExampleGen组件输入的所有数据点。

这个函数需要是纯的(意味着没有副作用),并且只使用 Tensorflow 操作,因为(I)这个预处理步骤可以序列化为 TF 图,并烘焙到最终的模型,以及(ii)这个步骤可以在 Apache Beam 上运行,这意味着它将被大规模并行化(对于非常大的数据集很好)。

preprocessing_fn接收一个tf.Example对象(可以把它想象成一个奇特的字典)并应该返回一个dictionary,它将被序列化为一个tf.Example以输入到模型中。在这一步,只要你只使用 TF 函数,你可以做任何你想做的事情。

在构建管道时,我们将看到一个preprocessing_fn及其参数的例子。

我们指向这个模块文件来构建组件。

由我生成

运动鞋

这里是model.fit步骤发生的地方。与Transform步骤相同,我们指向上面有run_fnmodule filerun_fn函数接收一系列[fn_args](https://github.com/tensorflow/tfx/blob/9743f3dcb84b933f0eb672c46200b3485e2c9794/tfx/components/trainer/fn_args_utils.py#L61),这些将在构建管道时进行探索。

run_fnTransform接收实际的预处理实例。它将它提供给训练步骤中的模型,然后返回模型。返回的模型将被进一步验证,然后被推送到部署上下文。

经过训练的组件可以在不同的基础设施上运行。您可以使用本地资源在本地运行它,或者设置基础设施必需品(如 GPU 和 CPU)并在 Vertex AI 上作为托管容器运行它。

由我生成

推进器

Pusher是 TFx 管道的最后一个组件。它的目的是,给定从Trainer得到的模型,将它推送到一个云存储桶,从那里它将被服务(或者由一个 TF 服务实例监听桶,或者由一个使用 TF Lite 的 web/移动应用程序)。

在推送之前,可以在模型之上进行几个验证,例如评估它在测试集上的度量,甚至检查它是否实际运行在 TF 服务容器上。我们将保持简单,如果人们喜欢这个,我们将发布一个更复杂的管道。

由我生成

有了这些组件,我们就可以开始实现管道,看看它实际上是如何工作的。

在 Jupyter 笔记本上实现管道

我们将开始在 Jupyter 笔记本上实现,你可以在 Colab 这里打开它。我们将以交互方式运行它,并检查事情是如何运行的。之后,我们将构建相同的管道,但将其部署在 Vertex AI 管道上。

运行设置单元后,我们必须导入tfx及其InteractiveContext:

我们的数据将放在相对的data_local目录中,所以我们只需要从中创建一个CsvExampleGen。请注意,我们使用交互式上下文运行组件,然后将能够提交它们的输出。

在开始处理数据集之前,获取数据集的统计数据和模式是一个很好的做法。将来,这不仅可以用于验证模式数据,还可以用于验证漂移数据。我们用SchemaGenStatisticsGen 来表示它:

我们现在将使用当前目录下的module.py文件中的预处理函数对数据进行预处理:

我们只输入缺失值和一次性编码的分类特征。这很简单,因为:

然后我们继续训练我们的模型。注意,我们使用 transform 组件的结果,并设置 eval 和 training 参数。因为我们使用这些组件来说明 TFx 管道的创建,所以我们不会对它们进行很长时间的培训:

在我们的模块文件中,我们从转换组件中获取数据。我们在内部可以访问在转换组件之后生成的fn_args.train_filesfn_args.eval_files,因为我们将examples作为关键字参数传递给了训练组件,所以它们在内部是可用的。

训练后,我们还可以访问预处理操作的张量流图,这将有助于将模型投入生产,而不必手动处理。我们还确保在生产中使用与训练和评估模型时所用数据相同的预处理。

在训练之后,我们只需要将我们的模型保存到它将要被保存的地方。在本教程中,我们将它保存在本地,但应该明确的是,我们可以将模型推送到云存储桶,在那里它将被生产环境访问(例如,桶上的 TF 服务容器监听)。

在 Vertex AI 上部署它

TFx 很棒,因为你可以使用相同的组件,并在云上运行它们。在某些情况下(如训练和转换数据),可以将它设置为在处理大数据的特定基础设施上运行(作为数据流,您可以大规模并行化转换步骤)。

您还可以指定训练步骤所需的资源,Vertex AI 将使用所述资源在 VM 上训练容器。我们现在将看看如何以最简单的方式做到这一点。

要做到这一点,让我们打开笔记本的顶点人工智能部分这里

我们的火车代码有相同的module.py,还有一个base_pipeline.py,它像我们在本地运行中所做的那样连接组件,但是没有交互上下文:

这里的变化是,我们将运行这个管道,而不是使用InteractiveContext来运行它,我们将创建一个.json蓝图,然后在 Vertex AI 上运行它。

神奇的是:使用相同的管道,我们将构建 json 蓝图在Kubeflow中运行它,然后使用 Vertex AI 管道(一个奇特的 Kubeflow 管道引擎)来实际运行它。

在运行它之前,转到pipeline_vertex.py,将第 29 行改为您的项目名称,将第 19 行和第 20 行改为您创建的 bucket。然后将本地数据上传到YOUR_BUCKET/datamodule.pyYOUR_BUCKET/components

您还应该为此设置一个服务帐户。

然后运行笔记本上的所有单元并打开 Vertex AI 管道,您应该会看到类似的东西:(以及您创建的桶上的模型二进制文件)。

由我在顶点人工智能管道上生成

结论

我们可以在 TFx 上使用相同的代码库,并在本地运行 train 作业(调试和处理小东西),并且在需要时,将它移动到 Vertex AI,只需稍作修改。这样,我们就可以处理大型数据集和模型,并以一种可行的方式训练。

很高兴看到 TFx 如何将 Tensorflow 生态系统的所有功能整合到一个适合生产的框架中。

本教程旨在简单明了地展示使用 TFx 是多么容易,同时给出一些代码示例供您入门。这里使用的大部分代码都是改编自汉尼斯·哈普克的机器学习管道,或者至少是受其启发,其中的 Github 库是在麻省理工学院许可下发布的。

如果你发现任何错误,有疑问,想进一步讨论这个话题或者只是想交个朋友,请随时到 piero.skywalker@gmail.com 找我。

谢谢!

参考

https://github.com/Building-ML-Pipelines/building-machine-learning-pipelines https://www.buildingmlpipelines.com/

构建动态客户情感仪表板

原文:https://towardsdatascience.com/building-dynamic-customer-sentiment-dashboards-a7cf75b0fe8d

通过获取实时警报,快速发现不满意的客户并防止差评病毒式传播

Unsplash 上由 Carlos Muza 拍摄的照片

语境

每个企业可能只有一个糟糕的一天,甚至一个糟糕的客户服务事件,可能会使客户非常不高兴。如今,大多数不开心的顾客在社交媒体上发泄他们的愤怒,在你注意到之前,坏消息可能会被放大数倍,导致品牌形象受到相当大的损害。

如果企业能够在这些消息发布后立即发现它们,那么就有可能进行一些损害控制。然而,考虑到社交媒体帖子的泛滥以及试图从干草堆中找到“一根坏针”,这是非常不切实际的。

NLP 能提供什么帮助?

在这种情况下,情感分析自动关键词提取、动态数据可视化和实时警报机制的组合方法可以通过提供实时警报来帮助缓解差评或对您品牌的负面看法的病毒式传播。

这将有助于企业整理所有的帖子,并提取相关的和需要立即采取行动的内容。

什么是情感分析?

如果你没有读过我之前的关于情感分析的文章,请在下面找到一个关于情感分析的简要概述:

情感分析也被称为“观点挖掘”。情感分析根据文本中使用的单词的情感语调给一串文本分配一个分数。

情绪得分——正面、负面和中性

  • “我对这项服务非常满意”是一个积极情绪的例子。这通常会有一个更接*+1 的情绪得分。
  • “我对服务非常失望”是一个带有负面情绪的字符串的例子,其情绪得分接*于-1。
  • “我在五月的最后一周利用了这项服务”是一个带有中性情绪的句子的例子。这将具有接* 0 的情绪得分。

动态情绪仪表板看起来像什么?

下面展示的是一个简单而强大的好情绪仪表板原型。让我们看看仪表板的各个部分。

情感仪表板的原型

  1. 水*轴是时间序列轴,其中时间间隔取决于什么适合于业务。
  2. 有两个垂直轴——主轴由评论的频率组成,次轴由情感分数组成。
  3. 正面评价&和负面评价的频率给了我们一些启示:

两个条形的高度显示了该品牌被提及的次数,是“品牌显著性”的一个指标。

正面评价与负面评价的比例反映了对该品牌的总体看法。较高的正比率有利于品牌,较高的负比率不利于品牌。

4.绿线是该品牌在该时间段内的最大正面情绪得分,红线表示最大负面情绪得分。

这两条线有助于确定这段时间内最快乐的顾客和最不快乐的顾客。

5.阈值线表示负面情绪得分,低于该得分时,实时警报系统将被激活。

将阈值设置为更负的分数可能会导致一些不开心的客户不被注意地溜走,而较高的阈值可能会触发大量警报,从而增加客户体验团队的工作。

创建动态情感仪表板的步骤

  1. 第一步是实时提取所有社交媒体帖子、在线评论*台的评论和品牌提及。几乎所有领先的社交媒体*台和评论门户都提供安全和授权的 API 来帮助提取内容。确保内容提取符合相关许可和授权的道德规范。
  2. 确保元数据与提取的数据相对应,包括发布日期/时间、位置(如果可用)、社交媒体使用信息等
  3. 需要对收集的数据进行预处理,并使其适用于进一步的 NLP 处理。
  4. 以这样一种方式设置数据表,即每个提及都有其可从后端检索的元数据。
  5. 对收集的数据运行情感分析引擎,并收集预处理文本数据的每个单元的情感得分。
  6. 在时间序列图上画出提及的频率以及最高和最低的情感分数。根据业务的性质,时间序列图的频率可以设置为分钟、小时或天。
  7. 对于时间序列图的每个实例,捕获具有最积极和最消极情绪得分的提及的关键词/主题。这可以使用关键词提取或主题建模来完成。
  8. 根据你的企业对负面评论的敏感程度,设定负面情绪得分的阈值。
  9. 如果最小情绪得分超过阈值,则激活实时警报系统。
  10. 警报系统应该向关键流程所有者发送一条消息,其中包含从元数据中提取的详细信息。

从动态仪表板中获取见解

动态仪表板的主要目的是为负面评论提供实时提醒。然而,从相同的数据中还可以获得其他一些见解。举几个例子:

  • 提供对品牌在一段时间内的总体情绪趋势的见解——是消极趋势还是积极趋势?
  • 提供数据,使用聚类技术对评论进行细分和描述 —通常发布负面评论最多的客户的特征是什么?这可能会为特定的被忽视的客户群提供改进产品/服务的见解。
  • 特征提取— 通过对负面评论进行主题建模,识别出可能会令顾客厌烦的特定产品特征

总结潜在的挑战

由于收集的数据非常庞大,因此数据收集、存储和检索可能会涉及巨大的成本。对建立和维护系统所涉及的成本与受损品牌形象的机会成本进行系统的成本效益分析,应能明确是否投资以及投资多少。

建造 Ellee——一个 GPT 3 和计算机视觉驱动的会说话的机器人泰迪熊,具有人类水*的对话智能

原文:https://towardsdatascience.com/building-ellee-a-gpt-3-and-computer-vision-powered-talking-robotic-teddy-bear-with-human-level-db7d08259583

我如何建造一个可以移动头部并自然交谈的机器泰迪熊。

Dexie 在和 Ellee 说话(图片由作者提供)

我有一个相当长的三周圣诞假期,尽管大部分时间都和家人在一起,但在假期结束前一周,我还有一些时间可以消磨,所以我决定做一些有趣的事情!我一直想为 GPT-3 人工智能模型找到一个好的用例,我还想为我们三岁的儿子德谢(Dexie)构建一些有趣、互动和有教育意义的东西,他非常健谈,非常好奇。所以,我想出了制造一个会说话和移动的机器人泰迪熊的主意。

概念

这只机器泰迪熊将能够看到和认出一个人,叫他们的名字并和他们说话。她将具有听觉能力来听正在说的话并产生自然的声音反应。她需要能够理解对话的内容,并相应地做出反应。稍后我会详细说明这一点。在整个谈话过程中,她需要能够移动她的头来看这个人的脸。

为了最大限度地提高亲密度,我决定把德西最喜欢的娃娃之一埃拉变成这个机器人。我给她起了个新名字,Ellee,代表电子版的 Ella。

德西和埃拉,他最喜欢的泰迪熊娃娃(图片由作者提供)

研究

为了实现上述目标,Ellee 需要具备以下模块:

  • 视线。Ellee 必须能够跟踪一个人的位置,并实时识别他们的脸,这样她就可以移动她的头来看着他们,并叫这个人的名字。为此,我需要一个连接到人工智能系统的摄像头,以检测一个人及其面部的存在和位置,并识别他们。需要一个经过训练可以识别人体及其面部的物体检测人工智能模型,该模型将在连接到摄像头的 GPU 驱动设备上运行。
  • 头部运动。 Ellee 需要能够左右和上下转动头部(两个自由度)。
  • 听证会。 Ellee 将需要能够听对话,这需要语音识别技术和麦克风。
  • 大脑。Ellee 需要能够理解正在说的话,并通过考虑过去的对话来提供一些上下文,从而生成自然的文本响应。这就需要一个强大的生成式文本 AI 模型。
  • 演讲。 Ellee 需要跟人打招呼,说出大脑模块产生的文本反应,这就需要文本到语音的技术。
  • 协调人。这是连接所有组件所必需的。

基于我对过去项目的经验,经过一些研究,我列出了运行该系统所需的硬件清单。

  • NVIDIA Jetson Nano(150 澳元)。这是一个由 GPU 驱动的小型嵌入式设备,将运行所有模块(特别是对象检测和面部识别人工智能模型)。这是一款非常适合这项工作的设备,因为它可以通过 USB 端口支持麦克风和音频输出,并且它有一个以太网端口,可以轻松访问互联网以进行 API 调用。你甚至可以插入鼠标和键盘,在设备上进行开发和调试,因为它有一个功能齐全的 Ubuntu 18.04 操作系统。

NVIDIA Jetson Nano

  • USB 麦克风和扬声器(20 澳元)。这些听起来可能并不复杂;然而,人们一直在报告一些扬声器和麦克风在 Jetson Nano 上无法工作,所以希望这些细节对您有用。我可能很幸运,因为我买的唯一一个 USB 扬声器在摆弄了一阵嘶嘶的反馈声后工作了。然而,我买的两个麦克风中只有一个(USB 的)能用。品牌和型号见下图。

扬声器和麦克风(图片由作者提供)

  • 相机——索尼 imx 219 160(35 澳元)。这是一个非常棒的微型 160 度 POV 8MP 万像素树莓相机,使 Ellee 能够看到和识别人。根据我对其他机器人项目的经验,广角 POV 是至关重要的——否则,Ellee 将无法发现任何人,除非他们直接在她面前,这感觉不自然。
  • 伺服电机 ($AUD 75)。两个 5 公斤/厘米的扭矩伺服电机连接到*移和倾斜支架将允许两个自由度的旋转。需要一个 PWM 驱动器来驱动电机,因为 Jetson Nano GPIO 引脚仅提供 1 mA 电流,而伺服电机消耗 3 A 电流。由于电机只需要移动 Ellee 的头部,这相当轻,5 公斤/厘米的扭矩就足够了。

伺服电机、支架和 PWM 驱动器(图片由作者提供)

  • 木棍和一个柜子 ($AUD 10)。木棍将作为 Ellee 的骨架结构来连接摄像机和伺服系统。棍子将被连接到木制的柜子上,硬件部件被藏在柜子里,Ellee 将坐在柜子的上面。

Ellee 的骨骼结构和一个柜子(图片由作者提供)

履行

有了一个可靠的计划,我开始完成我的使命。

建设视线

为了从摄像机获取视频,我使用了一个名为 Jetcam 的很棒的库,它是我扩展的。在 4 行代码内,您可以让整个系统运行起来:

from jetcam.csi_camera import CSICamera
camera = CSICamera(width=224, height=224, capture_width=224,         capture_height=224, capture_fps=30)
image = camera.read()

接下来,需要一个对象检测组件来分析视频帧,并检测人体和面部的位置,以便能够跟踪和查看它们。请记住,NVIDIA Jetson Nano 的 GPU 不如 RTX 等桌面级 GPU 卡强大,因此选择一种在准确性和性能之间取得良好*衡的对象检测模型架构至关重要。

过去,我一直使用 MobileNetSSDV2 模型架构来构建我在 Tensorflow 上运行的对象检测模型,该模型在准确性和性能(10FPS)之间实现了良好的*衡。这一次,我使用的是 MobileNetSSDV2 模型,它带有运行在 PyTorch 上的 NVIDIA JetPack SDK ,简单到只需添加三行代码:

import jetson.inference
net = jetson.inference.detectNet("ssd-mobilenet-v2", threshold=0.1)detections = net.Detect(image, width, height)

令我惊讶的是,该模型提供了 20 帧/秒。太棒了。我不需要建立自己的模型,因为它已经支持 91 个 COCO 类,包括我需要的人体。

我使用了一个 Dlib,一个现代机器学习框架,来检测和识别人脸。我可以构建自己的自定义对象检测,将 face 作为其中一个类;因此,我不需要运行另一个面部检测,这可能会提高一些性能。然而,我决定不这样做,因为我一直想尝试一下 Dlib 库,现在正是时候。此外,相对于简单地添加上述三行代码,我懒得构建自己的对象检测模型。
用 Dlib 检测人脸非常简单——两行代码给你一个检测到的人脸包围盒列表,以及它们的置信度得分。

import face_recognition
face_recognition.face_locations(image)

为了识别人脸,Dlib 提供了两个重要的功能。第一个函数是 face_encoding,它基本上是从人脸图像中计算出一个指纹,称为编码。这种编码唯一地识别一张脸。第二个函数是 face_distance,它计算人脸编码与人脸编码列表之间的距离(相异度)。结果是一个距离列表,每个面一个距离。距离最小的脸是最相似的脸。

这是我如何使用它们的。首先,使用 face_encoding 函数,我在应用程序开始时生成了我所有家庭成员的面部编码,并将它们保存在一个列表中。在运行时,我为视频帧中每个检测到的人脸调用 face_encoding,并将结果传递给 face_distance,以计算该编码与包含我的家庭成员编码的列表之间的距离。最后,我按升序对结果进行了排序,并挑选了第一个(鉴于它也通过了最小距离阈值,以防它们都不是我的家庭成员)。这是不是意味着 Ellee 再也认不出其他人了?我为她增加了一个功能,可以自动注册一个新的未被识别的人的脸,我将在后面的部分介绍。

对象检测和人脸识别(图片由作者提供)

检测/识别准确度相当好。然而,在 Jetson Nano 中,face_encoding 和 face_locations 函数的执行时间都在 0.5 秒左右。因此,为每个视频帧调用它们会显著降低系统 FPS,从 20FPS 降到 1FPS。这是不可接受的,因为这将在 Ellee 认为人脸的位置与实际位置之间引入+1 秒的滞后,这导致她的头以+1 秒的滞后跟随某人的脸。

对此的解决方案是以较低的 FPS 在线程中调用面部检测/识别,例如每 10 帧调用一次。鉴于相机设置为 10FPS 捕捉,这意味着我们每秒钟都在进行人脸检测/识别,这并不算太糟糕。我使用相同的技术每两帧运行一次对象检测,最终性能提高到 12FPS。

降低物体检测和面部检测/识别 fps(图片由作者提供)

虽然这个技巧有效,但我注意到它给 Ellee 的头部带来了+1 秒的延迟,即它跟随人的面部有一秒的延迟,因为检测到的面部位置每秒钟才更新一次。我通过不使用检测到的面部位置进行头部跟踪来解决这个问题。取而代之的是,我通过假设,在垂直方向上,它距离人边界框的顶部 5%的距离,在水*方向上,它在人边界框的中心,来伪生成一个人脸的位置。这种假设非常有效,因为大多数时候,被检测人的头部总是在视野中。

人脸包围盒的伪生成(图片由作者提供)

建立头部运动

头部运动模块通过 adafruit_servokit 框架控制两个伺服系统的角度,以达到目标航向和俯仰。Adafruit servokit 是一个兼容 Raspberry Pi 的框架,允许你用几行代码控制伺服电机。当我们使用 PWM 驱动器来控制伺服电机时,我们需要从连接到 PWM 驱动器的杰特森纳米 SCL 和 SDA GPIO 引脚发送控制信号。因此,在初始化时,我们用 SCL 和 SDA 引脚配置伺服套件。我们只需要驱动两个伺服系统;然而,这个 PWM 驱动器可以驱动多达 16 个伺服电机。要实际移动伺服电机,我们只需通过传递适当的 servo_no,为 self.kit.servo[servo_no]赋值。

from adafruit_servokit import ServoKit
import board
import busio# Initialization
self.kit.servo[servo_no].angle = value
i2c_bus0 = (busio.I2C(board.SCL_1, board.SDA_1))
self.kit = ServoKit(channels=16, i2c=i2c_bus0# Move servo [servo_no] to a specific value
self.kit.servo[servo_no].angle = value

请注意,伺服值并不直接对应于实际角度。因此,我们需要在代码中执行一些标准化操作,将角度值映射到伺服值。

非连续伺服电机(我们的)只允许最大 180 度旋转。无论是航向还是俯仰,我们都不需要 Ellee 的头部旋转超过 160 度。因此,我们在控制代码中进一步实现了最小和最大范围限制,将航向限制在 10 到 170 度,俯仰限制在 35 到 90 度。

头部旋转轴和限制。(熊的形象获得了 shutterstock 的授权)

因此,对于 Ellee 来说,要移动她的头部以面对被检测的人,我们需要做的就是将被检测的人的面部的 x 坐标转换为相对于 Ellee 的当前头部方向的方向角度,并相应地用映射值设置相关的伺服。然后,我们对一个音高重复这一过程。
虽然这看起来很简单,但直接将伺服值设置为目标角度有一些缺点:

  • 伺服运动相当快。一旦你设置了一个值,伺服就会以一个恒定的速度过快地向那个值移动,感觉非常机器人化。一个人移动头部的速度越来越慢,并逐渐减速到最终位置完全停止。
  • 两个伺服系统以相同的速度移动。事实上,我们无法控制速度。如果航向和俯仰伺服系统需要从其当前值移动不同的量,需要移动较少的那个将首先到达其目的地。下图左图对此进行了说明,其中绿色圆圈代表起始值,蓝色圆圈代表最终值。垂直轴代表俯仰,而水*轴代表航向。蓝线代表头部运动。您可以看到,航向和俯仰最初以相同的速度移动,直到俯仰达到其最终值 10。然后,只有航向向最终值 90°移动。这是不自然的,因为当我们移动头部时,头部和俯仰同时到达它们的目标。

很明显,我们需要能够以可控的速度独立地向新的目标值移动两个伺服系统,这样我们就可以让它们缓慢移动,同时达到目标值。

用内插法控制航向和俯仰(图片由作者提供)

同样的问题也存在于游戏行业中,当制作任何东西的动画时,比如虚拟角色的头部。解决方案是使用插值技术将移动路径分成几个航路点,并逐步向最终位置递增移动航向和俯仰,如上图右图所示。橙色点代表每个航路点,用于设置从起始值到最终值的两个伺服值。最初,步长很大,当接*最终位置时,步长逐渐变小。这将导致头部逐渐减速直至完全停止。

建立听证会

听觉模块负责通过麦克风听语音,并使用语音识别技术将其转换为文本。延迟在这里非常重要,因为处理时间越长,Ellee 在对话中响应的时间就越长。理想情况下,您希望在 edge(在设备中)上运行语音识别,以避免互联网延迟。然而,在 edge 上运行需要一个强大的设备,据我所知,在撰写本文时,还没有在设备上运行的 edge 语音识别技术能够与 Jetson Nano 的计算能力相媲美,接*谷歌语音识别,这是我可以接受的标准。这也是为什么 iPhone 的 Siri、Google Home 和亚马逊 Alexa 都会把我们的声音发送到云端进行语音识别。

因此,我决定使用谷歌语音识别云服务。为了最大限度地减少延迟,我使用了一种流媒体技术,即不断将检测到的语音发送到云端,以便它可以在这个人说完整句话之前进行识别。使用这个技巧,我设法在这个人说完一句话的 1.5 秒内得到识别的文本结果,不管句子有多长。

谷歌语音识别(官方标志图片)

建造大脑

Ellee 的大脑负责从当前对话中生成文本响应。因此,我们需要聊天机器人技术。听起来很简单,实际上这是 Ellee 最复杂的部分。为什么?首先,它需要理解人说的最后一句话,以产生适当的反应。这本身已经很复杂了。这解释了为什么一个人类小孩需要三年才能获得基本的对话技巧,并且需要更多年才能掌握。但这还不是全部。为了做出恰当的回应,你还需要了解对话的背景,这些背景来自所有过去的对话。看看下面的对话交流。对于 Ellee 来说,要正确回答“是哪个城市?”,她首先需要查看过去的几次交流,以了解我们在谈论阿尔伯特·爱因斯坦和他去世的时间。如果没有这个背景,这个问题可能会被解释为他出生或生活的城市。Ellee 不仅需要掌握语言学,还需要获得历史知识才能回答这个问题。

人类:你知道阿尔伯特·爱因斯坦吗?

是的,我知道阿尔伯特·爱因斯坦。

人类:他什么时候死的?

他于 1955 年去世。

人类:是哪个城市?

那是新泽西州的普林斯顿

然而,人们可能会问一个需要其他知识领域的问题,如电影、音乐、数学、化学、体育等。Ellee 需要掌握所有这些领域。如果很难,他们是怎么搭建 Google Home,Siri,Alexa 的?他们都是基于检索的聊天机器人,只能回答已经准备好并存储在他们庞大数据库中的问题,因此有术语“检索”。试着问上面的问题,你会看到这些聊天机器人是如何悲惨地失败的。

为了达到上述要求,我们需要一个基于生成的聊天机器人,它根据自己的直觉,即通过理解正在说的内容和对话的上下文,逐字生成其响应。

我在过去使用各种技术构建了几个聊天机器人,从检索到生成,没有一个接*满足上述要求。

见见 GPT 3 号!这是通用 NLP 人工智能模型的最新突破之一,由 OpenAI 团队构建,并用来自维基百科和书籍的 45TB 文本进行训练。事实上,维基百科只是其训练集的 3%,所以你可以想象这个模型的庞大规模。训练花费惊人的 1200 万美元,拥有 1750 亿个参数!

OpenAI(官方标志图像)

让 GPT-3 更加独特的是,它是一个通用的语言模型,只需用简单易懂的语言给出指令,就可以完成任何与文本相关的任务。这使得 GPT-3 可以执行各种任务,如完成一首诗,写一份商业计划书,以及执行情感分析和文本分类,而不需要提供普通 NLP 模型所需的数百万个训练集。

为了在 GPT 3 中构建 Ellee,我只需要用简单的语言用这个指令来训练它。

以下是与 Gus 创造的一个叫 Ellee 的 AI 的对话。艾莉住在澳洲的米查姆。Ellee 喜欢和人交谈。她最喜欢的颜色是绿色。Ellee 乐于助人,富有创造力,聪明,非常友好。

AI:嗨[姓名]!你今天想谈什么?

人类:[人类 _ 回应]

第一段对 Ellee 的个性至关重要,这将影响她如何交谈。我希望她有创造力并且友好。向她提供一些背景信息,比如她在哪里出生,谁创造了她,以及她最喜欢的颜色,这很有用,因为她将能够在她的回答中使用这些信息。例如,回答这样的问题:你住在哪个国家?你喜欢绿色吗?你叫什么名字?

我把面部识别模块识别出的人名放在[姓名]下,把语音识别模块识别出的文字回复放在[人 _ 回应]下。仅此而已。GPT-3 然后将生成下一个埃利的反应,我将它与下一个被识别的人类反应一起添加回指令中。

为了有助于我们的说明,让我们说,她正在与德谢交谈,德谢回答说:“我很好,谢谢。”。让我们来谈谈运动。对此,埃利的回答是“我真的很喜欢运动”。你最喜欢的运动是什么?然后德歇用“我爱篮球”来回应。下一个培训说明将如下所示:

以下是与 Gus 创造的一个叫 Ellee 的 AI 的对话。艾莉住在澳洲的米查姆。Ellee 喜欢和人交谈。她最喜欢的颜色是绿色。Ellee 乐于助人,富有创造力,聪明,非常友好。

AI:嗨德协!你今天想谈什么?

人类:我很好,谢谢。让我们来谈谈运动。

艾:我真的很喜欢运动。你最喜欢的运动是什么?

人类:我爱篮球

GPT 3 号将会生成埃利的下一个响应。这个过程反复发生,直到对话结束。通过这种方式,GPT-3 将了解过去对话的背景,以便能够做出更好的回应。为了减少处理时间和成本,我将过去的对话限制在最多 20 次。GPT-3 需要巨大的计算能力来运行;因此,可以通过 API 调用 OpenAI web 服务来访问它。

现在,大脑完成了。其实之前关于阿尔伯特·爱因斯坦的对话交流才是和 Ellee 真正的对话,最后一个问题她都能答对!

你可以在本博客结尾的视频的最后一部分亲自见证 Ellee 的对话能力。

建筑名称提取

除了生成文本响应,大脑模块还负责识别 Ellee 在会话中与之交谈的人的名字。这个超出范围的要求是突然出现的。我想,如果 Ellee 不认识她正在交谈的人,她可以提取他们的名字,如果在他们的谈话中被提到,并注册他们的面部图像,这将是非常酷的。因此,在未来的谈话中,她会知道他们。

我们已经从视觉模块得到了他们的面部图像,我们也知道 Ellee 不认识他们。然而,Ellee 如何从下面的对话中提取他们的名字呢?

艾:你好。我叫艾莉。你今天好吗?

人类:早上好,艾莉。我今天很好。你好吗,艾莉?

艾:我也不错。你在忙什么?

人类:我正和我妻子莫尼卡下午散步。很高兴见到你。我是约翰。

AI:你想和我聊天吗?

人类:我很乐意。你是做什么的?

解决这一问题的常用方法是构建命名实体抽取 NLP 模型。一个人工智能模型,它用成千上万个带有名称标签的句子进行训练,以学习模式来找出哪些单词是要提取的名称,并使用该模型来识别对话脚本中的哪些单词是名称。

命名实体抽取训练集实例

这是一个费力的过程,鉴于我们只对与 Ellee 交谈的人的名字感兴趣,而不是任何名字,这使得它更具挑战性。我们需要某种转换器模型,它理解已识别名称的上下文,以捕获我们感兴趣的名称。

嘿,这不是 GPT-3 作为通用语言模型应该能够解决的任务吗?哦,天啊,的确如此!我是这样做的。我首先从 Ellee 中剥离出所有的对话交流,只留下人类的交流。然后,我用如下的简单指令构建了一个训练指令。

下面是一个叫 Ellee 的 AI 和一个人的对话。从这段对话中提取这个人的名字:如果没有找到名字,我会用“未知”来回应。

早上好艾莉。我今天很好。你好吗,艾莉?

我正和我妻子莫尼卡下午散步。很高兴见到你。我是约翰。

我很乐意。你是做什么的?

名称:

这是我唯一需要告诉 GPT 3 号的事。它神奇地将名字约翰添加到提供的名字的右边

它知道 Monica,即使它被提到,也不是我们正在交谈的那个人的名字。

这是相当令人兴奋的,而且完全改变了游戏规则。

大厦演讲

我用亚马逊 Polly 从大脑生成的文本中合成了 Ellee 的声音。这是另一种云服务,增加了 200 毫秒的延迟。然而,声音的质量是惊人的自然。

亚马逊 Polly(官方 logo 图片)

协调员

协调器的工作是通过跨模块发送数据将所有模块粘合在一起。它有一个状态机,跟踪 Ellee 的当前思维状态,这决定了她接下来要做什么,例如开始听、停止听、开始说话、移动她的头部、重置她的头部位置等。例如,当 Ellee 第一次看到 Dexie 时,控制器创建了一个以 Dexie 为焦点人物的新会话。这是至关重要的,因为有时可以检测到不止一个人,我希望 Ellee 能够看到她一直在说话的同一个人。然后,控制器将从瞄准模块获得 Dexie 的边界框位置,计算并发送新的航向和俯仰角到头部运动模块,成为新的目标,这样她的头部开始跟随他。当 Dexie 保持可见超过两秒钟时,控制器将指示语音模块问候他并开始监听。当一个句子被完整地说出时,它会从听觉模块中抓取已识别的文本,并将其传递给大脑,通过 API 调用 GPT-3 产生响应,并等待响应。在响应时,它将获取响应文本,并将其传递给语音模块来朗读它。当 Dexie 不再可见超过 10 秒时,控制器将重置对话会话,并准备好寻找下一个可见的人。

组装

完成所有模块后,现在是组装硬件的时候了。在我爸爸木工技术的帮助下,我们建造了 Ellee 的骨骼结构。这是一个坚持,我可以附上两个伺服(航向和俯仰)和安装在俯仰伺服相机。

我爸爸的工作室和木制道具(图片由作者提供)

这根棍子然后被放入 Ellee 的体内,安装在她坐的木箱上。这两个伺服系统将位于她的头部,颈部关节的位置,并用螺钉固定在 Ellee 的头部,以确保她的头部与伺服系统一起移动。添加了几层木头来扩展相机的范围,这样它就可以在正确的位置从她被摘除的左眼球中出来。

(图片由作者提供)

我花了几次迭代才把它做好,这实际上是我在这个项目中花了大部分时间的地方。最后,我们完成了组装!然而,存在一些重大问题。Ellee 的脖子看起来像是刚刚被 Freedy Krueger 切开,特别是当她抬起头的时候。她的左眼球,也就是摄像头,比右眼球高,有一个很大的可见的洞,因为摄像头拉起了原来的洞,导致它撕裂和放大。这个看起来像科学怪人的埃利不会和德克西在一起的。我需要做点什么。

(图片由作者提供)

幸运的是,凭借我妻子神奇的缝纫技术和去一趟布料和工艺品供应商 Spotlight 买几条白毛巾和缝纫工具,我们治好了她。

(图片由作者提供)

正如你在下面看到的,她现在看起来好多了。我们给她做了一条围巾盖住她喉咙上的洞,这也让她更时尚。😃

(图片由作者提供)

最后,我们让 Ellee 坐在我爸爸做的一个小木柜上面,并把棍子牢牢地固定住。这个机柜有足够的空间来容纳 Jetson Nano、PWM 驱动器和所有布线。

(图片由作者提供)

表演时间

终于到了让德西第一次和 Ellee 互动的时候了。他不知道我把 Ellee 变成机器人的计划,所以看到他对 Ellee 的第一反应是无价的!他在艾丽身边徘徊,试图理解她究竟是如何移动和说话的。随着他慢慢变得更加舒适,我们与 Ellee 一起进行了一次愉快的交谈。任务完成。耶!!!

包括搭建过程在内的整个体验都记录在下面的视频中。一定要看最后一部分,我用一个更复杂的对话来测试 Ellee。

Ellee 演示视频

我对系统性能相当满意。尽管运行了如此多的模块和几个 AI 模型,它仍以 12FPS 的速度运行。她的头部运动也能很好地跟随她正在交谈的人。对话延迟,即 Ellee 响应对话的延迟,最多只有 2.5 秒,考虑到中间有语音识别、文本生成和文本到语音转换,并且所有这些都是与互联网的往返,这还不算太差。

未来改善

建造 Ellee 非常有趣,教会了我一些东西:

  • 我需要买一台 3D 打印机。如果需要的话,你可以精确地设计和重新印刷一些道具组件,这比手工砍木头要容易得多。
  • 1 FPS 的面部识别/检测并不理想,有时当检测到多个人但面部识别尚未启动时,Ellee 不可能知道她一直在与哪个人交谈。为了提高面部识别/检测频率,我可能需要使用稍微强大的硬件,如 Jetson Xavier NX 或 Jetson AGX Xavier。
  • 有时,当检测到多人时,只有一个人在和 Ellee 说话,她会看着不说话的人,因为她会优先考虑与她更亲*的人和她认识的人。一个解决方案是添加一个意图检测人工智能模型来检测哪个人正在说话。更好的是,如果 Ellee 可以读唇语并将其与她的听力相匹配,即使两个人都在说话,她也可以确定哪一个在和她说话,如果他们同时说话,还可以分割和分离他们的声音。这进一步证明了使用更强大的硬件的合理性。

隐私和道德风险

当建立一种触及隐私和道德的人工智能技术时,我们需要负责任。首先,面部识别技术目前还不是很流行,特别是在隐私方面。Ellee 的面部识别只是用来个性化她与你的谈话,而不是为了跟踪或监视的目的,因此我对此很放心。第二,从伦理的角度来看,你用 GPT-3 构建的聊天机器人很难控制他们的行为,尤其是事实。有时 Ellee 可以编造一个完全合理但错误的事实,这可能会冒犯一些人。因此,请不要把她的回答看得太重。应避免将此类聊天机器人用于具有严重后果的目的,如医疗或情感咨询。

就这样,伙计们!我希望你喜欢阅读我的令人兴奋的暑假计划,就像我喜欢与你分享一样。

完整的源代码可以在这里找到。

如果你喜欢这个博客,请给我一些掌声,并通过我的 linkedin 与我联系。

为初学者构建 ETL 管道

原文:https://towardsdatascience.com/building-etl-pipelines-for-beginners-17c3492d29d2

概述和 Python 实现

丹尼·梅内塞斯的照片:https://www . pexels . com/photo/photo-on-laptop-computer-943096/

数据科学家角色最吸引人的方面是有机会建立预测模型或进行研究,从而产生可操作的见解。

然而,如果没有可用和可访问的数据,这些任务是不可能完成的。

为了获得足够支持分析或产品开发的数据,数据科学家通常选择构建 ETL 管道。

ETL 是 extract-transform-load 的缩写,它是一系列的过程,需要接收数据,处理数据以确保可用性,并将其存储在一个安全且可访问的位置。

ETL 管道的吸引力在于它以最大的效率和最小的摩擦促进了数据收集、处理和存储。

在这里,我们探索 ETL 的各个组成部分,然后演示如何使用 Python 构建一个简单的 ETL 管道。

提取

在进行任何分析之前,需要获得相关数据。

ETL 的第一阶段需要从一个或多个数据源提取原始数据。这样的源可以包括*面文件、数据库和 CRM。

改变

在这一点上,所有的原始数据都被收集了,但是它不太可能适合使用。

因此,ETL 的第二阶段需要转换数据以确保其可用性。

人们可能希望对他们的数据应用多种类型的转换。

1。数据清理

应该删除任何不需要的记录或变量。数据清理可能以移除要素、缺失值、重复值或异常值的形式出现。

2。重新格式化

通常,当从多个来源提取数据时,重新格式化成为一个必要的步骤。即使不同的来源报告相同的信息,它们也可能以自己独特的格式报告。

例如,两个数据源可能具有日期功能,但一个数据源可能以“日-月-年”格式显示日期,而另一个数据源可能以“月-日-年”格式显示日期。为了使数据可用,来自所有来源的信息必须遵循单一格式。

3。特征提取

可以使用现有要素的信息创建新要素。例如,从字符串变量中提取信息,或者从日期变量中提取年/月/日。

4。聚合

可以汇总数据以得出所需的指标(例如,客户数量、收入等。).

5。加入

来自多个来源的数据可以合并,以创建一个全面的数据集。

6。过滤

不需要的类别可以从数据集中省略。

负荷

在应用了所有的转换之后,数据适合于分析,但是需要存储起来以备后续使用。

ETL 的第三个也是最后一个阶段需要将数据加载到一个安全且可访问的位置。

以下是用户在存储数据时可以选择的一些选项。

1。关系数据库

一种非常流行的方法是将数据存储在关系数据库中。通过这种方法,用户可以定期用新获得的数据追加或覆盖存储在数据库中的数据。

2。*面文件

用户还可以选择将其数据存储在*面文件中(例如,Excel 电子表格、文本文件)。

个案研究

通过使用 Python 构建一个简单的管道,我们可以看到 ETL 过程的效果。

假设我们需要获取与新冠肺炎相关的新闻文章的数据,以便进行某种类型的分析。

为了实现这个目标,我们将编写一个程序,它可以:

  • 收集当天发表的关于新冠肺炎的新闻文章的数据
  • 转换数据,使其适合使用
  • 将数据存储在数据库中。

通过这一渠道,我们可以获得当天所有相关新闻文章的信息。通过每天运行这个程序,我们将获得关于新冠肺炎新闻文章的连续数据供应。

练习所需的模块如下所示:

注意:这个练习涉及到使用纽约时报文章搜索 API 提取数据。如果您不熟悉这个 API,或者不熟悉使用 API 进行数据收集,请查阅本文:

https://betterprogramming.pub/data-collection-with-api-for-beginners-52b02e571944

第一步:提取

首先,我们需要使用 New York Times API 获取关于新冠肺炎的新闻文章的原始数据。

我们可以首先创建一个函数,该函数创建用 API 对任何给定查询和日期进行必要请求所需的 URI。

我们可以使用该功能获取在选定日期发表的所有与新冠肺炎相关的新闻文章。

由于 API 只为每个请求提供 10 篇文章,我们需要发出多个请求,直到我们收集了所有的数据,然后将这些数据存储在一个数据帧中。

以下是数据集功能的概述。

代码输出(由作者创建)

第二步:变换

现在,我们需要确保数据经过处理,以便可以使用。

在可用的特性中,唯一需要的是新闻文章的标题、URL、发表日期和作者。此外,收集的文章必须公正客观,也就是说,不需要论坛版。

对于这样的场景,要应用的理想转换是数据清理和过滤。需要删除所有缺少标题的记录以及任何重复的记录。接下来,当我们寻找客观的文章时,应该从数据集中过滤掉所有的论坛版文章。最后,应该省略任何不相关的特征。

我们用下面的代码实现了所有这些。

这是经过处理的数据集的预览。

代码输出(由作者创建)

第三步:加载

因为数据现在是可接受的格式,所以应该存储在关系数据库中(在本例中是 PostgreSQL)以备将来使用。为了实现这一点,我们需要实现对象关系映射(ORM),这可以通过 SQLAlchemy 模块来完成。

这里,我们创建了一个引擎,在这个引擎中,我们传递一个已经创建的数据库的位置。使用这个引擎,我们可以将数据存储在名为“news_articles”的表中。如果该表已经存在,它将追加新数据,而不是被覆盖。使用这种方法,我们可以保留任何以前收集的数据。

现在,数据被发送到提供的位置,可以用 SQL 查询直接访问它。

代码输出(由作者创建)

仅使用几个模块,我们就能够构建一个简单的 ETL 管道,它使用 API 收集数据,处理数据以删除不需要的信息,并将转换后的数据存储在数据库中以供将来使用,这一切都是一蹴而就的。

总之,该代码将获取所有与新冠肺炎相关的最新发表的新闻文章。从理论上讲,如果一个用户连续一年每天运行这个程序,他们将获得全年新冠肺炎新闻文章的所有相关数据。

要考虑的其他工具

虽然演示展示了如何用一个简单的程序实现 ETL,但实际业务案例的数据管道更加复杂,通常需要包含其他工具和技术。

  1. 云*台

在案例研究中,转换后的数据存储在本地机器中。

但是,当涉及大量数据时,在本地存储数据并不是一种可行的方法。因此,依赖云*台(如 AWS、GCP)进行数据存储是很常见的。

2。大数据框架

如果使用大数据,ETL 管道可能需要结合大规模数据处理框架(例如 Apache Spark ),以便通过并行处理加快操作。

3。作业调度器

ETL 很少是一劳永逸的工作。数据可能需要定期收集,以便保持最新。

假设您不是一个从不生病或从不错过周末的机器人,您可能需要利用一个作业调度程序(例如 Apache Airflow)来自动化 ETL 工作流。

结论

照片由 Unsplash 上的 Prateek Katyal 拍摄

当我还是个新手时,我理解 ETL 的概念,但总是被执行它所需的复杂代码所吓倒。

希望我已经展示了可以用基本的编程知识创建 ETL 管道。如果您是初学者,我邀请您通过使用您熟悉的工具构建自己的管道来获得一些实践经验。

一旦你熟悉了基础知识,就可以随意增加更多的技能。很快,您将能够构建类似于真实业务案例中使用的复杂管道。

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

用 Python 构建生成器管道

原文:https://towardsdatascience.com/building-generator-pipelines-in-python-8931535792ff

本文提出了一种构建生成器管道的优雅方法

发电机管道:解决问题的捷径。马修·布罗德尔在 Unsplash 上的照片

在软件中,管道意味着对可迭代的每个元素按顺序执行多个操作(例如,调用一个又一个函数),每个元素的输出都是下一个元素的输入。

在 Python 中,可以用各种方式构建管道,有些方式比其他方式简单。在本文中,我们将讨论一种优雅的方式:生成器管道。

让我举一个简单的例子。对于我们来说,使用由简单操作组成的管道会更容易,这样我们就不必关注每个操作在做什么。例如,对于整数范围内的每个数字,您希望应用以下计算:

  • 求*方根
  • 数字翻倍,加 12
  • 求数字的*方
  • 加上圆周率的*方根
  • 添加 75
  • 返回舍入到两位小数后的结果

当然,没有人会这样做,但是想象一下,每一步都在 iterable 的特定元素上执行操作。例如,iterable 可以包含许多文件,您希望读取它们,对它们进行预处理,对每个文本应用 NLP 模型,分析结果,然后返回它们。或者,您有许多包含定量数据的文件,您希望读取每个文件,检查并预处理数据,使用 ML 模型,然后以某种有组织的方式返回结果。这两个示例可以像上面的示例一样组织在一个管道中,因为每个示例都有一个 iterable,其中包含一系列操作要依次应用到的项目,一个项目的输出是下一个项目的输入。

常见的方法

可能最自然的方法是获取 iterable 的每个元素,并逐步应用这些计算。所以,当你有两个函数要应用于x时,比如说,f1()然后是f2(),你可以通过f2(f1(x))来完成。就这么简单——但是当应用更多的函数并且它们接受更多的参数时,产生的代表f2(f1(x))的代码就变成了一个难以理解的怪物。你会在下面看到。

首先,让我们创建一个对数字执行操作的管道。

:下面这段是描述代码中类型提示的题外话。不感兴趣可以跳过。

正如你在上面的代码中看到的,我决定使用类型别名。在许多情况下,我认为这种方法比使用原始类型提示更具可读性(科萨克 2022 )。因此,我创建了类型别名XType来表示x的类型。另一个类型别名,PipeItems,代表一个 iterable,其中包含了我们将要创建的管道中将要处理的项目;这样就带了XType的对象。在我们的例子中,这些是数字,但一般来说,这可以是任何对象。函数power()add()也将XType的值作为输入,但是这不是必须的;这取决于创建管道的操作。代表特定操作的函数应该接受前一个操作返回的类型的对象。

我们的第一种方法是简单地一个接一个地应用所有的函数(因此函数名为get_pipeline_all_calc),可能是这样的:

什么?!

你怎么能读懂这样的代码,更不用说理解它了?我尝试了十几个版本,只是为了找出哪一个是最好的,但是……没有一个是最好的。因此,因为我需要使行更短,我选择了这一个,尽管它肯定是可读的。但是,他们都不是。

在下面,您会发现在使用标志-l 79之后[black](https://github.com/psf/black)对这段代码做了什么,标志-l 79将最大行长度保持在 79:

我不认为这有多大帮助…你自己决定这段代码对你来说是否是可理解的。

这样的代码不仅难以阅读和理解,而且难以编写和重写。你自己试试。例如,回到管道的描述并自己实现它。或者在上面的函数中增加一个新的步骤;比如说,在加圆周率的*方根之前,把这个数提高到 3 的次方。这样做只是为了看看使用这种方法有多难。

上面的版本工作正常,无论它看起来多么疯狂和不可理解。Python 的禅说“简单比复杂好”,所以应该有更好的方法。

还有就是:发电机管线

发电机管道

get_pipeline_all_calc()函数返回一个生成器,所以我们可以称之为生成器管道。因为它返回了一个generator对象,我们必须评估它的项目。这是生成器管道的一个伟大之处:在被创建之后,管道(正式地说,它是一个generator对象)可以被延迟评估,也就是按需评估。可以一次完成,可以分步完成,也可以在想要或者需要的时候对后续项目进行评估。

然而,许多人不同意这是一个发电机管道;更确切地说,它是压入生成器的计算。真正的生成器管道在管道的每一步都使用一个生成器

因此,要构建一个生成器管道,您需要使每一步都成为基于前一步的生成器的生成器。第一步将 iterable 与原始项一起使用。

这实际上比听起来简单。下面的函数显示了实现这一点的一种方法:

您当然可以使用不同的命名,这可以稍微提高可读性。例如,不使用step_i的名称,其中i代表步骤的 ID,实际上您可以使用一个有意义的名称。例如,step_1可以是root_squaredstep_2可以变成doubledstep_3可以是added_12。虽然在这里这样的命名并不真正有用,但在许多情况下会有用。您也可以在每个步骤中使用map()(稍后我们将返回到map())。

对我来说,生成器管道版本get_pipeline_original()可读性更好,也更容易更新(例如,添加一个新操作)。我看到了链中的每一步,所以很容易添加新的一步,不像在get_pipeline_all_calc()中。

然而,代码并不完美。我们能进一步改进它吗?

我们能把发电机管道做得更好吗?

get_pipeline_original()函数展示了创建生成器管道的典型方法。我不认为它经常被使用,尽管它有可读性。虽然可读,但它不是完全可读的。例如,我不喜欢视觉混乱,这主要是由在每一步中创建生成器表达式的for循环造成的。也许最重要的是,为了理解这样的管道,您需要同时考虑两件事:操作和生成器表达式。在它们之间,前者构成了本质,后者构成了方法论的具体内容。

正如我上面写的,我们可以使用map()函数来代替。但不幸的是,这没有多大帮助:我们必须理解所有的地图。

事实上,正是管道作业构成了其本质。我们应该关注它们,因为如果我们想要了解管道,我们需要了解其每个步骤的以下方面:

  • 对操作的输入
  • 手术的作用
  • 操作的输出

回到上面的代码。我们看到了这些方面,但是当试图理解代码时,我们的大部分注意力都放在了理解生成器表达式上。

为了使代码更具可读性,我们可以尝试减少没有描述其本质的代码量。问自己以下问题:每个操作都需要专用的生成器表达式吗?

答案是,当然不是。既然没有,我们可以试着简化代码,例如,去掉所有的生成器表达式,只留下一个。考虑以下代码:

这里,calculate()包含单个元素的操作管道,而get_pipeline_proposed()items iterable 的每个元素创建一个管道生成器——也就是一个生成器管道。之前在get_pipeline_all_calc()中,所有的操作都在一行中调用,这里没有;它们以流水线的形式出现,每一行代表一个操作。我们很容易看到,除了第一个操作之外,每个操作都将前一个操作的输出作为输入。

让我们回到这句话:真正的生成器管道在管道的每一步都使用一个生成器。对于这种方法,我们不能这么说,因为calculate()函数由一个管道组成,然后这个管道在生成器中循环。抛开命名的理论考虑,我认为我们仍然可以称这种方法为生成器管道:这是一种使用生成器评估的管道。

在我看来,get_pipeline_proposed()calculate()函数比我们上面考虑过的任何其他版本都更具可读性,包括get_pipeline_original()。前者比后者需要更少的字符和更短的行——因此,它不会受到视觉混乱的影响。

不过,我们可以在calculate()中使用不同的命名。你觉得下面的版本怎么样?

我觉得是喜好问题。在这个例子中,我会选择带有x的版本,因为它表明我们从每一步得到的仍然是同一个变量,只是经过了处理。但是当每个步骤执行不同的操作时,例如,读取文件、处理文本、运行 NLP 模型,那么这样的命名可以增加可读性。

在这种情况下,使用管道操作符将函数链接起来会产生更可读的代码。我将在另一篇文章中讨论这个问题,因为它值得单独关注。

带地图的发电机管道()

不能用[map()](/does-python-still-need-the-map-function-96787ea1fb05)创建一个生成器管道吗?难道map()不是被创造出来的吗?

的确,我们可以。为此,我们将使用上面定义的calculate()函数,管道创建函数如下:

就是这样!这是生成器管道的另一个版本,用map()函数构建。

标杆管理

代码可读性是一回事;性能是另一个。传统的生成器管道创建的生成器数量与管道中的步骤数量一样多。虽然创建一个生成器在性能方面很便宜,但这仍然意味着要创建和使用几个生成器,而不是一个。当然,提议的版本(使用生成器表达式或map())尽管没有使用那么多的生成器,但使用了相同数量的对象,所以它们的评估与生成器的评估一样昂贵吗?

对于基准测试,我将使用[timeit](https://docs.python.org/3/library/timeit.html)模块。如果你想了解更多关于这个模块的内容,你可以阅读我的文章,从走向数据科学 ,在这篇文章中,我解释了这个包的一些有趣的复杂性。

我们将运行基准测试,以比较以下四种方法的性能:

  • get_pipeline_all_calc():第一个版本,一个接一个地进行所有的计算;
  • get_pipeline_original():传统的生成器管道,其中每个计算都是使用专用的生成器来完成的;
  • get_pipeline_proposed():提出的发电机管道,是对第一个版本的修改;;和
  • get_pipeline_proposed_map():提议的生成器管道,使用map()创建。

基准测试代码太长且重复,无法在此展示,因此您可以在本 GitHub gist 中找到它。你会在这里找到结果。

以下是从基准测试中得出的结论:

  • 四个中最快的是get_pipeline_all_calc(),即创建一个能同时调用所有函数的生成器。这并不奇怪,因为这个版本创建生成器、调用函数和创建对象的开销最低。
  • 最慢的是传统的生成器流水线get_pipeline_original(),它使用的生成器和操作一样多。这也不足为奇,因为这个版本的开销很大,需要创建与管道中的操作一样多的生成器。
  • 使用生成器表达式get_pipeline_proposed()的建议解决方案位于中间。
  • 使用map()get_pipeline_proposed_map()的建议解决方案比get_pipeline_all_calc()慢,但比get_pipeline_proposed()快。
  • 上述结果适用于 iterable 的所有长度,即[100, 1_000, 10_000, 100_000, 1_000_000]。正如本例所示,可以预料 iterable 元素的执行时间实际上是相同的。

然而,请注意,我们分析的是由非常快速的操作组成的流水线。如果其中一个或多个需要很长时间来执行,基准测试将显示这两个方法之间几乎没有区别,因为在calculate()中创建生成器或创建几个对象的开销可以忽略不计。

如果您想检查这一点,将上面定义的double()功能更改如下:

并使用number=1。你将看不出这四种方法之间有什么有意义的区别。这是因为这一次,与操作的执行时间相比,在calculate()中创建生成器和附加对象的开销可以忽略不计。

因此,请记住,只有当管道本身非常快时,考虑性能才有意义。但是这个实验的主要目的是表明所提出的发电机管道结构并不比其他方法慢很多。事实证明这是真的。因此,使用这种方法时,您不必担心性能。此外,如果性能很重要,所提出的解决方案甚至会比传统的发电机管道更快——虽然只是一点点,但仍然如此。

结论

传统的生成器管道由与管道中的操作一样多的生成器组成。生成器管道代码比一次调用所有函数的生成器更容易理解。这就是为什么在两种选择中,生成器管道是首选。

然而,在快速操作的情况下,这种方法比在一个生成器中一次一个地调用所有操作要慢。有趣的是,map()版本比这更快。然而,对于更长的操作,这种差异变得可以忽略不计,对于所有版本,您将得到几乎相同的结果。

在本文中,我已经展示了使用一个生成器的生成器管道确实比其他版本更易读。特别是,这一呼吁:

比下面的生成器管道可读性差:

这两种方法都导致了完全相同的generatorresults。但是它们是不同的,在这种情况下,简洁不会带来可读性。

生成器管道的确可读性更好,但这并不意味着它的可读性不能再提高了。每个操作(步骤)本身就是一个生成器,这有助于我们理解每个步骤中发生了什么。

我不喜欢这段代码的重复和不必要的视觉混乱。这背后的主要原因是生成器表达式中的for循环。乍一看,它们可能很重要,尽管事实是它们并不重要。

还有一点:那些创建过几个生成器管道的人知道,创建它们有时会很棘手;不一定很难编码,但有时很棘手。第一种方法——在一个长链中一个接一个地调用函数——可能是一个更大的挑战,尤其是当您需要在中间添加一个新的操作时。

这也是我写这篇文章的原因。我建议采用一种介于两者之间的方法,将前一种方法的简洁性与后一种方法的可读性结合起来。通过这样做,这种方法避免了不必要的代码片段,这些片段在每一步中都重复出现,尽管它们不做任何必要的事情。在这样的渠道中,我们有几个基本项目:

  • 生成器:事实是我们只有一个 iterable,所以我们应该只有一个生成器;在提议的生成器管道结构中,step1创建了这个生成器;
  • 操作:在上面的例子中,它们由函数表示,每个函数执行一个操作;在我们这里是function1function2function3function4
  • 结果生成器对象:这是你需要得到结果时所求值的对象,但是注意它是在第一步开始的,最后一步仍然使用它。

使用本文中提出的生成器管道结构,我们将得到一个新类型的生成器管道,如下所示:

我在上面提到过编写get_pipeline_all_calc()get_p[ipeline_original)可能会很棘手。这种结构并不棘手。很直白。很清楚。虽然仍然比第一种方法长,但它不会像原始管道那样添加不必要的代码。与其他两个版本相比,我肯定更喜欢这个版本,尽管我确实理解并接受这也是一个偏好问题。

在性能方面,新的生成器管道介于两者之间,其中map()版本比生成器表达式版本性能更好。但是我们必须记住,通常情况下,这并不重要,如果一个管道需要一些计算时间,那么创建多个生成器(就像在原始生成器管道中所做的那样)的开销是可以忽略的。

当然,这篇文章没有提供任何新奇的东西。我们很多人都用过类似的管道,只是不叫它们“发电机管道”;或者不打电话给他们。

由于传统的生成器管道通常被认为是创建内存高效管道的好方法(例如,兰登 2012乌赞 2020卡尔曼 2021 ),本文建议以一种新的方式创建生成器管道,将所有操作包装在一个函数中,并使用该函数创建一个生成器。

这种方法更简单、更高效、可读性更强。总之是比较好。

资源

在不*衡数据上建立可解释模型

原文:https://towardsdatascience.com/building-interpretable-models-on-imbalanced-data-a6ea5ae89bc6

预测电信提供商的客户流失

Christophe Hautier 在 Unsplash 上拍摄的照片

我一直认为,要真正学习数据科学,你需要实践数据科学,我想做这个项目来练习在分类问题中处理不*衡的类。这也是一个开始使用 mlflow 帮助跟踪我的机器学习实验的绝佳机会:它允许我跟踪我使用的不同模型、我训练的参数和我记录的指标。

该项目旨在利用在 Kaggle 1上发现的电信数据来预测客户流失。也就是说,我们希望能够根据我们掌握的客户信息,预测给定客户是否会离开电信提供商。为什么这很有用?嗯,如果我们可以预测哪些顾客会在离开之前离开,那么我们就可以试着做点什么了!例如,我们可以为他们提供特定的优惠,也许我们甚至可以使用该模型为我们提供洞察,为他们提供什么,因为我们将知道,或者至少有一个想法,关于他们为什么离开。

性能与可解释性

在我们开始考虑编写任何代码之前,了解和理解手头的问题/任务是非常重要的。在这种情况下,构建一个像 XGBOOST 这样真正强大的模型有用吗?不,当然不是。我们的目标不是从我们的模型中榨出每一滴性能。我们的目标是了解人们离开的原因,这样我们就可以采取一些措施,努力让他们留下来。在一个理想的世界中,我们会构建一个非常可解释的模型,但是在现实中,我们可能不得不在性能和可解释性之间找到一个折中的办法。一如既往,逻辑回归将是一个良好的开端。

模型评估

我们现在需要决定如何评估我们的模型,以及我们对什么满意。我认为事先决定一个最终目标很重要,否则,很难决定何时停止,挤出那多余的 1%很多时候是不值得的。

由于我们数据的性质,我们的类别可能会非常不*衡,我们感兴趣的情况(客户离开)是少数类别。这使得选择正确的指标变得非常重要。

作者图片

我们感兴趣的指标是精度召回,以及其他与此相关的指标。精确度是正确的正面预测与正面预测总数的比率。召回率是正确的正面预测与数据集中正面预测总数的比率。在我们的案例中,我们试图通过预测哪些客户将离开来留住客户:因此,如果我们错过了将一些客户归类为“流失”,而他们并没有(误报),我们也不会太大惊小怪。如果有的话,这些错误分类可能是客户,如果什么都不改变,他们将很快成为“流失”,因为他们可能位于决策边界的边缘。因此,我们期待最大化召回,因为这将最小化假阴性的数量。

我们还将看一下 F-measure ,因为它提供了一种用一个分数来表达对精确度和召回率的关注的方法——我们不想牺牲精确度来获得 100%的召回率!

型号规格

一旦我们建立了最终模型,我们就可以使用精确回忆曲线来优化我们在正面(少数类)上的表现。在这种情况下,我们将假设我们虚构的电信业务中的利益相关者希望实现 0.80 的召回(即我们正确识别 80%的阳性样本),同时最大化精确度。

数据

  • train.csv 训练集。包含 20 列 4250 行。3652 个样本(85.93%)属于类别流失=否,598 个样本(14.07%)属于类别流失=是。
  • test.csv —测试集。包含 850 行 18 列。

EDA(以及所有建模等。)是在不同的 python 脚本中完成的,我选择在这里不包括它,因为它与我所写的主题无关。尽管如此,这仍然非常重要,你可以在我的 Github 页面上找到整个项目。

由于 state 列的基数很高,我们在对它们进行编码时需要小心,否则我们最终会得到 50 个不同的特性!

df.head()

作者图片

我们可以查看相关矩阵,以了解任何初始的有希望的特征。

作者图片

好吧,这看起来不是很好,但我们可以看到,客户流失与 total_day_minutes、total_day_charge 和 number_customer_service_calls 有一定的相关性。让我们用 total_day_minutes 和 number_customer_service_calls 建立一个简单的基线模型(我们省略 total_day_charge,因为它与 total_day_minutes 密切相关)。

特征工程

我们可以生成一些特性来封装每日总数。我们还将状态特征映射到状态所属的区域,因为这大大降低了特征维数。最后,我们可以将客户流失目标值映射到二进制,因为这是许多模型所需要的。

建模

首先需要做的是将数据分为训练集和验证集,因为这是避免过度拟合、提高可推广性和帮助我们比较潜在模型的重要步骤。在这种情况下,我们使用分层的 K 折叠交叉验证,因为我们的数据集是高度不*衡的,我们希望确保跨折叠的类分布是一致的。

基线模型

我们从构建一个简单的基线模型开始,这样我们就有一些东西可以与我们后来的模型进行比较。在回归场景中,我们可以简单地使用每次预测的目标变量的*均值,但是在我们的分类情况下,我们将使用针对两个最相关的特征训练的逻辑回归模型。

在开始之前,让我们初始化 Mlflow 并编写一个通用评分函数来评估我们的模型。

现在让我们构建基线模型,看看它在每个验证集上的表现如何!

Average F1 = 0.08725760427444854
Recall = 0.04847338935574229
Precision = 0.440932400932401

所以,是的,结果不是很好(事实上他们很糟糕),但这仅仅意味着我们会变得更好。这只是一个基线!我们可以尝试一些事情来改进我们的模型:

  • 我们可以通过过采样和欠采样来*衡我们的类,因为这种不*衡导致我们的模型偏向大多数类。
  • 我们可以训练更多的功能。

SMOTE:对少数民族阶层进行过度采样

不*衡分类的一个问题是,少数类的例子太少,模型无法有效地学习决策边界。解决这个问题的一个方法是在少数民族班级中过度取样。这可以通过简单地复制来自训练数据集中的少数类的示例来实现,尽管这不会向模型提供任何附加信息。复制少数类中的例子的一个改进是综合少数类中的新例子。在本文的中介绍的一种常见技术是 SMOTE。值得注意的是,过采样并不是我们唯一的选择,例如,我们可以通过使用诸如 TOMEK-links 之类的技术来进行欠采样(或者将两者结合起来)。然而,在我们的例子中,最好的性能提升来自单独的 SMOTE。

然而,在我们这样做之前,让我们编写一个通用的特征处理管道,以便为建模准备好数据。我们的函数返回一个 Sklearn 管道对象,我们可以用它来拟合和转换我们的数据。它首先将数据分为数字、分类特征和二进制特征,因为我们对每一种特征都进行了不同的处理。分类特征使用一键编码进行编码,而二进制特征则保持不变。最后,对数字要素的缺失值进行估算。也尝试了缩放数字特征,但是没有带来性能的提高。这也符合我们的最大利益,不要缩放这些数据,因为这会增加解释结果的难度。

下面写了一个通用的训练函数,注意我们可以选择是否要使用 SMOTE。SMOTE 中的采样策略控制着我们对少数类重新采样的程度,这是我们以后可以调整的。

在我们使用 SMOTE 之前,让我们针对所有特性训练一个逻辑回归模型,以尝试获得一个新的基线。

Average F1 = 0.3066722566642678
Recall = 0.2107563025210084
Precision = 0.5720696669209255

结果肯定比以前好,但仍然不是很好。我们已经等得够久了,让我们试试用 SMOTE 吧!我们将使用 SMOTE 对我们的流失数据点进行过采样,以便我们最终获得相等的类别分布。

一个重要的附带说明:当使用 SMOTE 时,我们需要在没有被过采样的验证集上评估性能。否则,我们将无法获得真正的绩效衡量。

train_and_eval(df, model_name="lr_all_features_smote")Average F1 = 0.49789052753778434
        Recall = 0.6906862745098039
        Precision = 0.3894759793369957

哇!我们把 F1 的分数从 0.31 提高到了 0.50,召回从 0.21 提高到了 0.69!值得注意的是,精度几乎保持不变。这是为什么呢?那么,什么是精密测量呢?从数学上来说,精度是真阳性的数量除以真阳性的数量加上假阳性的数量。它告诉我们,当试图预测阳性样本时,我们的模型在 47%的情况下是正确的。因此,通过过采样,我们减少了假阴性的数量,但也增加了假阳性的数量。这是可以的,因为我们决定支持假阳性而不是假阴性。看到这种变化的一个直观方法是查看一个混淆矩阵

作者图片

我们可以看到 TP 号从 33 到 90,FN 号从 86 到 29,太棒了!然而,结果是,我们看到 FP 数从 21 变成了 158。但是,如前所述,我们对此没有意见,因为我们更关心找出哪些客户会离开。一个小的旁注:FP 和 FN 比率可以使用概率阈值来调整,比较这两个模型的最简单的方法是比较 F1 分数。

特征选择

我们可以训练一个更复杂的模型,然后用它来选择特征。具体来说,我们训练一个随机森林分类器,然后使用 SHAP 值来选择最有希望的特征。缩小特征空间有助于减少维数和概化,同时也使解释结果更容易。这是双赢!

shap.summary_plot(shap_values, features=X, feature_names=feature_names, plot_type='bar')

作者图片

我们现在可以定义一个新的管道进行预处理,并且只选择我们需要的特征。

让我们看看我们的逻辑回归模型如何处理我们缩小范围的特征!

train_and_eval(df, model_name="lr_selected_features_smote")Average F1 = 0.5025520964212148
        Recall = 0.6940336134453782
        Precision = 0.3940789241235847

太棒了,我们删除了一些功能,性能略有提高。这向我们证实了其他功能并不重要。

但是我们能比逻辑回归做得更好吗?

在这里全力以赴训练一个 XGBOOST 模型是很容易的,但是记住这是而不是我们的目标。我们的目标是建立一个可解释的模型,我们可以用它来试图留住顾客。和逻辑回归一样,决策树分类器非常容易解释。让我们看看进展如何。

from sklearn.tree import DecisionTreeClassifier
train_and_eval(df, model=DecisionTreeClassifier(), model_name="dt_selected_features_smote")Average F1 = 0.8152920749388362
        Recall = 0.8461484593837536
        Precision = 0.786946006985776

它做得很好!我们需要非常小心,尽管决策树过度拟合就像没有明天一样。让我们继续调整这两个模型上的超参数,看看我们是否可以进一步优化。我们将使用 Optuna ,因为我喜欢它的简单和快速。

超参数调谐

这里我们需要非常小心,决策树很容易过度拟合,这就是为什么随机森林模型通常是首选。随机森林能够以更好的方式对数据进行概化,因为随机特征选择充当了一种正则化形式。正如前面所讨论的,在我们的例子中,我们更关心可解释性而不是性能。现在,尽管交叉验证对于观察模型如何泛化很有用,但它不一定能防止过度拟合,因为我们最终只会过度拟合验证集。

过度拟合的一个衡量标准是训练分数远高于测试分数。我最初尝试将 Optuna 试验中的目标函数设置为交叉验证的验证分数,但这仍然会导致过度拟合,因为 DTs 没有太多的正则化。

另一种可能性,这种情况下工作出色,是衡量交叉验证的训练分数和验证分数之间的差异与验证分数本身。例如,对于 F1 分数,一个可能的目标函数是

作者图片

在这种情况下,验证和训练之间的差异的 RMSE 的权重比验证 F1 分数小四倍。优化该函数迫使训练和有效分数保持接*,同时也最大化验证分数。

def train_valid_f1_score(mean_train, mean_test):
    '''
    RMSE of the difference between testing and training is weighted four times less than the test accuracy 
    '''
    return np.sqrt((mean_test - mean_train)**2) + 4 * (1 - mean_test)

这给了我们以下结果:

  • 逻辑回归最佳超参数:
  • 决策树最佳超参数:{'max_depth': 7,' min_samples_leaf': 4,' min_samples_split': 10,' criterion': 'gini'},
  • SMOTE 采样策略LR 为 0.70,DT 为 0.56

让我们用这些参数训练一些模型,看看我们得到了什么!

params = {'solver' : 'liblinear', 'penalty' : 'l2', 'C' : 1.14, 'tol' : 0.0002, 'max_iter' : 150}
train(df, LogisticRegression, params, 0.70)Average training F1 score:   0.5009518013999269
Average validation F1 score: 0.5034031228088905
Overfit score:               1.9888388301734015params = {'max_depth': 7, 'min_samples_leaf': 4, 'min_samples_split': 10, 'criterion': 'gini'}
train(df, DecisionTreeClassifier, params, 0.70)Average training F1 score:   0.9153416502877135
Average validation F1 score: 0.8928098706544405
Overfit score:               0.451292297015511

虽然这个模型看起来很棒,而且没有过度拟合的迹象,但我们还是会选择下面这个最大深度较低的模型。我们的目标是可解释性,深度 7 并不能真正给我们带来可解释性。所以我们为了可解释性牺牲了一点准确性。

params = {'max_depth': 4, 'min_samples_leaf': 18, 'min_samples_split': 17, 'criterion': 'entropy'}
train(df, DecisionTreeClassifier, params, 0.78)Average training F1 score:   0.8217456929425225
Average validation F1 score: 0.816103270284813
Overfit score:               0.7412293415184575with mlflow.start_run(run_name="dt_tuned") as mlops_run:
        mlflow.log_metric("F1", 0.8161)

太好了,所以我们现在有几个潜在的模型。我们将继续使用决策树模型,因为逻辑回归模型不太符合要求。

修剪决策树

虽然我们的模型似乎没有过度拟合,但我们仍然要修剪决策树,因为它有助于我们去除没有太多预测能力的子节点。我们这样做是希望这有助于我们的模型更好地推广。大多数正则化带来的额外好处是,它还有助于提高模型的可解释性。

我们可以通过选择正确的成本复杂性参数来修剪我们的树。我们将开始使用我们选择的超参数在整个数据集上训练模型,以找到我们的 α 的空间——成本复杂性参数。

# train full model 
params = {'max_depth': 4, 'min_samples_leaf': 18, 'min_samples_split': 17, 'criterion': 'entropy'}
clf, features = train_full(df=df, model=DecisionTreeClassifier, params=params)# get cost complexity alphas from model
ccp_alphas = clf.cost_complexity_pruning_path(x_train, y_train)['ccp_alphas']

然后,我们可以通过交叉验证的方式对这些 α 中的每一个进行评分,以找到要选择的最佳复杂度。

{'0.0':                   0.816103270284813,
 '0.0005789325156238256': 0.816103270284813,
 '0.0005930920192462885': 0.816103270284813,
 '0.0009867859833913376': 0.816103270284813,
 '0.0011725425508943704': 0.816103270284813,
 '0.0019927001338321468': 0.816103270284813,
 '0.003228215942898363':  0.816103270284813,
 '0.0041894808849862325': 0.816103270284813,
 '0.007194356040110275':  0.8142671700825213,
 '0.00811411007422369':   0.8147206576154481,
 '0.023868223077443323':  0.7667293021998818,
 '0.06419408989742087':   0.6540410407393117}

我们可以看到值 α = 0.00811411 是可以选择的最佳复杂度。一般来说,随着 α 增加,节点数量和深度减少。因此,我们选择最高的 α 值,该值仍然具有良好的 F1 *均得分。

我们现在可以训练我们的最终模型了!

params = {'max_depth': 4, 'min_samples_leaf': 18, 'min_samples_split': 17, 'criterion': 'entropy', 'ccp_alpha': 0.00811411}
clf, features = train_full(df=df, model=DecisionTreeClassifier, params=params)

它在测试数据上表现如何

Average F1 = 0.7772925764192139
Recall = 0.6592592592592592
Precision = 0.9468085106382979with mlflow.start_run(run_name="dt_final_test_data") as mlops_run:
        mlflow.log_metric("F1", 0.777)
        mlflow.log_metric("Recall", 0.659)
        mlflow.log_metric("Preision", 0.947)

该模型在测试数据上表现良好!我们可以看到,精确度比召回率高得多,但是这是可以通过改变预测概率阈值来调整的。在我们的例子中,我们试图在最大化精确度的同时达到 80%的召回率。

选择最佳概率阈值

我们现在可以调整概率阈值来尝试和优化我们的精确召回权衡。让我们绘制一条精确召回曲线,并找出实现 80%召回率的最佳阈值。

作者图片

0.4263565891472868

如果我们在我们的测试集上再次预测,那么希望我们会看到更接*我们期望的回忆!是的,我知道我犯了两次使用测试集的大罪,但这是为了演示的目的。测试集通常是“一劳永逸”的情况。

Average F1 = 0.7958477508650519
Recall = 0.8518518518518519
Precision = 0.7467532467532467

厉害!我们已经做了我们想做的事情,事情进展顺利!很高兴看到测试分数与我们之前的分数如此相似,因为这表明我们没有过度拟合我们的模型。让我们在 MLFLOW 上记录这个模型。

MLFLOW 实验会话

作者图片

哎呀,我把 precision 拼错了…哦,好吧。

模型口译

基于树的模型根据要素中的特定临界值多次分割数据。通过拆分,创建数据集的不同子集,其中每个实例属于某个子集。为了预测每个叶节点中的结果,使用该节点中训练数据的*均结果。

决策树的解释非常简单:从根节点开始,到下一个节点,边告诉你你在看哪些子集。一旦到达叶节点,该节点会告诉您预测的结果。所有的边都用 AND 连接。

因此,我们可以预测的一般方式是:如果特征 x 比阈值 c (/大)……那么预测的结果是该节点中实例的目标的最常见值。

让我们画出这棵树,看看我们能推断出什么!

作者图片

树中的每个节点都会给出一个条件,下面左边的节点为真,右边的节点为假。第一次拆分是使用全天分钟功能执行的,该功能计算一天中所有呼叫的总分钟数。例如,我们可以看到,如果总分钟数小于 71,我们沿着树向左走,如果分钟数大于 71,我们向右走。

来自树的每个预测是通过沿着树向下直到找到根节点来进行的。例如,如果客户一天的总通话时间少于 71 分钟,并且他们的总费用在 0.04 到 1 分钟之间,那么我们会预测他们会流失。

我们可以看到,电信提供商的收费似乎是客户之间的一个重要区别因素,这一点在前面的 SHAP 特征重要性图中得到了证实。通过左边的树,我们可以看到,日费用高但日分钟数低的客户更倾向于流失而不是停留。然而,如果一天的费用少于 3 分钟,无论多少分钟,顾客都倾向于留下来!对此的一个可能的解释是,客户使用的移动计划并不完全符合他们的需求,但这需要进一步调查。

另一个有趣的发现是,如果一个客户一天的总通话时间很长(> 71 分钟),并且他们不和客服通话(< 0.965 次通话,即没有通话),那么他们比那些和客服通话的客户更有可能流失。同样,这将需要进一步的调查,以得出结论,为什么这是真的。

与大多数数据问题一样,这通常会导致更多的问题需要回答!

结论

我们已经建立了一个可解释的机器学习模型,可以识别出可能流失的客户,我们希望召回 80%(我们实际上在测试集上达到了 85%)和 75%的精确度。也就是说,我们正确识别了 85%的流失客户,75%的流失预测是准确的。使用这一模型,我们可以了解促使客户离开的关键因素,并希望我们可以利用这一模型来努力留住更多的客户。

感谢阅读,我希望你喜欢它。如果您有任何意见或问题,请随时发表评论或通过电子邮件联系我们。

如果你做到了这一步,这里有一些你可能感兴趣的我的其他故事…

**

[1] Kaggle 练习数据集。(2020;4 月)2020 年客户流失预测,1。2022 年 01 月 04 日检索自https://www . ka ggle . com/c/customer-churn-prediction-2020/data。**

更快地构建机器学习模型

原文:https://towardsdatascience.com/building-machine-learning-models-faster-e9fa52ad701

使用 GuildAI 控制机器学习模型

GuildAI(来源:作者)

创建机器学习模型是一项简单的任务,因为有不同的 Python 库可以轻松地帮助构建任何类型的 ML 或 AI 模型,但这只是最初的一步,因为我们需要分析结果,调整 hypermeters,通过构建管道和跟踪模型性能来加速模型开发。

如果你想建立一个可持续的高性能模型,所有这些步骤都非常重要。要实现所有这些步骤的自动化,有不同的 Python 库,但这需要时间和大量的工作,同样地,手动完成会花费更多的时间。

如果我告诉你,所有这些步骤都可以用一个单独的库自动完成,而且非常轻松省时,会怎么样?使用单个库,您可以:

  1. 追踪你的模特的训练
  2. 分析性能
  3. 调整超参数
  4. 自动化管道
  5. 云上的培训和备份等。

GuildAI 是一个开源的 python 库,它不仅有助于创建机器学习模型,还能满足随后的其他需求,正如我们上面提到的。它是一个基于 GUI 的交互式仪表板,允许与 ML 模型相关的不同功能。

在本文中,我们将使用 GuildAI 来跟踪模型性能。

让我们开始吧…

安装所需的库

我们将从使用 pip 安装 GuildAI 开始。下面给出的命令可以做到这一点。

pip install guildai

创建模型

对于 GuildAI,我们需要创建一个模型,并将其保存在一个. py 文件中,我们将使用 GuildAI 呈现该文件,以在一个独立的仪表板中分析该模型。将该文件命名为“train.py”。

让我们创建模型,

import numpy as np x = 0.1
noise = 0.1 loss = (np.sin(5 * x) * (1 - np.tanh(x ** 2)) + np.random.randn() * noise)print("loss: %f" % loss)

正如你在这里看到的,我们已经创建了一个基本模型来理解 GuildAI 是如何工作的。

GuildAI 仪表板

现在,我们需要遵循某些步骤来使用 GuildAI,使用新的 Jupyter 笔记本,我们将创建一个新目录来存储我们的 train.py 文件。

!mkdir guild-start

在这之后,我们将移动到公会开始目录。

!cd guild-start

由于我们已经将模型存储在这个目录中,现在我们可以使用 train.py 文件在这个目录中直接运行 GuildAI。下面给出的命令可以做到这一点。

!guild run train.py

为了查看仪表板,我们需要使用 view 命令。

!guild view

GuildAI(来源:作者)

正如你在这里看到的,这是 GuildAI 仪表板的主页。在这里,我们可以清楚地分析模型及其属性,如损耗、噪声等。这个模型只运行过一次,所以我们没有什么可以比较的,但是你可以运行多次实验来进行比较。

继续尝试使用不同的模型,并创建 GuildAI 仪表板来解释模型和比较实验。如果您发现任何困难,请在回复部分告诉我。

本文是与 Piyush Ingale 合作完成的。

在你走之前

感谢 的阅读!如果你想与我取得联系,请随时通过 hmix13@gmail.com 联系我或我的 LinkedIn 个人资料 。可以查看我的Github*简介针对不同的数据科学项目和包教程。还有,随意探索* 我的简介 ,阅读我写过的与数据科学相关的不同文章。

在云中构建机器学习模型:一种范式转变

原文:https://towardsdatascience.com/building-machine-learning-models-in-the-cloud-a-paradigm-shift-ff7dbbc39312

区分用于机器学习开发的持久计算和短暂计算

照片由Pero KalimeroUnsplash

2017 年,我在云中运行了我的第一个机器学习(ML)模型,然而,当时我并没有意识到这一点。从我的角度来看,我只是连接到这个叫做“远程服务器”的东西,并执行我的 python 脚本。

今天,在云中构建、培训和部署 ML 模型正在不断地商品化。从用于强化培训的可扩展计算、实时推理、打包为 API 的 ML 模型,到托管 ML *台、低代码/无代码 ML 解决方案、AutoML,不胜枚举。

在这篇文章中,我想分享一些关于在云中构建机器学习模型如何导致这些工作负载的底层计算基础设施方面的范式转变的想法。

更具体地说,我将关注在构建阶段开发人员体验中的变化;数据探索、模型构建和模型调整(即“我们如何构建机器学习模型”),同时略微涉及部署阶段。我将有意保持一种与工具无关的方法,较少关注使用哪种技术堆栈或云服务,而更多关注概念本身。

为什么会有范式转变?

*一方面,组织内部对机器学习的需求增加了。项目的数量增加了,数据科学团队的规模也变大了。这导致团队需要更多的计算能力来满足其用例的需求。

另一方面,访问云降低了经济高效和可扩展计算的门槛。组织能够:*

  1. 从固定支出(即预先购买服务器)转变为可变支出(即随用随付定价模式)。
  2. 降低运营开销。
  3. 停止猜测数据科学团队所需的计算能力。

这导致了云中 ML 工作负载数量的增加。数据科学团队被用来在一个地方运行一切;持久的环境。而云提供了各种各样的服务,每种服务都有其独特的优势:全天候运行的托管持久远程服务器、仅在脚本执行期间运行并收费的临时作业、基于无服务器的服务等。

当我们迁移到云时,大多数团队都在一个地方运行所有的东西;持久的基于云的环境。当在云中运行 ML 工作负载时,没有为正确的工作使用正确的工具会导致反模式,这对许多团队来说是一个未开发的潜力,并且需要范式转换。

旅程

第一章:我们的起点

最初,数据科学团队只能使用他们的笔记本电脑进行有限的计算,并将在本地开发一切。对于幸运的人来说,他们将获得第二台强大的机器,为他们提供更多的 RAM/CPU,在某些情况下还会有一两个 GPU。这使得数据科学家能够超越小型和大多数表格数据集,并利用深度学习或 AutoML 等技术处理计算密集型用例。

作者图片

第二章:我不能再在我的电脑上运行了

*随着团队开始触及其物理基础设施的极限,他们开始过渡到远程服务器。这主要是出于对更大容量的需求,但也允许团队内部资源的“共同化”。当您的同事不运行他们的密集计算时,您可以利用可用的容量,从而降低成本并提高团队的迭代速度。

一般这些远程服务器都是由 IT 部门提供的。你必须确定团队所需的容量,太高的话,你要为你不使用的东西付费,太低的话,你就有再次遇到容量问题的风险。此外,请求或购买内部部署的服务器、设置它们并持续管理它们需要时间和精力(几周到几个月)。*

作者图片

第三章:向云迁移

然后云来了。团队现在能够访问由云提供商管理的远程服务器。如果团队变得更大,您只需点击几下鼠标就可以增加服务器的大小,如果没有人使用它们,您只需减少它们的容量,甚至关闭它们。在某些情况下,您甚至可以为不同的工作负载和团队创建不同的服务器,实现团队和用例之间的“共同化”和隔离的混合。此外,云计算可以轻松访问各种工作负载的各种计算实例,从 CPU 密集型到其他具有数百 GB RAM 和/或多个 GPU 的工作负载。

作者图片

然而,团队很快意识到(遗憾的是不是全部),当在小数据集上测试、重构代码或简单地编写文档时,他们并不总是缩减到最小容量。想象一下,购买一台拥有 4 个 GPU 和超过 200GB 内存的机器来完成这些低计算量的任务。除了潜在的高成本之外,在弹性计算的背景下,这似乎不是正确的做法。实例用法的典型示例如下:

作者图片

早期采用者实施的第一个防护是这些远程服务器的自动关闭和启动(即,在工作时间之外,当 CPU/GPU 空闲时)。这样做的代价是会丢失 RAM 中保存的任何内容,并且在需要时会等待几分钟让服务器重新联机。

尽管如此,当您处理代码、构建和测试模型时,不得不不时重启您的工作环境可能会很不方便。

第四章:短暂计算

当你看前面的图表时,它提出了一个问题:如果我们将低强度和高强度的计算任务分离会怎么样?如果我们结合使用持续运行的经济高效的计算实例和仅在脚本执行期间运行的强大计算实例,会怎么样?

数据科学团队将能够两全其美。保持他们的工作环境运行,并在需要时,在特定时间内将他们代码库的繁重执行任务委托给其他实例。这可以通过短暂计算来实现:

作者图片

*有趣的是,**大多数数据科学团队并不是这样开始使用短暂计算的。通常,他们会在将机器学习项目投入生产的过程中偶然发现它。在此过程中,您将代码打包到容器中,自动执行训练、推理等步骤。为了让事情变得更加稳健和自动化,下一步自然是“在云中创造就业”。这基本上意味着您现在能够在可扩展的计算上运行您的容器化代码,同时云提供商自动配置和关闭所需的基础架构(即短暂计算)。

然而,你可以在开发阶段利用同一个委托人。***

第五章:范式转变——在开发过程中使用短暂计算

尽管现在这种情况仍然很少见,但是一些团队也注意到了在开发阶段使用短暂计算的潜力。这是结合稳定性、规模和成本效益的一个很好的方式。下图说明了一个数据科学家团队如何设置他们的环境,该团队运行具有各种计算需求的各种工作负载:

作者图片

*左边的持久化实例提供了一个稳定的环境,可以在其中编写代码、构建初始模型,以及执行任何不需要密集计算的任务。考虑到它们的成本效率,我们可以创建多个实例来确保大型团队中工作空间的隔离。右侧的短暂作业通过云服务提供的 API 启动。除了主要的代码逻辑,我们还需要以某种方式打包代码,并指定服务配置(基于您使用的服务)。这里的关键区别在于,您的整个代码逻辑现在都运行在由服务启动的临时作业上,而不是运行在发出 API 调用的实例上。

此外,如果您的持久化实例托管在云中,您可以轻松地对其进行扩展(添加更多 RAM、CPU 或 GPU)。这允许您在目标基础设施上快速测试您的代码,而不需要太多的努力。但是,应该是例外,而不是常态。否则,你会回到第三章。*

对数据科学家开发体验的影响

在开发阶段使用混合工作环境(即持久的和短暂的)有以下优点:

  1. 您只需为短暂实例的执行时间付费(例如,1 小时 34 分钟、10 分钟等。).不再将这些宝贵的资源浪费在低计算任务上。
  2. 您的开发环境不会受到您启动的繁重计算任务的影响。你的内核/运行时没有不堪重负,可以继续开发。
  3. 您不局限于持久性实例的 RAM/CPU/GPU。您可以使用不同的数据集、超参数和算法并行启动许多训练变体,从而加快您的实验周期。

另一方面,您需要考虑以下缺点:

  1. 您将对您的代码库进行更改,以确保它在上述服务中运行:容器化、文件夹结构、数据访问方式、超参数传递方式等。
  2. 你的互动少了。由于您的脚本现在是远程执行的,所以很难像在持久化实例中那样进行调试(逐行执行或者使用 IDE 的调试器)。

根据哪些元素对您的组织最重要,您可能更倾向于持久或短暂的计算。基于我迄今为止与 AI/ML 从业者(数据科学家、ML 工程师等)的互动。),混合工作环境的使用是关键。它有助于减少业务的上市时间,同时也为团队提供了一个无摩擦和体面的开发人员体验。

结束语

对于 AI/ML 从业者和 C 级决策者来说,同样重要的是要记住,在云中构建机器学习不仅是为了获得更大、更强大的实例,而且是为了我们如何使用它们。

更重要的是,我们如何实验、构建和调优模型的过程中引入了变化:使用持久和短暂计算的混合环境,而不是在相同的持久环境中运行一切。

感谢阅读!如果您有任何问题,请随时通过 Twitter(@ohamzaoui 1)或 Linkedin(hamzaouiothmane)联系我们。

构建节省内存的元混合推荐引擎:从后到前(第 2 部分)

原文:https://towardsdatascience.com/building-memory-efficient-meta-hybrid-recommender-engine-back-to-front-part-2-51a7d4546e90

作者图片

系列概述

的前一部分中,我们回顾了基于记忆的推荐系统的机制,并构建了一个定制的协同过滤推荐器。今天,我们将应用来自流行 Python 模块的“开箱即用”推荐器,评估它们的效率,并尝试一些技术来无缝改进被称为网飞奖获得者的 SVD 预测算法。在 RBC Group ,我们乐于分享我们在设计所谓的元混合推荐引擎以解决实际业务问题方面的经验。

你能推荐更多吗?

值得提醒的是,基于记忆的推荐器的魔力就在几步之内:

  • 准备和缩放用户-项目交互的向量(所谓的特征向量);
  • 计算用户特征向量之间的距离(所谓的“相似度”矩阵);
  • 考虑所选预测算法的补充规则和限制,将“相似性”矩阵乘以可用评级矩阵。

这表明协同过滤推荐器的准确度改进包括:

  • 调整“用户的邻域”,即考虑更相似用户的评级;
  • 过滤评级矩阵,即考虑与用户先前评级的项目相似的项目;
  • 或者两者都有!

这些改进将把您的“纯”基于记忆的推荐器转变为混合推荐器,这意味着不可避免地失去了模型的简单性和对外部数据源的需求(与评分矩阵相关),例如来自用户简档、在线会话和/或项目的文本和视觉内容的数据。

RBC Group ,我们开发并成功采用了一种有点特殊的技术,以保持 recsys 复杂性、可解释性和资源效率之间的*衡,这表明我们的发现是一种元混合推荐器

该技术限于 (1) 基于项目的描述性特征(也称为元数据)对项目进行初步聚类,以及 (2) 利用基于用户的协同过滤推荐器对聚类方式的原子化评级矩阵进行后续处理(完成)。

在这种情况下,每个用户的邻域被有效地调整,因为从相同聚类的项目的评级构建特征部分向量。这种部分向量具有较小的差异,因为期望用户以相同的方式对相似的项目(在一个聚类内)进行评级。因此,部分向量倾向于更好地反映用户的实际偏好/厌恶。

此外,所提供的技术在拟合模型和进行预测时大大减少了内存消耗。这就是为什么我们将这种元混合推荐器描述为内存高效的

正如在本系列出版物中早先的,我们将继续使用来自 MovieLens 的五星评级的“ml-latest-small”数据集和自由文本标记活动来展示所有这些功能。我们将使用所有这些数据对数据集进行初始聚类。也就是电影。

首先,让我们将类型信息转换成虚拟变量:

作为预测因素之一,我们还提取了电影上映的年份,并使用某种频率编码技术对其进行了转换。

让我们进一步从标签中提取和设计一些有用的预测器。将标签列表与标题文本结合后,我们可以利用该组合来构建 TD-IDF 向量:

包括降维在内的所有主要预处理步骤都包含在一个管道中:

在应用简单的 k-均值聚类之前,我们已经确定了最佳聚类数:

让我们给集群分配任意标签,看看电影的分布情况:

看起来现在我们已经准备好进行我们的主要实验了。因此,我们将 SVD 算法(来自 Python surprise 模块)应用于(1)原始数据,然后应用于(2)循环中聚类数据的每个片段。每次我们都会测量精度和召回率的分布来比较结果:

元混合推荐器的一些突出表现是明显的。此外,如前所述,这种方法也是内存高效的:

为了更深入地理解这个例子中推荐引擎内部发生了什么——您应该浏览整个代码

外卖笔记

因此,我们的主要信息很简单,但却很强大:当你开始把(协同过滤)推荐引擎想象成一种算法,它基于用户 A 与已经体验过与项目 X 交互的其他用户的相似度来以某种方式估计用户 A 与未知项目 X 的交互(也称为项目的评级)——你会立即意识到这种估计的准确性应该在多大程度上取决于测量用户之间相似度的准确性。提高测量这种相似性的准确性的一种方法(超出了纯数学方法的范围)是通过将您要比较的用户集合限制为他们共享共同特征的一些高级子集(元聚类)。如果你喜欢对你的项目集进行(元)聚类,并反复运行你的推荐引擎,情况也是如此。这种“幼稚”的方法可以显著提高任何“开箱即用”推荐算法的整体效率。

构建无用的 ML 模型

原文:https://towardsdatascience.com/building-ml-models-that-are-useless-ba70df6957d

来自医疗保健行业的教训

图为:可能是机器学习模型。看起来像是在医院的某个地方运行,对吗??在 Unsplash 上由 Carlos Muza 拍摄的照片

所以你建立了一个 ML 模型。不错!这真的有用吗?不仅仅是在 Kaggle 排行榜的意义上,而是在字面上赌上性命的意义上?这两者之间的差异在 2021 年《自然》杂志的一篇论文中得到充分证明[ 1 ],该论文显示,在最*发表的所有新冠肺炎诊断人工智能模型中,没有一个模型能够强有力地推广到它们被训练的数据集之外的数据集。换句话说,尽管他们的排行榜分数*乎完美,但如果他们中的任何一个人真的被部署到医院,那就太糟糕了。

这个问题对我来说很*,因为我在医疗保健行业工作,并在 2020 年以这个身份合作开发了一个这样的新冠肺炎诊断人工智能模型。我们从未将我们的模型用于临床部署,因为后来已经开发出了更简单的测试,不需要对每个进入诊所的人进行 X 射线检查,但花在该模型上的大部分开发时间都花在了寻找避免《自然》杂志论文中强调的那种泛化失败的方法上。

图为:这不是确定谁感染了新冠肺炎的最简单的方法。照片由国家癌症研究所Unsplash 上拍摄

那么你如何知道你的模型是否真的有效,或者你的模型只是看起来有效?[ 1 中的作者认为,为了真正验证一个模型,可解释的人工智能(XAI)技术是必要的。我同意这种观点,尽管常见的 XAI 技术实际上可能会有它们自己的一套陷阱[ 2 ][ 3 ],值得单独发布。不过,在这篇文章中,我想关注一些更基本的东西:

如果你想知道你的模型是否真的有效,你需要在(多个)完全不同于你训练它的数据集上测试它。

这并不意味着只是将你的数据集分成 20%用于定期评估,10%用于最终测试。我的建议是找到一组独立收集的样本,并使用它们作为第二(或第三或第四)数据集来验证你的模型。显然,这说起来容易做起来难——即使是在拥有大量可支配资源的工业规模的公司中。除了定位这些额外数据的明显问题之外,您还必须维护多个数据管道来管理所有这些数据(因为这些不同的数据集可能需要不同的预处理/规范化步骤,以便为您的模型准备好输入)。

这个问题的数据管理部分至少可以通过一些特定的工具变得更容易,所以在这篇文章的剩余部分,让我们通过几个代码示例来完成这个任务,这些代码示例也说明了在考虑可推广性的情况下进行开发时可以获得的一些意想不到的好处。

图为:从各方面考虑,这是一个相当不错的数字。马塞尔·埃伯勒在 Unsplash 上的照片

我们来做一个对数字进行分类的神经网络。为什么?因为有很多关于这个的数据集,所以说明主要观点应该很容易。我们将使用 FastEstimator (FE)框架,因为它可以与 TensorFlowPyTorch 一起工作,我不知道你会怎么做。还因为我帮助编写了 FE,试图使这个和其他工业风格的用例更容易。

传统 ML 工作流程的关键步骤时间到了(无需遵循我们的新黄金法则):

  1. 查找您的数据集。让我们用 USPS [ 4 ]吧,因为里面有一些数字。
  2. 设计你的模型。这不是今天的重点,我们就用 ResNet9 [ 5 ]吧。
  3. 训练你的模型。
  4. 发表一篇关于你开创性成果的论文。

为了节省时间,下面是步骤 1-3 的代码:

这里还没有什么特别的

现在我们只需要运行我们的培训,看看我们做得如何:

USPS 验证性能(马修斯相关系数)与时间的关系。*均值+-标准差。Dev 跑了 5 圈。(图片由作者提供)

看起来我们做得不算太差,最终*均测试成绩为 0.968。不过,让我们暂停一会儿,在这里讨论两件快速的事情。首先,你总是进行多次试验来得到标准偏差,对吗?第二,您不再使用准确性来衡量您的分类性能,对吗??由于马修斯相关系数(MCC)被犯罪性地低估了,让我们强调一下这一点,让任何人都可以略读一下。

如果您使用准确性或 F1 分数来验证您的模型,您应该使用 MCC。

在这种情况下,我们的测试数据集具有*衡的类别分布,因此我们的 MCC 和准确性值基本相同(0.968 比 0.971)。尽管如此,养成良好的习惯是很重要的,因为真实的数据集基本上从来都不是*衡的,尤其是在医疗保健领域。当你 99%的数据来自健康患者,而你的模型有着惊人的 99%的准确率时,MCC 会让你避免意外愚弄自己。

不管怎样,现在我们准备好第四步了,对吗?不完全是。让我们应用前面的第一条黄金法则,在一些完全不同的数据集上测试我们的模型,以防万一。MNIST [ 7 和 Scikit-Learn(以下简称 SKL) [ 8 提供的 digits 数据集怎么样?我听说那些里面有一些数字…铁会让事情变得相对干净:

几行额外的努力可以走很长的路

请注意,添加这些额外的数据集只需要对管道进行很小的更改,就可以将所有数据加载到相同的大小,而像 MinMax 这样的共享转换可以保持原样。您选择使用的任何指标都将针对每个数据集单独进行自动计算,以及跨所有数据集进行计算。现在,让我们看看我们的模型表现得有多好,因为我们已经有了一些额外的数字数据集:

具有多个数据集的训练概要。*均值+-标准差。Dev 跑了 5 圈。(图片由作者提供)

正如你所看到的,尽管我们的网络在 USPS 数据集上表现很好(0.97),但在 MNIST (0.78)和 SKL (0.66)上表现很差。当我们将所有数据集放在一起考虑时,我们的 MCC 仅为 0.80 左右。对于那些还不熟悉 MCC 的人来说,这里的精度几乎相同:分别为 0.97、0.79、0.67 和 0.80(MCC 和精度上限都是 1.0,但 MCC 往往会在您偏离完美性能时更快地惩罚您)。在任何情况下,尽管之前我们可能已经得出结论,我们的网络很好地理解了数字,但我们现在知道它实际上只理解 USPS 风格的数字。这绝对不是我们想要实现的目标。

这就产生了问题。埃文·丹尼斯在 Unsplash 上的照片

那我们该怎么办?我们的模型被证明是相当无用的。也许 80%听起来不太糟糕,但是如果邮政服务使用我们的模型解析 5 位邮政编码,只有 33%的邮件会到达它应该去的地方。除非我们成立一家失败即服务公司,否则我们必须做出一些改进。

一种选择是在我们的 MNIST 和 SKL 数据集上进行训练。虽然这是提高这些特定数据集性能的一种简单方法,但我们将不得不寻找更多的数字数据集来充分测试我们的概化性能。那听起来不太令人兴奋,所以让我们试试别的。

早在 NeurIPS 2019 年,美国陆军的研究人员发表了一篇论文[ 9 ],提出了神经网络 softmax 层的替代方案。他们没有使用带有一个 hot 类编码的 softmax 层,而是表明神经网络可以通过简单地切换到带有使用 hadamard 标签编码的类的 tanh 输出层来更加鲁棒地抵抗敌对攻击(尽管强攻击仍然可以打破这种防御[ 10 ])。这个的细节超级有趣,所以我强烈建议你去看看这篇论文。虽然这篇文章不是他们论文的总结,但我只是好奇这种方法是否也能帮助我们提高泛化性能(相对健壮的模型已经被证明具有更多可解释的特征图[ 10 ],这反过来可能会导致更好的泛化)。为了测试它,我们只需要对最终的网络层做一点小小的修改,并添加一个额外的管道预处理步骤,将我们的类标签转换为 hadamard 代码表示。

在这里,我在 TensorFlow 模型的顶部缝合更改,但您可以使用 PyTorch 代替。FE 处理两者。

虽然作者没有宣传这是一种提高模型泛化的方法,但是让我们看看我们是如何做到的:

专业提示:拥有多个图表是给管理层留下深刻印象的好方法。(图片由作者提供)

看起来我们已经有了一些明确的要点:

  1. 我们的模型对邮政服务来说技术上还是没用的 (点一下就知道标题了)
  2. 如果我们只考虑 USPS 的性能,我们不清楚 hadamard 码的引入是否带来了任何改进
  3. 当将所有数据集放在一起考虑时,引入 hadamard 码实际上确实显著改善了我们的最佳 MCC

为了更清楚地了解第 2 点,让我们放大 USPS 性能图:

和以前一样,橙色是哈达玛,蓝色是基线。(图片由作者提供)

如果这是您拥有的所有数据,您可能会说 hadamard 显示了一些初步的前景,但最终测试集性能在彼此的 1 个标准偏差内(0.967 基线对 0.973 hadamard)。

由于大多数人开发模型时一次只考虑一个数据集,他们可能会在这里停下来并恢复他们的更改,而没有注意到该方法的泛化优势。

然而,一旦我们开始审视 MNIST 和 SKL 的表现,我们就会看到一个非常不同的故事。hadamard 模型的泛化性能明显高于基线架构。这反映在组合 MCC 图中曲线的实线分离中(最大总 MCC 从 0.80 增加到 0.88,MNIST 峰值性能从 0.77 增加到 0.84,SKL 峰值性能从 0.66 增加到 0.82)。Hadamard 码并没有完全弥补我们的推广差距,但它们确实有所帮助,也许免费给了我们一些额外的对抗鲁棒性。我要了。

那么接下来呢?似乎我们停留在第三步,而每个人都知道第四步是名誉和荣耀所在。好吧,我们已经发现了一项技术,将我们的总得分提高了 0.08 分。如果我们能设法再做一次,我们就有了一个真正通用的 ML 模型。实际上,我们可能需要一个更好的数据集来实现这一点。2021 年有一个叫做 DIDA 的大电影值得一看。这可能看起来不是一个非常令人满意的结论,但是,嘿,如果我已经有了模型泛化的完美解决方案,我现在应该是正在进行第 4 步的人;)

更重要的是,我们现在有了一种方法来轻松测试模型修改对我们的泛化性能的影响。正如我们所见,如果没有明确的监控,我们很容易忽视我们正在试验的想法的真正效用。现在我们有了它,我们可以做出更好的决定,并且更有信心,当我们最终得到满意的测试分数时,我们的模型不会在生产中无情地背叛我们。名誉和荣耀我们来了!

他们给最佳论文颁奖,对吗?什么??等等,那还有什么意义?图片由 tommao 王Unsplash 上拍摄

参考

[1] A. DeGrave,J. Janizek 和 S. Lee,用于射线照相新冠肺炎检测的 AI 选择信号的捷径 (2021),Nature Machine Intelligence
[2]j . Adebayo,J. Gilmer,M. Mulley,I. Goodfellow,M. Hardt 和 B. Kim,显著图的健全性检查 (2018),NeurIPS
3 C. Rudin,停止解释高风险决策和使用的黑盒机器学习模型 用于手写文本识别研究的数据库 (1994),IEEE
[5] K. He,X. Zhang,S. Ren,和 J. Sun,用于图像识别的深度残差学习 (2016),
[6] D. Chicco 和 G. Jurman,Mathews 相关系数(MCC)在二分类评估中相对于 F1 分数和准确度的优势 (2020),BMC Genomics 【T17 IEEE
[8] C. Kaynak,组合多分类器的方法及其在手写数字识别中的应用 (1995),博阿济齐大学科学与工程研究生院硕士论文。
[9] G .维尔马和 A .斯瓦米,纠错输出码提高了深度神经网络的概率估计和对抗性鲁棒性 (2019),NeurIPS
[10] F. Tramèr,N. Carlini,W. Brendel 和 A. Mary,对对抗性示例防御的适应性攻击 (2020),neur IPS
[11]h . Kusetogullari,A. Yavariabdi,j

用拥抱脸变压器构建 NLP 供电的应用

原文:https://towardsdatascience.com/building-nlp-powered-applications-with-hugging-face-transformers-9f561effc84f

和 Docker 部署在 Google Chrome 上

布雷特·乔丹在 Unsplash 上的照片

我最*完成了拥抱脸团队的几个人写的关于变形金刚的奇妙的新自然语言处理的书,并受到启发,将我的一些新知识用于一个基于 NLP 的小项目。在寻找一些想法的时候,我看到了泰赞·萨胡的一篇优秀博客文章,他在文章中构建了一个微软 Edge 扩展来解释你屏幕上高亮显示的文本。我想更进一步,通过:

  • a)用 ONNX 运行时量化优化模型推理
  • b)包括诸如摘要、命名实体识别(NER)和关键词提取的特征。

这个想法是,这创造了最终的随笔伴侣,因为它可以帮助快速理解摘要和 NER 的文本,它可以让那些创造性的汁液随着转述的文本和关键词同义词流动。显然,我希望人们用它来重写他们自己的作品,而不是其他人的…

谷歌 Chrome 上的随笔伴侣演示(图片由作者提供 _

TL;DR: 这个库 包含了本文提到的所有代码。ML 的东西可以在src文件夹中找到,Chrome 扩展的东西在 扩展 文件夹中。

模型

在这个扩展中有四个不同的模型(和 tokenizers)在运行,其中三个是在拥抱脸中发现的!

T5 释义模式

这是所使用的最大型号,达到 2.75Gb(!),并根据拉姆斯里·古塔姆的解释进行了微调。 T5 是在非监督和监督任务的多任务混合上预先训练的编码器-解码器模型,并且对于该模型,每个任务被转换成文本到文本的格式。

首先使用 NLTK 的句子分词器 sent_tokenize 将文本拆分成句子。然后,每个句子通过 T5 模型,每个句子的释义输出被连接起来,得到一个新的释义段落。

释义模型(图片由作者提供)

一些示例输出:

对你的知识的最终考验是你将它传递给另一个人的能力。你将它从一个人传递给另一个人的能力是对你智力的最终衡量。

这很好,我们可以看到我们的转述文本是连贯的,并且具有与原文不同的结构!

BART 汇总模型

BART 也是一个编码器-解码器(seq2seq)模型,具有双向(像 BERT)编码器和自回归(像 GPT)解码器。通过(1)用任意噪声函数破坏文本,以及(2)学习模型以重建原始文本,来预训练 BART。

例如,最*一篇关于欧盟 USB-C 规则实施的 250 字长的新闻文章用 55 个字来概括:

到 2024 年秋季,所有在欧盟销售的便携式电子设备都需要使用 USB Type-C 进行充电。其目的是通过只有一个“通用充电器”来减少电子垃圾,对消费者更加友好。受影响最大的将是苹果的 iPhones 和 iPads,它们将不再能够使用 lightning 线缆。

哇!它工作得非常出色,简明扼要地挑出了文章中的所有要点。

NER 模型

使用了使用 conll03 英语数据集NER 微调的 DistilBERT base 无壳模型。DistilBERT 是一个比 BERT 更小更快的模型,后者以自我监督的方式在相同的语料库上进行预训练,使用 BERT 基础模型作为教师。NER 模型只是一个蒸馏编码器,在末尾添加了一个标记分类头来预测每个实体:人、位置、组织和杂项。

NER 模型(作者图片)

在 chrome 扩展中,使用 SpaCy 在 HTML 中呈现 NER 结果。下面是一些输出示例。

NER 输出示例(图片由作者提供)

KeyBERT 关键词提取模型

该模型利用 BERT 文本嵌入和余弦相似性来寻找文档中与文档本身最相似的子短语。这个想法是这些子短语是文本中最重要的短语。

首先使用 KeyBERT 模型从文本中提取关键词。一旦找到关键词,WordNet 就会被用来寻找每个关键词的同义词。值得注意的是,在 WordNet 中,相似的单词被分组到一个称为 Synset 的集合中,Synset 中的单词被词条化。

KeyBERT 模型(图片由作者提供)

在句子中找到的关键词和相关同义词'最终考验你的 知识 是你的能力 传达 它到另一个'是:

  • 知识:理解、认知、掌握
  • 运送:运输、搬运、交付

模型优化

在 GPU 上运行推理时,模型的初始性能是 OK ,但在 CPU 上运行时性能非常差。甚至 GPU 推理也没有我希望的那么快,因为解码器模型必须顺序解码模型输出,而这是无法并行化的。内存也是一个问题,因为我希望能够在内存和计算资源有限的设备上使用这一扩展(我想到了我的老式2015 MacBook pro…),其中两个型号的大小超过了 1.5Gb!我的目标是尽可能减少这些模型的内存占用,优化 CPU 推理的性能,同时保持模型的性能。

量化蒸馏是两种常用于应对规模和性能挑战的技术。蒸馏已经用于 NER 模型,因为 DistilBERT 是 O.G. BERT 模型的蒸馏版本。在我的例子中,由于我有限的计算资源,T5/BART 的提取是不可能的。

我采取的第一步是将 PyTorch 模型转换为 ONNX,这是一种用于机器学习算法的开放表示格式,它允许我们为目标设备(本例中为 CPU)优化推理。拥抱脸变压器库包括一个工具来轻松地转换模型到 ONNX,这是用来转换蒸馏模型。转换编码器-解码器模型有点棘手,因为 seq2seq 转换目前不被 Hugging Face 的 ONNX 转换器支持。然而,我能够利用这个奇妙的 GitHub 库,它分别转换编码器和解码器,并将两个转换后的模型包装在拥抱面 Seq2SeqLMOutput 类中。

将模型转换为 ONNX 后,QInt8 量化用于以较低位宽数逼*浮点数,显著减少了模型的内存占用,并提高了性能,因为计算可以进一步优化。然而,量化会引入性能损失,因为我们在转换中丢失了信息,但是已经广泛证明(例如,参见此处的)权重可以用 8 位整数表示,而性能没有显著下降。毫不奇怪,ONNX 模型的量化超级容易!

模型量化(图片作者提供)

然后,我们可以针对 CPU 使用率优化模型推断!

优化的 NER 推理(图片由作者提供)

模型优化的结果如下所示。我们可以看到,我们的努力导致了大约 2 倍的大小缩减和大约 3 倍的延迟提升!

PyTorch、ONNX 和 ONNX +量化模型(6 核英特尔酷睿 i7)之间的比较(图片由作者提供)

使用 FastAPI + Docker 部署模型端点

为了部署我们的应用程序,我使用了两个工具作为主要构建模块: FastAPIDocker 。FastAPI 使围绕模型构建 web 框架变得非常容易,Docker 是一个容器化工具,允许我们在任何环境中轻松打包和运行应用程序。

使用 FastAPI,我们将模型打包并构建一个 API 来与它通信。我们还使用 Pydantic 来验证用户输入和模型输出,我们必须非常小心!例如,我们确保输入文本是一个字符串,摘要模型的响应是一个字符串,关键字提取模型返回一个包含字符串列表的字典。

FastAPI 应用程序(图片由作者提供)

Docker 然后被用来容器化 API 服务器,这允许我们在任何机器上运行应用程序,而不用担心复制我的确切环境的麻烦。为了构建服务器的 Docker 映像,我在项目的根文件夹中创建了一个 Docker 文件。

创建 Docker 图像(由作者创建的图像)

为了在线托管扩展,我们可以使用 Azure 的容器服务或者 T2 AWS 的弹性容器服务。我还没有抽出时间来做这件事,但是计划在不久的将来做这件事!

构建 Google Chrome 扩展

拼图的最后一块是构建由 3 部分组成的 chrome 扩展:

  • manifest.json 包含扩展的配置
  • &style . CSS定义用户界面
  • popup.js 与 API 通信,实现扩展的实际功能。

来自扩展的 API 调用(图片由作者提供)

我按照这个神奇的教程来构建 web 扩展,所以如果你想做类似的事情,请查看一下。我自己做的唯一更改是更改输出以包含更多模型,并以 HTML 格式呈现 NER 结果。这是在网上找到的一些示例文本的最终产品的样子!

Essay Companion 的输出示例(图片由作者提供)

结束语

在这篇博文中,我展示了如何利用最先进的 transformer 模型来构建“智能”的基于文本的应用程序。在 CPU 上优化模型推理之前,我首先为解释、摘要、名称实体识别和关键字提取找到合适的模型。然后,我使用 FastAPI 在 API 端点部署模型,并为了可再现性将应用程序容器化。

这个项目的所有代码都可以在 这个 GitHub 资源库 中找到。

随时联系我LinkedIn

使用 Azure Synapse 和 Power BI 构建简单的商业智能项目

原文:https://towardsdatascience.com/building-simple-business-intelligence-project-using-azure-synapse-and-power-bi-39dc5246de02

使用 Synapse 和 Power BI 进行 Spotify 数据分析

迈克·科诺诺夫在 Unsplash 上拍摄的照片

在本文中,我使用 Azure Synapse 和 Power BI 创建了一个非常简单的端到端商业智能(BI)项目。但是,在此之前,BI 是什么?简而言之,BI 是一个收集、存储、分析原始数据并将其转换为可操作的见解的过程,可帮助组织做出更好的数据驱动型决策。

这个项目是使用 Azure Synapse analytics 和 Spotify 数据集创建的。Azure Synpase 是一个统一的*台,接收、探索、准备、转换、管理和提供数据,以满足即时的 BI 和机器学习需求。

作者图片

以下是该项目遵循的步骤(请参考上文),

  1. 通过 Azure Synapse 笔记本从 Spotify API 提取数据(由 Apache Spark 提供支持)
  2. 使用 Synapse notebook 再次转换数据
  3. 将数据从 Synapse 笔记本加载到 Azure 数据湖
  4. 使用 Synapse 笔记本分析数据
  5. 将数据湖中的数据连接到 Power BI 并构建仪表板。

我们也可以使用 Azure Synpase 管道而不是笔记本来提取数据。然而,我在这个项目中使用了一个笔记本。

在这里,我从我的 synapse 笔记本上复制了代码并粘贴到本文中,因为没有将 Synapse 笔记本嵌入到介质中的选项。拍笔记本截图对阅读没有好处。

本文不会涉及如何在 Azure 或任何云概念中创建 Synapse 笔记本。请点击查看如何创建 Synapse 笔记本。

先决条件

  1. Spotify 客户端 ID 和客户端密码
  2. Azure Synapse 订阅
  3. 超级商务智能桌面
  4. 基础云知识

1)从 Spotipy API 中提取数据

第一步是从 Spotify 获取 ClientID 和 Client Secret。为此,我们需要在 Spotify 网站注册,然后创建一个应用来获取 ClientID 和客户端秘密。

Spotify 提供了 SpotiPy API 作为包装器,提取数据非常容易。

在 Synapse Notebook 中,我们可以通过文本文件添加外部 python 库,并将其作为一个包上传到 spark 池中。我将文本文件命名为requirements.txt,并添加了库名。请检查文档。此外,我们需要创建一个 spark pool(用于计算功能)来运行 synapse 笔记本。请查看此处如何在 synapse studio 中创建 spark pool。

作者图片

导入 spotipy 库并初始化 spotipy,提供存储在名为 client_id 和 client_secret 的变量中的ClinetIDClient Secret

**# Import spotipy and initialise**import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
client_id = 'YOUR SPOTIFY CLIENT ID'
client_secret = 'YOUR SPOTIFY CLIENT SECRET ID'
client_credentials_manager = SpotifyClientCredentials(client_id, client_secret)
sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)

导入其他必要的库并用于可视化。

**# Import necessary library**import pandas as pd
import numpy as npimport chart_studio.plotly as py
import chart_studio.tools as tls
import plotly.express as px
from plotly.offline import init_notebook_mode, iplot
init_notebook_mode(connected=True)
import plotly.graph_objs as go
import chart_studio

Spotify API 提供曲目、艺术家和曲目功能。首先,我们提取了曲目和艺术家特征。然后,我们提取轨迹特征并将它们合并成一个数据集。

获取追踪要素

Spotipy 提供了一个 sp.search()方法来查询轨迹要素。请在此查看文档。请注意,我们一次最多只能有 50 首曲目,最多可以有 1000 条记录。我非常有兴趣看到与印度市场相关的数据,因此我只提取了与印度相关的数据。我们需要在搜索查询的 market 参数中提到国家代码。有关市场代码的更多详情,请查看此处

# Creating an empty lists for track featuresartist = []
track = []
track_popularity = []
artist_id = []
track_id = []
length = []#Quering Track featuresfor i in range(0,1000,50):**# Market = 'IN' -> IN is India**tracks = sp.search(q='year:2022', type = 'track', market = 'IN', limit = 50, offset = i)for i, j in enumerate(tracks['tracks']['items']):
    artist.append(j['artists'][0]['name'])
    artist_id.append(j['artists'][0]['id'])
    track.append(j['name'])
    track_id.append(j['id'])
    track_popularity.append(j['popularity'])
    length.append(j['duration_ms'])

将追踪要素转换为数据框

# Store in the dataframedf = pd.DataFrame({'Artist' : artist, 'Track' : track, 'Track_id' : track_id, 'Track_popularity' : track_popularity, 'Artist_id' : artist_id, 'Length' : length})print(df.shape)df

获取艺术家特征

现在,我们需要为上面提取的曲目添加艺术家特征。API 提供了三种艺术家特征Artist popularitygeneresfollowers

**# Creating an empty list for artist features**artist_popularity = []
genres = []
followers = []for i in artist_id:
    artist = sp.artist(i)
    artist_popularity.append(artist['popularity'])
    genres.append(artist['genres'])
    followers.append(artist['followers']['total'])**#Assigning the above features into dataframe 'df'**df = df.assign(Artist_Popularity = artist_popularity, Genres = genres, Followers= followers)
df

提取轨迹数字特征

最后,提取上述轨迹的数字特征。获得这些数字特征既容易又简单。请查看文档了解特性详情。有兴趣看看这些功能danceability, energy, loudness, speechiness, acousticness, liveness, valence and tempo

**#Creating a empty list**track_features = []**#Extracting the track features by looping into track id and creating a new dataframe called "tfeatures"**for i in df['Track_id']:
    feature = sp.audio_features(i)
    track_features.append(feature)
tfeatures = pd.DataFrame(columns = ['danceability', 'energy', 'key', 'loudness', 'mode', 'speechiness', 'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo', 'type', 'id', 'uri', 'track_href', 'analysis_url', 'duration_ms', 'time_signature'])
for j in track_features:
    for k in j:
        tfeatures = tfeatures.append(k, ignore_index=True)
tfeatures.head()

我们完成了从 API 中提取数据。下一步是转换数据。

2)转换数据

做了如下的小改造,

  1. 将曲目、艺术家和曲目数字特征合并到一个数据框中。
  2. 删除不需要的列。
  3. 大写的艺术家和轨道功能;也改变了数据类型。
  4. 将持续时间列从毫秒转换为分钟。
**# Merging Track and Audio Features and stored in 'df1'**df1 = pd.merge(df, tfeatures, how = 'inner', left_on = 'Track_id', right_on = 'id')**# Dropping the unwanted features**df1 = df1[['Artist', 'Track', 'Track_id', 'Track_popularity', 'Artist_id','Artist_Popularity', 'Genres', 'Followers', 'danceability','energy', 'key', 'loudness', 'mode', 'speechiness', 'acousticness','instrumentalness', 'liveness', 'valence', 'tempo', 'duration_ms']]**#Capitalizing the Artist and Track**df1['Artist'] = df1['Artist'].str.capitalize()
df1['Track'] = df1['Track'].str.capitalize()**# Changing the data type**df1['key'] = df1['key'].astype(int)
df1['mode'] = df1['mode'].astype(int)
df1['instrumentalness'] = df1['instrumentalness'].astype(float)
df1['duration_ms'] = df1['duration_ms'].astype(int)**# Duration_ms in milliseconds. Convert into minutes**df1['duration_ms'] = df1.apply(lambda x: round(x['duration_ms']/60000,2), axis=1)

3)将数据加载到 Azure 数据湖中

将数据帧df1作为名为spotify.csv的 CSV 文件存储到 Azure Data Lake 中

我们需要将我们的spotify.csv写入数据湖,这非常简单&只需一行代码。请注意,您需要有写权限才能将数据保存到数据湖中。

abfss://your_container_name@your_storage_account_name.dfs.core.windows.net/filepath

在这里,我的容器名是spotify,我的存储帐户名是storagegen2dstest

# Saving the Spotify.csv into the data lake.df1.to_csv('abfss://spotify@storagegen2dstest.dfs.core.windows.net/spotify.csv', sep=',', encoding='utf-8', index=False)

我们完成了数据湖的数据加载。现在,我们可以开始分析了。您可以直接开始分析,而无需将数据保存到数据湖中;然而,我想保留它,因为我想以后与 Power BI 连接。此外,当 Spotify 中的数据每天发生变化时,我们可以自动执行上述提取、转换和加载操作。

4)分析数据

我想找到以下问题的答案,

十大榜单上有多少印度歌曲?

最不流行的 10 首歌是什么?

排名前 10 的流派有哪些?流行类歌曲的百分比是多少?

关注者排名前 15 的艺人有哪些?那里有印度艺术家吗?

哪首歌最适合跳舞?

在数据集中找出 5 位拥有多首歌曲的艺术家

音频特征之间有什么关系吗?

从数据湖中读取 CSV。

df1 = pd.read_csv('abfss://spotify@storagegen2dstest.dfs.core.windows.net/spotify.csv')

十大榜单上有多少印度歌曲?

**# Sorting the dataframe by Track_popularity**
df1.sort_values('Track_popularity', ascending = False, inplace = True)**# taking the Track and track_popularity and store in a seperate dataframe called "top"**top = df1[['Track', 'Track_popularity']]
top = top.head(15)**# Building visuals**fig = px.bar(top, x='Track', y="Track_popularity", orientation = 'v', title = "Top 10 most popular song",labels={"Track": "Track",
"Track_popularity": "Track Popularity"}
)
fig.show()

作者图片

我们的数据与印度市场有关。我没看到前 10 名里有任何印度歌曲。

最不受欢迎的歌曲是什么?

**#Sorting the dataframe by Track_popularity**df1.sort_values('Track_popularity', ascending = True, inplace = True)
top = df1[['Track', 'Track_popularity', 'Artist']]
top = top.head(10)

有趣的是,在最不喜欢的 10 首歌曲中也没有印度歌曲。

将歌曲按流派分组,流行类有多少首?

**# Taking track, Track_id, genre and store in a new dataframe called genre**genre = df1[['Track', 'Track_id', 'Genres']]
genre = genre.explode('Genres')genre['Genres'] = genre['Genres'].str.capitalize()
genre = genre.groupby('Genres')['Track_id'].count()
genre = genre.to_frame().reset_index()
genre.sort_values('Track_id', ascending = False, inplace = True)
genre = genre.head(10)**#Building Visuals**fig = px.pie(genre, values= 'Track_id', names = 'Genres', title = "Top Genres by total songs", color_discrete_sequence=px.colors.sequential.RdBu)
fig.show()

作者图片

流行音乐占据了前十大流派。此外,我们可以看到许多印度流派都在名单上,如宝莱坞,德西嘻哈,电影等。将* 25%的歌曲属于“流行”类。

关注者排名前 15 的艺术家

**# Grouping the artist and finding top 15 by followers**
followers = df1[['Artist', 'Followers']].groupby('Artist').sum().sort_values('Followers', ascending = False).head(15).reset_index()**#Building visuals**fig = px.bar(followers, y = "Followers", x = 'Artist', orientation = 'v', title = "Top 15 Artist by Followers",labels={"Artist": "Artist","Followers": "Total Followers"})fig.show()

作者图片

不确定艺术家的追随者是全球计算的还是每个市场的。然而,我可以看到两位印度艺术家,像‘阿尼鲁德·拉维钱德尔’和‘A . r .拉赫曼’进入前 15 名。

哪首歌最适合跳舞?

我们可以使用 danceability 特性来找到哪首歌最适合跳舞。可跳舞性值从 0 到 1。如果该值为 0.0,则最不适合跳舞,1 最适合跳舞。

**# Finding top song for best to dance**dance = df1[['Track', 'danceability']].groupby('Track').mean().sort_values('danceability', ascending = False).head(15).reset_index()**# Building visuals**
fig = px.bar(dance, x = 'Track', y = "danceability",  orientation = 'v', title = "Which song is the best to dance to?",labels={"Track": "Track","danceability": "Danceability"})fig.show()

作者图片

印度歌曲有,但不在前 5 名。

在数据集中找出 5 位拥有多首歌曲的艺术家

**# Finding top 5 artists with more than one song**
number = df1[['Artist', 'Track_id']].groupby('Artist').count().sort_values('Track_id', ascending = False).head(5).reset_index()number.rename(columns = {'Track_id':'Total Songs'}, inplace = True)colors=['#fae588','#f79d65','#f9dc5c','#e8ac65','#e76f51','#ef233c','#b7094c'] #color palette**# Building Visuals**
fig = px.treemap(number, path=['Artist'],values='Total Songs', title = "Top 5 artists with more than one song", width=800, height=400)fig.update_layout(treemapcolorway = colors, #defines the colors in the treemap margin = dict(t=50, l=25, r=25, b=25))fig.show() 

作者图片

我可以看到一位印度艺术家(阿尼鲁德·拉文钱德尔)位列前五名。

轨迹特征的相关矩阵

**# Separating features from the dataframe**features = df1[['danceability', 'energy','loudness', 'mode','speechiness', 'acousticness','instrumentalness', 'liveness', 'valence', 'tempo', ]]**# Finding correlation**
df = features.corr()**#Building Visuals**
fig = go.Figure()
fig.add_trace(go.Heatmap(x = df.columns,y = df.index,z = np.array(df),text=df.values,texttemplate='%{text:.2f}'))fig.update_layout(width = 800, height = 800,title = "Correlation Matrix for Track Features", coloraxis1=dict(colorscale="Plasma"))
fig.show()

作者图片

音频特征间的相关矩阵。我没有看到这些特征之间有任何高度的相关性,也看到了负相关性。

上述结果是动态的,因为 Spotify API 中的日常数据会根据用户发生变化。

我已经在 Azure Synapse 笔记本上完成了上述全部分析。正如我提到的,没有将 synapse 笔记本嵌入介质的选项。在这里给 synapse 笔记本拍照不会很好看。因此,我只是复制了本文中的代码。

作者图片

建筑能源商务智能报告

这是一个有趣的数据集来讲故事。我只是将数据湖中的 CSV 文件连接到 Power BI 并构建了简单的报告。你可以点击查看互动链接。

作者图片

我们也可以使用其他云*台来执行上述项目。我只是用 Azure,因为我主要是在日常工作的基础上。如果是内部解决方案,我们可以使用 Python、MySQL 和 Power BI。

感谢阅读。

如何构建合适的数据集

原文:https://towardsdatascience.com/building-succesful-machine-learning-models-through-proper-datasets-9058f85de5f7

因为坏数据导致坏模型

Unsplash 上由 Tabea Schimpf 拍摄的照片

为什么合适的数据集很重要?

从根本上说,机器学习模型是一种基于训练数据对自身进行配置以预测结果的东西。如果您输入的模型训练数据并不代表模型在实际使用时将面临的数据,那么除了有缺陷的预测之外,您什么都不能指望。本文将为您提供创建良好数据集的见解,该数据集能够表示模型在现实世界中使用时将面临的数据。

但是在我们开始构建伟大的数据集之前,首先,我们必须理解数据集的组件(类、标签、特征和特征向量)。然后,我们将继续进行数据准备。这就是我们处理缺失特征缩放特征的地方。接下来,我们讨论为什么以及如何对数据集进行分区。最后,我们将看看为什么我们应该对我们的数据进行最后一次检查以及如何发现仍然可能存在的问题,例如异常值误标 数据

让我们开始吧。

数据集的组件

类别和标签

在分类任务中,输入要素被映射到离散输出变量。例如,通过考虑输入数据,模型预测输出是“狗”、“猫”、“马”等。这些输出变量被定义为。训练数据中的每个输入都有一个名为标签的标识符来表示这些类别。

分类模型如何将输入数据分类。

对一个模型来说,输入只是数字。模型不关心或不知道狗的图像或声音样本之间的区别。这也适用于标签。因此,类可以用我们想要的任何方式来表示。在实践中,我们经常使用从 0 开始的整数值来将标签映射到它们各自的类。下面是一个例子。

┌───────┬────────────┐
│ Label │   Class    │
├───────┼────────────┤
│     0 │ person     │
│     1 │ bicycle    │
│     2 │ car        │
│     3 │ motorcycle │
│     4 │ airplane   │
│     5 │ bus        │
│     6 │ train      │
│     7 │ truck      │
│     8 │ boat       │
│     9 │ traffic    │
└───────┴────────────┘
The table is an exerpt from the [COCO dataset](https://cocodataset.org/#home), showing its classes and labels.

在上面的示例中,表示自行车的每个输入都标记为 1,表示船的每个输入都标记为 8。你现在可能会想,我们到底在贴什么标签?一个人或者一条船的投入实际上是什么?这就是最重要的特性出现的地方。

特征和特征向量

要素是模型用来生成标签作为输出的输入。如上所述,这些是数字,根据任务的不同代表不同的东西。例如,在包含三种鸢尾花数据的鸢尾数据集中,特征是萼片和花瓣的尺寸。

The **four available features** in the iris dataset for the iris flower species known as Setosa are shown below. 
(only the first 5 rows of the dataset are shown)    **Sepal.Length  Sepal.Width  Petal.Length  Petal.Width** Species 
1          5.1          3.5           1.4          0.2    setosa
2          4.9          3.0           1.4          0.2    setosa
3          4.7          3.2           1.3          0.2    setosa
4          4.6          3.1           1.5          0.2    setosa
5          5.0          3.6           1.4          0.2    setosa

注:上述数据集中的 为鸢尾花的不同种类, SetosaVirginicaVersicolor 分别赋予这些类 标签 0、1 和 2。

因此,特征只是我们将用作机器学习模型输入的数字。当训练机器学习模型时,该模型学习输入特征和输出标签之间的关系。我们在这里假设特征和标签之间实际上存在关系。在没有要学习的关系的情况下,模型可能无法训练。

一旦训练结束,学习到的关系用于预测输入特征向量(作为输入给出的特征集)的输出标签,其中未知类别标签。如果模型继续做出错误的预测,原因可能是用于训练模型的特征不足以识别良好的关系。这就是为什么选择正确的功能在任何机器学习项目的开始都很重要。更多关于选择好的特性和为什么坏的特性应该被忽略的信息可以在我下面的帖子中找到。

特征可以是不同的类型,例如浮点数、整数、序数值(自然的、有序的类别,其中值之间的差不总是相同的)和分类值(其中数字用作代码,例如男性=0,女性=1)。

概述:特征是表示我们已知的东西的数字,可以帮助建立与输出标签的关系。看看下面 Iris 数据集的一些行,现在显示了所有组件、要素类和标注。包含所有四个特征的每一行是一个特征向量。

 Features                          
          ________________|_____________________       Class   Label
         |                                      |        |       |
**Sepal.Length  Sepal.Width  Petal.Length  Petal.Width  Species** 
    5.1          3.5           1.4          0.2       setosa     0
    7.1          3.0           5.9          2.1       virginica  1
    4.7          3.2           1.3          0.2       setosa     0
    6.5          3.0           5.8          2.2       virginica  1
    6.9          3.1           4.9          1.5       versicolor 2

准备数据

既然我们已经很好地掌握了数据集所包含的内容,那么在我们开始构建伟大的数据集之前,有两件重要的事情需要考虑:

  • 如何处理缺失的特征值
  • 特征缩放

处理缺失的功能

您可能会遇到数据中缺少要素的情况,例如,您可能忘记进行测量,或者某个样本的某些数据已损坏。大多数机器学习模型不具备接受缺失数据的能力,所以我们必须用一些数据来填充这些值。

在这种情况下,您可以采取两种方法。您可以添加一个远远超出要素范围的值,因为模型对该值的重视程度较低,或者使用数据集上要素的*均值来代替缺失值。

在下面的示例中,缺少一些功能,用空格表示。

**Sepal.Length  Sepal.Width  Petal.Length  Petal.Width  Label** 
    5.1          3.5           1.4                      0
    7.1                        5.9          2.1         1
    4.7          3.2                        0.2         0
                 3.0           5.8          2.2         1
    6.9          3.1           4.9          1.5         2

计算所有特征的*均值,如下表所示

**Sepal.Length  Sepal.Width  Petal.Length  Petal.Width** 
    5.95         3.2           4.5          1.5 

通过用*均值替换缺失值,我们可以获得可用于训练模型的数据集。这并不比拥有真实数据更好,但在数据丢失时应该足够好了。如果数据集足够大,另一种方法是通过生成的直方图识别该模式(最常出现的值)。

特征缩放

通常,由不同特征组成的特征向量可以具有多个不同的范围。例如,一组要素的值介于 0 和 1 之间,而另一个要素的值介于 0 和 100,000 之间。另一个会在-100 到 100 之间。因此,一些特征将由于其较大的范围而支配其他特征,这导致模型在准确性方面受到影响。为了克服这个问题,使用了特征缩放

为了理解这一概念以及巩固我们在上述章节中所学的内容,我们将创建一个合成数据集并查看它。

┌────────┬─────┬──────┬──────┬───────────┬────────┬───────┐
│ **Sample** │ **f1** │ **f2** │ ** f3 ** │ **f4** │ **f5** │ **Label** │
├────────┼─────┼──────┼──────┼───────────┼────────┼───────┤
│      0 │  30 │ 3494 │ 6042 │  0.000892 │ 0.4422 │     0 │
│      1 │  17 │ 6220 │ 7081 │ 0.0003064 │ 0.5731 │     1 │
│      2 │  ***16 │ 3490 │ 1605 │ 0.0002371 │   0.23*** │     0 │
│      3 │   5 │ 9498 │ 7650 │ 0.0008715 │ 0.8401 │     1 │
│      4 │  48 │ 8521 │ 6680 │ 0.0003957 │ 0.3221 │     1 │
│      5 │  64 │ 2887 │ 6073 │ 0.0005087 │ 0.6635 │     1 │
│      6 │  94 │ 6953 │ 7970 │ 0.0005284 │ 0.9112 │     0 │
│      7 │  39 │ 6837 │ 9967 │ 0.0004239 │ 0.4788 │     1 │
│      8 │  85 │ 9377 │ 4953 │ 0.0003521 │ 0.5061 │     0 │
│      9 │  46 │ 4597 │ 2337 │ 0.0004158 │ 0.8139 │     0 │
└────────┴─────┴──────┴──────┴───────────┴────────┴───────┘
The first column is the sample number. 
Each row of a sample is an input to the model, given as a **feature vector.** A feature vector is represented by 5 **features** for each sample
- feature set is {f1, f2, f3, f4, f5}
- Typically the full feature vector is refered to with the uppercase letter (F).
- Feature vector for sample 3 can be refered to as F3\. 
One feature vector is highlighted in bold for sample 2 in the table.
The last column is the **label**. There are two **classes**, represented by the labels 0 and 1.Notice how samples start with 0\. This is because we work with Python and Python is 0 indexed.

现在到缩放部分。您可以在我们的综合数据表中看到,不同的特性有不同的范围。让我们看看所有的特征,并考虑它们的最小值、最大值、*均值和范围值。

┌──────────┬───────────┬───────────┬──────────┬────────────┐
│ Feature  │   Range   │  Minimum  │ Maximum  │  Average   │
├──────────┼───────────┼───────────┼──────────┼────────────┤
│ f1       │        89 │         5 │       94 │       44.4 │
│ f2       │      6611 │      2887 │     9498 │     6187.4 │
│ f3       │      8362 │      1605 │     9967 │     6035.8 │
│ f4       │ 0.0006549 │ 0.0002371 │ 0.000892 │ 0.00049316 │
│ f5       │    0.6812 │      0.23 │   0.9112 │     0.5781 │
└──────────┴───────────┴───────────┴──────────┴────────────┘Notice how the features have widely varying ranges. This means that we should carry out feature scaling.

让我们来看两种缩放方式。首先,我们考虑最简单的方法,称为,意思是定心。这是通过减去整个数据集中要素的*均值来实现的。整个集合的*均值就是每个值的总和除以值的总数。

f1 的*均值是 44.4。因此,为了使 f1 居中,我们将属于 f1 特征的每个样本值替换为值-44.4。对于样本 0,它是 30–44.4,对于样本 2,它是 17–44.4,依此类推。对所有值都这样做,我们得到下表。

┌────────┬───────┬─────────┬─────────┬─────────────┬─────────┐
│ Sample │  f1   │   f2    │   f3    │     f4      │   f5    │
├────────┼───────┼─────────┼─────────┼─────────────┼─────────┤
│      0 │ -14.4 │ -2693.4 │     6.2 │  0.00039884 │ -0.1359 │
│      1 │ -27.4 │    32.6 │  1045.2 │ -0.00018676 │  -0.005 │
│      2 │ -28.4 │ -2697.4 │ -4430.8 │ -0.00025606 │ -0.3481 │
│      3 │ -39.4 │  3310.6 │  1614.2 │  0.00037834 │   0.262 │
│      4 │   3.6 │  2333.6 │   644.2 │ -0.00009746 │  -0.256 │
│      5 │  19.6 │ -3300.4 │    37.2 │  0.00001554 │  0.0854 │
│      6 │  49.6 │   765.6 │  1934.2 │  0.00003524 │  0.3331 │
│      7 │  -5.4 │   649.6 │  3931.2 │ -0.00006926 │ -0.0993 │
│      8 │  40.6 │  3189.6 │ -1082.8 │ -0.00014106 │  -0.072 │
│      9 │   1.6 │ -1590.4 │ -3698.8 │ -0.00007736 │  0.2358 │
└────────┴───────┴─────────┴─────────┴─────────────┴─────────┘
The synthetic data set we created, after mean centering. 
Note that now, the average value for each feature is 0\. In other words the center for each feature is 0 and values can be above or below this center.

*均居中足以进行特征缩放。但是我们可以更进一步。请注意,即使我们进行均值居中,范围也是不一样的。通过执行所谓的标准化标准化,我们可以将*均值周围的数据分布(标准差)改变到相同的范围内。换句话说,我们把标准差改为 1。因为我们还执行均值居中,所以在归一化数据时,除了标准差为 1 之外,要素的均值为 0。

如果我们有一个特征值 x,可以简单地用值 z 代替 x,其中:

方程式计算标准分数。

其中: μ 是数据集上每个特征的*均值σ标准差

幸运的是,由于我们正在使用 Python 和 Numpy,这可以通过一行代码来完成。

# Assuming the data is stored in the numpy array F
**F = (F - F.mean(axis=0)) / F.std(axis=0)**

这将提供下表:

┌────────┬───────┬───────┬───────┬───────┬───────┐
│ Sample │  f1   │  f2   │  f3   │  f4   │  f5   │
├────────┼───────┼───────┼───────┼───────┼───────┤
│      0 │ -0.51 │ -1.14 │     0 │  1.89 │ -0.63 │
│      1 │ -0.98 │  0.01 │  0.43 │ -0.89 │ -0.02 │
│      2 │ -1.01 │ -1.14 │ -1.84 │ -1.21 │ -1.62 │
│      3 │ -1.41 │   1.4 │  0.67 │  1.79 │  1.22 │
│      4 │  0.13 │  0.99 │  0.27 │ -0.46 │ -1.19 │
│      5 │   0.7 │  -1.4 │  0.02 │  0.07 │   0.4 │
│      6 │  1.77 │  0.32 │   0.8 │  0.17 │  1.55 │
│      7 │ -0.19 │  0.28 │  1.64 │ -0.33 │ -0.46 │
│      8 │  1.45 │  1.35 │ -0.45 │ -0.67 │ -0.33 │
│      9 │  0.06 │ -0.67 │ -1.54 │ -0.37 │   1.1 │
└────────┴───────┴───────┴───────┴───────┴───────┘
Now we see that all features are similar to each other, compared to our original dataset. 

对数据集进行分区

由于我们遵循了上面的步骤,我们现在有了一组很好的特征向量。但是等等!在用它训练我们的模型之前,我们需要做一些事情。我们不应该使用整个数据集来训练我们的模型,因为我们需要将数据集分成两个,或者理想情况下三个子集。这些子集被称为训练数据验证数据、测试数据

训练数据用于训练模型。

测试数据用于评估训练模型的准确性和性能。重要的是,模型永远不要在训练期间看到这些数据,因为那样的话,我们将根据它已经看到的数据来测试模型。

不是每个型号都需要验证数据。但是,当模型是深度学习模型时,它会有所帮助。该数据集的使用类似于训练过程中测试数据的使用。它告诉我们训练过程进行得如何,并提供关于何时停止训练以及模型是否适合数据的信息。

综上所述,训练验证数据用于建立模型。测试数据被保留以评估模型。

每组应该放多少数据?

根据一些文献,将数据集的 90%分成训练数据和 5%分别用于验证和测试数据被认为是标准的做法,而其他人建议 70%用于训练,15%分别用于较小数据集的验证和测试,或者 80%用于训练,10%用于验证和测试。

保留数据集中的先验概率很重要。如果一个类以 20%的概率存在于现实世界中,这也应该反映在我们的数据集中。这也应该扩展到我们创建的子集。我们如何做到这一点?有两种方法:

  • 按类别划分
  • 随意采样

按类别划分

当您处理小型数据集时,可以使用此方法。首先,我们确定每个类有多少个样本。接下来,对于每个类,我们将选择的百分比放在一边,并将所有内容合并在一起。

例如,假设我们有 500 个样本,其中 400 个属于类别 0,100 个来自类别 1。我们想做一个 90/5/5 的分割(90%的训练数据,5%的测试和 5%的验证)。这意味着我们从类 0 中随机选择 360 个样本,从类 1 中随机选择 90 个样本来创建训练集。从类别 0 的剩余未使用的 40 个样本中,随机选择 20 个样本用于验证数据,20 个用于测试数据。从类别 1 的剩余未使用的 10 个样本中,每个样本将进入验证和测试数据集。

随意采样

如果我们有足够的数据,我们不需要非常精确,并遵循上述方法。相反,我们可以随机化整个数据集,并根据必要的百分比拆分我们的数据。

但是如果我们有一个小的数据集呢?我们可以使用诸如 k 倍交叉验证的方法来确保这种方法的问题,例如训练和测试数据中的不*衡,可以得到缓解。如果你想了解更多关于这个方法的知识,可以看看我下面的帖子。

https://medium.com/@praveennellihela/what-is-k-fold-cross-validation-5a7bb241d82f

最后看一下我们的数据

现在,我们已经执行了许多操作,以确保我们的数据集足以训练一个模型。我们确保它具有良好的特性,没有缺失的特性,并且这些特性是规范化的。我们还以适当的方式将数据集划分为子集。

最后重要的一步是最后一次查看数据,以确保一切都有意义。这允许我们识别任何错误标记的数据缺失或异常数据。这些错误可以通过将数据加载到电子表格中来识别,或者对于更大的数据集,使用 Python 脚本来汇总数据。我们应该查看*均值、中值、标准偏差以及最大值和最小值,以了解是否有任何异常数据。我们还可以生成箱线图来识别异常值。

迈克尔逊实验的方框图示例,用户:史高斯——自己的作品,公共领域

在执行了上述所有步骤后,我们可以确信我们的数据集是一个合适的数据集,可以帮助模型很好地训练,进而对现实世界的数据产生准确可靠的预测。这可能看起来很费力,但请永远记住,如果你给你的机器学习模型输入垃圾,它们只会输出垃圾。您应该始终确保您的数据集足够好。

摘要

在这篇文章中,我们看了为什么一个好的数据集是重要的。然后,我们通过查看组件(如类和要素)来了解数据集的构成。我们讨论了数据准备以及如何处理缺失特征和缩放特征。接下来,我们讨论了为什么以及如何对数据集进行分区。最后,我们了解了为什么我们应该对我们的数据进行最终检查,以及如何发现仍然存在的问题,例如异常值和错误标记的数据。

如果你觉得这篇文章有用,可以考虑订阅我和加入媒体。你的会员支持我和你直接阅读的其他作家。

感谢您的阅读!在以后的帖子中再见。

用 Python 枚举和 difflib 构建单词校正器和代码比较工具。

原文:https://towardsdatascience.com/building-word-correctors-and-code-comparison-tools-with-python-enums-and-difflib-860d369d53d1

照片由诺德伍德主题Unsplash 上拍摄

Python 总是以其几乎无穷无尽的模块和 API 适应任何*台给我们带来惊喜。无论何时,只要您有时间阅读文档,寻找实际的实现,总有一些东西需要学习。此外,当您发现某些模块时,您可以想到有趣的用例,反之亦然。

在 Python 附带的有趣的附加元素中,我们将在这个简短的教程中关注两个 gem,即enums ( since Python 3.4 )difflib ( since Python 2.1 ),以解决与词法纠正和版本代码比较相关的简单用例。

在第一部分中,我们试图提出一个带有枚举的 Python 词法校正器。在第二部分,我们缓慢但稳定地过渡到使用difflib,并尝试用它构建一个小的代码比较工具。

1。枚举:

枚举器也是用其他编程语言实现的数据结构。它们帮助我们建立一组带有常量值的逻辑实体。
根据官方文件:

枚举是绑定到唯一常数值的一组符号名称(成员)。在枚举中,成员可以通过标识进行比较,并且枚举本身可以被迭代。

一般来说,建议用大写字母指定成员。我们不一定会一直遵守这条规则。

让我们宣布一个DSStrategy,它包含任何数据科学策略的通用和重要步骤:

from enum import Enumclass DSStrategy(Enum):
    Cleaning = 1
    Exploring = 2
    Learning = 3
    Evaluating = 4

我们可以像这样循环遍历枚举结构:

>>> for strategy in Strategy:
...    print(strategy, end=" "), print(strategy.value)Strategy.Cleaning 1
Strategy.Exploring 2
Strategy.Learning 3
Strategy.Evaluating 4

有趣的事情:我们有一组分解方法可以用来将额外的行为分层到我们的枚举器中。例如,当我们声明成员时,_order_分隔符规定了特定的顺序。假设你不遵守指示的顺序:

>>> class DSStrategy(Enum):
...    _order_ = 'Cleaning Exploring Learning Evaluating'
...    Cleaning = 1
...    Learning = 2
...    Exploring = 3
...    Evaluating = 4...
TypeError: member order does not match _order_

如果成员的值不重要,将每个成员与auto实例相关联就可以了。然后,Enum 类将为您计算出这些值。如果没有指定,默认情况下,该值从 1 开始。

>>> from enum import Enum, auto>>> class DSStrategy(Enum):
...    _order_ = 'Cleaning Exploring Learning Evaluating'
...    Cleaning = auto()
...    Exploring = auto()
...    Learning = auto()
...    Evaluating = auto()

>>> for strategy in Strategy:
...    print(strategy, end=" "), print(strategy.value)Strategy.Cleaning 1
Strategy.Exploring 2
Strategy.Learning 3
Strategy.Evaluating 4

枚举器使用起来非常简单,不会引起太多的麻烦..并不总是这样!
我们的下一个用例将枚举带到了下一个层次。我们将构建一个小而简单的词法 Python 校正器。

我们最初为枚举器提供 Python 关键字,以单下划线为前缀,并将类型和错误消息作为值。使用单下划线不是为了扰乱成员的可访问性、名称空间或类似的东西。只是从语法上来说,不能使用简单的 python 关键字作为变量或成员。这是我想到的第一个简单的解决方法,所以我应用了它。

__init__方法将每个成员视为一个整体实例。它调用第一个和第二个值作为成员的属性。custom_message方法用于动态构建一个property,显示一条消息来警告我们出现了词法错误。

我们编写了一个小库,在其中我们故意注入了一些词法错误:

我们将它保存到一个本地文件中,并用一些基于正则表达式的处理加载它:

注意lines_of_code是如何存储预处理的代码行的:

>>> lines_of_code['def_ add x  y  ',
 'return x   y',
 '',
 'def sub x  y  ',
 'return x   y',
 '',
 'deff mul x  y  ',
 'returnn x   y',
 '',
 'def div x  y  ',
 'tryy  ',
 'return x   y',
 'exceptt ',
 'print  Problem dividing by 0\.  ',
 'return Nonne']

让我们编写一个函数,一行一行地扫描lines_of_code,一次看一行,扫描每个单词,并与我们的KeyWords枚举器中定义的 python 关键字列表进行比较。

我们慢慢地开始过早地揭开一些difflib的美丽。注意使用了get_close_matches来查找最*的单词。我们稍后将对此进行详细阐述。让我们首先测试我们的功能。我们用__members__属性调用关键字成员列表。

>>> check_and_correct(lines_of_code, KeyWords.__members__)Error at line 0 .
Function definer. Careful how you define your function. 
Error at line 6 .
Function definer. Careful how you define your function. 
Error at line 7 .
Return Keyword. Careful how you close your functions !
Error at line 10 .
Exception Keyword. Easier to ask for forgiveness than permission. Wink wink !
Error at line 14 .
None operator. You should be reviewing your None usage.

实验很有趣,但是没有检测到一些错误的行,这是可以理解的,因为我们只向枚举器提供了所有 Python 关键字的子集。因此,我们没有足够的地面真相。

拥有所有 Python 关键字的更正确和完整的方法是调用keyword库并调用它的kwlist实例。您可能会找到组成 Python 语法的所有关键词。

>>> from keyword import kwlist
>>> for kw in kwlist: print(kw, end=" |")False |None |True |and |as |assert |async |await |break |class |continue |def |del |elif |else |except |finally |for |from |global |if |import |in |is |lambda |nonlocal |not |or |pass |raise |return |try |while |with |yield |

让我们稍微修改一下前面的函数,使其适应新的关键字查找列表:

接下来的内容清楚地表明,我们的模块拦截了更多的词法不规则性:

>>> check_and_correct_kwlist(lines_of_code, kwlist)Error at line 0.
Maybe you wanted to write def instead of def_.
Error at line 6 .
Maybe you wanted to write def instead of deff.
Error at line 7 .
Maybe you wanted to write return instead of returnn.
Error at line 10 .
Maybe you wanted to write try instead of tryy.
Error at line 12 .
Maybe you wanted to write except instead of exceptt.
Error at line 14 .
Maybe you wanted to write None instead of Nonne.

我们对我们的小型修正引擎更有信心,因为我们有一个更完整的关键字集(不像我们的关键字枚举器仍然缺少一些)。

您可能仍然对get_close_matches功能感到好奇,您应该深入了解一下。让我们在英语词汇的一个重要的子集上尝试一下,并从中构建一个单词校正器。首先,我们将词汇表作为一个列表加载:

with open('words_alpha.txt','r') as f:
    lines = [line.lstrip(' ').rstrip('\n') for line in f]

我能想到的第一个词:computers。让我们寻找compputeers,这样我们就可以测试校正器了:

>>> get_close_matches('compputeers', lines, n = 4, cutoff = .8)['computers', 'computes', 'computer', 'compeers']

使用非常直观。1st2nd自变量分别代表要查找的单词和要提供的查找列表。n是与搜索的单词最相似的候选单词的数量。cutoff是一个相似性得分阈值,低于该阈值的候选人将不予考虑。这将节省你很多时间来探索和测试其他模糊匹配和相似性包。difflib是标准库的纯宝石。

2.difflib 的增量:

通俗地说,difflib是一个比较序列的包,无论是简单的列表,字符串等等。

在许多方面,似乎difflib比我们之前看到的更有诱惑力。我们能想到的一个有趣的用例是代码比较器。我们的目的是比较两个版本的基本*均计算模块。

还记得当你是一个完全的 Python 初学者时,你奋力克服了它。凭借当时获得的技能,你最初的反应会驱使你写下以下内容:

*## V1.py*def compute_average_V1(elements):
    sum_elements = 0
    for el in elements:
        sum_elements += el
    length = len(elements)
    mean = sum_elements / length
    return mean

然而,由于您从未停止学习并热衷于优化您的代码,在您编写新版本一年后:

*## V2.py*def compute_average_V2(elements):
    sum_elements = sum(elements)
    mean = sum_elements / len(elements)
    return mean

现在,你正在想比较这两个版本的最快方法。让我们一起努力吧。首先,我们将两个版本都作为列表加载。

首先,我们决定将它们作为两个字符串进行比较:

V1 = ' '.join(lines_of_code_V1)
V2 = ' '.join(lines_of_code_V2)

所以第一个版本应该是这样的:

'def compute_average_V1(elements): sum_elements = 0 for el in elements: sum_elements += el length = len(elements) mean = sum_elements / length return mean'

出于好奇,我们用SequenceMatcher类检查两个版本之间的任何相似之处。我们可以把它看作一个 API,帮助我们捕获和匹配两个字符串共有的部分:

>>> from difflib import SequenceMatcher
>>> sm = SequenceMatcher(None, V1, V2)>>> for block in sm.get_matching_blocks():
...    print(block)Match(a=0, b=0, size=21)
Match(a=22, b=22, size=27)
Match(a=71, b=49, size=3)
Match(a=102, b=52, size=36)
Match(a=139, b=95, size=1)
Match(a=141, b=98, size=12)
Match(a=153, b=110, size=0)

尽管看起来很有趣,但它并没有提供足够的信息来动态地告诉我们代码进化是如何进行的。我们可以尝试另一种方法:

get_opcodes在从第一个版本到第二个版本所需的透明性和操作方面非常优雅。
在每个操作码的开头,你知道代码片段是否被替换、删除或者在整个版本中保持不变。
difflib提供了第二个 api,它更侧重于计算差异,而不是匹配序列,这也有一个简单的实现。

different delta 的每一行都以两个字母的代码开头:

  • ‘- ‘ :表示该行是版本 1 独有的。
  • ‘+ ’:表示该行是版本 2 独有的
  • ‘‘ :表示该行为两个版本所共有。
  • ‘? ‘ :表示该行在两个序列中都不存在。这也意味着该行可能只是描述了导致上面行中增量的特定字符。

那么,你把所有关于差异的细节汇总到一个格式化的文件中怎么样?第三个有趣的 API 允许将增量转换成 HTML 页面,如果代码片段更大的话,这个页面会更直观、更易读。

from difflib import HtmlDiffhtmlDiff = HtmlDiff()
result = htmlDiff.make_file(lines_of_code_V1, lines_of_code_V2)
with open('comparison_result.html','w') as file :
    file.write(result)

我们剩下要做的就是通过浏览器打开文件:

作者拍摄的照片

你现在对自己在学习道路上的进步有了更清晰的认识。这个工具可以帮助你做到这一点。

参考

公文— 列举difflib

构建您的数据字段指南

原文:https://towardsdatascience.com/building-your-data-field-guide-e2bdae49a9da

如何超越数据字典

外面是一片丛林。图片作者。

当遇到新的数据集时,数据字典有助于理解和探索它。数据字典通常是 EDA 的第一个工具,但它们仍然缺乏甚至模糊了上下文。在建立任何模型或分析之前,需要理解数据。对于包含 50 列或更多列的数据集,不断地在字典中查找信息是一种精神负担,会减缓理解过程。为了更快地提取洞察力,您需要一种更快的方法来建立对数据集的直觉。

数据本身只是噪音。它只在正确的上下文中有用。毕竟,整数 3 可以是任何东西——某个数量的计数、类别或 ID。数据集中通常包含列名或数据标题,这并不奇怪。理想情况下,数据提供者描述“数据字典”中每一列的含义。就拿 Kaggle 上的“房价预测”挑战来说吧。如果没有它的数据字典,几乎不可能推断出栏和栏表明一所房子靠*一条嘈杂的道路或铁路。像 RoofMatl (屋顶材料)这样的其他列就不那么神秘了,但是如果没有数据字典的话,仍然不是很明显。****

要快速建立对数据的直觉,您需要超越数据字典,构建数据字段指南。现场指南由 3 个部分组成:

数据字段指南的三个组成部分。图片作者。

最重要的是要记住,数据字段指南必须由 you 制作。仅仅接收数据字典是不够的。那太被动了,无法快速建立直觉。你必须积极参与理解你在探险中可能看到的东西。创建列集群(也称为“可视化数据字典”)是将可用信息分成更少部分的一种练习。当相关时,讲述数据的起源故事是将列放入的另一个策略。两者都会让你对你的数据有一个新的认识。

处理原始健身数据

为了提供具体的例子,让我们使用一个真实的数据集。在另一篇文章中,我谈到了通过从 Strava 应用程序下载的活动数据来探索你的健身情况。这很容易做到,通过练习制作自己的数据集是一次富有成效的冒险。在文件格式转换过程之后,下面是一个骑自行车的例子:

自行车骑行文件中的前 5 行。作者提供的图片[0]。

这是一个用 Python 创建的熊猫数据框架,但是不用担心——这些方法是与语言无关的。这个数据集只有 6 列开始。这可能看起来没什么可做的,但是,在一个较小的数据集上解释这种方法将有助于展示每个步骤,而不会迷失在组合混乱中。我已经将这一过程应用于工作中的数据集,这些数据集有数百列,来源不明。

1.数据字典

字段指南的第一个组成部分是数据字典。如果你有一本字典,那么你的一些工作已经为你做了。然而,你仍然需要对它的有效性保持一种健康的怀疑,并找出它不完整或令人困惑的地方。

如果有列名,但没有字典,那该怎么办?最好的办法是向数据集提供者询问这些信息。如果这是不可能的(或者需要太长时间),那么就由您来构建自己的数据字典。在示例 Strava fitness 数据中,有 2 列是由我的数据读取过程创建的,但是其他 4 列除了它们的名称之外没有任何解释。我的数据字典的初稿如下:

  • track ( int ) =游乐设备数据中 GPX 轨道的唯一 ID。派生列
  • segment(int)= GPX 轨道内 GPX 段的唯一 ID。派生列
  • time ( datetime ) =表示记录行的时间的简单时间戳。
  • elevation ( float ) =以英尺为单位的用户高度。
  • longitude ( 浮点 ) =用户的经度,以度为单位。
  • latitude ( float ) =用户所在的纬度,以度为单位。

时间戳没有记录时区信息。因此在当地时间是天真的。(不过我知道现在是东部时区)

至少,数据字典将对每个列的名称、数据类型、描述以及数字列单元或类别枚举进行分类(例如:0 =女性,1 =男性)。这个基本数据字典将随着数据集的探索而更新。EDA 过程可以用新的列来丰富数据集。跟踪每一列的来源将有助于记录用于机器学习的数据集的谱系。

名称又能代表什么呢

名字和命名惯例可能是非常私人的事情。一般来说,列名应该简洁而具有描述性,但是如何在列名中体现这一点是主观的。如果你不喜欢一个名字,你的选择取决于你和谁一起工作。在我的 Strava 数据探索中,我是唯一的利益相关者,所以我可以并将更改名称以适应我的需要。如果您在团队中,更改生产数据集中的名称可能是不可能的或不切实际的。在实验中使用别名是可以的,只要团队能够理解。集成您的更改可能需要撤销那些列别名。

完成字典

一个好的数据字典将提供数据示例以及数值列的上下限。上一节的 dataframe 图像中显示了每列的示例。对于原始列,界限是:

按作者分类的表格。

对于每一列,绝对界限代表列的物理或设计极限。浮点数相对于整数或字符串的技术限制通常没有用。陆地海拔从死海海*面以下 1358 英尺到珠穆朗玛峰顶部。我既没有在约旦也没有在尼泊尔骑过自行车,所以绝对界限不会有助于发现异常值。这就是“实际界限”的由来。我大部分的骑行都在俄亥俄州,那里的海拔在 455 到 1550 英尺之间。类似的逻辑可以应用于纬度和经度值,以形成粗略的地理围栏。

总之,字段指南的数据字典部分将类似于:

图片作者。

尽管有用,数据字典是密集的。对于包含数百个列并且每个列都有许多属性的数据集来说,可能很难解析——尤其是对于新的数据集。如果您知道要搜索的列,那么字典是查找该信息的完美工具。在知识管理方面,词典具有良好的“可查找性”。在表格字典格式中,发现哪些列是相关的可能是一项更困难的任务。数据字段指南的下两个组件将创建具有更好“可发现性”的资产[4]。

2.列簇

是时候将这些信息整理成一个可视化的参考资料了。毕竟一张图抵得上一千个字!对于我们有 6 列的数据集,这个过程相对简单。目标是将相关的列分组到聚类中,并标记每个聚类。这种方法有时被称为亲和作图法或 KJ 法[5]。我建议用 Powerpoint、谷歌幻灯片或像壁画这样的数字白板来做这个练习。您可以使用便签来完成,但是当您浏览数据时,这些聚类可能会发生变化。

在您选择的数字工具中,键入数据集中每一列的名称,如下所示:

Strava 游乐设备数据中的六个可用列。图片作者。

接下来,拖动逻辑上相似的列,使它们在一个簇中彼此相邻。给每个集群留出一些空间,以便在视觉上进行区分。一旦你对你的集群有信心,添加一个文本框来标记它们。用相同的颜色填充一组中的每个列框,以加强视觉上的区分。命名一个集群看起来很棘手,但是一旦你找到了“正确的”集群,你就知道了。通过集群名称来记住这些列会容易得多,并且向同事解释它们也容易得多。至于颜色,我一般是凭手感挑颜色。id 栏很无聊(但很有用),所以它们是灰色的。警告或警报可能会变成橙色或红色。

数据字典的聚集版本。有 3 种主要的列类别。图片作者。

更复杂的数据集将有许多相关的列,建立良好的聚类将需要对数据字典进行严格的分析。将一个列放入一个集群而不是另一个集群,这可能归结为您必须做出的一个假设。当你形成关于数据的假设或问题时,记录它们。我会在以后的博客中告诉你如何使用它们。

如果您已经使用自己的数据集完成了这一步,那么您现在就拥有了 EDA 的宝贵资产。除了典型的列与列之间的交互,您还可以通过探索集群之间的交互来发现新的见解。您可能会发现影响整体数据质量的新内容或不一致之处。除了提高“可发现性”,创建集群实际上是一种“分块”练习。组块是将信息组织成更小的单元(组块)的过程,这些单元更容易处理和记忆。我是一个视觉学习者,所以有一个视觉指导这些组块就更好了。建立数据直觉依赖于识别模式。到目前为止,我已经展示了如何基于它们的语义关系对列进行聚类。接下来,我将根据它们的时间关系对列进行聚类。

3.数据起源的故事

数据字段指南还必须包含关于数据创建时间线的注释。字典只是作为名词的列的参考。把你的数据集想象成一个动词——或者更好,一部电影!把你的数据想象成一个有着自己起源故事的超级英雄(或超级恶棍)是很有趣的。数据随着事件的发生而变化,样本捕捉重要事件。某些列仅在数据创建过程中的某些点上相关。将数据想象成电影有助于将复杂的多列数据集分解成更容易记忆和与他人分享的故事。

回想一下上一篇文章中的示例健身数据是以 GPS 交换格式(GPX)文件的形式生成的。GPS 坐标、高度和时间数据都是在游乐设备运行时生成的。总的来说,这个数据故事相对容易理解,但仍有细微差别潜伏在那里。

有许多方法可以捕捉数据之旅。一部真正的电影需要太多的努力,但是其他技术比如故事板会有所帮助。故事板在创建数据的关键时刻贯穿用户的旅程。故事板的目标是讲述足够多的用户旅程,以尽可能多地看到相关栏目的变化。对于更复杂的交互,可能会有多个用户旅程来捕捉不同的信息块。试着突出用户旅程中的共同组成部分,以便在不同的“情节”故事板中处理变化。

我可以将 Strava storyboard 总结成下面的一张图片(和方便的参考):

完成用户和数据之旅,体验 Strava 赛道之旅。图片作者。

到达停车场后,用户和数据的旅程如下:

  1. 我打开 Strava 应用程序,开始记录新的活动。
  2. 我在桥下和山上骑车,用计时和定位数据创建跟踪点。在山顶,我停下来拿了些水和点心。Strava 的自动暂停功能可以暂时暂停数据收集。
  3. 我骑车下山,绕着湖回到起点。我结束了履带车。我记录关于旅行的元数据,如标题、任何照片和我感觉到的努力程度,从 1 到 10。
  4. 我保存旅程并上传到 Strava
  5. Strava 服务器接收我的乘车数据,并对其进行处理,以便显示在我的账户信息中
  6. 如果我以后想下载,会为我的骑行生成一个 GPX 文件。此外,骑行总结被添加到一个活动日志文件中——也可以下载。

4 个核心列(时间和本地化)与步骤 1 至 3 相关。当 GPS 数据的不连续记录应为游乐设备的后半段生成一个新的segment ID 时,ID 列在开始时和步骤 2 中都是相关的。

定义“游乐设备”

斯特拉瓦跟踪跑步,徒步旅行,游泳以及骑自行车。斯特拉瓦称每次上传为“活动”。这很有效,因为它没有对活动类型做任何假设。在自行车骑行类型中,它也不假设什么是“骑行”。

想象一下我在山顶停下来吃午饭的用户旅程。我可能会结束当前的活动并上传它。午饭后,我会开始一项新的活动。是每次上传都算作一次单独的骑行,还是整个环湖往返之旅?选择一个定义取决于你分析的目的。

考虑一个包含驾驶数据的汽车远程信息数据集,而不是自行车数据。从司机的角度来看,去杂货店的“旅行”可能非正式地意味着往返于商店的车程。然而,该数据将显示为两个不同的点火开关循环。

这个难题也适用于电子商务。如果在一次登录中,客户将一件产品添加到购物车中,几个小时后再次登录时才购买该产品,该怎么办?您认为这个用户旅程是一个“会话”还是基于登录来划分?

无论是骑自行车、开车还是网上购物,我们给用户旅程贴的标签都充满了假设。这些假设将影响我们试图评估的指标和分析。因此,在数据源的故事中把它们叫出来可以让你意识到它们。

数据“绘图孔”

数据质量问题可能会导致很多令人头疼的问题,但是数据源故事对于发现一些质量问题是有用的。例如,Strava 似乎依赖于我的手机上已经安装的谷歌地图来获取本地化数据。如果谷歌地图有问题,需要更新,会发生什么?嗯…

我没有这样骑车!图片作者。

还有更多人为的问题:

  • 忘记记录一次乘坐
  • 行驶过程中电池耗尽
  • 手动暂停活动,但忘记稍后取消暂停
  • 忘记停止录音…然后开车回家

在试图找出数据质量问题的根源之前,捕捉理想或典型的用户旅程可能会有所帮助。无论如何,将数据质量放入故事中有助于理解下游的质量问题。

结论

在本文中,我介绍了数据字段指南的 3 个组成部分。创建指南是您和数据集之间的一个活动过程。它通过创建更多可发现的知识资产(列簇和数据起源故事)来包含和增强数据字典的价值。现场指南的最后两项资产对于减少数据混乱和快速建立直觉至关重要。

如果您遵循并创建了自己的数据字段指南,那么问题和假设无疑会出现。这些构成了数据探索的起点。在我的下一篇文章中,我将详细介绍如何利用你的问题、假设和新的数据域指南来规划你的 EDA。

参考文献:

[1]统计学家,地球上最低的地方 (2016),Statista.com

[2] NOAA,从地球中心测量,地球上的最高点是什么? (2022),海洋服务。NOAA.gov

3地球科学教师友好指南,最高和最低海拔(按州)

[4]火橡树战略,知识管理:可发现性与可发现性 (2019),Fireoakstrategies.com

[5]Asq.org ASQ,什么是亲和图 (2022)

[6]福布斯,是什么让组块成为如此有效的学习方式? (2017),Forbes.com

使用人工智能和 Python 构建您的饮食

原文:https://towardsdatascience.com/building-your-diet-using-artificial-intelligence-with-python-d618d601ab02

以下是如何使用线性优化来解决一个日常问题:我们的饮食

布鲁克·拉克在 Unsplash 拍摄的照片

免责声明!我不是营养师,我也绝对不认为这篇文章可以取代一篇。这个实验只从一个数据科学家的角度有效。当你制定你的食谱时,请不要太在意,并和有资格的专业人士谈谈。

当我在意大利的时候,我正在作为一名自由搏击手在被称为意大利武术学院的红魔队接受训练。我们在欧洲和世界各地旅行,像专业人员一样接受训练。

所有职业运动员都知道,当你在那些水*上训练时,没有什么是随机的。你需要睡好觉,你需要努力训练,你需要训练心态,你需要吃好。良好的饮食对成为一名优秀运动员、保持健康和避免受伤至关重要。

我说过并遵循多种营养计划,我知道这是一项多么艰难的工作。你需要考虑多种因素,比如处于特定的体重范围,拥有特定的肌肉百分比,拥有良好的心态,不要有过于严格的饮食,跟上令人疲惫的训练课程和卡路里范围。然后你必须信任你的营养师,你的营养师也必须信任你。可能的话,你的教练,你的心理教练,你的理疗师,你的营养师应该一起合作。这是一项艰巨的工作。确实是。

这意味着,正如我在引言中所说的,这篇文章明确指出,人工智能无法为你建立完美的饮食。我坚信,至少在今天,我们仍然需要一个合适的医生来分析我们的情况,并给我们提供最佳的营养计划。

尽管如此,我相信建立营养计划可以被形式化为一个(极其复杂的)最优化问题。很简单的说,你要吃饱(不能太多也不能太少)才能达到某个目标。
这意味着,如果我们意识到我们正在大量简化问题,我们可以尝试将这个任务视为优化任务,并构建一个算法来解决它。

让我们开始吧!

1.将问题形式化

在制定营养计划时,有很多事情需要考虑,我确信我忽略了其中的很多。

为此,我和我的一个朋友Cristian Beniamino Milito聊了聊,她是一位认证营养师。他同意我的看法,事情比我想象的要困难得多,但他给了我一些指导。

我们应该得到:

  1. ****一个人每天每公斤摄入一克蛋白质(例如,如果你体重 70 公斤,每天摄入 70 克蛋白质)
  2. 碳水化合物应该是你每天摄入热量的一半。
  3. 剩下的部分应该是脂肪****

因此,一个 70 公斤、1500 卡路里的人的脂肪、碳水化合物、蛋白质和 T21 的比例应该是这样的:

作者图片

现在实际上,我们更感兴趣的不是固定卡路里的数量,而是保持尽可能少的卡路里,因为我们有一定数量的蛋白质、碳水化合物和脂肪。

这就是优化算法的用武之地。我们将会有一个我们想要达到的卡路里的指示性数字,但是我们宁愿尝试最小化它,记住我们严格地必须满足我们计划中的蛋白质、碳水化合物和脂肪的数量。

我们来形式化一下。

2.关于优化

以下是优化的技术定义:

一个算法是一个优化,如果它在一个给定的域中找到一个函数的最小值或最大值。

例如,让我们以下面的函数为例:

作者图片

在这种情况下,函数的就是区间[-1,1]。该域中函数的最小值是 x = 0,y = 0。我们想要一个能够找到最小值的算法。

作者图片

好吧,但是每个人都可以创建自己的自定义问题,这并不一定意味着它足够有趣。😅

我们为什么要研究优化?我们为什么要做出这种算法?为什么我们现在谈论这个问题?****

让我们这样陈述:我们在日常生活中大量使用优化。有时候我们想都没想就做了。

假设你正在购买杂货,你需要买刀片。当然,如果你买了 10000 个刀片,你的银行里就没有钱了。尽管如此,如果你买了 4 个剃须刀片,你需要在 4 天内再买一次(假设你每天都刮胡子),你会在汽油上花更多的钱,这是不明智的。所以我们通常做的是在精神上解决一个优化问题。

作者图片

我们购买的数量介于两者之间:

  • 足够大,有足够数量的刀片,可以用一两个星期
  • 小的足以在银行账户里还有剩余的钱:)

作者图片

这就是为什么优化有它的名字,并且被研究了这么久。我们可以使用的一个更友好的词是“ 折衷 ”:在给定某些条件()的情况下,我们想要目标的最佳值(最小值或最大值)。

所以现在我们知道为什么我们有这些优化算法了。太好了。现在,为什么我们现在使用它们?

我们现在的目标是优化卡路里的数量。特别是,我们希望最小化它们。同时,我们确实希望我们的营养计划多样化,我们希望它能满足我们想要达到的蛋白质、碳水化合物和脂肪的需求。

那么我们该怎么做呢?

3.关于单纯形

所以,让我们建立一个问题的数学模型。
正如我们说过的,我们正试图最小化一个成本- 损失功能。为了使用下面的方法解决这个任务,损失函数必须是线性的。
这意味着给定一组变量 x,

作者图片

我们的损失函数会是这样的:

作者图片

其中 c 是成本的固定向量。
在我们的例子中 x 是什么?还有什么是 c ?嗯,因为我们必须最小化卡路里,很明显 c 必须是每种食物所含卡路里的矢量,比如说,100 克。而且我们说过,是固定的
*

*我很乐意它不被固定,我也很乐意把它改成每公斤巧克力 0.5 卡路里,但不幸的是,它不是这样。

太棒了。现在,什么是 x ?如果你按照这个想法,我想现在很清楚,比如说,x1 必须是我们决定吃的“食物 1 号”的单位(相对于 100 克)。

太好了。比方说,我告诉你最小化 L(x ),我告诉你没有别的。好吧,在这种情况下,答案相当简单: x = 0 ,L(0) = 0。但是听说不吃东西生存很有挑战性。

这意味着我们必须最小化函数,假设我们遵守某些约束。正如我们所说,这些限制是我们需要放进肚子里的蛋白质、 碳水化合物、脂肪的克数。

与我们之前所做的类似,我们现在将描述三个其他向量,我们称之为:

  • f ,那是脂肪的矢量(例如 100 克 1 号食物有 f_1 克脂肪)****

作者图片

  • e,碳水化合物的矢量(如 100 克 1 号食物中有 c_1 克碳水化合物)****

作者图片

我没有用 c 因为我的损失函数里已经有了。我知道。糟糕的商业举动。遗憾的😒

  • p,蛋白质的载体(如 100 克 1 号食品中有 p_1 克蛋白质)****

作者图片

并且约束看起来像这样:

作者图片

其中 E、P 和 F 是我们需要摄入的碳水化合物、蛋白质和脂肪的克数。

简而言之,是的,我们希望尽可能少摄入热量,但我们也希望它们足以为我们的身体提供适量的能量。

由于我们有很多食物,我们会尝试所有满足上述约束的可能组合,并选择热量最少的一个(所谓的最小不是一种可能性)。这是一个连续线性优化的例子,有一个众所周知的算法可以用来解决它,它被称为 单纯形

具体说明这些算法如何工作超出了本文的范围。这么说吧,这个想法是我们在一个定义域内,这个定义域内函数的最小值或最大值位于极值点。

我们将使用单纯形算法,特别是使用 Python 和一个被称为纸浆的库。让我们开始吧。

4.动手编码

4.1 预处理

在这一点上,我们有我们需要的一切。让我们导入一些库:

现在,我们需要做的是下载数据。它们在 Kaggle 上,免费,随时可用( CC0:公有领域)。

一旦我们这样做了,我们就可以像这样轻松地导入它们:

让我们总结一下:

信息很多,可能有点多。我们的简化实验真正需要的是下面的:

  • 上菜量
  • 碳水化合物
  • 总 _ 胖
  • 卡路里
  • 蛋白质

这就是我们真正需要的,所以让我们选择这些列:

太好了。我们继续吧。首先所有的 serving_size 看起来都是 100g。如果是这样的话,我们根本不需要那个列。让我们来看看:

没错。超级没用的专栏。让我们摆脱它。

现在我们需要将碳水化合物蛋白质、总脂肪列改为浮点数列:

现在,节食的一个重要步骤是变得多样化。我们将通过允许我们的模型只查看数据集的特定随机部分而不是整个数据集来实现这一目标。我们将这样做:

我说的“多样化”是指我们不会一周 7 天、一天 5 餐都吃同样的食物。通过查看数据集的随机部分,我们每天都有不同的食物数据集可供查看。

例如,本周一我们会有一份可以吃的食物清单。

***注:**很容易想象,如果我们不考虑随机分割,而是尝试使用特定标准构建“日数据集”,这一步可以得到改进。还需要有某种领域知识来实现这个准则,我没有,因为我不是营养师。

在第一章中,我们讨论了一个人应该吃的食物的 T42 克数和卡路里。为了计算这些(指示性)值,我们可以使用以下两个函数:

假设我们的体重是 70 公斤,每天需要摄入 2000 卡路里的热量。我们需要吃东西:

而言,这意味着:

4.2 优化

我们所说和讨论的一切都可以用下面几行代码来实现和总结:

例如,一个体重 70 公斤的人需要摄入 1500 卡路里的热量,我们运行以下模型:

我们得到以下结果:

4.3 最终产品

让我们用这些函数来循环一天:

是时候运行它了!假设一个 70 公斤的人需要 3000 卡路里:

这就是饮食:

5.奖金!

这个模型有点太简单了。我敢肯定,如果你的营养师要给你一个你需要吃的食物清单,你会改变营养师,你是对的。

即使我们的模型被简化了,我们至少应该试着承认这样一个事实:我们每天吃 3-5 顿饭,并据此采取行动。现在,我们该怎么做呢?

嗯,有几种方法可以做到这一点。
最野蛮的是把食物的克数除以餐数。例如,如果我们应该吃 134 克羊肉(见星期一),我们可以试着说:“让我们早上吃 134/3 克(哎哟……),午餐吃 134/3 克,晚餐吃 134/3 克。”

这听起来一点也不好。于是我请我的天使( 克里斯蒂安·贝尼米里托 ) 告诉我怎么做。

他告诉我,就卡路里而言,我们应该这样想:

  • 第 1 号小吃约占 10%
  • 约 10%为号小吃 2
  • 约 30%用于晚餐
  • 35%用于午餐
  • 15 %用于早餐

作者图片

为了将这一点纳入我们的模型,我们需要做以下事情:

  • 随机将我们一天的数据分成 5 份(每顿饭一份)。我们已经考虑了 7 种不同的尺寸,记得吗?
**split_size = np.linspace(0,len(data),8)**

我们将做同样的事情,将我们的日数据拆分到餐数据中。总的来说,我们将把全长数据分成 75 个随机位。*

  • 建立新的约束,基于我们之前所说的。从形式上讲,新的约束如下所示:

作者图片

现在我们将有:

作者图片

为了尊重我们之前说过的话,我们将:

  • d(早餐)= 1/0.15
  • d(午餐)= 1/0.35
  • d(晚餐)= 1/0.30
  • d(零食 1)= d(零食 2) = 1/0.10

我们可以构建一个新的函数,我们称之为 better_model,,它会给我们一个完整的饮食,这就是我们所期望的。我所说的“完全饮食”指的是不仅按天划分,还按“餐”划分的饮食。

这是函数:

让我给你看一个 70 公斤的人摄入 1500 卡路里的结果:

这是周一饮食。正如我们所见,你有 5 种不同的膳食:点心 1、点心 2、早餐、午餐和晚餐。

让我给你看更多的例子:

这就是我们将要吃的周六午餐:

这就是我们周日吃的早餐:

这是我们周一:作为小吃吃的东西

6.考虑

嗯,有很多事情要考虑。第一个非常明显。这种模式并不像认证营养师那样有效。营养师总是在研究数量,什么对我们的身体好,什么对我们的身体不好,什么对我们的思想健康。尽管如此,通过这个模型,我们绝对可以肯定,我们在摄入尽可能低的热量的同时,仍然尊重我们对蛋白质、脂肪和碳水化合物的需求。

我们是怎么做到的?让我重述一下。

  1. 我们定义问题。我们看到了我们应该吃多少卡路里,这是基于一个真实世界的专业人士的领域知识( 克里斯蒂安·贝尼米里托 )
  2. 我们定义了数学模型。我们用数学术语介绍了最优化的概念
  3. 我们看到了如何将我们的问题(建立饮食)解释为优化问题。
  4. 我们用 Python 和 PuLP 实现了代码

虽然我们在这方面还有很多需要改进的地方,而且营养师在这个过程中仍然需要 100%的参与,但我确实相信这可能是一个很好的起点,让我们可以在工作场所使用人工智能来提出建议。

7.结论

如果你喜欢这篇文章,你想知道更多关于机器学习的知识,或者你只是想问我一些你可以问的问题:

A.在 Linkedin 上关注我,我在那里发布我所有的故事
B .订阅我的 简讯 。这会让你了解新的故事,并给你机会发短信给我,让我收到你所有的更正或疑问。
C .成为 推荐会员 ,这样你就不会有任何“本月最大数量的故事”,你可以阅读我(以及成千上万其他机器学习和数据科学顶级作家)写的任何关于最新技术的文章。

使用 Power BI 从头开始构建您的第一个交互式仪表盘

原文:https://towardsdatascience.com/building-your-first-interactive-dashboard-from-scratch-using-power-bi-af7a3e0203d4

关于 Microsoft Power BI,您需要从头到尾了解的一切

丹尼尔·科尔派在 Unsplash 上拍摄的照片

这篇博文是我与 BusinessOne 合作的培训研讨会的两部分摘要的第二部分,BusinessOne 是一家由学生运营的公益咨询俱乐部,位于墨尔本大学。

该研讨会旨在帮助 BusinessOne 的项目团队学习使用 Microsoft Excel 的基本数据清理技术,并介绍 Microsoft Power BI 作为一种工具来构建交互式仪表盘,以交流数据集中的关键见解。

今天的博文将关注研讨会的后半部分,即使用我们在研讨会前半部分清理的事务数据集,使用 Power BI 从头构建一个仪表板。

为了更容易理解,我建议先看一遍第一部分。或者,你可以在下面看到研讨会的完整录音。

车间使用的所有材料也可以在我的 GitHub 这里找到。

介绍

Power BI 是微软开发的交互式软件,主要用于数据可视化和商业智能。

输入数据可以来自各种来源,包括数据库、网页或结构化文件,如 Excel 电子表格、CSV 文件等。然后,这些数据可以通过不同的视觉效果可视化,以交流见解。

在本次研讨会中,我们将从 Excel 电子表格中读取数据,使用内置的 Power BI 可视化工具创建可视化效果,并在完成后在线发布报告。

在这个过程中,我还将介绍 Power BI 用户界面,并简要描述作为初学者第一次开始时需要注意的一些事情。

Power BI 上的 Starter 报告页面;作者图片

检索数据

构建任何仪表板的第一步都是获取数据。只需从“获取数据”图标下的一系列选项中进行选择,包括 Excel、SQL Server、CSV 文件等。

Power BI 可以从一系列不同的数据源获取数据;作者图片

这里,因为我们的事务数据集是 Excel 电子表格的形式,所以我们将继续使用 Excel 工作簿选项。

选择客户和交易记录表;作者图片

为了构建我们想要的仪表板,我们需要选择 customer 和 transaction 表。选中后,点击加载按钮加载到 Excel 电子表格中。

Power BI 用户界面

现在让我们讨论一下 Power BI 用户界面。Power BI 中有 3 个主要选项卡:

  1. 报告
  2. 数据
  3. 模型

首先,报告选项卡。这是我们在构建仪表板时将花费大部分时间的选项卡。这是我们可以拖放我们想要创建的不同视觉效果的地方。我们的最终产品将准确反映报告页面上的内容。

Power BI 中的“报告”选项卡;作者图片

接下来,我们有数据选项卡。“数据”选项卡显示当前加载到 Power BI 中并可供使用的所有表格。这里,我们有两个数据表:customer 和 transaction,稍后我们将使用它们来构建我们的仪表板。

Power BI 中的数据选项卡;作者图片

最后但同样重要的是,模型选项卡。model 选项卡为我们提供了表之间关系的视图。在这里,我们可以看到交易表和客户表之间的多对一关系,在忠诚卡号字段上连接。

换句话说,这意味着忠诚度卡号对于客户表中的每一行都是唯一的,但在交易表中是重复的。这是有意义的,因为客户可以在一年中多次购买商品,因此会多次出现在事务表中,而客户表只为每个唯一的客户保留一条记录(行)。

Power BI 中的“模型”选项卡,图片由作者提供

事务表和客户表之间的多对一关系;作者图片

创造你的第一个视觉效果

Power BI 中的视觉效果示例;作者图片

Power BI 与 PowerPoint 非常相似,都有类似的拖放感觉。我们可以在上面看到 Power BI 中现成的一系列视觉效果:卡片、饼图、表格和条形图。

对我来说,学习 Power BI 的最佳方式是体验视觉效果,以及根据自己的需求定制它们的方式。为了帮助您开始,我将演示如何创建一个简单的卡片视觉效果,即下面看到的一个汇总指标。

Power BI 中的显卡;作者图片

首先,我们需要选择一个视觉。在这种情况下,我们需要一个卡片视觉效果。然后,只需将一个字段从右侧的可用表中拖到字段部分。这将自动填充卡片视觉效果,并显示 193 万美元的值,这是该零售商店在该特定财政年度产生的总销售额。

一旦填充了视觉效果,您将可以选择自定义它们,例如更改颜色、字体大小、数值小数位数等等。正如我之前说过的,学习这个的最好方法是简单地通过试验不同的选项,看看它们每个都做了什么。

定制视觉效果的选项;作者图片

DAX 公式

有时候,计算并不像计算总销售额那样简单。以畅销产品为例,我们希望产品名称的销售量最高。

因为我们在计算中考虑了多个字段,即产品名称和销售数量,所以仅使用其中一个字段名称是不够的。这就是达克斯的用武之地。

DAX 是数据分析表达式的缩写。它们与 Excel 公式非常相似,因为它们主要用于使用内置函数定义自定义计算。

为了证明这一点,让我们使用 DAX 来计算最畅销的产品。

Best Seller = TOPN(1, VALUES('transaction'[Product Name]), CALCULATE(SUM('transaction'[Product Quantity])))

该公式本质上是说,对每种产品的所有销售量求和,从最高到最低排列,最后取最上面的一个。这会给我们带来最畅销的产品。

过滤

接下来,我们将学习过滤器。顾名思义,过滤器允许我们只显示满足特定条件的数据。

将表格筛选到前 50 行;作者图片

假设上面的例子中,一个表显示了记录销售额最高的前 50 名客户。这可能是过滤器最简单的用例之一。

要应用筛选器,我们需要做的就是首先选择表,导航到筛选器窗格,然后应用按总销售额字段排序的前 N 名筛选器。参见下图以供参考。

作者图片

切片器和切片器交互

现在让我们来谈谈切片机。切片器,类似于卡片以及我们上面看到的所有其他图表,是 Power BI 中的一种视觉类型。它允许用户过滤连接到特定切片器的图表上的数据。

这里,我们有一个不同产品名称的切片器。比方说,如果有人要选择一个特定的产品,这将改变聚集柱形图和时间序列图,以反映所选择的产品。

选择所有产品的默认切片器;作者图片

仅选择特定产品将会更改与其连接的图表;作者图片

不过,在使用切片器时要记住的一点是切片器交互。除非手动关闭,否则切片器将与同一报告页面上的所有视觉效果进行交互。

这可以在上面的格式部分找到。

编辑切片器交互;作者图片

视觉分组

将视觉效果组合在一起有两个好处:

  1. 保持工作区整洁,这反过来使我们能够快速、轻松地在页面上找到特定的视觉效果
  2. 同时隐藏或移动一组视觉效果

视图部分下的选择图标;作者图片

虽然没有硬性规定你应该如何分组你的视觉效果,但我个人喜欢根据它们在仪表盘上的位置或类别来分组。

例如,在这里,我把我所有的视觉效果分成了 3 个独立的、有适当标签的桶:

  • 汇总指标
  • 客户人口统计
  • 销售细目

如果我需要回去检查或更改报告中的某个特定视图,这只会让事情变得更容易。

根据视觉效果在仪表板上的位置或类别对其进行分组;作者图片

发布报告

最后,在所有艰苦的工作完成后,一旦您对报告的外观感到满意,您现在就可以发布和共享报告了。

只需导航到主页部分,点击发布图标,按照提示操作,直到报告在线发布。一旦上线,你就可以分享给不同的用户群,获得反馈。

在线发布报告;作者图片

总之,Power BI 是一个非常强大和流行的工具,特别是用于商业智能目的。它不仅易于使用,而且如果使用得当,还可以向各种利益相关者传达关于数据的有意义的见解。

为了补充这篇文章,我强烈建议在我的 YouTube 频道上观看完整的视频,在那里我将更详细地演示这里涉及的每个步骤。

如果你从这篇文章中发现了任何价值,并且还不是一个媒体会员,如果你使用下面的链接注册会员,这对我和这个*台上的其他作者来说意义重大。它鼓励我们继续推出像这样的高质量和信息丰富的内容——提前感谢您!

https://chongjason.medium.com/membership

不知道接下来要读什么?这里有一些建议。

用 R 语言构建你的第一个闪亮的应用程序

原文:https://towardsdatascience.com/building-your-first-shiny-app-in-r-82c7d1f5f309

了解如何使用 R 构建一个闪亮的应用程序,并展示您的代码和交互工作

照片由西格蒙德@ unsplash.com 拍摄

【免责声明:此帖子包含一些我的 Udemy 课程的附属链接】

因此,您已经使用 R 开发了您的数据科学模型或分析,现在您可能想以一种可视化和直观的方式展示结果。

你对你的故事讲述做了一些调整,添加了一些情节,但是你觉得你的视觉效果有点静态,让你在整个故事讲述过程中重复代码或情节。此外,您很难在输出中隐藏您的代码,这是很关键的,您知道这会给业务或非技术用户带来一些困惑。

很幸运,你有shiny闪亮 是一个很棒的构建 R 应用的库,你可以在其中嵌入 R 代码和结果,而不必构建自己的前端和后端。shiny这些功能令人惊叹,使您能够即时提供应用,同时让您的用户与您的模型或分析结果进行交互。

关于shiny的另一个很酷的事情是,它可以直接从 R 中使用,就像任何其他库一样——使用 R 代码,我们可以建立一个基本的 HTML 交互页面,可以用来显示图表、数据框或其他元素。

在本帖中,我们将探索一点shiny库,目的是帮助你构建你的第一个应用。我们将使用几个例子,例如:

  • 建立一个简单的“hello world 应用程序”,了解后端和前端流程。
  • 构建一个简单的散点图应用程序。
  • 构建一个具有三种不同视图的应用程序,包括来自决策树的一些结果。

在这篇文章的最后,你应该准备好使用 r 来使用这个库。虽然shiny需要一些时间来掌握,但我们在这里学到的东西有望成为一个良好的开端,让你开始使用复杂的shiny应用程序,以及用它连接你的数据帧和绘图。

我们开始吧!

我们第一个闪亮的香草应用

让我们从在我们的环境中安装shiny开始:

install.packages(‘shiny’)

安装完shiny包后,我们可以使用常用的library函数将它加载到我们的环境中:

library(shiny)

首先,我们将声明一个fluidPage。这个功能将是我们支配和与 web 应用前端交互的主要方式。就其核心而言,fluidPage是一个非常标准的行列格式用户界面(它甚至包括一些 bootstrap 特性),对于大多数基本数据产品来说,这应该足够了。它通常也是可调整的,能够在应用程序中整合许多部分,正如我们将在几分钟内看到的。

简单来说,fluidPage可以认为是我们shiny app 的前端层。在这里,我们将构建用户将看到的一切,以及它将如何与后端交互。

我们的第一页非常简单,只打印了"My first shiny page!":

sp <- fluidPage(
  "My first shiny page!"
)

为了运行我们的应用程序,我们还需要设置我们的后端——这可以通过定义一个将传递给我们的shinyApp的自定义函数来完成。现在,我们的server函数将完全为空(目前我们在后端没有使用任何东西):

server <- function(input, output) {
}

使用shinyApp,我们可以通过前端(sp)后端层(server)启动我们的第一个应用程序:

shinyApp(sp, server)

运行上面的代码后,应该会弹出一个新窗口!这个窗口是我们的闪亮的应用程序被提供的地方:

我们的第一款闪亮应用——作者图片

我们的应用程序将在本地主机 http://127.0.0.1:7388/ 上的我们的计算机中提供。您应该在 R 控制台上的shinyApp命令下面看到您的本地主机地址:

闪亮的应用程序地址——作者图片

当你运行应用程序时,你也可以在浏览器上输入地址,这也将带你到你的应用程序。

当然,目前我们的应用程序是无用的——我们没有任何输入或输出,如果能够与我们的应用程序进行交互,那将非常有用!接下来,让我们使用mtcars数据框架,构建一个带有 2D 散点图的简单应用程序!

构建交互式绘图应用程序

当然,我们不仅仅局限于编写一个基本的文本应用程序。例如,假设我们想在我们的应用程序上显示一个与此类似的图:

重量与 mpg mtcars 曲线图—图片由作者提供

我正在使用玩具数据集mtcars绘制汽车重量油耗(每加仑英里数)的散点图:

library(ggplot2)

ggplot(
  data = mtcars,
  aes(x=mpg, y=wt)
) + geom_point(color='darkgreen')

我们可以做的一件很酷的事情是在shiny应用程序中提供散点图。此外,为了让我们的应用程序更有趣,我们希望有动态的xy列,使用用户的输入。

虽然看起来很难做到这一点,但我们可以简单地通过改变我们的后端和前端层的一些部分来做到这一点。让我们从让用户从mtcars中可用的列列表中选择两列开始:

sp <- fluidPage(

  selectInput(
    inputId = 'var1',
    label = 'Column X Axis',
    choices = colnames(mtcars)
  ),

  selectInput(
    inputId = 'var2',
    label = 'Column Y Axis',
    choices = colnames(mtcars)
  )

)

selectInput函数创建一个下拉框,其中包含mtcars中所有可用的列。selectInputshiny中众多可用的输入法之一,我们可以将colnames(mtcars)传递给 choices 参数,它将负责用所有元素填充我们的下拉列表。

inputId在大多数shiny应用中极其重要——它是我们在前端和后端之间发送信息的方式。在这种情况下,每当我们指向后端的一个var1var2时,R 将获取我们在下拉框中指定的值。在某些场景中,我们来自前端的变量必须包含在reactive上下文中,我们将在我们将要构建的第三个应用程序中看到这一点。

如果我们把这个新的sp赋予我们的应用,我们的前端就有了新的东西:

shinyApp(sp, server)

绘图应用程序-作者提供的图片

有意思!我们的前端现在有两个下拉框,我们可以在其中选择XY轴的列。但是..我们怎样才能把这些元素赋予我们的情节呢?这就是后端层的用武之地:

server <- function(input, output) {
  output$scatter <- renderPlot({
      ggplot(
      data = mtcars,
      aes_string(x=input$var1, y=input$var2)
    ) + geom_point(color='darkgreen')
  })
}

注意,我们可以使用标准的输入和输出参数。当我们在shinyApp的上下文中使用server函数时,输入和输出将在应用程序内部被解析。例如,input$var1将从页面中的第一个selectInput中选取数据,因为第一个selectInput的 id 是var1

当我们现在运行我们的shinyApp时,我们的应用看起来会有点不同:

shinyApp(sp, server)

带有散点图的绘图应用程序-图片由作者提供

我们有我们的闪亮的应用程序可用的情节!在当前的视图中,散点图并没有说太多,因为我们正在绘制mpgmgp,同一个变量。有趣的是,如果我们改变下拉列表中的列,我们的绘图将自动更新:

绘图应用程序,散点图 mpg 与 wt-作者提供的图片

酷!所以,基本上,每次我们切换 id 为var1selectInput时,存储的列都存储在变量中,并提供给服务器上的input$var1。同样的道理也适用于input$var2var2。从逻辑上讲,id 是我们用来连接后端和前端层的参考。

我们还可以在前端方面进一步调整我们的应用程序。例如,我们可以将我们的布局拆分成一个并排的应用程序,使用sidebarLayout并将每个部分包装在sidebarPanelmainPanel中:

sp <- fluidPage(

  sidebarLayout(

    sidebarPanel(
      selectInput(
        inputId = 'var1',
        label = 'Column X Axis',
        choices = colnames(mtcars)
      ),

      selectInput(
        inputId= 'var2',
        label = 'Column Y Axis',
        choices = colnames(mtcars)
      )
    ),

    mainPanel(
      plotOutput("scatter")
    )
  )
)

我们的应用程序现在将具有以下外观:

带有侧边栏布局的绘图应用程序-图片由作者提供

我们的新布局是一个两列格式的图,我们可以操纵。我们选择了一个sideBar布局,但是,还有更多的布局可供我们选择,这里有。

基于模型构建应用程序

我们已经使用绘图应用程序构建了第一个有用的shiny应用程序!我们研究的主要层次是:

  • 我们可以使用自定义的server函数创建一个后端。
  • 我们可以使用fluidPage设计前端

对于我们在shiny中开发的大多数单页面应用程序来说,这种机制是重复的。如果你对多页面应用很好奇,也可以查一下 shiny.router

对于我们的最后一个例子,让我们使用三列布局的shiny提供一个基本的决策树模型。

首先,我们将在我们的mtcars数据之上训练一个决策树,尝试基于wtcylhp来预测mpg:

library(rpart)

dtree <- rpart(data=mtcars, mpg ~ wt + cyl + hp,
               control = list(minsplit=1))

当然,这是一个可笑的决策树和模型训练过程,它甚至不包含一个适当的训练-测试分割。这里的目标只是在shiny的上下文中使用一个基本模型,所以现在让我们忽略这些细节。

让我们将这个dtree对象保存到一个rds文件中:

saveRDS(object=dtree, file='decisiontree.rds')

假设我们想要构建以下应用程序:

计划中的应用程序—作者图片

这里,我们需要三列,这是一个混合了fluidRowcolumn的优秀用例。让我们从构建一个三列布局开始,从我们的前端开始:

 sp <- fluidPage(

  fluidRow(

    column(4, 'Column 1!'),

    column(4, 'Column 2!'),

    column(4, 'Column 3!')       
  )
)

server <- function(input, output) {
}
shinyApp(sp, server)

我们的shinyApp将具有以下外观:

三栏应用程序—作者图片

请注意,我们的Column 1!Column 2!Column 3!在应用程序的前端等距分布。关于fluidRowcolumn很酷的一点是,它们以一种“boostrapy”的方式运行,这意味着,如果我们不使用全屏,我们的列将被堆叠,而不是将数据压缩成不可读的东西。

从第一列开始,我们希望有一个所有可用汽车的列表,以便我们可以选择我们将在中间一列突出显示的汽车—这样做很容易,因为我们已经了解了selectInput:

 sp <- fluidPage(

  fluidRow(

    column(4, 
        selectInput(
          inputId = 'selectedcar',
          label = 'Car List',
          choices = rownames(mtcars)
        )
    ),

    column(4, 'Column 2!'),

    column(4, 'Column 3!')       
  )
)

server <- function(input, output) {
}
shinyApp(sp, server)

在左栏中,我们现在可以选择特定的汽车:

三栏应用程序—作者图片

在第二列中,我们希望用突出显示的选定汽车来绘制我们的预测图。为此,我们需要做几件事情,即与后端交互:

  • 加载我们的模型并预测所有汽车的重量。
  • 建立一个 MPG 与 HP 的二维图表(如果您愿意,您也可以添加另一个选择第二个变量的下拉列表)
  • 高亮显示所选汽车的散点。

下面是我们如何构建符合这些要求的后端的示例:

server <- function(input, output) {
  model <- readRDS('decisiontree.rds')
  predictions <- data.frame(cbind(
    mtcars$hp,
    predict(object = model, mtcars)
  ))

  colnames(predictions) <- c('hp','pred_mpg')

  sc <- reactive({
    selected_car <- input$selectedcar
  })

  output$scatter <- renderPlot({

    select_car <- sc()

    (
      plot_all_cars <- ggplot(
        data = predictions,
        aes(x=hp, y=pred_mpg)
      ) 
      + geom_point(colour="darkgreen") 
      + geom_point(data=predictions[select_car, ], aes(x=hp, y=pred_mpg), colour="red", size=5)
    )
  }) 

让我们一步一步地详述我们的后端代码:

  • 首先,我们使用model<-readRDS('decisiontree.rds')
    加载我们的模型,并使用predict函数从我们的模型中获得预测。
  • 然后,我们定义我们的predictions object.colnames
  • 之后,我们设置了一个reactive对象。reactive对象是会影响任何其他对象的对象(需要计算)。例如,在这种情况下,select_car将影响应用于geom_point图中的dataframe的过滤器。由于这需要在后台进行一些计算,我们需要在这个对象上设置一个反应上下文,定义它用函数sc()调用。
  • 最后,我们用两个geom_point来定义plot_all_cars:一个用于所有汽车,另一个(将作为亮点)用于我们选择的汽车。注意select_car将包含从sc返回的反应对象,我们将使用左边的下拉菜单加载该对象。

现在让我们来看看我们的应用程序的外观:

三栏应用程序(突出显示马自达 RX4 Wag)-作者图片

如果我们突出显示另一辆车,我们的图的突出显示会发生变化:

三栏应用程序(突出显示克莱斯勒帝国)-作者图片

在前端,我们只需改变我们的第二列,为它添加一个plotOutput模块:

sp <- fluidPage(

  fluidRow(

    column(4, 
        selectInput(
          inputId = 'selectedcar',
          label = 'Car List',
          choices = rownames(mtcars)
        )
    ),

    column(4, 
           plotOutput('scatter')
           ),

    column(4, 'Column 3!')       
  )
)

只剩下一件事了!在右栏中绘制我们的决策树——让我们在后端也这样做,但是,首先,我们需要加载rattle库:

library(rattle)

然后,对我们的后端和前端层进行一些更改:

server <- function(input, output) {
  model <- readRDS('decisiontree.rds')
  predictions <- data.frame(cbind(
    mtcars$hp,
    predict(object = model, mtcars)
  ))

  colnames(predictions) <- c('hp','pred_mpg')

  sc <- reactive({
    selected_car <- input$selectedcar
  })

  output$scatter <- renderPlot({

    select_car <- sc()

    (
      plot_all_cars <- ggplot(
        data = predictions,
        aes(x=hp, y=pred_mpg)
      ) 
      + geom_point(colour="darkgreen") 
      + geom_point(data=predictions[select_car, ], aes(x=hp, y=pred_mpg), colour="red", size=5)
    )
  })

  output$dtree_plot <- renderPlot({
    fancyRpartPlot(model, sub='')
  })

}

sp <- fluidPage(

  fluidRow(

    column(4, 
        selectInput(
          inputId = 'selectedcar',
          label = 'Car List',
          choices = rownames(mtcars)
        )
    ),

    column(4, 
           plotOutput('scatter')
           ),

    column(4, 
           plotOutput('dtree_plot')
          )       
  )
)

超级简单!对于上面的后端代码,我们添加了:

output$dtree_plot <- renderPlot({
    fancyRpartPlot(model, sub='')
  })

在我们的前端引用它之后,我们最终的应用程序如下所示:

三栏应用程序—作者图片

如您所见,用 R 构建shiny应用程序非常简单。在这篇文章中,我们检查了一些重要的组件,例如:

  • 使用fluidPage构建我们应用程序的前端。
  • 使用自定义函数server构建我们应用程序的后端。
  • 使用idsreactive元件连接我们的前端和后端。
  • 使用不同的 UI 元素,如fluidRowsidebarPanel

我希望这篇文章给了你一些可以为你的项目开发的很酷的应用的想法。作为结论:shiny包含一堆可调整的元素,很难记住所有的元素。你可以保存的一个更重要的页面是闪亮文章,这是图书馆的官方文档,它将指导你了解我们在这篇文章中没有涉及的其他内容。

感谢你花时间阅读这篇文章!

以下是我们构建的应用程序的完整代码:

library(shiny)

# First App

sp <- fluidPage(
  "My first shiny page!"
)
server <- function(input, output) {
}
shinyApp(sp, server)

# Second App
sp <- fluidPage(

  sidebarLayout(

    sidebarPanel(
      selectInput(
        inputId = 'var1',
        label = 'Column X Axis',
        choices = colnames(mtcars)
      ),

      selectInput(
        inputId= 'var2',
        label = 'Column Y Axis',
        choices = colnames(mtcars)
      )
    ),

    mainPanel(
      plotOutput("scatter")
    )
  )
)

server <- function(input, output) {
  output$scatter <- renderPlot({
      ggplot(
      data = mtcars,
      aes_string(x=input$var1, y=input$var2)
    ) + geom_point(color='darkgreen')
  })
}
shinyApp(sp, server)

# Training model
library(rpart)

dtree <- rpart(data=mtcars, mpg ~ wt + cyl + hp,
               control = list(minsplit=1))

# Save dtree file
saveRDS(object=dtree, file='decisiontree.rds')

# Third App
sp <- fluidPage(

  fluidRow(

    column(4, 
        selectInput(
          inputId = 'selectedcar',
          label = 'Car List',
          choices = rownames(mtcars)
        )
    ),

    column(4, 
           plotOutput('scatter')
           ),

    column(4, 
           plotOutput('dtree_plot')
          )       
  )
)

server <- function(input, output) {
  model <- readRDS('decisiontree.rds')
  predictions <- data.frame(cbind(
    mtcars$hp,
    predict(object = model, mtcars)
  ))

  colnames(predictions) <- c('hp','pred_mpg')

  sc <- reactive({
    selected_car <- input$selectedcar
  })

  output$scatter <- renderPlot({

    select_car <- sc()

    (
      plot_all_cars <- ggplot(
        data = predictions,
        aes(x=hp, y=pred_mpg)
      ) 
      + geom_point(colour="darkgreen") 
      + geom_point(data=predictions[select_car, ], aes(x=hp, y=pred_mpg), colour="red", size=5)
    )
  })

  output$dtree_plot <- renderPlot({
    fancyRpartPlot(model, sub='')
  })

}

shinyApp(sp, server)

如果你想参加我的 R 课程,请随时加入这里( R 编程绝对初学者 )或这里( 数据科学训练营 )。我的 R 课程适合初学者/中级开发人员,我希望有你在身边!

构建您自己的数据集:优势、方法和工具

原文:https://towardsdatascience.com/building-your-own-dataset-benefits-approach-and-tools-6ab096e37f2

构建自己的数据集而不是使用预建解决方案的重要性

Maksym Kaharlytskyi 在 Unsplash 上的照片

如果我们在谷歌上搜索如何成为数据科学家,我们会发现大量资源敦促我们学习线性代数和微积分的基础知识。

给我们的动力是,这些将是我们理解后面要研究的机器和深度学习算法的基础。它们是准备材料,在我看来,资源表明这是正确的。这是没有办法的。

然而,我发现,尽管作为一名数据科学家,这对成长和理解此事是必要的,但这还不够。有很多技术高超的分析师在努力找工作,并在专业环境中证明自己的价值。

我在 2022 年 1 月写了这篇文章,但我很确定这种说法对未来也适用。去看看 Kaggle(例如这里的)、Reddit(在 /r/machinelearninglearnmachinelearning )或 Discord 频道上关于这个话题的任何讨论。

这些人非常非常熟练。然而,他们在面对现实中挣扎。这一现实需要超越数据科学或计算机工程的知识。我不想傲慢地说我知道真相,这些人应该跟着我。事实并非如此。

我自己也面临着这种困境,在这份工作中经历了 6 年之后,我今天仍然经常面临这种困境。这并不容易,但我逐渐开发了启发法和方法,证明对提高我在工作区内外的价值很有用。

我觉得我今天可以分享的建议是许多在线数据科学课程中不常听到的,即从头创建数据集来解决我们感兴趣的问题。

让我们开始吧。

从头构建自己的数据集有什么好处?

有几个原因促使我推荐这种方法。这些问题从实用主义到更个人化的都有。以下是一个概述:

  1. 我们对该项目自始至终承担全部责任
  2. 我们完全拥有我们所使用的材料
  3. 我们对问题和我们的数据产生了非常深刻的理解(具体知识)
  4. 我们的问题无法通过已知的开放数据集来解决

在我看来,责任、所有权、知识和影响力是个人和职业发展的核心。让我们一个一个地探索它们,以理解从头开始创建数据集的业务。

有责任

从零开始创建数据集迫使我们处于完全负责的境地——数据中的任何错误或偏差都是我们自己造成的。因此,能够正确检索数据是非常重要的,要考虑测量系统的有效性和一致性(例如,如果我们使用物理传感器),或者如果数据是在线找到的(例如,带有文本),要编写干净且结构良好的代码。

所有权

当我们参与进来,花时间去做刮擦、创建调查或进行采访等肮脏的工作时,我们用这些工具培养了纪律性、耐心和技能。

如果使用的数据集是公开的,这就不会发生,因为其他研究人员过去已经为我们做了这项工作。拥有我们数据的所有权也使我们能够有目的、有目的地领导任何可用于研究的团队。

特定知识

这条道路将充满挑战,我们将独自或一起面对。在任何情况下,我们都可以肯定,我们会带着对问题的更具体和更一般的认识越过红线。如果我们多次探索这条道路,我们也可以成为这方面的专家(所谓的利基)。

杠杆作用

拥有杠杆意味着我们能够做或提供不是每个人都能做的事情。拥有杠杆赋予我们价值。如果我们有很大的优势,我们可以提供服务于他人的解决方案,并且他们能够支付得起。

当我们为一个公共数据集不能充分覆盖的问题从头构建数据集时,我们正在创建一个有价值的资产。这个价值或多或少取决于项目和最终产品的目标。

基本上,和几乎所有的发展项目一样,你从中获得的东西对个人和职业发展几乎总是很重要的。企业家的态度出现了——如果项目在所有提到的层面上都是我们的,那么我们必须能够做出牺牲,让它有机会看到光明。在数据科学中,这种牺牲通常始于训练数据集的创建。

方法

假设我们想在电脑前模拟我们的健康状况。假设是,一天中的某些时候,我们的身体会变得更加疲劳,这种疲劳可能是由于日常工作或生理波动造成的。

对于任何想研究这一现象的人来说,正确的方法是放下一个小的实验设计。事实上,建模阶段在所有方面都跟随着实验阶段

你不会对你一无所知的事物建模,而足够准确地了解事物的唯一方法是运用科学方法。

我提到的例子很有趣——让我们看看如何将科学方法应用于这样一个项目。以下是步骤

  1. 假设定义 研究几乎总是基于一个或多个实验假设。有时它可能只是探索性的,但大多数都有支持它们的研究人员所做的陈述。在我们的情况下,我们刚才已经提到了:一天中有一些时刻我们更累,这些时刻是由于工作压力或生理波动的原因。 我们的目标将是通过找到支持相反观点的证据来证伪这一主张(波普尔的可证伪性原理)。如果这个证据很重要(从统计学上来说),那么我们就能够拒绝零假设(描述实验前世界的假设)并接受另一个假设(我们已经通过实验收集了足够证据的假设)。
  2. 准备
    准备阶段允许我们组织活动流程,并找到我们需要的各种工具。在我们的情况下,我们应该在工作日收集数据。我们将购买传感器应用于身体,以跟踪心率、血压和血饱和度。此外,我们可以使用软件来跟踪我们在电脑上的活动。我们还必须考虑一天中的时间:最初的直觉是,每个活动的索引将是一个时间戳,指示精确的时间。
  3. 数据收集和实验 在这个阶段我们试图观察或造成证伪我们假设的效果。在我们的例子中,由佩戴的传感器和使用的软件表示的指示器将告诉我们实际发生了什么。也许只有当我们在工作中与一群特定的人交谈时,或者当我们做了一件我们不太喜欢的事情时,我们才会观察到精力水*的下降。发生的每一个事件都将通过在前面一点中设置的技术设备进行测量和存储。
  4. 结果分析
    这是过程的最后一个阶段(除非你发表了一篇关于它的论文,在这种情况下该方法结束于陈述阶段)。这里我们将应用大量的数据分析技术来验证和阐明研究结果。

只有在分析了我们的研究结果之后,我们才能说我们理解了我们一开始所面临的问题。我补充说,这是我们在对观察到的现象建模时唯一合乎道德的时刻

我在伦理上使用这个词是因为,虽然建模可以不遵循所描述的方法,但只有通过这条途径,研究的作者才能从一开始就得到他想要的东西:对现象的更好理解

任何其他尝试都是由超出更深层次理解的目标传达的,例如,给某人留下好印象(所谓的地位游戏)或解决其他人的问题,这些人反过来不需要更深层次地理解问题。

我想具体说明,不想追求一个纯知识导向的目标并没有错。我们都在玩地位游戏,我们都喜欢做好工作并为此获得报酬。这不是重点。

关键是,如果我要建议我自己的儿子如何在这样一个领域进行个人项目,我会对他说这些话。不做并没有错…但是我认为如果你做了,你会成为一个更好的人,无论是在职业上还是个人方面。

分析师从头开始创建数据集的有用工具

这里列出了我在职业生涯中为项目收集数据的方法。发展应用这些技术的技能对于任何希望有机会独立完成项目的分析师都是有用的。

网页抓取

绝对是最著名的数据检索数字技术之一。如果你足够熟练,现在没有什么是刮不到的。它包括从网站或其他在线资源收集数据。

这些数据必须在 web 浏览器中可见,并且必须设计为可供其他用户查看。

不过还是有规则的:不要抓取明确要求你不要抓取的网站——这是不道德的——也不要孜孜不倦地抓取(也就是说,不要淹没服务器)。当抓取时,我们向服务器发送请求以接收数据并保存它。如果我们做得太快,而且没有控制,我们可能会损坏服务器。

最大的障碍是社交媒体,但仍然有有效的方法可以在网上找到。

我们可以在 Python 中使用的工具有

  • 对于小项目-> beautiful soup+Requests 的组合(这里的是一个很好的开始模板)
  • 对于大型项目-> 刺儿头
  • 对于任何 JavaScript 渲染需求-> 剧作家

调查

我们经常忘记调查是非常强大的数据收集技术。有一些像 Pollfish 这样的网站允许你提交大规模的调查,并通过非常精确的分析采访网络上成千上万的用户。

物理传感器

由于物联网领域和 Apple Watch 等可穿戴设备的发展,物理传感器的使用在过去十年中有了很大增长。今天,我们可以很容易地为我们的数据收集设置物理仪器,包括人和物。

剧本

如果我们拥有一个网站,并且精通 Javascript 或 Php,我们可以编写跟踪脚本,在用户同意的情况下,可以收集其使用行为的数据。有点像 Google Analytics 和 HotJar。我想补充一点,收集此类信息可能会受到 GDPR 法规的管辖,因此我们必须谨慎并负责任地使用数据。

结论

最后,我必须提到,购买数据也是可能的,根据质量不同,价格也不同。

通常,这些数据很难由单个个人找到(例如来自太空深处或海洋深处的数据),并且由于它们所描述的现象,很难精确地汇总在一起。

数据科学家经常购买数据集,以便将大量数据加入到他们已经拥有的数据存储库中。

如果您想支持我的内容创作活动,请随时关注我下面的推荐链接,并加入 Medium 的会员计划。我将收到你投资的一部分,你将能够以无缝的方式访问 Medium 的大量数据科学文章。

https://medium.com/@theDrewDag/membership

我希望我对你的教育有所贡献。下次见!👋

Python 中的 Burrows Wheeler

原文:https://towardsdatascience.com/burrows-wheeler-in-python-c07cbf71b3f0

神奇的算法来索引和压缩大型字符串数据,然后迅速找到子字符串

照片由 SOULSANAUnsplash 上拍摄

Burrows Wheeler 变换(BWT)是由 Michael Burrows 和 David Wheeler 在 1994 年开发的。简而言之,BWT 是一种字符串转换,作为无损压缩的预处理步骤。BWT 的实现展示了线性 O(n)性能和空间复杂度。最初的设计是为了用 bzip2 等技术准备压缩数据,BWT 在生物信息学中获得了突出的地位,允许快速绘制短阅读图谱,为高通量基因测序铺*了道路。

在本文中,我们将在 python 中实现一个简单的 BWT,然后展示如何使用简化的后缀数组 BWT 找到不匹配的小段。

BWT 算法:

  1. 旋转字母:苹果变成['eappl ',' leapp ',' pleap ',' pplea ',' apple']
  2. 按字母顺序排列旋转单词:['apple ',' eappl ',' leapp ',' pleap ',' pplea']
  3. 取最后一列:elppa

苹果公司的 BWT 成为埃尔帕

BWT的简单实现

用后缀数组实现模糊字符串搜索的 BWT

在这个 python 实现中,我们将生成一个后缀数组,并使用它来执行 BWT。

来自后缀数组的 BWT 算法:

  1. 生成后缀数组:苹果变成[5,0,4,3,2,1]
  2. 后缀数组 BWT 的实现:apple + [5,0,4,3,2,1]变成:e$lppa
  3. 查找匹配位置:apple 中的应用程序返回位置[0]
  4. 查找不完全匹配:apple 中不匹配=1 的 apl 返回位置[0,1]

下面的代码块定义了用于查找不完全匹配的函数。 generate_all("apple") 下面块中的 terminal 函数返回一组唯一字母、BWT 输出、 LF 映射、唯一字母索引和后缀数组。

generate_all("apple")

退货:

({'a ',' e ',' l ',' p'},
'e\(lppa ', {'e': [1,1,1,1,1,1,1,0], 'p': [0,0,0,1,2,2,2,0], 'l': [0,0,1,1,1,1,1,0], '\)': [0,1,1,1,1,1,0],【0

在 BWT 数据中识别(模糊匹配)子串

这里,我们通过以下算法识别 BWT 字符串中的匹配。所有搜索都是反向进行的,例如在苹果中查找应用程序,首先是查找“p”,然后是“pp”,然后是“app”。

注意:Last First (LF)属性是最后一列中第 I 次出现的字母[X]对应于第一列中第 I 次出现的字母[X]。

  1. 在 BWT 的第一列中查找搜索字符串中最后一个字母的范围
  2. 看看 BWT 最后一栏的相同范围
  3. 在 LF 映射中查找下一个要搜索的字符。将“下一个字符”列[NC]设置为等于 LF 映射矩阵中观察范围的映射条目。将 LF 映射矩阵中的下一列[NC+1]设置为等于当前范围最后+1 行中的“下一个字符”值。
  4. 找到第一行中“下一个字符”的范围,并使用 NC & NC+1 在“下一个字符”范围内找到正确的子范围。
find("app", 'apple', mismatches=0)

回报: [0]

我们还实现了一种方法来识别子序列中的错配。但是字符必须相同。

find("apZ", 'apple', mismatches=1)

返回: []“空返回,因为 Z 不在引用字符串中”

find("ape", 'apple', mismatches=1)

退货: [0]

执行后缀数组 BWT 的完整笔记本可以在 GitHub 上找到。

BWT &寻找爱丽丝梦游仙境

安妮·斯普拉特在 Unsplash 上的照片

我们下面的组块将展示 BWT 的有用性和速度。我们将从古腾堡的项目下载一份爱丽丝漫游奇境记,并从第 8 章开始阅读课文。这留给我们一个 61235 个字符的文本文档,由 3762 行 27432 个单词组成(对于我们的目的来说仍然很大)。然后我们将寻找短语“砍掉她的头”被提及的次数。我们将比较我们的 BWT 搜索和默认的 python 字符串搜索。

创建后缀数组并执行 BWT 大约需要 3 秒钟。这就是神奇的地方。创建 BWT 后,我们可以执行比标准字符串搜索快几个数量级的搜索。即使是模糊搜索也比标准字符串搜索快 100 倍。哇!

字符串搜索的速度性能比较。图片作者。

包裹

这篇文章的代码可以在我的个人GitHub 上找到。将小的基因序列映射成一个巨大的基因组串的速度是 BWT 搜索在生物信息学工具中流行的原因,如蝴蝶结BWA 。我的名字是科迪·格利克曼,可以在 LinkedIn 上找到我。一定要看看我的其他一些文章!

https://glickmancody.medium.com/membership

DBT 的商业逻辑

原文:https://towardsdatascience.com/business-logic-in-dbt-cf72a31bbcfd

实现定制逻辑有很多种可能性,这个框架可以帮助您对选项进行排序。

DBT 的默认配置。图片作者。

我真的很喜欢使用 DBT 来构建数据管道,它执行的框架要求你以标准化的方式构建管道。该工具完美地结合了数据工程和数据科学,为前者提供了一种动态编码的方式,并约束了后者。作为一名数据科学家,我花了数年时间,在数据管道内外,通过运行 python 进程动态生成复杂的 SQL 代码。模板是这种自由流动的数据科学工作的第二天性,DBT 为数据工程师展示了这种工具以及适用于大多数管道的结构。

数据工程的核心是业务逻辑:转换本身包含打开数据的关键信息。对于我们这里的例子,让我们将美国的一个state列重新编码为一个region:四个人口普查被指定为东北、南部、中西部和西部的区域之一。为了帮助我们在构建这一渠道的各种选项之间做出决定,让我们列出我们关心的问题:

  1. 人类可读性:我们应该能够理解逻辑,并看到值来自哪里。
  2. 灵活性:我们希望为这种映射中的变化做好准备,例如,一个新的州加入美国或一个现有的州切换区域。
  3. 机器可读性和 DRY:这个逻辑应该可以被程序解释,这样我们就不需要重复硬编码的值。

选项 1:将您的业务逻辑硬编码在 SQL 中

从各方面来看,这都不太理想,但这可能是一个起点。它可能看起来像这样:

SELECT state, 
       CASE WHEN state IN ('CT', 'ME', ...) THEN 'Northeast'
            WHEN state IN ('IL', 'IN', ...) THEN 'Midwest'
            ...
       ELSE NULL END AS region
  FROM {{ ref('my_table') }}

您可以只编码三个区域,让ELSE语句捕捉第四个区域。我们将编码所有四个:显式击败隐式。

使用这种硬编码的逻辑,每次更改都需要更改代码。

如果您的逻辑还没有被硬编码到 SQL 中,请选择选项 2 或 3。让我们不要对这种方法进行过多的讨论,向前推进到在每一点上都更好的解决方案。

选项 2:将逻辑放在一个表中,并使用一个连接来执行它

这里有一个我可以支持的想法。您的代码就是您的代码:它从 state 列创建 region 列。如果确切的映射发生变化,数据需要更新,但不是您的代码!这些数据可以用许多不同的方式来管理,甚至可以由业务涉众或下游用户来管理。具体来说,这应该是这样的:

SELECT state, 
       lookup.region
  FROM {{ ref('my_table') }}
  JOIN {{ source('fact_schema', 'lookup_table') }} lookup
       USING(state)

这与我们的目标非常吻合,现在我们需要一种存储逻辑本身的方法。我认为有三种选择:

选项 2(a):使用 DBT seed csv

dbt 中的种子是 dbt 加载到数据库中的 csv 文件,供下游使用。这使得 csv 文件处于版本控制中,对于这个用例来说,这是一个很酷的特性。这确实是 dbt 中 seed 功能的预期用途,我同意他们在文档中的建议:

种子是您的 dbt 项目中的 CSV 文件(通常在您的seeds目录中),dbt 可以使用dbt seed命令将其加载到您的数据仓库中。

种子可以像引用模型一样在下游模型中被引用——通过使用ref 函数

因为这些 CSV 文件位于您的 dbt 存储库中,所以它们是受版本控制的,并且代码是可审查的。种子最适合不经常改变的静态数据。

种子的良好用例:
-国家代码到国家名称的映射列表
-从分析中排除的测试电子邮件列表
-员工帐户 id 列表

选项 2(b):将它作为一系列插入存储在 SQL 文件中

这个选项很臭,我就不多说了。明确地说,我的意思是编写一个.sql文件,将数据直接插入到表中。您可以在 dbt 中这样做,这里有一个简单的版本来说明这个想法,如lookup_table.sql:

WITH data as (
SELECT 'CT' as state, 'Northeast' as region
UNION
SELECT 'ME' as state, 'Northeast' as region
UNION
SELECT 'IL' as state, 'Midwest' as region
UNION
SELECT 'IN' as state, 'Midwest' as region
)
SELECT state, region
FROM data

选项 2(c):存储在 python/R 中,并直接推送到数据库

这里的主要问题是,您将项目分成了两个部分:预 dbt 步骤和 dbt 步骤。虽然有些情况下这是有意义的,但是我们可以在 dbt 中轻松完成这一步,所以我们不要追求这个选项。

选项 3:在动态查询中使用 DBT 变量

dbt_project.yml中,您可以定义变量,然后在查询中使用这些变量。我们可以将数据作为变量存储在 yaml 中,如下所示:

vars:
  state_lookup:
    Northeast:
      - CT
      - ME
    Midwest:
      - IL
      - IN

然后我们将动态生成 SQL,如下所示

SELECT state,
       CASE {% for k, v in var("state_lookup").items() %}
            WHEN state in ({% for t in v %}'{{ t }}'{% if not loop.last %}, {% endif %}{% endfor %}) THEN {{ k }}{% endfor %}
            ELSE NULL END AS region
  FROM {{ ref('my_table') }}

这里的中间部分只是构建一个逗号分隔的列表,为它编写一个函数会使它看起来更好。让我们用一个csl过滤器(逗号分隔列表)快速查看一下:

SELECT state,
       CASE {% for k, v in var("state_lookup").items() %}
            WHEN state in ({{ v|csl }}) THEN {{ k }}{% endfor %}
            ELSE NULL END AS region
  FROM {{ ref('my_table') }}

这是人和机器都可读的(耶为 yaml!),而且它很灵活,是目前为止我最喜欢的选择。愿你的数据管道畅通,你的传呼机警报少!

购买一个域,连接到您的应用程序,并使用 SSL 保护 AWS 中的教程

原文:https://towardsdatascience.com/buy-a-domain-connect-to-your-app-and-secure-with-ssl-tutorial-within-aws-c7479aa24d64

大多数文章开发了原型,但是没有将 MVP 与领域联系起来。我将在本文中展示如何使用 AWS 实现这一点。

艾通过作者创造艺术。 上见下见【NFT】;灵感来自阿曼达·冯月 Unsplash

目录

关于这篇文章

在本文中,我将介绍在 AWS 中启动一个示例 web 应用程序的步骤。在我的上一篇文章中,我谈到了用 python 创建自己的 web 应用程序,以及如何用 AWS Lambda 部署它,或者用 AWS Fargate 来完成:

我使用 AWS 是因为我已经在我的上一个项目中使用了它,并发现它有很好的文档记录,操作起来很直观,即使有如此多的选项可供选择。设置一切非常简单,你会发现很棒的官方和非官方文档。然而,我意识到大多数文章都是关于应用程序的发布,而不是如何把它和自己的域名联系起来。这对于测试来说很好,但是如果你想发布一个原型,你当然需要你自己的领域。尤其是域名本身通常非常便宜。

因此,现在我想展示如何在 AWS 中为您的 web 应用程序购买和注册域名。

我将用一些个人观点来说明我是如何做到的。

从目录中可以看出,本文的主要部分包括:

  • 购买域名
  • 将域连接到应用程序
  • 添加负载*衡器
  • 启用 https

先决条件:

  • 拥有包含网页的容器化(Docker)应用程序
  • 购买域名的意愿
  • 在 AWS 中使用 Fargate 集群(尽管对于 AWS 中任何类似的容器管理服务,许多步骤都是类似的)

如果你在这一点上有困难,可以看看我以前关于这些主题的文章。

我应该使用什么类型的 AWS 基础设施

这里有一篇很棒的文章我要推荐。他在我看来总结得很完美。因此,我想要的是部署我的容器,但不想更深入地了解基础设施。这就是我选择 AWS Fargate 的原因。

作者截图

但是,这里需要注意的是,AWS Fargate 没有附带自由层选项!(写作时)。小型设置非常便宜,但要记住。

购买域名

第一件事当然是买域名。

请注意,购买域名不包括在 AWS 免费层系统中。域名费用从 10 美元起。

只需通过 AWS 仪表板进入域部分,然后单击“注册域”。

作者截图

然后,您将被引导完成选择域名的过程。

确保事先设置有效的支付方式。如果不是这样,购买过程将被取消,但您的购买将在账单上。我真的认为这是一个错误。我向 AWS 提交了一份报告,并希望这将得到解决,以便进一步使用。为了避免这种情况,请确保您的支付方式有效。您可以在您的帐户设置中找到它。

一切正常后,您将收到一封电子邮件,您的注册域名将出现在相应的选项卡下。(见上面我的截图)。

使用您的 AWS 公共 IP 地址连接域

有多种方法可以做到这一点。我只会用一种方式展示。

转到 Route 53 菜单,导航到“托管区域”页面,为您的域创建一个新记录。

作者截图

然后创建一个新记录。

从 ec2 任务概述页面获取公共地址,并将其作为静态值添加到您的域中。对于记录名称,只需在名称中添加一个“www ”:

作者截图

有几件事要记住:

  • 这是一个静态链接,这意味着如果 IP 地址发生变化,您需要更新此条目
  • 这要求您的应用程序将其内容发送到 IP 地址,而不是其他端口(通常是 8080)。您不能在此指定端口。您必须在设置 Docker 容器的 EC 实例/集群/任务中对此进行更改。
  • 当我们添加负载*衡器并启用 https 时,此条目将会改变!(见下文)

作者截图

但是,正如您所看到的,网页是通过 HTTP 而不是 HTTPS 提供的。所以下一步是添加安全证书。

启用 HTTPS

老实说,这比我一开始预想的要多得多。知道原因很重要。

向群集添加规则

转到 EC2 群集的安全组,确保添加(或已经添加)了相应的入站规则以允许 https 设置。

作者截图

获得证书

导航到 AWS 证书管理器

作者截图

添加您的域

作者截图

并填写您认为合适的其余问题。

我选择了通过电子邮件确认。批准此邮件后,您将成功颁发证书。

作者截图

添加负载*衡器

AWS 中添加 SSL 证书的一种方法是添加负载*衡器。因为我想*衡服务器的负载,所以我将展示我是如何做的。

只需输入负载*衡器并选择 EC2 功能:

作者截图

然后,我们选择应用程序负载*衡器:

作者截图

  1. 为您的负载*衡器命名
  2. 该计划将面向互联网,并
  3. 选择 https,
  4. 选择可用子网

作者截图

在“安全设置”选项卡中,您可以选择之前设置的证书(从 ACM)

作者截图

  • 建立一个安全组。确保允许 https 和 http 流量

作者截图

  • 配置路由。确保对 Fargate 使用目标类型“IP”
  • 监听目标组中的 HTTP 协议

作者截图

  • 保留目标中的默认值,因为它将自动分配!
  • 回顾一切并创建您的负载*衡器
  • 如果您想将路由直接设置为 DNS 地址,您可以复制负载*衡器的 DNS 名称

作者截图

注意,我只是在这里设置了 https 请求。理想情况下,您也可以在这里设置从 http 到 https 域的重定向。更多信息可以在这里找到。

更新路线 53

  • 进入 Route 53 >路由区域>点击域名>
  • 转到记录集
  • 使用域名创建托管区域
  • 创建/编辑名称为空的记录集,键入 A,
  • 应用程序负载*衡器的别名,并选择您创建的 DNS

作者截图

创建集群服务

在服务中,您可以添加先前定义的负载*衡器。要理解服务和任务之间的区别,请看这个 SO 问题

  • 转到 ECS 群集中的“service”选项卡
  • 根据需要填写所有内容

作者截图

  • 在下一步(网络设置)中,使用 VPCs 的默认值
  • 添加一个应用程序负载*衡器,将监听器设置为 https(端口 443)并指定您的目标组(您已经在负载*衡器的路由中定义了)

作者截图

  • 完成这些步骤并创建服务

现在,您需要等待一段时间,直到一切都设置好并运行。

使用 https 负载*衡器查看您的结果

现在,您可以导航到任务的公共 IP:

作者截图

当然还有您的 https 域:

作者截图

如果你在整个过程中有问题,看看这个关于如何设置 https 的问题。我发现它很有用。

注意:由于该应用程序正在开发中。当您尝试访问时,可能无法访问该域。这是我所希望的,因为我仍然在玩 AWS 架构设置,并希望避免太多的成本。

这篇文章是一个更大项目的一部分,我将在www.shouldibuycryptoart.com下发布这个项目。该应用程序正在开发中。如果你想关注它的发展,请随时联系我或关注我的社交媒体账户。

有用的文章

放弃

我与本文中使用的任何服务都没有关联。

我不认为自己是专家。除了做其他事情,我只是记录事情。因此,内容并不代表我的任何专业工作的质量,也不完全反映我对事物的看法。如果你觉得我错过了重要的步骤或者忽略了什么,可以考虑在评论区指出来或者联系我。

这是 2021 年 3 月 22 日编写的。我无法监控我的所有文章。当你阅读这篇文章时,提示很可能已经过时,过程已经改变。

我总是乐于听取建设性的意见以及如何改进。

关于

丹尼尔是一名艺术家、企业家、软件开发人员和商业法毕业生。他的知识和兴趣目前围绕着编程机器学习应用程序及其所有相关方面。从本质上说,他认为自己是复杂环境的问题解决者,这在他的各种项目中都有所体现。

->在 https://medium.com/subscribe/@createdd订阅此处或在媒体上阅读更多->https://medium.com/@createdd/membership

连接到:

直接:

艺术相关:

购买到死:理解顾客终身价值

原文:https://towardsdatascience.com/buy-till-you-die-understanding-customer-lifetime-value-eb2c0a23b85

BG/NBD 模型解释道。

了解每个特定客户给你的业务增加了多少价值是至关重要的,尤其是因为它有助于确定客户获取和保留预算。客户终身价值模型是获得这种洞察力的有用工具。然而,似乎有很多关于它们的混淆,而且很明显,每个人都在错误地使用它们。我们如何仅从交易数据中预测终身价值和客户流失?BG/NBD 代表什么?这些模型依赖什么样的数据?让我们来了解一下!

了解基本情况

在我们深入研究客户终身价值或 CLV 模型之前,让我们先弄清楚一些基本的定义和假设。

什么是客户终身价值?

客户终身价值是企业在特定客户的整个生命周期中从他们那里获得的所有收益,通常以货币形式表示。

顾客的一生当然不需要指他们实际的出生和死亡。相反,客户的一生被理解为他们与公司打交道的时间。简而言之,它是顾客第一次购买和最后一次购买之间的时间。

客户终身价值是企业在特定客户的整个生命周期中从他们那里获得的所有收益,通常以货币形式表示。

为了计算特定客户的 CLV,我们需要预测他们在流失之前购买的产品或服务的数量,并乘以一些货币数字,如价格(这将导致 CLV 表现为每个客户的收入)或利润率(这将使我们的 CLV 估计每个客户的利润)。

如果您是一名会计,您可能希望将结果折算为今天的美元价值,这样您就可以知道每个客户现在的价值(也就是说,将未来的通货膨胀考虑在内)。就是这样。

(非)合同环境下的客户终身价值

预测客户购买量以及他们何时流失的任务听起来像是机器学习模型的一个很好的用例。这是正确的,前提是你已经为你的模型找到了一些可以学习的目标。输入合同数据和非合同数据之间的重要区别。

当公司和客户之间的关系以书面协议或合同的形式正式确立时,我们就处于契约的环境中。想想你的网飞订阅。网飞知道你订阅和取消订阅的确切日期,那是你的一生。他们也有很多关于你的其他数据:你看了什么电影,你对它们的评价如何,一天中你看得最多的时间,可能还有你的年龄和性别等等。基于所有这些数据,建立一个预测一个人的流失概率的机器学习模型是相当简单的。

利用合同数据,机器学习模型可以预测 CLV。在非合同环境中,我们需要不同的方法。

现在想想你最*在一家网上商店购物。你没有与他们签订任何合同,这使得他们无法确切知道你是否已经搅拌。你可能会决定明天或十年后再和他们一起购买。此外,除了你的购物历史,他们没有太多关于你的信息(特别是如果你是作为客人在网上购物)。这是一个非合同设置。在这种情况下,你的亿万参数巨型变压器将没有什么用处,因为没有基本事实标签,你无法训练有监督的机器学习模型。这就是为什么我们需要不同的方法。

至死不渝地购买模型

多年来广泛使用的估算非合同数据 CLV 的最流行和最准确的方法是一类被称为“买到死”的概率模型。其中一个特别的例子是 BG/NBD Gamma-Gamma 模型,它已经被广泛采用。

事实上,这是两个独立的模型:BG/NBD 预测每个客户的未来交易数量,然后将其输入 Gamma-Gamma 模型,以预测其货币价值。让我们看看他们两个。

贝塔几何/负二项分布模型

BG/NBD 模型预测客户未来的购买数量及其存活的概率,即在非合同环境下,或换句话说,仅根据过渡数据,尚未被搅动。它通过对模型的参数强加一些分布假设来做到这一点,我们很快就会谈到。但是首先,让我们来看看我们需要什么样的数据。

数据要求

我们的模型仅基于交易数据。这意味着对于每个客户,我们需要知道他们每次购买的时间、数量和价值。这些数据应该很容易从公司的交易系统中获得。

有了手头的数据,我们需要为每个客户计算四个不同的数量:

  • 最*,或者客户第一次和最后一次购买之间的时间;
  • 频率,或顾客在
    T5 购物的时间段计数(重要提示:网络上的一些资源声称频率是顾客重复购物的次数,这使得它比总购物次数少一次。这是不正确的,因为 frequency 应该忽略同一时间段内的多次购买。);
  • 时间,或者客户的年龄(这是客户第一次购买和我们分析的结束日期之间的时间差,往往是数据中可获得的最后日期);
  • 货币价值,或客户重复购买的*均收入或收益)。

新*和时间可以用任何感兴趣的单位来表示:小时、天、周等等。

为了让您对必要的事务数据有一个大概的了解,让我们来看看btyd Python 包(停止主动维护的流行的lifetimes包的继承者)附带的 cdnow 数据集。

 customer_id       date  volume  value
0           00001 1997-01-01       1  11.77
1           00002 1997-01-12       1  12.00
2           00002 1997-01-12       5  77.00
3           00003 1997-01-02       2  20.76
4           00003 1997-03-30       2  20.76
              ...        ...     ...    ...
69654       23568 1997-04-05       4  83.74
69655       23568 1997-04-22       1  14.99
69656       23569 1997-03-25       2  25.74
69657       23570 1997-03-25       3  51.12
69658       23570 1997-03-26       2  42.96

我们可以很容易地解析这些数据来提取模型所需的特征。

请注意,我们删除了频率为零的所有行,即只购买了一次的所有客户。他们无论如何也不会对模型做出贡献。我们解析的数据帧现在看起来如下。

 frequency  recency      T  monetary_value
customer_id                                           
00001              0.0      0.0  545.0        0.000000
00002              0.0      0.0  534.0        0.000000
00003              5.0    511.0  544.0       27.140000
00004              3.0    345.0  545.0       23.723333
00005             10.0    367.0  545.0       35.628000
                ...      ...    ...             ...
23566              0.0      0.0  462.0        0.000000
23567              0.0      0.0  462.0        0.000000
23568              2.0     28.0  462.0       49.365000
23569              0.0      0.0  462.0        0.000000
23570              1.0      1.0  462.0       42.960000

模型假设

是时候讨论一下贝塔几何/负二项分布模型本身了。让我们看看它是如何工作的,它做了哪些假设。

  • 交易。该模型假设,只要客户还活着,他们的交易数量就遵循一个泊松过程,并且具有某个比率λ。例如,λ=2 意味着顾客*均每个时间段购买两次。每个客户的比率λ是不同的,其在所有客户中的分布被假定为伽玛分布。客户死了(也就是他们搅了),成交笔数自然是零。
  • 搅动。每次购买后,客户可能会以某种概率流失 p. 这种概率对于每个客户都是不同的,其在所有客户中的分布被假设为 Beta 分布。

现在让我们花一点时间来思考我们的模型的假设。这里有几件事可能会引起混淆,需要详细说明。

首先,为什么用泊松分布来模拟客户的交易,这意味着每次购买的发生都独立于前一次?这在某些用例中可能比在其他用例中更错误(在我买了一台新冰箱后,我不会很快需要另一台)。尽管如此,做出这个简化的假设使得模型的数学更容易,并且在实践中运行良好。嘿,毕竟,任何模型的工作都是简化现实,以允许对其做出推论!

第二,我最*被一个客户问了一个有趣的问题:交易数据需要服从泊松分布吗,这是使用模型的先决条件吗?答案是否定的!首先,您可能甚至没有足够的数据来估计每个客户遵循某种特定分布的概率。然后,同样,该算法用泊松分布对数据建模以简化事情,因为泊松是这种类型数据的自然选择。只要您的数据包含每个客户在不同时间段的多次交易,就没问题!

模型拟合

让我们把模型放在一起。我们如何将它与数据相匹配,结果如何?想想简单的线性回归模型。当您将它与数据拟合时,结果是模型参数,在这种情况下是对目标的特征影响。在我们的 BG/NBD 模型中,目标也是估计模型参数的值。

BG/NBD 模型的工作是估计两种概率分布的参数:

  • 伽马分布,客户的个人交易率λ由此而来,
  • 贝塔分布,客户的个人流失概率由此而来 p.

β有两个参数,习惯上称为 ab ,而γ也需要两个参数: rα。这总共有四个模型参数。它们是使用最大似然法估计的,这意味着选择这样的参数值,使模型最有可能产生我们实际拥有的数据。让我们在实践中看到它。

 coef  se(coef)  lower 95% bound  upper 95% bound
r        2.009639  0.041406         1.928483         2.090795
alpha  154.393028  3.700599       147.139853       161.646203
a        0.455240  0.019355         0.417304         0.493175
b        1.264726  0.069044         1.129400         1.400051

这里我们有四个模型参数及其估计值、标准误差和置信区间

分析模型

为了验证我们的模型是否有意义,要做的第一件事是绘制它已经估计的两个分布,以直观地评估它们的形状。

模型估计的 Beta(左)和 Gamma(右)分布。图片由作者提供。

两者似乎都有道理。最重要的是,大多数客户的流失率都很低,这是他们所期望的。

下一步是看一下频率/新*图表。为了画出它,我们需要确定数据中的频率和新*性的范围。然后,我们为两者的所有可能组合创建一个精细的网格,并将它们提供给模型,以便它预测在预定义的时间跨度内未来购买的预期数量(稍后将详细介绍如何进行预测)。在这里,我们设置了T=7来获取下周的预期购买数量。

不同*期和频率的客户的预计购买次数。图片由作者提供。

显而易见,我们可以预期,历史上有着高频率和高新*性的客户会购买最多的产品(右下方)。从这个最佳位置(右上角)向图的顶部移动的是那些已经有一段时间没有购买的高频客户。这可能意味着他们已经搅动,不会再购买。另一方面,最佳购买点左侧(左下角)是不经常购买的客户,但我们最*见过他们,所以他们可能会也可能不会再次购买,因此预测下降的尾巴伸向左下角。

接下来,我们来看看流失概率。或者更准确地说,具有特定频率/新*组合的客户还没有被搅动。

具有特定频率/最*组合的客户存活的概率。图片由作者提供。

这个图表的解释与我们之前研究过的非常相似。不出所料,最*频繁出现的顾客(右下方)最有可能还活着。我们有一段时间没见的老顾客可能已经发生了变化(右上)。现在让我们看看左下角。在最不频繁的客户中,有一些我们已经有一段时间没有见过了(最*大约 300),但是由于他们通常不经常购买,所以有合理的机会他们还没有搅动。

我们刚刚分析的两种可视化都依赖于模型的预测,无论是流失概率估计还是未来购买的预测数量。这些是怎么做的?接下来我们来讨论一下。

做预测

假设客户的历史频率为 3.5,最*频率为 450,年龄为 500 天,那么在接下来的 100 天内,他们会购买多少次?

预测是0.680,这意味着我们可以预期这样的客户*均会完成大约三分之二的购买。换句话说,如果我们观察 100 名具有相同特征的客户,我们可以预计其中 2/3 的人会购买一次,1/3 的人不会购买(或者 1/3 的人会购买两次,其余的人不会购买,从而得出相同的*均值)。

我们是如何得出这一预测的?嗯,这都是应用于模型估计的概率密度的数学。详见 Fader 等人的这篇论文(见附录中的方程 10 及其推导)。

简而言之,漫长的数学推导归结为估计消费者到时间 100 时的购买次数的*均值(或期望值)。这是通过首先估计客户在该时间段内的每个时间点还活着的概率来完成的,并且基于结果以及他们的最*和频率,计算最可能的购买次数。

虽然推导本身相当复杂(它利用贝叶斯定理来计算流失率 p 和交易率λ的联合分布,同时还求解高斯超几何函数的欧拉积分),但是需要评估的预测的结果公式相当简单。

钱,钱,钱:伽玛-伽玛模型

到目前为止,我们一直在预测未来的交易数量。这就是我们的 BG/NBD 模型可以带我们走多远。但是为了完成我们的客户终身价值评估,我们需要将这些销量预测转换成货币形式。最常用的方法是使用 Gamma-Gamma 模型,这是购买到死方法的一个组成部分。

Gamma-Gamma 模型使用每个客户的交易频率和*均货币价值来估计他们单笔交易的预期价值。然而,它依赖于一个重要的假设,即它的两个输入之间没有相关性。在实践中,最好事先验证这是否成立。

 monetary_value  frequency
monetary_value        1.000000   0.072322
frequency             0.072322   1.000000

对于我们的数据来说,这种相关性相当弱,因此很可能符合伽玛-伽玛模型。

准备好模型后,我们可以估计每个客户的*均交易值。

0        28.233236
1        26.190742
2        35.487043
3        90.966309
4        31.681303
           ...    
11387    28.755547
11388    41.177010
11389    30.689138
11390    44.424422
11391    38.599852

现在,我们可以将这些数字乘以 BG/NBD 模型预测的每个客户的交易数量,但这样我们就会忽略一个重要的经济现象:通货膨胀。

通货膨胀是货币的价值随着时间的推移而减少,这意味着你一年后得到的 100 美元比你钱包里已经有的 100 美元价值更低。在 CLV 世界,客户在未来带来的价值在当前的价值低于其名义货币价值。为了把它考虑进去,我们需要用某个贴现率来贴现未来的美元价值。

虽然要使用的正确折扣值可能是一个经济辩论的主题,甚至可能是特定于业务的,但在本例中,我们将任意将其设置为 1%。那么,我们每个客户在未来 12 个月的 CLV 是多少?

0         76.130371
1         32.012197
2         73.677196
3        112.946757
4         89.223161
            ...    
11387     43.924401
11388     19.281169
11389     28.020272
11390      3.078582
11391      2.298163

因此,我们得到了我们的 CLV 估计:这些是货币价值,贴现到今天的美元,我们可以期望每个客户在下一年带给我们。

最后的话

我们已经了解了什么是客户终身价值,为什么了解它至关重要,以及如何在非合同环境下使用“一买到底”模型来估算它。我们已经讨论了数据需求、模型的假设和架构,以及如何在 Python 中拟合和分析它。

我们忽略的一个重要方面是模型验证。我们如何知道这个模型对未来的客户是否有用,因为他们在培训中没有见过这个模型?如果你对这个话题感兴趣,一定要让我知道,我会单独写一篇关于它的文章。

如果你喜欢这篇文章,为什么不在我的新文章上 订阅电子邮件更新 ?通过 成为媒介会员 ,你可以支持我的写作,并无限制地访问其他作者和我自己的所有故事。

需要咨询?你可以问我任何事情,也可以在这里 预定我 1:1

你也可以试试我的其他文章。不能选择?从这些中选择一个:

基于机器学习和 Python 的地理分析

原文:https://towardsdatascience.com/calculating-closest-landmass-between-two-points-on-earth-214f73b48fdc

一个圆形的行星如何使分析变得更加困难

来源

介绍

我对最*上传的一个现有项目做了一些重要的更新。请查阅原创文章了解背景和更深层次的背景。

但简单回顾一下:我和我的妻子有一种不寻常的需要,即确定地球上两个坐标之间的中点,然后找到离该中点最*的陆地。我确信这个问题的解决方案可以通过快速的谷歌搜索很容易找到,但是没有这样的运气。因此,我决定深入地理的兔子洞,为这个很少有人会问的晦涩问题找到一个解决方案。

脚本更新

1.计算地理中点:

在最初的版本中,我们利用谷歌来确定地理中点,然后脚本的主要功能是找到最*的陆地。在当前版本中,我们从两个起始坐标本身开始,脚本从头开始计算中点。这大大节省了时间,因为对于那些不知道自己到底在找什么的人来说,在谷歌上找到中点很困难。我不想让用户错误搞砸最终结果。然而,加入这种变化有点棘手。人们最初会认为我们在中学都学过的中点公式就足够了;然而,因为地球是圆的,不是二维笛卡尔*面,所以它有点复杂。位于太*洋中部的 国际日期变更线 是地球的“圆度”成为计算问题的地方。需要 3 个中点函数(以及随后的大量脚本来确定 3 个函数中的哪一个)来确定正确的中点。

情形 1:2 个起始坐标未穿越 IDL 线。

大多数情况下都是这种情况。只要输入之间的最短距离不与下图所示的红线相交,基本中点公式就能完美工作:

维基百科

澳大利亚到非洲,加拿大到智利,美国到欧洲等。所有这些都是公*的游戏,因为它们之间的最短距离避开了那条线。

维基百科上的中点方程

情况 2 和 3:两个输入点之间的最短距离穿过 IDL

因为一旦你穿过这条线(从东到西),纬度直接从+180 度到-180 度,它扭曲了中点方程的用法。

例如,如果我们对坐标(0,-179.8)和(0,179.9)使用中点方程,它将在(0,0.05)处计算整个世界的中点,而不是在(0,-179.95)处计算几英里以外的中点。上谷歌地图,把这些坐标粘贴进去,你就能看到自己了。

为了说明这一点,我们需要制作另外两个场景:1 .当中点应该落在线的西边时(意味着两点的纬度都是负值),以及 2。当中点落在线的东部时(意味着两点的纬度都是正的)。上海到纽约是中点落在 IDL 以东的一个例子;夏威夷到印度是中点落在 IDL 以西的一个例子。

如果你看看下面代码中的第 7-15 行,你会看到其中用到的数学。第 17-20 行是比较的基本中点。

上面的第 32-68 行是计算 3 个中点中哪一个是适合当前情况的。您可以通过注释来理解每个计算以及它如何影响下一个计算。更多细节见我的github/看完整脚本。

2.确定中点已经在陆地上的情况

我添加了一个 if 语句,它解释了中点在陆地上的 29%的情况(因为地球的 29%是陆地)。如果中点恰好是陆地,代码只输出坐标,避免搜索最*陆地的迭代循环。

3.形象化

现在有了两个起始坐标和中点的可视化。很容易迷失在坐标数字本身的抽象中,所以每次运行这个脚本时看到它们在地图上可视化对我来说都是有价值的。请看下面的视觉效果图。

中美起点的中点原例

结论:

这些编辑很有趣,在这个过程中我学到了很多地理知识。现在任何像我一样古怪的人,如果需要识别离两个坐标中点最*的陆地,可以比以往任何时候都容易。使用这个脚本也是发现小的、不知名的岛屿的好方法。我强烈建议对 python 或地理感兴趣的人尝试一下,因为你会学到很多关于这两方面的知识。

编辑:我还注意到,有更复杂的方法可以找到更精确的地理中点。本文中显示的方法是基于二维*均的*似方法;然而使用三角和三维计算显示 在这里 ,可以发现一个更精确的结果。

考虑通过我的推荐链接加入 Medium:https://andrewhershy.medium.com/membership

如果你觉得这篇文章有帮助/有趣,请看看我的其他文章:

如何使用 python 在海上寻找陆地

我写了一个 python 脚本帮我玩彩票

基尼指数 vs 信息熵

Alteryx 是否是你数据分析需求的正确选择?

使用数学和 python 优化你的投资组合

Excel vs SQL:概念上的比较

使用 Python 计算机器学习中的数据漂移

原文:https://towardsdatascience.com/calculating-data-drift-in-machine-learning-53676ff5646b

机器学习模型的漂移检测

图片来自UnsplashRalfs Blumbergs

本文旨在提供与 Python 中的数据漂移相关的直觉和实现。它将涵盖两种计算漂移的方法之间的实现和差异,即交叉熵和 KL 散度。以下是这篇文章的提纲。

目录

  • 什么是数据漂移?
  • 漂移指标
    -交叉熵
    • KL 散度
  • 解决方案架构
    -需求
  • 实现
    -生成数据
    -训练模型
    -生成观察值
    -计算漂移
    -可视化随时间的漂移
  • 计算漂移的障碍
  • 结束语
  • 资源

什么是数据漂移?

MLOps 是构建成功的机器学习模型并将其部署到生产中的一个不可或缺的组件。数据漂移可以归入 MLOps 中的模型监控类别。它指的是量化观察数据相对于训练数据的变化。随着时间的推移,这些变化的影响可能会对模型生成的预测质量产生巨大影响,通常会变得更糟。跟踪与训练特征和预测相关联的漂移对于模型监控和识别何时应该重新训练模型应该是不可或缺的。

你可以参考我的另一篇文章,了解在生产环境中监控机器学习模型背后的概念和架构的更多细节这里

您可能不希望监控与模型预测/特征相关联的漂移的唯一情况是,在生成预测的基础上定期重新训练模型。这可能是与时间序列模型的许多应用相关的常见事件。然而,还有各种其他的东西可以跟踪,以确定您正在生成的模型的质量。本文将主要关注与经典机器学习(分类、回归和聚类)相关的模型。

漂移度量

下面概述的两个度量标准都是量化一对概率分布相似程度的统计度量。

交叉熵

交叉熵可以由以下公式定义:

交叉熵公式。图片取自交叉熵维基百科【2】。

  • p:真实概率分布
  • 问:估计概率分布

从信息论的角度来看,熵反映了消除不确定性所需的信息量3。请注意,分布 A 和 B 的交叉熵将不同于分布 B 和 A 的交叉熵。

KL 散度

Kullback Leibler 散度,也称为 KL 散度,可通过以下公式定义:

  • p:真实概率分布
  • 问:估计概率分布

然后,kull back–lei bler 散度被解释为使用针对 Q 优化的代码而非针对 P 优化的代码对 P 的样本进行编码所需的比特数的*均差异[1]。请注意,分布 A 和 B 的 KL 散度将不同于分布 B 和 A 的 KL 散度。

请注意,这两种度量都不是距离度量。这是因为度量缺乏对称性。

entropy / KL divergence of A,B != entropy / KL divergence of B,A

解决方案架构

下图概述了机器学习生命周期在合并模型监控时的运行方式。如上面的需求所示,为了监控模型的性能,应该从训练阶段保存各种数据。即用于训练模型的特征和目标数据。这将提供一个比较新观察结果的基础事实数据源。

模型监控架构。图片由作者提供。

要求

以下模块和版本用于在本地运行实现中显示的代码。它们都是著名的数据科学/分析/机器学习库,因此安装特定版本对大多数用户来说应该不是问题。

Python=3.9.12
pandas>=1.4.3
numpy>=1.23.2
scipy>=1.9.1
matplotlib>=3.5.1
sklearn>=1.1.2

履行

我将通过一个使用合成数据的例子来展示如何计算数据随时间的漂移。请注意,为我生成的值将与您生成的值不一致,因为它们是随机生成的。此外,由于它是随机生成的,所以从提供的可视化/数据中没有任何真正有意义的结果可以解释。目的是为您的应用程序提供可重用和可重新配置的代码。

生成数据

上面的脚本将生成一个由 1000 行和列uuid, feature1, feature2, feature3, target组成的合成数据集。这将是我们训练模型的基础数据。

火车模型

出于本教程的目的,上面的脚本将允许您根据我们上面生成的特征和目标创建一个随机森林回归模型。假设这个模型将被推送到生产环境中,并且每天都会被调用。

生成观察结果

上面的脚本将在模型被生产和调用的第一天生成与特性相关的观察。我们现在可以将地面实况训练数据相对于观察数据的差异可视化。

可视化特征 1 的训练数据与观察数据。图片由作者提供。

从上图中可以看出,在模型生产的第一天,对特征的观察比地面实况多。这是一个问题,因为我们不能比较两个长度不同的值列表。如果我们试图比较两个不同长度的数组,将会产生错误的结果。现在为了计算漂移,我们需要使观测值的长度等于地面真实数据的长度。我们可以通过创建 N 个桶,并确定每个桶中的观察频率来做到这一点。实质上是创建一个直方图。下面的代码片段将可视化这些分布的相互关系。

地面实况数据的特征分布与特征 1 的观测数据。图片由作者提供。

既然两个数据集大小相同,我们可以比较两个分布中的漂移。

计算漂移

上面的脚本概述了如何计算与观察数据相关的相对于训练数据的漂移(使用scipy中的entropy实现)。它首先通过matplotlib中的hist方法将输入向量的大小标准化为相同长度,通过softmax函数将这些值转换为概率,然后通过entropy函数最终计算漂移。

可视化随时间的漂移

随着模型在生产中使用时间的推移,可视化与模型中每个特征相关联的漂移分数。图片由作者提供。

根据您为数据集生成的结果,建议确定某个阈值,如果模型所依赖的大多数重要特征的漂移分数超过该阈值,这将是重新训练模型的有力指标。通过sklearn或 SHAP 之类的东西,可以很容易地识别基于树的模型的特征重要性。

计算漂移的障碍

在计算你正在使用的机器学习模型的数据漂移时,你可能会遇到各种各样的障碍。

  1. 处理值为 0 的特征/预测。这将产生与两种漂移实现相关的零分频误差。解决这个问题的一个快速简单的方法是用一个非常接*零的小值来代替零。由于监控漂移可能是逐案进行的,所以要主动了解这可能对您正在处理的问题产生什么影响。
  2. 比较一对不同长度的分布。假设您根据与每个功能和目标相关联的 1,000 次观察来训练模型。然而,当您每天生成预测时,根据*台获得的流量,功能和目标的观察量从 1,000 到 10,000 不等。这是有问题的,因为你不能比较两个不同大小的分布。要解决这个问题,您可以使用上述实现中的宁滨方法,将训练数据和观察值归入相同大小的组中,然后计算该数据之上的漂移。这可以通过matplotlib库中的histogram方法轻松完成。
  3. 使用softmax函数将频率转换为概率时,获取NaN值。这是因为softmax函数依赖于指数。您将在 softmax 的输出中收到NaN结果,因为计算机无法计算大数的指数。如果是这种情况,您可能希望研究另一个不是 softmax 的实现,或者研究将您传入的值规范化,以便 softmax 可以工作。

结束语

本文重点讨论了在经典机器学习的应用中如何计算数据漂移。我回顾了 KL 散度和交叉熵等常见漂移计算指标背后的直觉和实现。

此外,本文概述了个人在试图计算漂移时会遇到的常见障碍。即,当有零值时被零除的误差,以及关于比较一对大小不同的分布的问题。请注意,这篇文章主要对那些不经常重新训练您的模型的人有帮助。模型监控将作为一种手段来衡量模型的成功,并确定它何时因漂移而表现倒退。两者都是模型开发阶段再培训或重复的指标。

你可以在我的 GitHub 页面这里查看与本教程相关的资源库。

如果你想转型进入数据行业,并希望得到经验丰富的导师的指导和指引,那么你可能想看看最敏锐的头脑。Sharpest Minds 是一个导师*台,导师(他们是经验丰富的实践数据科学家、机器学习工程师、研究科学家、首席技术官等。)将有助于你的发展和学习在数据领域找到一份工作。点击查看

资源

如果你觉得这篇文章有用,这里有一些我写的其他文章,你可能也会觉得有用。

计算四分位数:逐步解释

原文:https://towardsdatascience.com/calculating-quartiles-a-step-by-step-explanation-f5457de460ee

Python 中分析和计算四分位数的方法

图片由卢卡斯像素上拍摄

四分位数是代表数据观察的四个等分区间的统计对象。这些计算是比较数据不同部分(如最大值、最小值、中间值和异常值)的有用方法。这些统计值对于比较各组是必不可少的。例如,如果我们观察 2022 年美国收入的四分位数,这些值将比我们只考虑纽约市肿瘤学家的收入更分散,因为第一个四分位数的所有收入范围比第二个四分位数大得多。四分位数可以让我们发现年薪 50,000 美元的纽约肿瘤学家是个异数。更有甚者,这一工资可能与典型值相差甚远,分析师或数据科学家可能会得出结论,这是一个错误的值,可能是人为输入错误的结果。

虽然这个例子看起来很直观,但是计算四分位数是一种统计上严格的方法,可以测量和比较数据中值的分布。鉴于这种严格性,quartiles 有许多行业应用,包括比较公司薪酬、执行客户细分、检测金融市场中的欺诈以及比较娱乐业中的门票销售。

进行这种分析的最常见方法之一是在 Pandas 中使用分位数方法,这种方法很有用,因为它可以计算任何类型的分位数,如中位数、三分位数(3 组)、五分位数(5 组)以及一直到百分位数(100 组)。此外,Seaborn 库是一个 Python 可视化工具,允许您通过箱线图可视化四分位数。

四分位数的另一个用途是分析数据的子组。Pandas 分位数方法也使得分析数据中子组中的四分位数变得简单明了。考虑电话和互联网服务提供商的客户账单数据。这些数据可能包含客户任期、性别和服务类型等信息。不同层次的四分位数可以洞察哪些因素对客户保持率影响最大。使用四分位数,您可以获得有助于回答各种分析问题的见解。例如,四分位数可以帮助我们解决以下所有问题:

  1. 使用光纤互联网服务的客户比使用 DSL 的客户保留服务的时间更长吗?
  2. 性别与客户任期的长短有什么关系?
  3. 留下来的客户和离开的客户在月费上有显著差异吗?

四分位数及其可视化可以帮助我们以严格的分析方式解决这些问题。

在这里,我们将使用电信客户流失数据集来计算和分析四分位数。该数据包含一家虚构的电信公司的客户账单信息。它规定了客户是停止还是继续使用这项服务,也就是所谓的客户流失。这些数据是公开的,在 Apache 2.0 许可下可以自由使用、共享和修改。

读入数据

首先,让我们导入 Python Pandas 库:

import pandas as pd

接下来,让我们将数据读入熊猫数据框:

df = pd.read_csv('telco_churn.csv')

现在,让我们用 head()方法打印前五行:

print(df.head())

作者图片

四分位数对应于被分成四个区间的数据观察值。具有以下定义:

  1. 第一个四分位数(Q1):这相当于第 25 个百分位数。这意味着 25%的数据低于 Q1,75%的数据高于 Q1。
  2. 第二个四分位数(Q2):这相当于第 50 个百分位数。这个值也称为中间值。这意味着 50%的数据低于 Q2,50%的数据高于 Q2。
  3. 第三个四分位数(Q3):这相当于第 75 个百分位数。这意味着 75%的数据低于 Q2,25%的数据高于 Q2。
  4. 第四个四分位数:这只是数据中的最大值。

用熊猫生成四分位数

用 Pandas 库计算四分位数很简单。让我们计算整个数据集中以月为单位显示的任期列的四分位数。为此,我们将对 Pandas 数据框对象使用分位数方法。我们将 0.25 作为分位数方法的参数。

print("First Quartile (Q1) for Tenure: ", df['tenure'].quantile(0.25))

作者图片

我们看到任期的第一个四分位数是 9 个月。这意味着任期的第 25 百分位是 9 个月,也就是说 25%的客户在公司的时间少于 9 个月。让我们计算第二个四分位数。我们通过传入 0.5 作为参数来实现这一点:

print("Second Quartile (Q2) for Tenure: ", df['tenure'].quantile(0.50))

作者图片

我们看到任期的第二个四分位数是 29 个月。这意味着任期的第 50 百分位是 29 个月,也就是说 50%的客户在公司的时间少于 29 个月。

最后,我们来计算第三个四分位数。这里,我们将 0.75 作为分位数方法中的参数:

print(“Third Quartile (Q3) for Tenure: “, df[‘tenure’].quantile(0.75))

作者图片

在这里,保有权的第三个四分位数是 55 个月。这意味着任期的第 75 百分位是 55 个月,也就是说 75%的客户在公司的时间少于 29 个月。

利用熊猫生成分位数

分位数方法的名称是指一个统计量,它是将数据观察值划分为任意数量的区间的一种概括。对于四分位数,我们将数据观察值分为四个区间。但是用同样的方法,我们可以很容易地将数据分成 5 个区间(五分位数),10 个区间(十分位数),甚至 100 个区间(百分位数)。如何分割数据观测值的选择取决于应用程序,这就是为什么有一个执行这些分割的通用方法是有用的。

例如,要生成一个十分位数,我们只需将 0.1-0.9 范围内的参数传递给分位数方法。值 0.1 对应于第 10 个百分位数(即 10%的数据观测值低于该值),0.9 对应于第 90 个百分位数(90%的数据观测值低于该值)。让我们来计算任期中的第 9 个十分位数,也称为第 90 个百分位数:

print("Ninth Decile for Tenure: ", df['tenure'].quantile(0.9))

作者图片

四分位数&数据子组的分位数

为子组生成四分位数也很有用。例如,我们可能想比较使用光纤的客户的任期和使用 DSL 的客户的任期。让我们来计算每个分组的四分位数。首先,让我们在 internet 服务栏中筛选数据帧,为使用 DSL 的客户创建一个数据帧,为使用光纤的客户创建另一个数据帧:

df_dsl = df[df['InternetService'] == 'DSL']df_fiberoptic = df[df['InternetService'] == 'Fiber optic']

现在,让我们看看 DSL 和光纤的第三个四分位数:

print("Third Quartile (Q3) for Tenure — DSL: ", df_dsl['tenure'].quantile(0.75))print("Third Quartile (Q3) for Tenure — Fiber Optic: ", df_fiberoptic['tenure'].quantile(0.75))

作者图片

我们看到 DSL 和光纤客户的第三季度值是相同的;对于 DSL 和光纤,56 个月的租期大于数据中客户租期值的 75%。

我们还可以看看第九个十分位数:

print("Ninth Decile for Tenure — DSL: ", df_dsl['tenure'].quantile(0.9))print("Ninth Decile for Tenure — Fiber Optic: ", df_fiberoptic['tenure'].quantile(0.9))

作者图片

在这里,我们可以看到 DSL 和光纤在第九个十分位数上的微小差异。对于 DSL 来说,90%的客户在公司的时间少于 70 个月。就光纤而言,90%的客户在该公司工作不到 69 个月。

由于我们正在处理的数据与客户流失相对应,所以让我们看看那些离开的人和那些留下的人之间是否有任何差异。让我们来看看流失客户和留下来的客户每月的费用有什么不同。让我们为每个组创建过滤数据框:

df_churn_yes = df[df['Churn'] == 'Yes']df_churn_no = df[df['Churn'] == 'No']

接下来,让我们计算任期的第三个四分位数:

print("Third Quartile (Q3) for Tenure — Churn: ", df_churn_yes['tenure'].quantile(0.75))print("Third Quartile (Q3) for Tenure — No Churn: ", df_churn_no["tenure"].quantile(0.75))

作者图片

国际矿物学会

我们看到,对于那些翻盘的客户,75%的人停留时间少于 29 个月。对于那些留下来的人,75%的人在公司呆了不到 61 个月。这说明了我们已经预料到的情况,即离开的客户比留下的客户对终身职位的价值低得多。

让我们考虑一下每月充电器专栏中流失客户与留下客户的对比:

print("Third Quartile (Q3) for Tenure — Churn: ", df_churn_yes['MonthlyCharges'].quantile(0.75))print("Third Quartile (Q3) for Tenure — No Churn: ", df_churn_no['MonthlyCharges'].quantile(0.75))

作者图片

在这里,我们看到流失客户的第三季度月费为 94.2 美元,而留下来的客户的第三季度月费为 88.4 美元。由此,我们可以得出结论,75%的流失客户支付的价格低于 94.20 美元,而 75%的留下客户支付的价格低于 88.4 美元。这可能意味着最终离开这项服务的客户被多收了钱。

用箱线图可视化四分位数

通常,生成清晰表示感兴趣的统计量的可视化是有用的。直观表示四分位数的一个好方法是通过箱线图。Seaborn Python 可视化库使得生成这些图变得容易。让我们从导入 Matplotlib 和 Seaborn 库开始:

import seaborn as snsimport matplotib.pyplot as pltsns.set()

让我们针对整个数据集生成一个任期箱线图:

sns.boxplot(df['tenure'])plt.show()

作者图片

可以通过查看蓝框来检查四分位数。蓝框左侧对应第一个四分位数(Q1),中间的黑线是第二个四分位数(Q2,也称为中位数),蓝框右侧是第三个四分位数(Q3)。

我们可以做的另一件事是定义一个函数,允许我们跨类别比较四分位数:

from collections import Counterdef get_boxplot_of_categories(data_frame, categorical_column, numerical_column, limit): keys = [] for i in    dict(Counter(df[categorical_column].values).most_common(limit)): keys.append(i) df_new = df[df[categorical_column].isin(keys)] sns.boxplot(x = df_new[categorical_column], y = df_new[numerical_column])

让我们使用完整的数据框架调用这个函数,将流失列作为分类列,将任期列作为数值列。显示的类别数量限制为五个:

get_boxplot_of_categories(df, 'Churn', 'tenure', 5)

作者图片

我们发现,Q1 和第三季度留下来的客户的保有价值高于流失的客户,这是我们所预期的。让我们生成每月费用和流失率的图表:

get_boxplot_of_categories(df, 'Churn', 'MonthlyCharges', 5)

作者图片

我们看到,Q1,Q2 和第三季度的月费值,对于那些留下来的比那些流失。我们也预料到这一点,因为正如我们前面看到的,留在公司的客户通常比离开公司的客户每月支付的费用少。

这篇文章中的代码可以在 GitHub 上找到。

结论

四分位数和分位数对于比较数据中的组非常有用。在数据观察中,不同的组和类别之间的值的分布会有很大的不同。了解数据中的数值如何在组内分布可以帮助数据科学家和分析师了解客户人口统计的差异。这种分析可以帮助公司识别更有可能继续购买服务或产品的高价值客户。公司可以利用这些信息继续锁定高价值客户,同时通过交易和促销锁定临时或不经常购买的客户。公司可以使用这种技术来开发强大的客户保持计划。

如果你有兴趣学习 python 编程的基础知识、Pandas 的数据操作以及 python 中的机器学习,请查看Python for Data Science and Machine Learning:Python 编程、Pandas 和 sci kit-初学者学习教程 。我希望你觉得这篇文章有用/有趣。

本帖原载于 内置博客 。原片可以在这里找到https://builtin.com/data-science/how-to-calculate-quartiles

使用 Python 评估 K-均值聚类的 Calinski-Harabasz 指数

原文:https://towardsdatascience.com/calinski-harabasz-index-for-k-means-clustering-evaluation-using-python-4fefeeb2988e

在本教程中,我们将探索 Calinski-Harabasz 索引及其在 Python 中 K-Means 聚类评估中的应用。

梅尔·普尔在 Unsplash 上的照片

目录

  • 介绍
  • 卡林斯基-哈拉巴斯指数解释
  • Python 中的 Calinski-Harabasz 索引示例
  • 结论

介绍

卡林斯基-哈拉巴斯指数(CH)是聚类算法评价指标之一。它最常用于通过 K-Means 聚类算法对给定数量的聚类评估分割的良好性。

我们之前讨论过 Davies-Bouldin 指数Dunn 指数,Calinski-Harabasz 指数是另一种评估聚类性能的指标。

什么是卡林斯基-哈拉巴斯指数?

Calinski-Harabasz 指数(也称为方差比标准)计算为所有聚类的聚类间离差之和与聚类内离差之和的比值(其中离差是距离的*方和)。

如何解读卡林斯基-哈拉巴斯指数?

高 CH 意味着更好的聚类,因为每个聚类中的观测值更接*(更密集),而聚类本身彼此更远(分离得更好)。

在下一节中,将通过几个例子详细描述计算 CH 的过程。

为了继续学习本教程,我们需要以下 Python 库:sklearn 和 matplotlib。

如果您没有安装它,请打开“命令提示符”(在 Windows 上)并使用以下代码安装它:

pip install sklearn
pip install matplotlib

卡林斯基-哈拉巴斯指数解释

在这一部分中,我们将介绍计算的每一步,并提供有意义的示例来帮助更好地理解公式。

步骤 1:计算群集间离差

第一步是计算组间离差或组间*方和(BGSS)。

CH 中的类间离差测量类的质心和整个数据集的质心(重心)之间的距离*方的加权和。

组间*方和的计算方法如下:

作者图片

其中:

  • n_k :簇 k 中的观测数
  • C_k :簇的质心 k
  • C :数据集的质心(重心)
  • K :集群的数量

步骤 2:计算组内离差

第二步是计算组内离差或组内*方和(WGSS)。

CH 中的类内离差测量每个观察值与同一类的质心之间的*方距离之和。

对于每个集群 k ,我们将 WGSS_k 计算如下:

作者图片

其中:

  • n_k :簇 k 中的观测值个数
  • X_{ik} :星团 k 的第(I)次观测
  • C_k :簇的质心 k

然后对组内所有个体*方和求和:

作者图片

其中:

  • WGSS_k :聚类 k 的组内*方和
  • K :集群的数量

步骤 3:计算卡林斯基-哈拉巴斯指数

卡林斯基-哈拉巴斯指数被定义为所有聚类的聚类间离差之和以及聚类内离差之和。

卡林斯基-哈拉巴斯指数计算如下:

作者图片

其中:

  • BGSS :组间*方和(组间离差)
  • WGSS :组内*方和(组内离差)
  • N :观察总数
  • K :集群总数

从上面的公式中,我们可以得出结论,Calinski-Harabasz 指数的大值代表更好的聚类。

Python 中的 Calinski-Harabasz 索引示例

在这一节中,我们将通过一个例子来计算 Python 中 K-Means 聚类算法的 Calinski-Harabasz 指数。

首先,导入所需的依赖项:

您可以通过下面的代码使用任何数据。为了简单起见,我们将使用内置的虹膜数据集,特别是前两个特征:【萼片宽度】【萼片长度】:

让我们从 3 个集群的 K 均值目标开始:

并检查 Calinski-Harabasz 指数以获得上述结果:

你应该得到结果分数: 185.33266845949427 或者大概( 185.33 )。

为了更好地理解集群的外观,让我们将它们形象化:

我们应该会看到以下 3 个原始集群:

作者图片

因为我们计算了 3 个聚类的 CH 指数,并且原始数据具有 3 个标签,所以我们假设 3 个聚类的 CH 指数比任何其他数量的聚类的 CH 指数都高。

让我们计算一系列集群的 CH 指数,然后找出最高值:

想象一下:

我们发现一个有趣的结果,5 个聚类和 10 个聚类提供了比 3 个聚类更高的 Calinski-Harabasz 指数值(即使数据中标签的实际数量是 3)。

即使我们可以获得除 3 以外的更高的 CH 指数值,我们也可以观察到指数值仍然在 175 和 200 之间的非常接*的范围内。

结论

在本文中,我们讨论了如何使用 sklearn 库在 Python 中计算用于聚类评估的 Calinski-Harabasz 指数。

如果你有任何问题或者对编辑有任何建议,欢迎在下面留下评论,并查看我的更多 Python 编程文章。

原载于 2022 年 3 月 15 日【https://pyshark.com】

卡尔文——一个可以学习规划和导航未知环境的神经网络

原文:https://towardsdatascience.com/calvin-a-neural-network-that-can-learn-to-plan-and-navigate-unknown-environments-820a8444276f

碰撞避免长期价值迭代网络

总结: CALVIN 是一个神经网络,可以在新奇的 3D 环境中进行规划、探索和导航。它通过学习专家的示范来学习诸如解迷宫之类的任务。我们的工作建立在值迭代网络(VIN) [1],一种递归卷积神经网络。虽然 VIN 只在完全已知的环境中工作良好,但 CALVIN 甚至可以在未知的环境中工作,在这种环境中,代理必须探索环境以找到目标。

卡尔文测试的三个环境(图片由作者提供)

在这篇文章中,我想对我最*在 CVPR 2022(计算机视觉和模式识别会议)上发表的一篇论文做一个高层次的概述。这项研究的动机是提出一个更健壮的神经网络架构,它可以 学习规划 ,这是受价值迭代网络工作的启发[1]。

https://arxiv.org/abs/2108.05713

代号:https://github.com/shuishida/calvin

问题是

我们要解决的问题是演示中的视觉导航。给定固定数量的 RGB-D 图像的专家轨迹和所采取的动作,机器人代理必须学会如何导航。虽然使用定义障碍和目标的自上而下的地图进行规划很容易,但如果代理必须从 RGB-D 图像中了解障碍和目标的性质,则更具挑战性。

代理视为专家演示的一系列图像和动作(作者提供的图像)

航海的另一个重要方面是探索。我们的代理开始时对新环境没有任何了解,因此它必须在导航时构建环境地图,并学习探索最有可能通向目标的区域。

代理学会预测最能解释专家演示的奖励(图片由作者提供)

为了使代理能够在未经训练的环境中导航,它必须学习一些适用于所有环境的一般知识。特别是,我们专注于学习一个共享的过渡模型和奖励模型,它可以最好地解释专家演示,然后可以应用到新的设置。

代理学习可在所有环境中重复使用的运动动力学(图片由作者提供)

模型概述

我们的模型由两部分组成——一个学习映射组件,我们称之为格点网,它将过去的观察聚合到嵌入的地面投影图中,以及 CALVIN,它是一个可微分的规划器,对价值迭代进行建模。与强化学习中更常见的方法不同,在强化学习中,代理看到图像并试图反应性地预测最佳行动,通过由格点网学习适当的空间表示并使用 CALVIN 作为规划网络,我们的代理能够以空间上有意义的方式探索和导航考虑到过去的观察。

模型架构概述(图片由作者提供)

CALVIN 是值迭代网络(简称 VIN)的改进版本,它使用递归卷积作为空间任务的值迭代形式。它学习一个奖励图和一个卷积核,按照值迭代更新方程应用,产生一个 Q 值图,这是对代理可以获得的未来奖励的估计。一旦计算出价值图,代理就可以采取产生最高价值的行动来最大化其机会。

虽然 VIN 是一个简单的架构,但它有几个缺陷,最明显的是它在实践中没有严格地学习价值迭代。正如您在下图中看到的,VIN 生成的价值图不是我们从价值迭代中所期望的,而我们的模型 CALVIN 学习生成一个与理论解决方案几乎相同的价值图。我们发现不匹配是因为 VIN 没有足够的约束来惩罚障碍,因此做出了次优的决定,例如反复探索死胡同。

VIN 和 CALVIN 生成的价值图的比较。VIN 产生不可解释的、脆弱的价值图。(图片由作者提供)

卡尔文,另一方面,明确学习有效和无效的过渡。它将转换模型分解为共享代理运动模型和动作可用性模型。CALVIN 使用动作可用性模型来惩罚无效的动作,并防止值从不可到达的状态传播。除了对可用动作的这些约束,我们还改进了训练损失,以便模型可以在整个轨迹上利用训练信号,而不仅仅是当前状态。

卡尔文模型图(图片由作者提供)

实验

我们在三个领域进行了实验,特别是探索新的未知环境:网格迷宫环境、迷你世界[2]和主动视觉数据集3。卡尔文实现了更强大的导航,即使在未知的环境中,展示了探索行为,这是 VIN 所缺乏的。

在我们的网格迷宫设置中,代理只能在本地查看迷宫。代理可以选择向前移动、向左、向右旋转或触发完成。我们可以看到,代理人预测代理人尚未探索的地方会有更高的价值,当代理人看到目标位置时会有更高的奖励。

卡尔文在网格迷宫环境中

接下来,我们在一个名为 MiniWorld 的 3D 迷宫环境中进行了一个类似的实验,但这一次使用的是从代理的角度而不是从上往下看的 RGB-D 图像序列。当代理导航时,它用点阵点网建立一个嵌入图,然后输入 CALVIN。在这里,代理也已经学会将较低的值分配给墙壁,将较高的值分配给未探索的位置。我们可以观察到代理在遇到死胡同时设法返回,并重新规划到其他未探索的单元。当代理看到目标时,它会给目标附*的细胞分配较高的奖励。

迷你世界中的卡尔文

最后,我们使用主动视觉数据集测试了该代理,该数据集是由机器人*台获得的真实世界图像的集合,从中我们可以创建轨迹。对于这个任务,我们使用预先训练的 ResNet 嵌入,并把它们输入到网格点网中。这位特工被训练如何在房间里找到一个汽水瓶。

活动视觉数据集环境中的卡尔文

结论

与其他差异化规划师相比,卡尔文能够更稳健地探索和导航未知环境。VIN 的这种改进来自于明确模拟动作可用性,用于惩罚无效动作,以及使用轨迹重新加权改进训练损失。我们还引入了一个格点网主干,以空间一致的方式有效地融合了过去的观测结果。

参考

[1] Tamar 等人,“价值迭代网络”,NeurIPS 2016。

[2] M. Chevalier-Boisvert,【https://github.com/maximecb/gym-miniworld, 2018。

3 Ammirato 等人.“用于开发和基准化主动视觉的数据集”,ICRA,2017 年。

Python 中的摄像机标定示例

原文:https://towardsdatascience.com/camera-calibration-with-example-in-python-5147e945cdeb

Python 中图像形成和相机校准综合教程系列的第 5 部分

介绍

相机校准的基本思想是,给定世界上的一组已知点及其在图像中的相应投影,我们必须找到负责投影变换的相机矩阵。然后我们可以用这个矩阵把世界上的任何一点投射到图像上。我们将在本文中看到如何执行相机校准。

在此之前,让我们回顾一下相机的外部和内部。

回顾

相机外部矩阵

系列的第 2 部分中,我们看到摄像机外部矩阵是一个基矩阵的变化,它将一个点的坐标从世界坐标系转换到摄像机坐标系。它让我们从摄像机的角度来看这个世界。我们还看到它是旋转矩阵和*移矩阵的组合,旋转矩阵确定摄像机的方向,*移矩阵移动摄像机。因此,给定世界上一个点的坐标,我们可以应用相机外部矩阵来改变它在相机上的坐标,如下所示:

这里的符号如下:

这里,点被表示为齐次坐标,这实质上是在原始坐标上增加了一个额外的维度。

相机外部矩阵的最后一行只是 0 和 1,它们不会给变换增加任何值,因此我们可以安全地删除最后一行,并将等式重写为:

请注意,这里的输出形状是 3×1,而以前是 4×1。这意味着这个点在这里用欧几里得形式表示,这是一件好事,因为我们不需要额外的步骤来从齐次坐标转换回来;此外,管道中的下一个相机固有矩阵接受 3×1 的输入形状,我们将在接下来看到。

总之,给定世界坐标系中点的坐标,相机外部矩阵将坐标转换到相机坐标系。该转换可以表示为:

相机固有矩阵

现在我们已经使用相机外部矩阵获得了相机上某个点的坐标,下一步是将该点投影到相机的图像*面上并形成图像。这是相机固有矩阵的工作。在系列的第 3 部分中已经深入讨论了摄像机固有矩阵,但是概括来说,摄像机固有矩阵将摄像机给定坐标的点投影到摄像机的图像*面上。本质上,我们可以使用相机固有矩阵来获得图像中的点的像素位置。如下所示:

这些符号是:

输出将是均匀形式的投影。要将其转换为欧几里得形式,我们只需除以最后一个坐标并丢弃最后一个维度,如下所示:

这里 (u,v) 是点 P 在图像中的像素位置。相机固有矩阵由𝜅表示,并且该变换可以表示为:

结合相机的外在和内在

结合相机内矩阵和外矩阵,我们可以建立一个管道,该管道以世界坐标系中的一个点作为输入,并计算它在相机形成的图像上的投影。它可以表示为:

此外,使用矩阵组合,我们可以将两个矩阵组合成一个矩阵 M ,如下所示:

这个形状为 3×4 的矩阵 M 具有所有需要的信息。

把所有的放在一起

使用矩阵 M ,我们可以将整个管道表示为:

如果你观察,矩阵 M 有 12 个元素,但是我们在以前的文章中看到,内在矩阵和外在矩阵有 11 个自由度(6 个来自外在矩阵,5 个来自内在矩阵)。所以在 M 中有一个元素依赖于其他元素。

让我们假设最后一个元素 M(3,4) 是依赖元素。这意味着如果我们用这个元素来划分矩阵 M ,那么 M 内部的信息不会受到影响,因为它是一个依赖元素。

上式中,我们将矩阵 M 除以最后一个元素 M(3,4) 得到一个新的矩阵。这里需要注意的重要一点是,不管操作如何,两种情况下的输出或投影都是相同的。矩阵 M 的这一特性被称为比例不变性,意味着我们可以通过任何因子来缩放矩阵 M ,并且不会影响输出。

通常在现实应用中,矩阵 M 是未知的,摄像机标定的目标是利用一组已知点及其投影找到它。

几何摄像机校准

我们可以从上面的矩阵乘法中找到图像坐标 (ui,vi) 为:

我们可以将这个等式改写为:

我们可以进一步简化为:

如果你观察,上面的方程看起来就像是⃗中的齐次线性方程组,通常具有以下形式:

现在,一个齐次线性方程组可以用矩阵形式表示为a𝑥⃗= 0其中 A 是一个 m × n 矩阵,𝑥⃗是一个有 n 个元素的列向量, 0 ⃗是一个有 m 个元素的零向量。

所以现在我们可以用矩阵的形式来表示我们的方程,比如:我是⃗

上午 ⃗ = 0

上式表示 Am= 0这里的 m ⃗是向量形式的展*矩阵 M 。记住,我们的目标是求矩阵 M 的系数,这和解这个齐次系统是一样的。现在,每个齐次系统至少有一个解,称为零解,它是通过给每个元素赋值 0 而得到的。但这不是我们想要的解决方案。那么,如何才能找到这个齐次系统的非零解呢?

好吧,我们可以找到一个*似解,而不是找到一个精确解,我们甚至不确定它是否存在。

从数学上来说,这意味着我们可以找到, m 使得, |Am| 成为最小值,而不是找到精确解。本质上,我们试图最小化这里的代数误差

此外,我们可以归一化⃗,使它成为一个单位向量。记住,矩阵 M 是比例不变的,我们可以选择与幅度相等的比例因子来归一化它。同样, m ⃗只是矢量形式的展* M

所以,现在我们的问题是以 |m|= 1 为条件,求 |Am| 的最小值。在系列的第 4 部分中,我们看到如果单位向量𝑥⃗沿着𝐴⊺𝐴.的最小特征向量,|𝐴𝑥⃗|将是最小的

我们先来看矩阵 A :

其思想是,我们将在由 (Xi,易,子),表示的世界中找到一些点,并在由 (ui,vi),表示的图像中找到它们对应的投影,然后我们可以计算矩阵a。这个标记过程将手动完成。现在,如果我们标记 n 个点,矩阵 a 的形状将会是 2n×12,而⃗的形状将会保持在 12×1。零矢量的形状是 2n×1。

这里有个问题给你:求解上面的齐次系统,我们需要标注多少个点?⃗的大小是 12,这意味着有 12 个未知数,所以我们需要求解 12 个独立的方程来找到这 12 个未知数。所以我们要求 n 至少为6,这意味着我们应该标记至少 6 个独立的点来求解 m ⃗。我们也可以标记更多的点,一般来说,点越多越好;但是 6 是最小的。这里重音在单词 independent 上,意思是没有三个点应该是共线的。**

把所有的放在一起

好的,计划是标记至少 6 个点并计算矩阵 A. 然后我们可以计算𝐴⊺𝐴和 m ⃗将是它的具有最小特征值的特征向量。最后,我们可以将向量 m ⃗整形回 3×4 矩阵 M ,这将是我们校准的相机矩阵。使用 M,我们可以找到图像中任何世界点的投影。

让我们通过一个例子来看看这一点。

例子

安装

包含所有代码的 GitHub 库可以在这里找到。

假设您之前没有设置环境,现在可以通过运行以下命令来设置环境:

*# create a virtual environment in anaconda
conda create -n camera-calibration-python python=3.6 anaconda
conda activate camera-calibration-python# clone the repository and install dependencies
git clone https://github.com/wingedrasengan927/Image-formation-and-camera-calibration.git
cd Image-formation-and-camera-calibration
pip install -r requirements.txt*

注意:这里假设你已经安装了 anaconda。

我们将使用两个主要的库:

  • pytransform 3d:该库具有强大的 3D 空间可视化和变换功能。
  • ipympl:这是一个游戏规则改变者。它使 matplotlib 绘图具有交互性,允许我们在笔记本中实时执行*移、缩放和旋转,这在处理 3D 绘图时很有帮助。

直觉例子

要执行相机校准,我们首先需要准备地面实况,它本质上是世界上的一组点及其在图像上的对应投影。在现实世界中,我们手动测量点与相机的距离,并在图像中找到它们对应的像素。然而,在计算机上,我们可以模拟这个过程——我们可以创建一个相机外部矩阵和内部矩阵,并建立一个管道来计算世界点的投影。我们已经在本系列的第二部分和第三部分的中看到了如何创建矩阵。一旦有了地面真值,就可以用它来构造代数矩阵 A ,然后找它的转置,计算𝐴⊺𝐴,最后取特征值最小的𝐴⊺𝐴的特征向量,重新整形,得到标定后的相机矩阵 M

下面是 Jupyter 笔记本的代码:

代码就是我们上面讨论的数学方程。让我们一步一步地浏览笔记本,了解发生了什么:

  • 首先,我们定义必要的参数,并创建相机的外部矩阵和内部矩阵。这些是建造管道和准备地面真相所必需的。然后我们在世界上生成 n 个随机点(这里我选择了 n=12)。这是我们的世界空间的一个图,有相机和随机点。由于 ipympl,Jupyter 笔记本中的情节是交互式的。

设置

  • 我们可以计算这些点的投影并形成一幅图像。首先,我们必须应用外部矩阵来表示相机坐标系中的点,然后我们可以应用内部矩阵来获得投影,最后,我们可以在图像中绘制这些投影,如下所示。

图像中 n 个点的投影

  • 现在我们已经准备好了地面真相,它本质上是世界上的点 (Xi,易,子)和它们在图像中对应的投影 (ui,vi)。使用它们我们可以计算代数矩阵A。这是矩阵 A 如果你记得:**

代数矩阵 A

  • 在计算完矩阵 A 之后,我们可以计算𝐴⊺𝐴,并选择其具有最小特征值的特征向量。这将为我们提供大小为 12 的矢量⃗。最后,我们可以将⃗整形为 3×4,从而得到我们要寻找的校准后的矩阵。
  • 我们可以直接使用矩阵 M 来计算世界点在图像上的投影。它有所有必要的信息。让我们将矩阵 M 应用到我们生成的 n 个随机点上;这样,我们可以将来自校准矩阵 M 的投影与地面实况进行比较。下图显示了比较。

事实与预测

  • 嗯……从我们校准过的矩阵 M — 得到的预测让我们称之为预测 并不十分准确。有些观点接*事实,有些则相去甚远,但总体来说并不好。在下一部分,我们将讨论可能会发生什么,以及我们可以做些什么来改进。

该图描述了预测与实际情况的对比

几何误差

我们上面讨论的方法称为直接线性校准。它不是很好的一个原因是因为我们试图最小化代数误差——我们本质上是试图找到 m⃗的值,它正好符合代数方程:am :⃗:= 0 ;⃗;这种线性优化的问题是,当非线性误差和不确定性(如径向失真)悄悄进入相机时,这在现实世界中经常发生,该算法将严重失败。我们应该针对正确的错误类型进行优化。**

我们应该关注的误差叫做几何误差。几何误差本质上给出了预测与地面真实情况的距离的估计。这是通过测量点的预测投影与其地面真实投影之间的距离来实现的。当我们最小化几何误差时,我们实质上是最小化了预测和事实之间的距离。

几何误差由以下公式给出:

这里 d 是距离度量,我们通常用欧几里德距离。在我们的例子中,预测𝑥′𝑖由 MXi 给出,其中 M 是我们校准的摄像机矩阵, Xi 是世界坐标系中的一个点。因此,我们可以将等式改写为:

几何误差

好的,这个想法是执行某种非线性优化,并更新矩阵 M 的权重,以最小化几何误差。这种方法直观上类似于机器学习,其中我们使用梯度下降算法来更新模型,以最小化损失。幸运的是,我们不需要自己编写优化算法。scipyscipy.optimize模块中提供了几十种优化算法。

让我们看看如何用代码实现:

在上面的代码中,首先我们定义几何误差函数。该函数将向量 m ⃗和地面真相作为参数,地面真相是世界点及其在图像中的相应投影的集合。它使用我们上面讨论的公式计算预测值:

最后,我们计算所有点上的预测投影和实际投影之间的欧几里德距离,并计算几何误差。

来自模块scipy.optimize的函数minimize接受两个重要参数——误差函数和初始权重。我们可以将上面定义的geometric_error函数作为第一个参数传递,作为第二个参数,我们可以传递任何 12 维向量作为初始状态——然而,由于我们已经通过直接线性校准方法计算了 m ⃗的值,我们可以将其作为初始状态传递,而不是某个随机向量。作为第三个参数,我们可以传递一个包含 error 函数参数的元组。

在我们执行minimize函数之后,它运行并完成优化过程,并返回一个包含结果的对象。我们可以通过x属性从这个对象访问最终更新的权重。然后,我们可以将这个 12 维权重向量整形为 3×4 矩阵,并计算预测值。下图比较了我们通过这种方法得到的预测和地面真实预测:

事实与预测——它们重叠了!

正如我们所看到的,预测和事实是重叠的!这意味着我们已经准确地校准了摄像机矩阵。从上图中很难看出重叠部分,所以在下图中我把它们分开了:

这种方法的另一个优点是,即使投影变换是非线性的,它也可以对摄像机建模。这发生在现实世界中,因为我们不使用针丨孔丨相机,误差和不确定性,如径向失真蠕变。

摄像机校准算法

将所有这些放在一起,相机校准算法包括两个主要步骤:第一步是使用直接线性校准方法计算向量 m ⃗,第二步是通过将 m ⃗作为初始状态并使用非线性优化更新其权重来最小化预测和地面真实之间的几何误差。

结论

我希望你喜欢这篇文章和整个系列。如果你有任何疑问或问题,请在下面的评论中告诉我。你也可以通过 LinkedInTwitter 联系我。我打算发表更多关于计算机视觉和 3D 计算机视觉的文章,我们来连线。

参考

  1. 计算机视觉介绍— Udacity

图像制作者名单

本文中的所有图片和数字,除非在标题中明确提及其来源,否则均由作者提供。

Python 中带示例的相机外部矩阵

原文:https://towardsdatascience.com/camera-extrinsic-matrix-with-example-in-python-cfe80acab8dd

Python 中图像形成和相机校准综合教程系列的第 2 部分

介绍

之前的文章中,我已经介绍了相机成像的概念,并简要讨论了相机的外在和内在。

在这篇文章中,我们将深入探讨相机外部性,也称为相机姿态,并通过 Python 中的一个实际例子来加强我们的理解。

相机外部

摄像机可以位于世界上的任何地方,它可以指向任何方向。我们希望从摄像机的角度来观察世界上的物体,这种从世界坐标系到摄像机坐标系的转换称为摄像机外部或摄像机姿态。

文字坐标系 vs 相机坐标系

那么,怎样才能找到相机的姿势呢?一旦我们弄清楚相机是如何变换的,我们就能找到从世界坐标系到相机坐标系的变换。我们将详细探讨这个想法。

具体来说,我们需要知道相机是如何定向的,以及它在世界空间中的位置,有两个变换可以帮助我们:

  • 帮助定向相机的旋转变换。
  • 帮助移动相机的*移变换。

让我们详细看一下每一个。

旋转

通过旋转改变坐标

让我们看看将一个点旋转一个角度的变换。让我们在ℝ举一个简单的例子,我们将点𝑃在 XY *面上绕原点旋转一个角度𝜃,得到点𝑃′,如下图所示:

旋转变换。

𝑃的坐标是(𝑥,𝑦),𝑃′的坐标是(𝑥′,𝑦′).我们需要找到(𝑥′,𝑦′).

From the figure,
      sinα = y/r , cosα = x/r − [1]
⟹    xsinα = ycosα − [2]
also, x′ = rcos(θ+α)
⟹    x′ = (x/cosα) ∗ cos(θ+α) (from [1])
but,  cos(θ+α) = cosθcosα − sinθsinα
⟹    x′ = (x/cosα) ∗ (cosθcosα − sinθsinα)
⟹    x′ = xcosθ − xsinα ∗ (sinθ / cosα)
⟹    x′ = xcosθ − ycosα ∗ (sinθ / cosα) (from [2])
⟹    x′ = xcosθ − ysinθSimilarly,
      y′ = rsin(θ+α)
⟹    y′ = (y/sinα) ∗ sin(θ+α) (from [1])
but,  sin(θ+α) = sinθcosα + cosθsinα
⟹    y′ = (y/sinα) ∗ (sinθcosα + cosθsinα)
⟹    y′ = ycosθ + ycosα ∗ (sinθ / sinα)
⟹    y′ = ycosθ + xsinα ∗ (sinθ / sinα) (from [2])
⟹    y′ = ycosθ + xsinθ
⟹    y′ = xsinθ + ycosθHence we have,
      x′ = xcosθ − ysinθ
      y′ = xsinθ + ycosθ

旋转是线性运算,上面的等式可以表示为矩阵乘法:

旋转点𝑃′的坐标

这个操作是线性变换。这里,我们变换点,保持轴或基础固定。

延伸至 R3

我们可以很容易地将旋转变换推广到𝐑。𝐑绕标准 x 轴、y 轴和 z 轴旋转的变换矩阵如下:

绕 Z 轴旋转

绕 X 轴旋转

绕 Y 轴旋转

内在旋转与外在旋转

上述变换执行围绕标准轴的旋转。轴会随时固定。这叫做外旋。还有另一种称为固有旋转的旋转类型,我们在每一步都围绕其相对轴旋转对象,如下所示:

内在旋转。Euler 2 . gif:juansempere 衍生作品:Xavax,CC BY-SA 3.0<https://creativecommons.org/licenses/by-sa/3.0>,通过维基共享

内旋很难用欧几里得代数来执行,我们将坚持外旋。

通过旋转改变基底

在基变换中,目标是找到一个点在新基下的坐标。该点将被固定。

在下面的例子中,𝑋𝑌轴已经旋转了一个角度𝜃得到𝑋′𝑌′.给定旧基点𝑋𝑌上𝑃点的坐标,我们的目标是找到新基点𝑋′𝑌′.上𝑃点的坐标

旋转 XY 轴

𝑃至𝑋𝑌的坐标为(𝑦𝑥),至𝑋′𝑌′的坐标为(𝑦′).𝑥′)我们的目标是找到(𝑥′,𝑦′).

From the figure,
      sinα = y′/r , cosα = x′/r − [1]
⟹    x′sinα = y′cosα − [2]
also, x = rcos(θ+α)
⟹    x = (x′/cosα) ∗ cos(θ+α) (from [1])
but,  cos(θ+α) = cosθcosα − sinθsinα
⟹    x = (x′ / cosα) ∗ (cosθcosα − sinθsinα)
⟹    x = x′cosθ − xsinα ∗ (sinθ / cosα)
⟹    x = x′cosθ − y′cosα ∗ (sinθ / cosα) (from [2])
⟹    x = x′cosθ − y′sinθSimilarly,
      y = rsin(θ+α)
⟹    y = (y′/sinα) ∗ sin(θ+α) (from [1])
but,  sin(θ+α) = sinθcosα + cosθsinα
⟹    y = (y′/sinα) ∗ (sinθcosα + cosθsinα)
⟹    y = y′cosθ + y′cosα ∗ (sinθ / sinα)
⟹    y = y′cosθ + x′sinα ∗ (sinθ / sinα) (from [2])
⟹    y = y′cosθ + x′sinθ
⟹    y = x′sinθ + y′cosθHence we have,
      x = x′cosθ − y′sinθ
      y = x′sinθ + y′cosθ

上述等式可以矩阵形式表示为:

我们的目标是找到(𝑥′,𝑦′).所以,我们把矩阵移到另一边,取它的逆:

基数变化变换

理解线性变换和基变换之间的区别是非常重要的。

接下来我们将看到这两种转换是如何关联的。

线性变换与基变换的关系

如果你观察,基变换矩阵是线性变换矩阵的逆矩阵。这意味着,如果我们知道相机变换矩阵——负责在世界上旋转和移动相机的矩阵,我们可以对其求逆来获得基矩阵的变化,这可以帮助我们找到相机周围点的坐标。

翻译

通过*移改变坐标

*移的想法很简单-给定一个点𝑃,我们将它移动一个偏移量以获得点𝑃′,如下图所示:

翻译转换

这里,我们将坐标为(𝑦𝑥)的点𝑃移动一个偏移量(𝑏−𝑎),得到坐标为(𝑦′).𝑥′)的点𝑃′我们的目标是找到(𝑥′,𝑦′).

from the figure,
      x′ = x - a
      y′ = y + b

我们不能用矩阵乘法来表达上面的方程——至少不能用目前的表达方式。诀窍是增加一个额外的维度,然后我们可以将*移表示为线性变换,如下所示:

作为线性变换的翻译

用额外维度表示的坐标称为齐次坐标。

为了从齐次坐标中恢复欧几里得坐标,我们简单地除以最后一个元素,如下所示:

[x, y, 1] ≅ [x/1, y/1] = [x, y]

一般来说,我们在齐次空间中执行所有操作,因为这很容易操作,最后,当我们完成时,我们转换回欧几里得空间。稍后我们将更详细地了解这一点。

通过翻译改变基础

就像我们之前看到的,在基变换中,我们变换的是轴,而不是点。在下面的例子中,我们将𝑋𝑌轴移动一个偏移量来得到𝑋′𝑌′.我们的目标是找到𝑃点在𝑋′𝑌′.新坐标轴上的坐标

*移 XY 轴

𝑃相对于旧轴的坐标是𝑋𝑌(𝑥,𝑦),相对于新轴的坐标是𝑋′𝑌′(𝑥′,𝑦′).)这里的偏移量是(𝑎,𝑏).

from the figure,
      x′ = x - a
      y′ = y - b

同样,为了将上述方程表示为矩阵乘法,我们使用齐次坐标:

通过翻译改变基础

即使在翻译中,线性变换和基变换的变化也是相反的。

外部摄像机矩阵

我们已经分别研究了旋转和*移;但是,我们可以使用如下所示的矩阵组合一次执行这两个操作:

旋转+*移变换

这里,𝑅是形状(3,3)的旋转矩阵,𝑂是形状(3,1)的*移偏移量。我们可以通过对最终的变换矩阵求逆来得到基矩阵的变化。这种形状基矩阵(4,4)的变化被称为外部摄像机矩阵,由𝐸.表示

使用𝐸,我们可以找到相机上任何一点的坐标。

自由度

摄像机外部矩阵有 6 个自由度。沿 X、Y、Z 轴的三个旋转角度和三个*移偏移。

简化矩阵

我们可以看到相机外部矩阵的最后一行只有 0 和 1。它不会给转换增加任何价值,它的唯一目的是增加一个额外的维度——这意味着,正如我们将在下面的示例中看到的,我们可以很好地删除最后一行。

例子

结尾的动手例子就像饭后甜点——没有甜点的饭是不完整的!

安装

包含所有代码的 GitHub 库可以在这里找到。

您可以通过运行以下命令来设置环境:

# create a virtual environment in anaconda
conda create -n camera-calibration-python python=3.6 anaconda
conda activate camera-calibration-python# clone the repository and install dependencies
git clone https://github.com/wingedrasengan927/Image-formation-and-camera-calibration.git
cd Image-formation-and-camera-calibration
pip install -r requirements.txt

注意:这里假设你已经安装了 anaconda。

我们将使用两个主要的库:

  • pytransform3d: 该库具有强大的 3d 空间可视化和变换功能。
  • ipympl:这是一个游戏规则改变者。它使 matplotlib 绘图具有交互性,允许我们在笔记本中实时执行*移、缩放和旋转,这在处理 3D 绘图时非常有用。

直觉例子

在这个例子中,我们将首先为旋转和*移创建变换矩阵,将它们合并成一个矩阵,并使用它来变换摄像机。然后,我们将通过对这个变换矩阵求逆来创建基变换矩阵,并将其应用于一个点,然后将其坐标从世界坐标系更改为摄像机坐标系。

下面是示例的笔记本,也可以在存储库中找到。

让我们一步一步地分解它:

  • 首先,我们导入必要的库。utils.py文件包含所有必要的助手函数。神奇的命令%matplotlib widget启用 ipympl 后端,这使我们能够玩的阴谋。
  • 接下来,我们定义必要的参数,如角度,旋转顺序,*移偏移,焦距和图像*面的大小。焦距和图像*面只是为了演示,我们将在下一篇文章中详细讨论它们。
  • 这里我们保持简单,围绕标准的 y 轴旋转𝜋/4 角。然而,我们可以绕任意轴旋转任意次。只是要注意旋转的顺序。我们的*移偏移量是[0, -8, 0],它是沿 Y 轴的 8 个单位。
  • 使用这些参数,我们为旋转和*移创建变换矩阵。
  • 接下来,我们使用变换矩阵变换最初位于原点的相机,并绘制它。由于 ipympl,剧情是交互式的。尽量摆弄剧情,玩不同的视角。

  • 接下来,我们通过对变换矩阵求逆来创建基矩阵的变化,这是摄像机的外部矩阵。
  • 最后,我们取一个世界坐标为[-1/√2, -8, 1/√2]的点,应用基变换的变换,得到摄像机的坐标为[0, 0, 1]。这是有意义的,因为该点就在相机 Z 轴的正上方。

结论

我希望你喜欢这篇文章。在下一篇文章中,也就是系列的第 3 部分,我们将讨论相机的内在特性,看看这些点是如何投射到相机的图像*面上的。如果你有任何疑问或问题,请在下面的评论中告诉我。

参考

  1. 计算机视觉简介— Udacity

图像制作者名单

本文中的所有图片和数字,除非在标题中明确提及其来源,否则均由作者提供。

相机固有矩阵及 Python 中的示例

原文:https://towardsdatascience.com/camera-intrinsic-matrix-with-example-in-python-d79bf2478c12

Python 中图像形成和相机校准综合教程系列的第 3 部分

介绍

上一篇文章中,我们学习了相机外物学,即从相机视角观察世界。在本文中,我们将了解相机如何形成图像,并了解其内在参数。

点的投影

图像形成的基本思想是捕捉一个点在照相机图像*面上的投影。图像中的像素对应于图像*面上的投影。请记住,图像*面就像一个胶片,捕捉从点反射回来的光线。让我们看看这是如何工作的:

点的投影

在上图中,相机中心位于原点𝑂,图像*面从原点到-ve Z 轴的距离为𝑓。𝑓被称为焦距,通常是众所周知的相机。点𝑃在像*面上的投影是𝑃′.𝑃的坐标是(𝑥,𝑦,𝑧),𝑃′的坐标是(𝑥′,𝑦′,𝑓).)我们的目标是找到𝑃′.的坐标

from the figure,
      △OMP and △OO′P′ are similar triangles.
⟹    x′/x = y′/y = f/z
⟹    x′ = x ∗ f/z and y′ = y ∗ f/z

我们找到了𝑃′.的坐标从上面的等式中,我们可以看到,随着点𝑃远离相机,其𝑧坐标增加,其投影将变得更小。所以,一个物体离相机越远,它在图像中出现的就越小。

要获得图像中的像素,我们只需获取投影坐标,放弃最后一个维度,然后绘制这些点。

例如,我们发现𝑃′的坐标为(𝑥𝑓/𝑧,𝑦𝑓/𝑧,𝑓).它的图像坐标将是(𝑦𝑓/𝑧).𝑥𝑓/𝑧让我们把图像坐标表示为(𝑢,𝑣),然后:

(u, v) = (xf/z, yf/z)

好了,我们已经看到了照相机是如何成像的。那么,我们完成了相机内部的工作了吗?不会。在现实世界中,事情不会像预期的那样发展,其他参数会影响图像的形成。

让我们来看看每一个。

影响图像形成的参数

规模

当你购买相机时,它的可调焦距会在描述中给出,通常以毫米为单位,但你工作的空间可能会使用不同的单位,如像素。因此,我们需要引入一个比例因子,使单位标准化。

(u, v) = (𝛼 *x/z, 𝛼 * y/z)

在这里,你可以认为𝛼是缩放后的焦距或转换因子。

矩形像素

理想情况下,我们假设像素是正方形的,但在现实世界中,它们可以是不同高度和宽度的矩形。因此,我们需要为每个维度引入单独的比例因子。

(u, v) = (𝛼 * x/z, 𝛽 * y/z)

这里,𝛼是宽度尺寸的比例因子,𝛽是高度尺寸的比例因子。

抵消

从相机中心到图像*面的垂直线称为光轴。该轴与像*面相交的点称为光心。通常,光学中心和图像*面的原点彼此重合,但在现实世界中,它们可能会分开,如下图所示:

光学中心和原点可能不重合

因此,我们在等式中加入一个偏移来说明这一点:

(u, v) = (𝛼 * x/z + x0, 𝛽 * y/z + y0)

这里(𝑥0,𝑦0)是偏移量。

斜交

到目前为止,我们已经将图像*面描绘成一个矩形,宽度和高度方向相互垂直。但是在现实世界中,图像*面可能是倾斜的,类似于下图所示的*行四边形:

理想像*面与倾斜像*面

那么我们该如何应对呢?我们假设这些轴相互垂直,但实际上它们是成角度的。你想想,这是个换基问题。给定一个点相对于标准正交坐标轴的𝑃,我们需要用斜坐标轴来表示它。设(𝑦𝑥)是𝑃在标准正交基上的坐标,设(𝑦′𝑥′)是它在斜基上的坐标。我们的目标是找到(𝑥′,𝑦′).

From the above figure,
      cos(90−θ) = y/y′
⟹    sinθ = y/y′
⟹    y = y′sinθ
⟹    y′= y / sinθalso,
      sin(90−θ) = (x - x′)/y′
⟹    y′cosθ = x - x′
⟹    x′ = x - y′cosθ
but,  y′= y / sinθ
⟹    x′ = x - ycosθ / sinθ
⟹    x′ = x - ycotθ

既然我们找到了(𝑥′,𝑦′),让我们把他们纳入方程。我们只需要用这些新坐标替换旧坐标。

 u = 𝛼 * (x - ycotθ)/z + x0
      v = 𝛽 * (y / sinθ)/z + y0⟹    u = 𝛼x/z - (𝛼y/z)cotθ + x0
⟹    v = (𝛽y / zsinθ) + y0

相机固有矩阵

最后,在考虑了影响图像形成的参数之后,图像坐标给出如下:

(u, v) = [𝛼x/z - (𝛼y/z)cotθ + x0, (𝛽y / zsinθ) + y0]

我们可以用齐次坐标将此表示为矩阵乘法:

上面的矩阵叫做相机固有矩阵,用𝜅.来表示给定世界中一个点在摄像机下的坐标,我们可以将其乘以摄像机固有矩阵,以获得该点在图像中的齐次坐标。

Here,
𝑃′ - Homogeneous coordinates of the point in the image
𝜅  - Camera Intrinsic Matrix
𝑃𝑐 - Homogeneous Coordinates of the point in the world wrt camera 

要从齐次坐标转换,我们只需除以最后一个元素:

这里,(𝑢,𝑣)代表图像中点的欧几里德坐标或像素位置。

如果您观察,摄像机固有矩阵的最后一列是零列,我们可以删除它,因为它没有任何贡献,并进一步简化矩阵为:

现在,矩阵方程可以重写为:

这里,不需要以齐次形式表示点坐标。

Here,      
      𝑓      - focal length
      𝑠      - skew factor
      𝑐𝑥,𝑐𝑦   - offset
      𝑎     - aspect ratio

如您所见,相机固有矩阵中有五个自由度。

例子

所有的理论可能会让你有点困惑。因此,让我们做一个动手的例子来理清事情。

安装

包含所有代码的 GitHub 库可以在这里找到。

假设您之前没有设置环境,现在可以通过运行以下命令来设置环境:

# create a virtual environment in anaconda
conda create -n camera-calibration-python python=3.6 anaconda
conda activate camera-calibration-python# clone the repository and install dependencies
git clone https://github.com/wingedrasengan927/Image-formation-and-camera-calibration.git
cd Image-formation-and-camera-calibration
pip install -r requirements.txt

注意:这里假设你已经安装了 anaconda。

我们将使用两个主要的库:

  • pytransform3d: 该库具有强大的 3d 空间可视化和变换功能。
  • ipympl:这是一个游戏规则改变者。它使 matplotlib 绘图具有交互性,允许我们在笔记本中实时执行*移、缩放和旋转,这在处理 3D 绘图时很有帮助。

直觉例子

在本例中,我们将考虑一个简单的设置,其中相机位于原点,图像*面位于+ve Z 轴上方(我们将使用左手坐标系)。接下来,我们绘制一些点,使得它们都位于与图像*面*行的同一*面上,并位于图像*面的顶部。这样就很容易想象投影和图像的形成。接下来,我们创建相机固有矩阵,并使用它将点投影到图像*面上并形成图像。最后,我们使用这个矩阵变换图像*面。下面提供了详细的分步解释。

笔记本嵌在下面:

让我们一步一步来看:

  • 首先,我们导入必要的库。utils.py文件包含所有必要的助手函数。神奇的命令%matplotlib widget启用 ipympl 后端,这使我们能够玩的阴谋。
  • 接下来,我们定义图像*面和点的参数。这里,我在 XY 极限(-5,5)之间的 z=5 的高度上均匀地取了 6 个点。图像*面处于 z=2 的高度。最后,我们把它们都画出来。

  • 接下来,我们创建相机固有矩阵,它允许我们将点投影到图像*面上并形成图像。这里,我们创建了四个具有不同参数的矩阵来说明每个矩阵的效果。

  • 好了,我们已经修改了参数,并看到了它们对图像的影响。现在,有没有可能想象出在每种情况下像*面看起来是什么样子?摄像机内矩阵是基矩阵的变化,它的作用是从图像*面上采样点。现在,我们看到,如果我们对基变换矩阵求逆,就可以得到一个变换矩阵。因此,让我们取相机固有矩阵的逆矩阵,并将结果应用于图像*面。然而,我们必须从场景中移除焦距,因为它涉及图像*面的仰角,并且我们希望仰角是恒定的。我们只是想看看其他参数对图像*面的影响。

  • 上图显示了倾斜参数 s 设置为 2 时的图像*面,其图像显示在上图的右上角。请注意图像和图像*面的方向是相反的。

结论

我希望你喜欢这篇文章。我鼓励您使用笔记本,修改参数,并查看它们对图像的影响。如果你有任何疑问或问题,请在下面的评论中告诉我。

参考

  1. 计算机视觉介绍— Udacity

图像制作者名单

本文中的所有图片和数字,除非在标题中明确提及其来源,否则均由作者提供。

posted @ 2024-10-18 09:31  绝不原创的飞龙  阅读(357)  评论(0)    收藏  举报