我的编程空间,编程开发者的网络收藏夹
学习永远不晚

[python] 基于Gradio可视化部署机器学习应用

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

[python] 基于Gradio可视化部署机器学习应用

Gradio是一个开源的Python库,用于构建机器学习和数据科学演示应用。有了Gradio,你可以围绕你的机器学习模型或数据科学工作流程快速创建一个简单漂亮的用户界面。Gradio适用于以下情况:

  • 为客户/合作者/用户/学生演示你的机器学习模型。

  • 通过自动共享链接快速部署你的模型,并获得对模型性能的反馈。

  • 在开发过程中使用内置的操作和解释工具交互式地调试你的模型。

Gradio官方仓库为:Gradio官方仓库。Gradio官方文档见:Gradio官方文档如果在使用中遇到问题,多查查官方文档。参考Gradio-demo可以获得不同的Gradio应用示例。
Gradio需要Python3.7及以上版本才能运行,安装指令如下:

pip install gradio

文章目录

1 基础使用

Gradio只需要几行代码就可以建立一个展示应用,一些基础应用搭建示例将在本章进行介绍。

1.1 快速入门

1.1.1 Interface构建应用

通过Interface类可以快速构建应用。

简单应用

下面的示例建立了对输入文本进行简单处理的应用。

import gradio as gr# 输入文本处理程序def greet(name):    return "Hello " + name + "!"# 接口创建函数# fn设置处理函数,inputs设置输入接口组件,outputs设置输出接口组件# fn,inputs,outputs都是必填函数demo = gr.Interface(fn=greet, inputs="text", outputs="text")demo.launch()   

运行程序后,打开http://localhost:7860即可看到网页效果。左边是文本输入框,右边是结果展示框。Clear按钮用于重置网页状态,Submit按钮用于执行处理程序,Flag按钮用于保存结果到本地。

在这里插入图片描述

