Press "Enter" to skip to content

《理解Gradio中的保留》

如何利用 Web 应用程序进行分析

DALL-E 3 图片

我记得当我构建我的第一个 Web 应用程序时的那一刻。那是大约八年前,我是一名相当初级的分析师,并且坚信商业智能工具可以解决所有问题。

工程团队建立了一个新的 SDK 原型,并希望了解它是否能够更好地收集数据。他们在一组设备上进行测试,查看数据并将其与旧版本进行比较。然而,设备组合经常变化,因此在商业智能工具中保持更新需要相当多的工作。所以,我决定构建一个 Web 应用程序。

我找到了一系列文章(如果我没记错的话是十篇或十一篇),都阅读了,并尝试使用这些知识来完成我的任务。我花了大约一周的时间完成了第一个原型。我必须同时编写后端和前端,因此现在我至少可以自称是一名初级全栈开发人员了。对于后端,我使用了 Flask(幸运的是我没有接触到 Django,否则我可能会花费整整一个月),而对于前端,我使用了 Bootstrap 和 Leaflet。

总体而言,这是一个具有挑战性的任务,需要付出很多努力来提高工程技能。我相信,除了你的主要专业领域之外,对其他领域有更深入的理解总是值得的。

然而,我很高兴现在有很多工具可以让分析师和数据科学家在不到一小时内构建原型。在许多情况下,这样的原型可以将您的分析提升到一个新的水平。以下是一些示例:

  • 根据输入参数(如营销预算或即将推出新功能的市场)预测收入和受众量,
  • 加快团队工作速度或减少临时工作量的工具,例如 A/B 测试计算器或自动根本原因分析,
  • MVP 解决方案,例如,如果您想使用 LLM 来自动化一些内部流程,在开发生产版本之前先测试原型是值得的。我在之前的一篇文章中分享了这样的 ML 原型:“在一个小时内构建您的第一个深度学习应用”

在本文中,我想告诉您一个这样的框架,它可以帮助您快速而几乎轻松地创建外观漂亮的 Web 应用程序,而不需要费心处理 JavaScript 和 CSS。我们将了解 Gradio 的基础知识,开发几个 Web 应用程序,并将它们发布到 HuggingFace Spaces,以便任何人都可以访问它们。

Gradio 不是这种类型的唯一一个框架。还有其他几个开源的 Python 替代品:

  • Streamlit 是另一个流行且功能强大的用于构建数据应用程序的库。它还得到了 HuggingFace Spaces 的支持,因此您可以托管此类应用程序。
  • Dash 如果您已经使用 Plotly 并且需要更多自定义功能,Dash 可能更便捷。
  • 然而,如果您想构建一些定制和复杂的东西,您的最后选择将是 Flask,甚至是 Django

您可以在此文中找到有关不同框架主要特点的更多详细信息。

Gradio 基础知识

Gradio 是一个用于构建交互式应用程序的开源 Python 库。

Gradio 的主要优势包括:

  • 您可以仅使用 Python 构建应用程序,这也意味着您可以在应用程序中使用所有 Python 库,
  • 您可以在 Jupyter Notebook 中运行它,也可以将其作为一个独立的网页运行,
  • 您可以将 Gradio 应用程序永久托管在 HuggingFace Spaces 上。

没有万能药,因此 Gradio 也有其限制:

  • 它明确设计用于机器学习应用程序。因此,如果您将其用于其他用途,您可能需要更改默认设置(例如,使用allow_flagging= "never"关闭标记)。
  • 定制化有限,特别是在设计方面。
  • 请记住,Gradio 主要用于快速原型开发。它通常表现良好,但有时会出现一些奇怪的行为。例如,在 Safari 中进行表格编辑的工作方式不直观,或者有时需要重新启动 Jupyter Notebook 才能加载界面。

要开始使用Gradio,我们需要安装Python包。

pip install gradio

遵循老程序员的传统,让我们从“Hello, World!”开始。

我们可以使用gr.Interface类来定义界面(文档)。这是Gradio的核心类之一,它可以帮助您基于任何Python函数创建一个基于Web的应用程序。

