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

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

如何使用 IPyWidgets 和 Plotly 在 Python 中构建自定义标注工具

原文:towardsdatascience.com/how-to-build-a-custom-labeler-in-python-with-ipywidgets-and-plotly-f6cc1fd7e3cc?source=collection_archive---------21-----------------------#2023-01-10

在 Jupyter 环境中创建一个分割工具

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

·

关注 发布于 Towards Data Science ·11 分钟阅读·2023 年 1 月 10 日

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

从肾脏中分割出的肾小球。原始图像由HuBMAP 组织映射中心 提供,来自范德堡大学

你知道吗?你可以在几行代码内将一个简单的Jupyter Notebook 转变成一个强大的标注工具。

将一个简单的笔记本转变为标注工具在项目开始时可以节省大量时间,尤其是在你还在评估一个想法的潜力,并且不想在构建一个稳定的网页应用上花费太多时。

在这篇文章中,我将向你展示如何使用plotlyipywidget构建一个分割工具,以生成 python 中图像的二进制掩模。在图像分割中,二进制掩模可以用于训练深度学习模型,以高效地自动识别和隔离感兴趣区域。

我假设你已经熟悉 ipywidgets 和 plotly 的基础知识。如果不是,我强烈建议你先查看这篇文章,我们将介绍基础知识。

关于用例的一句话

我正在使用HuBMAP 数据集,这是一个 2021 年在 Kaggle 上举办的竞赛中的数据集。

我选择这个数据集,因为它完美地展示了现实生活中的应用:一个客户联系你,提供一堆图像,并询问你是否可以找到一种方法来自动隔离这些图像中的特定感兴趣区域。这些区域(在这个例子中)被称为肾小体,是肾脏中帮助过滤血液中废物和多余水分的小部分,见下图。

下图展示了你的客户在这里的期望:一个能够快速识别那些感兴趣区域的模型。

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

项目目标:提供肾脏图像的分割区域。原始图像由HuBMAP 组织映射中心提供,位于范德比尔特大学。

在探索阶段,时间至关重要。通常,我们只有原始数据,并需要快速生成标签来训练和评估模型,并评估项目的可行性。

与其花费数小时寻找可能仍需定制的开源工具,不如跟随我一步步构建你自己的自定义工具。你会发现,只需一点努力,你就可以轻松创建任何类型的部件来帮助你的日常任务。

构建部件的技术规格

在直接进入编码部分之前,我们需要退一步,考虑一下我们到底希望从我们的部件中得到什么。我们需要回答的问题包括:

  • 输入的格式是什么?是 jpeg 图像吗?Numpy 数组?栅格?等等。

  • 它们存储在哪里?本地?多个区域?还是在云端?

  • 你期望的输出是什么?另一个 jpeg 图像还是 numpy 数组?也许是存储在 json 中的注释?

  • 你期望的交互是什么?点击按钮做某事,具有一些 UI 元素以有效地从数据到数据导航,直接与图形交互,等等。

  • 对于每次交互,实际操作中你将如何管理?这将如何影响部件的内部状态?

  • 尝试在某处(即使是在纸上)绘制你希望你的部件看起来的样子。这将帮助你结构化部件的布局。

在我们的案例中:

我假设主要图像已经被分割成 256x256 像素的子图像,并存储为 .npy 文件在本地的 imgs/ 文件夹中。

main/
  |-- imgs/
  |     |-- 1.npy
  |     |-- 2.npy
  |     |-- etc..
  |-- masks/
  |-- widget_notebook.ipynb

生成的掩膜将存储在 masks/ 文件夹中,格式为 .npy,文件名与原始图像相同。

我们工具所需的功能包括:

  • 小部件将显示两个图像:一个是带有掩膜透明覆盖的原始图像(正在进行中),另一个是当前保存的掩膜,这将允许用户可视化当前完成的工作,并在需要时完成它。

  • 用户可以多次点击图像以生成定义区域的多边形。这将在小部件内用于生成一个掩膜,其中区域外的像素为 0,区域内的像素为 1。

  • 应该使用一个按钮来保存掩膜,当用户对其标签感到满意时,这样结果就会被存储并可以由他或其他服务(如深度学习模型)检索。

  • 应该使用一个按钮来删除当前的多边形,这将方便修复绘图错误。

  • 应该使用一个按钮来重置本地保存的掩膜,允许在需要时恢复到原始状态,而不必手动删除掩膜。

  • 用户可以通过下拉菜单在图像之间导航

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

我们希望实现的小部件布局的粗略视图。原始图像由 HuBMAP 组织组织 提供,来自范德比尔特大学

构建小部件

开始之前的一些良好实践

我建议你在自定义类中构建你的小部件。这将方便你在状态管理方面,因为所有内容都将保留在内部,并且更容易打包。

同时,作为一般建议,你应该先构建主要布局,然后在第二阶段再构建交互功能。

最后,尽量将各部分分开!创建每个按钮的专用函数或单独处理每个回调的函数不会增加额外的成本,但可能会为测试和调试节省宝贵的时间。

总结来说,为了高效构建各种小部件:

  1. 创建一个类来保存小部件中使用的所有内部状态

  2. 构建初始化图像和组件的函数,在第一阶段没有交互

  3. 添加交互并逐步测试

  4. 保持代码整洁有序

小部件内部状态逻辑

我们的小部件需要保持并修改多个信息:

  • _current_id 用于存储当前图像的名称,同时也用于访问和保存二进制掩膜。

  • _polygon_coordinates 列表将包含用户点击的点的坐标,并将用于生成二进制掩膜(使用 skimage 函数)

  • _initialize_widget 函数将生成构建布局所需的所有其他状态,如按钮、图像和中间掩膜。

class SegmentWidget:

    def __init__(self, path_imgs, path_masks):

        self._path_imgs = path_imgs
        self._path_masks = path_masks
        self._ids = sorted([os.path.splitext(e)[0] for e in  os.listdir(path_imgs)], key=int)
        self._current_id = self.ids[0]
        #This list will be used later to save in memory the coordinates 
        #of the clicks by the user
        self._polygon_coordinates = []
        #TODO: Initiation of all the layout components
        self._initialize_widget()

注意:Python 不管理公共和私有变量,但作为良好的 pep8 实践,非公共变量和函数可以用“_”作为前缀。

读取图像

我们需要开发一个函数,用于读取图像及其相关的掩码(如果存在)。

这个函数将在小部件初始化期间调用,但每当用户切换到新图像时也会调用。

 def _load_images(self):
        '''This method will be used to load image and mask when we select another image'''
        img_path = os.path.join(self._path_imgs,f"{self._current_id}.npy")
        self._current_img = np.load(img_path)
        h,w, _ = self._current_img.shape

        #There is not always a mask saved. When no mask is saved, we create an empty one.
        mask_path = os.path.join(self._path_masks,f"{self._current_id}.npy")
        if os.path.exists(mask_path):
            self._current_mask = np.load(mask_path)
        else:
            self._current_mask = np.zeros((h,w))
        #initiate an intermediate mask which will be used to store ongoing work
        self._intermediate_mask = self._current_mask.copy()

注意:我更倾向于在这里打包一个专用函数。我本可以把所有东西直接放在 init 中,但那样会更难消化。如果你发现一个整体上合理的任务,使用一个函数。

初始化图形

下一步是初始化我们的 FigureWidgets。

我们将使用 plotly.express.imshow 方法显示 RGB 图像,同时对二进制掩码使用 plotly.graph_objects.Heatmap。

我们目前专注于小部件的总体布局,因此将回调函数的定义留到以后。

def _initialize_figures(self):
    '''This function is called to initialize the figure and its callback'''
    self._image_fig = go.FigureWidget()
    self._mask_fig = go.FigureWidget()

    self._load_images() #Update the state loading the images

    #We use plotly express to generate the RGB image from the 3D array loaded
    img_trace = px.imshow(self._current_img).data[0]
    #We use plotly HeatMap for the 2D mask array
    mask_trace = go.Heatmap(z=self._current_mask, showscale=False, zmin=0, zmax=1)

    #Add the traces
    self._image_fig.add_trace(img_trace)
    self._image_fig.add_trace(mask_trace)
    self._mask_fig.add_trace(mask_trace)

    #A bit of chart formating
    self._image_fig.data[1].opacity = 0.3 #make the mask transparent on image 1
    self._image_fig.data[1].zmax = 2 #the overlayed mask above the image can have values in range 0..2
    self._image_fig.update_xaxes(visible=False)
    self._image_fig.update_yaxes(visible=False)
    self._image_fig.update_layout(margin={"l": 10, "r": 10, "b": 10, "t": 50}, 
                                  title = "Define your Polygon Here",
                                  title_x = 0.5, title_y = 0.95)
    self._mask_fig.update_layout(yaxis=dict(autorange='reversed'), margin={"l": 0, "r": 10, "b": 10, "t": 50},)
    self._mask_fig.update_xaxes(visible=False)
    self._mask_fig.update_yaxes(visible=False)

    #Todo: add the callbacks to the two charts

不要犹豫花一点时间来格式化,可能看起来很烦人,但拥有一个合适大小、信息量刚好的工具会在你操作时节省时间

按钮、下拉菜单和最终布局

我们现在将添加函数以将界面组件集成到小部件中。正如规格中所述,我们需要 3 个按钮(水平显示)、一个下拉菜单和两个并排的图形。

def _build_save_button(self):
    self._save_button = Button(description="Save Configuration")
    #Todo: add the callback

def _build_delete_current_config_button(self):
    self._delete_current_config_button = Button(description="Delete Current Mask")
    #Todo: add the callback

def _build_delete_all_button(self):
    self._delete_all_button = Button(description="Delete All Mask")
    #Todo: add the callback

def _build_dropdown(self):
    #The ids are passed as option for the dropdown
    self._dropdown = Dropdown(options = self._ids)
    #Todo: add the callback

def _initialize_widget(self):
    '''Function called during the init phase to initialize all the components
       and build the widget layout
    '''

    #Initialize the components
    self._initialize_figures()
    self._build_save_button()
    self._build_delete_current_config_button()
    self._build_delete_all_button()
    self._build_dropdown()

    #Build the layout
    buttons_widgets = HBox([self._save_button,
                            self._delete_current_config_button,
                            self._delete_all_button])

    figure_widgets = HBox([self._image_fig, self._mask_fig])

    self.widget = VBox([self._dropdown, buttons_widgets, figure_widgets])

def display(self):
    display(self.widget)

我添加了一个“公共” display() 方法(小部件中唯一的“公共”)。它将显示小部件的初始状态。

拥有这样的方法允许用户直接显示小部件,而无需在其内部状态中查找你的部件名称……

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

HuBMAP 组织的组织映射中心在范德堡大学提供的原始图像

处理交互

现在我们的小部件已完全初始化,我们可以开始处理不同的交互。

下拉菜单交互

下拉菜单允许用户浏览数据集中的不同图像。为了正常工作,下拉菜单的回调函数应:

  • 读取新图像和新掩码

  • 更新两个图形

  • 清空 _polygon_coordinates 中的点列表以开始一个新的多边形

要更新 px.imshow() 轨迹,我们需要修改其“source”参数。

要更新 go.Heatmap() 轨迹,我们修改轨迹的“z”参数。

请注意,每个轨迹通常显示为一个包含图表所有信息的对象。不要犹豫,展示它以查看你可以更轻松地修改什么。

def _callback_dropdown(self, change):

    #Set the new id to the new dropdown value
    self._current_id = change['new']

    #Load the new image and the new mask, we already have a method to do this
    self._load_images()

    img_trace = px.imshow(self._current_img).data[0]

    #Update both figure
    with self._image_fig.batch_update():
        #Update the trace 0 and the trace 1 containing respectively
        #the image and the mask
        self._image_fig.data[0].source = img_trace.source
        self._image_fig.data[1].z = self._current_mask

    with self._mask_fig.batch_update():
        self._mask_fig.data[0].z = self._current_mask

    #Reset the list of coordinates used to store current work in progress
    self._polygon_coordinates = []

