TowardsDataScience 2023 博客中文翻译(三百)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

数据工程中的流数据

原文:towardsdatascience.com/streaming-in-data-engineering-2bb2b9b3b603

流数据管道和实时分析

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

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

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

图片由DESIGNECOLOGIST提供,在Unsplash

流数据是最受欢迎的数据管道设计模式之一。将事件作为单个数据点创建了从一个点到另一个点的持续数据流,从而为实时数据摄取和分析提供了机会。如果你想了解数据流并学习如何构建实时数据管道,这篇文章适合你。了解如何测试解决方案,并模拟事件流的测试数据。这篇文章是一个绝佳的机会,让你掌握一些受欢迎的数据工程技能,使用流行的流处理工具和框架,即 Kinesis、Kafka 和 Spark。我想谈谈数据流的好处、示例和用例。

数据流究竟是什么?

流数据,也称为事件流处理,是一种数据管道设计模式,当数据点不断从源头流向目的地时使用。这可以实时处理,使实时分析功能能够快速对数据流和分析事件作出反应。由于流处理,应用程序可以对新数据事件触发即时响应,通常这将是处理企业级数据最受欢迎的解决方案之一。

只要在点 A 和点 B 之间进行数据处理,就会有一个数据管道 [1]。

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

流数据管道示例。图片由作者提供

在这个例子中,我们可以创建一个ELT 流处理数据管道到AWS Redshift。AWS Firehose delivery stream可以提供这种无缝集成,将数据直接创建到数据仓库表中。然后,数据将被转化,以使用AWS Quicksight作为 BI 工具生成报告。

假设我们需要创建一个报告仪表盘来展示公司中的收入来源。在许多场景中,业务需求是实时生成洞察。这正是我们需要使用流处理的情况。

数据流可以由各种数据源生成,例如物联网、服务器数据流、营销应用内事件、用户活动、支付交易等。这些数据可以以不同格式流动,并且经常变化。流处理模式的理念是实时应用 ETL 并无缝处理事件流。

每当我们需要处理毫秒级的数据延迟时,流处理是正确的选择。

考虑下面的例子以更好地理解它。所有应用程序都使用 OLTP 数据库[4],例如 MySQL。你的应用程序也是其中之一,但你需要将这些数据存储到数据仓库解决方案(DWH),即 Snowflake 或 BigQuery。

## 数据建模为数据工程师

初学者的终极指南

towardsdatascience.com

使用批量数据管道解决方案,我们可能希望从 MySQL 加载到 DWH 一次/每天/每小时/每五分钟等。流处理则相反,它将使用专用系统,例如 Kafka Connect。它会在数据进入数据库时立即处理数据。

流行的数据流工具

让我们深入了解过去几年中被证明最有用的流数据平台和框架。

  • Apache Spark — 用于大规模分析和复杂数据转换的分布式数据计算框架

  • Apache Kafka — 一个实时数据管道工具,具有分布式消息系统用于应用程序

  • AWS Kinesis — 一个用于分析和应用程序的实时流平台

  • Google Cloud Dataflow — 谷歌的实时事件处理和分析管道的流平台

  • Apache Flink — 一个分布式流数据平台,旨在进行低延迟数据处理。

几乎所有这些平台都有它们的托管云服务(如 AWS Kinesis、Google Cloud Dataflow),并且可以与其他服务(如存储(S3)、队列(SQS、pub/sub)、数据仓库(Redshift)或 AI 平台)无缝集成。

所有这些工具都可以部署在 Kubernetes、Docker 或 Hadoop 上,旨在解决一个问题——处理高容量和高速度的数据流。

数据流的好处

流数据管道设计模式帮助组织主动减轻与数据处理延迟相关的不利业务事件的影响,例如各种损失和中断、客户流失和财务衰退。由于今天业务需求的复杂性,传统的批处理数据处理是‘不可行’的解决方案,因为它只能处理累积时间的事务数据组。

所以使用数据流的业务优势如下:

  • 提高客户满意度,进而提高客户保留率

  • 减少操作损失,因为它可以提供关于系统中断和漏洞的实时洞察。

  • 投资回报率提高,因为公司现在可以更快地对业务数据做出反应,对客户需求和市场趋势的响应能力提高。

主要的技术优势在于数据处理,因为它以严格的逐个处理方式运行事件处理。与批处理处理相比,它具有更好的故障容忍性,如果管道中的一个事件因某些原因无法处理,那么只有这个事件会受到影响。在批处理管道中,由于单个数据点可能具有错误的模式或数据格式,整个数据处理块会因此失败。

流数据管道的主要缺点是成本

每次我们的流处理器达到端点时,它都需要计算能力。通常,流数据处理会导致与特定数据管道相关的更高成本。

构建流数据管道的挑战

  • 故障容忍性 — 我们能否设计和构建一个能够处理单一数据事件处理失败的数据平台?数据通常来自不同的数据源,甚至可能以不同的格式出现。在设计带有流组件的数据平台时,数据的可用性和持久性成为重要的考虑因素[3]。

数据平台架构类型

它能多好地满足你的业务需求?选择的困境。

towardsdatascience.com

  • 排队和排序 — 数据流中的事件必须正确排序。否则,数据处理可能会失败。例如,如果排序不正确,应用内消息将没有意义。

  • 可扩展性 — 应用程序需要扩展。就这么简单。设计一个能够很好应对来自源的事件数量增加的数据管道并非易事。能够为数据管道添加更多资源和数据处理能力是一个强大数据平台的重要组成部分。

  • 数据一致性 — 在分布式数据平台中,数据经常是并行处理的。这可能会成为挑战,因为在一个数据处理器中数据可能已经被修改,而在另一个处理器中变得陈旧。

现实世界中的一个例子

让我们看一下这个使用 AWS Kinesis 和 Redshift 构建的流数据管道示例。

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

示例管道。图片由作者提供

Amazon Kinesis Data Firehose 是一个 ETL 服务,可以高可靠性地收集、转换并分发流数据到数据湖、数据存储和分析服务。

我们可以用它将数据流传输到 Amazon S3,并将数据转换为分析所需的格式,无需开发处理管道。它对于机器学习(ML)管道也非常适合,其中模型用于检查数据并预测推断端点,因为数据流向其目标。

Kinesis Data Streams 与 Kinesis Data Firehose

Kinesis Data Streams 主要关注于消费和存储数据流。Kinesis Data Firehose 旨在将数据流传递到特定的目标。两者都可以消费数据流,但使用哪个取决于我们希望数据流去往何处。

AWS Kinesis Data Firehose 允许我们将数据流重定向到 AWS 数据存储。Kinesis Data Firehose 是收集、处理和加载数据流到 AWS 数据存储的最直接方法。

Amazon Kinesis Data Firehose 支持批处理操作、加密和流数据压缩,以及自动化的每秒 TB 级别的可扩展性。Firehose 可以无缝集成 S3 数据湖、RedShift 数据仓库解决方案或 ElasticSearch 服务。

AWS Kinesis Data Streams 是一个 Amazon Kinesis 实时数据流解决方案,具有卓越的可扩展性和耐用性,数据流全天候 24/7 可用给任何消费者。这使得它比 Kinesis Data Firehose 更昂贵。

如何使用 AWS CloudFormation 创建 Firehose 资源

请查看下面的 CloudFormation 模板。它部署了包括我们需要的 Firehose 在内的所有必要资源。

AWSTemplateFormatVersion: 2010-09-09
Description: >
  Firehose resources relating to data generation.

Parameters:
  Environment:
    AllowedValues:
      - staging
      - production
    Description: Target environment
    Type: String
    Default: 'staging'
  DataLocation:
    Description: S3 data lake bucket name.
    Type: String
    Default: data.staging.aws

Resources:
  MyDataStream:
    Type: AWS::KinesisFirehose::DeliveryStream
    Properties: 
      DeliveryStreamName: !Sub 'my-event-${Environment}'
      DeliveryStreamType: DirectPut
      ExtendedS3DestinationConfiguration: 
        BucketARN: 
          - !Sub 'arn:aws:s3:::${DataLocation}' # For example: 'arn:aws:s3:::data.staging.aws'
        BufferingHints:
          IntervalInSeconds: 300
          SizeInMBs: 30
        CompressionFormat: UNCOMPRESSED
        Prefix: !Sub 'events/my-event-${Environment}/'
        RoleARN: !GetAtt AccessRole.Arn

  AccessRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - firehose.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: !Sub '${AWS::StackName}-AccessPolicy'
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - s3:AbortMultipartUpload
                  - s3:GetBucketLocation
                  - s3:GetObject
                  - s3:ListBucket
                  - s3:ListBucketMultipartUploads
                  - s3:PutObject
                Resource:
                  - !Sub 'arn:aws:s3:::${DataLocation}'
                  - !Sub 'arn:aws:s3:::${DataLocation}/*'
                  # - 'arn:aws:s3:::data.staging.aws' # replace with your S3 datalake bucket
                  # - 'arn:aws:s3:::data.staging.aws/*'
              - Effect: Allow
                Action:
                  - kinesis:DescribeStream
                  - kinesis:GetShardIterator
                  - kinesis:GetRecords
                Resource:
                  - !Sub 'arn:aws:kinesis:${AWS::Region}:${AWS::AccountId}:stream/my-event-${Environment}'

可以使用 AWS CLI 工具在 AWS 中部署它。我们需要在命令行中运行这个(在你的账户中替换为唯一的存储桶名称):

./deploy-firehose-staging.sh s3-lambda-bucket s3-data-lake-bucket

我们的 shell 脚本如下所示:

#!/usr/bin/env bash
# chmod +x ./deploy-firehose-staging.sh
# Run ./deploy-firehose-staging.sh s3-lambda-bucket s3-data-lake-bucket
STACK_NAME=FirehoseStackStaging
LAMBDA_BUCKET=$1 #datalake-lambdas.aws # Replace with unique bucket name in your account
S3_DATA_LOCATION=$2 #data.staging.aws # S3 bucket to save data, i.e. datalake
# Deploy using AWS CLI:
aws \
cloudformation deploy \
--template-file firehose_stack.yaml \
--stack-name $STACK_NAME \
--capabilities CAPABILITY_IAM \
--parameter-overrides \
"Environment"="staging" \
"DataLocation"=$S3_DATA_LOCATION #"data.staging.aws"

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

已创建 Firehose 资源。图片由作者提供

现在我们需要创建一个事件生产者。我们可以使用 Python 完成这个操作,app.py 的代码如下:

import boto3
kinesis_client = boto3.client('firehose', region_name='eu-west-1')
...
response = client.put_record_batch(
    DeliveryStreamName='string',
    Records=[
        {
            'Data': b'bytes'
        },
    ]
)

put_record_batch 方法可以在一次调用中将多个数据记录写入交付流,这比单条记录写入方式能提供更好的每生产者吞吐量。PutRecord 用于将单条数据记录写入交付流。在本教程中选择哪个方法由你决定。

我们可以在 app.py 中使用下面的辅助函数生成一些合成数据。

def get_data():
    '''This function will generate random data for Firehose stream.'''
    return {
        'event_time': datetime.now().isoformat(),
        'event_name': random.choice(['JOIN', 'LEAVE', 'OPEN_CHAT', 'SUBSCRIBE', 'SEND_MESSAGE']),
        'user': round(random.random() * 100)}

现在这些数据可以通过以下方式发送到我们的事件生产者:

 try:
        print('Sending events to Firehose...')
        for i in range(0, 5):
            data = get_data()
            print(i, " : ", data)
            kinesis_client.put_record(
                DeliveryStreamName=STREAM_NAME,
                Record={
                    "Data":json.dumps(data)
                }
            )
            processed += 1
        print('Wait for 5 minutes and Run to download: aws s3 cp s3://{}/events/ ./ --recursive'.format(S3_DATA))
        # For example, print('Wait for 5 minutes and Run to download: aws s3 cp s3://data.staging.aws/events/ ./ --recursive')
    except Exception as e:
        print(e)

完成!我们已经创建了一个简单的流数据管道,将汇总结果输出到云存储(AWS S3)。

在命令行中运行 python app.py

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

事件连接器示例。作者提供的图片

查看下面的教程,了解更高级的数据管道示例 [2]

## 使用 Redshift Serverless 和 Kinesis 构建流数据管道

面向初学者的端到端教程

towardsdatascience.com

结论

项目理想的流数据平台并不存在。流设计有其好处,但在使用时也会遇到一些明显的挑战。选择哪个流工具不是一个容易的决定。这取决于你的业务目标和功能数据需求。你可能需要尝试并比较多个流平台,考虑功能、性能、成本、易用性和兼容性等特征。它会是一个机器学习管道吗?我们需要处理分区、窗口和连接吗?我们需要高吞吐量、容错性还是低延迟?

不同的流框架具有不同的能力,例如,Kafka 有一个方便的会话 ,可以很容易地集成到你的分析管道中。

我们的管道需要什么频率的数据传输和消费?它将交付到数据仓库解决方案还是数据湖中?一些平台比其他平台提供更好的集成功能。

另一个重要的考虑因素是必须对流数据进行的数据处理和分析的类型和复杂性

我建议根据你自己数据管道场景和公司主要利益相关者收集的需求来创建一个原型。最终的流数据管道应该是能够为业务增值并满足你的数据工程目标的。

推荐阅读:

[1] towardsdatascience.com/data-pipeline-design-patterns-100afa4b93e3

[2] towardsdatascience.com/building-a-streaming-data-pipeline-with-redshift-serverless-and-kinesis-04e09d7e85b2

[3] medium.com/towards-data-science/data-platform-architecture-types-f255ac6e0b7

[4] medium.com/towards-data-science/data-modelling-for-data-engineers-93d058efa302

使用笔记本风格工作区简化 dbt 模型开发

原文:towardsdatascience.com/streamline-dbt-model-development-with-notebook-style-workspace-eb156fe6e81

互动式构建和编排数据模型

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

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

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

作者提供的图片

最初发布于 https://mathdatasimplified.com 2023 年 6 月 5 日。

动机

dbt(数据构建工具)是一个强大的数据仓库数据转换工具。

