詳解Python Streamlit框架,用於構建精美資料視覺化web app,練習做個垃圾分類app

渡碼 發表於 2021-11-26
Python 框架 視覺化

今天詳解一個 Python 庫 Streamlit,它可以為機器學習和資料分析構建 web app。它的優勢是入門容易、純 Python 編碼、開發效率高、UI精美。

img

上圖是用 Streamlit 構建自動駕駛模型效果的 demo,左側是模型的引數,右側是模型的效果。通過調整左側引數,右邊的模型會實時地響應。

由此可以看出,對於互動式的資料視覺化需求,完全可以考慮用 Streamlit 實現。特別是在學習、工作彙報的時候,用它的效果遠好於 PPT。

因為 Streamlit 提供了很多前端互動的元件,所以也可以用它來做一些簡單的web 應用。今天我們也會用它來做個垃圾分類的 web app。

之前我們用 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
  Network URL: http://192.168.10.141: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)

img

dateframe 和 table 的區別是,前者可以在表格上做互動(如:排序),後者只是靜態的展示。它們支援展示的資料型別包括 pandas.DataFrame、pandas.Styler、pyarrow.Table、numpy.ndarray、Iterable、dict。

metric 元件用來展示指標的變化,資料分析中經常會用到。

st.metric(label="Temperature", value="70 °F", delta="1.2 °F")

img

value 參數列示當前指標值,delta 參數列示與前值的差值,向上的綠色箭頭代表相比於前值,是漲的,反之向下的紅箭頭代表相比於前值是跌的。當然漲跌顏色可以通過 delta_color 引數來控制。

json 元件用來展示 json 型別資料

st.json({
    'foo': 'bar',
    'stuff': [
        'stuff 1',
        'stuff 2',
    ],
})

img

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)

img

上述是 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)

img

跟直接寫 matplotlib.pyplot 一樣,只不過最終展示的時候呼叫 st.pyplot 便可以將圖表展示 Streamlit 頁面上。其他 Python 庫的使用方法與之類似。

輸入元件

前面我們介紹的三類元件都是輸出類、展示類的。對於互動式的頁面來說,接受使用者的輸入是必不可少的。

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)

img

selectbox 展示三個選項,並輸出當前選中的項(預設選中第一個)。當我們在頁面下拉選擇其他選項後,整個頁面程式碼會重新執行,但元件的選擇狀態 會保留在 option 中,因此,呼叫 st.write 後會輸出選擇後的選項。

st.write 也是一個輸出元件,可以輸出字串、DataFrame、普通物件等各種型別資料。

其他元件的使用與之類似,元件效果圖如下:

img

5.多媒體元件

Streamlit 定義了 image、audio 和 video 用於展示圖片、音訊和視訊。

可以展示本地多媒體,也通過 url 展示網路多媒體。

img

用法跟前面的元件是一樣的,後面的垃圾分類 APP 我們會用到 image 元件。

6. 狀態元件

狀態元件用來向使用者展示當前程式的執行狀態,包括:

  • progress:進度條,如遊戲載入進度
  • spinner:等待提示
  • balloons:頁面底部飄氣球,表示祝賀
  • error:顯示錯誤資訊
  • warning:顯示報警資訊
  • info:顯示常規資訊
  • success:顯示成功資訊
  • exception:顯示異常資訊(程式碼錯誤棧)

效果如下:

img

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,開通“影像垃圾分類” 介面即可。

介面的輸入如下:

img

除了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)展示成一個柱狀圖

img

img

關注公眾號 渡碼 回覆 “垃圾分類v2” 獲取全部程式碼。今天的內容基本上把 Streamlit 講完了,細節的內容大家可以自行參考官方文件,相信讀完該教程,再看官方文件就很容易了。
duma