def _build_dropdown(self):
    #The ids are passed as option for the dropdown
    self._dropdown = Dropdown(options = self._ids)
    self._dropdown.observe(self._callback_dropdown, names="value") 

我们现在可以浏览不同的图像。

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

HuBMAP 组织的组织映射中心在范德堡大学提供的原始图像

FigureWidget 的 on_click 交互

点击图像中的一个像素将把它的坐标存储在小部件状态中。

存储 3 个或更多像素应触发一个函数,该函数将:

  • 如果像素位于由坐标列表定义的多边形内或外,则生成值为 0 或 2 的新掩模数组。

  • 创建一个中间掩模,其中包含前一个掩模的信息和新生成的掩模信息。

  • 在左侧图形上显示新生成的掩模。

我们使用 skimage.draw 从坐标列表中高效生成多边形掩模。

Scikit-image 是处理各种图像处理任务的强大工具。查看 他们的文档 以了解更多可能性!

def _gen_mask_from_polygon(self):
    '''This function set to 2 the values inside the polygon defined by the list of points provided'''
    h,w = self._current_mask.shape
    new_mask = np.zeros((h,w), dtype=int)
    #Get coordinates inside the polygon using skimage.draw.polygon function
    rr, cc = polygon([e[0] for e in self._polygon_coordinates], 
                     [e[1] for e in self._polygon_coordinates], shape=new_mask.shape)

    #Recreate the intermediate_mask and set values inside ongoing polygon
    #to 2
    self._intermediate_mask = self._current_mask.copy()
    self._intermediate_mask[rr,cc]=2

def _on_click_figure(self, trace, points, state):
    #Retrieve coordinates of the clicked point
    i,j = points.point_inds[0]
    #Add the point to the list of points
    self._polygon_coordinates.append((i,j))

    #If more than 2 click have been done, create the new intermediate polygon
    #and update the mask on the image
    if len(self._polygon_coordinates)>2:
        self._gen_mask_from_polygon()
        with self._image_fig.batch_update():
            self._image_fig.data[1].z = self._intermediate_mask

然后我们可以更新 _initialize_figures 方法以附加回调函数。

注意:由于图形由多个图像层组成,我们将回调函数附加到顶层。

def _initialize_figures(self):

    #[...Rest of the function...]

    self._image_fig.data[-1].on_click(self._on_click_figure)

就这样!我们现在可以可视化我们图表交互的效果:

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

原始图像由HuBMAP 组织的组织映射中心提供,位于范德比尔特大学。

按钮交互

为完成我们的应用程序,让我们编写按钮交互的代码。

“保存配置”按钮。这个按钮通过以下方式保存掩模:

  1. 将 0 和 2 像素从 _intermediate_mask 复制到 _current_mask。

  2. 将 _current_mask 保存到 /masks 文件夹。

  3. 刷新图形。

  4. 重置 _polygon_coordinates 列表。

def _callback_save_button(self, button):
    self._current_mask[self._intermediate_mask==2]=1
    self._current_mask[self._intermediate_mask==0]=0
    mask_path = os.path.join(self._path_masks,f"{self._current_id}.npy")
    np.save(mask_path,self._current_mask)
    self._intermediate_mask = self._current_mask.copy()
    with self._image_fig.batch_update():
        self._image_fig.data[1].z = self._current_mask     
    with self._mask_fig.batch_update():
        self._mask_fig.data[0].z = self._current_mask
    self._polygon_coordinates = []

def _build_save_button(self):
    self._save_button = Button(description="Save Configuration")
    self._save_button.on_click(self._callback_save_button)

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

原始图像由HuBMAP 组织的组织映射中心提供,位于范德比尔特大学。

“删除当前掩模”按钮。这个按钮只是将 _intermediate_mask 重置为 _current_mask 的值,并刷新图形和 _polygon_coordinates 列表。

def _callback_delete_current_config_button(self, button):
    self._intermediate_mask = self._current_mask.copy()
    with self._image_fig.batch_update():
        self._image_fig.data[1].z = self._intermediate_mask
    self._polygon_coordinates = []

def _build_delete_current_config_button(self):
    self._delete_current_config_button = Button(description="Delete Current Mask")
    self._delete_current_config_button.on_click(self._callback_delete_current_config_button)

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

原始图像由HuBMAP 组织的组织映射中心提供,位于范德比尔特大学。

“删除所有掩模”按钮。这个按钮只是将 _intermediate_mask 重置为 0,并将 _polygon_coordinates 重置为空列表。

def _callback_delete_all_button(self, button):
    self._intermediate_mask[:] = 0
    with self._image_fig.batch_update():
        self._image_fig.data[1].z = self._intermediate_mask
    self._polygon_coordinates = []

def _build_delete_all_button(self):
    self._delete_all_button = Button(description="Delete All Mask")
    self._delete_all_button.on_click(self._callback_delete_all_button)

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

原始图像由HuBMAP 组织的组织映射中心提供,位于范德比尔特大学。

想自己试试吗?完整代码请见这里

结论

几小时的工作后,我们已经能够设置我们的工具,并现在具备快速标记图像的能力,这将帮助我们满足客户的原始请求。在准备了几百张图像的掩模后,我们可以训练一个初步模型,并评估是否需要动用更多资源来推动项目进一步发展。

发挥创造力

如果你到这里来了,恭喜你!你现在可以直接在你的 Jupyter Notebook 中生成一个自定义标签工具了。我们仅仅探讨了使用 plotly 和 ipywidgets 可以快速利用的众多用例中的一个,你现在拥有了开发自己应用程序的所有关键。

如何使用 ChatGPT 构建数据科学作品集网站

原文:towardsdatascience.com/how-to-build-a-data-science-portfolio-website-with-chatgpt-e57d29badf7f

利用 AI 的力量创建一个就业准备好的作品集网站

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

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

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

图片来源于 Midjourney

作为一名初级数据科学家,进入这个行业可能会很有挑战性,因为竞争空前激烈。

如果你没有学位或任何正式的资格证书来突出你的专业能力,这一点尤为重要。

我总是建议领域的初学者创建解决问题的数据科学项目,这样你会脱颖而出。

当你构建解决现实问题的数据项目时,你让雇主相信你能够丰富他们的业务并作为数据科学家创造收益。

记住,你正在与拥有硕士学位和博士学位的申请者竞争,因此如果你没有这些学位,你必须在项目建设上具有创意,以便使自己与其他候选人区分开来。

如果你想构建数据科学项目但不知道从哪里开始,我建议查看这篇文章,我在其中介绍了一些帮助我进入该领域的最佳项目。

我很乐意你能采纳一些这些想法,并将它们改造为你自己的项目。

为什么要构建数据科学作品集网站

一旦你完成了 2 到 3 个数据科学项目,我强烈建议创建一个网站,将所有的作品集中在一个地方展示。

实际上,我通过简单地给招聘人员发消息并发送我的作品集网站链接获得了我的前几个数据科学职位:

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

图片由作者提供

你的作品集网站是一个可以向潜在雇主展示你是谁以及你能带来什么的地方。

招聘经理不必通过你的 GitHub 仓库查看代码来了解你做过的工作,他们可以直接浏览你的作品集网站,一目了然地了解你。

使用 ChatGPT,你可以轻松构建一个引人注目的作品集网站。

我用 HTML、CSS 和 JavaScript 从零开始构建了我的第一个作品集网站。我还在之前的文章中写了这个过程。

由于我对网页开发了解甚少,并且不得不自己编写和调试所有代码,所以我花了大约一周时间才让我的网站上线。

然而,像 ChatGPT 这样的 AI 工具可以比以前更快地帮助你创建网站。

使用正确的提示,你可以简单地向 ChatGPT 描述你希望网站的样子,模型将为你提供代码来构建一个高度可定制的作品集网站,以满足你的具体要求。

在本教程中,我将展示如何使用 ChatGPT 构建一个数据科学作品集网站。

步骤 1:你应该如何结构化你的作品集网站?

在实际编码作品集网站之前,你需要决定它应该如何结构化——需要包含哪些部分,以便展现你的最佳面貌。

让我们向 ChatGPT 请求一些想法。

我将使用 GPT-4 模型进行本教程。如果你没有 ChatGPT 的付费订阅,可以使用默认的 GPT-3.5 模型,效果也应该很好。

这是我输入到 ChatGPT 的提示:

I am a data scientist with 3 years of experience in the field. 
Can you give me some ideas as to how I can structure my portfolio website?

这是我从模型得到的回复:

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

图片由作者提供

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

图片由作者提供

好的,ChatGPT 建议我们将作品集网站分为 5 个部分——主页、技能、项目、教育和博客。

这是一个典型作品集网站的结构。

你可以根据自己的需求进行调整,并要求它根据你的要求增加或减少部分。

步骤 2:为你的数据科学作品集网站构建内容

下一步是实际编写你想在网站每个部分中包含的内容。

如果你希望你的站点在 Google 搜索中排名靠前,你需要创建 SEO 友好的内容并使用正确的关键词。

你可以通过两种方式做到这一点:

方法 1:

只需输入一段关于你自己的简短介绍,并要求 ChatGPT 为作品集网站的每个部分生成 SEO 友好的内容。

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

图片由作者提供

这样做的一个好处是你不必花时间自己编写内容,ChatGPT 能很好地将正确的关键词融入其中,并创建引人入胜的内容。

然而,如果你的雇主使用 AI 检测工具来筛查你的作品集,你的工作可能会被标记为 ChatGPT 生成的内容,这可能对你作为求职者的形象不利。

方法 2:

由于 ChatGPT 生成的内容可能会引起潜在雇主的警惕,我建议你自己编写每个部分的文本。

然后你可以将内容粘贴到 ChatGPT 中,要求模型修复文本中的任何语法错误,并提高其 SEO 分数。

我推荐这样做,因为你不仅仅是在使用 AI 生成的文本来构建你的网站。实际上,你是在创建自己的内容,并只是请求 ChatGPT 对其进行优化,以提高排名和可读性。

下面是一个可以帮助你实现这一目标的提示示例:

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

图片来源于作者

以下是 ChatGPT 对我提示的回应:

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

图片来源于作者

注意到 ChatGPT 对我的内容没有做任何重大更改。相反,它包括了“Python”、“R”和“SQL”等术语,这些术语与招聘人员寻找具有特定技能的数据科学家有关。

招聘经理通常使用自动化工具扫描简历中的特定关键词,仅筛选符合要求的候选人。

这就是为什么优化你的作品集以适应招聘人员关注的特定专业领域如此重要。

ChatGPT 可以帮助你完成这项任务——实际上,根据内容营销专家的说法,它的一个主要用途包括内容增强和 提供量身定制的关键词,这些关键词有助于让网站脱颖而出。

步骤 3:使用 ChatGPT 创建你的作品集网站

现在,我们终于可以使用 ChatGPT 构建作品集网站了。

为了做到这一点,让我们提示 ChatGPT 给我们一些代码来构建作品集网站:

