用 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 Engine 和 Google Cloud Run 的文章。
我真的很喜欢使用 Snowpark for Python,我对它的许多特性感到兴奋,包括 Python UDFs、模型构建和部署。我想写的东西太多了。敬请关注我的下一篇文章!
承认
感谢您的反馈和支持!
参考文献:
- https://panel.holoviz.org/
- 【https://datashader.org/
- https://www . snow flake . com/blog/snow flake-partners-with-and-investments-anaconda-to-bring-enterprise-grade-open-source-python-innovation-to-the-data-cloud/
- https://www.snowflake.com/snowpark-for-python/
我是 Anaconda 的高级数据科学家 Sophia Yang。请随时在 Twitter 、 Linkedin 和 YouTube :)上与我联系
构建随机森林分类器来预测神经尖峰
在 Python 中构建随机森林分类器以预测真实神经细胞外尖峰的子类型的分步指南。
未喷涂上的 Fakurian 设计的“Braintree”
G *考虑到人脑本身神经元的异质性,分类工具通常被用来将电活动与不同的细胞类型和/或形态相关联。*这是神经科学界的一个长期问题,在不同物种、病理、大脑区域和层次之间可能有很大差异。幸运的是,随着快速增长的计算能力允许机器学习和深度学习算法的改进,神经科学家获得了进一步探究这些重要问题的工具。然而,正如 Juavinett 等人所述,在大多数情况下,编程技能在社区中的代表性不足,教授这些技能的新资源对于解决人类大脑的复杂性至关重要。
因此,为了提供一个相关的用例,在本文中,我们将为神经细胞外锋电位波形数据集构建一个随机森林分类器算法。随机森林分类器是广泛应用的监督学习模型,是解决分类问题的有效而强大的算法。可以说,它们位于分类器层次结构的顶端,旁边还有其他算法,如:逻辑回归、支持向量机、朴素贝叶斯分类器和决策树。
在此过程中,我们还将完成多维降维和聚类步骤(带有示例代码),以建立一个有监督的学习问题。一旦完成,任何级别的程序员将能够(1)识别和绘制独特的细胞外波形,( 2)实施随机森林算法对它们进行分类。
什么是决策树?
返璞归真!首先,为了理解随机森林图,掌握决策树是很重要的(这将很快有意义)。简而言之,决策树是一种由“节点”和“分支”组成的树状模型,是一种可视化展示决策及其结果的工具。
如 Angel Das 、 z_ai 所总结的,并且如下图所示,节点可以分类如下:
- **根节点:**起始节点。在决策树中,它通常评估最能分割数据的变量。
- **决策节点:**根据条件分支的子节点。
- **叶节点:**树的最终节点,不能再分支。
从根节点开始,按照 if-else 结构在决策节点对一个独立变量(连续变量或分类变量)进行拆分。沿着树向下移动,数据不断被“分割”,直到到达可以进行分类的叶节点。如果不是这种情况,则重复该过程,直到达到结果。
带有定义的决策树插图。图片作者。
决定分裂
决策树使用多种算法来决定问题的最佳分割,但**“基尼指数”和信息增益的“熵”是最常见的标准。两者都确定了节点处标签的杂质**,这是在判定节点处异质或混合值程度的度量。这是至关重要的,因为决策树算法找到了最佳标准,使集合成为更同质的子集(即↓杂质)而不是异质的子集(即↑杂质)。
熵是对一组数据的杂质的一种度量,可以通过以下公式表示:
熵公式。其中“ Pi 表示数据集中类别“I”的概率。
通过使用熵计算,可以计算节点处的随机性或无序度。信息增益使用熵来量化哪个特征通过减少熵来提供关于分类的最大信息,并因此做出相应的分割:
信息增益公式。从类“ X ”上的“ Y ”的熵中减去类“ Y 的熵。
与熵相似,基尼系数(也称为基尼系数或基尼系数)在 0 和 1 之间变化。它通过测量目标属性值的概率分布之间的差异来计算分类的杂质。0 的输出是同质的,而 1 是不纯的,这表明新的随机数据被错误分类的可能性很大。一旦实现,就进行节点分裂,这减少了这种计算的杂质。对于“C”类不同阶层,基尼公式将为:
基尼系数公式,其中" C" 表示数据子集中的总类别数,而" i" 是从 C 中选择的类别。
决策树的衰落
虽然决策树可以为分类问题提供一个可视化的解决方案,但是使用该算法的缺点应该被考虑在内:
- 它们将继续形成分支,直到每个节点都是同质的。如果测试一个小样本,这就产生了一个过度拟合的问题(鲁棒性)。
- 一次仅使用一个独立变量来确定分类,这可能会导致性能和准确性问题。
- 不处理缺少的值和数值属性。
随机森林分类算法是如何工作的?
理解分类和回归树(CART)的基本概念的重要性在这里发挥了作用,因为随机森林使用许多决策树的集合而不是一个来做出最终决策(集合技术)。这是特别强大的,因为模型的集合将胜过单个模型。最终,这解决了与决策树相关的一些缺点,因为它提高了性能和健壮性。值得注意的是,集合中的每棵树都是相对不相关的,这很重要,因为正如饶彤彤所概述的:
这些树保护彼此免受各自错误的影响(只要它们不总是在同一个方向出错)。虽然有些树可能是错误的,但许多其他的树将是正确的,因此作为一个群体,这些树能够朝着正确的方向移动。
一种简化的随机森林分类算法的图示。图片作者。
随机森林算法如何构建多棵树?
随机森林算法实现了 (1)引导聚合和(2)特征随机性的组合,以使用相同的数据集构建许多决策树。总体而言,它们在这样做的同时保持相对不相关,这是一个重要的特性:
- bootstrap 聚合(又名 Bootstrap 或 bagging) 是一种技术,涉及在给定迭代次数和变量(Bootstrap 样本)的基础上随机抽样数据子集,并进行替换。来自所有迭代和样本的预测通常被平均以获得最可能的结果。重要的是要理解,它不是将数据“分块”成小的大小,并在其上训练单个树,而是仍然保持初始数据大小。这是一个应用集合模型的例子。
- 特征随机性主要作用是降低决策树模型之间的相关性。与可以利用所有特征来辨别最佳节点分裂的决策树相比,随机森林算法将随机选择这些来进行决策。最终,这允许训练也在不同的特征上发生。
总的来说,通过这些方法,随机森林算法可以在相同数据的不同集合上进行训练(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)剪影方法:
- **Elbow 方法:**计算总的类内平方和(wss),它衡量聚类的紧密性。目的是尽可能减少这种情况。
- **剪影法:**我们将使用剪影法回测肘法。这种方法通过确定对象在其中的位置来测量群集的质量。高轮廓值表示“好”的聚类,而低轮廓值表示“差”的聚类。
这是这两种方法之间的微妙平衡,可以为您的数据确定最佳的聚类数。所以,让我们一步一步来:
# 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 关注我,或者在 GitHub 和 Kaggle 上加入我。
用 Python 构建亚马逊产品推荐系统
我为亚马逊的电子产品类别建立了一个推荐系统
介绍
T 这个项目的目标是为电子产品类别部分重建亚马逊产品推荐系统。
现在是十一月,黑色星期五到了!你是哪种类型的购物者?你是把当天想买的所有商品都存起来,还是宁愿打开网站,看看现场打折促销?
尽管网上商店在过去十年中取得了令人难以置信的成功,显示出巨大的潜力和增长,但实体店和网上商店的根本区别之一是消费者的冲动购买。
如果顾客面前摆着各种各样的产品,他们更有可能购买原本不打算购买的东西。冲动购买的现象居然被网上店铺的配置限制。同样的不会发生对于他们的物理同行。最大的实体零售连锁店让他们的顾客通过一条精确的路径,以确保他们在离开商店之前参观了每一个通道。
像亚马逊这样的网上商店认为可以重现冲动购买现象的方法是通过推荐系统。推荐系统识别客户刚刚购买或查看的最相似的或互补的产品。其目的是最大化网上商店通常缺乏的随机购买现象。
在亚马逊上购物让我对其中的机制非常感兴趣,我想重现(甚至是部分)他们推荐系统的结果。
根据博客“Recostream”的说法,亚马逊产品推荐系统有三种依赖关系**,其中一种是产品对产品推荐。当用户实际上没有搜索历史时,该算法将产品聚集在一起,并根据商品的元数据向同一用户推荐它们。**
数据
项目的第一步是收集数据**。幸运的是,圣地亚哥加利福尼亚大学的研究人员有一个存储库,让学生和组织外的个人使用这些数据进行研究和项目。可以通过下面的链接以及许多其他与推荐系统相关的有趣数据集来访问数据【2】【3】。产品元数据最后更新于 2014 年;许多产品今天可能无法买到。**
电子类元数据包含 498,196 条记录,总共有 8 列:
asin
—与每个产品相关联的唯一 IDimUrl
—与每个产品相关联的图像的 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.DataFrame
将data
列表作为数据帧读取,我们现在可以用它来构建我们的推荐器。
#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 文件,它用{}
来打开和关闭字符串,每个列名后面都跟有:
和相应的字符串。你可以注意到第一个产品缺少了price
、salesRank
、related
和brand information
。这些列自动填充有NaN
值。
当我们以数据框架的形式阅读整个列表后,电子产品显示出以下 8 个特征:
| asin | imUrl | description | categories |
|--------|---------|---------------|--------------|| price | salesRank | related | brand |
|---------|-------------|-----------|---------|
特征工程
特征工程负责数据清理并创建列,我们将在其中计算余弦相似度得分。由于 RAM 内存的限制,我不希望专栏特别长,因为评论或产品描述可能会特别长。相反,我决定创建一个包含categories
、title
和brand
列的“数据汤”。但在此之前,我们需要消除这三列中包含 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 & Noble NOOK Power Kit in Carbon BNADPN31
[['Electronics', 'eBook Readers & Accessories', 'Power Adapters']]
Barnes & 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 个参数:title
、cosine_sim
、df
和indices
。调用函数时,标题将是唯一要输入的元素。
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 & 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
列表中的一些asin
id 是否出现在推荐中。
#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 构建强大的个性化推荐引擎。包装出版。
如果你想看看我以前的一些文章
** **
构建一个语义分割的计算机视觉算法部署在边缘
生产计算机视觉项目中的技术挑战和学习
照片由美国国家癌症研究所通过 Unsplash 提供
在本文中,我们将讨论在边缘设备上部署语义分割**算法的挑战和技术。**特别是,我们将介绍在我们的项目中面临的技术挑战,这是一个部署在 iOS 设备上的自动伤口分割模型。
我们将首先简要介绍项目背景,然后介绍我们面临的技术挑战以及克服这些挑战的方法。
技术挑战 1 —小且不平衡的数据集
- 数据扩充
- 迁移学习
- 选择正确的指标
- 定制损失函数
技术挑战 2—边缘部署
- 性能与尺寸的权衡
- 量子化
技术挑战 3——创建灵活的再培训渠道
- 实验跟踪— WandB
- 配置管理—九头蛇
- 超参数搜索—贝叶斯优化
项目背景
今天,世界上大约有 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 服务
在本教程中,我们将回顾使用云服务,如 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_ENDPOINT 和 AZURE_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 服务器解决方案。
建立一个简单的人工智能驱动的人在回路系统来管理野生动物相机陷阱图像和注释
带着目标和同理心为非营利组织寻求可持续的技术解决方案
在这篇文章中,我简要记录了我设计和构建人工智能驱动的人在回路系统的旅程,以管理猫科动物保护基金的相机陷阱图像&注释。如果你对技术在野生动物保护、构建计算机视觉应用或与非营利组织合作中的作用感兴趣,你可能会发现这篇文章很有用。如果你想在进入这篇文章之前有一个高层次的概述,你也可以看看这个幻灯片。
猫科动物保护基金会是一个非营利的野生动物研究和保护组织,总部位于旧金山湾区,专门研究野猫,尤其是美洲狮。为了促进他们的研究,他们主要使用相机陷阱,到 2021 年,他们的业务已经发展到超过 100 个相机陷阱,每年生成超过 100 万张图像。然而,这种增长也意味着他们的数据管道开始淹没他们的底层软件工具,促使他们寻找更具可扩展性的解决方案。
他们知道将基于云的解决方案与人工智能/人工智能结合使用是一条出路,为了帮助他们建立这种解决方案,他们在 LinkedIn 上发布了一个招募志愿者的帖子,我碰巧发现了这个帖子。对于在大卫·爱登堡长大的人来说,单是“保护”这个词就足以引发兴奋感,但当我得知他们需要一个基于云的人工智能解决方案来改善他们的数据管道时,我被说服了!
1.笑一笑,美洲狮,你上镜头了!
在我们开始之前,让我们快速了解一下什么是相机陷阱,以及它们如何帮助野生动物研究。这里有一个 TL;来自本 WWF 帖子的博士——
- 相机陷阱是一种带有传感器的相机,可以检测前方的活动,并自动触发图像或视频捕捉。
- 他们让野生动物研究人员以不干扰它们自然行为的方式收集关于我们同类的数据。
现在,你可能在想,“但是为什么我们不能像我们做人口普查一样,穿过森林采访这些动物,并在最后击掌庆祝呢?”。对此,我显然会说,“好主意!我喜欢和五只美洲狮击掌,但我认为生态学家会翻白眼。然而,严肃地说,“在森林里漫步并收集数据”实际上是一种有效的技术,十多年前我曾经自愿这样做,但这是一个昂贵的、劳动密集型的过程,需要人们在森林里踮着脚尖,冒着被毫无防备、受到惊吓的大象踩成煎饼的风险,就我而言!
虽然收集数据的不同技术有不同的权衡,但在猫科动物的情况下,相机陷阱是一个很好的来源,因为猫通常很害羞,喜欢夜间活动。因此,为了支持他们的研究,Felidae 操作了 100 多个照相机陷阱,主要在旧金山湾区,每年产生成千上万的图像!
2.数据供应链
虽然相机陷阱很棒,但它们生成的原始图像在变成我们都爱的甜蜜科学之前还有一段路要走。通常,研究人员需要知道
- 照片拍摄的地点——这是通过标注纬度&经度设置相机陷阱时收集的。
- 图像拍摄时间—相机陷阱为每张图像生成一个时间戳,指示图像拍摄时间。
- 图像中的动物是什么——一旦收集了图像,注释者就会查看每张图像,并记下其中的动物。
正如您可能猜到的那样,手工注释这些图像是这个流程中最耗时的过程。但是当你考虑到相机失灵、错误、遮挡、吹树叶、月球漫步的小妖精(好吧,这可能是我编的)等等因素时。,你会得到很多甚至没有任何动物的图片。如果这还不够,只是为了迷惑研究人员,这些相机陷阱偶尔会决定将他们的内部时钟重置为 1970 年的某个时间,从而打乱所有的时间戳。他们似乎就是不能抓住机会!因此,为了管理这种错综复杂的数据管道,研究人员/组织根据其规模和对资源的访问权限设置了不同的工作流。
为了更好地理解这一点,让我们仔细看看 Felidae 的数据管道,它们遇到的不同问题,以及用于解决这些问题的工具套件。大体上,他们的工作流程可以分为三个任务
- 设置相机陷阱 —相机陷阱被布置在预定的位置,为了跟踪它们,Felidae 使用 Google Sheets 将相机陷阱的 id 与其纬度&经度关联起来。
- 收集图像——每隔一段时间,来自这些相机陷阱的存储卡就会被收集起来,通过收存箱或者通过物理邮件传送到办公室。Felidae 的工作人员随后将这些图像以及有关源摄像机陷阱的信息保存到物理计算机上。
- 注释图像 — 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 需要一个集成了以下功能组件的单一系统——
- 库存管理对相机陷阱、盒子、内存卡等实物库存进行编目。跟踪他们在战场上的部署。
- 摄像头陷阱管理追踪这些摄像头在研究期间监控的物理位置。
- 图像管理以简单、结构化的方式存储、浏览和共享他们收集的成千上万张图像。
- 注释管理给这些图片加上它们所包含的动物标签。
- 收集、上传和注释这些图片的志愿者和工作人员的用户管理。
如前所述,Google sheets 用于跟踪库存、相机陷阱和用户;Dropbox/Windows 文件系统管理的图像;最后,摄像机基础处理注释。然而,这些都没有在下面相互连接,这样做需要大量的体力劳动!当时,虽然没有万能的灵丹妙药(Wildlife Insights 已经很接近了),但是有大型活跃的工作组支持的相当成熟的子组件。因此,最合理的选择是简单地采用这些来代替内部线程,并将事情削减到只有一个薄薄的粘合层,即 web 应用程序。
我在这里的指导原则,由无数黑客马拉松塑造,是一个偏爱速度的原则。因此,当我开始时,我的首要任务是尽快部署一个基本但功能齐全的端到端产品。在给定的环境下,这种方法是理想的,因为在开发过程中,必须尽早将最终用户纳入进来。此外,作为一个内部应用程序,对错误和故障的容忍度更高。但是,也许更重要的是,它提供了当时非常需要的东西——18 个多月后的切实进步感,这将鼓舞士气,并在最近建立信任。
5.1.被解放的姜戈
核心粘合组件,即 web 应用程序,是以遍布 SQL、Node & React 的数千行部分功能代码的形式存在的。但令我惊讶的是,大多数(如果不是全部的话)只是在基表上实现了 CRUD 操作。虽然这些框架中可能有提供相同功能的第三方包,但我和其他任何人都不太了解这个生态系统,无法继续这些思路。然而,我对另一个流行的框架有相当多的经验, Django !
现在,添加新的技术依赖不是一件容易的事情,特别是当这种依赖会渗透到单点故障时,在这种情况下,就是我。但是,我对自己的承诺很有把握,在这种情况下,考虑到姜戈提供的众多好处,冒这个风险似乎是合情合理的
- 一个用于基本 CRUD 操作的内置 GUI, Django Admin 接口,以及一组可供选择的皮肤(我使用 Jazzmin )。
- 令人惊叹的 django-allauth 软件包增强了内置的用户管理系统。
- ORMs 超过原始 SQL,我发现它更简单、更干净(个人偏见)。
- 一种统一的语言,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 展示了一套功能,再次证明了它非常有用。具体来说,
- 它使识别文件类型(图像、视频、文档等)变得容易。)特别是对于媒体,它还暴露了 Exif 属性,如时间戳和位置(纬度/经度)——这对相机陷阱至关重要!
- 它自动生成内容哈希来唯一识别图像。
- 最后也是最重要的,它提供了一个简单的缩略图 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 的员工参与进来,所以让应用程序实时可用非常重要,即使是以准系统的形式。因此,关于底层软件基础设施的决策必须从一开始就做出。但是在开始之前,让我们后退一步,看看系统的不同组件,只是这次是作为软件的一部分。大体上,我们有—
- 网络应用,Django,作为用户与系统交互的主要界面。
- 一个关系数据库用来存放关于库存、相机陷阱、用户、图像和注释的结构化数据。
- 媒体资产主要包括要在网站上呈现的摄像机捕捉图像的压缩版本。
- 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 游戏的几个程序的内部工作原理之前,至少了解一下本文中使用的编程语言的基础知识是很方便的,这些语言主要是 Java 和 Python。因此,如果您正在开始这一领域的学习过程,并且正在为下面显示的任何步骤而奋斗,因为我们将涉及一些高级主题,如正则表达式或多线程,您可以随时访问在线免费学习资源,如freeCodeCamp或stack 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 在报酬增长到无穷大时变得非常关键。在游戏中,我们实际上是在控制代理人的短期和长期报酬有多高。这不是奖励带来的唯一问题,因为它是强化学习问题中最具挑战性的任务之一。如果您想了解更多关于这些问题的信息,请访问以下详细的 文章 。
培训过程
在讨论代理的学习过程之前,有必要定义几个与它在每个状态下做出决策时所遵循的标准相关的概念。
一方面,代理人需要有一个工具在特定的状态下及时做出决策 (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 上实现前面描述的策略,并做一些实质性的修改,为用户带来更好的可玩性和交互性。但首先,让我们简要总结一下我们的程序将遵循的整个算法。
- 用所有可能的单词初始化字典,并计算它们的分数。由于我们将使用西班牙语,它最初将包含 7980 个单词。
- 为程序提供六次机会来猜测玩家正在想的单词。然而,这次输入的不是一个单词,而是游戏本身在最初版本中应该给出的反馈。因此,玩家不是插入颜色代码,而是输入一系列字符,其中 0 代表相应字符的灰色,1 代表黄色,2 代表绿色。
- 挑选具有最大分值的单词并显示给用户。
- 等待并验证他的字符序列。如果不一致,请再试一次并显示一条错误消息。
- 使用正则表达式来检查程序是否猜中了正确的单词并赢得了游戏。
- 如果程序没有猜出这个单词,它将在未来的迭代中更新字典以丢弃不可能的单词,并重新计算得分值。
- 如果程序用完了尝试次数,它将“输掉”游戏并停止执行,除非用户想再玩一次。**
词典创作
如前所述,我们需要一组五个字母长的西班牙语单词来开始编码和测试我们的程序。我们可以从网上下载一个,但是相反,我们将使用 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
数据科学|工程|平台
规划强大的数据科学架构的构建模块
在现实世界中,数据科学流程超出了处理某些数据和训练预测模型的步骤。在生产系统中,数据科学过程对数据和模型交付、审计和准备感兴趣。数据科学是高度精细的,正确掌握数据科学是决定企业数据科学项目能否成功的关键因素。
在本文中,我们将讨论构建一个高效且健壮的 DS 平台所需的不同组件。
目标
我们为什么需要数据科学平台?
我们想从这个平台中得到什么?
企业在数据科学服务方面失败的主要原因与他们无法正确量化其 DS 需求有关。大多数人无法回答*“你为什么需要 DS?”*。
不幸的是,一些企业跳上了当时流行词汇的宣传列车,觉得有必要做数据科学。归根结底,这才是最重要的,不是吗?这有三种主要的可能情况:
- 运气好的话,会产生一个成功的项目
- 惨败
最终经常发生的是无法兑现宣传。因此,未能交付会导致声誉受损,当然,还会损害业务!
现代企业总是有一个数据科学平台的用例。数据是当今世界的关键。毕竟这是世界上最有价值的资产。然而,大多数人没有理解的是数据科学平台和人工智能(AI)平台之间的区别。数据科学超越了人工智能的局限。
数据科学是数据体跳动的心脏。数据科学是任何组织都可以理解其原始数据的媒介。数据科学最简单的形式是一个工具集,用于交付分析智能。
那么,为什么需要数据科学平台?我们想从这个平台中得到什么?
很简单,数据科学平台使您能够真正理解您的数据。
我们都经历过身体上的关系,我相信我们所有人都曾一度希望自己知道对方在想什么。作为一个组织,你与你的客户和利益相关者有关系。数据科学就是这么做的!让你知道并理解你的伙伴在想什么。它为你提供了正确的知识来评估什么有效,什么无效。帮助您决定按哪个按钮。
组件
在我们深入探讨数据科学平台的制胜之道之前,我想指出两个极其重要的注意事项:
- 思考未来
与任何技术一样,数据技术在范围和数量上都在快速发展。因此,保持我们对未来可伸缩性机会的选择可能是明智之举。 - 思考模块化 在构建我们的数据科学平台时,我们必须将它视为另一种产品。作为一个组织,你不应该建立一个数据科学平台来简单地帮助你的工作流程。为您的数据科学平台感到骄傲!展示它。推销它。最重要的是,卖掉它!
是的,你没听错。卖掉它。以这样一种方式构建您的平台,每个组件都是个性化的,可以作为单独的产品出售。该平台的每一个组件都应该能够完美地独立工作,并且能够轻松地与其他解决方案集成。
因此,让我们开始讨论可靠的数据科学平台的关键组件。
平台组件的数据流图。作者图片
组成部分 1:数据信息系统
**第 1 部分:**任何与数据相关的产品都需要从处理数据的传输开始。我们如何将数据放入我们的平台?我们有两个主要选择:
- 实时数据流
- ETL 或批量加载
我坚信实时数据流是几乎所有场景的必由之路;然而,这在很大程度上依赖于基础设施和用例。如果实时流媒体是你的选择,那么阿帕奇卡夫卡就是你的朋友。卡夫卡只是一个简单的信息传递解决方案。还有各种其他的解决方案,比如亚马逊 Kinesis 、 TIBCO Spotfire 和 RabbitMQ 。还是那句话,选你喜欢的毒药。
查看我的另一篇关于使用 Python 处理 Kafka 事件数据流的文章!
**第二部分:**数据存储方面。选择数据库系统在很大程度上取决于以下特征:
- 速度 —数据传入的速度有多快?
- 卷 —数据有多大(大小)?
- 多样化—你有多少种不同的数据结构?
- 延迟 —您希望数据库多快返回结果?
这是一个很大的讨论领域,并且远远超出了本文的目标,所以我不会深入讨论。
**第三部分:**数据处理与特征工程。一旦我们开始获取数据流,我们就可以开始动态处理这些数据。其最终目标是创建特征库(即预先计算的特定特征)。随着机器学习解决方案在数据科学平台中的集成,特征存储变得更加相关。在生产中,任何运行的机器学习模型都会不断地从特征存储中读取以检索它们的数据。
对此有各种解决方案。人们可以使用 Spark 之类的东西开发自己的工具来处理数据流,并将结果存储在数据库对象中(这取决于人们选择的数据库风格)。反过来,人们也可以使用现有的功能商店实现,如盛宴、泰克顿或霍普斯沃斯。这里要记住的重要一点是,我们应该始终有一个真实的来源,特别是对于工程特征。
组件 2:智能
这个组件是关于建立一个框架,允许自动和手动的机器学习模型训练。所有模型培训流程都应遵循标准模型评估策略(适用于所有模型),该策略将执行 A/B 测试和统计假设测试,以评估和比较基础模型。
最终模型应通过自动超参数调整进一步优化。在部署到您的生产环境之前,每个模型都应该首先通过这个框架。
适当的模型版本控制在这里也很关键。我们需要能够回答以下问题:
- 现在的模特是什么时候训练出来的?
- 当前模型的性能指标是什么?
- 那一天生产的是哪种型号?
这个任务的一个很棒的工具是 MLFlow 。
在这一点上,我们也应该开始考虑数据版本控制(DVC)——主要是因为同样的原因,你想要版本化你的代码库。
对于 DVC,我们可以:
- 作为训练时快照的版本数据(例如 MLFlow 中的工件),或
- 使用专门的数据版本系统;
这种方法将允许我们分别对数据和代码进行版本控制(数据的任何变化都会影响代码,但并不是所有的代码变化都会影响数据——所以它们应该是独立的单元)。与功能商店类似,人们可能会选择内部构建 DVC 或通过第三方解决方案。一些例子包括厚皮动物,DVC 和飞机库。
如果对 Hangar 感兴趣,可以随意查看我关于这个纯 Python DVC 实现的另一篇文章。
从长远来看,在我们的数据科学平台中正确实施 DVC 将被证明是非常强大的。特别是有了 MLFlow 这样的工具,我们就可以将 MLFlow 上版本化的训练数据模型绑定到该模型使用的训练数据集(在 DVC 版本化)。这也允许我们对我们的数据进行审计跟踪。尽管如此,模型版本控制和 DVC 都应该与一个元数据存储进行通信,以建立一个关于什么、何时以及如何的强大的审计追踪。但稍后会详细介绍。
这个组件的另一个方面是可解释的人工智能(XAI)。建立机器学习模型是一项任务。但是向最终用户提供可解释的预测是另一个层面的任务。现在,有许多 XAI 软件包可以帮助你解释任何机器学习算法的结果。因此,在我们的智能组件中整合这些技术确实有助于将我们的平台提升到一个新的水平。查看我关于这些技术之一的帖子!
这个组件的最后一个功能是模型监控。我们需要一种方法来评估我们的训练模型的性能,并持续监控它们的性能下降。模型监控和评估应分为三种方式:
- 培训期间的模型评估/基准测试
- 使用 XAI 和假设分析的模型监控
- 连续生产监控
组件 3:生产化
我们将我们的模型发布到生产中,供客户使用。这意味着我们的模型根据实时数据进行预测,并将结果传送到各自的目的地。该步骤还应该与元数据存储进行通信,以保持生产化审计跟踪(例如,当一个新模型被推向生产时,简单地保持所做的每一个预测的跟踪记录)。
可以集成到该组件中的另一个有趣机制是反馈环路。反馈回路是一种机制,其中机器学习模型刚刚做出的预测被反馈到相同的所述模型以进行重新训练。如果与可操作的预测相结合,这个方面变得越来越强大。这个反馈循环应该使我们能够尽可能接近实时地评估生产中的模型(取决于手头的任务),并促进向在线学习的过渡。这也将使我们能够在我们的平台中最小化模型衰减和概念漂移的影响。
例如,让我们假设一个预测客户是否会在未来 5 天内流失的数据模型。一旦我们做出预测,我们就可以对该预测进行 5 天的监控,以确定相应的客户实际上是否会流失。然后,该反馈将被反馈到初始模型,以进一步改进和提高性能。
组成部分 4:监测和暴露
这是我们可以炫耀我们冷静的观察和洞察力的部分。这是整个平台最重要的部分。这是经营的脸面;客户将直接与之交互和体验的组件。这是我们交付所有成果并密切监控所产生的商业价值的地方。
这是我们戴上创造性帽子的机会。我们需要构建仪表板来全面展示我们产品的功能,并确保客户能够全面、快速地吸收我们的见解。
我们还将能够持续监控当前的车型生产绩效,使我们能够在车型再培训和交付方面采取积极主动的态度。我们还可以采用多层仪表板,为不同类型的用户使用不同的视图。例如,开发者视图将允许我们深入研究特定模型的技术指标,而用户/监管者视图将加载 XAI 仪表板和假设分析。
组件 5:元数据 存储
它只是一种让我们对工作流程中发生的事情进行可审计跟踪的方式。在这里,我们可以存储查询、预测和模型训练的审计线索。人们还可以增强元数据存储,以包括不同产品的预测跟踪。
TL;速度三角形定位法(dead reckoning)
本质上,我们的数据科学平台架构应该由 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给我。
用 FastAPI 构建文本预处理微服务
原文:https://towardsdatascience.com/building-a-text-preprocessing-microservice-with-fastapi-ca7912050ba
用 Python 创建和分发一个简单的 NLP 应用程序
由 Timelab Pro 在 Unsplash 上拍摄的照片
介绍
预处理是机器学习/数据科学应用中最重要的步骤之一。在现实世界中,大多数数据集都是脏的,有缺失值,并且充满了不正确的列,如字符串、日期和其他类型的非数字特征。
在常规应用程序中,原始数据主要在表格中,标准化、缩放和编码等方法对于机器学习模型正常工作至关重要。
在 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 制作的图标。
总结一下,我们需要实现:
- 文本预处理功能。
- 端点的输入验证模型。
- 将接收文本和预处理步骤列表并返回预处理文本的端点。
先说文本预处理函数。如前所述,我们将使用正则表达式来规范化文本。下面的代码展示了几个负责它的类的实现。BaseRegExPreprocessing 类充当抽象类。每个继承的类都用其代表系统功能的值覆盖了 regex 和 replace_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 官方文件