在上面的示例中,我们看到了一个简单的基于文本的函数,但该函数可以是任何函数,从音乐生成器到税务计算器,再到机器学习模型的预测函数。核心的Interface类被初始化,有三个必要的参数。

  • fn: 围绕用户界面的函数
  • 输入:用于输入的组件(例如:“text”、"image)。
  • 输出:用于输出的组件(例如:“text”、"image)。

自定义输入组件

以下代码展示了如何自定义输入组件。

import gradio as grdef greet(name):    return "Hello " + name + "!"demo = gr.Interface(    fn=greet,    # 自定义输入框    # 具体设置方法查看官方文档    inputs=gr.Textbox(lines=3, placeholder="Name Here...",label="my input"),    outputs="text",)demo.launch()

多输入和多输出组件设置

对于复杂程序,输入列表中的每个组件按顺序对应于函数的一个参数。输出列表中的每个组件按顺序排列对应于函数返回的一个值。

import gradio as gr# 该函数有3个输入参数和2个输出参数def greet(name, is_morning, temperature):    salutation = "Good morning" if is_morning else "Good evening"    greeting = f"{salutation} {name}. It is {temperature} degrees today"    celsius = (temperature - 32) * 5 / 9    return greeting, round(celsius, 2)demo = gr.Interface(    fn=greet,    # 按照处理程序设置输入组件    inputs=["text", "checkbox", gr.Slider(0, 100)],    # 按照处理程序设置输出组件    outputs=["text", "number"],)demo.launch()

代码运行处理结果如下图所示:

在这里插入图片描述

图像组件

Gradio支持许多类型的组件,如image、dataframe、video。使用示例如下:

import numpy as npimport gradio as grdef sepia(input_img):        # 处理图像    sepia_filter = np.array([        [0.393, 0.769, 0.189],         [0.349, 0.686, 0.168],         [0.272, 0.534, 0.131]    ])    sepia_img = input_img.dot(sepia_filter.T)    sepia_img /= sepia_img.max()    return sepia_img# shape设置输入图像大小demo = gr.Interface(sepia, gr.Image(shape=(200, 200)), "image")demo.launch()

当使用Image组件作为输入时,函数将收到一个维度为(w,h,3)的numpy数组,按照RGB的通道顺序排列。要注意的是,我们的输入图像组件带有一个编辑按钮🖉,可以对图像进行裁剪和放大。以这种方式处理图像可以帮助揭示机器学习模型中的偏差或隐藏的缺陷。此外对于输入组件有个shape参数,指的设置输入图像大小。但是处理方式是保持长宽比的情况下,将图像最短边缩放为指定长度,然后按照中心裁剪方式裁剪最长边到指定长度。当图像不大的情况,一种更好的方式是不设置shape,这样直接传入原图。输入组件Image也可以设置输入类型type,比如type=filepath设置传入处理图像的路径。具体可以查看官方文档,😁文档写的很清楚。
代码运行处理结果如下图所示:

在这里插入图片描述

1.1.2 Blocks构建应用

相比Interface,Blocks提供了一个低级别的API,用于设计具有更灵活布局和数据流的网络应用。Blocks允许控制组件在页面上出现的位置,处理复杂的数据流(例如,输出可以作为其他函数的输入),并根据用户交互更新组件的属性可见性。

简单应用

import gradio as grdef greet(name):    return "Hello " + name + "!"with gr.Blocks() as demo:    # 设置输入组件    name = gr.Textbox(label="Name")    # 设置输出组件    output = gr.Textbox(label="Output Box")    # 设置按钮    greet_btn = gr.Button("Greet")    # 设置按钮点击事件    greet_btn.click(fn=greet, inputs=name, outputs=output)demo.launch()

Blocks方式需要with语句添加组件,如果不设置布局方式,那么组件将按照创建的顺序垂直出现在应用程序中,运行界面如下图示:

在这里插入图片描述

多模块应用

该例子介绍了一个多模块应用,其中各个模块的使用可以查看官方文档。

import numpy as npimport gradio as grdef flip_text(x):    return x[::-1]def flip_image(x):    return np.fliplr(x)with gr.Blocks() as demo:    # 用markdown语法编辑输出一段话    gr.Markdown("Flip text or image files using this demo.")    # 设置tab选项卡    with gr.Tab("Flip Text"):        #  Blocks特有组件,设置所有子组件按垂直排列        # 垂直排列是默认情况,不加也没关系        with gr.Column():            text_input = gr.Textbox()            text_output = gr.Textbox()            text_button = gr.Button("Flip")    with gr.Tab("Flip Image"):        # Blocks特有组件,设置所有子组件按水平排列        with gr.Row():            image_input = gr.Image()            image_output = gr.Image()        image_button = gr.Button("Flip")    # 设置折叠内容    with gr.Accordion("Open for More!"):        gr.Markdown("Look at me...")    text_button.click(flip_text, inputs=text_input, outputs=text_output)    image_button.click(flip_image, inputs=image_input, outputs=image_output)demo.launch()

1.2 关键特性

示例输入

下面的代码提供了创建一个简单计算器的模板。该程序运行后,点击flag按钮会在运行目录下指定flagged/logs.csv保存输入记录。保存目录的修改和相关信息设置查看官方文档flagging项。

import gradio as gr# 一个简单计算器def calculator(num1, operation, num2):    if operation == "add":        return num1 + num2    elif operation == "subtract":        return num1 - num2    elif operation == "multiply":        return num1 * num2    elif operation == "divide":        if num2 == 0:            # 设置报错弹窗            raise gr.Error("Cannot divide by zero!")        return num1 / num2demo = gr.Interface(    calculator,    # 设置输入    [        "number",         gr.Radio(["add", "subtract", "multiply", "divide"]),        "number"    ],    # 设置输出    "number",    # 设置输入参数示例    examples=[        [5, "add", 3],        [4, "divide", 2],        [-4, "multiply", 2.5],        [0, "subtract", 1.2],    ],    # 设置网页标题    title="Toy Calculator",    # 左上角的描述文字    description="Here's a sample toy calculator. Enjoy!",    # 左下角的文字    article = "Check out the examples",)demo.launch()

样式和排队

Gradio官方文档,搜索不同的组件加.style(如image.style),可以获取该组件的样式参数设置样例。例如image组件的设置如下:

img = gr.Image("lion.jpg").style(height='24', rounded=False)

如果函数推理时间较长,比如目标检测;或者应用程序处理流量过大,则需要使用queue方法进行排队。queue方法使用websockets,可以防止网络超时。使用方式如下:

demo = gr.Interface(...).queue()demo.launch()# 或with gr.Blocks() as demo:    #...demo.queue()demo.launch()

生成器

在某些情况下,你可能想显示一连串的输出,而不是单一的输出。例如,你可能有一个图像生成模型,如果你想显示在每个步骤中生成的图像,从而得到最终的图像。在这种情况下,你可以向Gradio提供一个生成器函数,而不是一个常规函数。下面是一个生成器的例子,每隔1秒返回1张图片。

import gradio as grimport numpy as npimport time# 生成steps张图片,每隔1秒钟返回def fake_diffusion(steps):    for _ in range(steps):        time.sleep(1)        image = np.random.randint(255, size=(300, 600, 3))        yield imagedemo = gr.Interface(fake_diffusion,                     # 设置滑窗,最小值为1,最大值为10,初始值为3,每次改动增减1位                    inputs=gr.Slider(1, 10, value=3, step=1),                     outputs="image")# 生成器必须要queue函数demo.queue()demo.launch()

1.3 应用分享

互联网分享

如果运行环境能够连接互联网,在launch函数中设置share参数为True,那么运行程序后。Gradio的服务器会提供XXXXX.gradio.app地址。通过其他设备,比如手机或者笔记本电脑,都可以访问该应用。这种方式下该链接只是本地服务器的代理,不会存储通过本地应用程序发送的任何数据。这个链接在有效期内是免费的,好处就是不需要自己搭建服务器,坏处就是太慢了,毕竟数据经过别人的服务器。

demo.launch(share=True)

Hugging Face托管

如果想长期免费分享应用,可以在Hugging Face中托管应用或者其他的一些方式,详细使用见分享Gradio应用。Hugging Face提供了多个基于Gradio搭建应用的demo,详情使用见Using Hugging Face Integrations

局域网分享

通过设置server_name=‘0.0.0.0’(表示使用本机ip),server_port(可不改,默认值是7860)。那么可以通过本机ip:端口号在局域网内分享应用。

# show_error为True表示在控制台显示错误信息。demo.launch(server_name='0.0.0.0', server_port=8080, show_error=True)

密码验证

在首次打开网页前,可以设置账户密码。比如auth参数为(账户,密码)的元组数据。这种模式下不能够使用queue函数。

demo.launch(auth=("admin", "pass1234"))

如果想设置更为复杂的账户密码和密码提示,可以通过函数设置校验规则。

# 账户和密码相同就可以通过def same_auth(username, password):    return username == passworddemo.launch(auth=same_auth,auth_message="username and password must be the same")

2 进阶使用

2.1 interface进阶使用

2.1.1 interface状态

全局变量

全局变量的好处就是在调用函数后仍然能够保存,例如在机器学习中通过全局变量从外部加载一个大型模型,并在函数内部使用它,以便每次函数调用都不需要重新加载模型。下面就展示了全局变量使用的好处。

import gradio as grscores = []def track_score(score):    scores.append(score)    # 返回分数top3    top_scores = sorted(scores, reverse=True)[:3]    return top_scoresdemo = gr.Interface(    track_score,     gr.Number(label="Score"),     gr.JSON(label="Top Scores"))demo.launch()

会话状态

Gradio支持的另一种数据持久性是会话状态,数据在一个页面会话中的多次提交中持久存在。然而,数据不会在你模型的不同用户之间共享。会话状态的典型例子就是聊天机器人,你想访问用户之前提交的信息,但你不能将聊天记录存储在一个全局变量中,因为那样的话,聊天记录会在不同的用户之间乱成一团。注意该状态会在每个页面内的提交中持续存在,但如果您在另一个标签页中加载该演示(或刷新页面),该演示将不会共享聊天历史。

要在会话状态下存储数据,你需要做三件事。

  • 在你的函数中传入一个额外的参数,它代表界面的状态。
  • 在函数的最后,将状态的更新值作为一个额外的返回值返回。
  • 在添加输入和输出时添加state组件。
import randomimport gradio as grdef chat(message, history):    history = history or []    message = message.lower()    if message.startswith("how many"):        response = random.randint(1, 10)    elif message.startswith("how"):        response = random.choice(["Great", "Good", "Okay", "Bad"])    elif message.startswith("where"):        response = random.choice(["Here", "There", "Somewhere"])    else:        response = "I don't know"    history.append((message, response))    return history, history# 设置一个对话窗chatbot = gr.Chatbot().style(color_map=("green", "pink"))demo = gr.Interface(    chat,    # 添加state组件    ["text", "state"],    [chatbot, "state"],    # 设置没有保存数据的按钮    allow_flagging="never",)demo.launch()

2.1.2 interface交互

实时变化

在Interface中设置live=True,则输出会跟随输入实时变化。这个时候界面不会有submit按钮,因为不需要手动提交输入。

import gradio as grdef calculator(num1, operation, num2):    if operation == "add":        return num1 + num2    elif operation == "subtract":        return num1 - num2    elif operation == "multiply":        return num1 * num2    elif operation == "divide":        return num1 / num2demo = gr.Interface(    calculator,    [        "number",        gr.Radio(["add", "subtract", "multiply", "divide"]),        "number"    ],    "number",    live=True,)demo.launch()

流模式

在许多情形下,我们的输入是实时视频流或者音频流,那么意味这数据不停地发送到后端,这是可以采用streaming模式处理数据。

import gradio as grimport numpy as npdef flip(im):    return np.flipud(im)demo = gr.Interface(    flip,     gr.Image(source="webcam", streaming=True),     "image",    live=True)demo.launch()

2.2 Blocks进阶使用

2.2.1 Blocks事件

可交互设置

任何输入的组件内容都是可编辑的,而输出组件默认是不能编辑的。如果想要使得输出组件内容可编辑,设置interactive=True即可。

import gradio as grdef greet(name):    return "Hello " + name + "!"with gr.Blocks() as demo:    name = gr.Textbox(label="Name")    # 不可交互    # output = gr.Textbox(label="Output Box")    # 可交互    output = gr.Textbox(label="Output", interactive=True)    greet_btn = gr.Button("Greet")    greet_btn.click(fn=greet, inputs=name, outputs=output)demo.launch()

事件设置

我们可以为不同的组件设置不同事件,如为输入组件添加change事件。可以进一步查看官方文档,看看组件还有哪些事件。

import gradio as grdef welcome(name):    return f"Welcome to Gradio, {name}!"with gr.Blocks() as demo:    gr.Markdown(    """    # Hello World!    Start typing below to see the output.    """)    inp = gr.Textbox(placeholder="What is your name?")    out = gr.Textbox()    # 设置change事件    inp.change(fn = welcome, inputs = inp, outputs = out)demo.launch()

多个数据流

如果想处理多个数据流,只要设置相应的输入输出组件即可。

import gradio as grdef increase(num):    return num + 1with gr.Blocks() as demo:    a = gr.Number(label="a")    b = gr.Number(label="b")    # 要想b>a,则使得b = a+1    atob = gr.Button("b > a")    atob.click(increase, a, b)    # 要想a>b,则使得a = b+1    btoa = gr.Button("a > b")    btoa.click(increase, b, a)demo.launch()

多输出值处理

下面的例子展示了输出多个值时,以列表形式表现的处理方式。

import gradio as grwith gr.Blocks() as demo:    food_box = gr.Number(value=10, label="Food Count")    status_box = gr.Textbox()    def eat(food):        if food > 0:            return food - 1, "full"        else:            return 0, "hungry"    gr.Button("EAT").click(        fn=eat,         inputs=food_box,        # 根据返回值改变输入组件和输出组件        outputs=[food_box, status_box]    )demo.launch()

下面的例子展示了输出多个值时,以字典形式表现的处理方式。

import gradio as grwith gr.Blocks() as demo:    food_box = gr.Number(value=10, label="Food Count")    status_box = gr.Textbox()    def eat(food):        if food > 0:            return {food_box: food - 1, status_box: "full"}        else:            return {status_box: "hungry"}    gr.Button("EAT").click(        fn=eat,         inputs=food_box,        outputs=[food_box, status_box]    )demo.launch()

组件配置修改

事件监听器函数的返回值通常是相应的输出组件的更新值。有时我们也想更新组件的配置,比如说可见性。在这种情况下,我们可以通过返回update函数更新组件的配置。

import gradio as grdef change_textbox(choice):    # 根据不同输入对输出控件进行更新    if choice == "short":        return gr.update(lines=2, visible=True, value="Short story: ")    elif choice == "long":        return gr.update(lines=8, visible=True, value="Long story...")    else:        return gr.update(visible=False)with gr.Blocks() as demo:    radio = gr.Radio(        ["short", "long", "none"], label="Essay Length to Write?"    )    text = gr.Textbox(lines=2, interactive=True)    radio.change(fn=change_textbox, inputs=radio, outputs=text)demo.launch()

2.2.2 Blocks布局

Blocks应用的是html中的flexbox模型布局,默认情况下组件垂直排列。

组件水平排列

使用Row函数会将组件按照水平排列,但是在Row函数块里面的组件都会保持同等高度。

import gradio as grwith gr.Blocks() as demo:    with gr.Row():        img1 = gr.Image()        text1 = gr.Text()    btn1 = gr.Button("button")demo.launch()

组件垂直排列与嵌套

组件通常是垂直排列,我们可以通过Row函数和Column函数生成不同复杂的布局。

import gradio as grwith gr.Blocks() as demo:    with gr.Row():        text1 = gr.Textbox(label="t1")        slider2 = gr.Textbox(label="s2")        drop3 = gr.Dropdown(["a", "b", "c"], label="d3")    with gr.Row():        # scale与相邻列相比的相对宽度。例如,如果列A的比例为2,列B的比例为1,则A的宽度将是B的两倍。        # min_width设置最小宽度,防止列太窄        with gr.Column(scale=2, min_width=600):            text1 = gr.Textbox(label="prompt 1")            text2 = gr.Textbox(label="prompt 2")            inbtw = gr.Button("Between")            text4 = gr.Textbox(label="prompt 1")            text5 = gr.Textbox(label="prompt 2")        with gr.Column(scale=1, min_width=600):            img1 = gr.Image("test.jpg")            btn = gr.Button("Go")demo.launch()

组件可视化

如下所示,我们可以通过visible和update函数构建更为复杂的应用。

import gradio as grwith gr.Blocks() as demo:    # 出错提示框    error_box = gr.Textbox(label="Error", visible=False)    # 输入框    name_box = gr.Textbox(label="Name")    age_box = gr.Number(label="Age")    symptoms_box = gr.CheckboxGroup(["Cough", "Fever", "Runny Nose"])    submit_btn = gr.Button("Submit")    # 输出不可见    with gr.Column(visible=False) as output_col:        diagnosis_box = gr.Textbox(label="Diagnosis")        patient_summary_box = gr.Textbox(label="Patient Summary")    def submit(name, age, symptoms):        if len(name) == 0:            return {error_box: gr.update(value="Enter name", visible=True)}        if age < 0 or age > 200:            return {error_box: gr.update(value="Enter valid age", visible=True)}        return {            output_col: gr.update(visible=True),            diagnosis_box: "covid" if "Cough" in symptoms else "flu",            patient_summary_box: f"{name}, {age} y/o"        }    submit_btn.click(        submit,        [name_box, age_box, symptoms_box],        [error_box, diagnosis_box, patient_summary_box, output_col],    )demo.launch()

组件渲染

在某些情况下,您可能希望在实际在UI中呈现组件之前定义组件。例如,您可能希望在相应的gr.Textbox输入上方显示使用gr.examples的示例部分。由于gr.Examples需要输入组件对象作为参数,因此您需要先定义输入组件,然后在定义gr.Exmples对象后再进行渲染。解决方法是在gr.Blocks()范围外定义gr.Textbox,并在UI中希望放置的任何位置使用组件的.render()方法。

import gradio as grinput_textbox = gr.Textbox()with gr.Blocks() as demo:    # 提供示例输入给input_textbox,示例输入以嵌套列表形式设置    gr.Examples(["hello", "bonjour", "merhaba"], input_textbox)    # render函数渲染input_textbox    input_textbox.render()demo.launch()

2.2.3 样式修改

自定义css

要获得额外的样式功能,您可以设置行内css属性将任何样式给应用程序。如下所示。

import gradio as gr# 修改blocks的背景颜色with gr.Blocks(css=".gradio-container {background-color: red}") as demo:    box1 = gr.Textbox(value="Good Job")    box2 = gr.Textbox(value="Failure")demo.launch()

元素选择

您可以向任何组件添加HTML元素。通过elem_id选择对应的css元素。

import gradio as gr# 这里用的是id属性设置with gr.Blocks(css="#warning {background-color: red}") as demo:    box1 = gr.Textbox(value="Good Job", elem_id="warning")    box2 = gr.Textbox(value="Failure")    box3 = gr.Textbox(value="None", elem_id="warning")demo.launch()

3 参考

来源地址:https://blog.csdn.net/LuohenYJ/article/details/127489768

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

[python] 基于Gradio可视化部署机器学习应用

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Gradio机器学习模型快速部署工具应用分享

这篇文章主要为大家介绍了Gradio机器学习模型快速部署工具应用分享原文翻译,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-14

Gradio机器学习模型快速部署工具怎么应用

这篇文章主要介绍了Gradio机器学习模型快速部署工具怎么应用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Gradio机器学习模型快速部署工具怎么应用文章都会有所收获,下面我们一起来看看吧。1.嵌入 IFra
2023-07-05

Gradio机器学习模型快速部署工具应用分享前篇

这篇文章主要为大家介绍了Gradio机器学习模型快速部署工具应用分享前篇,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-14

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录