我们需要指定以下参数:

  • inputs界面的输入组件(在我们的情况下,只有一个文本框),
  • outputs界面的输出组件(在我们的情况下,也只是一个文本框),
  • fn核心功能(一个获取输入并返回输出的函数,在我们的情况下,从输入中获取名称并返回“Hello, <name>!”),
  • title & description一些Markdown格式的内容,使我们的应用更加用户友好。
import gradio as grdemo = gr.Interface(    inputs=[gr.Textbox(label="名称", lines=1)],    outputs=[gr.Textbox(label="结果", lines=1)],    fn=lambda x: '你好,%s!' % x,    title="Hello, World!",    description="使用Gradio创建的第一个应用",    allow_flagging='never')demo.launch()

您可以在Jupyter Notebook中运行此代码并查看结果。这对于调试非常方便。之后,我们将讨论如何使您的Web应用对其他人可用。

Image by author

就是这样:只需几行代码,您的第一个Gradio应用就可以运行了。而且,我必须注意到它看起来非常不错,我们不需要使用任何前端技巧。

当您从Jupyter Notebook中工作时,Gradio会在后台启动许多进程,所以定期使用gr.close_all()关闭连接是值得的。

我们看了最基本的例子,并了解了Gradio的构建块:输入、输出和函数。现在,我们准备开始真实的分析任务。

增长模拟

作为第一个示例,我们将研究对产品的用户增长的保留率的影响。

保留作为增长的基础

两个参数决定了产品的增长:

  • 获取(每个周期的新用户数量),
  • 保留(保留用户在产品中的能力)。

让我们模拟用户基数随着保留曲线的变化而增长。

我们可以使用以下一组参数(abcd)来描述任何保留曲线:

《理解Gradio中的保留》 四海 第3张

让我们讨论一下保留的最常见情况:队列由产品中的第一个动作定义,并且所有动作都计入保留。在这种情况下,periods = 0的保留必须等于1(因为队列入口和保留事件是相同的)。因此,我们可以自动定义其中一个参数:

《理解Gradio中的保留》 四海 第4张

长期保留是增长的主要因素。它决定了客户是否长时间使用您的产品,使得您的产品能够可持续增长,还是客户在一个月内流失,而您需要获取越来越多的新用户以实现增长。在我们的公式中,a参数负责长期保留。

《理解Gradio中的保留》 四海 第5张

我们可以使用这个公式来定义保留曲线。所以我们现在有了继续进行开发所需的一切。

可视化保留图表

让我们简单开始,制作一个能够接受保留曲线参数并将其关系显示为图表的应用程序。

与我们的“Hello, World”示例类似,我们需要使用gr.Interface类,并传递inputsoutputsfn来进行映射。

  • 现在我们需要更多的输入参数。因此,inputs将是一个控件列表。我们将使用gr.Slidergr.Dropdown控件。对于gr.Slider,我们需要传递最小值、最大值、默认值和一个在函数中使用的标签。对于gr.Dropdown,我们需要定义一个可能值的列表、默认值和一个标签。
  • 我们仍然只有一个输出——一个图表,所以outputs将是没有任何参数的gr.Plot
  • fn函数将将输入映射到输出,因此它将获取输入参数并返回一个将被可视化的plotly.Figure对象。
import plotly.express as px# functions to calculate retentiondef get_retention(a, b, c, d, periods):    return  a + 1./(b + c * (periods ** d))def get_retention_same_event(a, c, d, periods):    b = 1./(1 - a)    return get_retention(a, b, c, d, periods)# define function - return plot depending on input parametersdef get_retention_plot(a, c, d, num_periods):    df = pd.DataFrame({'x': range(num_periods + 1)})    df['retention'] = df.x.map(lambda x: get_retention_same_event(a, c, d, x))    return px.line(df, x = 'x', y = 'retention',                   color_discrete_sequence = px.colors.qualitative.Prism,                   title = 'Retention curve', labels = {'x': 'period'})# define inputsinputs = [    gr.Slider(0, 1, 0.03, label="a"),    gr.Slider(0, 5, 0.55, label="c"),    gr.Slider(0, 5, 1.5, label="d"),    gr.Dropdown([10, 30, 60, 90], value = 30, label="Number of Periods"),    gr.Dropdown([10, 100, 1000, 10000], value = 10000, label="Number of new users each period")]# define outputsoutputs = gr.Plot()# define interfacedemo = gr.Interface(    fn=get_retention_plot,    inputs=inputs,    outputs=outputs,    cache_examples=True,    allow_flagging = 'never' # hiding default flag functionality in the interface)# launchdemo.launch(debug = True)

