Gradio 快速搭建 ML Demo 教程¶
一句话说明¶
Gradio 让你三行代码就能把机器学习模型变成一个可交互的网页 Demo,面试时直接甩链接展示项目,比 PPT 酷一百倍。
Gradio 是什么?¶
白话解释¶
想象你训练了一个"猫狗分类器"模型,想让别人试用——传统做法是写前端页面+后端 API,至少折腾两天。而 Gradio 就是一个"模型展示柜":你把模型函数丢进去,它自动生成一个漂亮的网页界面,用户上传图片就能看到预测结果。
三行代码示例¶
import gradio as gr # 导入 Gradio
demo = gr.Interface(fn=lambda x: x, inputs="text", outputs="text") # 创建界面
demo.launch() # 启动网页
Gradio vs 23 篇的 Streamlit 有什么不同?¶
| 维度 | Gradio(本篇) | Streamlit(23 篇已讲) |
|---|---|---|
| 定位 | ML 模型 Demo(输入→预测→输出) | 数据仪表盘/数据 App |
| 核心模式 | 函数式:一个函数搞定 | 脚本式:从上到下写代码 |
| 最擅长 | 模型推理演示、API 生成 | 数据探索、图表仪表盘 |
| 部署平台 | HuggingFace Spaces(一键) | Streamlit Cloud |
| 学习曲线 | 极低(三行代码上手) | 低(但需要了解响应式) |
简单记忆:要展示模型 → 用 Gradio;要做数据看板 → 用 Streamlit。
核心概念¶
1. Interface(接口)—— 最简单的方式¶
Interface 是 Gradio 的高层 API,只需要三个参数:
白话:Interface 就像一个"自动售货机"——你告诉它"投币口长什么样"(inputs)、"出货口长什么样"(outputs)、"中间怎么处理"(fn),它自动帮你搭好整个机器。
2. Blocks(块)—— 灵活布局¶
Blocks 是 Gradio 的低层 API,允许你自由控制布局、组合组件:
with gr.Blocks() as demo: # 用 with 语句创建一个 Blocks
gr.Markdown("# 标题") # 放一个标题
with gr.Row(): # 水平排列
inp = gr.Textbox() # 左边放输入框
out = gr.Textbox() # 右边放输出框
btn = gr.Button("提交") # 放一个按钮
btn.click(fn=my_func, inputs=inp, outputs=out) # 绑定事件
白话:Interface 是"自动售货机",Blocks 是"乐高积木"——你可以自由拼出任何形状的界面。
3. Component(组件)—— 输入输出的零件¶
常用组件一览:
| 组件 | 用途 | 示例场景 |
|---|---|---|
gr.Textbox | 文本输入/输出 | 情感分析、翻译 |
gr.Image | 图片输入/输出 | 图像分类、风格迁移 |
gr.Audio | 音频输入/输出 | 语音识别 |
gr.Slider | 滑块 | 调节参数(阈值、温度) |
gr.Dropdown | 下拉菜单 | 选择模型/数据集 |
gr.File | 文件上传 | 上传 FASTA/CSV |
gr.Dataframe | 表格 | 展示分析结果 |
gr.Plot | Matplotlib/Plotly 图 | 展示可视化 |
gr.Label | 分类标签 | 显示预测概率 |
gr.Chatbot | 聊天界面 | 对话机器人 |
4. Event(事件)—— 触发动作¶
在 Blocks 中,每个组件都可以绑定事件:
btn.click(fn=处理函数, inputs=[输入组件], outputs=[输出组件]) # 点击按钮触发
textbox.change(fn=处理函数, inputs=[输入组件], outputs=[输出组件]) # 内容变化触发
textbox.submit(fn=处理函数, inputs=[输入组件], outputs=[输出组件]) # 按回车触发
安装配置¶
# 推荐在 bioinfo 环境中安装(截至 2025 年 6 月最新版 6.14.0)
conda activate bioinfo
# 安装 Gradio(需要 Python >= 3.10)
pip install gradio
# 验证安装
python -c "import gradio as gr; print(f'Gradio 版本: {gr.__version__}')"
# 预期输出: Gradio 版本: 6.14.0
# 可选:安装常用 ML 库(后续 Demo 需要)
pip install scikit-learn transformers torch pillow matplotlib
注意:Gradio 6.x 需要 Python >= 3.10,如果你的环境是 3.8/3.9,需要使用
pip install gradio==4.44.1(v4 最后版本)。
实操教程¶
Demo 1:Hello World(入门)¶
# === gradio_hello.py ===
# 最简单的 Gradio Demo:输入名字,输出问候语
import gradio as gr # 导入 Gradio 库
def greet(name):
"""接收用户名字,返回问候语"""
return f"你好,{name}!欢迎体验 Gradio Demo!"
# 创建 Interface
# fn: 处理函数
# inputs: 输入组件类型("text" 是 gr.Textbox 的简写)
# outputs: 输出组件类型
demo = gr.Interface(
fn=greet, # 处理函数
inputs=gr.Textbox( # 输入:文本框
label="请输入你的名字", # 显示标签
placeholder="例如:小明" # 占位提示
),
outputs=gr.Textbox( # 输出:文本框
label="问候语" # 显示标签
),
title="Hello World Demo", # 页面标题
description="输入你的名字,获取一句问候!" # 页面描述
)
# 启动 Demo(会在浏览器自动打开 http://127.0.0.1:7860)
demo.launch()
运行:
Demo 2:文本分类(情感分析)¶
# === gradio_text_clf.py ===
# 用 scikit-learn 做一个简单的中文情感分析 Demo
import gradio as gr # 导入 Gradio
import re # 正则表达式,用于简单分词
# 定义一个简单的基于关键词的情感分析器
# (实际项目中会用训练好的模型,这里为了演示简化)
POSITIVE_WORDS = {"好", "棒", "优秀", "喜欢", "开心", "不错", "满意", "推荐", "赞", "优质"}
NEGATIVE_WORDS = {"差", "烂", "糟糕", "难用", "讨厌", "失望", "垃圾", "坑", "后悔", "恶心"}
def sentiment_analysis(text):
"""
对输入文本做情感分析
返回:字典格式 {标签: 概率},Gradio 的 Label 组件会自动显示为概率条
"""
if not text.strip(): # 如果输入为空
return {"请输入文本": 1.0}
words = set(text) # 简单按字拆分(中文场景)
pos_count = len(words & POSITIVE_WORDS) # 统计正面词数量
neg_count = len(words & NEGATIVE_WORDS) # 统计负面词数量
total = pos_count + neg_count # 总匹配数
if total == 0: # 没有匹配到任何情感词
return {"正面": 0.5, "负面": 0.5} # 返回中性
pos_score = pos_count / total # 计算正面比例
neg_score = neg_count / total # 计算负面比例
return {"正面": pos_score, "负面": neg_score} # 返回概率字典
# 创建 Demo
demo = gr.Interface(
fn=sentiment_analysis, # 处理函数
inputs=gr.Textbox( # 输入:文本框
label="输入评论文本",
placeholder="例如:这个产品真的很棒,推荐购买!",
lines=3 # 文本框高度为 3 行
),
outputs=gr.Label( # 输出:标签(自动显示概率条)
label="情感分析结果",
num_top_classes=2 # 显示前 2 个类别
),
title="中文情感分析 Demo",
description="输入一段中文评论,分析其情感倾向(正面/负面)",
examples=[ # 预设示例(用户可以直接点击)
["这个产品真的很棒,推荐购买!"],
["太差了,用了两天就坏了,非常失望"],
["还行吧,没什么特别的感觉"]
]
)
demo.launch()
Demo 3:图像分类¶
# === gradio_image_clf.py ===
# 用预训练的 MobileNet 做图像分类 Demo
import gradio as gr # 导入 Gradio
import torch # PyTorch
from torchvision import transforms, models # 图像变换和预训练模型
import json # 解析标签文件
import urllib.request # 下载标签文件
# ---------- 模型加载(只执行一次) ----------
model = models.mobilenet_v2(pretrained=True) # 加载预训练的 MobileNet V2
model.eval() # 切换到推理模式(关闭 dropout 等)
# 下载 ImageNet 标签(1000 个类别名称)
url = "https://raw.githubusercontent.com/pytorch/hub/master/imagenet_classes.txt"
try:
labels = urllib.request.urlopen(url).read().decode("utf-8").splitlines()
except Exception:
labels = [f"class_{i}" for i in range(1000)] # 下载失败时用占位标签
# 图像预处理管道(和 MobileNet 训练时一致)
preprocess = transforms.Compose([
transforms.Resize(256), # 短边缩放到 256
transforms.CenterCrop(224), # 中心裁剪 224x224
transforms.ToTensor(), # 转为 Tensor(0-1)
transforms.Normalize( # ImageNet 标准化
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
),
])
def classify_image(image):
"""
对上传的图像做分类预测
image: PIL.Image 对象(Gradio 自动转换)
返回: {类别名: 概率} 字典
"""
if image is None: # 未上传图像
return {}
img_tensor = preprocess(image).unsqueeze(0) # 预处理 + 增加 batch 维度
with torch.no_grad(): # 不计算梯度(省内存)
outputs = model(img_tensor) # 前向传播
probs = torch.nn.functional.softmax(outputs[0], dim=0) # Softmax 得概率
top5_probs, top5_indices = torch.topk(probs, 5) # 取概率最高的 5 个
result = {}
for prob, idx in zip(top5_probs, top5_indices):
result[labels[idx.item()]] = prob.item() # {类别名: 概率}
return result
# 创建 Demo
demo = gr.Interface(
fn=classify_image, # 分类函数
inputs=gr.Image( # 输入:图像
type="pil", # 传入 PIL.Image 格式
label="上传图片"
),
outputs=gr.Label( # 输出:分类标签
label="分类结果",
num_top_classes=5 # 显示 Top 5
),
title="图像分类 Demo(MobileNet V2)",
description="上传一张图片,MobileNet V2 模型会预测它是什么",
)
demo.launch()
Demo 4:聊天机器人¶
# === gradio_chatbot.py ===
# 用 Gradio 的 ChatInterface 搭建一个简单聊天机器人
import gradio as gr # 导入 Gradio
import random # 随机数(模拟回复)
def chatbot_response(message, history):
"""
聊天机器人的回复函数
message: 用户当前输入的消息(字符串)
history: 历史对话记录(Gradio 自动管理)
返回: 机器人的回复(字符串)
"""
# 简单的规则回复(实际项目中替换为 LLM API 调用)
message_lower = message.lower()
if "你好" in message or "hello" in message_lower:
return "你好!我是生信小助手,有什么可以帮你的?"
elif "基因" in message:
return "基因(Gene)是编码蛋白质的 DNA 片段。你想了解基因的哪方面?比如基因表达、基因突变、基因组学?"
elif "宏基因组" in message:
return "宏基因组(Metagenomics)是直接从环境样本(如粪便、土壤)提取所有微生物的 DNA 进行测序分析。常用工具有 MetaPhlAn、HUMAnN、Kraken2 等。"
elif "帮助" in message or "help" in message_lower:
return "我可以回答生信相关问题,试试问我:\n- 什么是宏基因组?\n- 基因是什么?\n- 推荐学习资源"
else:
responses = [
f"这是个好问题!关于'{message}',你可以查看相关文献了解更多。",
f"'{message}'是一个有趣的话题,建议在 PubMed 上搜索相关论文。",
"抱歉,这个问题超出了我的知识范围,建议查阅专业教材。"
]
return random.choice(responses) # 随机选一个回复
# 用 ChatInterface(聊天专用的高层 API)
demo = gr.ChatInterface(
fn=chatbot_response, # 回复函数
title="生信小助手", # 标题
description="一个简单的生信问答聊天机器人",
examples=["你好", "什么是宏基因组?", "基因是什么?"], # 预设问题
theme="soft" # 使用柔和主题
)
demo.launch()
Demo 5:多输入多输出¶
# === gradio_multi_io.py ===
# 多输入多输出示例:BMI 计算器 + 健康建议
import gradio as gr # 导入 Gradio
import matplotlib # 导入 matplotlib
matplotlib.use("Agg") # 使用非交互后端(服务器环境必须)
import matplotlib.pyplot as plt # 绑定图表绘制
import numpy as np # 数值计算
def calculate_bmi(height_cm, weight_kg, age, gender):
"""
多输入函数:接收身高、体重、年龄、性别
多输出:BMI 文本、健康等级标签、BMI 分布图
"""
if height_cm <= 0 or weight_kg <= 0:
return "请输入有效的身高和体重", {}, None
height_m = height_cm / 100 # 厘米转米
bmi = weight_kg / (height_m ** 2) # BMI = 体重 / 身高²
# 判断 BMI 等级
if bmi < 18.5:
category = "偏瘦"
advice = "建议适当增加营养摄入"
elif bmi < 24:
category = "正常"
advice = "继续保持健康的生活方式"
elif bmi < 28:
category = "偏胖"
advice = "建议适当控制饮食并增加运动"
else:
category = "肥胖"
advice = "建议咨询医生制定健康计划"
# 文本输出
text_result = f"身高: {height_cm}cm | 体重: {weight_kg}kg | 性别: {gender}\n"
text_result += f"BMI: {bmi:.1f} | 等级: {category}\n"
text_result += f"建议: {advice}"
# 标签输出(概率条形式展示各等级)
label_result = {
"偏瘦": max(0, 1 - abs(bmi - 16) / 10),
"正常": max(0, 1 - abs(bmi - 21) / 10),
"偏胖": max(0, 1 - abs(bmi - 26) / 10),
"肥胖": max(0, 1 - abs(bmi - 32) / 10),
}
# 绘制 BMI 分布图(matplotlib)
fig, ax = plt.subplots(figsize=(6, 3)) # 创建图
categories = ["偏瘦\n<18.5", "正常\n18.5-24", "偏胖\n24-28", "肥胖\n>28"]
colors = ["#3498db", "#2ecc71", "#f39c12", "#e74c3c"]
ranges = [18.5, 24, 28, 40]
ax.barh(categories, ranges, color=colors, alpha=0.6) # 水平条形图
ax.axvline(x=bmi, color="red", linewidth=2, label=f"你的 BMI: {bmi:.1f}") # 标记线
ax.set_xlabel("BMI 值")
ax.legend()
ax.set_title(f"BMI 评估结果 ({gender}, {age}岁)")
plt.tight_layout()
return text_result, label_result, fig # 返回三个输出
# 创建多输入多输出 Demo
demo = gr.Interface(
fn=calculate_bmi,
inputs=[ # 多个输入组件(列表)
gr.Slider( # 身高滑块
minimum=100, maximum=220,
value=170, step=1,
label="身高 (cm)"
),
gr.Slider( # 体重滑块
minimum=30, maximum=150,
value=65, step=0.5,
label="体重 (kg)"
),
gr.Number( # 年龄数字框
value=25,
label="年龄"
),
gr.Radio( # 性别单选
choices=["男", "女"],
value="男",
label="性别"
),
],
outputs=[ # 多个输出组件(列表)
gr.Textbox(label="计算结果"), # 文本输出
gr.Label(label="BMI 等级分布"), # 标签输出
gr.Plot(label="BMI 分布图"), # 图表输出
],
title="BMI 健康计算器",
description="输入身高、体重、年龄和性别,获取 BMI 分析报告",
)
demo.launch()
Demo 6:自定义主题¶
# === gradio_theme.py ===
# 自定义 Gradio 主题示例
import gradio as gr # 导入 Gradio
# ---------- 方式 1:使用内置主题 ----------
# Gradio 内置主题:Base, Default, Origin, Monochrome, Soft, Ocean, Glass
# demo = gr.Interface(..., theme=gr.themes.Soft())
# demo = gr.Interface(..., theme="soft") # 字符串简写也行
# ---------- 方式 2:自定义主题颜色 ----------
custom_theme = gr.themes.Soft(
primary_hue="teal", # 主色调:青色(默认 orange)
secondary_hue="blue", # 次色调:蓝色
neutral_hue="slate", # 中性色:灰石色
font=gr.themes.GoogleFont("Noto Sans SC"), # 使用思源黑体(支持中文)
)
def echo(text):
return f"你输入了:{text}"
# 使用自定义主题
demo = gr.Interface(
fn=echo,
inputs=gr.Textbox(label="输入"),
outputs=gr.Textbox(label="输出"),
title="自定义主题演示",
description="使用 Teal + Blue 配色方案的自定义主题",
theme=custom_theme # 应用自定义主题
)
demo.launch()
生信实战¶
实战 1:基因序列分类器 Demo¶
# === gradio_gene_classifier.py ===
# 生信实战:基因序列 GC 含量分析 + 类型预测
import gradio as gr # 导入 Gradio
def analyze_gene_sequence(sequence, seq_type):
"""
分析基因序列:计算 GC 含量、长度、碱基组成,预测序列类型
sequence: DNA/RNA 序列字符串
seq_type: 序列类型(DNA/RNA)
"""
# 清洗序列:去空格、换行,统一大写
seq = sequence.upper().replace(" ", "").replace("\n", "")
seq = ''.join(c for c in seq if c in "ATCGUN") # 只保留有效碱基
if len(seq) < 10:
return "序列太短(至少10个碱基)", {}, ""
length = len(seq) # 序列长度
# 碱基统计
base_counts = {base: seq.count(base) for base in "ATCGUN"}
valid_bases = {k: v for k, v in base_counts.items() if v > 0}
# GC 含量计算
gc_count = seq.count("G") + seq.count("C")
gc_content = gc_count / length * 100 # GC 百分比
# 基于 GC 含量的简单分类预测
# (真实场景用训练好的分类模型,这里用规则演示)
if gc_content > 60:
prediction = {"嗜热菌基因": 0.6, "高GC革兰氏阳性菌": 0.3, "普通基因": 0.1}
elif gc_content > 40:
prediction = {"普通基因": 0.6, "人类基因": 0.25, "嗜热菌基因": 0.15}
else:
prediction = {"AT富集区": 0.5, "启动子区域": 0.3, "普通基因": 0.2}
# 格式化结果
report = f"=== 序列分析报告 ===\n"
report += f"序列类型: {seq_type}\n"
report += f"序列长度: {length} bp\n"
report += f"GC 含量: {gc_content:.1f}%\n"
report += f"碱基组成: {valid_bases}\n"
report += f"\n=== AT/GC 比例 ===\n"
at_count = seq.count("A") + seq.count("T") + seq.count("U")
report += f"AT(U): {at_count} ({at_count/length*100:.1f}%)\n"
report += f"GC: {gc_count} ({gc_content:.1f}%)\n"
return report, prediction, seq[:50] + "..." if length > 50 else seq
# 创建生信 Demo
demo = gr.Interface(
fn=analyze_gene_sequence,
inputs=[
gr.Textbox( # DNA/RNA 序列输入
label="输入基因序列",
placeholder="粘贴 DNA/RNA 序列,例如:ATCGATCGATCG...",
lines=5
),
gr.Radio( # 序列类型选择
choices=["DNA", "RNA"],
value="DNA",
label="序列类型"
),
],
outputs=[
gr.Textbox(label="分析报告"), # 文本报告
gr.Label(label="序列来源预测"), # 分类预测
gr.Textbox(label="清洗后序列预览"), # 处理后序列
],
title="基因序列分析器",
description="输入 DNA/RNA 序列,自动分析 GC 含量、碱基组成,并预测序列来源",
examples=[
["ATCGATCGATCGATCGATCGATCG", "DNA"],
["GCGCGCGCGCGCGCGCGCGCGCGC", "DNA"],
["AUGCUAGCUAGCUAGCUAGCUAGC", "RNA"],
],
)
demo.launch()
实战 2:蛋白质结构特征预测 Demo¶
# === gradio_protein_demo.py ===
# 生信实战:蛋白质序列基本特征分析
import gradio as gr # 导入 Gradio
import matplotlib # 导入 matplotlib
matplotlib.use("Agg") # 非交互后端
import matplotlib.pyplot as plt # 绑定图表绘制
import numpy as np # 数值计算
# 氨基酸分子量表(单位:Da)
AA_MW = {
'A': 89.1, 'R': 174.2, 'N': 132.1, 'D': 133.1, 'C': 121.2,
'E': 147.1, 'Q': 146.2, 'G': 75.0, 'H': 155.2, 'I': 131.2,
'L': 131.2, 'K': 146.2, 'M': 149.2, 'F': 165.2, 'P': 115.1,
'S': 105.1, 'T': 119.1, 'W': 204.2, 'Y': 181.2, 'V': 117.1,
}
# 氨基酸疏水性(Kyte-Doolittle 标度)
AA_HYDRO = {
'A': 1.8, 'R': -4.5, 'N': -3.5, 'D': -3.5, 'C': 2.5,
'E': -3.5, 'Q': -3.5, 'G': -0.4, 'H': -3.2, 'I': 4.5,
'L': 3.8, 'K': -3.9, 'M': 1.9, 'F': 2.8, 'P': -1.6,
'S': -0.8, 'T': -0.7, 'W': -0.9, 'Y': -1.3, 'V': 4.2,
}
def analyze_protein(sequence, window_size):
"""
分析蛋白质序列的基本理化特征
sequence: 氨基酸单字母序列
window_size: 疏水性滑窗大小
"""
# 清洗序列
seq = sequence.upper().replace(" ", "").replace("\n", "")
seq = ''.join(c for c in seq if c in AA_MW) # 只保留标准氨基酸
if len(seq) < 5:
return "序列太短", None
# 基本统计
length = len(seq)
mw = sum(AA_MW.get(aa, 0) for aa in seq) - (length - 1) * 18.02 # 减去缩合水
aa_counts = {aa: seq.count(aa) for aa in set(seq)}
# 疏水性滑窗分析
window = int(window_size)
hydro_profile = [] # 疏水性曲线
for i in range(len(seq) - window + 1):
window_seq = seq[i:i + window] # 取窗口内的氨基酸
avg_hydro = np.mean([AA_HYDRO.get(aa, 0) for aa in window_seq])
hydro_profile.append(avg_hydro)
# 绘制疏水性图谱
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 6))
# 子图1:疏水性曲线
ax1.plot(hydro_profile, color='steelblue', linewidth=1.2)
ax1.axhline(y=0, color='red', linestyle='--', alpha=0.5) # 零线
ax1.fill_between(range(len(hydro_profile)), hydro_profile, 0,
where=[h > 0 for h in hydro_profile],
alpha=0.3, color='orange', label='疏水区')
ax1.fill_between(range(len(hydro_profile)), hydro_profile, 0,
where=[h <= 0 for h in hydro_profile],
alpha=0.3, color='blue', label='亲水区')
ax1.set_xlabel('残基位置')
ax1.set_ylabel('疏水性 (Kyte-Doolittle)')
ax1.set_title(f'疏水性图谱 (窗口={window})')
ax1.legend()
# 子图2:氨基酸组成饼图
top_aa = sorted(aa_counts.items(), key=lambda x: -x[1])[:8] # 取前8
labels = [f"{aa} ({count})" for aa, count in top_aa]
sizes = [count for _, count in top_aa]
ax2.pie(sizes, labels=labels, autopct='%1.0f%%', startangle=90)
ax2.set_title('氨基酸组成(Top 8)')
plt.tight_layout()
# 文本报告
report = f"=== 蛋白质特征分析 ===\n"
report += f"长度: {length} aa\n"
report += f"分子量: {mw:.1f} Da ({mw/1000:.1f} kDa)\n"
report += f"平均疏水性: {np.mean([AA_HYDRO.get(aa, 0) for aa in seq]):.2f}\n"
report += f"预测类型: {'膜蛋白(富含疏水区)' if np.mean([AA_HYDRO.get(aa,0) for aa in seq]) > 0 else '可溶蛋白(亲水为主)'}\n"
return report, fig
# 创建 Demo
demo = gr.Interface(
fn=analyze_protein,
inputs=[
gr.Textbox(
label="输入蛋白质序列(氨基酸单字母码)",
placeholder="例如:MVLSPADKTNVKAAWGKVGAHAGEYGAEALERMFLSFPTTKTYFPHFDLSH",
lines=4
),
gr.Slider(
minimum=5, maximum=21, value=9, step=2,
label="疏水性分析窗口大小"
),
],
outputs=[
gr.Textbox(label="特征分析报告"),
gr.Plot(label="疏水性图谱 & 氨基酸组成"),
],
title="蛋白质序列特征分析",
description="输入蛋白质氨基酸序列,分析分子量、疏水性图谱、氨基酸组成等特征",
examples=[
["MVLSPADKTNVKAAWGKVGAHAGEYGAEALERMFLSFPTTKTYFPHFDLSH", 9],
["MALWMRLLPLLALLALWGPDPAAAFVNQHLCGSHLVEALYLVCGERGFFYTPKTRREAEDLQVGQVELGGGPGAGSLQPLALEGSLQKRGIVEQCCTSICSLYQLENYCN", 11],
],
)
demo.launch()
实战 3:物种丰度可视化 Demo¶
# === gradio_abundance.py ===
# 生信实战:微生物物种丰度可视化(宏基因组方向)
import gradio as gr # 导入 Gradio
import matplotlib # 导入 matplotlib
matplotlib.use("Agg") # 非交互后端
import matplotlib.pyplot as plt # 绑定图表绘制
import numpy as np # 数值计算
import io # 字节流
# 模拟物种丰度数据(真实场景从 MetaPhlAn/Kraken2 结果读取)
MOCK_DATA = {
"健康组": {
"Bacteroides": 25.3, "Faecalibacterium": 18.7, "Prevotella": 12.1,
"Roseburia": 8.5, "Bifidobacterium": 7.2, "Akkermansia": 5.8,
"Eubacterium": 4.3, "Ruminococcus": 3.9, "其他": 14.2,
},
"T2D 患者": {
"Bacteroides": 32.1, "Faecalibacterium": 8.2, "Prevotella": 6.5,
"Roseburia": 4.1, "Bifidobacterium": 3.8, "Akkermansia": 2.1,
"Eubacterium": 2.9, "Ruminococcus": 5.7, "其他": 34.6,
},
}
def plot_abundance(group, chart_type, top_n):
"""
根据选择的分组和图表类型绘制物种丰度图
"""
top_n = int(top_n)
if group == "对比两组":
# 绘制对比柱状图
fig, ax = plt.subplots(figsize=(10, 5))
species = list(MOCK_DATA["健康组"].keys())[:top_n]
x = np.arange(len(species))
width = 0.35
healthy = [MOCK_DATA["健康组"].get(s, 0) for s in species]
t2d = [MOCK_DATA["T2D 患者"].get(s, 0) for s in species]
ax.bar(x - width/2, healthy, width, label="健康组", color="#2ecc71")
ax.bar(x + width/2, t2d, width, label="T2D 患者", color="#e74c3c")
ax.set_xlabel("物种")
ax.set_ylabel("相对丰度 (%)")
ax.set_title("健康组 vs T2D 患者 肠道菌群丰度对比")
ax.set_xticks(x)
ax.set_xticklabels(species, rotation=45, ha="right", fontsize=8)
ax.legend()
report = "=== 关键差异 ===\n"
for s in species:
diff = MOCK_DATA["T2D 患者"].get(s, 0) - MOCK_DATA["健康组"].get(s, 0)
direction = "升高" if diff > 0 else "降低"
report += f"{s}: T2D 组{direction} {abs(diff):.1f}%\n"
else:
data = MOCK_DATA[group]
sorted_items = sorted(data.items(), key=lambda x: -x[1])[:top_n]
species = [item[0] for item in sorted_items]
abundances = [item[1] for item in sorted_items]
fig, ax = plt.subplots(figsize=(8, 5))
if chart_type == "柱状图":
colors = plt.cm.Set3(np.linspace(0, 1, len(species)))
ax.bar(species, abundances, color=colors)
ax.set_ylabel("相对丰度 (%)")
plt.xticks(rotation=45, ha="right", fontsize=8)
elif chart_type == "饼图":
ax.pie(abundances, labels=species, autopct='%1.1f%%', startangle=90)
else: # 堆叠条形图
ax.barh(species[::-1], abundances[::-1],
color=plt.cm.Pastel1(np.linspace(0, 1, len(species))))
ax.set_xlabel("相对丰度 (%)")
ax.set_title(f"{group} - 肠道菌群 Top{top_n} 物种丰度")
report = f"=== {group} 物种丰度 ===\n"
for s, a in sorted_items:
report += f"{s}: {a:.1f}%\n"
plt.tight_layout()
return report, fig
# 创建 Demo
demo = gr.Interface(
fn=plot_abundance,
inputs=[
gr.Dropdown( # 分组选择
choices=["健康组", "T2D 患者", "对比两组"],
value="对比两组",
label="选择分组"
),
gr.Radio( # 图表类型
choices=["柱状图", "饼图", "水平条形图"],
value="柱状图",
label="图表类型"
),
gr.Slider( # Top N
minimum=3, maximum=9, value=7, step=1,
label="显示 Top N 物种"
),
],
outputs=[
gr.Textbox(label="丰度数据"),
gr.Plot(label="丰度可视化"),
],
title="肠道菌群物种丰度可视化",
description="基于 T2D(2型糖尿病)宏基因组数据,可视化肠道菌群物种丰度分布",
)
demo.launch()
Gradio vs Streamlit 详细对比¶
| 对比维度 | Gradio | Streamlit |
|---|---|---|
| 定位 | ML 模型 Demo / API | 数据应用 / 仪表盘 |
| 代码量 | 3-10 行起步 | 10-30 行起步 |
| 核心 API | gr.Interface() / gr.Blocks() | st.write() / st.sidebar() |
| 输入处理 | 声明式(定义组件类型即可) | 命令式(手动写 widget) |
| 自动生成 API | 自动生成 REST API | 需要额外配置 |
| 分享 | share=True 生成临时公网链接 | 需部署到 Streamlit Cloud |
| HuggingFace | 原生支持 Spaces | 也支持,但不如 Gradio 紧密 |
| 状态管理 | 简单(函数输入输出) | st.session_state(较复杂) |
| 聊天界面 | gr.ChatInterface 一行搞定 | 需手动拼 st.chat_message |
| 实时更新 | 事件驱动 | 每次交互重跑整个脚本 |
| 学习曲线 | 极低 | 低 |
| 最适合 | 模型推理、论文 Demo、API | 数据分析报告、BI 看板 |
选择建议: - 面试展示模型 → Gradio(三行代码 + HuggingFace 一键部署) - 做数据看板/报告 → Streamlit(23 篇已讲) - 两者都会是加分项
部署到 HuggingFace Spaces¶
HuggingFace Spaces 是 Gradio 应用的首选部署平台,免费且无需服务器。
步骤 1:创建 Space¶
# 方式 1:网页创建
# 访问 https://huggingface.co/new-space
# Space name: 输入项目名(如 gene-sequence-classifier)
# SDK: 选择 Gradio
# 点击 Create Space
# 方式 2:命令行(需要先 pip install huggingface_hub)
pip install huggingface_hub
huggingface-cli login # 输入你的 HuggingFace token
步骤 2:准备文件¶
你的 Space 仓库至少需要两个文件:
app.py(Gradio 应用主文件):
# === app.py ===
# HuggingFace Spaces 会自动运行这个文件
import gradio as gr
def greet(name):
return f"Hello, {name}!"
demo = gr.Interface(fn=greet, inputs="text", outputs="text")
demo.launch() # 不需要指定端口,Spaces 会自动处理
requirements.txt(Python 依赖):
步骤 3:推送部署¶
# 克隆你的 Space 仓库
git clone https://huggingface.co/spaces/你的用户名/项目名
cd 项目名
# 放入 app.py 和 requirements.txt
# ... 复制文件 ...
# 推送即部署
git add .
git commit -m "Initial commit"
git push
# 几分钟后访问 https://huggingface.co/spaces/你的用户名/项目名 即可看到
也可以用 share=True 快速分享¶
# 在本地开发时,一行代码生成公网链接(72 小时有效)
demo.launch(share=True)
# 输出类似:Running on public URL: https://xxxxx.gradio.live
常见报错与解决¶
报错 1:ModuleNotFoundError: No module named 'gradio'¶
原因:没有安装 Gradio 或环境不对
解决:
conda activate bioinfo # 确认激活了正确的 conda 环境
pip install gradio # 安装 Gradio
pip install --upgrade gradio # 或升级到最新版
报错 2:OSError: Cannot find empty port in range: 7860-7860¶
原因:端口 7860 被占用(上一个 Gradio 进程没关)
解决:
# Linux/WSL 下查找并杀掉占用进程
lsof -i :7860
kill -9 <PID>
# 或者指定其他端口
demo.launch(server_port=7861)
报错 3:ValueError: Unknown component: xxx¶
原因:使用了不存在的组件名称或 API 变动
解决:
# Gradio 6.x 中很多组件名做了调整
# 错误写法(旧版):inputs="textbox"
# 正确写法(新版):inputs=gr.Textbox()
# 查看官方文档确认组件名:https://www.gradio.app/docs
报错 4:RuntimeError: CUDA out of memory¶
原因:加载的模型太大,GPU 显存不够
解决:
# 方法 1:使用 CPU
model = model.to("cpu")
# 方法 2:使用更小的模型
model = models.mobilenet_v2(pretrained=True) # 而不是 resnet152
# 方法 3:在 HuggingFace Spaces 申请 GPU(免费额度有限)
报错 5:gr.Interface has no attribute 'queue'¶
原因:Gradio 6.x 中 queue 已默认启用,不再需要手动调用
解决:
# 旧版写法(Gradio 4.x):
demo.queue().launch()
# 新版写法(Gradio 6.x):
demo.launch() # queue 默认开启,直接 launch 即可
报错 6:中文显示乱码(matplotlib 图表)¶
原因:matplotlib 默认字体不支持中文
解决:在代码开头加上:
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans'] # 优先用黑体
matplotlib.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# WSL 中可能需要安装字体:
sudo apt install fonts-wqy-microhei
速查表¶
组件速查¶
| 组件 | 代码 | 功能 |
|---|---|---|
| 文本框 | gr.Textbox(label="xx") | 文本输入/输出 |
| 数字 | gr.Number(value=0) | 数字输入 |
| 滑块 | gr.Slider(0, 100, value=50) | 范围选择 |
| 下拉框 | gr.Dropdown(choices=["a","b"]) | 下拉选择 |
| 单选 | gr.Radio(choices=["a","b"]) | 单选按钮 |
| 多选 | gr.CheckboxGroup(choices=["a","b"]) | 多选框 |
| 复选框 | gr.Checkbox(label="同意") | 单个开关 |
| 图片 | gr.Image(type="pil") | 图片输入/输出 |
| 音频 | gr.Audio(type="filepath") | 音频输入/输出 |
| 视频 | gr.Video() | 视频输入/输出 |
| 文件 | gr.File(label="上传") | 文件上传 |
| 表格 | gr.Dataframe() | 表格展示 |
| 图表 | gr.Plot() | Matplotlib/Plotly 图 |
| 标签 | gr.Label() | 分类概率展示 |
| 画廊 | gr.Gallery() | 图片画廊 |
| 聊天 | gr.Chatbot() | 聊天记录展示 |
| Markdown | gr.Markdown("## 标题") | 富文本展示 |
| 按钮 | gr.Button("提交") | 触发操作 |
| 代码 | gr.Code(language="python") | 代码编辑器 |
| HTML | gr.HTML("<b>粗体</b>") | 自定义 HTML |
| JSON | gr.JSON() | JSON 可视化 |
常用操作速查¶
# ---------- 快速启动 ----------
demo.launch() # 默认 http://127.0.0.1:7860
demo.launch(share=True) # 生成公网链接(72小时)
demo.launch(server_port=8080) # 自定义端口
demo.launch(server_name="0.0.0.0") # 允许外部访问
# ---------- Interface 常用参数 ----------
gr.Interface(
fn=my_func, # 处理函数
inputs=[...], # 输入组件(列表=多输入)
outputs=[...], # 输出组件(列表=多输出)
title="标题", # 页面标题
description="描述", # 页面描述
examples=[[...]], # 预设示例
theme="soft", # 主题(soft/default/monochrome等)
live=True, # 输入变化即时触发(不用点按钮)
flagging_mode="never", # 关闭 flag 功能
)
# ---------- Blocks 常用布局 ----------
with gr.Blocks() as demo:
gr.Markdown("# 标题")
with gr.Tab("标签页1"): # 标签页
...
with gr.Tab("标签页2"):
...
with gr.Row(): # 水平排列
with gr.Column(scale=2): # 左列(占 2 份宽度)
...
with gr.Column(scale=1): # 右列(占 1 份宽度)
...
with gr.Accordion("展开详情"): # 可折叠区域
...
# ---------- ChatInterface ----------
gr.ChatInterface(
fn=chat_func, # fn(message, history) -> response
type="messages", # 消息格式(Gradio 6.x 默认)
examples=["你好", "帮助"], # 预设消息
title="聊天机器人",
)
# ---------- 事件绑定(Blocks 中) ----------
btn.click(fn=func, inputs=[a], outputs=[b]) # 点击
textbox.submit(fn=func, inputs=[a], outputs=[b]) # 回车
textbox.change(fn=func, inputs=[a], outputs=[b]) # 内容变化
slider.release(fn=func, inputs=[a], outputs=[b]) # 松开滑块
# ---------- 主题 ----------
gr.themes.Base() # 基础(蓝色)
gr.themes.Default() # 默认(橙色)
gr.themes.Soft() # 柔和
gr.themes.Monochrome() # 黑白
gr.themes.Origin() # 原始
gr.themes.Ocean() # 海洋
gr.themes.Glass() # 玻璃
延伸资源¶
| 资源 | 说明 |
|---|---|
| Gradio 官方文档 | 组件 API、参数详解 |
| Gradio Guides | 官方教程,从入门到进阶 |
| HuggingFace Spaces | 浏览别人的 Gradio Demo 学习 |
| Gradio GitHub | 源码 + Issue 讨论 |
| awesome-demos | HF 上最受欢迎的 Demo 合集 |
| 本项目 23 篇 Plotly+Streamlit | 数据可视化方向,与本篇互补 |
| 本项目 13 篇 AlphaFold | 蛋白质结构预测,可结合 Gradio 展示 |
| 本项目 49 篇 PyTorch 入门 | 深度学习模型训练,训练完用 Gradio 展示 |
面试加分技巧:把研究项目(T2D 肠道菌群分类器)用 Gradio 包装成 Demo,部署到 HuggingFace Spaces,面试时直接甩链接。面试官能亲手操作你的模型,比说一百句"我做了XX分析"都有说服力。