Can you create a portfolio website with the following elements using HTML, CSS, and JavaScript:
1\. Bio/Introduction: This section should have a header tag (H1) that says "Bio." You can use the same text I pasted in the <p> tag of this section. On the right hand side of this section, please use this image: [`unsplash.com/photos/hgFY1mZY-Y0.`](https://unsplash.com/photos/hgFY1mZY-Y0.)2\. Projects: This section should have a H1 tag that says "Projects." It should also have three cards that are clickable, I will provide the URLs myself. Each card should have the following images respectively: [`unsplash.com/photos/zwsHjakE_iI,`](https://unsplash.com/photos/zwsHjakE_iI,) [`unsplash.com/photos/hGV2TfOh0ns,`](https://unsplash.com/photos/hGV2TfOh0ns,) [`unsplash.com/photos/s9CC2SKySJM.`](https://unsplash.com/photos/s9CC2SKySJM.) Please style this using Bootstrap's card component.3\. Skills: This section should have a H1 tag that says "Skills." It should list Python, SQL, Data Analysis, and R. Create this in the form of small bar charts - Python (80%), SQL (70%), Data Analysts (90%) and R (30%).4\. Contact: This section is the footer, and should have a sign up box that allows users to enter their email addresses and click subscribe. It should have a H1 tag that says "Contact Me."Please style all the code using Bootstrap, and structure it in the form of a portfolio website.

当要求 ChatGPT 编写代码或构建最终产品时,你需要非常具体,这就是为什么我清楚地概述了我希望每个部分的结构。

我指定了我想要创建的所有部分,以及我想使用的标题类型。我甚至提供了想在网站上包含的 Unsplash 图片的链接。

最后,我要求它使用 Bootstrap 来为网站设计样式。这是一个流行的 CSS 框架,用于构建美观、响应式的网站,并减少从头设计页面所需的时间。

你可以根据你希望网站的结构来更改要求。

ChatGPT 根据我提供的指示给了我一些代码:

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

它告诉我自己上传每个部分的图片,因为 Unsplash 不允许直接嵌入链接,并为我提供了两个单独文件的代码——HTML 和 CSS。

我将它们保存在同一目录下并运行了代码,结果网站看起来像这样:

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

图片来源于作者

这是一个简单的框架,需要用文本和图片内容进行丰富。

为了确保我们确切知道需要进行哪些更改,ChatGPT 在代码的特定部分留下了注释,这些部分需要由文本描述或图片替换:

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

作者提供的图片

如果你还没有相关的个人或项目图片可以在网站上使用,只需从UnsplashPexels下载一些作为占位符即可。

在代码中包含相关内容和图片后,让我们刷新网站,看看效果如何:

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

作者提供的图片

我觉得这看起来还不错,不过感觉需要一些小的改动,使其更专业。

例如,导航栏和“简介”部分有点太靠近了。它们应该分开一点,导航栏也应该更深一些,以便提高可见性。

简介中的文字太短,留有很多空白,使得视觉效果不佳。

让我们请 ChatGPT 进行一些修改:

Can you do these things:
1\. Make the navigation bar darker, and separate it from the "Bio" section. They are currently almost overlapping. If you make this bar dark, the text should be made lighter for it to be visible.2\. The text in the "Bio" section should be made slightly bigger, as there currently is a lot of empty space at the bottom.3\. The "Email Address" text should be made bigger or highlighted in bold, and centered horizontally. The "Subscribe" button, project title, project description, and "View Project" button should also be centered horizontally.

请记住,ChatGPT 可能会忽略这些指示中的一个或多个(它对我确实有),但你只需再次提示它,以提醒其确切需要做出的更改,它最终会做对的。

这是 ChatGPT 为我提供更新代码后的最终版本网站:

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

作者提供的图片

注意:ChatGPT 刚刚设计了联系按钮,但它目前没有任何功能。如果你想让它工作,只需请 ChatGPT 提供如何使用类似 MailChimp 的免费邮件订阅服务的指示。

看!就是这样!

我们成功地在不到一天的时间内与 ChatGPT 一起构建了一个有吸引力的作品集网站。

下一步是部署这个网站,这可以通过GitHub Pages瞬间完成。

ChatGPT 生成的用于构建作品集网站的完整代码可以在我的GitHub 仓库中找到。

这篇文章就到这里,谢谢阅读!

如何构建一个完全自动化的数据漂移检测管道

原文:towardsdatascience.com/how-to-build-a-fully-automated-data-drift-detection-pipeline-e9278584e58d?source=collection_archive---------2-----------------------#2023-08-01

自动化指南:检测和处理数据漂移

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

·

关注 发表在 Towards Data Science · 10 分钟阅读 · 2023 年 8 月 1 日

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

作者提供的图片

动机

数据漂移发生在生产环境中的输入特征分布与训练数据不同,从而可能导致不准确性和模型性能下降。

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

作者提供的图片

为了减轻数据漂移对模型性能的影响,我们可以设计一个工作流,检测漂移,通知数据团队,并触发模型重训练。

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

作者提供的图片

工作流

工作流包括以下任务:

  1. 从 Postgres 数据库中获取参考数据。

  2. 从网络获取当前生产数据。

  3. 通过比较参考数据和当前数据来检测数据漂移。

  4. 将当前数据附加到现有的 Postgres 数据库中。

  5. 当出现数据漂移时,采取以下措施:

  • 发送 Slack 消息以提醒数据团队。

  • 重新训练模型以更新其性能。

  • 将更新后的模型推送到 S3 存储。

这个工作流程被安排在特定时间运行,例如每周一上午 11:00。

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

图片由作者提供

总体而言,工作流程包括两种类型的任务:数据科学任务和数据工程任务。

数据科学任务,用粉色框表示,由数据科学家执行,涉及数据漂移检测、数据处理和模型训练。

数据工程任务,用蓝色和紫色框表示,由数据工程师执行,涉及数据移动和发送通知的任务。

数据科学任务

检测数据漂移

为了检测数据漂移,我们将创建一个 Python 脚本,该脚本接受两个 CSV 文件“data/reference.csv”(参考数据)和“data/current.csv”(当前数据)。

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

图片由作者提供

我们将使用Evidently,一个开源的机器学习可观测性平台,来将参考数据(作为基线)与当前生产数据进行比较。

如果检测到数据漂移,则“drift_detected”输出为 True;否则为 False。

from evidently.metric_preset import DataDriftPreset
from evidently.report import Report
from kestra import Kestra

data_drift_report = Report(metrics=[DataDriftPreset()])
data_drift_report.run(reference_data=reference, current_data=current)
report = data_drift_report.as_dict()
drift_detected = report["metrics"][0]["result"]["dataset_drift"]

if drift_detected:
    print("Detect dataset drift")
else:
    print("Detect no dataset drift")

Kestra.outputs({"drift_detected": drift_detected})

完整代码。

重新训练模型

接下来,我们将创建一个负责模型训练的 Python 脚本。该脚本以过去和当前数据的结合作为输入,并将训练好的模型保存为“model.pkl”文件。

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

图片由作者提供

def train_model(X_train: pd.DataFrame, y_train: pd.Series, model_params: DictConfig):
    y_train_log = np.log1p(y_train)
    model = Ridge()
    scorer = metrics.make_scorer(rmsle, greater_is_better=True)
    params = dict(model_params)

    grid = GridSearchCV(model, params, scoring=scorer, cv=3, verbose=3)
    grid.fit(X_train, y_train_log)
    return grid

model = train_model(X_train, y_train, config.model.params)
joblib.dump(model, "model.pkl")

完整代码。

推送到 GitHub

在开发完这两个脚本后,数据科学家可以将它们推送到 GitHub,从而允许数据工程师在创建工作流时使用它们。

在这里查看这些文件的 GitHub 仓库:

[## GitHub - khuyentran1401/detect-data-drift-pipeline: 一个检测数据漂移并重新训练模型的流水线]

一个检测数据漂移并在出现漂移时重新训练模型的流水线 - GitHub …

github.com](https://github.com/khuyentran1401/detect-data-drift-pipeline?source=post_page-----e9278584e58d--------------------------------)

数据工程任务

流行的协调库如 Airflow、Prefect 和 Dagster 需要修改 Python 代码以使用其功能。

当 Python 脚本与数据工作流紧密集成时,整体代码库可能会变得更复杂,维护也更困难。如果没有独立的 Python 脚本开发,数据工程师可能需要修改数据科学代码以添加编排逻辑。

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

图片来源:作者

另一方面,Kestra 是一个开源库,它允许你独立开发 Python 脚本,然后通过 YAML 文件无缝地将它们融入数据工作流。

这样,数据科学家可以专注于模型处理和训练,而数据工程师则可以专注于处理编排。

因此,我们将使用 Kestra 设计一个更模块化和高效的工作流。

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

图片来源:作者

克隆 detect-data-drift-pipeline 仓库 以获取 Kestra 的 docker-compose 文件,然后运行:

docker compose up -d

访问 localhost:8080 以访问和探索 Kestra UI。

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

图片来源:作者

按照这些 说明 配置本教程所需的环境。

在开发目标流程之前,让我们通过创建一些简单的流程来熟悉 Kestra。

从 Python 脚本访问 Postgres 表

我们将创建一个包含以下任务的流程:

  • getReferenceTable:从 Postgres 表中导出 CSV 文件。

  • saveReferenceToCSV:创建一个本地 CSV 文件,可以被 Python 任务访问。

  • runPythonScript:使用 Python 读取本地 CSV 文件。

为了在saveReferenceToCSVrunPythonScript任务之间传递数据,我们将通过将这两个任务放置在相同的工作目录中,并将它们封装在wdir任务内部来实现。

id: get-reference-table
namespace: dev
tasks:
  - id: getReferenceTable
    type: io.kestra.plugin.jdbc.postgresql.CopyOut
    url: jdbc:postgresql://host.docker.internal:5432/
    username: "{{secret('POSTGRES_USERNAME')}}"
    password: "{{secret('POSTGRES_PASSWORD')}}"
    format: CSV
    sql: SELECT * FROM reference
    header: true
  - id: wdir
    type: io.kestra.core.tasks.flows.WorkingDirectory
    tasks:
    - id: saveReferenceToCSV
      type: io.kestra.core.tasks.storages.LocalFiles
      inputs:
        data/reference.csv: "{{outputs.getReferenceTable.uri}}"
    - id: runPythonScript
      type: io.kestra.plugin.scripts.python.Script
      beforeCommands:
        - pip install pandas
      script: | 
        import pandas as pd
        df = pd.read_csv("data/reference.csv")
        print(df.head(10))

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

图片来源:作者

执行流程将显示以下日志:

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

图片来源:作者

使用输入参数化流程

让我们创建另一个可以用输入参数化的流程。这个流程将包含以下输入:startDateendDatedataURL

getCurrentCSV任务可以使用{{inputs.name}}符号访问这些输入。

id: get-current-table
namespace: dev
inputs:
  - name: startDate
    type: STRING
    defaults: "2011-03-01"
  - name: endDate
    type: STRING
    defaults: "2011-03-31"
  - name: dataURL
    type: STRING 
    defaults: "https://raw.githubusercontent.com/khuyentran1401/detect-data-drift-pipeline/main/data/bikeride.csv"
tasks:
  - id: getCurrentCSV
    type: io.kestra.plugin.scripts.python.Script
    beforeCommands:
      - pip install pandas
    script: |
      import pandas as pd
      df = pd.read_csv("{{inputs.dataURL}}", parse_dates=["dteday"])
      print(f"Getting data from {{inputs.startDate}} to {{inputs.endDate}}")
      df = df.loc[df.dteday.between("{{inputs.startDate}}", "{{inputs.endDate}}")]
      df.to_csv("current.csv", index=False)

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

图片来源:作者

这些输入的值可以在每次执行流程时指定。

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

图片来源:作者

将 CSV 文件加载到 Postgres 表中

以下流程执行以下任务:

  • getCurrentCSV:运行 Python 脚本以在工作目录中创建 CSV 文件。

  • saveFiles:将工作目录中的 CSV 文件发送到 Kestra 的内部存储。

  • saveToCurrentTable:将 CSV 文件加载到 Postgres 表中。

iid: save-current-table
namespace: dev
tasks:
  - id: wdir
    type: io.kestra.core.tasks.flows.WorkingDirectory
    tasks:
    - id: getCurrentCSV
      type: io.kestra.plugin.scripts.python.Script
      beforeCommands:
        - pip install pandas
      script: |
        import pandas as pd
        data_url = "https://raw.githubusercontent.com/khuyentran1401/detect-data-drift-pipeline/main/data/bikeride.csv"
        df = pd.read_csv(data_url, parse_dates=["dteday"])
        df.to_csv("current.csv", index=False)
    - id: saveFiles
      type: io.kestra.core.tasks.storages.LocalFiles
      outputs:
        - current.csv
  - id: saveToCurrentTable
    type: io.kestra.plugin.jdbc.postgresql.CopyIn
    url: jdbc:postgresql://host.docker.internal:5432/
    username: "{{secret('POSTGRES_USERNAME')}}"
    password: "{{secret('POSTGRES_PASSWORD')}}"
    from: "{{outputs.saveFiles.uris['current.csv']}}"
    table: current
    format: CSV
    header: true
    delimiter: ","

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

图片来源:作者

运行此流程后,你将在 Postgres 数据库中的“current”表中看到结果数据。

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

图片由作者提供

从 GitHub 仓库中运行文件

此流程包括以下任务:

  • cloneRepository:克隆一个公共 GitHub 仓库

  • runPythonCommand:从 CLI 执行一个 Python 脚本

这两个任务将在同一工作目录中操作。

id: clone-repository
namespace: dev
tasks:
  - id: wdir
    type: io.kestra.core.tasks.flows.WorkingDirectory
    tasks:
      - id: cloneRepository
        type: io.kestra.plugin.git.Clone
        url: https://github.com/khuyentran1401/detect-data-drift-pipeline
        branch: main
      - id: runPythonCommand
        type: io.kestra.plugin.scripts.python.Commands
        commands:
          - python src/example.py

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

图片由作者提供

运行流程后,你将看到以下日志:

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

图片由作者提供

按计划运行一个流程

我们将创建另一个基于特定时间表运行的流程。以下流程在每周一上午 11:00 运行。

id: triggered-flow
namespace: dev
tasks:
  - id: hello
    type: io.kestra.core.tasks.log.Log
    message: Hello world
triggers:
  - id: schedule
    type: io.kestra.core.models.triggers.types.Schedule
    cron: "0 11 * * MON"

上传到 S3

此流程包括以下任务:

  • createPickle:在 Python 中生成一个 pickle 文件

  • savetoPickle:将 pickle 文件转移到 Kestra 的内部存储

  • upload:将 pickle 文件上传到 S3 桶

id: upload-to-S3
namespace: dev
tasks:
  - id: wdir
    type: io.kestra.core.tasks.flows.WorkingDirectory
    tasks:
    - id: createPickle
      type: io.kestra.plugin.scripts.python.Script
      script: |
        import pickle
        data = [1, 2, 3]
        with open('data.pkl', 'wb') as f:
          pickle.dump(data, f)
    - id: saveToPickle
      type: io.kestra.core.tasks.storages.LocalFiles
      outputs:
        - data.pkl  
  - id: upload
    type: io.kestra.plugin.aws.s3.Upload
    accessKeyId: "{{secret('AWS_ACCESS_KEY_ID')}}"
    secretKeyId: "{{secret('AWS_SECRET_ACCESS_KEY_ID')}}"
    region: us-east-2
    from: '{{outputs.saveToPickle.uris["data.pkl"]}}'
    bucket: bike-sharing
    key: data.pkl

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

图片由作者提供

运行此流程后,data.pkl 文件将上传到“bike-sharing”桶中。

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

图片由作者提供

将一切整合在一起

构建一个流程来检测数据漂移

现在,让我们结合所学创建一个检测数据漂移的流程。每周一上午 11:00,这个流程执行以下任务:

  • 从 Postgres 数据库中获取参考数据。

  • 运行一个 Python 脚本以从网络获取当前生产数据。

  • 克隆包含漂移检测代码的 GitHub 仓库

  • 运行一个 Python 脚本,通过比较参考数据和当前数据来检测数据漂移。

  • 将当前数据附加到现有的 Postgres 数据库中。

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

图片由作者提供

id: detect-data-drift
namespace: dev
inputs:
  - name: startDate
    type: STRING
    defaults: "2011-03-01"
  - name: endDate
    type: STRING
    defaults: "2011-03-31"
  - name: data_url
    type: STRING 
    defaults: "https://raw.githubusercontent.com/khuyentran1401/detect-data-drift-pipeline/main/data/bikeride.csv"
tasks:
  - id: getReferenceTable
    type: io.kestra.plugin.jdbc.postgresql.CopyOut
    url: jdbc:postgresql://host.docker.internal:5432/
    username: "{{secret('POSTGRES_USERNAME')}}"
    password: "{{secret('POSTGRES_PASSWORD')}}"
    format: CSV
    sql: SELECT * FROM reference
    header: true
  - id: wdir
    type: io.kestra.core.tasks.flows.WorkingDirectory
    tasks:
      - id: cloneRepository
        type: io.kestra.plugin.git.Clone
        url: https://github.com/khuyentran1401/detect-data-drift-pipeline
        branch: main
      - id: saveReferenceToCSV
        type: io.kestra.core.tasks.storages.LocalFiles
        inputs:
          data/reference.csv: "{{outputs.getReferenceTable.uri}}"
      - id: getCurrentCSV
        type: io.kestra.plugin.scripts.python.Script
        beforeCommands:
          - pip install pandas
        script: |
          import pandas as pd
          df = pd.read_csv("{{inputs.data_url}}", parse_dates=["dteday"])
          print(f"Getting data from {{inputs.startDate}} to {{inputs.endDate}}")
          df = df.loc[df.dteday.between("{{inputs.startDate}}", "{{inputs.endDate}}")]
          df.to_csv("data/current.csv", index=False)
      - id: detectDataDrift
        type: io.kestra.plugin.scripts.python.Commands
        beforeCommands:
          - pip install -r src/detect/requirements.txt
        commands:
          - python src/detect/detect_data_drift.py
      - id: saveFileInStorage
        type: io.kestra.core.tasks.storages.LocalFiles
        outputs:
          - data/current.csv
  - id: saveToCurrentTable
    type: io.kestra.plugin.jdbc.postgresql.CopyIn
    url: jdbc:postgresql://host.docker.internal:5432/
    username: "{{secret('POSTGRES_USERNAME')}}"
    password: "{{secret('POSTGRES_PASSWORD')}}"
    from: "{{outputs.saveFileInStorage.uris['data/current.csv']}}"
    table: current
    format: CSV
    header: true
    delimiter: ","
triggers:
  - id: schedule
    type: io.kestra.core.models.triggers.types.Schedule
    cron: "0 11 * * MON"

构建一个流程来发送 Slack 消息

接下来,我们将创建一个流程,通过一个 Slack Webhook URLdetect-data-drift 流程中的 detectDataDrift 任务返回 drift_detected=true 时发送 Slack 消息。

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

图片由作者提供

id: send-slack-message
namespace: dev
tasks:
  - id: send
    type: io.kestra.plugin.notifications.slack.SlackExecution
    url: "{{secret('SLACK_WEBHOOK')}}"
    customMessage: Detect data drift

triggers:
  - id: listen
    type: io.kestra.core.models.triggers.types.Flow
    conditions:
    - type: io.kestra.core.models.conditions.types.ExecutionFlowCondition
      namespace: dev
      flowId: detect-data-drift
    - type: io.kestra.core.models.conditions.types.VariableCondition
      expression: "{{outputs.detectDataDrift.vars.drift_detected}} == true"

运行 detect-data-drift 流程后,send-slack-message 流程将发送一条 Slack 消息。

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

图片由作者提供

构建一个流程以重新训练模型

最后,我们将创建一个流程来重新训练模型。这个流程执行以下任务:

  • 从 Postgres 数据库的当前表导出一个 CSV 文件

  • 克隆包含模型训练代码的 GitHub 仓库

  • 运行一个 Python 脚本来训练模型并生成一个 pickle 文件

  • 上传 pickle 文件到 S3

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

图片由作者提供

id: train-model
namespace: dev
tasks:
  - id: getCurrentTable
    type: io.kestra.plugin.jdbc.postgresql.CopyOut
    url: jdbc:postgresql://host.docker.internal:5432/
    username: "{{secret('POSTGRES_USERNAME')}}"
    password: "{{secret('POSTGRES_PASSWORD')}}"
    format: CSV
    sql: SELECT * FROM current
    header: true
  - id: wdir
    type: io.kestra.core.tasks.flows.WorkingDirectory
    tasks:
      - id: cloneRepository
        type: io.kestra.plugin.git.Clone
        url: https://github.com/khuyentran1401/detect-data-drift-pipeline
        branch: main
      - id: saveCurrentToCSV
        type: io.kestra.core.tasks.storages.LocalFiles
        inputs:
          data/current.csv: "{{outputs.getCurrentTable.uri}}"
      - id: trainModel
        type: io.kestra.plugin.scripts.python.Commands
        beforeCommands:
          - pip install -r src/train/requirements.txt
        commands:
          - python src/train/train_model.py
      - id: saveToPickle
        type: io.kestra.core.tasks.storages.LocalFiles
        outputs:
          - model.pkl
  - id: upload
    type: io.kestra.plugin.aws.s3.Upload
    accessKeyId: "{{secret('AWS_ACCESS_KEY_ID')}}"
    secretKeyId: "{{secret('AWS_SECRET_ACCESS_KEY_ID')}}"
    region: us-east-2
    from: '{{outputs.saveToPickle.uris["model.pkl"]}}'
    bucket: bike-sharing
    key: model.pkl
triggers:
  - id: listenFlow
    type: io.kestra.core.models.triggers.types.Flow
    conditions:
      - type: io.kestra.core.models.conditions.types.ExecutionFlowCondition
        namespace: dev
        flowId: detect-data-drift
      - type: io.kestra.core.models.conditions.types.VariableCondition
        expression: "{{outputs.detectDataDrift.vars.drift_detected}} == true"After running this flow, the model.pkl file will be uploaded to the "bike-sharing" bucket.

运行此流程后,model.pkl 文件将上传到“bike-sharing”桶中。

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

图片由作者提供

扩展此工作流程的想法

与其依赖计划的数据提取来识别数据漂移,我们可以利用Grafana 的外发 WebhookKestra 的入站 Webhook来建立实时数据监控,并在数据漂移发生时立即触发流程。这种方法能够在数据漂移发生时立即检测,避免了等待计划脚本运行的需要。

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

作者提供的图片

请在评论中告诉我你认为这个工作流程可以如何扩展,以及你希望在未来的内容中看到其他什么用例。

我喜欢撰写关于数据科学概念的文章,并玩弄不同的数据科学工具。你可以通过以下方式保持更新我的最新帖子:

如何在 2023 年构建多 GPU 系统进行深度学习

原文:towardsdatascience.com/how-to-build-a-multi-gpu-system-for-deep-learning-in-2023-e5bbb905d935?source=collection_archive---------0-----------------------#2023-09-16

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

·

关注 发布于 Towards Data Science ·10 min read·2023 年 9 月 16 日

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

我的深度学习搭建——始终在进行中 😃.

本文提供了如何构建多 GPU 系统进行深度学习的指南,希望能节省你的研究时间和实验。

目标

构建一个多 GPU 系统,用于计算机视觉和 LLMs 模型的训练,而不会破坏预算! 🏦

第一步. GPU

让我们从有趣(和昂贵的 💸💸💸)部分开始吧!

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

H100 怪兽!图片来自 NVIDIA

购买 GPU 时的主要考虑因素是:

  • 内存(VRAM)

  • 性能(张量核心,时钟速度)

  • 插槽宽度

  • 功率(TDP)

内存

现在深度学习任务需要大量内存。即使是微调 LLMs 也很庞大,而计算机视觉任务,特别是 3D 网络,也可能变得内存密集。自然,最重要的方面是 GPU 的VRAM。对于 LLMs,我建议至少 24 GB 内存,对于计算机视觉任务,我建议不低于 12 GB。

性能

第二个标准是性能,可以通过 FLOPS(每秒浮点运算)来估算:

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

过去的关键数字是电路中的 CUDA 核心数量。然而,随着深度学习的兴起,NVIDIA 推出了专门的张量核心,每时钟周期可以执行更多的 FMA(融合加法)操作。这些已经被主要深度学习框架支持,2023 年您应该关注这些。

以下是我在经过大量手动工作后编制的按内存分组的 GPU 原始性能图表:

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

基于 CUDA 和张量核心(TFLOPs)的 GPU 原始性能。

注意在比较不同 GPU 的性能时必须格外小心。不同代/架构的 Tensor 核心不可比。例如,A100 每时钟周期执行 256 FP16 FMA 操作,而 V100“仅”执行 64 个。此外,较旧的架构(Turing, Volta)不支持 32 位张量操作。更难比较的是 NVIDIA 并不总是报告 FMA,甚至在白皮书中也没有,且相同架构的 GPU 可能有不同的 FMA。我一直在这个上碰壁😵‍💫。还要注意,NVIDIA 通常用稀疏性来宣传张量 FLOPS,而这一特性仅在推理时可用。

为了识别性价比最高的 GPU,我使用 eBay API 收集了 eBay 价格,并计算了新卡的每美元相对性能:

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

基于 CUDA 和张量核心(TFLOPs / USD)的 GPU 相对性能。价格基于当前 eBay 价格(2023 年 9 月)。

我对二手卡做了相同的处理,但由于排名变化不大,所以省略了图表。

为了选择最适合您预算的 GPU,您可以选择拥有最大内存的顶级 GPU。我的推荐是:

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

根据当前 eBay 价格(2023 年 9 月),不同预算的 GPU 推荐。

如果您想深入了解更多技术细节,建议阅读 Tim Dettmers 的优秀指南选择适合深度学习的 GPU

插槽宽度

在构建多 GPU 系统时,我们需要规划如何将 GPU 物理安装到 PC 机箱中。由于 GPU 越来越大,尤其是游戏系列,这成为了一个问题。消费级主板最多有 7 个 PCIe 插槽,PC 机箱也围绕这个设置进行设计。一块 4090 根据制造商的不同,可能会占用 4 个插槽,因此你可以理解为什么这会成为问题。此外,我们应该在非风冷或水冷的 GPU 之间至少留出 1 个插槽,以避免过热。我们有以下选项:

水冷

水冷变体将占用最多 2 个插槽,但它们价格更贵。你也可以将风冷 GPU 改装成水冷,但这会使保修失效。如果你不选择一体式(AIO)解决方案,你将需要构建自定义水冷系统。如果你想安装多个水冷 GPU,这一点尤其重要,因为 AIO 散热器可能无法适配机箱。自行构建系统有风险,我个人不会在昂贵的显卡上尝试。我只会直接从制造商处购买 AIO 解决方案(风险规避 🙈)。

风冷 2-3 槽显卡和 PCIe 扩展卡

在这种情况下,你可以将显卡交错地安装在 PCIe 插槽上,并通过 PCIe 扩展卡连接显卡。PCIe 扩展卡可以放置在 PC 机箱内部的某个位置,或放在开放空气中。在任何情况下,你都应该确保 GPU 固定好(另见关于 PC 机箱的部分)。

功率(TDP)

现代 GPU 的功耗越来越大。例如,一块 4090 需要 450W,而 H100 可以达到 700W。除了电费,安装三块或更多显卡也成为了一个问题。这在美国尤其如此,因为电源插座的最大功率约为 1800w。

如果你接近电源/电源插座的最大功率,解决这个问题的一个方案是功率限制。要减少 GPU 可以吸取的最大功率,你只需:

sudo nvidia-smi -i <GPU_index> -pl <power_limit>

where:
GPU_index: the index (number) of the card as it shown with nvidia-smi
power_limit: the power in W you want to use

通过将功率限制在 10-20%之间,已被证明可以将性能降低不到 5%,并且能使显卡保持更凉爽(Puget Systems 的实验)。例如,将四块 3090 显卡的功率限制为 20%会将其功耗降低到 1120w,并且可以轻松适配 1600w 的电源/1800w 的插座(假设其余组件消耗 400w)。

步骤 2. 主板和 CPU

构建的下一步是选择一个允许多个 GPU 的主板。在这里主要考虑的是 PCIe 通道。我们需要每块显卡至少有 PCIe 3.0 x8 通道(见Tim Dettmers 的帖子)。PCIe 4.0 或 5.0 更为稀有,对于大多数深度学习用途并不必要。

除了插槽类型外,插槽的间距将决定你可以放置 GPU 的位置。确保你已经检查了间距,并且你的 GPU 确实可以放在你想要的位置。请注意,大多数主板在使用多个 GPU 时会将一些 x16 插槽配置为 x8。获取这些信息的唯一可靠途径是查阅显卡的手册。

最简单的方法是避免花费数小时的研究,并使你的系统未来-proof,是选择一个到处都有 x16 插槽的主板。你可以使用 PCPartPicker 并筛选出具有 7+ PCIe x16 插槽 的主板。这给我们提供了 21 种产品选择。然后我们 缩减列表 选择我们想要的最小 RAM 数量(例如 128 GB),并选择 DDR4 / DDR5 类型,将产品数量减少到 10 种:

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

基于 PCPartPicker 的主板至少有 7 个 PCIe x16 插槽和 128 GB DDR4/DDR5 RAM。

上述列表中支持的 CPU 插槽是 LGA2011–3 和 LGA2066。接下来,我们转到 CPU 选择,选择具有所需核心数量的 CPU。这些主要用于数据加载和批处理准备。每个 GPU 至少应有 2 核心 / 4 线程。对于 CPU,我们还应检查它支持的 PCIe 通道。过去十年的任何 CPU 应该至少支持 40 条通道(覆盖 4 个 GPU,每个 GPU x8 通道),但最好还是谨慎为好。通过筛选例如 具有上述插槽的 16+ 核 CPU,我们得到以下 CPU:

  • Intel Xeon E5 (LGA2011–3):8 个结果

  • Intel Core i9 (LGA2066):9 个结果

然后,我们根据核心数量、可用性和价格选择我们喜欢的主板和 CPU 组合。

LGA2011–3 和 LGA2066 插槽都已经非常老旧(分别是 2014 年和 2017 年),因此你可以在 eBay 上找到这两个主板和 CPU 的好交易。一块 ASRock X99 WS-E 主板和一颗 18 核的 Intel Xeon E5–2697 V4 在二手状态下可能花费不到 300 美元。不要购买便宜的 ES 或 QS 版本的 CPU,因为这些是工程样品,可能会出现故障 ⚠️️。

如果你想购买更强大和/或更新的组件和/或 AMD CPU,可以查看例如 4+ PCIe x16 插槽的主板,但确保检查插槽间距。

在这个阶段,开始一个 PCPartPicker 构建 是个好主意。 🛠️

PCPartPicker 会为你检查组件之间的兼容性,让你的生活更轻松。

第 3 步。RAM 🐏

在这里,最重要的方面是 RAM 的数量。RAM 在深度学习循环中的不同地方使用:从硬盘加载数据以创建批次、加载模型以及当然是原型设计。所需的 RAM 数量很大程度上取决于您的应用程序(例如,3D 图像数据需要更多的额外 RAM),但您应该以 GPU VRAM 总量的 1 倍到 2 倍为目标。类型至少应为 DDR4,但 RAM 时钟不是非常重要,所以不要把钱花在这里 🕳️。

在购买 RAM 时,您应该确保其形状因素、类型、模块数量和每个模块的内存都与您的主板规格一致(PCPartPicker 是您的好帮手!)。

第 4 步。硬盘

另一个可以节省开支的组件是硬盘 😌。硬盘空间的大小很重要,并且取决于应用程序。您不一定需要超高速硬盘或 NVME,因为它们不会影响您的深度学习性能。数据最终会加载到 RAM 中,为了避免成为瓶颈,您可以简单地使用更多的并行 CPU 工作线程。

第 5 步。电源供应器 (PSU) 🔌

正如我们所见,GPU 是高功耗组件。在设置多 GPU 系统时,选择 PSU 成为一个重要的考虑因素。大多数 PSU 能提供高达 1600w 的功率 —— 这符合美国插座的功率限制。有一些 PSU 可以提供更高的功率,但需要一些研究,并且它们特别针对矿工。

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

PCPartPicker 为您的构建提供的估算功率。

要确定系统的功率,您可以再次使用 PCPartPicker 来计算您构建的总功率。为了确保安全,我们需要额外增加 10%以上的功率,因为 GPU 的实际功耗有时会超过其规格。

一个重要的标准是PSU 效率,这由 80 PLUS 评级标记。电源将达到其宣传的功率,但在过程中会损失一些功率。80 PLUS 铜牌电源的效率为 82%,而例如金牌电源的效率为 87%。如果我们有一个功率需求为 1600w 的系统,并且我们在 20% 的时间内使用它,我们将节省 22 美元每年,前提是电价为 0.16 美元/千瓦时。在比较价格时,请将这一点纳入您的计算中。

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

PSU 效率评级。表格来源于 techguided

在满负荷运行时,一些 PSU 比其他的噪音更大,因为它们使用高速运转的风扇。如果您在靠近机箱的地方工作(或睡觉!),这可能会有一些影响,因此查看手册中的分贝数是个好主意 😵。

在选择电源时,我们需要确认它是否有足够的连接器来支持所有部件。特别是 GPU 使用 8 针(或 6+2)电缆。这里有一个重要的提示:对于 GPU 的每个电源插槽,我们应该使用单独的 8 针电缆,而不是使用同一根电缆的多个输出(串联连接)。8 针电缆通常额定功率约为 150w。当使用单根电缆为多个电源插槽供电时,GPU 可能无法获得足够的电力,从而导致降频。

步骤 6. 机箱

最后但同样重要的是,选择一个 PC 机箱并不简单。GPU 可以非常庞大,一些机箱可能无法容纳它们。例如,4090 的长度可以达到 36 厘米 👻!

此外,使用 PCIe 延长条安装 GPU 可能需要一些技巧。有一些较新的机箱允许安装额外的显卡,特别是像 Phanteks Enthoo 719 这样的双系统机箱。另一个选择是 Lian-Li O11D EVO,它可以通过 Lian-Li Upright GPU Bracket 以直立位置容纳 GPU。我没有这些机箱,所以不确定它们如何适配,例如多个 3090 / 4090。然而,即使你的 PC 机箱不直接支持直立安装,你仍然可以使用 Lian-Li 支架来直立安装 GPU。你需要在机箱上钻 2-3 个孔,但并不是很复杂。

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

使用 Lian Li 直立支架将 GPU 安装在直立位置。

结束

希望你喜欢阅读本指南,并且发现了一些有用的提示。本指南旨在帮助你研究如何构建多 GPU 系统,而不是替代研究。如果你有任何问题或意见,请随时发给我。如果我在上述内容中有任何错误,我非常感谢你留下评论或私信,以便进一步改进 🙏!

注意:除非另有说明,所有图片均由作者提供。

如何使用 Python 构建一个类似 Shazam 的 Telegram 机器人

原文:towardsdatascience.com/how-to-build-a-shazam-like-telegram-bot-using-python-98dc081c53d5

一个教程,教你如何创建和部署一个可以实时接收音乐并帮助你查找歌曲标题和歌手的 Telegram 机器人

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

·发布于 Towards Data Science ·阅读时间 9 分钟·2023 年 1 月 24 日

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

照片由 Austin Neill 提供,来源于 Unsplash

当你在外面听到音乐,比如在酒吧或商店里,如果你非常喜欢这首歌,难道不会想知道这首歌的标题和歌手的名字吗?

我遇到过很多次这种情况,只有一个解决方案。Shazam。下载它并点击弹出按钮来识别音乐。

在这篇文章中,我们将构建一个类似的应用来发现歌曲和歌手。开发这个数据科学项目的一种快速简便的方法是使用 Python 创建一个 Telegram 机器人。

当你听音乐时,你可以录制声音。这个机器人会将音频转录成文本,并为你找到歌曲的标题和歌手。让我们开始吧!

目录:

  • 第一部分:用 Python 创建 Telegram 机器人

  • 第二部分:将 Telegram 机器人部署到 Fly.io

第一部分:用 Python 创建 Telegram 机器人

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

作者插图

本教程将分为两个部分。在第一部分中,我们将专注于创建一个可以转录音频并发现歌曲信息的 Telegram 机器人。之后,我们会将这个 Telegram 机器人部署到 fly.io 上,这个平台是众多支持云端应用构建、运行和扩展的平台之一。

  • 要求

  • 安装库

  • 创建 Telegram 机器人的第一步

  • 转录歌曲的音频

  • 了解有关歌曲的信息

要求:

在开始用 Python 编程之前,需要遵循一些步骤。

  • 打开 Telegram,搜索 @botfather 并点击第一个结果。

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

  • 按下开始按钮以开始与 BotFather 对话。

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

  • 输入/newbot并按照截图中的指示操作。创建机器人后,你将收到 BotFather 发给你的令牌,从而允许你访问 Telegram API。

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

  • 输入你的 Telegram 机器人名称。

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

安装库

首先,安装一个允许用少量代码创建 Telegram 机器人的 Python 库。它叫做pyTelegramBotAPI

pip install pyTelegramBotAPI

为了转录音频,我将使用Steamship的 API。在安装包之前,你需要安装 nodej。在 Windows 上,你需要从这里安装并将路径添加到 PATH 环境变量中,而在 ubuntu 上你需要运行两行代码:

sudo apt install nodejs
sudo apt install npm

然后你可以在你的 Python 虚拟环境中安装 Steamship CLI。

pip install steamship
npm install -g @steamship/cli
ship login

安装完成后,我们将进入第三个 API,它允许我们从音频中发现歌曲。这可以通过访问 Google 搜索结果来实现,使用的 API 叫做SerpApi

pip install google-search-results

创建 Telegram 机器人的第一步

一旦你获得了 Telegram 的 API 令牌并创建了 Telegram 机器人,它应该会出现在你的 Telegram 应用中,我们可以开始探索pyTelegramBotAPI,它提供了用少量代码创建 Telegram 机器人的 Python 实现。

还有许多其他不同的 Python 库可以用来构建 Telegram 机器人,但由于我发现许多教程使用了这个库,且文档做得很好,我选择了它来完成我的小项目。

import os
import json
...
from steamship import Steamship, TaskState
import telebot
from serpapi import GoogleSearch

f = open("cred.json", "rb")
params = json.load(f)
BOT_TOKEN = params['BOT_TOKEN']
bot = telebot.TeleBot(BOT_TOKEN)

我们有一个文件cred.json,其中包含了机器人令牌以及 Steamship 和 SerpApi 的 API 密钥:

{
"BOT_TOKEN":<your_bot_token>,
"API_KEY":<your_serpapi_key>,
"STEAM_API_KEY":<your_steamship_api_key>
 }

在第 11 行,我们简单地创建了一个TeleBot实例,它是一个提供处理 Telegram 消息功能的类。例如,让我们定义一个消息处理器,如果你在聊天中输入/start/hello,它会返回消息“插入你的歌曲音频”。

@bot.message_handler(commands=['start', 'hello'])
def send_welcome(message):
    bot.reply_to(message, "Insert the audio of your song!")

bot.infinity_polling()

在代码末尾,我们使用bot.infinity_polling()来启动机器人。

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

作者提供的截图

运行python bot.py后,你应该会看到类似于上图的结果。

在函数send_welcome之后,我们可以定义另一个函数,叫做telegram_bot,它将处理“语音”类型的音频文件。

@bot.message_handler(content_types=['voice'])
def telegram_bot(message,bot_token=params["BOT_TOKEN"]):
    # insert audio
    file_info = bot.get_file(message.voice.file_id)
    # extract telegram's url of the audio 
    audio_url = 'https://api.telegram.org/file/bot{}/{}'.format(bot_token,file_info.file_path)

bot.infinity_polling()

我们希望获得你发送给机器人的音频文件的公开 URL。要检查 URL 是否正确,请在浏览器中复制以下路径,替换令牌和文件路径。如果有效,它应该会将音频下载到你的本地电脑。

https://api.telegram.org/file/bot<bot_token>/<file_path>

我们需要做的最后一个操作是将 OGA 格式的音频转换为 MP3 格式。这是一个重要步骤,因为 OGA 文件不支持转录。

为了将音频转换为 mp3 文件,我们将使用 ffmpeg 包装器,它允许使用命令行转换各种音频文件。在使用之前,你需要先安装它。如果你在 Windows 上工作,请查看这个 指南,它展示了所有步骤,而在 Linux 上,你只需使用命令 sudo apt install ffmpeg

在函数 convert_oga_to_mp3 的第 4 行进行转换后,我们需要再次提取新音频文件的 URL,该文件的格式会有所不同。

转录音频的歌曲

现在,我们已经到了教程中最有趣的部分,这使我们达到了 Telegram 机器人的目标。在继续之前,登录 这里 以获取 API 密钥。首先,我们想使用 Steamship API 转录音频。函数 transcribe_audio 以之前检索到的音频 URL 和 Steamship 的实例为输入,Steamship 是一个提供许多 AI 应用功能和方法的类。在这种情况下,它专注于音频转录。

代码可以用几行代码来总结:

  • 在第 3 行,我们指定将使用 audio_markdown 包来转录音频,然后生成 Markdown 输出。

  • 在下一行,我们通过调用 invoke 来调用方法 transcribe_url。我们还需要传递音频的 URL。

  • 在函数的中间,我们调用方法 get_markdown,这将允许我们接收音频的转录。为了避免无限循环,如果不成功,重试次数被限制为 100。

  • 该函数在最后返回转录结果,我们在 Telegram 机器人上获得结果文本。

发现有关歌曲的信息

在获得转录后,我们进入下一步,即发现歌曲的标题和歌手。这可以通过使用 SerpApi 来实现,它是一个 API,允许访问 Google 和其他网站的结果。

我们需要向 GoogleSearch 传递一个字典,包含如歌曲歌词、SerpApi 的 API 密钥等参数,并从 Google 获得结果。它返回一个 JSON 输出,可以转换为 Python 字典以获取信息。之后,一条文本消息将发送回发件人,这可能是未发现的/已实现的发现。

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

应用程序应该如何工作的示意图。

第二部分:将 Telegram 机器人部署到 Fly.io

到目前为止,我们可以通过运行以下命令行来使机器人工作:

python bot.py

但这意味着机器人只有在你运行此代码时才会工作,这并不实用。最好是让 Telegram 机器人始终可用,让人们尝试。

因此,我们需要部署 Telegram 机器人。最初,我想使用 Heroku,这是另一个用于部署应用程序的闭源平台,但它不再提供免费层。

所以,我选择了 Fly.io 作为云服务,因为它提供了免费计划,并且文档齐全。

  • 创建 requirements.txt 和 Dockerfile

  • 连接到 Fly.io

  • 启动应用程序

  • 部署应用程序

1. 创建 requirements.txt 和 Dockerfile

要将其投入生产,我们需要 requirements.txt 文件,其中包含所有的 Python 依赖项。可以通过在终端中运行命令行 pipreqs 自动创建该文件。你必须安装 pipreqs 以使命令行有效。项目所需的 Python 包如下:

google_search_results==2.4.1
pyTelegramBotAPI==4.9.0
python-dotenv==0.21.1
requests==2.28.1
steamship==2.3.5

除了这个文件,我们还需要另一个文件,名为 Dockerfile,其内容应如下:

FROM python:3.10.2

WORKDIR /bot

COPY requirements.txt /bot/
RUN pip install -r requirements.txt
RUN apt-get update
RUN apt-get install ffmpeg -y
RUN apt-get install nodejs -y
RUN apt-get install npm -y

COPY . /bot

CMD python bot.py

2. 连接到 Fly.io

第三个要求是安装 flyctl,一个可以部署我们应用程序的命令行工具。你可以在 这里 找到说明,具体取决于你的操作系统。

如果你还没有创建 Fly.io 账户,你需要通过运行以下命令行来创建一个:

flyctl auth signup

如果你已经有账户,你只需要通过终端复制登录:

fly auth login

3. 启动应用程序

要开始这个项目,我们可以输入以下命令行:

flyctl launch

系统会要求你提供以下信息:

  • 写下应用程序名称。

  • 选择部署的区域。

  • 如果你想要设置一个 Postgresql 数据库。此情况下,答案是否定的。

  • 如果你现在想要设置 Upstash Redis 数据库。此情况下,答案是否定的。

  • 如果我们现在想要部署的话。答案是现在不行。我们稍后再部署。

在部署应用程序之前,我还决定利用 fly.io 提供的一个功能,即秘密管理。正如你所知道的,凭据是敏感的,你可能希望不将你的秘密透露给除你自己以外的任何人。这可以通过以下命令行实现:

flyctl secrets set BOT_TOKEN=<your_bot_token>
flyctl secrets set STEAM_TOKEN=<your_steam_api>
flyctl secrets set API_KEY=<your_serpapi_key>

运行这些命令行后,Python 代码需要进行修改。我们不再使用包含凭据的 JSON 文件,这些凭据直接存储在 fly.io 平台上。例如,我们可以通过以下方式获取 Telegram 机器人的令牌:

from dotenv import load_dotenv

load_dotenv()
bot = telebot.TeleBot(os.getenv("BOT_TOKEN"))

你需要对其他 API 密钥做相同的操作。你可以通过以下命令行查看命令列表:

flyctl secrets list

4. 部署应用程序

最终,我们达到了珠穆朗玛峰的顶峰!只需再输入一条命令,就完成了:

flyctl deploy

这样,我们的包含 Telegram 机器人的 Docker 容器就构建并部署完成了。代码将运行在云端,而不再是在本地计算机上。现在 Telegram 机器人将开始工作。

最终思考:

我希望你欣赏这个数据科学项目。它可以给你提供其他可能应用的想法。网上有很多资源可以满足你的需求。你只需要创造力、耐心和努力工作。如果你拥有这些条件,那么你能做的事没有限制。GitHub 代码在这里。感谢阅读。祝你有美好的一天!

如何使用 Python 构建 ELT

原文:towardsdatascience.com/how-to-build-an-elt-with-python-8f5d9d75a12e

提取、加载和转换数据

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

·发表于 Towards Data Science ·阅读时间 7 分钟·2023 年 2 月 7 日

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

图片由 JJ Ying 提供,来源于 Unsplash

ELT(提取、加载、转换)是一种现代的数据集成方法,与 ETL(提取、转换、加载)略有不同。ETL 在将数据加载到数据仓库之前进行转换,而 ELT 则将原始数据直接加载到数据仓库中,并使用 SQL 进行转换。

构建 ELT 是数据和分析工程师工作中非常重要的一部分,它也可以成为数据分析师和科学家更广泛领域的有用技能,或者是那些构建完整作品集的求职者。

在这篇文章中,我们将使用来自 dummyJSON 的数据在 Python 中构建一个简短的 ELT 管道。dummyJSON 是一个虚假的 REST API,提供 9 种类型的资源:

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

dummyjson.com 的截图

我们将尝试找出哪些客户在我们的虚拟商店中花费了最多的钱。

这个脚本将包括 3 个步骤:

  1. 从 dummyJSON API 提取数据

  2. 将原始数据加载到 BigQuery

  3. 执行查询以进行分析

让我们开始构建我们的数据管道吧!

提取数据

我们需要从 API 中检索 2 个资源:购物车和用户。

让我们创建一个函数,执行 API 调用并返回 JSON 数据:

import requests

ENDPOINT = "https://dummyjson.com/"
def make_api_call(resource):
    ENDPOINT = "https://dummyjson.com/"
    response = requests.get(f"{ENDPOINT}{resource}") # making a request to the correct endpoint
    if response.status_code == 200:
        return response.json()
    else:
        raise Exception(response.text)

print(make_api_call("carts"))

我们使用 requests 库来发出简单的 HTTP GET 请求。我们通过状态码检查请求是否成功,并返回 JSON 数据。

 {
  "carts": [
    {
      "id": 1,
      "products": [
        {
          "id": 59,
          "title": "Spring and summershoes",
          "price": 20,
          "quantity": 3,
          "total": 60,
          "discountPercentage": 8.71,
          "discountedPrice": 55
        },
        {...}
        // more products
      ],
      "total": 2328,
      "discountedTotal": 1941,
      "userId": 97,
      "totalProducts": 5,
      "totalQuantity": 10
    },
    {...},
    {...},
    {...}
    // 20 items
  ],
  "total": 20,
  "skip": 0,
  "limit": 20
}

我们已经有了关于订单的数据!让我们尝试获取客户数据:

{
  "users": [
    {
      "id": 1,
      "firstName": "Terry",
      "lastName": "Medhurst",
      "maidenName": "Smitham",
      "age": 50,
      "gender": "male",
      "email": "atuny0@sohu.com",
      "phone": "+63 791 675 8914",
      "username": "atuny0",
      "password": "9uQFF1Lh",
      "birthDate": "2000-12-25",
      "image": "https://robohash.org/hicveldicta.png?size=50x50&set=set1",
      "bloodGroup": "A−",
      "height": 189,
      "weight": 75.4,
      "eyeColor": "Green",
      "hair": {
        "color": "Black",
        "type": "Strands"
      },
      "domain": "slashdot.org",
      "ip": "117.29.86.254",
      "address": {
        "address": "1745 T Street Southeast",
        "city": "Washington",
        "coordinates": {
          "lat": 38.867033,
          "lng": -76.979235
        },
        "postalCode": "20020",
        "state": "DC"
      },
      "macAddress": "13:69:BA:56:A3:74",
      "university": "Capitol University",
      "bank": {
        "cardExpire": "06/22",
        "cardNumber": "50380955204220685",
        "cardType": "maestro",
        "currency": "Peso",
        "iban": "NO17 0695 2754 967"
      },
      "company": {
        "address": {
          "address": "629 Debbie Drive",
          "city": "Nashville",
          "coordinates": {
            "lat": 36.208114,
            "lng": -86.58621199999999
          },
          "postalCode": "37076",
          "state": "TN"
        },
        "department": "Marketing",
        "name": "Blanda-O'Keefe",
        "title": "Help Desk Operator"
      },
      "ein": "20-9487066",
      "ssn": "661-64-2976",
      "userAgent": "Mozilla/5.0 ..."
    },
    {...},
    {...}
    // 30 items
  ],
  "total": 100,
  "skip": 0,
  "limit": 30
}

这次,有些事情引起了我们的注意:总共有 100 个用户,但我们只收到了 30 个。

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

作者截图

因此,我们需要再次调用那个 API,直到我们获取所有数据,跳过我们已经拥有的数据。然而,我们不希望将那些totalskiplimit键发送到数据仓库;让我们只保留用户和购物车。

这是我们更新后的函数:

def make_api_call(resource):
    ENDPOINT = "https://dummyjson.com/"
    results_picked = 0
    total_results = 100 #We don't know yet, but we need to initialize
    all_data = []
    while results_picked < total_results:
        response = requests.get(f"{ENDPOINT}{resource}", params = {"skip" : results_picked})
        if response.status_code == 200:
            data = response.json()
            rows = data.get(resource)
            all_data += rows #concatening the two lists
            total_results = data.get("total")
            results_picked += len(rows) #to skip them in the next call
        else:
            raise Exception(response.text)
    return all_data

users_data = make_api_call("users")
print(len(users_data))

这一次,我们有了 100 个用户!

加载数据

现在,是时候将数据上传到 BigQuery 了。我们将使用BigQuery Python 客户端库

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

来源:cloud.google.com/bigquery/docs/batch-loading-data

从文档中可以看到,可以将本地文件加载到 BigQuery 中。目前,我们的 JSON 只是一个字典。让我们将其下载到本地文件中。

我们将使用原生库json并将 JSON 数据写入文件。需要记住的是,BigQuery 接受换行符分隔的 JSON 格式,而不是逗号分隔格式。

import json    

def download_json(data, resource_name):
    file_path = f"{resource_name}.json"
    with open(file_path, "w") as file:
        file.write("\n".join([json.dumps(row) for row in data]))

download_json(carts_data, "carts")
download_json(users_data, "users")

我们现在可以检查我们的 carts.json 文件是否为正确的 JSON 格式:

{"id": 1, "products": [{"id": 59, "title": "Spring and summershoes", "price": 20, "quantity": 3, "total": 60, "discountPercentage": 8.71, "discountedPrice": 55}, {"id": 88, "title": "TC Reusable Silicone Magic Washing Gloves", "price": 29, "quantity": 2, "total": 58, "discountPercentage": 3.19, "discountedPrice": 56}, {"id": 18, "title": "Oil Free Moisturizer 100ml", "price": 40, "quantity": 2, "total": 80, "discountPercentage": 13.1, "discountedPrice": 70}, {"id": 95, "title": "Wholesale cargo lashing Belt", "price": 930, "quantity": 1, "total": 930, "discountPercentage": 17.67, "discountedPrice": 766}, {"id": 39, "title": "Women Sweaters Wool", "price": 600, "quantity": 2, "total": 1200, "discountPercentage": 17.2, "discountedPrice": 994}], "total": 2328, "discountedTotal": 1941, "userId": 97, "totalProducts": 5, "totalQuantity": 10}
// other carts
{"id": 20, "products": [{"id": 66, "title": "Steel Analog Couple Watches", "price": 35, "quantity": 3, "total": 105, "discountPercentage": 3.23, "discountedPrice": 102}, {"id": 59, "title": "Spring and summershoes", "price": 20, "quantity": 1, "total": 20, "discountPercentage": 8.71, "discountedPrice": 18}, {"id": 29, "title": "Handcraft Chinese style", "price": 60, "quantity": 1, "total": 60, "discountPercentage": 15.34, "discountedPrice": 51}, {"id": 32, "title": "Sofa for Coffe Cafe", "price": 50, "quantity": 1, "total": 50, "discountPercentage": 15.59, "discountedPrice": 42}, {"id": 46, "title": "women's shoes", "price": 40, "quantity": 2, "total": 80, "discountPercentage": 16.96, "discountedPrice": 66}], "total": 315, "discountedTotal": 279, "userId": 75, "totalProducts": 5, "totalQuantity": 8}

现在让我们尝试上传我们的文件!

首先,我们需要下载 Python 客户端库

完成后,我们需要下载服务账户密钥并创建一个环境变量,以告诉 BigQuery 我们的凭证存储在哪里。在终端中,我们可以输入以下命令:

export GOOGLE_APPLICATION_CREDENTIALS=service-account.json

然后,我们可以编写 Python 函数。幸运的是,BigQuery 文档为我们提供了代码示例:

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

作者截图

我们可以使用这个示例定义一个新函数:

def load_file(resource, client):
    table_id = f"data-analysis-347920.medium.dummy_{resource}"

    job_config = bigquery.LoadJobConfig(
        source_format=bigquery.SourceFormat.NEWLINE_DELIMITED_JSON, 
        autodetect=True,
        write_disposition="write_truncate"
    )

    with open(f"{resource}.json", "rb") as source_file:
        job = client.load_table_from_file(source_file, table_id, job_config=job_config)

        job.result()  # Waits for the job to complete.

        table = client.get_table(table_id)  # Make an API request.
        print(
            "Loaded {} rows and {} columns to {}".format(
                table.num_rows, len(table.schema), table_id
            )
        )

client = bigquery.Client()
load_file("carts", client)
load_file("users", client)
Loaded 20 rows and 7 columns to data-analysis-347920.medium.dummy_carts
Loaded 100 rows and 27 columns to data-analysis-347920.medium.dummy_users

我们告诉 BigQuery 每次都截断我们的表格,因此如果我们重新运行脚本,现有行将被覆盖。

我们现在在 BigQuery 中有了我们的两个表:

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

作者截图

转换数据

现在是时候进入 ELT 中的最后一部分了!

我们希望有一个包含用户及其在我们商店消费金额的表格。

让我们连接两个表格以获取这些信息:

SELECT 
  u.id AS user_id,
  u.firstName AS user_first_name, 
  u.lastName AS user_last_name,
  SUM(total) AS total_spent
FROM `data-analysis-347920.medium.dummy_users` u
LEFT JOIN  `data-analysis-347920.medium.dummy_carts` c
ON u.id= c.userId
GROUP BY u.id,user_first_name,user_last_name
ORDER BY total_spent DESC

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

作者截图

看来 Trace Douglas 是我们的最高消费者!让我们将这个表格作为 ELT 的一部分添加。

query= """
SELECT 
  u.id AS user_id,
  u.firstName AS user_first_name, 
  u.lastName AS user_last_name,
  SUM(total) AS total_spent
FROM `data-analysis-347920.medium.dummy_users` u
LEFT JOIN  `data-analysis-347920.medium.dummy_carts` c
ON u.id= c.userId
GROUP BY u.id,user_first_name,user_last_name
"""

query_config= bigquery.QueryJobConfig(
    destination = "data-analysis-347920.medium.dummy_best_spenders", 
    write_disposition= "write_truncate"
    )
client.query(query, job_config= query_config)

我移除了 ORDER BY,因为这在计算上很昂贵;我们仍然可以在查询dummy_best_spenders表时对结果进行排序。

让我们检查一下我们的表格是否已创建:

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

作者截图

就这样,我们用几行代码完成了第一个 ELT!

进一步探索 ELT

在处理实际和更大的项目时,还有一些其他事项需要考虑:

  • 我们将处理更大的数据量。每天都有新数据,所以我们必须逐步追加数据到表格中,而不是每天处理所有数据。

    • 随着我们的 ELT 变得越来越复杂,涉及多个数据源,我们可能需要使用像 Airflow 或 Prefect 这样的工作流编排工具。
    • 我们只能将小于 10MB 的文件直接加载到 BigQuery 中。要加载更大的文件,我们需要先将其加载到 Cloud Storage 中。

- 资源

  • 希望你喜欢这篇文章!如果你喜欢,请关注我获取更多关于 Python、SQL 和分析的内容。

  • 成为会员并阅读 Medium 上的所有故事。你的会员费用将直接支持我和你阅读的其他作者。你还将获得对 Medium 上每个故事的完全访问权限。

  • ## 通过我的推荐链接加入 Medium — Marie Truong

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

如何构建一个互联的多页面 Streamlit 应用

原文:towardsdatascience.com/how-to-build-an-interconnected-multi-page-streamlit-app-3114c313f88f?source=collection_archive---------8-----------------------#2023-07-24

从规划到执行——我是如何构建 GPT 实验室的

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

·

关注 发表在 Towards Data Science ·9 分钟阅读·2023 年 7 月 24 日

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

照片由 Clément HélardotUnsplash 上提供

注意:本文最初发布于 Streamlit 博客。我希望在这里与 Medium 社区分享。

哇!自从我首次发布关于 构建 GPT 实验室的经验教训 的博客文章以来,这三个月真是令人难以置信! 🚀

感谢你们的巨大支持,GPT Lab 已获得超过 9K 的应用查看次数、1150+ 的独特登录用户、900+ 次与助手的会话、650+ 个测试过的提示以及 180+ 个创建的助手。该应用还在 Streamlit 应用画廊中与其他优秀应用一起展示。

gptlab.streamlit.app/?embed=true

你们中的许多人问我,“你是如何规划和构建如此大型的 Streamlit 应用的?”迫不及待地想回答,我决定将 GPT Lab 开源。

在这篇文章中,我将分享关于这个雄心勃勃项目的策略和思维过程的见解。我希望这能激励你们将 Streamlit 推向极限,并将你们雄心勃勃的应用变为现实。

💡 想跳到下一部分?查看 应用程序 代码

规划一个大型的 Streamlit 应用

构建大型 Streamlit 应用,例如 GPT Lab,需要仔细的规划,而不仅仅是将代码拼凑在一起。对于 GPT Lab,我专注于规划这四个关键方面:

  1. 功能和用户体验。 应用程序将做什么?我们旨在提供什么样的用户体验?

  2. 数据模型。 数据将如何持久化?应该存储在数据库中的内容与会话状态变量中的内容应该如何区分?

  3. 代码结构。 应该如何构建应用以确保模块化、可维护性和可扩展性?

  4. 会话状态。 需要哪些会话状态变量来链接用户界面?

理解这些方面提供了我所尝试构建的更清晰的视图,并提供了一个系统性处理复杂任务的框架。

让我们更详细地探讨每个方面。

功能和用户体验:创建初始规范和低保真用户体验模型

首先,我创建了一个简单的规范文档(或称为“规范”),概述了整体范围和方法。我还包括了一个站点地图,详细说明了我想支持的用例。该规范为我提供了明确的路线图以及衡量进展的手段。

这是原始规范的摘录:

范围

构建一个平台,允许生成式 AI (GA) 机器人爱好者为他们的朋友和家人创建自己的基于 GPT-3 的聊天机器人。目标是验证足够多的 GA 机器人爱好者是否愿意构建他们的利基领域机器人。

方法

一个公共的 Streamlit 网站,允许用户与四个预训练的教练机器人中的一个互动,或创建并与他们的机器人互动。

与大多数开发项目一样,我做了一些修改。但原始的站点地图大部分保持不变,因为我能够实现大多数计划中的功能。

这是站点地图的最终版本:

GPT Lab
│
├── Home
│
├── Lounge
│
├── Assistant
│   ├── Search for assistant
│   ├── Assistant details
│   ├── Active chat
│   └── Chat recap
│
├── Lab
│   ├── Step 1: initial prompt + model config
│   ├── Step 2: test chat
│   ├── Step 3: other configs
│   └── Step 4: confirmation
│
├── FAQ
│
└── Legal
    ├── Terms
    └── Privacy policy

我不能过分强调功能规划的重要性。它提供了一个路线图、衡量进展的方法以及思考数据模型的起点。

数据模型:确定模式

从一开始,我就认识到后端数据存储对于持久化用户、助手和会话记录至关重要。在考虑了我的选择后,我决定使用 Google Firestore,因为它具有可扩展性、实时能力和慷慨的免费层。为了支持未来的扩展,我战略性地设计了数据模型。尽管当前应用程序仅使用了其潜力的一部分,但可以在 GPT Lab 中添加提示版本控制。这将使用户能够编辑或恢复他们的助手。

💡 注意:在应用程序后台和数据模型中,助手被称为机器人,尽管我之前坚持不在用户界面中称其为机器人 😅。

现在,让我们深入探讨 GPT Lab 中的四个主要 Firestore 集合:users、user_hash、bots 和 sessions。

用户和 user_hash

用户集合是应用程序存储有关用户信息的地方。为了保护用户隐私,应用程序不会存储任何个人可识别信息(PII)。相反,每个用户仅与其 OpenAI API 密钥的单向哈希值相关联。每当用户创建助手或开始/结束与助手的会话时,指标字段会递增。这允许在应用程序内进行基本的分析收集。

Users Collection
   |
   | - id: (Firestore auto-ID)
   | - user_hash: string (one-way hash value of OpenAI API key)
   | - created_date: datetime
   | - last_modified_date: datetime
   | - sessions_started: number
   | - sessions_ended: number
   | - bots_created: number

Google Firestore 不提供确保集合中文档字段值唯一性的方法,因此我创建了一个名为 user_hash 的单独集合。这确保每个唯一的 API 密钥只有一个关联的用户记录。每个用户文档唯一地关联到一个 user_hash 文档,每个 user_hash 文档可能与一个用户文档相关联。数据模型足够灵活,以适应未来更改 API 密钥的用户(用户可以使用旧的 API 密钥登录,然后更换为新的密钥)。

User_hash Collection
   |
   | - id = one-way hash value of OpenAI API key
   | - user_hash_type: string (open_ai_key)
   | - created_date: datetime

机器人

机器人集合存储 AI 助手的配置。每个 AI 助手的核心是其大型语言模型(LLM)、模型配置和提示。为了在未来实现提示和模型配置的适当版本控制,model_configs 和 prompts 被建模为子集合(GPT Lab 的愿景之一是成为你提示的存储库)。

为了最小化子集合读取(以便无需不断查询子集合以获取活动记录),活动子集合的文档 ID 也在文档级别存储。session_type 字段指示助手是否处于头脑风暴或辅导会话中,这会影响会话消息的截断技术。

最后,当用户开始或结束与助手的会话时,指标字段会递增。

Bots Collection
   |
   | - id: (Firestore auto-ID)
   | - name: string
   | - tag_line: string
   | - description: string
   | - session_type: number
   | - creator_user_id: string
   | - created_date: datetime
   | - last_modified_date: datetime
   | - active_initial_prompt_id: string
   | - active_model_config_id: string
   | - active_summary_prompt_id: string
   | - showcased: boolean
   | - is_active: boolean
   |
   v
   |--> Model_configs subcollection
   |     |
   |     | - config: map
   |     |     | - model: string 
   |     |     | - max_tokens: number 
   |     |     | - temperature: number 
   |     |     | - top_p: number 
   |     |     | - frequency_penalty: number 
   |     |     | - presence_penalty: number 
   |     | - created_date: datetime
   |     | - is_active: boolean
   |
   v
   |--> Prompts subcollection
         |
         | - message_type: string
         | - message: string
         | - created_date: datetime
         | - is_active: boolean
         | - sessions_started: number
         | - sessions_ended: number

Sessions

sessions 集合存储会话数据。它包含两种类型的会话:实验室会话(用于测试提示)和助手会话(用于与创建的助手聊天)。为了减少对机器人文档频繁检索的需要,它的信息会在会话文档中缓存。这是有道理的,因为如果实现编辑助手的用例,机器人文档可能会出现偏差。

messages_str 字段存储发送到 OpenAI LLM 的最新有效负载。此功能允许用户恢复之前的助手会话。messages 子集合存储实际的聊天消息。注意,实验室会话聊天消息不会被存储。

为了确保用户的机密性和隐私,OpenAI 请求有效负载和会话消息在保存到数据库之前会被加密。这种数据模型允许用户重新启动以前的会话并继续与助手聊天。

Sessions Collection
   |
   | - id: (Firestore auto-ID)
   | - user_id: string
   | - bot_id: string
   | - bot_initial_prompt_msg: string
   |
   | - bot_model_config: map
   |     | - model: string 
   |     | - max_tokens: number 
   |     | - temperature: number 
   |     | - top_p: number 
   |     | - frequency_penalty: number 
   |     | - presence_penalty: number 
   |
   | - bot_session_type: number
   | - bot_summary_prompt_msg: string
   | - created_date: datetime
   | - session_schema_version: number
   | - status: number
   | - message_count: number
   | - messages_str: string (encrypted)
   |
   v
   |--> Messages subcollection
         |
         | - created_date: datetime
         | - message: string (encrypted)
         | - role: string

通过从一开始就仔细考虑所有潜在的用例,我创建了一个未来-proof 的数据模型,能够满足应用程序不断发展的需求和功能。在接下来的部分中,我们将深入了解后端应用程序代码的结构,以了解它如何支持和实现这个强大的数据模型。

代码结构:为了可扩展性和模块化进行结构化

我创建 GPT Lab 是为了赋能那些技术水平低或没有技术技能的用户,使他们能够构建自己的基于提示的 LLM AI 应用,而无需担心底层基础设施。我的目标是最终提供后端 API,将用户的自定义前端应用(无论是否使用 Streamlit)与他们的 AI 助手连接。这激励我设计了一个解耦架构,将前端 Streamlit 应用与后端逻辑分开。

后端代码的结构如下:

+----------------+     +-------------------+     +-------------------+     +------------+
|                |     |                   |     |                   |     |            |
|  Streamlit App |<--->| util_collections  |<--->| api_util_firebase |<--->|  Firestore |
|                |     | (users, sessions, |     |                   |     |            |
|                |     |  bots)            |     |                   |     |            |
+----------------+     +-------------------+     +-------------------+     +------------+
                             |
                             |
                             v
                     +-----------------+     +------------+
                     |                 |     |            |
                     | api_util_openai |<--->|   OpenAI   |
                     |                 |     |            |
                     +-----------------+     +------------+

模块如下:

  • api_util_firebase 处理与 Firestore 数据库的 CRUD 操作。

  • api_util_openai 与 OpenAI 的模型进行交互,向上游模型提供统一的聊天模型,修剪聊天消息,并尝试检测和防止提示注入攻击。

  • api_util_usersapi_util_sessionsapi_util_bots 是它们各自 Firestore 集合的接口。它们与 api_util_firebase 和 api_util_openai 进行交互,并实现 GPT Lab 特定的业务逻辑。

这种设计使得代码的不同部分可以独立开发、测试和扩展。它还建立了一个更简单的迁移路径,将后端 util_collections 模块转换为 Google Cloud Functions,这些函数可以通过 API Gateways 公开。

会话状态:管理 UI 和用户流程

正如在 第一篇博客文章 中解释的,我使用了会话状态变量来控制和管理 Streamlit 页面上的功能。以下说明了这些变量在整个应用中的使用方式:

home.py

  • 用户 控制是否渲染 OpenAI API 密钥模块

pages/1_lounge.py

  • 用户 控制是否渲染 OpenAI API 密钥模块,启用助手选择,并显示我的助手标签页。

  • 用户选择与助手互动后,助手详细信息会存储在 bot_info 中。

pages/2_assistant.py

  • 用户 控制是否渲染 OpenAI API 密钥模块。

  • bot_infosession_idsession_ended 决定显示哪个屏幕变体。

  • bot_info 不存在:检查 assistant_id 是否在 URL 参数中。如果没有,则提示用户搜索助手。

  • bot_infosession_id 存在,并且 session_ended 为 false:显示聊天会话屏幕。

  • bot_infosession_id 存在,并且 session_ended 为 true:显示聊天会话回顾屏幕。

  • 在聊天会话中,session_msg_list 存储对话内容。

pages/3_lab.py

  • 用户 控制是否渲染 OpenAI API 密钥模块以及是否允许用户在实验室中开始创建助手。

  • lab_active_step 控制渲染哪个实验室会话状态:

  • 如果为 1:渲染步骤 1 UI 以设置助手的初始提示和模型。

  • 如果为 2:渲染步骤 2 UI 测试与助手的聊天。

  • 如果为 3:渲染步骤 3 UI 完成助手详细信息。创建时,机器人记录会在 Firestore DB 中创建,并将文档 ID 保存到 lab_bot_id。

  • 如果为 4 且 lab_bot_id 已设置:渲染步骤 4 UI 显示助手创建确认。

  • 在测试聊天会话期间,lab_msg_list 存储测试消息。通过使用单独的 lab_bot_idbot_info,我可以让用户在休息室/助手和实验室之间来回跳转,而不会丢失每个部分的进度。

在完成前期规划后,接下来的执行变得更加可控。

总结

在这篇文章中,我涵盖了创建 GPT 实验室所需的前期规划,包括功能、数据模型、代码和会话状态。我希望这能激励你构建自己的雄心勃勃的 Streamlit 应用程序。

通过 TwitterLinkedin 与我联系。我很期待你的反馈。

祝你使用 Streamlit 愉快! 🎈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值