让我们尝试运行这个应用程序。它正常工作——我们可以看到一个图表,如果我们提交新的参数,它会发生变化。

《理解Gradio中的保留》 四海 第6张

添加更多图表

我们的目标是查看保留对于增长的影响,所以我们需要添加显示不仅保留还有观众随时间变化的图表。让我们修改我们的界面。

为了简单起见,我们将假设在每个周期中,相同数量的新用户开始使用我们的产品(cohort_size参数)。

我们只需要对我们的实现进行一些修改:

  • 修改get_retention_plot函数,使其获得一个额外的参数用于用户群大小,计算随时间变化的用户数量,并返回三个图表。
  • 现在,outputs参数等于三个gr.Plot()对象的列表。
def get_retention_plot(a, c, d, num_periods, cohort_size):    
    ret_df = pd.DataFrame({'x': range(num_periods + 1)})    
    ret_df['retention'] = ret_df.x.map(lambda x: get_retention_same_event(a, c, d, x))        

    ret_fig = px.line(ret_df.iloc[1:], x = 'x', y = 'retention',                       
                      color_discrete_sequence = px.colors.qualitative.Prism,                       
                      title = '留存曲线')    

    # simulation    
    tmp_data = []    
    for cohort in range(num_periods + 1):        
        for cohort_period in range(num_periods + 1):            
            period = cohort_period + cohort            
            if period > num_periods:                
                continue            
            retention = get_retention_same_event(a, c, d, cohort_period)            
            tmp_data.append(                
                {                    
                    'cohort': 'cohort %s' % str(cohort).rjust(3, '0'),                    
                    'cohort_period': cohort_period,                    
                    'period': period,                    
                    'retention': retention,                    
                    'users': int(round(retention * cohort_size))                
                }            
            )    

    users_df = pd.DataFrame(tmp_data)    
    users_fig = px.area(users_df.groupby('period').users.sum(),                    
                    color_discrete_sequence = px.colors.qualitative.Prism,                       
                    title = '活跃用户')    

    cohorts_fig = px.area(users_df.pivot_table(index = 'period', columns = 'cohort', values = 'users',                    
                    aggfunc = 'sum'),                    
                    color_discrete_sequence = px.colors.qualitative.Prism,                     
                    title = '各周期活跃用户')    

    return ret_fig, users_fig, cohorts_fig

inputs = [    
    gr.Slider(0, 1, 0.03, label="a"),    
    gr.Slider(0, 5, 0.55, label="c"),    
    gr.Slider(0, 5, 1.5, label="d"),    
    gr.Dropdown([10, 30, 60, 90], value = 30, label="周期数"),    
    gr.Dropdown([10, 100, 1000, 10000], value = 10000, label="每周期新增用户数")
]

outputs = [gr.Plot(), gr.Plot(),  gr.Plot()]

demo = gr.Interface(    
    fn=get_retention_plot,    
    inputs=inputs,    
    outputs=outputs,    
    allow_flagging = 'never',    
    cache_examples=True,
)

demo.launch(debug = True)

神奇的是,现在我们可以看到完整的图像并分析它们之间的关系。然而,仍有改进的空间 – 我们可以添加格式来使我们的应用程序对用户更加方便。

作者提供的图片

添加一点样式

我们可以微调界面,使其更加用户友好和简单。

为此,我们将使用gr.Blocks()作为上下文。该功能允许您创建更多自定义的Web应用程序,并定义布局和数据流(触发函数和随后执行的事件)。

