今天詳解一個 Python/ target=_blank class=infotextkey>Python 庫 Streamlit,它可以為機器學習和數據分析構建 web App。它的優勢是入門容易、純 Python 編碼、開發效率高、UI精美。
上圖是用 Streamlit 構建自動駕駛模型效果的 demo,左側是模型的參數,右側是模型的效果。通過調整左側參數,右邊的模型會實時地響應。
由此可以看出,對于交互式的數據可視化需求,完全可以考慮用 Streamlit 實現。特別是在學習、工作匯報的時候,用它的效果遠好于 PPT。
因為 Streamlit 提供了很多前端交互的組件,所以也可以用它來做一些簡單的web 應用。今天我們也會用它來做個垃圾分類的 web app。
,時長00:16
之前我們用 Streamlit 做過兩個app,《植物識別app》和《動物識別app》。但只是用了 Streamlit 一小部分功能。今天我們就按照 Streamlit 官網文檔,對其做個詳解。
1
文本組件
我使用的是 Python 3.8 環境,執行 pip install streamlit 安裝。安裝后執行 streamlit hello 檢查是否安裝成功。
先來了解下 Streamlit 最基礎的文本組件。
文本組件是用來在網頁上展示各種類型的文本內容。Streamlit 可以展示純文本、Markdown、標題、代碼和LaTeX公式。
import streamlit as st
# markdown
st.markdown('Streamlit is **_really_ cool**.')
# 設置網頁標題
st.title('This is a title')
# 展示一級標題
st.header('This is a header')
# 展示二級標題
st.subheader('This is a subheader')
# 展示代碼,有高亮效果
code = '''def hello():
print("Hello, Streamlit!")'''
st.code(code, language='python')
# 純文本
st.text('This is some text.')
# LaTeX 公式
st.latex(r'''
a + ar + a r^2 + a r^3 + cdots + a r^{n-1} =
sum_{k=0}^{n-1} ar^k =
a left(frac{1-r^{n}}{1-r}right)
''')
上述是 Streamlit 支持的文本展示組件,代碼存放 my_code.py 文件中。編碼完成后,執行 streamlit run my_code.py ,streamlit 會啟動 web 服務,加載指定的源文件。
啟動后,可以看到命令行打印出以下信息
streamlit run garbage_classifier.py
You can now view your Streamlit app in your browser.
Local URL: http://localhost:8501
在瀏覽器訪問 http://localhost:8501/ 即可。
當源代碼被修改,無需重啟服務,在頁面上點擊刷新按鈕就可加載最新的代碼,運行和調試都非常方便。
2
數據組件
dataframe 和 table 組件可以展示表格。
import streamlit as st
import pandas as pd
import numpy as np
df = pd.DataFrame(
np.random.randn(50, 5),
columns=('col %d' % i for i in range(5)))
# 交互式表格
st.dataframe(df)
# 靜態表格
st.table(df)
dateframe 和 table 的區別是,前者可以在表格上做交互(如:排序),后者只是靜態的展示。它們所展示的數據類型包括 pandas.DataFrame、pandas.Styler、pyarrow.Table、numpy.ndarray、Iterable、dict。
metric 組件用來展示指標的變化,數據分析中經常會用到。
st.metric(label="Temperature", value="70 °F", delta="1.2 °F")
value 參數表示當前指標值,delta 參數表示與前值的差值,向上的綠色箭頭代表相比于前值,是漲的,反之向下的紅箭頭代表相比于前值是跌的。當然漲跌顏色可以通過 delta_color 參數來控制。
json 組件用來展示 json 類型數據
st.json({
'foo': 'bar',
'stuff': [
'stuff 1',
'stuff 2',
],
})
Streamlit 會將 json 數據格式化,展示地更美觀,并且提供交互,可以展開、收起 json 的子節點。
3
圖表組件
Streamlit 的圖表組件包含兩部分,一部分是原生組件,另一部分是渲染第三方庫。
原生組件只包含 4 個圖表,line_chart、area_chart 、bar_chart 和 map,分別展示折線圖、面積圖、柱狀圖和地圖。
chart_data = pd.DataFrame(
np.random.randn(20, 3),
columns=['a', 'b', 'c'])
st.line_chart(chart_data)
上述是 line_chart 的示例,其他圖表的使用方法與之類似。
Streamlit 圖表可設置的參數很少,除了數據源外,剩下只能設置圖表的寬度和高度。
雖然 Streamlit 原生圖表少,但它可以將其他 Python 可視化庫的圖表展示在 Streamlit 頁面上。支持的可視化庫包括:matplotlib.pyplot、Altair、vega-lite、Plotly、Bokeh、PyDeck、Graphviz。
以 matplotlib.pyplot 為例,使用方式如下:
import matplotlib.pyplot as plt
arr = np.random.normal(1, 1, size=100)
fig, ax = plt.subplots()
ax.hist(arr, bins=20)
st.pyplot(fig)
跟直接寫 matplotlib.pyplot 一樣,只不過最終展示的時候調用 st.pyplot 可以將圖表展示出來 Streamlit 頁面上。其他 Python 庫的使用方法與之類似。
4
輸入組件
前面我們介紹的三類組件都是輸出類、展示類的。對于交互式的頁面來說,接受用戶的輸入是必不可少的。
Streamlit 提供的輸入組件都是基本的,都是我們在網站、移動APP上經常看到的。包括:
- button:按鈕
- download_button:文件下載
- file_uploader:文件上傳
- checkbox:復選框
- radio:單選框
- selectbox:下拉單選框
- multiselect:下拉多選框
- slider:滑動條
- select_slider:選擇條
- text_input:文本輸入框
- text_area:文本展示框
- number_input:數字輸入框,支持加減按鈕
- date_input:日期選擇框
- time_input:時間選擇框
- color_picker:顏色選擇器
它們包含一些公共的參數:
- label:組件上展示的內容(如:按鈕名稱)
- key:當前頁面唯一標識一個組件
- help:鼠標放在組件上展示說明信息
- on_click / on_change:組件發生交互(如:輸入、點擊)后的回調函數
- args:回調函數的參數
- kwargs:回調函數的參數
下面以 selectbox 來演示輸入組件的用法
option = st.selectbox(
'下拉框',
('選項一', '選項二', '選項三'))
st.write('選擇了:', option)
selectbox 展示三個選項,并輸出當前選中的項(默認選中第一個)。當我們在頁面下拉選擇其他選項后,整個頁面代碼會重新執行,但組件的選擇狀態 會保留在 option 中,因此,調用 st.write 后會輸出選擇后的選項。
st.write 也是一個輸出組件,可以輸出字符串、DataFrame、普通對象等各種類型數據。
其他組件的使用與之類似,組件效果圖如下:
5
多媒體組件
Streamlit 定義了 image、audio 和 video 用于展示圖片、音頻和視頻。
可以展示本地多媒體,也通過 url 展示網絡多媒體。
用法跟前面的組件是一樣的,后面的垃圾分類 APP 我們會用到 image 組件。
6
狀態組件
狀態組件用來向用戶展示當前程序的運行狀態,包括:
- progress:進度條,如游戲加載進度
- spinner:等待提示
- balloons:頁面底部飄氣球,表示祝賀
- error:顯示錯誤信息
- warning:顯示報警信息
- info:顯示常規信息
- success:顯示成功信息
- exception:顯示異常信息(代碼錯誤棧)
效果如下:
7
其他內容
到這里,Streamlit 的組件基本上就全介紹完了,組件也是 Streamlit 的主要內容。
這小節介紹一下其他比較重要的內容,包括頁面布局、控制流和緩存。
頁面布局。之前我們寫的 Streamlit 都是按照代碼執行順序從上至下展示組件,Streamlit 提供了 5 種布局:
- sidebar:側邊欄,如:文章開頭那張圖,頁面左側模型參數選擇
- columns:列容器,處在同一個 columns 內組件,按照從左至右順序展示
- expander:隱藏信息,點擊后可展開展示詳細內容,如:展示更多
- container:包含多組件的容器
- empty:包含單組件的容器
控制流。控制 Streamlit 應用的執行,包括
- stop:可以讓 Streamlit 應用停止而不向下執行,如:驗證碼通過后,再向下運行展示后續內容。
- form:表單,Streamlit 在某個組件有交互后就會重新執行頁面程序,而有時候需要等一組組件都完成交互后再刷新(如:登錄填用戶名和密碼),這時候就需要將這些組件添加到 form 中
- form_submit_button:在 form 中使用,提交表單。
緩存。這個比較關鍵,尤其是做機器學習的同學。剛剛說了, Streamlit 組件交互后頁面代碼會重新執行,如果程序中包含一些復雜的數據處理邏輯(如:讀取外部數據、訓練模型),就會導致每次交互都要重復執行相同數據處理邏輯,進而導致頁面加載時間過長,影響體驗。
加入緩存便可以將第一次處理的結果存到內存,當程序重新執行會從內存讀,而不需要重新處理。
使用方法也簡單,在需要緩存的函數加上 @st.cache 裝飾器即可。前兩天我們講過 Python 裝飾器。
DATE_COLUMN = 'date/time'
DATA_URL = ('https://s3-us-west-2.amazonaws.com/'
'streamlit-demo-data/uber-raw-data-sep14.csv.gz')
@st.cache
def load_data(nrows):
data = pd.read_csv(DATA_URL, nrows=nrows)
lowercase = lambda x: str(x).lower()
data.rename(lowercase, axis='columns', inplace=True)
data[DATE_COLUMN] = pd.to_datetime(data[DATE_COLUMN])
return data
8
垃圾分類
最后講解垃圾分類APP的代碼,前面介紹幾大類組件在該 APP 都有涉及。
垃圾分類模型我用的是天行 API ,大家可以去 https://www.tianapi.com/ 注冊賬號,獲取 appkey,開通“圖像垃圾分類” 接口即可。
接口的輸入如下:
除了key外,其他 3 個參數需要用 Streamlit 組件實現,代碼如下:
import base64
import requests
import streamlit as st
import pandas as pd
import numpy as np
add_selectbox = st.sidebar.selectbox(
"圖片來源",
("本地上傳", "URL")
)
uploaded_file = None
img_url = None
if add_selectbox == '本地上傳':
uploaded_file = st.sidebar.file_uploader(label='上傳圖片')
else:
img_url = st.sidebar.text_input('圖片url')
cls_mode = {'嚴格模式': 0, '模糊模式': 1}
mode_name = st.sidebar.radio('分類模式', cls_mode)
mode = cls_mode[mode_name]
使用了 3 個輸入組件,因為 img 和 imgurl 是二選一,所以我們用下拉單選框控制僅展示一個組件。
當輸入圖片后,我們希望在頁面上將圖片展示出來
# 請求結果
img_base64 = None
if uploaded_file:
st.image(uploaded_file, caption='本地圖片')
base64_data = base64.b64encode(uploaded_file.getvalue())
img_base64 = base64_data.decode()
if img_url:
st.image(img_url, caption='網絡圖片')
使用 image 多媒體組件即可。如果是本地圖片,需要將其轉成 base64 編碼的字符串。
最后,請求接口,獲取分類結果即可
if img_base64 or img_url:
cls_res = get_img_cls_res(img_base64, img_url, mode)
lajitype_to_name = {0: '可回收物', 1: '有害垃圾', 2: '廚余垃圾', 3: '其他垃圾', 4: '無法識別'}
if cls_res.status_code == 200:
cls_df = pd.DataFrame(cls_res.json()['newslist'])
cls_df['分類'] = cls_df.index.astype(str) + '-' + cls_df['name'] + '-' + cls_df['lajitype'].apply(lambda x: lajitype_to_name[x])
cls_df['置信度'] = cls_df['trust']
cls_df.set_index(["分類"], inplace=True)
print(cls_df)
st.bar_chart(cls_df[['置信度']])
else:
st.write(cls_res)
get_img_cls_res 函數是請求接口的函數
def get_img_cls_res(img_base64, img_url, mode):
url = 'https://api.tianapi.com/txapi/imglajifenlei/index'
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
body = {
'key': 'APPKEY',
'mode': mode
}
if img_base64:
body["img"] = img_base64
if img_url:
body['imgurl'] = img_url
response = requests.post(url, headers=headers, data=body)
return response
根據返回的數據格式,將數據按照置信度(trust)展示成一個柱狀圖