然而,它确实存在一些限制,包括以下几点:

  1. 缺乏输出预览: 使用 dbt core 时,无法在构建模型之前预览模型的输出,这可能会阻碍对数据转换过程的验证和迭代。

  2. 特征工程的局限性: 由于 SQL 是 dbt 的主要语言,因此在执行复杂的特征工程任务时存在一定的局限性。为了执行超出 SQL 能力的复杂特征工程,可能需要额外的工具或语言,如 Python。

  3. 部分 ETL 解决方案: 虽然 dbt 在数据转换方面表现出色,但它并未提供全面的端到端解决方案来处理数据加载、数据提取和编排等任务。

为了缓解这些挑战,dbt Cloud 提供了诸如开发数据模型、预览输出和通过用户友好的网页界面调度 dbt 作业等功能。然而,随着项目数量的增加,使用 dbt Cloud 的成本可能会变得相当高。

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

作者提供的图片

Mage + dbt 集成

dbt cloud 的一个免费替代品是 Mage,这是一个开源的数据管道工具,用于数据转换和集成任务。

Mage 无缝地与 dbt 互补,带来了诸多好处,包括:

  1. 集成的基于网页的 IDE: Mage 提供了一个方便的基于网页的 IDE,你可以在一个界面内轻松开发和探索数据模型。

  2. 语言灵活性: 使用 Mage,你可以将不同工具和语言的优势与 dbt 结合,以增强数据处理能力。

  3. 可视化 dbt 模型输出: Mage 提供了内置的可视化功能,允许用户通过几次点击轻松地可视化 dbt 模型生成的输出。

  4. 数据提取和加载: 除了数据转换,Mage 还提供了数据提取和加载的功能,实现更全面的端到端数据管道解决方案。

  5. 管道调度和重试机制: Mage 允许你调度数据管道并自动重试失败的组件,确保数据集成过程的顺利和可靠执行。

让我们深入探讨这些功能。

随意通过克隆这个 GitHub 仓库来探索和实验源代码:

[## GitHub - khuyentran1401/dbt-mage

当前无法执行该操作。你在另一个标签页或窗口中登录了。你在另一个标签页或窗口中登出了…

github.com

设置

安装 Mage

你可以通过 Docker、pip 或 conda 安装 Mage。本文将使用 Docker 安装 Mage 并初始化项目。

docker run -it -p 6789:6789 -v $(pwd):/home/src mageai/mageai /app/run_app.sh mage start [project_name]

例如,我们将项目命名为“dbt_mage”,因此命令变为:

docker run -it -p 6789:6789 -v $(pwd):/home/src mageai/mageai /app/run_app.sh mage start dbt_mage

其他安装 Mage 的方式请参考 这里

创建管道

在浏览器中打开 localhost:6789/ 查看 Mage UI。

点击“新建”,选择“标准(批量)”来创建一个新的批量管道。将其重命名为“dbt_pipeline”。

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

作者提供的图片

安装依赖

由于我们将使用 BigQuery 作为 dbt 的数据仓库,我们需要通过将 dbt-bigquery 添加到“requirements.txt”文件中并点击“安装包”来安装它。

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

作者提供的图片

创建 dbt 项目

要创建 dbt 项目,请导航到右侧面板并点击终端按钮。

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

作者提供的图片

移动到项目下的“dbt”文件夹并执行命令 dbt init

cd dbt_mage/dbt 
dbt init demo -s

该命令将“demo”文件夹添加到 dbt 目录。

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

作者提供的图片

右键点击“demo”文件夹,创建一个名为“profiles.yml”的新文件。在此文件中指定你的 BigQuery 凭证。

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

作者提供的图片

请参考 此文档 以获取指定其他数据平台提供商凭证的说明。

现在设置完成,我们准备好探索 Mage 的令人兴奋的功能了!

集成的基于 Web 的 IDE

Mage 提供了一个用户友好的基于 Web 的 UI,简化了 dbt 模型的创建。它的独特功能允许进行交互式代码开发,类似于在 Jupyter Notebook 中工作。每个块都作为一个独立的可执行代码文件。

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

图片来源:作者

要创建一个新的 dbt 模型,点击“DBT model”并提供名称和位置。

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

图片来源:作者

编写查询并点击“Compile & preview”以预览查询结果。

预览结果后,通过点击三点图标并选择“Run model”来执行模型。

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

图片来源:作者

你可以在同一个编辑器中创建额外的模型,只需再次点击“DBT model”。

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

图片来源:作者

使用{{ ref('model_name') }}来引用另一个模型,就像在 dbt 中一样。

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

图片来源:作者

语言灵活性和 ETL 功能

Mage 使你能够将 dbt 模型与其他语言(包括 Python、R 或 SQL)结合,用于数据加载、转换和导出目的。

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

图片来源:作者

例如,我们创建两个 Python 数据加载器——一个用于美国数据,一个用于印度数据。

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

图片来源:作者

然后,我们将结合一个 dbt 块以连接这些数据加载器的输出。通过点击“Edit parent block”并选择数据加载器块来设置join_tables块的父块。

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

图片来源:作者

当一个 dbt 模型依赖于非 dbt 上游块时,Mage 会自动将该块的源添加到“dbt/demo/models/mage_sources.yml”文件中。

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

图片来源:作者

现在你可以在 dbt 块中利用 Python 块的输出。

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

图片来源:作者

然后,我们可以设置一个名为convert_object_to_int的变换器块,将其作为join_tables的下游块以处理其输出。

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

图片来源:作者

可视化 dbt 模型输出

虽然传统工具如 Tableau 可以用于可视化,但 Mage 提供了一个集中化的解决方案,可以在一个地方处理和分析 dbt 块的输出。

为了演示这一点,我们创建另一个块,名为convert_week_to_datetime,它将Week列转换为 datetime 类型。

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

图片来源:作者

点击“Add chart”图标,选择“Time series line chart”,并创建一个时间序列可视化。

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

图片来源:作者

你将看到如下的折线图:

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

作者提供的图片

管道调度和重试机制

Mage 使你能够调度管道运行,并包含一个重试机制,以处理失败的块而无需重新运行整个管道。

要立即执行管道,请点击左侧面板上的“触发器”图标,然后选择“立即运行管道”。

你可以通过选择新创建的管道触发器并点击“查看块运行”来监控所有块运行的实时状态。

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

作者提供的图片

要调度管道,请点击“创建新触发器”,选择“调度”,并定义所需的频率。

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

作者提供的图片

点击“重试未完成的块”以重试失败的块而无需重新启动整个管道。

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

作者提供的图片

缺点

虽然 Mage 是 dbt 的绝佳补充,但在使用 Mage 时需要考虑一些缺点:

  1. 项目复杂性增加: 将 Mage 和 dbt 集成可能会增加项目结构的复杂性。

  2. 更长的错误信息: 由于 Mage 在块代码周围添加了额外的代码,错误信息通常比标准错误信息要长。

  3. 学习曲线: 虽然 Mage 提供了直观的用户体验,但熟悉这个新工具需要一些时间和努力。

结论

如果你寻求提高效率并且愿意接受项目复杂性的轻微增加,Mage 是补充你的 dbt 项目的理想工具。

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

使用 GPT-3 精简你的文档

原文:towardsdatascience.com/streamline-your-documentation-with-gpt-3-5d9f2bbf217c

使用人工智能生成 Python 文档字符串

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

·发表于 Towards Data Science ·6 分钟阅读·2023 年 1 月 16 日

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

图像由 DALL-E 生成,提示语为:“一个机器人在计算机终端检查代码。赛博朋克风格,逼真。”

GPT-3,OpenAI 开发的最新语言模型,具有生成类人文本的能力,使其成为各种自然语言处理任务的强大工具。

该模型还进行了编程语言的训练。一个应用场景是生成 Python 函数的文档字符串(docstrings)。

什么是文档字符串?

文档字符串是出现在 Python 函数中的第一个语句。它提供了函数及其输入和输出的简要描述,使其他开发人员更容易理解和使用代码。编写清晰且信息丰富的文档字符串可能耗时且乏味,尤其是在拥有许多函数的大型项目中。

# A docstring example
def square(n):
    """Takes in a number n and returns the square of n"""
    return n**2

通过在一个有用的提示中提供 Python 函数的源代码,可以使用 GPT-3 来生成文档字符串。

示例:计算一组数字的平均值

假设你编写了一个计算数字列表平均值的函数。你希望 GPT-3 创建文档字符串。

以下是提示的示例:

# Python 3.7

def mean_of_arr(arr):
    return sum(arr)/len(arr)

# An elaborate, high quality docstring for the above function:
"""

制定正确的提示非常重要。

注意我们如何通过以下方式帮助 GPT-3:

  • 告诉它编程语言(Python 3.7),

  • 提供关于如何显式要求生成详细且高质量的函数文档字符串的评论,

  • 然后添加一行带有三个双引号的内容,这也是文档字符串的标准起始模式。

GPT-3 生成了以下响应

 This function takes an array of numbers and returns the mean of the array.
    The mean is the sum of the numbers divided by the length of the array.

GPT-3 甚至知道如何缩进文本块。

需要注意的是,生成的文档字符串可能不完美,可能需要一些编辑。然而,GPT-3 可以在编写文档字符串的过程中节省大量时间和精力。

使用文档字符串生成器自动创建文档字符串

将你的代码复制并粘贴到 OpenAI 的 playground 中,仅仅让它为你生成文档字符串有点乏味。

我们已经看到 AI 工具直接嵌入我们的开发环境中,如 GitHub CopilotAmazon CodeWhisperer.

未来的程序员将是人类和 AI 的混合体。让我们自己开始玩转这一能力。

假设你在使用 Jupyter notebook,如在 Colab,并且你想快速为一个新函数生成文档字符串。

假设你已经有一个 OpenAI 账户,生成你的 API 密钥 并将其粘贴到下面的示例中,复制到你的 notebook。

import openai # Install via "pip install openai"
import inspect

openai.api_key = "YOUR_OPENAI_API_KEY_GOES_HERE"

def generate_docstring(my_func, python_version=3.7):
    # Grab the source code from the function as a string
    source_code = inspect.getsource(my_func)

    # Remove any existing docstring
    if my_func.__doc__ is not None:
        source_code = source_code.replace(my_func.__doc__,"")\
                                 .replace("\"\"\"","")

    # Wrap the source code with hints for GPT-3
    prompt = f"# Python {python_version}\n\n" \
           + source_code \
           + "\n# An elaborate, high quality docstring for the above function:\n\"\"\""

    # Send prompt to GPT-3
    response = openai.Completion.create(
                   model="code-davinci-002",
                   prompt=prompt,
                   temperature=0,
                   max_tokens=150,
                   top_p=1.0,
                   frequency_penalty=0.0,
                   presence_penalty=0.0,
                   stop=["#", "\"\"\""]
               )

    # Return the generated docstring
    docstring = response["choices"][0]["text"]
    return docstring

inspect 库的 getsource 函数从你传递给它的任何函数中获取源代码,并将其作为字符串返回。

如果函数已经有了文档字符串,我不想让 GPT-3 产生偏见,因此我包括了一些代码来剥离任何现有的文档字符串。

我假设代码是 Python,但我将 Python 版本留作可选变量。

实验

现在进入有趣的部分。让我们看看它在几个示例中的表现。

选择排序

这是一种时间复杂度为 O(n²) 的排序算法,意味着它在大列表上效率低下,但它的优点是简单。

def sort(arr):
    for i in range(len(arr)):
        min_idx = i
        for j in range(i+1, len(arr)):
            if arr[min_idx] > arr[j]:
                min_idx = j
        arr[i], arr[min_idx] = arr[min_idx], arr[i]
    return arr

我明确选择不将函数命名为 selection_sort,因为我不希望 GPT-3 仅仅从它在训练语料库中见过的选择排序示例中获取文档字符串。

有趣的是,GPT-3 推断出这里使用的是选择排序算法。很可能在它的语料库中,选择排序算法就是以这种方式编写的。以下是 GPT-3 返回的结果:

 This function sorts an array of integers in ascending order.
    It uses the selection sort algorithm.

    Parameters
    ----------
    arr : list
        The array to be sorted.

    Returns
    -------
    list
        The sorted array.

那是一个相当适当的文档字符串。

将数据拆分为训练集和测试集

在对数据进行机器学习模型训练之前,常见的一步是将数据拆分为训练集和测试集。

假设你有一个函数来完成这个任务,并将结果写入两个文件:

import pandas as pd
from sklearn.model_selection import train_test_split

def randomly_split_dataset(folder, filename, split_ratio=[0.8, 0.2]):
    df = pd.read_json(folder + filename, lines=True)
    train_name, test_name = "train.jsonl", "test.jsonl"
    df_train, df_test = train_test_split(df, test_size=split_ratio[1], 
                                         random_state=42)
    df_train.to_json(folder + train_name, orient='records', lines=True)
    df_test.to_json(folder + test_name, orient='records', lines=True)
    return

仅将 randomly_split_dataset 函数传递给我们的文档字符串生成器:

my_docstring = generate_docstring(randomly_split_dataset)

print(my_docstring)

GPT-3 返回:

 This function takes in a folder and filename, and splits the dataset into
    two files, train.jsonl and test.jsonl, with the split ratio specified by
    the user.

    Parameters
    ----------
    folder : str
        The folder where the dataset is located.
    filename : str
        The name of the dataset file.
    split_ratio : list
        The ratio of the split, with the first element being the training set
        and the second element being the test set.

    Returns
    -------
    None

    Examples
    --------
    >>> randomly_split_dataset("data/", "train

不错。它在示例中突然停止,但我发现可以通过增加 max_tokens 的数量来改进这一点。

现在进行最后的测试:记录它自己。

为文档字符串生成器编写文档字符串

也许最令人满意的结果是让文档字符串生成器为自己编写文档字符串。

my_docstring = generate_docstring(generate_docstring)

print(my_docstring)

结果是一个完全可以接受的文档字符串,我们现在可以将其复制并粘贴到函数中。

 This function takes a function as input and returns a docstring for that function.
    It uses the OpenAI API to generate the docstring.

    Parameters
    ----------
    my_func : function
        The function for which you want a docstring.
    python_version : float
        The version of Python you are using.

    Returns
    -------
    docstring : str
        The generated docstring.

结论

你的体验可能会有所不同。在我自己的实验中,结果有时会显得怪异或与我测试的功能无关。其他时候,我被明显的洞察力震惊。调整 temperature 变量可能值得尝试,以探索更多“创造性”的输出。

需要注意的是,GPT-3 实际上并不理解代码。它不能对你的代码进行推理。GPT-3 是在数十亿行代码上进行训练的,其中包括许多高质量的 docstrings。这个拥有 1750 亿参数的神经网络非常擅长预测在给定提示之后最有可能出现的 token(单词等)序列。

除了生成 docstrings,GPT-3 还可以用于许多其他自然语言处理任务,如文本总结、问答和语言翻译。GPT-3 的潜在应用广泛,未来它的使用情况将非常有趣。

总结来说,GPT-3 可以成为生成 Python 函数 docstrings 的有用工具,帮助开发者以最小的努力编写清晰且有信息量的文档。由于该模型仍在开发中,我们可以期待它在软件开发的其他方面如何改进。

如果你喜欢阅读这些故事并希望支持我作为作者,请考虑注册成为 Medium 会员。每月 $5,可访问我所有的写作以及成千上万其他作家的作品。如果你通过 我的链接 注册,我将获得一小部分佣金,对你没有额外费用。

## 使用我的推荐链接加入 Medium — Mikhail Klassen

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

mikhailklassen.medium.com

优化 Azure 虚拟机性能并降低成本:提升效率的可靠策略

原文:towardsdatascience.com/streamlining-azure-vm-performance-while-slashing-costs-proven-strategies-for-optimal-efficiency-23a9bfc7fe62

在不妨碍效率的前提下降低成本的技术

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

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

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

照片来源 Growtika Unsplash

概述

在设置 Azure 虚拟机时,重要的是要在环境配置之前了解定价模型和服务内容。如果不这样做,我们可能会面临高额账单,而这些费用本可以通过遵循成本优化策略来避免。本文将讨论实用的策略和见解,帮助你避免这种情况并更好地控制成本。我们还将讨论多少成本过高以及 Azure 定价中高级功能的作用。

请注意,本文中的图像包含 Azure 的尺寸和配置。这些图像来源于写作时某个地区的 Azure 门户。它们仅用于演示目的,不应视为你所在地区当前可用或配置的指示。建议参考官方 Azure 文档(或你的 Azure 门户)以获取最新和准确的成本及定价信息。

让我们开始吧。

实施调度机制来启动和停止你的 Azure 虚拟机

Azure 虚拟机的计费基于资源使用情况,包括 CPU、内存和存储消耗。需要注意的是,即使虚拟机处于空闲状态或未被主动使用,你仍然会为这些资源付费。换句话说,即使虚拟机内部没有正在运行的进程/作业,你也会因为虚拟机‘开着’而被收费。因此,需要优化工作负载以防止不必要的费用。

为了有效处理这一点,请执行以下操作:

  1. 确定你打算在虚拟机中设置的进程和作业。这可以是与你的应用服务器相关的进程,甚至是虚拟机上托管的数据库中运行的作业。

  2. 确定作业之间的依赖关系,即,识别需要并行运行的作业和需要同时运行的作业。还要记录那些与其他作业无关的作业。

  3. 尝试在一致的时间范围内安排作业运行。例如,如果你有多个并发作业从数据库中处理数据,将作业运行对齐在相同时间范围内是有意义的。

  4. 一旦你缩小了时间范围,你可以安排你的虚拟机在所需的时间框架内启动和停止。例如,如果作业需要在某个时区的每晚 9 PM 到 12 AM 之间运行,你可以安排你的虚拟机在 8:45 PM 启动。

使用 Powershell、CLI 或控制台可以安排虚拟机的启动和停止。Azure 控制台为用户提供了使用 Azure 门户管理虚拟机的功能和便利。

你可以通过访问 Azure 门户中的虚拟机来找到启动、停止和释放虚拟机的选项。以下是 Azure 提供的自动化模板。

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

Azure 控制台中的任务(图片来源:作者)

以下是访问自动化模板的步骤 -

选择你的虚拟机并访问任务(预览)。

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

Azure 虚拟机 — 自动化 - 任务(图片来源:作者)

点击‘添加任务’

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

Azure 虚拟机 — 任务 — 添加任务(图片来源:作者)

选择模板‘启动虚拟机’

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

Azure 虚拟机 - 自动化模板(图片来源:作者)

在进行身份验证后,你可以配置启动虚拟机的时间。

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

Azure 虚拟机 — 配置启动虚拟机自动化任务(图片来源:作者)

相同的步骤也可以用于关闭和释放虚拟机。

减少成本的最重要一步是解除分配虚拟机。 停止虚拟机和解除分配虚拟机之间存在很大差异。当你停止虚拟机时,Azure 资源管理会暂时暂停虚拟机,但保留分配的资源并保存虚拟机的状态。然而,当你解除分配虚拟机时,Azure 资源管理会将分配的资源释放回 Azure 资源池。这些释放的资源可以供其他资源或服务使用,也可以根据需求分配给其他应用程序或虚拟机。这样,你可以节省计算成本。请记住,你仍然需要支付存储费用。下面是一个显示 Azure 环境及其与虚拟机交互的组件的示意图。

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

Azure 资源管理(作者提供的图片)

一个值得考虑的好策略是在非活动期间将虚拟机解除分配。解除分配非生产环境也是一个不错的选择,可以帮助实现显著的成本节省。确保解除分配与你的操作需求一致,并且不会影响关键流程或服务。

将磁盘从高级改为标准

希望拥有高级磁盘存储、管理磁盘和自动扩展等高级功能是很自然的。但是,通常需要问自己这些功能是否真的适合你的用例。

Azure 支持多种高级和标准磁盘。正如你可能猜到的,高级磁盘比标准磁盘更贵。如果你有像数据库和大数据处理这样的 I/O 密集型操作,你将需要继续使用高级磁盘。但是,如果你的工作负载没有时间限制且不太密集,那么标准磁盘是一个不错的选择。我们用一个例子来看看这个问题。

假设你有一个脚本每天(每天一次)从外部文件系统中提取数据,并填充到基于云的报告工具中。这是一项不那么密集的操作,可以轻松地使用标准磁盘处理。

假设你在虚拟机上托管了一个 SQL 数据库,该数据库直接连接到一个用户可以进行临时查询的自助报告。如果是这种情况,最好使用高级磁盘。不过,你可以考虑将高级磁盘降级到较低层级,以获得一些成本节省。

以下是通过控制台更改磁盘的过程。点击 Azure 虚拟机并访问侧边栏中的‘磁盘’选项。

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

Azure 虚拟机 — 磁盘(作者提供的图片)

点击磁盘名称中的超链接。

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

Azure 虚拟机- 当前操作系统(作者提供的图片)

点击侧边栏中的‘大小 + 性能’。

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

Azure 虚拟机- 大小 + 性能(作者提供的图片)

当前使用的配置会以灰色高亮显示。这是用户可以将磁盘从高级磁盘更改为标准磁盘类型的地方。您可以通过点击选择不同的配置。请注意,任何不适用于您的环境的配置将无法点击。

更改虚拟机配置

Azure 提供不同尺寸、内存、存储和计算能力的虚拟机。每一系列和代的 Azure 虚拟机都旨在提供特定的性能特征,让用户根据其工作负载选择最合适的类型。下图展示了 Azure 虚拟机提供的不同代数。

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

Azure 虚拟机世代(作者提供的图片)

确定您的工作负载,并降级到最符合工作负载要求且能够节省成本的虚拟机。以下是进行相应操作的步骤 -

  1. 点击虚拟机

  2. 点击侧边栏中的“尺寸”

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

Azure 虚拟机 - 尺寸(作者提供的图片)

结果面板显示了不同的虚拟机世代及其所有相关详细信息,如每小时费用、内存和支持的工作负载。

请注意,这只是一个示例图片,并不旨在展示所有可用的配置选项。

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

Azure 虚拟机 - 磁盘世代与尺寸(作者提供的图片)

向右滚动以访问每小时费用。

请注意,这些成本是撰写文章时的情况。当前正在使用的虚拟机的尺寸和代数会在顶部显示。要更改虚拟机配置,请点击“调整大小”。

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

Azure 虚拟机 - 调整虚拟机配置(作者提供的图片)

考虑更改为定价较低的地区

Azure 的定价在不同地区可能有所不同。需求较低的地区可能会有稍低的定价,而需求较高和资源有限的地区可能价格稍高。规划部署时,可以使用 Azure 定价计算器比较不同地区的定价。不过,尽量选择离您较近的地区,以避免不必要的网络延迟。

例如,你可能会发现,位于东欧的某些区域比美国东海岸和西海岸的区域便宜。假设你有一些位于欧洲的用户需要访问你设置的虚拟机。为了提高性能,设置在离他们位置较近的区域的虚拟机可能更有意义,而不是在美国区域设置虚拟机。然而,需要注意的是,在确定虚拟机设置的最佳区域时,可能需要考虑数据传输规定和限制。此外,还需要考虑的是,随着区域距离当前位置的远离,网络延迟可能增加,从而导致响应时间变慢。

总结

在这篇文章中,我们讨论了 4 种策略,以减少 Azure 账单上的成本,同时不影响现有/新工作负载的效率。所有这些策略都可以通过 Azure 门户无缝实现,无需使用编程方法。理解你的工作负载需求和处理需求是管理成本效益环境的关键。同时,平衡成本节省与性能和数据传输规定等因素同样至关重要。通过应用这些技巧,你可以优化 Azure VM 性能,同时有效管理成本,确保资源的高效利用,实现你期望的结果。

在探索性数据分析中简化重复任务

原文:towardsdatascience.com/streamlining-repetitive-tasks-during-exploratory-data-analysis-46a40fe1d588

数据科学中的自动化

邀请你识别你的重复 EDA 任务,并创建一个自动化工作流,通过一个示例工具进行说明。

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

·发布于 Towards Data Science ·7 min read·Oct 24, 2023

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

图片来源:作者 (DALL-E 生成)

编程原则:自动化单调的任务

人们常说懒惰的程序员是最好的程序员。然而,更准确的说法是,那些没有耐心处理重复工作流的程序员会投入前期时间来自动化他们能自动化的所有任务,以避免这些任务。简而言之,最好的程序员不会耐心重复单调的任务——他们会自动化它们。熟练的程序员之所以“懒惰”,是因为他们在前期投入时间创建工具,以便在未来节省精力。这可能意味着学习键盘快捷键、创建自定义模块或寻找聪明的软件来自动化工作流。

在一篇标题为“为什么优秀的程序员懒惰和愚蠢”的文章中,Philipp Lenssen 说道:

“只有懒惰的程序员才会避免编写单调、重复的代码——从而避免冗余,这对软件维护和灵活重构来说是敌人 […] 为了让一个懒惰的程序员成为一个优秀的程序员,他(或她)也必须在学习如何保持懒惰时非常不懒惰——也就是说,哪些软件工具使他的工作更轻松,哪些方法可以避免冗余,以及他如何使自己的工作能够被轻松维护和重构。”

没有人喜欢枯燥单调的任务,如果有人发现自己在项目中重复相同的功能,这种总体的沮丧感就会开始缠绕他们,并低声耳语,“将它们打包成模块。

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

图片来源:作者

EDA 的重复性质

这些低语在我探索性数据分析阶段确实出现了。

探索性数据分析(EDA) 涉及使用统计技术和可视化来研究数据、理解其结构、识别模式,并检测任何不规则性或异常值。通常,对于新的数据集需要相同的分析和可视化,因此 EDA 可以大大受益于自动化。

完全自动化的限制

然而,在之前的尝试中,我每次都被阻碍,因为完全自动化受到了每个数据集独特挑战的限制,例如确定编码策略和确保数据类型正确。数据清洗过程与数据分析之间的相互作用是重复的,因此,很难完全标准化。

模块化方法

为了解决这个限制,我创建了一个工具,假设数据已经经过了最小处理并具有正确的数据类型。它还需要定义数值列、分类列和目标列(假设我们在处理分类任务)。

它包含了什么?

  • 数值和分类数据的高级统计

  • 统计显著性测试

  • 相关性热图

  • 类别平均值

  • 数据分布可视化

该函数还提供了可选参数的灵活性,以启用或禁用上述任何功能。

本文旨在展示创建定制化 EDA 工具的价值。虽然示例侧重于自动化摘要和可视化,但关键是识别您在重复 EDA 工作中的痛点,并将您自己的重复工作流程编码化。我将重点展示工具的关键功能和示例输出,而不是包含完整代码。

数据集

数据集已上传到 Kaggle,目的是研究哪些因素可能预测患者是否会被诊断为中风。

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

数据集的五个随机样本观察。作者提供的图片。

轻度预处理和特征工程

我开始了这个过程:

  • 从“胆固醇水平”中提取 HDL 和 LDL 胆固醇值

  • 为每个症状生成二进制指示符列

  • 通过标签编码将分类列和目标列转换为数值代码

# Define a function to extract values from a column and convert to integer
def extract_and_convert(column, prefix):
    return column.str.extract(f'{prefix}(\d+)')[0].astype(int)

# Extract HDL and LDL values and add them as new columns
df['HDL'] = extract_and_convert(df['Cholesterol Levels'], 'HDL:')
df['LDL'] = extract_and_convert(df['Cholesterol Levels'], 'LDL:')
# List of unique symptoms
unique_symptoms = ['Difficulty Speaking', 'Headache', 'Loss of Balance', 'Dizziness',
                   'Confusion', 'Seizures', 'Blurred Vision', 'Severe Fatigue',
                   'Numbness', 'Weakness']

# Create binary columns for each unique symptom indicating its presence in 'Symptoms'
df[unique_symptoms] = df['Symptoms'].str.contains('|'.join(unique_symptoms))
# Convert categorical columns to numerical codes using label encoding
df[categorical_columns] = df[categorical_columns].apply(lambda x: pd.factorize(x)[0])

# Convert the target variable to numerical codes using label encoding
df[target] = pd.factorize(df[target])[0]

轻度预处理数据的示例

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

特征工程后数据集的 5 个随机样本观察。作者提供的图片。

从这里,我需要采取两个步骤:

  1. 定义数值列、分类列和目标列

  2. 运行 summary() 并输入我希望看到的函数

Summary()

定义数值列、分类列和目标列

# Define numerical columns 
numerical_columns = ['age', 'bmi', 'glucose', 'stress', 'bp', 'hdl', 'ldl', ]

# Define categorical columns
categorical_columns = ['gender', 'hypertension', 'heart_dis', 'married', 'work', 'residence',
                       'smoker', 'alcohol', 'fitness', 'stroke_history', 'family_stroke_history',
                       'diet', 'speech', 'headache', 'balance', 'dizziness', 'confusion',
                       'seizures', 'vision', 'fatigue', 'numbness', 'weakness']

# Define target column
target = 'diagnosis'

在这篇文章中,我包含了更大的工具summary(),并排除了辅助函数:calculate_entropy()statistical_tests()plot_distribution_plots()plot_correlation_heatmap()calculate_categorical_summarycalculate_numerical_summary()

Summary() 实现

def summary(df: pd.DataFrame, 
            numerical_columns: list, 
            categorical_columns: list, 
            target: str,
            categorical_summary: Optional[bool] = True, 
            numerical_summary: Optional[bool] = True,
            perform_tests: Optional[bool] = True, 
            plot_corr_heatmap: Optional[bool] = True,
            calculate_cat_averages: Optional[bool] = True, 
            plot_distribution: Optional[bool] = True) -> None:
    """
    Generate a summary of data exploration tasks.
    """
    df_numerical = df[numerical_columns]
    df_categorical = df[categorical_columns]

    # Join numerical and categorical columns together
    df_joined = df_numerical.join(df_categorical)
    df_joined[target] = df[target]

    if categorical_summary:
        print('\nCATEGORICAL SUMMARY')
        categorical_summary = calculate_categorical_summary(df_categorical)
        display(categorical_summary.round(2))

    if numerical_summary:
        print('\nNUMERICAL SUMMARY')
        numerical_summary = calculate_numerical_summary(df_numerical)
        display(numerical_summary.round(2))

    if perform_tests:
        print('\nSTATISTICAL TESTS')
        df_summary = statistical_tests(df, categorical_columns, numerical_columns, target)
        display(df_summary.round(2))

    if plot_corr_heatmap:
        plot_correlation_heatmap(df_joined)

    if calculate_cat_averages:
        for col in categorical_columns:
            display(df_joined.groupby(col).mean())

    if plot_distribution:
        plot_distribution_plots(df, categorical_columns + [target], numerical_columns)

类别和数值汇总

该工具生成两个统计汇总——一个用于类别变量,一个用于数值变量。

类别汇总提供了对每个类别的高层次洞察,包括:

  • 唯一值的数量

  • 最频繁的值及其频率

  • 缺失值的百分比

  • 熵——分布的随机性测量

数值汇总计算常见的描述性统计数据,如:

  • 唯一值的数量

  • 缺失值的百分比

  • 异常值的数量

  • 集中趋势测量(均值,中位数)

  • 离散度测量(标准差,最小/最大)

这种分解作为对类别和数值数据分布及完整性的快速评估。这些汇总有效地指出了需要更深入探索的领域,如显著的缺失数据或重要的异常值。总体而言,它们提供了数据集基本特征的全面快照。

例如,下文中可以明显看出血压数据中有四个异常值,其中一半的人群有中风病史,75%的患者表现出高血压。

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

图片由作者提供。

统计测试

统计测试汇总包括统计测试结果,用于评估每个特征与目标变量之间的关系。该工具对类别变量运行卡方检验,对数值变量运行双尾 t 检验,以评估每个特征与目标之间的关系。

然而,这些测试有其局限性。它们检测线性相关性,但可能忽略非线性关联或变量之间的复杂交互。结果提供了识别潜在预测特征的起点,但需要进一步分析来揭示细致的关系。因此,自动化测试加速了初步特征筛选,但应结合更深入的技术,如多变量建模和集成方法,以获得进一步的见解。

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

图片由作者提供。

相关热图

这种可视化突出了数值变量、序数变量和目标变量之间的斯皮尔曼相关性。选择斯皮尔曼相关性是因为它在捕捉各种类型关系方面更具鲁棒性。与皮尔逊相关性不同,斯皮尔曼的相关性是非参数的,适用于序数、类别或非线性关系。

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

斯皮尔曼相关热图。图片由作者提供。

图示

对于分布可视化,summary()将返回类别变量的条形图以及数值变量的直方图和箱型图。分布可视化可以揭示数据应如何分离和处理不同,并可能突出质量保证(QA)问题或异常。

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

类别数据的条形图。图片作者提供。

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

直方图、箱型图以及不包含异常值的箱型图用于数值数据。图片作者提供。

总结

本文展示了一个以自动生成统计摘要、可视化和基本特征分析为重点的示例 EDA 工具。虽然不全面,但它允许快速探索新数据集,并揭示洞察以指导更有针对性的分析。通过一些定制,这些工具可以适应不同领域或业务背景的典型探索性工作流程。

关键在于识别流程中的冗余,并提前花时间将工作流程编码化。这会随着时间的推移积累,使你能够将认知资源集中在更高价值的领域,如领域知识、特征工程和建模。简而言之——创建你的工具,自动化重复工作,让自动化处理繁重的工作,以便你可以专注于艺术。

精简无服务器 ML 推理:释放 Candle 框架在 Rust 中的力量

原文:towardsdatascience.com/streamlining-serverless-ml-inference-unleashing-candle-frameworks-power-in-rust-c6775d558545?source=collection_archive---------4-----------------------#2023-12-21

使用 Hugging Face 的新 Candle 框架构建一个精简且强大的模型服务层,用于向量嵌入和搜索

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

·

关注 发表在 Towards Data Science · 14 分钟阅读 · 2023 年 12 月 21 日

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

图片由 Clay Banks 提供,发布于 Unsplash

1. 介绍

在过去十年中,AI 研究和工具的惊人进展催生了更准确、更可靠的机器学习模型,以及使将 AI 功能集成到现有应用程序中变得越来越简单的库和框架。

然而,在要求高的生产环境中,推理规模仍然是一个相当大的挑战。例如,假设我们有一个简单的搜索服务,它接收几个关键词,然后使用语言模型将其嵌入到向量中,并在某个向量数据库中搜索类似的文本或文档。这是一个相当流行的用例,也是 RAG 架构的核心部分,RAG 架构通常用于将生成型 AI 应用于特定领域的知识和数据。

从本身来看,这似乎是一个相对简单的实现用例。我们可以使用许多开源语言模型和模型中心,几行代码就可以用作嵌入模型。如果我们进一步假设需要存储和查询的向量数量相对适中(例如少于 1M),那么有很多简单的向量存储和搜索选项:从纯内存存储到数据库如 Postgres、Redis 或 Elastic。

但,如果我们的服务需要每秒处理数千或数十万的请求怎么办?如果我们需要在每个请求上保持相对较低的 — 毫秒级 — 延迟呢?如果我们需要快速扩展以应对请求的高峰怎么办?

尽管我们的使用案例确实相当简单,但规模和负载要求无疑使其成为一项挑战。可扩展的高吞吐量系统通常基于多个小而高效的二进制文件实例,这些实例可以快速启动、扩展并处理请求。在机器学习系统,尤其是深度学习的背景下,这带来了挑战,因为常见的库通常比较笨重,部分原因是大多数库是用 Python 实现的,而 Python 在要求高的环境中扩展性较差。

因此,面对这些挑战时,我们要么选择使用一些付费服务平台来处理规模问题,要么不得不使用多种技术创建自己的专用服务层。

针对这些挑战,Hugging Face 引入了Candle框架,它被描述为“一个注重性能和易用性的 Rust 轻量级 ML 框架”。Candle 使我们能够在 Rust 中构建稳健且轻量的模型推理服务,使用类似 torch 的 API。基于 Candle 的推理服务将能够轻松扩展、快速启动,并以极快的速度处理请求,使其更适合于旨在应对规模和弹性挑战的云原生无服务器环境。

本帖的目的是展示如何使用 Candle 框架实现之前描述的流行用例,从头到尾。我们将深入探讨基于 Candle 和 Axum(作为我们的 Web 框架)的相对简单但强大的向量嵌入和搜索 REST 服务实现。我们将使用一个特定的新闻头条数据集,但代码可以很容易扩展到任何文本数据集。

这将是一个非常实践和实用的帖子。第二部分展示了我们服务的主要设计或流程,以及我们将开发和使用的相关组件。第三部分聚焦于 Candle 框架,并展示如何使用 Bert 模型实现向量嵌入和搜索功能。第四部分展示了如何使用 Axum 将模型推断功能封装在 REST Web 服务中。第五部分解释了如何创建我们服务所需的实际嵌入和工件。第六部分总结。

2. 高级服务设计

我将从我们要实现的推断服务的主要构建块开始。主要要求是创建一个 HTTP REST 端点,该端点将接收一个由几个关键字组成的文本查询,并响应与搜索查询最相似的前 5 条新闻头条。

对于这个任务,我们将使用Bert作为语言模型,因为它通常在文档嵌入任务中表现良好。在一个离线批处理过程中(将在第五部分中解释),我们将使用 Bert 来嵌入大约 20K 条新闻头条或文档,并为每个头条创建一个向量嵌入,服务将使用这些嵌入来搜索匹配项。嵌入和文本头条将被序列化为二进制格式,每个服务实例将加载这些格式以服务查询请求。

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

作者提供的图片

如上所示,在接收到带有搜索查询的请求后,服务将首先使用语言模型将搜索查询嵌入到一个向量中。接下来,它将搜索预加载的embedding以找到 N 个最相似的向量(每个向量代表一个新闻头条)。最后,它将使用最相似向量的索引通过映射文件提取它所代表的实际文本头条。

我们将通过创建一个名为BertInferenceModel的模块或 rust 库来实现这一点,该模块将提供主要功能:模型加载、句子嵌入和向量搜索。该模块将被一个使用 Axum Web 框架实现的 REST 服务使用。下一节将专注于实现该模块,而后续的部分将专注于 Web 服务本身。

请注意,接下来的部分包括许多代码示例,但为了清晰起见,它们仅展示了实现的主要功能。有关解决方案的完整实现,请参阅下面链接的配套 git 仓库。

3. 使用 Candle 进行模型服务和嵌入

本节将重点介绍将作为 Candle 库 API 上的抽象层的模块的实现。我们将实现一个名为BertInferenceModel的结构体,该结构体包含三个主要功能:模型加载、推理(或句子嵌入)以及使用余弦相似度进行简单的向量搜索。

pub struct BertInferenceModel {
    model: BertModel,
    tokenizer: Tokenizer,
    device: Device,
    embeddings: Tensor,
}

BertInferenceModel 将封装我们从 Hugging Face 仓库下载的 Bert 模型和分词器,并基本上包装它们的功能。

从 Hugging Face Hub 加载模型

BertInferenceModel 将通过 load() 函数实例化,该函数将返回一个新实例的结构体,加载了相关模型和分词器,并准备进行推理任务。加载函数的参数包括我们希望加载的模型的名称和修订版(我们将使用 Bert 句子转换器)以及嵌入文件路径。

 pub fn load(
        model_name: &str,
        revision: &str,
        embeddings_filename: &str,
        embeddings_key: &str,
    ) -> anyhow::Result<Self> {}

如下加载函数代码所示,加载模型涉及创建一个包含 Hugging Face 仓库相关属性的Repo结构体(例如名称和修订版),然后创建一个API结构体以实际连接到仓库并下载相关文件(用于创建模型的模型权重使用 HuggingFace 的 safetensors 格式表示)。

api.get函数返回相关文件的本地名称(无论是下载的还是仅从缓存中读取的)。文件将只下载一次,而随后的api.get调用将仅使用缓存版本。我们使用分词器配置文件实例化一个分词器结构体,并使用权重文件(以 safetensors 格式)和配置文件来构建我们的模型。

在加载了模型和分词器之后,我们最终可以加载实际的嵌入文件,用于搜索匹配项。我将稍后展示如何使用相同的模型生成嵌入文件,然后将其序列化为文件。使用 HuggingFace 的 safetensors 模块将嵌入加载为 Tensor 相对简单,我们只需要文件名和保存 Tensor 时给予的密钥。

现在我们已经加载了模型和分词器,并且内存中有了嵌入向量,我们完成了对返回给调用函数的 BertInferenceModel 的初始化,可以继续实现推理方法。

句子推理和嵌入

推理函数也相当简单。我们首先使用加载的分词器对句子进行编码(第 5 行)。*encode()*函数返回一个 Encoding 结构体,该结构体具有一个 get_ids() 函数,返回一个数组或句子中单词的数值表示。接下来,我们将令牌 ID 数组包装在一个 Tensor 中,以便将其输入到我们的嵌入模型中,并使用模型的前向函数获取表示句子的嵌入向量(第 10 行)。

我们从嵌入模型中在第 12 行得到的向量维度是[128, 384]。这是因为 Bert 用大小为 384 的向量表示每个标记或词,且句子向量的最大输入长度为 128(因为我们的输入只有几个单词,所以大部分是填充)。换句话说,除了填充和其他指令标记外,我们基本上获得了每个标记或词的大小为 384 的向量。

接下来,我们需要将句子向量从大小为[128, 384]的张量压缩成一个大小为[1, 384]的单一向量,该向量将代表或捕捉句子的“精髓”,以便我们可以将其与嵌入中的其他句子进行匹配,并找到与之相似的句子。为此,并且部分因为我们处理的输入是短关键字而不是长句子,我们将使用最大池化,它本质上通过取给定张量每个维度的最大值来创建一个新向量,以捕捉每个维度中最显著的特征。正如您在下方看到的,这使用 Candle 的 API 实现起来相当简单。最后,我们使用 L2 归一化以避免偏差,并通过确保所有向量具有相同的幅度来改善余弦相似性度量。您可以在下方看到池化和归一化函数的实际实现。

测量向量相似性

尽管这与 Candle 库没有直接关系,但我们的模块也将提供一个向量搜索实用方法,该方法将接收一个向量并利用其内部嵌入以返回最相似向量的索引。

这实现得相当简单:我们首先创建一个元组集合(第 7 行),其中元组的第一个成员将表示相关文本的索引,第二个成员将表示余弦相似性评分。然后,我们遍历所有索引,测量每个与我们需要匹配的给定向量之间的余弦相似性。最后,我们将(索引,相似性评分)的元组添加到集合中,对其进行排序,并返回前 N 个请求的结果。

4. 嵌入和搜索 Web 服务

现在我们有了一个封装主要模型功能的结构体,我们需要将其封装在一个 REST 服务中。我们将创建一个 REST 端点,包含一个 POST 路由,该路由将接收 JSON 有效负载中的几个关键字,并返回在预加载嵌入中的最相似向量的索引。根据请求,服务将把关键字嵌入到一个向量中,搜索其内存中嵌入的相似性,并返回最相似向量的索引。该服务将使用索引在文本映射文件中找到相应的标题。

我们将使用优秀的 Axum web 框架来实现这个服务。相关代码大部分是典型的 Axum 模板代码,所以我不会详细讲解如何使用 Axum 创建 REST 端点。与许多 web 框架一样,构建 REST 端点通常涉及创建一个 Router 并在某个路由上注册一个处理函数来处理请求。然而,ML 模型服务层具有额外的复杂性,即管理模型本身的状态和持久性。模型加载可能在性能上很昂贵,因为它涉及加载模型文件的 IO 操作(无论是从 Hugging Face 的仓库还是本地)。同样,我们需要找到一种方法来缓存和重用模型以应对多个请求。

为了满足这些要求,Axum 提供了应用状态功能,我们可以用来初始化和持久化任何我们想要注入到每个请求上下文中的资产。首先让我们逐行查看服务的整个初始化代码,看看它是如何工作的。

每个服务实例都会创建并加载一个模型包装器,然后缓存它以供每个接收到的请求重用。在第 3 行,我们通过调用*load()*函数来创建模型包装器,以引导并加载模型。除了从 HF 加载的 Bert 模型的名称和版本,我们还需要指定嵌入文件的位置,该文件被加载到内存中以便搜索相似的向量,以及在创建嵌入时使用的密钥。

除了实际的模型,我们还需要缓存映射文件以供每个请求重用。服务在使用模型嵌入关键词后,会在其嵌入文件中搜索最相似的向量,然后返回它们的索引。服务接着使用映射文件提取与最相似向量的索引对应的实际文本。在更稳健的生产系统中,服务会从某个快速访问的数据库中获取实际文本,但在我们的案例中,从存储在文件中的预加载字符串列表中读取就足够了。在第 10 行,我们加载了之前保存为二进制文件的列表。

现在我们有两个需要缓存和重用的资产——模型(包装器)和映射文件。Axum 使我们能够使用Arc,即线程安全的引用计数指针,每个请求都会共享。正如第 15 行所示,我们在包含模型包装器和映射文件的元组周围创建了一个新的 Arc。在第 17–19 行,我们创建了一个新的 HTTP 路由来处理每个请求的函数。

 let shared_state = 
      Arc::new((bert_model, text_map));

   let app = Router::new()
        .route("/similar", post(find_similar))
        .with_state(shared_state);

为了缓存元组并使其对每个请求可用,我们使用*with_state(state)*函数将其注入到相关的请求上下文中。我们来看看具体是如何操作的。

处理请求

我们的服务将处理 HTTP POST 请求,这些请求包含以下有效负载,这些有效负载包括关键词和我们想要接收的相似向量或标题的数量。

{
    "text": "europe climate change storm",
    "num_results":5
}

我们将实现处理函数的相应请求和响应结构体,Axum 将在需要时处理序列化和反序列化。

#[derive(Deserialize)]
struct ReqPayload {
    keywords: String,
    num_results: u32,
}

#[derive(Serialize)]
struct ResPayload {
    text: Vec<String>,
}

接下来,我们可以进入处理函数本身。处理函数将接受 2 个参数:我们之前初始化的应用程序状态(Axum 将负责将其注入到每个函数调用中),以及我们之前定义的请求结构体。

处理每个请求将包括 4 个主要阶段,这些阶段现在应该已经很清楚了。在第 5 行,我们首先提取一个指向状态元组的引用,该元组持有对模型和映射文件的引用。在第 6 行,我们使用模型将关键词嵌入到一个向量中。接下来,在第 9 行,我们搜索 N 个最相似的向量。*score_vector_similarity()*函数返回一个由元组组成的向量,每个元组包含一个索引和余弦相似度分数。最后,我们遍历这些索引元组,从映射文件中提取对应索引的字符串,并将其封装到响应有效负载结构中。

然后……我们就可以开始了!虽然这可能并没有具体说明太多,但我在我的 Mac 上进行了测试,使用了大约 20K 向量的嵌入,并获得了 100ms 的良好平均响应时间。对于基于 Bert 的向量嵌入和向量搜索来说,这算是不坏。

curl -s -w "\\nTotal time: %{time_total}s\\n" \ 
 -X POST http://localhost:3000/similar \
 -H "Content-Type: application/json" \
 -d '{"text": "self driving cars navigation", "num_results": 3}' | jq
{
  "text": [
    "Item:Stereo Acoustic Perception ... (index: 8441 score:0.8516491)",
    "Item:Vision-based Navigation of ... (index: 7253 score:0.85097575)",
    "Item:Learning On-Road Visual .....  (index: 30670 score:0.8500275)"
  ]
}

Total time: 0.091665s

(这个示例是使用在 Arxiv 论文摘要数据集上生成的嵌入创建的。实际数据集可以在这里以公共领域许可证获取。)

5. 生成嵌入

在我们结束之前,还有一个最后的组件需要覆盖。到目前为止,我们假设了一个嵌入文件的存在,在其中我们搜索相似的向量。然而,我还没有解释如何创建嵌入文件本身。

请记住,在上一节中创建的结构体*— BertInferenceModel*,已经包含了一个将一组关键词嵌入到向量中的函数。当我们创建一个需要嵌入多个关键词集的函数时,我们只需将它们作为批量处理即可。

我们使用BertInferenceModel的主要区别在于使用 tokenizer 的encode_batch函数而不是encode,前者接受一个字符串向量而不是单个字符串。然后,我们将所有向量堆叠成一个单一的张量,并将其输入到模型的*forward()*函数中,就像我们处理单个向量嵌入时一样(你可以在下面链接的辅助仓库中查看函数的完整源代码)。

一旦我们拥有能够嵌入多个字符串的函数,嵌入生成器本身就相当简单。它使用 rayon crate 来并行处理文本文件的嵌入,然后将结果堆叠在一起,创建一个单一的张量。最后,它使用 safetensors 格式将嵌入写入磁盘。嵌入是这个管道中的重要资产,因为它需要被复制到每个服务实例。

现在我们可以得出结论 😃

6. 结论

在机器学习工程中,最大的挑战之一是大规模推断。人工智能绝非轻量级,因此,扩展推断工作负载往往是一项非常昂贵或过度设计的痛苦挑战。这正是 Hugging Face 的 Candle 库试图解决的难题。它使用类似 Torch 的 Rust API,使我们能够创建一个精简且快速的模型服务层,能够轻松扩展并在无服务器环境中运行。

在这篇文章中,我展示了如何使用 Candle 创建一个端到端的模型推断层,能够处理向量嵌入和搜索请求。我解释了如何将 Bert / sentence transformers 模型包装成一个内存占用小的库,并在基于 Axum 的 REST 服务中使用它。

Hugging Face 的 Candle 库的真正价值在于其能够弥合强大机器学习能力与高效资源利用之间的差距。通过利用 Rust 的性能和安全特性,Candle 为更可持续和成本效益高的机器学习解决方案铺平了道路。这对那些希望在不增加开销的情况下大规模部署 AI 的组织特别有利。我希望借助 Candle,我们将看到一波不仅性能高效,而且更轻量且适应各种环境的机器学习应用。

一些关于 Candle 的资源

本文的所有源代码可以在我的 GitHub 仓库中找到 这里

Streamlit 和 MongoDB:在云端存储你的数据

原文:towardsdatascience.com/streamlit-and-mongodb-storing-your-data-in-the-cloud-c28663313ade

Streamlit Cloud 没有本地存储,因此在应用程序终止时创建的数据会丢失——除非你使用类似 MongoDB 的第三方存储

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

·发表于 Towards Data Science ·12 分钟阅读·2023 年 8 月 25 日

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

经典 NoSQL 数据库——照片由 Jan Antonin Kolar 提供,Unsplash

Streamlit 允许你将公共应用程序部署到他们的云端,免费提供,但你在本地创建的任何文件或数据库将在应用结束时消失。这可能不是你想要的行为,因此我们将探讨使用 MongoDB 的解决方案。

对于许多应用程序来说,丢失这些数据并不成问题。例如,如果你设计了一个从外部源读取数据的仪表板,那么你生成的任何数据都可能是临时的,仅在应用运行期间需要。

但正如我在为文章开发应用程序时所提到的,Simple Surveys with Streamlit,如果应用程序本身生成需要存储的数据,这就不那么简单了。在那个应用程序中,我将数据存储在本地文件中,但在基于云的部署中,这些数据将在应用停止运行时消失——正确的解决方案是使用外部数据存储。

我们将看看如何通过 MongoDB 实现这一点,但也有其他选择。

有哪些选择?

在 Streamlit 文档中,有关于连接各种数据库和云存储供应商的指南。它们基本上分为三个领域:数据桶,例如 AWS S3 和 Google Cloud Storage,你可以在其中存储任何东西;SQL 数据库,如微软的 SQL Server、MySQL、PostgreSQL;以及 NoSQL 数据库,Firestore 和 MongoDB 就是其中的例子。对于每种类型,你显然需要访问托管该特定数据库的服务器。

坦白说,我不是 SQL 的最大粉丝。对我来说,SQL 代码和 Python 之间的脱节让人感到不适。(话虽如此,我确实欣赏 SQL 的强大功能和便利,并且在这里这里这里写过相关内容。)

但 NoSQL 数据库如 MongoDB 感觉更符合 Python 的工作方式。

我相信关于速度、效率、易用性、安全性等各种争论都有。但我不打算讨论这些。我将使用 MongoDB,因为这是我的个人喜好和偏见,你可以自己决定它是否是一个好的选择。

调查应用程序

我将使用我开发的一个应用程序版本来演示在 Streamlit 中构建简单调查。我有意编写它以便于移植到不同的数据存储,因此所有的数据存储代码都在一个库中。其目的是,为了从使用本地数据存储转到基于云的数据库,你只需重新编写库即可。

但首先,我们需要访问 MongoDB 数据库。

MongoDB Atlas

你可以下载 MongoDB 并在自己的服务器上运行一个实例。或者,你可以使用像 MongoDB Atlas 这样的托管服务。我们将使用后者。

MongoDB Atlas 是搜索中出现的第一个托管服务——虽然还有其他服务,但我不知道有哪个提供免费层,因此我们将使用这个。作为一种免费增值服务,你可以从零开始,这几乎满足了你对简单(或者实际上不那么简单)应用程序的所有需求,但限制你存储数据为 0.5 GB。

要使用 MongoDB,你需要注册一个帐户(使用上面的链接),并且还应阅读他们网页上资源标签中的全面入门指南。

一旦设置好,你将拥有创建连接字符串的信息,如下所示:

"mongodb+srv://<user>:<password>@<cluster-url>?retryWrites=true&writeConcern=majority"

替换 <user><password><cluster-url> 为你自己的详细信息,并将其保留为 Streamlit 密钥。

一个 MongoDB 数据库

MongoDB 由 集群数据库 组成。数据库内有 集合(有点像表),集合内有 文档(以类似 JSON 的格式存储的实际数据)。

为了本教程的方便,我创建了一个数据库和两个集合。这些集合将存储构成调查的问题以及用户生成的结果。我将它们命名为 survey1results1——我知道这并不特别有创意。

在结果收集部分,每个文档将代表一个问题。在结果表中,每个文档将代表对调查中所有问题的单独回答。

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

调查 数据库 截图

点击其中一个集合名称将显示文档(如果有的话)。

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

调查 1 集合中的问题

如你所见,这些条目非常类似于 Python 字典。注意 _id 字段:这是一个唯一标识符,对于 MongoDB 文档来说是必需的;你可以在创建文档时指定它,否则 MongoDB 会自动为你创建一个。

应用程序

你可以在原始文章中阅读实现的详细信息。这里,我们将专注于数据库代码。(在本文发布后不久,将会有一个指向我网页上所有代码的链接。)但让我们快速回顾一下应用程序的功能。

原始应用程序由三个页面组成:

  • 第一个页面允许用户通过指定单独的问题并存储它们来创建一个简单的问卷。

  • 第二个页面向用户展示问卷,收集响应并将其添加到之前的响应中。

  • 第三个页面展示结果;它读取响应并对其进行一些简单的分析 —— 绘制概览柱状图,并让用户选择一个问题以更详细地查看该问题的响应。数据也可以作为 CSV 文件下载。

每个页面使用一个库,DButils,来访问数据。在原始应用程序中,数据存储为 JSON 文件。

为了保持简单,我将专注于问卷的展示和收集的数据的展示。这意味着我们需要对数据库进行读写操作。(我的想法是,创建问卷可能最好在本地完成,并将结果存储在文件中,之后再将其完整上传到 MongoDB —— 因此,本文将不涉及应用程序的这一方面。)

数据库库

原始库使用了一些辅助函数,但该版本应用程序使用的函数有:

  • get_survey() — 这个函数用于检索整个问卷

  • append_results(value) — 这个函数将新的响应添加到现有响应中

  • get_results() — 这个函数用于检索所有的响应

对于 MongoDB 版本,我们需要一点前置准备。

import streamlit as st
from pymongo.mongo_client import MongoClient

DB = "survey"
SURVEY_KEY = "survey1"
RESULTS_KEY = "results1"

uri = st.secrets['mongoURI']
client = MongoClient(uri)

我们显然需要导入 Streamlit;我们还需要从 pymongo 库中导入 MongoClient。然后,我们设置一些便利常量,将数据库和集合名称硬编码到代码中。这当然使得库特定于这个应用程序,如果我们希望在不同的应用程序中再次使用该库,可能会避免这种情况。我们更倾向于在应用程序中设置这些值,并将其传递给库。

变量 uri 是我们之前创建的连接字符串,通过它我们创建一个 MongoDB 客户端,从中可以访问我们的数据。

读取和写入数据非常简单。首先,你需要指定数据库以及该数据库中的集合。然后,你可以使用 find() 函数来读取数据,使用 insert_one()insert_many() 函数将数据添加到集合中。

我们将在这里说明读取和写入函数的简单用法,但要想充分利用 MongoDB,你需要查阅他们网站上的文档,例如 如何在 MongoDB 中使用 Python

读取数据

要从 MongoDB 集合中读取所有数据,我们使用 find() 函数。下面的代码将从我们其中一个集合中读取所有数据——key 参数应为 SURVEY_KEYRESULTS_KEY

def get(key):
    coll = db[key]
    item_details = coll.find()
    return list(data)

find() 返回的值是一个游标,我们可以遍历它以检索数据。然而,由于我们在这里处理的数据量很小,将其转换为 Python 列表以便所有数据都保存在内存中是完全可以接受的。

但这并不是我们想要的。正如你会记得的,每个集合中的文档都有一个由 MongoDB 自动提供的唯一标识符。对于这个应用程序,我们不需要,也确实不想要那个属性。因此,让我们更仔细地查看这个函数。

MongoDB 查询和过滤器

正如你所料,我们可以对 MongoDB 数据库进行查询。为了方便这一点,find() 函数可以接受修改其行为的参数。每个参数都是可选的,正如我们看到的,通过不指定这些参数,我们检索到所有的数据。

下面我们可以看到一个小调查的片段。这是集合中所有文档的数组,每个文档由 id 字段加上三个调查问题和每个问题的回答组成。

[
  {
    "_id": "ObjectId('64de4b2425de44afbff94ba5')",
    "How many years of programming experience do you have?": "less than 1",
    "What programming language do you use most?": "Python",
    "Towards Data Science is one of the most useful publications on Medium": "Strongly agree"
  },
  {
    "_id": "ObjectId('64de4b3125de44afbff94ba6')",
    "How many years of programming experience do you have?": "1 to 5",
    "What programming language do you use most?": "Python",
    "Towards Data Science is one of the most useful publications on Medium": "Agree"
  },

...

]

如果我们只需要对某个问题的回答,可以添加一个查询参数。在下面的代码中,我们指定我们想要包含对问题“你使用最多的编程语言是什么?”的回答为 ‘R’ 的文档。该参数以 Python 字典的形式提供,你可以在代码下方立即看到响应。

def get(key):
    coll = db[key]
    item_details = coll.find({'What programming language do you use most?':'R'})
    return list(item_details)
[
  {
    "_id": "ObjectId('64df43224c943462625ec464')",
    "How many years of programming experience do you have?": "5 to 10",
    "Towards Data Science is one of the most useful publications on Medium": "Disagree",
    "What programming language do you use most?": "R"
  },
  {
    "_id": "ObjectId('64df43324c943462625ec465')",
    "How many years of programming experience do you have?": "1 to 5",
    "Towards Data Science is one of the most useful publications on Medium": "Neither agree not disagree",
    "What programming language do you use most?": "R"
  }
]

正如你所预期的,返回值是与查询匹配的文档集合——它相当于 SQL 中的 SELECT * WHERE …

这为我们提供了 MongoDB 查询如何工作的有用见解,但并未解决省略 id 字段的问题。为此,我们需要一个第二个参数。

第二个参数也是 Python 字典的形式,并充当过滤器,例如 {'_id':False}。这告诉 MongoDB 我们不需要 id 字段。我们可以在此参数中指定字段列表,每个字段标记为 TrueFalse,取决于我们是否希望包含它们。

如果我们将这个第二个参数添加到之前的查询中,结果将是这样:

[
  {
    "How many years of programming experience do you have?": "5 to 10",
    "Towards Data Science is one of the most useful publications on Medium": "Disagree",
    "What programming language do you use most?": "R"
  },
  {
    "How many years of programming experience do you have?": "1 to 5",
    "Towards Data Science is one of the most useful publications on Medium": "Neither agree not disagree",
    "What programming language do you use most?": "R"
  }
]

与之前相同的条目,但省略了 id 字段。

好的,让我们回到我们真正想要的,即所有的文档,但省略 id 字段。我们仍然需要指定两个参数,但第一个参数需要告诉 MongoDB 我们想要所有文档,我们通过传递一个空字典来实现。

def get(key):
    coll = db[key]
    item_details = coll.find({},{'_id':False})
    return list(item_details)

这给了我们想要的结果。

[
  {
    "How many years of programming experience do you have?": "less than 1",
    "What programming language do you use most?": "Python",
    "Towards Data Science is one of the most useful publications on Medium": "Strongly agree"
  },
  {
    "How many years of programming experience do you have?": "1 to 5",
    "What programming language do you use most?": "Python",
    "Towards Data Science is one of the most useful publications on Medium": "Agree"
  },
  {
    "How many years of programming experience do you have?": "more than 10",
    "What programming language do you use most?": "Python",
    "Towards Data Science is one of the most useful publications on Medium": "Strongly agree"
  },
  {
    "How many years of programming experience do you have?": "5 to 10",
    "What programming language do you use most?": "Julia",
    "Towards Data Science is one of the most useful publications on Medium": "Neither agree not disagree"
  },
  {
    "How many years of programming experience do you have?": "more than 10",
    "What programming language do you use most?": "other",
    "Towards Data Science is one of the most useful publications on Medium": "Agree"
  },
  {
    "How many years of programming experience do you have?": "5 to 10",
    "Towards Data Science is one of the most useful publications on Medium": "Disagree",
    "What programming language do you use most?": "R"
  },
  {
    "How many years of programming experience do you have?": "1 to 5",
    "Towards Data Science is one of the most useful publications on Medium": "Neither agree not disagree",
    "What programming language do you use most?": "R"
  },
  {
    "How many years of programming experience do you have?": "more than 10",
    "Towards Data Science is one of the most useful publications on Medium": "Strongly agree",
    "What programming language do you use most?": "Python"
  },
  {
    "How many years of programming experience do you have?": "1 to 5",
    "Towards Data Science is one of the most useful publications on Medium": "Agree",
    "What programming language do you use most?": "Julia"
  }
] 

当我们读取调查问题时,我们想做的正是同样的事情——返回所有文档,但不包含 id 字段——因此这个函数用于读取两个数据库。我们只需要提供正确的键,无论是 SURVEY_KEY 还是 RESULTS_KEY

追加数据

在这个版本的应用中,我们仅向结果集合中添加数据。具体方法如下,使用 insert_one() 函数完成。

def append_results(value):
    coll = db[RESULTS_KEY]
    coll.insert_one(value)

再次,我们选择具有适当键的集合,并添加以 Python 字典列表形式表示的调查问题及其每个回答的值。正如我们之前提到的,MongoDB 会自动添加唯一的 id。我们最终得到的集合与我们上面看到的类似。

如果你需要向集合中追加一组文档,insert_many() 函数可以为你完成这项工作。

代码

库的完整代码如下——实际上非常简洁!

import streamlit as st
from pymongo.mongo_client import MongoClient

DB = "survey"
SURVEY_KEY = "survey1"
RESULTS_KEY = "results1"

uri = st.secrets['mongoURI']
client = MongoClient(uri)
db = client[DB]

# Get functions
def get(key):
    coll = db[key]
    item_details = coll.find({},{'_id':False})
    return list(item_details)

def get_survey(key=SURVEY_KEY):
    return get(key)

def get_results(key=RESULTS_KEY):
    return get(key)

# Append results function
def append_results(value):
    coll = db[RESULTS_KEY]
    coll.insert_one(value)

结论

我希望你能看到,使用 MongoDB 是相当简单的,并且为 Streamlit 应用中的持久存储问题提供了一个简洁的解决方案。我们本可以使用托管的 SQL 解决方案,但正如我之前提到的,NoSQL 解决方案在我看来更符合 Python 的处理方式——将 Python 列表映射到 MongoDB 集合是完全自然的。

不过,我应该补充一点:本文中的代码是在 Streamlit 引入新的 st.experimental_connection() 特性之前编写的,这种方法或其他基于类的解决方案可能会为生产代码提供更好的前景,特别是当 MongoDB 将被用于多个不同的项目时。然而,对于像这样的临时项目,我在这里使用的方法似乎简单且足够。

感谢阅读,希望这对你有帮助。如果你对代码或我的方法有任何疑问,或有任何评论,或者只是想说“你好”,请在下面添加评论——我总是会回复。

当你阅读这篇文章时,我的网站上应该有指向完整应用代码的链接 website。在那里你可以找到其他关于 Streamlit、数据可视化、数据科学和编程的一些文章和书籍——请查看一下。

Streamlit 教程:为数据科学项目创建 Word 报告

原文:towardsdatascience.com/streamlit-tutorial-creating-word-reports-for-data-science-projects-96a749483cb3

将 python-docx 和 Streamlit 结合用于数据科学报告自动化

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

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

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

报告图像由作者使用 Midjourney 基础计划生成。

在数据相关项目的结束阶段,无论是石油物理学还是数据科学,创建报告是非常常见的。生成的报告为客户和最终用户提供了在研究过程中获得的关键结果和结论的信息,并详细说明了使用的方法。

然而,创建结构化报告可能是一个繁琐且耗时的过程,特别是在确保报告格式正确和数据以最佳方式呈现时。

本文将展示我们如何使用流行的Streamlit库,结合python-docx,来创建自动化报告过程的第一步。

python-docx库将允许我们创建一个 Microsoft Word 报告。将报告以这种格式呈现将使我们能够进行编辑,并在转换为 PDF 之前完成最后的润色。

尽管本文中的示例需要主要的手动输入,但它可以适应大语言模型的强大功能,以总结数据并创建所需的文本。

让我们开始构建一个 Streamlit Word 文档报告生成器吧。

导入库和数据

首先,我们将导入我们要使用的主要库。这些库包括Streamlitpandasmatplotlibpython-docx

import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
import docx

接下来,我们将设置 Streamlit 页面布局为宽幅。这使得应用程序能够占据浏览器窗口的整个宽度,而不是位于中间的窄列中。

st.set_page_config(layout='wide')

设置 Streamlit 用户界面(UI)

现在我们可以开始构建用户界面(UI)。

我们将从为我们的应用程序添加标题开始。

st.title('Streamlit Data Report Generator')

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

报告生成器的起始点。图片来源:作者。

为了简化操作,我们将使用 pd.read_csv() 预加载我们的数据并传入一个文件名。

df = pd.read_csv('Xeek_Well_15-9-15.csv')

本教程使用的数据集是 Xeek 和 FORCE 2020 举办的机器学习竞赛(Bormann 等,2020)中的一个子集。该数据集在 Creative Commons Attribution 4.0 International 许可证下授权。

为了使应用程序更具灵活性,我们可以添加一个文件上传器,允许用户加载他们自己的数据。

你可以在我的文章 上传和读取 Streamlit 文件 中了解更多关于如何做到这一点的信息。

使用 st.form 创建报告表单

当小部件包含在 Streamlit 应用程序中时,每次编辑或选择它们时,Streamlit 应用程序都会重新运行。为了防止这种情况,我们可以创建一个表单。

这将允许我们输入值,应用程序只会在按下按钮时运行。

我们可以使用 with st.form('report') 创建一个表单,然后添加我们想要的输入。

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

带有报告详细部分的 Streamlit 报告生成器。图片来源:作者。

我们将使用应用程序的上部来创建报告元数据。这包括报告的标题、作者、客户以及报告的日期。

这些元素中的每一个都与来自 Streamlit 的用户输入小部件相关联。

 report_title = col1.text_input("Enter report title")
    report_author = col1.text_input("Enter the report author's name")
    report_date = col2.date_input("Select a date for the report")
    report_client = col2.text_input("Enter the client's name")

为了显示表单,我们需要添加一个提交按钮。使用 st.form_submit_button() 可以完成这一点。在这里,我们可以传递一个将出现在按钮上的标签。

if st.form_submit_button('Generate'):
        generate_report(report_title)

在此之下,我放置了一个 generate_report 函数调用,我们将很快创建这个函数。目前,这将作为一个占位符。

这是目前表单的代码。

with st.form('report'):
    st.write("### Report Details")
    col1, col2 = st.columns(2, gap='large')

    report_title = col1.text_input("Enter report title")
    report_author = col1.text_input("Enter the report author's name")
    report_date = col2.date_input("Select a date for the report")
    report_client = col2.text_input("Enter the client's name")

    if st.form_submit_button('Generate'):
        generate_report(report_title)

在 Streamlit 表单中创建报告部分

报告通常由多个部分或章节组成。

为了说明如何在我们的应用程序中创建一个非常简单的部分,我们将添加一些输入,让用户输入部分标题和摘要。

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

带有部分输入框的 Streamlit 报告生成器。图片来源:作者。

在上图中,我添加了两个新的输入小部件。

一个部分标题,这是一个简单的文本输入(st.text_input),和该部分的摘要,这是一个文本区域(st.text_area)。

此外,我创建了两个新的列,以将它们与上面的列分开。如果我们想在这些表单部分之间添加任何全宽的文本/信息,这一点非常重要。

这是我们目前的表单代码:

with st.form('report'):
    st.write("### Report Details")
    col1, col2 = st.columns(2, gap='large')

    report_title = col1.text_input("Enter report title")
    report_author = col1.text_input("Enter the report author's name")
    report_date = col2.date_input("Select a date for the report")
    report_client = col2.text_input("Enter the client's name")

    sect_col1, sect_col2 = st.columns(2, gap='large')

    sect_col1.write("### Section Details")
    section_title = sect_col1.text_input("Enter section title")
    section_text_summary = sect_col1.text_area("Section Summary")

我们可以扩展此功能,以便用户可以添加多个部分。每个部分都可以被编码为在新页面上开始,使用分页符。

此外,为了使其更全面,我们可以在应用程序中生成报告的预览。

可能性非常多!

在 Word 文档中包含数据框使用 docx

表格在报告中至关重要,因为它们有助于以清晰、简单和有组织的方式展示信息。这使读者能够快速理解数据,并将其与同一或不同表中的其他数据值/类别进行比较。

为了说明在报告中包含一个表格,我们可以使用 pandas describe() 函数生成的统计摘要作为示例。

在 UI 中,我们可以添加一个多选选项,允许用户从数据框中选择列。如果我们有许多列且只对其中一些感兴趣,这将特别方便。

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

Streamlit 报告生成器允许用户从数据框中选择列。图片由作者提供。

在创建多选条目框之前,我们首先需要从数据框中获取列名,这可以通过创建一个新变量并将其分配给 df.columns 来完成。

然后我们使用 st.multiselect() 创建多选框。由于我们在处理列,因此需要调用所需的列。在这种情况下,是 sect_col2

with st.form('report'):
    st.write("### Report Details")
    col1, col2 = st.columns(2, gap='large')

    report_title = col1.text_input("Enter report title")
    report_author = col1.text_input("Enter the report author's name")
    report_date = col2.date_input("Select a date for the report")
    report_client = col2.text_input("Enter the client's name")

    sect_col1, sect_col2 = st.columns(2, gap='large')

    sect_col1.write("### Section Details")
    section_title = sect_col1.text_input("Enter section title")
    section_text_summary = sect_col1.text_area("Section Summary")

    data_features = df.columns

    sect_col2.write("### Data Summary")
    data_to_summarise = sect_col2.multiselect("Select features to include in statistical summary", 
                                              options=data_features)

    if st.form_submit_button('Generate'):
        generate_report(report_title)

接下来,我们需要创建两个函数。

第一个函数将获取我们感兴趣的特征和数据框,并生成数据的统计摘要。

def create_df_stats_summary(dataframe, features_to_include):
    sub_df = dataframe[features_to_include].copy()
    return sub_df.describe()

第二个函数要复杂一些。

由于 python-docx 不原生支持数据框,我们需要使用 docx 创建一个表格,如下所示:

def add_df_to_docx(doc, dataframe):
    # Reset the index and get the new shape
    dataframe = dataframe.reset_index()
    num_rows, num_cols = dataframe.shape

    # Add a table to the document with the necessary number 
    # of rows and columns
    table = doc.add_table(rows=num_rows + 1, cols=num_cols)

    # Add the header row
    for i, col in enumerate(dataframe.columns):
        table.cell(0, i).text = str(col)

    # Add the data rows
    for i, row in dataframe.iterrows():
        for j, value in enumerate(row):
            table.cell(i + 1, j).text = str(value)

    return table

当按钮被按下时,我们将调用这些函数。

将图表添加到 Word 文档

图表是报告的另一个重要部分。它们使我们能够简明扼要地传达大量数据。

为了说明在最终 Word 文档中创建和包含图表,我们将允许用户从数据集中选择三列。然后,这些将用于创建一个散点图,并添加到报告中。

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

在包括散点图选项后,Streamlit 报告生成器。图片由作者提供。

如上图所示,我们将在前面两个部分下方添加三个选择框。这些将添加到三个新列中,并使用 Streamlit 的 selectbox() 创建。

with st.form('report'):
    st.write("### Report Details")
    col1, col2 = st.columns(2, gap='large')

    report_title = col1.text_input("Enter report title")
    report_author = col1.text_input("Enter the report author's name")
    report_date = col2.date_input("Select a date for the report")
    report_client = col2.text_input("Enter the client's name")

    sect_col1, sect_col2 = st.columns(2, gap='large')

    sect_col1.write("### Section Details")
    section_title = sect_col1.text_input("Enter section title")
    section_text_summary = sect_col1.text_area("Section Summary")

    data_features = df.columns

    sect_col2.write("### Data Summary")
    data_to_summarise = sect_col2.multiselect("Select features to include in statistical summary", 
                                              options=data_features)

    st.write("### Scatterplot Setup")
    sub_col1, sub_col2, sub_col3 = st.columns(3)

    chart_x = sub_col1.selectbox('X axis', options=data_features)
    chart_y = sub_col2.selectbox('Y axis', options=data_features)
    chart_z = sub_col3.selectbox('Z axis', options=data_features)

    if st.form_submit_button('Generate'):
        generate_report(report_title)

然后我们将创建一个新的函数,称为 create_scatterplot,用于生成我们的图形。

我们将设置我们的函数以接受多个参数:

  • dataframe:包含数据的数据框对象

  • xaxis:要在 x 轴上绘制的特征

  • yaxis:要在 y 轴上绘制的特征

  • colour:用于为数据点上色的特征

  • plot_name:我们图表的名称。这将用作文件名

  • xaxis_scale:一个包含两个元素的列表,用于定义 x 轴的最小值和最大值范围

  • yaxis_scale:一个包含两个元素的列表,用于定义 y 轴的最小值和最大值范围

默认情况下,xaxis_scaleyaxis_scale 都会设置为 None。如果用户没有提供这些,matplotlib 将使用数据绘制的最小值和最大值作为轴的范围。

Python-docx 本身不支持 matplotlib 图形。作为一种解决方法,我们需要将我们的图保存为文件,然后在开始写入 Word 文档时使用。

def create_scatterplot(dataframe, xaxis, yaxis, colour, plot_name,
                       xaxis_scale= None, yaxis_scale=None):
    fig, ax = plt.subplots()

    ax.scatter(dataframe[xaxis], dataframe[yaxis],
                c=dataframe[colour], cmap='viridis')

    ax.set_xlabel(xaxis)
    ax.set_ylabel(yaxis)

    if xaxis_scale is not None:
        ax.set_xlim(xmin=xaxis_scale[0], xmax=xaxis_scale[1])

    if yaxis_scale is not None:
        ax.set_ylim(ymin=yaxis_scale[0], ymax=yaxis_scale[1])

    filename = f'{plot_name}.png'
    plt.savefig(filename)

向 Streamlit UI 添加分隔水平线

为了帮助分隔 UI 并使每个部分突出显示,我们可以使用 st.write('---') 添加水平线。

这将从 Markdown 语言转换为实际的行。

如果你想了解更多关于 st.write 函数的信息,可以查看:如何使用 Streamlit 的 st.write 函数来改善你的 Streamlit 仪表板。

我们的最终代码如下:

with st.form('report'):
    st.write("### Report Details")
    col1, col2 = st.columns(2, gap='large')

    report_title = col1.text_input("Enter report title")
    report_author = col1.text_input("Enter the report author's name")
    report_date = col2.date_input("Select a date for the report")
    report_client = col2.text_input("Enter the client's name")

    st.write("---")
    sect_col1, sect_col2 = st.columns(2, gap='large')

    sect_col1.write("### Section Details")
    section_title = sect_col1.text_input("Enter section title")
    section_text_summary = sect_col1.text_area("Section Summary")

    data_features = df.columns

    sect_col2.write("### Data Summary")
    data_to_summarise = sect_col2.multiselect("Select features to include in statistical summary", 
                                              options=data_features)

    st.write("---")

    st.write("### Scatterplot Setup")
    sub_col1, sub_col2, sub_col3 = st.columns(3)

    chart_x = sub_col1.selectbox('X axis', options=data_features)
    chart_y = sub_col2.selectbox('Y axis', options=data_features)
    chart_z = sub_col3.selectbox('Z axis', options=data_features)

    if st.form_submit_button('Generate'):
        generate_report(report_title)

创建 Word 报告生成函数

我们的最后一步是创建 generate_report 函数。

这个函数将接收我们从用户那里收集的所有内容,然后将其写入我们的 Word 文档中。

如下代码所示,我们首先需要创建我们的 docx 对象,通过调用 docx.Document() 来完成。

然后,我们开始使用标题和段落的组合来创建报告的每个部分。其中一些利用 f-strings,以便我们可以将文本与输入变量结合起来。

接着,我们将添加之前创建的散点图,这可以通过 doc.add_picture() 完成。

最后一部分包含我们的 dataframe 统计摘要,它调用 add_df_to_docx 函数。

最后,我们将报告保存到 docx 文件中。

def generate_report(report_title, report_author, report_date, report_client,
                    section_title=None, 
                    section_text_summary=None, 
                    data_stats_summary=None, 
                    graph_figure=None):

    doc = docx.Document()

    # Add Title Page followed by section summary
    doc.add_heading(report_title, 0)
    doc.add_paragraph(f'Authored By: {report_author}')
    doc.add_paragraph(f'Created On: {str(report_date)}')
    doc.add_paragraph(f'Created For: {report_client}')
    doc.add_heading(section_title, 1)
    doc.add_paragraph(section_text_summary)

    # Add Scatter plot
    doc.add_heading('Data Visualisation', 2)
    doc.add_picture(graph_figure)

    # Add dataframe summary
    doc.add_heading('Data Summary', 2)
    summary_table = add_df_to_docx(doc, data_stats_summary)
    summary_table.style = 'LightShading-Accent1'

    doc.save('report.docx')

    return st.info('Report Generated')

一旦写入函数创建完毕,我们就可以填充用户点击生成按钮时的操作。

首先,我们需要调用 summary_statsscatter_plot_file 函数。这些函数的结果将被传递到 generate_report 函数中。

 if st.form_submit_button('Generate'):
        summary_stats = create_df_stats_summary(df, data_to_summarise)
        scatter_plot_file = create_scatterplot(df, chart_x, chart_y, chart_z, 
                                               plot_name='scatter', yaxis_scale=[3,1], )

        generate_report(report_title, report_author, report_date, report_client, 
                        section_title, section_text_summary, summary_stats,
                        graph_figure='scatter.png')

当我们查看我们的应用时,可以填写输入框中的所需信息并点击生成。

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

Streamlit Word 报告生成器的最终视图。图像由作者提供。

这将创建我们下面看到的 Word 文档。

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

从 Streamlit 应用生成的报告的第一页。图像由作者提供。

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

从 Streamlit 应用生成的报告的第二页。图像由作者提供。

摘要

创建报告是任何数据科学或岩石物理工作流程中的关键部分。然而,创建这些报告往往是耗时且繁琐的。

结合使用 Streamlit 创建用户界面和 docx 创建 Word 文档,我们可以帮助减少报告生成的负担,并开始自动化这一过程。

随着大型语言模型(LLMs)的到来,我们可能将这些模型集成到此应用中,以进一步提升其功能,并将自动化提升到一个新的水平。

参考文献

Bormann, Peter, Aursand, Peder, Dilib, Fahad, Manral, Surrender, & Dischington, Peter. (2020). FORCE 2020 Well well log and lithofacies dataset for machine learning competition [数据集]. Zenodo. doi.org/10.5281/zenodo.4351156

Streamlit Word 文档报告生成器的完整代码

以下是生成 Word 报告的 Streamlit 应用的完整代码:

import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
import docx

st.set_page_config(layout='wide')

def create_df_stats_summary(dataframe, features_to_include):
    sub_df = dataframe[features_to_include].copy()
    return sub_df.describe()

def create_scatterplot(dataframe, xaxis, yaxis, colour, plot_name,
                       xaxis_scale= None, yaxis_scale=None):
    fig, ax = plt.subplots()

    ax.scatter(dataframe[xaxis], dataframe[yaxis],
                c=dataframe[colour], cmap='viridis')

    ax.set_xlabel(xaxis)
    ax.set_ylabel(yaxis)

    if xaxis_scale is not None:
        ax.set_xlim(xmin=xaxis_scale[0], xmax=xaxis_scale[1])

    if yaxis_scale is not None:
        ax.set_ylim(ymin=yaxis_scale[0], ymax=yaxis_scale[1])

    filename = f'{plot_name}.png'
    plt.savefig(filename)

def add_df_to_docx(doc, dataframe):
    # Reset the index and get the new shape
    dataframe = dataframe.reset_index()
    num_rows, num_cols = dataframe.shape

    # Add a table to the document with the necessary number 
    # of rows and columns
    table = doc.add_table(rows=num_rows + 1, cols=num_cols)

    # Add the header row
    for i, col in enumerate(dataframe.columns):
        table.cell(0, i).text = str(col)

    # Add the data rows
    for i, row in dataframe.iterrows():
        for j, value in enumerate(row):
            table.cell(i + 1, j).text = str(value)

    return table

def generate_report(report_title, report_author, report_date, report_client,
                    section_title=None, 
                    section_text_summary=None, 
                    data_stats_summary=None, 
                    graph_figure=None):

    doc = docx.Document()

    # Add Title Page followed by section summary
    doc.add_heading(report_title, 0)
    doc.add_paragraph(f'Authored By: {report_author}')
    doc.add_paragraph(f'Created On: {str(report_date)}')
    doc.add_paragraph(f'Created For: {report_client}')
    doc.add_heading(section_title, 1)
    doc.add_paragraph(section_text_summary)

    # Add Scatter plot
    doc.add_heading('Data Visualisation', 2)
    doc.add_picture(graph_figure)

    # Add dataframe summary
    doc.add_heading('Data Summary', 2)
    summary_table = add_df_to_docx(doc, data_stats_summary)
    summary_table.style = 'LightShading-Accent1'

    doc.save('report.docx')

    return st.info('Report Generated')

st.title('Streamlit Data Report Generator')
df = pd.read_csv('Xeek_Well_15-9-15.csv')

with st.form('report'):
    st.write("### Report Details")
    col1, col2 = st.columns(2, gap='large')

    # Setup the title and associated data
    report_title = col1.text_input("Enter report title")
    report_author = col1.text_input("Enter the report author's name")
    report_date = col2.date_input("Select a date for the report")
    report_client = col2.text_input("Enter the client's name")

    st.write("---")
    sect_col1, sect_col2 = st.columns(2, gap='large')

    # Setup the first report section and associated data
    sect_col1.write("### Section Details")
    section_title = sect_col1.text_input("Enter section title")
    section_text_summary = sect_col1.text_area("Section Summary")

    data_features = df.columns

    sect_col2.write("### Data Summary")
    data_to_summarise = sect_col2.multiselect("Select features to include in statistical summary", 
                                              options=data_features)

    st.write("---")

    st.write("### Scatterplot Setup")
    sub_col1, sub_col2, sub_col3 = st.columns(3)

    chart_x = sub_col1.selectbox('X axis', options=data_features)
    chart_y = sub_col2.selectbox('Y axis', options=data_features)
    chart_z = sub_col3.selectbox('Z axis', options=data_features)

    if st.form_submit_button('Generate'):
        summary_stats = create_df_stats_summary(df, data_to_summarise)
        scatter_plot_file = create_scatterplot(df, chart_x, chart_y, chart_z, 
                                               plot_name='scatter', yaxis_scale=[3,1], )

        generate_report(report_title, report_author, report_date, report_client, 
                        section_title, section_text_summary, summary_stats,
                        graph_figure='scatter.png')

感谢阅读。在你离开之前,你应该订阅我的内容,将我的文章直接送到你的邮箱。 你可以在这里操作!或者,你也可以 注册我的通讯 以获取额外内容,直接免费送到你的邮箱。

其次,通过注册会员,你可以获得完整的 Medium 体验,并支持我和其他成千上万的作家。每月仅需 $5,你可以完全访问所有精彩的 Medium 文章,还可以通过写作赚钱。

如果你通过 我的链接注册, 你将直接用你的一部分费用支持我,并且不会额外增加你的费用。如果你这么做了,非常感谢你的支持。

压力测试你的 NLP 模型

原文:towardsdatascience.com/stress-test-for-your-nlp-models-94dba45b6d83

指标无法显示 NLP 模型的实际性能。学习如何彻底测试你的模型并修复注释伪影。

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

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

数据集伪影是自然语言处理(NLP)中的一个问题,会影响模型在实际环境中的表现。尽管预训练模型在基准数据集上表现良好,但在其他环境中表现却很差。这些失败是由于数据集伪影或注释伪影 —— 是语言模型在训练过程中学到的 [1] 的虚假相关性

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

Nathan Dumlao 摄影,图片来源于 Unsplash

事实证明,模型在训练过程中会吸收大量伪影,这些伪影源于数据集的特殊性和注释者带来的偏见。伪影为何有害?基本上,它们为你的模型提供了记忆一些实际是虚假的因果关系的捷径,而不是学习正确的“推理”。例如,如果一个数据集中有很多男性角色是医生的例子,那么模型在推断时会更倾向于男性成为医生,而不是女性。也可能构造一些对抗性示例,使得模型的表现出乎意料地低。

作为数据科学家的你,工作是尽可能消除伪影,同时提高模型的整体性能。

找出所有这些伪影!

首先,你需要了解你的敌人。因此,你需要找到这些伪影。为了找到这些伪影,我们需要定义我们要寻找的东西。总而言之,我们可以列出以下伪影,尽管这份列表并不详尽:

  • 模型无法理解比较。

  • 模型无法理解强化词。

  • 一个模型无法理解分类(同义词、反义词等)

  • 一个模型对拼写错误、无关的变化等缺乏鲁棒性

  • 一个模型无法适当地处理命名实体

  • 一个模型对某些少数群体或性别表现出不公平

  • 一个模型无法理解事件的顺序

  • 一个模型无法适当地处理否定

  • 一个模型无法理解共指

  • 一个模型无法理解角色如代理、对象等

  • 一个模型对某些触发词不够鲁棒(某些词组“破解”你的模型,使其显示一些不希望看到的结果)

  • 一个模型无法处理对抗样本(对抗样本是通过在输入段落中添加干扰句子创建的,但它们既不与正确答案矛盾,也不会混淆人类)

  • 以及其他……

现在既然我们知道了它们的面貌,我们想找出伪影。在查看数据集或标注它们时,你可能会发现一些伪影,但没有比训练一个基准模型并进行一些测试更好的方法来找到它们。幸运的是,有一个完美的工具——CheckList [2]。它不是灵丹妙药,但它非常有用,因为它帮助分析了上述大多数伪影。

伪影检查

工具和仪器

多亏了作者,有一个很棒的开源工具可以即刻用于一些数据集(如 SQuAD、QQP 等)。让我们仔细看看。

CheckList 是一个测试套件,灵感来源于软件开发中的单元测试。作者创建了一些脚本,这些脚本生成了许多带有特殊“模板”的测试,例如:

ret = editor.template({'question': 'Is this a {adj} movie?',
                       'context': 'This is a {adj} movie.' },
                      labels='Yes, this is {adj}.',
                      adj=['good', 'great', 'awesome', 'excellent'])
print(ret.data[0])
print(ret.labels[0])
print()
print(ret.data[1])
print(ret.labels[1])
print()

这个模板将返回一堆上下文、问题和答案,这些将用于测试你的模型:

{'question': 'Is this a good movie?', 'context': 'This is a good movie.'}
Yes, this is good.

{'question': 'Is this a great movie?', 'context': 'This is a great movie.'}
Yes, this is great.

使用这个工具,你可以生成任何数量的此类示例。它的优点在于你可以自定义提供的测试套件,以添加或编辑任何特定的模板。

此外,一旦你设置好测试模板并准备好测试套件,你可以运行测试并获得一个相当整洁的总结,你甚至可以用小部件可视化这个总结(在 Colab 中不起作用)。这个工具使用起来相当简单,而且文档也很完善。所以花些时间去了解一下吧。

测试结果

那么模型呈现了什么结果呢?作者们在 2019 年对大多数流行的最先进模型进行了大量测试,发现它们都有偏见并存在许多伪影。

作者总结了在以下模型上的 CheckList 测试结果(按图中从左到右的顺序排列):

  • Microsoft 的文本分析

  • Google Cloud 的自然语言

  • Amazon 的 Comprehend

  • BERT-base

  • RoBERTa-base

根据论文,测试模型的失败率在几个测试中都相当高。它们在大多数与否定处理相关的测试中表现糟糕,在情感随时间变化和两个陈述的比较中表现不佳。有趣的是,特别是 BERT 基础的模型也未能正确分类中性情感句子。因此,事实证明,即使在大量数据上训练,大多数 NLP 模型也容易受到伪影的影响。

以示例为例,我和我的同事 Derrick Xu 对 SQuAD 训练的 ELECTRA-small 模型进行了类似的测试。正如预期的那样,我们得到了更差的结果(图 1)。尽管模型本身获得了 86.3 的 F1 分数和 78.5 的准确匹配分数,但它存在许多偏差,这可以在图 1 中看到。

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

图 1. 针对 SQuAD 数据集训练的 ELECTRA-small 模型的测试结果。

我们还通过使用对抗性 AddOneSent 数据集(见图 2)评估了基线 QA 模型的对抗性表现。

与在 SQuAD 开发集上的性能相比,F1 分数从 86%降至 49.6%,准确匹配分数从 78%降至 42.1%。

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

图 2:模型在 SQuAD 数据集示例上的预测失败示例。对抗性句子和错误预测的答案以红色标记。原始短语和正确答案以蓝色标记。

因此,除了修复通用语言能力(使用 CheckList 发现),我们还寻求提高模型在对抗性示例上的表现。

现在修复它们!

有几种方法可以对抗标注伪影:

  • 对难度较大的数据子集或黄金标签分布模糊的数据进行重新训练。建议使用数据集制图[3]或其他任何方法来寻找这些示例。

  • 基于集成的去偏差:使用一个弱模型学习相关性,然后训练你的模型学习该模型的残差[4],或者将其从输出分布中移除。这将使你的主要模型在困难示例上进行额外训练。

  • 还有其他方法……

基本上,所有的方法都归结为对模型难以推断的数据进行重新训练。所以最简单的方法就是生成这些数据并重新训练你的模型。这实际上效果很好。

幸运的是,你不必手动编写所有额外的数据。你已经设置了 CheckList 生成工具。因此,你只需要设置模板即可开始使用。

为了修复我们的 ELECTRA-small 模型,我们也使用了 CheckList 工具。以下是生成额外数据以改进比较能力的示例代码。

import checklist
import spacy
import itertools
import json

import checklist.editor
from checklist.test_types import MFT, INV, DIR
from checklist.expect import Expect
from checklist.test_suite import TestSuite
from checklist.perturb import Perturb
import checklist.text_generation

# Template to generate comparison examples
adj = ['large', 'fat', 'fresh', 'kind', 'deep', 'wierd', 'poor', 'clear', 'bold', 'calm', 'clever', 'firm', 'mean', 'quick', 'quiet', 'strong', 'bright', 'light']
adj = [(x.rstrip('e'), x) for x in adj]

temp1 = editor.template(
    [(
    '{first_name} is {adj[0]}er than {first_name1}.',
    'Who is less {adj[1]}?'
    ),(
    '{first_name} is {adj[0]}er than {first_name1}.',
    'Who is {adj[0]}er?'
    )
    ],
    labels = ['{first_name1}','{first_name}'],
    adj=adj,
    remove_duplicates=True,
    nsamples=1000,
    save=True
    )

# Generating train extension from comparisons
train_extension_comparison = []
id_n = 0
for string in range(len(temp1['data'])):
  for i in range(len(temp1['data'][string])):
    index_of_answer = temp1['data'][string][i][0].find(temp1['labels'][string][i])
    train_extension_comparison.append({
      'id':f'aug{id_n}',      
      'title':'aug_comparison',
      'context':temp1['data'][string][i][0],
      'question':temp1['data'][string][i][1],
      'answers':{"text": [temp1['labels'][string][i], temp1['labels'][string][i], temp1['labels'][string][i]], "answer_start": [index_of_answer, index_of_answer, index_of_answer]}
    })
    id_n += 1

# will generate 1996 examples with different compbinations 
# of adjectives and first names according to the template.

然后只需将获得的数据倒入并与原始训练数据连接。在我们的案例中,这些数据是 SQuAD 训练数据。不要忘记将新数据与原始数据混合,以避免训练数据的偏斜。

我们为该模型重复生成了几个能力的数据。最终,我们实现了对原始 SQuAD 训练数据的大约 30% 扩展。我们还包含了一些对抗数据,这也是对抗伪影的方法之一。刘等人(2019)[5] 发现,当使用来自挑战数据集的 500 个或更多对抗示例重新训练模型(即 BiDAF,QANet)时,模型性能会提高。因此,我们也将大约 750 个对抗示例包含到扩展数据集中,对整个数据集进行混洗,并重新训练了模型。

我们进行了与之前相同的 CheckList 测试,以下是我们得到的结果。结果如图 3 所示。

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

图 3. CheckList 测试结果在重新训练模型与基线模型(在 SQuAD 数据集上训练的 ELECTRA-small)之间的比较。

如你所见,结果并不完美。我们显然降低了模型在某些能力上的表现,但主要是那些数据未生成用于重新训练的能力。对于那些我们已增强数据的能力,性能显著提升(失败率降低)。

然而,有些结果并不那么稳定。可能没有足够的数据来提高对否定能力(尽管我们确实针对了这一能力)、核心指代和时间能力的表现。

与此同时,我们成功地提高了对抗数据集上的性能(图 4),并弥补了对抗重新训练在原始开发数据集上带来的性能下降。

就最终整体指标而言,原始开发数据集上的测试结果显示:精确匹配度从 78.5 提升至 78.7,F1 分数从 86.3 下降至 86.2。这实际上是一个好结果,因为根据刘等人(2019)的研究,使用对抗数据进行重新训练会导致原始开发数据集上重新训练的 QA 模型性能显著下降(在我们的案例中,精确匹配度下降至 74.2,F1 分数为 81.9),因此单独进行对抗训练看起来并不太有吸引力。然而,与其他伪影处理技术(如在我们案例中的 CheckList 生成数据增强)结合使用,可以获得更好的性能,并显著提高对抗数据上的表现。

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

图 4. 重新训练模型在 SQuAD 对抗数据集上的表现。与基线模型相比的改进用绿色突出显示。

总结来说,我们在提高模型能力和解决伪影问题的同时,整体指标大致保持不变。

总结

CheckList + 对抗训练只是分析和修复标注伪影的方法之一。为了显著提升模型性能并消除伪影,你必须同时使用几种方法。

然而,我不能过分强调,你必须考虑这些文档并与之抗争。这是朝着更强健、公平且减少偏见的自然语言处理模型迈出的重要一步!

参考文献

本文由我和Derrick Xu共同提供。

除非另有说明,否则所有图片均由作者提供。

[1] Gururangan, S., Swayamdipta, S., Levy, O., Schwartz, R., Bowman, S. R., & Smith, N. A. (2018). 自然语言推理数据中的注释伪影。arXiv 预印本 arXiv:1803.02324

[2] Ribeiro, M. T., Wu, T., Guestrin, C., & Singh, S. (2020). 超越准确性:使用 CheckList 对 NLP 模型进行行为测试。arXiv 预印本 arXiv:2005.04118

[3] Swayamdipta, S., Schwartz, R., Lourie, N., Wang, Y., Hajishirzi, H., Smith, N. A., & Choi, Y. (2020). 数据集制图:通过训练动态映射和诊断数据集。arXiv 预印本 arXiv:2009.10795

[4] He, H., Zha, S., & Wang, H. (2019). 通过拟合残差来消除自然语言推理中的数据集偏差。arXiv 预印本 arXiv:1908.10763

[5] Liu, N. F., Schwartz, R., & Smith, N. A. (2019). 通过微调进行免疫:分析挑战数据集的方法。arXiv 预印本 arXiv:1904.02668

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值