块将为我们打开新的机会:

  • 使用gr.Blocks(),我们可以使用gr.Row()gr.Column()来组织布局。
  • gr.Markdown允许您添加markdown元素,例如标题或者甚至LaTeX公式(默认情况下,需要将它们放在美元符号内)。
  • gr.Accordion可以帮助您隐藏一些默认情况下不想显示给用户的参数。
  • 此外,此方法允许您定义更复杂的更新逻辑。例如,不仅在提交按钮上更新绘图,还可在任何输入参数更改时更新绘图。我们将在以下示例中使用此功能。

使用块时,我们需要将每个输入和输出定义为变量,例如 a = gr.Slider(0, 1, 0.03, label="a")

还有没有默认的控件,所以我们必须自己定义按钮 – btn_caption = gr.Button("提交")

还必须指定按钮点击后的操作,设置已经熟悉的参数——inputsoutputsfn

btn_caption.click(fn=get_retention_plot,         inputs=[a, c, d, num_periods, cohort_size],         outputs=[plot1, plot2, plot3])

这是完整的代码版本。

with gr.Blocks() as demo:    gr.Markdown("# Understanding Growth 🚀")    with gr.Row():        with gr.Column():            gr.Markdown("## Retention curve parameters 📈")            gr.Markdown(r"$\textbf{retention}(\textsf{x}) = \textsf{a} + \frac{\textsf{1}}{\textsf{b} + \textsf{c} * \textsf{x}^{\textsf{d}}}\ where\ \textsf{b} = \frac{\textsf{1}}{\textsf{1}-\textsf{a}}$")            with gr.Row():                a = gr.Slider(0, 1, 0.03, label="a")                c = gr.Slider(0, 5, 0.55, label="c")                d = gr.Slider(0, 5, 1.5, label="d")            with gr.Accordion("More options", open=False):                with gr.Row():                    num_periods = gr.Dropdown([10, 30, 60, 90], value = 30, label="Number of Periods")                    cohort_size = gr.Dropdown([10, 100, 1000, 10000], value = 10000, label="Number of new users each period")            btn_caption = gr.Button("Submit")        with gr.Column():            plot1 = gr.Plot()    with gr.Row():        plot2 = gr.Plot()        plot3 = gr.Plot()        btn_caption.click(fn=get_retention_plot,         inputs=[a, c, d, num_periods, cohort_size],         outputs=[plot1, plot2, plot3])demo.launch()

托管您的应用程序

此外,我们还可以使用HuggingFace Spaces来托管我们的Web应用程序,并与他人轻松共享。

要开始使用Spaces,您需要先注册一个账号。如果您还没有注册,请点击这个链接。这将不会花费您几分钟。

下一步是创建一个新的Space。您可以在文档中找到更详细的说明。

Image by author

对于新的Space,您必须填写以下参数:名称、许可证和以Gradio作为您的SDK。

Image by author

然后,您需要将代码提交到Hugging Spaces的Git仓库中。首先,我们需要克隆仓库。

-- cloning repogit clone https://huggingface.co/spaces/<your_login>/<your_app_name>cd <your_app_name>

最近,HuggingFace已更改了Git认证过程,因此我们需要先创建一个token,然后为Git repo设置它。

git remote set-url origin https://<your_login>:<token>@huggingface.co/spaces/<your_login>/<your_app_name>git pull origin

现在,是时候提交与我们的应用程序相关的文件了。我们至少需要以下文件:

  • app.py,其中包含启动Gradio应用程序的Python代码
  • requirements.txt,其中包含您的应用程序所需的Python软件包列表。在我们的情况下,只有pandasplotly

接下来使用git的基本步骤:add、commit和push到HuggingFaces。

git add app.pygit add requirements.txtgit commit -m 'retention模拟器应用的第一个版本'git push

构建这个应用程序花了一些时间,现在已经完成了。现在我们的网络应用程序正在HuggingFaces Spaces上运行。你可以在这里试试。

作者上传的图片

