《Python实战进阶》No.41: 使用 Streamlit 快速构建 ML 应用
Python实战进阶 No.41: 使用 Streamlit 快速构建 ML 应用
摘要
Streamlit 是一个专为数据科学家和机器学习工程师设计的开源框架,它允许您在几分钟内创建精美的交互式应用程序。本文将介绍 Streamlit 的核心功能,并通过两个实战案例展示如何构建基于 ChatGPT API 的聊天助手以及结合 EasyOCR 的 AI 家庭教师应用。无论您是想快速展示机器学习模型的效果,还是为非技术用户提供友好的交互界面,Streamlit 都是一个理想的选择。
核心概念和知识点
Streamlit 的基本组件与布局
Streamlit 提供了丰富的组件,使您能够轻松创建交互式界面:
- 文本元素:
st.title()
,st.header()
,st.subheader()
,st.text()
,st.markdown()
- 输入组件:
st.text_input()
,st.number_input()
,st.slider()
,st.selectbox()
- 媒体元素:
st.image()
,st.audio()
,st.video()
- 布局组件:
st.sidebar
,st.columns()
,st.expander()
- 状态组件:
st.progress()
,st.spinner()
,st.success()
,st.error()
Streamlit 采用自上而下的布局方式,代码的执行顺序决定了界面元素的排列顺序。
import streamlit as st
st.title("我的第一个Streamlit应用")
st.header("这是一个标题")
st.subheader("这是一个子标题")
# 侧边栏
with st.sidebar:
st.header("侧边栏")
option = st.selectbox("选择一个选项", ["选项1", "选项2", "选项3"])
# 列布局
col1, col2 = st.columns(2)
with col1:
st.write("这是第一列")
st.button("点击我")
with col2:
st.write("这是第二列")
st.checkbox("勾选我")
数据可视化与模型集成
Streamlit 支持多种数据可视化库,包括 Matplotlib、Plotly、Altair 等,使数据展示变得简单直观:
import streamlit as st
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import plotly.express as px
# 创建示例数据
data = pd.DataFrame({
'日期': pd.date_range('20230101', periods=100),
'值': np.random.randn(100).cumsum()
})
# Matplotlib 可视化
st.subheader("Matplotlib 图表")
fig, ax = plt.subplots()
ax.plot(data['日期'], data['值'])
ax.set_title("随机数据趋势")
st.pyplot(fig)
# Plotly 可视化
st.subheader("Plotly 图表")
fig = px.line(data, x='日期', y='值', title="交互式数据趋势")
st.plotly_chart(fig)
对于机器学习模型的集成,Streamlit 提供了简单的方式来加载模型并处理用户输入:
import streamlit as st
import joblib
import pandas as pd
# 加载预训练模型(示例)
@st.cache_resource
def load_model():
return joblib.load('model.pkl')
model = load_model()
# 用户输入
st.header("预测界面")
feature1 = st.slider("特征1", 0.0, 10.0, 5.0)
feature2 = st.slider("特征2", 0.0, 10.0, 5.0)
# 预测
if st.button("预测"):
input_data = pd.DataFrame([[feature1, feature2]], columns=['特征1', '特征2'])
prediction = model.predict(input_data)
st.success(f"预测结果: {prediction[0]}")
应用部署与分享
Streamlit 应用可以通过多种方式部署和分享:
- 本地运行:
streamlit run app.py
- Streamlit Cloud:免费托管平台,直接连接到 GitHub 仓库
- Docker 容器化:便于在任何环境中部署
- 云服务提供商:如 AWS、GCP、Azure 等
部署示例(使用 Streamlit Cloud):
- 将代码推送到 GitHub 仓库
- 访问 Streamlit Cloud
- 连接您的 GitHub 账户并选择仓库
- 点击部署
实战案例
案例一:基于 ChatGPT API 的聊天助手
让我们构建一个简单但功能完整的 ChatGPT 聊天助手:
import streamlit as st
import openai
import time
# 设置页面配置
st.set_page_config(page_title="ChatGPT 助手", page_icon="💬", layout="wide")
# 初始化会话状态
if "messages" not in st.session_state:
st.session_state.messages = []
# 设置 OpenAI API 密钥
openai.api_key = st.secrets["openai_api_key"] # 在Streamlit Cloud中设置密钥
# 页面标题
st.title("💬 ChatGPT 智能助手")
st.markdown("这是一个基于 ChatGPT API 的智能聊天助手,可以回答您的各种问题。")
# 侧边栏配置
with st.sidebar:
st.header("设置")
model = st.selectbox("选择模型", ["gpt-3.5-turbo", "gpt-4"])
temperature = st.slider("创造性 (Temperature)", min_value=0.0, max_value=1.0, value=0.7, step=0.1)
st.header("会话")
if st.button("清空会话"):
st.session_state.messages = []
st.experimental_rerun()
# 显示聊天历史
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# 用户输入
prompt = st.chat_input("请输入您的问题...")
# 处理用户输入
if prompt:
# 添加用户消息到历史
st.session_state.messages.append({"role": "user", "content": prompt})
# 显示用户消息
with st.chat_message("user"):
st.markdown(prompt)
# 显示助手消息(带加载动画)
with st.chat_message("assistant"):
message_placeholder = st.empty()
full_response = ""
# 调用 OpenAI API
try:
response = openai.ChatCompletion.create(
model=model,
messages=[{"role": m["role"], "content": m["content"]} for m in st.session_state.messages],
temperature=temperature,
stream=True
)
# 模拟打字效果
for chunk in response:
if chunk.choices and chunk.choices[0].delta.get("content"):
content = chunk.choices[0].delta.content
full_response += content
message_placeholder.markdown(full_response + "▌")
time.sleep(0.01)
message_placeholder.markdown(full_response)
# 添加助手回复到历史
st.session_state.messages.append({"role": "assistant", "content": full_response})
except Exception as e:
st.error(f"发生错误: {str(e)}")
运行效果:
- 用户可以在输入框中输入问题
- ChatGPT 会实时生成回答,并显示打字效果
- 聊天历史会被保存,用户可以随时清空会话
- 侧边栏允许用户调整模型和创造性参数
案例二:基于 EasyOCR 和 Streamlit 的 AI 家庭教师
这个应用将结合 EasyOCR 进行图像文字识别,并使用 ChatGPT 回答关于识别内容的问题:
import streamlit as st
import easyocr
import numpy as np
import cv2
import openai
import tempfile
import os
from PIL import Image
# 设置页面配置
st.set_page_config(page_title="AI 家庭教师", page_icon="🧠", layout="wide")
# 初始化 OpenAI API
openai.api_key = st.secrets["openai_api_key"]
# 初始化 EasyOCR
@st.cache_resource
def load_ocr_reader():
return easyocr.Reader(['ch_sim', 'en']) # 支持中文和英文
reader = load_ocr_reader()
# 初始化会话状态
if "chat_history" not in st.session_state:
st.session_state.chat_history = []
if "ocr_text" not in st.session_state:
st.session_state.ocr_text = ""
if "current_image" not in st.session_state:
st.session_state.current_image = None
# 页面标题
st.title("🧠 AI 家庭教师")
st.markdown("上传图片或拍照,AI 将识别内容并回答您的问题!")
# 创建两列布局
col1, col2 = st.columns([1, 1])
# 第一列:图像上传和处理
with col1:
st.header("图像输入")
# 选项卡:上传图片或拍照
tab1, tab2 = st.tabs(["上传图片", "拍照"])
with tab1:
uploaded_file = st.file_uploader("选择一张图片", type=["jpg", "jpeg", "png"])
if uploaded_file is not None:
# 保存上传的图片
image = Image.open(uploaded_file)
st.session_state.current_image = np.array(image)
st.image(image, caption="上传的图片", use_column_width=True)
with tab2:
camera_image = st.camera_input("拍摄图片")
if camera_image is not None:
# 保存拍摄的图片
image = Image.open(camera_image)
st.session_state.current_image = np.array(image)
st.image(image, caption="拍摄的图片", use_column_width=True)
# 处理图像按钮
if st.button("识别图像文字") and st.session_state.current_image is not None:
with st.spinner("正在识别文字..."):
# 使用 EasyOCR 识别文字
results = reader.readtext(st.session_state.current_image)
# 提取文本
extracted_text = ""
for (bbox, text, prob) in results:
extracted_text += text + " "
st.session_state.ocr_text = extracted_text
# 在图像上标记识别的文字
annotated_image = st.session_state.current_image.copy()
for (bbox, text, prob) in results:
# 转换坐标为整数
top_left = tuple(map(int, bbox[0]))
bottom_right = tuple(map(int, bbox[2]))
# 在图像上绘制边界框
cv2.rectangle(annotated_image, top_left, bottom_right, (0, 255, 0), 2)
# 添加文本标签
cv2.putText(annotated_image, text, (top_left[0], top_left[1] - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
# 显示标记后的图像
st.image(annotated_image, caption="识别结果", use_column_width=True)
# 显示识别的文字
st.subheader("识别的文字")
st.write(extracted_text)
# 第二列:聊天界面
with col2:
st.header("AI 助教聊天")
# 显示识别的文字
if st.session_state.ocr_text:
with st.expander("识别的文字内容", expanded=False):
st.write(st.session_state.ocr_text)
# 显示聊天历史
chat_container = st.container()
with chat_container:
for message in st.session_state.chat_history:
if message["role"] == "user":
st.markdown(f"**你**: {message['content']}")
else:
st.markdown(f"**AI 助教**: {message['content']}")
# 用户输入
user_question = st.text_input("请输入您的问题...", key="user_input")
if st.button("发送") and user_question:
# 添加用户问题到历史
st.session_state.chat_history.append({"role": "user", "content": user_question})
# 构建 ChatGPT 的系统提示
system_prompt = "你是一位专业的AI家庭教师,擅长解答学生的问题。"
if st.session_state.ocr_text:
system_prompt += f"以下是从图像中识别的文字内容,请基于这些内容回答问题:\n{st.session_state.ocr_text}"
# 构建消息列表
messages = [{"role": "system", "content": system_prompt}]
for message in st.session_state.chat_history:
messages.append({"role": message["role"], "content": message["content"]})
# 调用 ChatGPT API
with st.spinner("AI 正在思考..."):
try:
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=messages,
temperature=0.7,
max_tokens=1000
)
# 获取回复
ai_response = response.choices[0].message.content
# 添加回复到历史
st.session_state.chat_history.append({"role": "assistant", "content": ai_response})
# 重新加载页面以显示新消息
st.experimental_rerun()
except Exception as e:
st.error(f"发生错误: {str(e)}")
# 侧边栏:设置和操作
with st.sidebar:
st.header("操作")
if st.button("清空聊天记录"):
st.session_state.chat_history = []
st.experimental_rerun()
if st.button("清空图像和识别结果"):
st.session_state.current_image = None
st.session_state.ocr_text = ""
st.experimental_rerun()
st.header("关于")
st.markdown("""
**AI 家庭教师** 结合了:
- EasyOCR 进行文字识别
- ChatGPT 提供智能回答
- Streamlit 构建交互界面
适用于学生拍摄课本、作业等内容,获取即时解答和辅导。
""")
运行效果:
- 用户可以上传图片或使用摄像头拍照
- EasyOCR 识别图片中的文字,并在图像上标记
- 用户可以基于识别的内容向 AI 助教提问
- AI 助教(ChatGPT)会根据识别的内容和用户问题提供回答
- 完整的聊天历史记录和清空功能
与 AI 大模型的相关性
Streamlit 与 AI 大模型的结合具有强大的协同效应:
-
快速原型开发:Streamlit 允许数据科学家和 AI 研究人员快速构建模型演示,无需前端开发经验。
-
模型可解释性:通过交互式界面,用户可以调整参数并实时观察模型输出,增强对 AI 模型的理解。
-
降低使用门槛:为非技术用户提供友好的界面,使他们能够轻松使用复杂的 AI 模型。
-
API 集成简化:如我们的案例所示,Streamlit 可以轻松集成 OpenAI、Hugging Face 等 AI 服务的 API。
-
多模态应用:支持文本、图像、音频等多种输入形式,适合构建多模态 AI 应用。
在我们的实战案例中,我们展示了如何将 ChatGPT 和 EasyOCR 这样的 AI 模型无缝集成到 Streamlit 应用中,创建了既实用又易用的 AI 工具。
总结
Streamlit 是一个强大而简洁的工具,特别适合数据科学家和机器学习工程师快速构建交互式应用。通过本文的学习,我们掌握了:
- Streamlit 的基本组件和布局系统
- 如何在 Streamlit 中集成数据可视化和机器学习模型
- 应用部署与分享的方法
- 如何构建实用的 AI 应用,如 ChatGPT 聊天助手和 OCR 识别助教
Streamlit 的优势在于其简单性和快速开发能力,使得从想法到应用的过程变得异常高效。对于想要展示 AI 模型或数据分析结果的开发者来说,Streamlit 是一个不可多少的工具。
扩展思考
Streamlit 在企业级应用中的局限性
虽然 Streamlit 非常适合原型开发和小型应用,但在企业级应用中可能面临一些挑战:
-
性能限制:Streamlit 应用在每次交互时会重新运行整个脚本,这可能导致大型应用的性能问题。
-
定制化限制:虽然 Streamlit 提供了丰富的组件,但与成熟的前端框架相比,定制化能力较弱。
-
并发处理:Streamlit 不是为高并发负载设计的,在多用户同时访问时可能出现性能瓶颈。
-
状态管理:复杂应用中的状态管理相对简单,可能不足以支持复杂的业务逻辑。
-
安全性考虑:企业级应用通常需要更严格的安全措施,而 Streamlit 在这方面的功能相对基础。
如何结合前端框架提升用户体验
为了克服 Streamlit 的一些限制,可以考虑以下策略:
-
混合架构:使用 Streamlit 作为快速原型和内部工具,而将面向客户的产品用 React、Vue 等框架开发。
-
API 后端:将 Streamlit 应用的核心功能封装为 API(使用 FastAPI 或 Flask),然后由前端框架调用。
-
组件扩展:利用 Streamlit 的自定义组件功能,集成 React 组件以增强交互性。
-
微前端架构:将 Streamlit 应用嵌入到更大的微前端架构中,各司其职。
-
渐进式迁移:随着应用成熟,可以逐步将关键功能迁移到更适合生产环境的技术栈。
示例:创建 React 组件并在 Streamlit 中使用:
import streamlit as st
import streamlit.components.v1 as components
# 定义一个简单的 React 组件
react_component = """
<div id="react-root"></div>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script>
const e = React.createElement;
class FancyButton extends React.Component {
constructor(props) {
super(props);
this.state = { clicked: false };
}
render() {
return e(
'button',
{
onClick: () => {
this.setState({ clicked: true });
// 与 Streamlit 通信
if (window.Streamlit) {
window.Streamlit.setComponentValue('按钮被点击了');
}
},
style: {
padding: '10px 20px',
background: this.state.clicked ? '#4CAF50' : '#2196F3',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
transition: 'all 0.3s'
}
},
this.state.clicked ? '已点击!' : '点击我'
);
}
}
ReactDOM.render(
e(FancyButton),
document.getElementById('react-root')
);
</script>
"""
# 在 Streamlit 中使用 React 组件
st.title("Streamlit 与 React 集成示例")
# 显示 React 组件
component_value = components.html(react_component, height=100)
# 显示组件返回的值
if component_value:
st.write(f"组件返回值: {component_value}")
通过这种方式,我们可以在保持 Streamlit 简单性的同时,利用前端框架的强大功能来增强用户体验。
总之,Streamlit 是一个强大的工具,特别适合快速开发和原型设计。通过了解其优势和局限性,我们可以在适当的场景中充分发挥其价值,同时在必要时结合其他技术来构建更完善的应用。