看起来比我们最初的版本要漂亮得多,因为布局不需要滚动,用户也不必猜测acd参数的含义。

预测保留

我们已经学会了在Web应用程序中根据一些参数生成图表。但在现实生活中,我们通常需要输入大量数据,因此让我们看看如何在应用程序中使用来自.csv文件的数据。

以一个例子来说,我们将查看前几个期间的实际保留数据,并尝试预测以下期间的保留数据。这是一个相当常见的任务,因为我们通常不希望等待三个月才能比较新队列的第三个月保留率。我们将上传实际数据作为.csv文件。

让我们不浪费时间,直接进入实施部分。

从文件中获取数据

下面是生成整个界面和业务逻辑的代码。它可能看起来有点复杂,不过不用担心。我们稍后将讨论核心要点。

# 解析文件或字符串并返回数据框def parse_file(input_text_or_file, num_periods):    if isinstance(input_text_or_file, str):        df = pd.read_csv(StringIO(input_text_or_file), sep = '\t')    else:        df = pd.read_csv(input_text_or_file.name, sep = '\t')    return df# 获取数据框并返回绘图def show_graph_for_df(df, num_periods):    df['period'] = df.period.map(int)    df['retention_fact'] = df.retention_fact.map(float)    result = scipy.optimize.minimize(lambda x: get_mse_for_retention(x, df), [random.random(), random.random(), random.random()])    a, c, d = result.x    pred_df = pd.DataFrame({'period': range(num_periods + 1)})    pred_df['retention_pred'] = pred_df.period.map(lambda x: get_retention_same_event(a, c, d, x))    pred_df = pred_df.merge(df, how = 'left')        fig = go.Figure()    fig.add_trace(go.Scatter(x=pred_df.period, y=pred_df.retention_fact, name='实际',                             line=dict(color=plotly.colors.qualitative.Prism[0], width=3)))        fig.add_trace(go.Scatter(x=pred_df.period, y=pred_df.retention_pred, name='预测',                             line=dict(color=plotly.colors.qualitative.Prism[0], width=3, dash='dot')))        fig.update_layout(title='每日保留模型 (a = %.2f, c = %.2f, d = %.2f)' % (a, c, d),                       yaxis_title='保留率',                       xaxis_title='期间')    return fig#获取文件并返回绘图def show_graph_for_file(temp_file, num_periods):    df = parse_file(temp_file, num_periods)    return show_graph_for_df(df, num_periods)# 默认的数据示例default_csv = 'period\tretention_fact\n0\t1\n1\t0.55\n2\t0.4\n3\t0.35\n4\t0.3\n'#与gr.Blocks()交互的接口:    gr.Markdown('# 预测保留曲线 📊')    periods = gr.Dropdown([10, 30, 90, 180], label="期间数", value = 30)    gr.Markdown('上传具有数据的.csv文件,使用默认数据作为示例或在“上传的数据”部分手动输入数字。')    gr.Markdown('''__文件格式:__ 2列(`period`和`retention_fact`)''')        with gr.Row():        upload_button = gr.UploadButton(label="上传文件", file_types = ['.csv'], live=True, file_count = "single")        default_button = gr.Button('显示示例')        with gr.Row():        with gr.Accordion("已上传的数据", open=False):            gr.Markdown('您可以更改表中的值')            table = gr.Dataframe(type="pandas", col_count=2, interactive = True, headers = ['period', 'retention_fact'])                with gr.Row():            image = gr.Plot()        # 触发器和事件的业务逻辑    upload_button.upload(fn=show_graph_for_file, inputs=[upload_button, periods], outputs=image, api_name="upload_graph")    upload_button.upload(fn=parse_file, inputs=[upload_button, periods], outputs=table, api_name="upload_csv")    default_button.click(fn=lambda x: show_graph_for_file(default_csv, x), inputs=[periods], outputs=image, api_name="upload_example_graph")    default_button.click(fn=lambda x: parse_file(default_csv, x), inputs=[periods], outputs=table, api_name="upload_example_csv")    table.change(fn=show_graph_for_df, inputs=[table, periods], outputs=image, api_name="upload_table_graph")    periods.change(fn=show_graph_for_df, inputs=[table, periods], outputs=image, api_name="upload_periods_graph")demo.launch(debug=True)

让我们仔细看看。界面中有以下元素:

  • periods — 输入参数
  • upload_button — 允许您从 .csv 文件中加载数据的输入参数
  • default_button — 示例中的预定义值,用于更新表格和图形
  • table 显示从上传的数据(来自 .csv 文件或示例)生成的数据框,您还可以直接更改表格中的数字,图形将随之更新
  • image — 输出参数,显示绘图结果
作者提供的图像

函数 parse_file 会获取来自 upload_button 的文件或默认示例的字符串,并返回一个 pandas 数据框,供我们进一步使用。因此,使用文件中的数据非常简单。

关键的业务逻辑定义在下面的代码片段中。它为所有界面元素定义了操作:

  • 上传 .csv 文件时,表格和图形将被更新
  • 点击“显示示例”按钮时,表格和图形将被更新
  • 更改表格中的数据时,只有图形会被更新
  • 更改周期数时,只有图形会被更新
upload_button.upload(fn=show_graph_for_file, inputs=[upload_button, periods], outputs=image, api_name="upload_graph")upload_button.upload(fn=parse_file, inputs=[upload_button, periods], outputs=table, api_name="upload_csv")default_button.click(fn=lambda x: show_graph_for_file(default_csv, x), inputs=[periods], outputs=image, api_name="upload_example_graph")default_button.click(fn=lambda x: parse_file(default_csv, x), inputs=[periods], outputs=table, api_name="upload_example_csv")table.change(fn=show_graph_for_df, inputs=[table, periods], outputs=image, api_name="upload_table_graph")periods.change(fn=show_graph_for_df, inputs=[table, periods], outputs=image, api_name="upload_periods_graph")

定义最佳适配函数

我们解决方案的关键部分是找到适合实际数据的最佳适配函数。让我们看看如何做到这一点。

  • 首先,我们定义了函数 get_mse_for_retention,该函数返回参数集(acd)的误差。它还以数据框作为输入。
  • 我们使用标准的均方差(MSE)作为我们将最小化的误差。
  • 然后,我们将使用 scipy.optimize.minimize 函数进行优化。我们只需要传入两个参数:要优化的函数(我们传入了带有硬编码数据框的 lambda 函数,因为我们仅优化参数)和参数的初始值(只是一组随机值的列表)。
  • 优化完成后,我们可以使用 result.x 访问最优参数。
def get_mse_for_retention(params, df):    tmp_df = df.copy()    tmp_df['retention_pred'] = tmp_df.index.map(        lambda x: get_retention_same_event(params[0], params[1], params[2], x)    )        tmp_df['se'] = (tmp_df.retention_fact - tmp_df.retention_pred)    tmp_df['se'] = tmp_df['se']**2        return tmp_df.se.mean() ** 0.5result = scipy.optimize.minimize(lambda x: get_mse_for_retention(x, df), [random.random(), random.random(), random.random()])a, c, d = result.xprint(a, c, d)

这就是了,现在我们知道了我们实际数据的理论留存曲线,可以在应用程序中用它进行预测。

最后一步

我按照相同的指示将此应用程序发布到了 HuggingFace Spaces。您可以在这里尝试一下。

您可以在GitHub上找到这两个应用程序的完整代码。

摘要

在本文中,我们探讨了 Gradio 库的基础知识,并学习了如何仅使用 Python 构建出令人愉快的 Web 应用程序。

我们学习了几种方法:

  • 高级的 gr.Interface 类,可以让您快速获得一个可工作的原型。
  • 使用 gr.Blocks 的更可定制化方法,您可以在其中指定所需的精确布局,并定义输入和输出之间的复杂关系。

非常感谢您阅读本文。我希望本文对您有所启发。如果您有任何后续问题或评论,请在评论区留言。

参考资料

本文受“使用 Gradio 构建生成 AI 应用程序”课程的启发。

Leave a Reply

Your email address will not be published. Required fields are marked *