Gordon  Murray

Gordon Murray

1668744900

Serving a Machine Learning Model with FastAPI and Streamlit

Machine learning is a hot topic at present. With technology companies moving in the direction of artificial intelligence and machine learning to cash in early, the field has grown tremendously large. Many of these companies create their own machine learning solutions and sell them to others using a subscription-based model.

Since the majority of machine learning models are developed in Python, the web frameworks that serve them up are usually Python-based as well. For a long time, Flask, a micro-framework, was the goto framework. But that's changing. A new framework, designed to compensate for almost everything Flask lacks is becoming more and more popular. It's called FastAPI.

FastAPI is faster than Flask because it brings asynchronous function handlers to the table:

Web Framework Benchmarks

Source: TechEmpower Web Framework Benchmarks

As you can see from the figure above, FastAPI is almost 3x faster than Flask.

The third position is held by Starlette, which FastAPI is built on.

FastAPI supports data validation via pydantic and automatic API documentation as well.

Review the Features guide from the official docs for more info. It's also encouraged to review Alternatives, Inspiration, and Comparisons, which details how FastAPI compares to other web frameworks and technologies, for context.

Streamlit, meanwhile, is an application framework that makes it easy for data scientists and machine learning engineers to create powerful user interfaces that interact with machine learning models.

Although Streamlit can be used in production, it's best for rapid prototyping. By serving up the model with FastAPI, you can quickly move to a production-ready UI with Dash or React, after the prototype is approved.

With that, we're going to build a style transfer application based on the Perceptual Losses for Real-Time Style Transfer and Super-Resolution paper and Justin Johnson's pre-trained models. We'll use FastAPI as the backend to serve our predictions, Streamlit for the user interface, and OpenCV to do the actual prediction. Docker will be used as well.

One powerful Feature of OpenCV's Deep Neural Networks (DNN) module is that it can load trained models from Torch, TensorFlow, and Caffe, effectively saving us the trouble of installing those dependencies.

Objectives

By the end of this tutorial, you will be able to:

  1. Develop an asynchronous API with Python and FastAPI
  2. Serve up a machine learning model with FastAPI
  3. Develop a UI with Streamlit
  4. Containerize FastAPI and Streamlit with Docker
  5. Leverage asyncio to execute code in the background outside the request/response flow

Project Setup

Create a project folder called "style-transfer":

$ mkdir style-transfer
$ cd style-transfer

Then, create two new folders with "style-transfer":

$ mkdir frontend
$ mkdir backend

Add __init__.py files to each folder.

FastAPI Backend

Add a new file to "backend" called main.py:

# backend/main.py

import uuid

import cv2
import uvicorn
from fastapi import File
from fastapi import FastAPI
from fastapi import UploadFile
import numpy as np
from PIL import Image

import config
import inference


app = FastAPI()


@app.get("/")
def read_root():
    return {"message": "Welcome from the API"}


@app.post("/{style}")
def get_image(style: str, file: UploadFile = File(...)):
    image = np.array(Image.open(file.file))
    model = config.STYLES[style]
    output, resized = inference.inference(model, image)
    name = f"/storage/{str(uuid.uuid4())}.jpg"
    cv2.imwrite(name, output)
    return {"name": name}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=8080)

This is our server. FastAPI creates two endpoints, one dummy ("/") and one for serving our prediction ("/{style}"). The serving endpoint takes in a name as a URL parameter. We're using nine different trained models to perform style transfer, so the path parameter will tell us which model to choose. The image is accepted as a file over a POST request and sent to the inference function. Once the inference is complete, the file is stored on the local filesystem and the path is sent as a response.

Next, add the following config to a new file called backend/config.py:

# backend/config.py

MODEL_PATH = "./models/"

STYLES = {
    "candy": "candy",
    "composition 6": "composition_vii",
    "feathers": "feathers",
    "la_muse": "la_muse",
    "mosaic": "mosaic",
    "starry night": "starry_night",
    "the scream": "the_scream",
    "the wave": "the_wave",
    "udnie": "udnie",
}

When introduced, style transfer was a game-changer. The only disadvantage was that the image had to be trained to obtain a style for it. This means, to get a styled image, you need to run through the original images multiple times before getting a better result. In 2016, the Perceptual Losses for Real-Time Style Transfer and Super-Resolution paper introduced fast-style transfer, which means you can style any image in a single pass. We're going to use the same technique with the trained models provided by the author.

Now, we need to download the models. Add a script to the project root called download_models.sh:

BASE_URL="https://cs.stanford.edu/people/jcjohns/fast-neural-style/models/"

mkdir -p backend/models/
cd backend/models/
curl -O "$BASE_URL/instance_norm/candy.t7"
curl -O "$BASE_URL/instance_norm/la_muse.t7"
curl -O "$BASE_URL/instance_norm/mosaic.t7"
curl -O "$BASE_URL/instance_norm/feathers.t7"
curl -O "$BASE_URL/instance_norm/the_scream.t7"
curl -O "$BASE_URL/instance_norm/udnie.t7"
curl -O "$BASE_URL/eccv16/the_wave.t7"
curl -O "$BASE_URL/eccv16/starry_night.t7"
curl -O "$BASE_URL/eccv16/la_muse.t7"
curl -O "$BASE_URL/eccv16/composition_vii.t7"

Download:

$ sh download_models.sh

Add the inference function to backend/inference.py:

# backend/inference.py

import config
import cv2


def inference(model, image):
    model_name = f"{config.MODEL_PATH}{model}.t7"
    model = cv2.dnn.readNetFromTorch(model_name)

    height, width = int(image.shape[0]), int(image.shape[1])
    new_width = int((640 / height) * width)
    resized_image = cv2.resize(image, (new_width, 640), interpolation=cv2.INTER_AREA)

    # Create our blob from the image
    # Then perform a forward pass run of the network
    # The Mean values for the ImageNet training set are R=103.93, G=116.77, B=123.68

    inp_blob = cv2.dnn.blobFromImage(
        resized_image,
        1.0,
        (new_width, 640),
        (103.93, 116.77, 123.68),
        swapRB=False,
        crop=False,
    )

    model.setInput(inp_blob)
    output = model.forward()

    # Reshape the output Tensor,
    # add back the mean substruction,
    # re-order the channels
    output = output.reshape(3, output.shape[2], output.shape[3])
    output[0] += 103.93
    output[1] += 116.77
    output[2] += 123.68

    output = output.transpose(1, 2, 0)
    return output, resized_image

Here, we loaded the Torch model, performed resizing, and converted it into the blob format required. Then we passed the pre-processed image into the network/model and obtained the output. The post-processed image and resized image are returned as output.

Lastly, add the dependencies to a requirements file:

# backend/requirements.txt

fastapi
numpy
opencv-python
pillow
python-multipart
uvicorn

That's it for the backend. Let's configure Docker and then test it out.

Docker Setup

First, add a Dockerfile to the "backend" folder:

# backend/Dockerfile

FROM python:3.10.1-slim

WORKDIR /app

RUN apt-get update
RUN apt-get install \
    'ffmpeg'\
    'libsm6'\
    'libxext6'  -y

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8080

CMD ["python", "main.py"]

'ffmpeg', 'libsm6', and 'libxext6' are required for OpenCV.

From the "backend" folder in your terminal, build the image:

$ docker build -t backend .

Run the container:

$ docker run -p 8080:8080 backend

INFO:     Started server process [1]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)

In your browser, navigate to http://localhost:8080/. You should see:

{
  "message": "Welcome from the API"
}

Kill the container once done.

Streamlit Frontend

For the UI, add a main.py file to the "frontend" folder:

# frontend/main.py

import requests
import streamlit as st
from PIL import Image

STYLES = {
    "candy": "candy",
    "composition 6": "composition_vii",
    "feathers": "feathers",
    "la_muse": "la_muse",
    "mosaic": "mosaic",
    "starry night": "starry_night",
    "the scream": "the_scream",
    "the wave": "the_wave",
    "udnie": "udnie",
}

# https://discuss.streamlit.io/t/version-0-64-0-deprecation-warning-for-st-file-uploader-decoding/4465
st.set_option("deprecation.showfileUploaderEncoding", False)

# defines an h1 header
st.title("Style transfer web app")

# displays a file uploader widget
image = st.file_uploader("Choose an image")

# displays the select widget for the styles
style = st.selectbox("Choose the style", [i for i in STYLES.keys()])

# displays a button
if st.button("Style Transfer"):
    if image is not None and style is not None:
        files = {"file": image.getvalue()}
        res = requests.post(f"http://backend:8080/{style}", files=files)
        img_path = res.json()
        image = Image.open(img_path.get("name"))
        st.image(image, width=500)

Take note of the code comments above. Put simply, we created an upload image widget along with a select dropdown displaying each of the styles from the STYLES dict. We also added a button that, when pressed, sends the image to the backend as a POST request payload to http://backend:8080/{style}. Upon receiving the image path in the response from the backend, the image is opened and displayed.

Refer to Streamlit's Get started guide and the API reference for help with displaying text and data as well as adding basic interactivity with widgets.

Add the Streamlit dependency to a requirements.txt file:

# frontend/requirements.txt

streamlit==1.2.0

Docker Compose

Next, let's Dockerize the frontend and wire both containers together with Docker Compose.

frontend/Dockerfile:

# frontend/Dockerfile

FROM python:3.10.1-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8501

CMD ["streamlit", "run", "main.py"]

docker-compose.yml:

version: '3'

services:
  frontend:
    build: frontend
    ports:
      - 8501:8501
    depends_on:
      - backend
    volumes:
        - ./storage:/storage
  backend:
    build: backend
    ports:
      - 8080:8080
    volumes:
      - ./storage:/storage

The most important thing here is that we mapped the storage of the host machine to the storage of each container. This is important for sharing the path and also to persist data when spinning down the containers.

Thus, both the backend and frontend can access images from the same shared volume:

# backend
name = f"/storage/{str(uuid.uuid4())}.jpg"
cv2.imwrite(name, output)
return {"name": name}

# frontend
img_path = res.json()
image = Image.open(img_path.get("name"))

To test, from the project root, build the images and spin up both containers:

$ docker-compose up -d --build

Navigate to http://localhost:8501:

Demo App

Async Model Serving

Now that you have seen how to use FastAPI, Streamlit, and OpenCV to perform a style transfer, let's do a little experiment.

One of the most powerful features of FastAPI is that it supports asynchronous functions. So, let's leverage an async function to convert the input image into multiple styles. We'll process the first style synchronously, and then send back the response as the remaining models are processed in the background.

Add the following functions to backend/main.py:

# backend/main.py

async def generate_remaining_models(models, image, name: str):
    executor = ProcessPoolExecutor()
    event_loop = asyncio.get_event_loop()
    await event_loop.run_in_executor(
        executor, partial(process_image, models, image, name)
    )


def process_image(models, image, name: str):
    for model in models:
        output, resized = inference.inference(models[model], image)
        name = name.split(".")[0]
        name = f"{name.split('_')[0]}_{models[model]}.jpg"
        cv2.imwrite(name, output)

The generate_remaining_models function generates each of the remaining styles using asyncio.

Check out the Speeding Up Python with Concurrency, Parallelism, and asyncio article for more info on asyncio.

Add the following imports:

import asyncio

from concurrent.futures import ProcessPoolExecutor

from functools import partial

Update the get_image function so that it creates the asynchronous tasks before sending the response back:

# backend/main.py

@app.post("/{style}")
async def get_image(style: str, file: UploadFile = File(...)):
    image = np.array(Image.open(file.file))
    model = config.STYLES[style]
    start = time.time()
    output, resized = inference.inference(model, image)
    name = f"/storage/{str(uuid.uuid4())}.jpg"
    cv2.imwrite(name, output)
    models = config.STYLES.copy()
    del models[style]
    asyncio.create_task(generate_remaining_models(models, image, name))
    return {"name": name, "time": time.time() - start}

Once the first prediction is made, we'll delete the style from a copy of the original styles. Then the remaining styles are passed to generate_remaining_models.

Add the import:

import time

Next, update the block of the following if statement in frontend/main.py:

# frontend/main.py

if st.button("Style Transfer"):
    if image is not None and style is not None:
        files = {"file": image.getvalue()}
        res = requests.post(f"http://backend:8080/{style}", files=files)
        img_path = res.json()
        image = Image.open(img_path.get("name"))
        st.image(image)

        displayed_styles = [style]
        displayed = 1
        total = len(STYLES)

        st.write("Generating other models...")

        while displayed < total:
            for style in STYLES:
                if style not in displayed_styles:
                    try:
                        path = f"{img_path.get('name').split('.')[0]}_{STYLES[style]}.jpg"
                        image = Image.open(path)
                        st.image(image, width=500)
                        time.sleep(1)
                        displayed += 1
                        displayed_styles.append(style)
                    except:
                        pass

Add the import to the top:

import time

So, after displaying the first style, we continue to check for the remaining styles, displaying each until all nine are on the page.

Update the containers and test:

$ docker-compose up -d --build

Now, the remaining styles will be displayed asynchronously without blocking the initial response.

multiple style transfer

Conclusion

FastAPI is a modern, async alternative to Flask. It has a lot of features that Flask lacks and is faster than Flask since it leverages Starlette and supports asynchronous function handlers. FastAPI has a lot of additional features like data validation, automatic API documentation, background tasks as well as a powerful dependency injection system. Plus, since you'll most likely be taking advantage of Python type hints (so you can leverage data validation), you'll be able to move faster in development due to editor autocompletion and automatic error checks.

You can find the final code in the style-transfer repo on GitHub.

Original article source at: https://testdriven.io

#fastapi #streamlit #machinelearning 

Serving a Machine Learning Model with FastAPI and Streamlit
Archie  Clayton

Archie Clayton

1668571286

Build Web Applications with Python and Streamlit

Learn how to create an awesome Web App with Python and Streamlit. Learn to use Streamlit to build Web Applications using only Python

Want to make websites without using HTML, CSS and JS? Learn to use Streamlit to build Web Applications using only Python

Learn to build interactive Web Applications using only Python's Streamlit. HTML, CSS and Js are not required. Using Streamlit is really easy and you can see its impact on your data apps in a very short span of time. This short course is a great way to get started with this amazing Python library.

We have several sections in this course. The course starts with an Introduction to Streamlit, advantages and official resources to get started with Streamlit.

Streamlit is a low code web development framework thereby making the development of web applications easy for citizen developers also. Through the course, great applications built by the Streamlit community have been showcased. Code walkthroughs are also included.

This helps the learner learn how Streamlit code is usually structured and get familiar with the nuances of the library. By the end of this course, you will have two Streamlit Web applications which you can further develop for your own purposes.

The course throws light upon the entire Streamlit development process, from starting a new Python file to deploying the application on the web so that it can be shared anywhere.

The instructor has won hackathons and competitions by building Web Applications using Streamlit.

Learn to use the most commonly used components that Streamlit provides and have a look at the various resources provide.

This course prepares you to take a Web Application from scratch to deployment.

What you’ll learn:

  •        Web development with Python
  •        Streamlit

Are there any course requirements or prerequisites?

  •        Python

Who this course is for:

  •        Developers who wish to develop Web Applications using Python without having to learn HTML, CSS, JavaScript.
  •        Students
  •        Python and Streamlit enthusiasts

Subscribe: https://www.youtube.com/channel/UC2S7KicQMgJaVY1Kjw1druQ/featured 

#python #streamlit #programming #developer #softwaredeveloper #computerscience #webdev #webdeveloper #webdevelopment 

Build Web Applications with Python and Streamlit
Jack  Shaw

Jack Shaw

1666591620

Streamlit Layout: How to Layout Your Streamlit App with Python

How to layout your Streamlit app

In this tutorial, we're going to use the following commands to layout our Streamlit app:

  • st.set_page_config(layout="wide") - Displays the contents of the app in wide mode (otherwise by default, the contents are encapsulated in a fixed width box.
  • st.sidebar - Places the widgets or text/image displays in the sidebar.
  • st.expander - Places text/image displays inside a collapsible container box.
  • st.columns - Creates a tabular space (or column) within which contents can be placed inside.

Demo app

Streamlit App

Code

Here's how to customize the layout of your Streamlit app:

import streamlit as st

st.set_page_config(layout="wide")

st.title('How to layout your Streamlit app')

with st.expander('About this app'):
  st.write('This app shows the various ways on how you can layout your Streamlit app.')
  st.image('https://streamlit.io/images/brand/streamlit-logo-secondary-colormark-darktext.png', width=250)

st.sidebar.header('Input')
user_name = st.sidebar.text_input('What is your name?')
user_emoji = st.sidebar.selectbox('Choose an emoji', ['', '😄', '😆', '😊', '😍', '😴', '😕', '😱'])
user_food = st.sidebar.selectbox('What is your favorite food?', ['', 'Tom Yum Kung', 'Burrito', 'Lasagna', 'Hamburger', 'Pizza'])

st.header('Output')

col1, col2, col3 = st.columns(3)

with col1:
  if user_name != '':
    st.write(f'👋 Hello {user_name}!')
  else:
    st.write('👈  Please enter your **name**!')

with col2:
  if user_emoji != '':
    st.write(f'{user_emoji} is your favorite **emoji**!')
  else:
    st.write('👈 Please choose an **emoji**!')

with col3:
  if user_food != '':
    st.write(f'🍴 **{user_food}** is your favorite **food**!')
  else:
    st.write('👈 Please choose your favorite **food**!')

Line-by-line explanation

The very first thing to do when creating a Streamlit app is to start by importing the streamlit library as st like so:

import streamlit as st

We'll start by first defining the page layout to be displayed in the wide mode, which allows the page content to expand to the browser's width.

st.set_page_config(layout="wide")

Next, we'll give the Streamlit app a title.

st.title('How to layout your Streamlit app')

An expandable box titled About this app is placed under the app title. Upon expansion, we'll see additional details inside.

with st.expander('About this app'):
  st.write('This app shows the various ways on how you can layout your Streamlit app.')
  st.image('https://streamlit.io/images/brand/streamlit-logo-secondary-colormark-darktext.png', width=250)

Input widgets for accepting user input is placed in the sidebar as specified by using the st.sidebar command before the Streamlit commands text_input and selectbox. Input values entered or selected by the user are assigned and stored in the user_name, user_emoji and user_food variables.

st.sidebar.header('Input')
user_name = st.sidebar.text_input('What is your name?')
user_emoji = st.sidebar.selectbox('Choose an emoji', ['', '😄', '😆', '😊', '😍', '😴', '😕', '😱'])
user_food = st.sidebar.selectbox('What is your favorite food?', ['', 'Tom Yum Kung', 'Burrito', 'Lasagna', 'Hamburger', 'Pizza'])

Finally, we'll create 3 columns using the st.columns command which corresponds to col1, col2 and col3. Then, we assign contents to each of the column by creating individual code blocks starting with the with statement. Underneath this, we create conditional statements that display 1 of 2 alternative text depending on whether the user had provided their input data (specified in the sidebar) or not. By default, the page displays text under the else statement. Upon providing user input, the corresponding information that the user gives to the app is displayed under the Output header text.

st.header('Output')

col1, col2, col3 = st.columns(3)

with col1:
  if user_name != '':
    st.write(f'👋 Hello {user_name}!')
  else:
    st.write('👈  Please enter your **name**!')

with col2:
  if user_emoji != '':
    st.write(f'{user_emoji} is your favorite **emoji**!')
  else:
    st.write('👈 Please choose an **emoji**!')

with col3:
  if user_food != '':
    st.write(f'🍴 **{user_food}** is your favorite **food**!')
  else:
    st.write('👈 Please choose your favorite **food**!')

It is also worthy to note that f strings were used to combine pre-canned text together with the user provided values.

Further reading


Download Details:

Author: dataprofessor
Source Code: https://github.com/dataprofessor/streamlit-layout

#python #streamlit 

Streamlit Layout: How to Layout Your Streamlit App with Python
Isai  Upton

Isai Upton

1660283580

Machine Learning Model with FastAPI and Streamlit

Machine learning is a hot topic at present. With technology companies moving in the direction of artificial intelligence and machine learning to cash in early, the field has grown tremendously large. Many of these companies create their own machine learning solutions and sell them to others using a subscription-based model.

Source: https://testdriven.io

#machine-learning #fastapi #streamlit #python 

Machine Learning Model with FastAPI and Streamlit
Thai  Son

Thai Son

1660276380

Mô Hình Học Máy Với FastAPI Và Streamlit

Học máy là một chủ đề nóng hiện nay. Với việc các công ty công nghệ đi theo hướng trí tuệ nhân tạo và máy học để kiếm tiền từ sớm, lĩnh vực này đã phát triển rất lớn. Nhiều công ty trong số này tạo ra các giải pháp học máy của riêng họ và bán chúng cho những người khác bằng cách sử dụng mô hình dựa trên đăng ký.

Vì phần lớn các mô hình học máy được phát triển bằng Python, nên các khuôn khổ web phục vụ chúng cũng thường dựa trên Python. Trong một thời gian dài, Flask, một micro-framework, là goto framework. Nhưng điều đó đang thay đổi. Một khung công tác mới, được thiết kế để bù đắp cho hầu hết mọi thứ mà Flask thiếu đang ngày càng trở nên phổ biến hơn. Nó được gọi là FastAPI.

FastAPI nhanh hơn Flask vì nó đưa các trình xử lý chức năng không đồng bộ vào bảng:

Điểm chuẩn của Khung Web

Nguồn: TechEmpower Web Framework Benchmarks

Như bạn có thể thấy từ hình trên, FastAPI nhanh hơn gần như gấp 3 lần so với Flask.

FastAPI cũng hỗ trợ xác thực dữ liệu thông qua tài liệu API tự động và khổng lồ.

Trong khi đó, Streamlit là một khung ứng dụng giúp các nhà khoa học dữ liệu và kỹ sư máy học dễ dàng tạo ra các giao diện người dùng mạnh mẽ tương tác với các mô hình máy học.

Mặc dù Streamlit có thể được sử dụng trong sản xuất, nhưng nó là tốt nhất để tạo mẫu nhanh. Bằng cách cung cấp mô hình với FastAPI, bạn có thể nhanh chóng chuyển sang giao diện người dùng sẵn sàng sản xuất với Dash hoặc React, sau khi nguyên mẫu được phê duyệt.

Cùng với đó, chúng tôi sẽ xây dựng một ứng dụng chuyển kiểu dựa trên giấy Mất cảm giác cho Chuyển kiểu theo thời gian thực và siêu phân giải và các mô hình được đào tạo trước của Justin Johnson. Chúng tôi sẽ sử dụng FastAPI làm chương trình phụ trợ để phục vụ các dự đoán của chúng tôi, Streamlit cho giao diện người dùng và OpenCV để thực hiện dự đoán thực tế. Docker cũng sẽ được sử dụng.

Một tính năng mạnh mẽ của mô-đun Deep Neural Networks (DNN) của OpenCV là nó có thể tải các mô hình được đào tạo từ Torch, TensorFlow và Caffe, giúp chúng tôi tiết kiệm hiệu quả sự cố cài đặt các phụ thuộc đó.

Mục tiêu

Đến cuối hướng dẫn này, bạn sẽ có thể:

  1. Phát triển một API không đồng bộ với Python và FastAPI
  2. Cung cấp mô hình học máy với FastAPI
  3. Phát triển giao diện người dùng với Streamlit
  4. Chứa FastAPI và Streamlit với Docker
  5. Tận dụng asyncio để thực thi mã trong nền bên ngoài luồng yêu cầu / phản hồi

Thiết lập dự án

Tạo một thư mục dự án có tên "style-transfer":

$ mkdir style-transfer
$ cd style-transfer

Sau đó, tạo hai thư mục mới với "style-transfer":

$ mkdir frontend
$ mkdir backend

Thêm tệp __init__.py vào mỗi thư mục.

FastAPI Backend

Thêm một tệp mới vào "phụ trợ" được gọi là main.py :

# backend/main.py

import uuid

import cv2
import uvicorn
from fastapi import File
from fastapi import FastAPI
from fastapi import UploadFile
import numpy as np
from PIL import Image

import config
import inference


app = FastAPI()


@app.get("/")
def read_root():
    return {"message": "Welcome from the API"}


@app.post("/{style}")
def get_image(style: str, file: UploadFile = File(...)):
    image = np.array(Image.open(file.file))
    model = config.STYLES[style]
    output, resized = inference.inference(model, image)
    name = f"/storage/{str(uuid.uuid4())}.jpg"
    cv2.imwrite(name, output)
    return {"name": name}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=8080)

Đây là máy chủ của chúng tôi. FastAPI tạo ra hai điểm cuối, một giả ( "/") và một để phục vụ dự đoán của chúng tôi ( "/{style}"). Điểm cuối phục vụ lấy tên làm tham số URL. Chúng tôi đang sử dụng chín mô hình được đào tạo khác nhau để thực hiện chuyển kiểu, vì vậy tham số đường dẫn sẽ cho chúng tôi biết mô hình nào cần chọn. Hình ảnh được chấp nhận dưới dạng tệp qua yêu cầu ĐĂNG và được gửi đến inferencehàm. Sau khi hoàn tất suy luận, tệp được lưu trữ trên hệ thống tệp cục bộ và đường dẫn được gửi dưới dạng phản hồi.

Tiếp theo, thêm cấu hình sau vào một tệp mới có tên là backend / config.py :

# backend/config.py

MODEL_PATH = "./models/"

STYLES = {
    "candy": "candy",
    "composition 6": "composition_vii",
    "feathers": "feathers",
    "la_muse": "la_muse",
    "mosaic": "mosaic",
    "starry night": "starry_night",
    "the scream": "the_scream",
    "the wave": "the_wave",
    "udnie": "udnie",
}

Khi được giới thiệu, chuyển giao phong cách là một yếu tố thay đổi cuộc chơi. Bất lợi duy nhất là hình ảnh phải được đào tạo để có được một phong cách cho nó. Điều này có nghĩa là, để có được một hình ảnh được tạo kiểu, bạn cần phải chạy qua các hình ảnh gốc nhiều lần trước khi có được kết quả tốt hơn. Vào năm 2016, tờ báo về Sự mất mát tri giác đối với Truyền kiểu thời gian thực và Siêu phân giải đã giới thiệu tính năng truyền kiểu nhanh, có nghĩa là bạn có thể tạo kiểu cho bất kỳ hình ảnh nào trong một lần chuyển. Chúng tôi sẽ sử dụng kỹ thuật tương tự với các mô hình được đào tạo do tác giả cung cấp.

Bây giờ, chúng ta cần tải xuống các mô hình. Thêm một tập lệnh vào thư mục gốc của dự án có tên là download_models.sh :

BASE_URL="https://cs.stanford.edu/people/jcjohns/fast-neural-style/models/"

mkdir -p backend/models/
cd backend/models/
curl -O "$BASE_URL/instance_norm/candy.t7"
curl -O "$BASE_URL/instance_norm/la_muse.t7"
curl -O "$BASE_URL/instance_norm/mosaic.t7"
curl -O "$BASE_URL/instance_norm/feathers.t7"
curl -O "$BASE_URL/instance_norm/the_scream.t7"
curl -O "$BASE_URL/instance_norm/udnie.t7"
curl -O "$BASE_URL/eccv16/the_wave.t7"
curl -O "$BASE_URL/eccv16/starry_night.t7"
curl -O "$BASE_URL/eccv16/la_muse.t7"
curl -O "$BASE_URL/eccv16/composition_vii.t7"

Tải xuống:

$ sh download_models.sh

Thêm inferencehàm vào backend / inference.py :

# backend/inference.py

import config
import cv2


def inference(model, image):
    model_name = f"{config.MODEL_PATH}{model}.t7"
    model = cv2.dnn.readNetFromTorch(model_name)

    height, width = int(image.shape[0]), int(image.shape[1])
    new_width = int((640 / height) * width)
    resized_image = cv2.resize(image, (new_width, 640), interpolation=cv2.INTER_AREA)

    # Create our blob from the image
    # Then perform a forward pass run of the network
    # The Mean values for the ImageNet training set are R=103.93, G=116.77, B=123.68

    inp_blob = cv2.dnn.blobFromImage(
        resized_image,
        1.0,
        (new_width, 640),
        (103.93, 116.77, 123.68),
        swapRB=False,
        crop=False,
    )

    model.setInput(inp_blob)
    output = model.forward()

    # Reshape the output Tensor,
    # add back the mean substruction,
    # re-order the channels
    output = output.reshape(3, output.shape[2], output.shape[3])
    output[0] += 103.93
    output[1] += 116.77
    output[2] += 123.68

    output = output.transpose(1, 2, 0)
    return output, resized_image

Ở đây, chúng tôi đã tải mô hình Torch, thực hiện thay đổi kích thước và chuyển đổi nó thành định dạng blob cần thiết. Sau đó, chúng tôi truyền hình ảnh đã được xử lý trước vào mạng / mô hình và thu được kết quả đầu ra. Hình ảnh được xử lý sau và hình ảnh đã thay đổi kích thước được trả về dưới dạng đầu ra.

Cuối cùng, thêm các phần phụ thuộc vào tệp yêu cầu:

# backend/requirements.txt

fastapi
numpy
opencv-python
pillow
python-multipart
uvicorn

Đó là nó cho phần phụ trợ. Hãy cấu hình Docker và sau đó kiểm tra nó.

Thiết lập Docker

Đầu tiên, thêm Dockerfile vào thư mục "phụ trợ":

# backend/Dockerfile

FROM python:3.10.1-slim

WORKDIR /app

RUN apt-get update
RUN apt-get install \
    'ffmpeg'\
    'libsm6'\
    'libxext6'  -y

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8080

CMD ["python", "main.py"]

'ffmpeg', 'libsm6' và 'libxext6' là bắt buộc đối với OpenCV.

Từ thư mục "phụ trợ" trong thiết bị đầu cuối của bạn, hãy xây dựng hình ảnh:

$ docker build -t backend .

Chạy vùng chứa:

$ docker run -p 8080:8080 backend

INFO:     Started server process [1]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)

Trong trình duyệt của bạn, điều hướng đến http: // localhost: 8080 / . Bạn nên thấy:

{
  "message": "Welcome from the API"
}

Diệt vùng chứa sau khi thực hiện xong.

Giao diện người dùng Streamlit

Đối với giao diện người dùng, hãy thêm tệp main.py vào thư mục "giao diện người dùng":

# frontend/main.py

import requests
import streamlit as st
from PIL import Image

STYLES = {
    "candy": "candy",
    "composition 6": "composition_vii",
    "feathers": "feathers",
    "la_muse": "la_muse",
    "mosaic": "mosaic",
    "starry night": "starry_night",
    "the scream": "the_scream",
    "the wave": "the_wave",
    "udnie": "udnie",
}

# https://discuss.streamlit.io/t/version-0-64-0-deprecation-warning-for-st-file-uploader-decoding/4465
st.set_option("deprecation.showfileUploaderEncoding", False)

# defines an h1 header
st.title("Style transfer web app")

# displays a file uploader widget
image = st.file_uploader("Choose an image")

# displays the select widget for the styles
style = st.selectbox("Choose the style", [i for i in STYLES.keys()])

# displays a button
if st.button("Style Transfer"):
    if image is not None and style is not None:
        files = {"file": image.getvalue()}
        res = requests.post(f"http://backend:8080/{style}", files=files)
        img_path = res.json()
        image = Image.open(img_path.get("name"))
        st.image(image, width=500)

Hãy lưu ý các bình luận mã ở trên. Nói một cách đơn giản, chúng tôi đã tạo một widget hình ảnh tải lên cùng với một danh sách thả xuống được chọn hiển thị từng kiểu từ STYLESdict. Chúng tôi cũng đã thêm một nút, khi được nhấn, sẽ gửi hình ảnh đến phần phụ trợ dưới dạng tải trọng yêu cầu POST tới http://backend:8080/{style}. Khi nhận được đường dẫn hình ảnh trong phản hồi từ chương trình phụ trợ, hình ảnh sẽ được mở và hiển thị.

Tham khảo hướng dẫn Bắt đầu của Streamlit và tài liệu tham khảo API để được trợ giúp hiển thị văn bản và dữ liệu cũng như thêm tính tương tác cơ bản với các tiện ích con.

Thêm phần phụ thuộc Streamlit vào một tệp tin request.txt :

# frontend/requirements.txt

streamlit==1.2.0

Docker Soạn

Tiếp theo, hãy Dockerize giao diện người dùng và kết nối cả hai vùng chứa với nhau bằng Docker Compose.

frontend / Dockerfile :

# frontend/Dockerfile

FROM python:3.10.1-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8501

CMD ["streamlit", "run", "main.py"]

docker-compost.yml :

version: '3'

services:
  frontend:
    build: frontend
    ports:
      - 8501:8501
    depends_on:
      - backend
    volumes:
        - ./storage:/storage
  backend:
    build: backend
    ports:
      - 8080:8080
    volumes:
      - ./storage:/storage

Điều quan trọng nhất ở đây là chúng tôi đã ánh xạ việc lưu trữ của máy chủ đến việc lưu trữ của từng vùng chứa. Điều này rất quan trọng để chia sẻ đường dẫn và cũng để duy trì dữ liệu khi quay xuống các vùng chứa.

Do đó, cả backend và frontend đều có thể truy cập hình ảnh từ cùng một ổ đĩa được chia sẻ:

# backend
name = f"/storage/{str(uuid.uuid4())}.jpg"
cv2.imwrite(name, output)
return {"name": name}

# frontend
img_path = res.json()
image = Image.open(img_path.get("name"))

Để kiểm tra, từ gốc dự án, hãy xây dựng các hình ảnh và quay lên cả hai vùng chứa:

$ docker-compose up -d --build

Điều hướng đến http: // localhost: 8501 :

Ứng dụng Demo

Cung cấp mô hình không đồng bộ

Bây giờ bạn đã thấy cách sử dụng FastAPI, Streamlit và OpenCV để thực hiện chuyển kiểu, hãy làm một thử nghiệm nhỏ.

Một trong những tính năng mạnh mẽ nhất của FastAPI là nó hỗ trợ các chức năng không đồng bộ. Vì vậy, hãy tận dụng một hàm không đồng bộ để chuyển đổi hình ảnh đầu vào thành nhiều kiểu. Chúng tôi sẽ xử lý kiểu đầu tiên một cách đồng bộ và sau đó gửi lại phản hồi khi các kiểu còn lại được xử lý ở chế độ nền.

Thêm các chức năng sau vào backend / main.py :

# backend/main.py

async def generate_remaining_models(models, image, name: str):
    executor = ProcessPoolExecutor()
    event_loop = asyncio.get_event_loop()
    await event_loop.run_in_executor(
        executor, partial(process_image, models, image, name)
    )


def process_image(models, image, name: str):
    for model in models:
        output, resized = inference.inference(models[model], image)
        name = name.split(".")[0]
        name = f"{name.split('_')[0]}_{models[model]}.jpg"
        cv2.imwrite(name, output)

Hàm generate_remaining_modelstạo từng kiểu còn lại bằng cách sử dụng asyncio .

Hãy xem bài viết Tăng tốc Python với Đồng tiền, Song song và asyncio để biết thêm thông tin về asyncio.

Thêm các lần nhập sau:

import asyncio

from concurrent.futures import ProcessPoolExecutor

from functools import partial

Cập nhật get_imagehàm để nó tạo các tác vụ không đồng bộ trước khi gửi phản hồi trở lại:

# backend/main.py

@app.post("/{style}")
async def get_image(style: str, file: UploadFile = File(...)):
    image = np.array(Image.open(file.file))
    model = config.STYLES[style]
    start = time.time()
    output, resized = inference.inference(model, image)
    name = f"/storage/{str(uuid.uuid4())}.jpg"
    cv2.imwrite(name, output)
    models = config.STYLES.copy()
    del models[style]
    asyncio.create_task(generate_remaining_models(models, image, name))
    return {"name": name, "time": time.time() - start}

Sau khi dự đoán đầu tiên được thực hiện, chúng tôi sẽ xóa kiểu khỏi bản sao của các kiểu ban đầu. Sau đó, các phong cách còn lại được chuyển đến generate_remaining_models.

Thêm nhập khẩu:

import time

Tiếp theo, cập nhật khối của ifcâu lệnh sau trong frontend / main.py :

# frontend/main.py

if st.button("Style Transfer"):
    if image is not None and style is not None:
        files = {"file": image.getvalue()}
        res = requests.post(f"http://backend:8080/{style}", files=files)
        img_path = res.json()
        image = Image.open(img_path.get("name"))
        st.image(image)

        displayed_styles = [style]
        displayed = 1
        total = len(STYLES)

        st.write("Generating other models...")

        while displayed < total:
            for style in STYLES:
                if style not in displayed_styles:
                    try:
                        path = f"{img_path.get('name').split('.')[0]}_{STYLES[style]}.jpg"
                        image = Image.open(path)
                        st.image(image, width=500)
                        time.sleep(1)
                        displayed += 1
                        displayed_styles.append(style)
                    except:
                        pass

Thêm nhập vào đầu:

import time

Vì vậy, sau khi hiển thị kiểu đầu tiên, chúng tôi tiếp tục kiểm tra các kiểu còn lại, hiển thị từng kiểu cho đến khi tất cả chín kiểu trên trang.

Cập nhật các vùng chứa và kiểm tra:

$ docker-compose up -d --build

Bây giờ, các kiểu còn lại sẽ được hiển thị không đồng bộ mà không chặn phản hồi ban đầu.

Sự kết luận

FastAPI là một giải pháp thay thế không đồng bộ hiện đại cho Flask. Nó có rất nhiều tính năng mà Flask thiếu và nhanh hơn Flask vì nó thúc đẩy Starlette và hỗ trợ các trình xử lý chức năng không đồng bộ. FastAPI có rất nhiều tính năng bổ sung như xác thực dữ liệu, tài liệu API tự động, tác vụ nền cũng như hệ thống tiêm phụ thuộc mạnh mẽ. Ngoài ra, vì rất có thể bạn sẽ tận dụng các gợi ý kiểu Python (vì vậy bạn có thể tận dụng xác thực dữ liệu), bạn sẽ có thể phát triển nhanh hơn do tính năng tự động hoàn thành của trình chỉnh sửa và kiểm tra lỗi tự động.

Bạn có thể tìm thấy mã cuối cùng trong repo chuyển kiểu trên GitHub.

Nguồn:  https://testdriven.io

#machine-learning #fastapi #streamlit #python 

Mô Hình Học Máy Với FastAPI Và Streamlit

Модель машинного обучения с FastAPI и Streamlit

В настоящее время машинное обучение является горячей темой. Благодаря тому, что технологические компании движутся в направлении искусственного интеллекта и машинного обучения, чтобы получить прибыль раньше, поле деятельности стало чрезвычайно большим. Многие из этих компаний создают собственные решения для машинного обучения и продают их другим по модели на основе подписки.

Поскольку большинство моделей машинного обучения разрабатываются на Python, веб-фреймворки, которые их обслуживают, также обычно основаны на Python. Долгое время Flask, микрофреймворк, был фреймворком goto. Но это меняется. Новый фреймворк, призванный компенсировать почти все, чего не хватает Flask, становится все более популярным. Он называется FastAPI.

FastAPI быстрее, чем Flask, потому что он добавляет в таблицу обработчики асинхронных функций:

Тесты веб-фреймворка

Источник: тесты TechEmpower Web Framework.

Как видно из рисунка выше, FastAPI почти в 3 раза быстрее, чем Flask.

FastAPI также поддерживает проверку данных с помощью pydantic и автоматической документации по API.

Между тем, Streamlit — это платформа приложений, которая позволяет специалистам по обработке и анализу данных и инженерам по машинному обучению легко создавать мощные пользовательские интерфейсы, взаимодействующие с моделями машинного обучения.

Хотя Streamlit можно использовать в производстве, он лучше всего подходит для быстрого прототипирования. Обслуживая модель с помощью FastAPI, вы можете быстро перейти к готовому пользовательскому интерфейсу с помощью Dash или React после утверждения прототипа.

С этим мы собираемся создать приложение для переноса стиля на основе статьи Perceptual Losses for Real-Time Style Transfer and Super-Resolution и предварительно обученных моделей Джастина Джонсона. Мы будем использовать FastAPI в качестве серверной части для обслуживания наших прогнозов, Streamlit для пользовательского интерфейса и OpenCV для фактического прогнозирования. Также будет использоваться Docker.

Одна мощная особенность модуля Deep Neural Networks (DNN) OpenCV заключается в том, что он может загружать обученные модели из Torch, TensorFlow и Caffe, эффективно избавляя нас от необходимости устанавливать эти зависимости.

Цели

К концу этого урока вы сможете:

  1. Разработайте асинхронный API с помощью Python и FastAPI.
  2. Предоставьте модель машинного обучения с помощью FastAPI
  3. Разработайте пользовательский интерфейс с помощью Streamlit
  4. Контейнеризируйте FastAPI и Streamlit с помощью Docker
  5. Используйте asyncio для выполнения кода в фоновом режиме вне потока запросов/ответов.

Настройка проекта

Создайте папку проекта под названием «style-transfer»:

$ mkdir style-transfer
$ cd style-transfer

Затем создайте две новые папки с помощью «style-transfer»:

$ mkdir frontend
$ mkdir backend

Добавьте файлы __init__.py в каждую папку.

Серверная часть FastAPI

Добавьте новый файл в «бэкенд» с именем main.py :

# backend/main.py

import uuid

import cv2
import uvicorn
from fastapi import File
from fastapi import FastAPI
from fastapi import UploadFile
import numpy as np
from PIL import Image

import config
import inference


app = FastAPI()


@app.get("/")
def read_root():
    return {"message": "Welcome from the API"}


@app.post("/{style}")
def get_image(style: str, file: UploadFile = File(...)):
    image = np.array(Image.open(file.file))
    model = config.STYLES[style]
    output, resized = inference.inference(model, image)
    name = f"/storage/{str(uuid.uuid4())}.jpg"
    cv2.imwrite(name, output)
    return {"name": name}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=8080)

Это наш сервер. FastAPI создает две конечные точки, одну фиктивную ( "/") и одну для обслуживания нашего прогноза ( "/{style}"). Обслуживающая конечная точка принимает имя в качестве параметра URL. Мы используем девять разных обученных моделей для переноса стиля, поэтому параметр пути скажет нам, какую модель выбрать. Изображение принимается в виде файла по запросу POST и отправляется в inferenceфункцию. После завершения вывода файл сохраняется в локальной файловой системе, а путь отправляется в качестве ответа.

Затем добавьте следующую конфигурацию в новый файл с именем backend/config.py :

# backend/config.py

MODEL_PATH = "./models/"

STYLES = {
    "candy": "candy",
    "composition 6": "composition_vii",
    "feathers": "feathers",
    "la_muse": "la_muse",
    "mosaic": "mosaic",
    "starry night": "starry_night",
    "the scream": "the_scream",
    "the wave": "the_wave",
    "udnie": "udnie",
}

Когда его представили, передача стиля изменила правила игры. Единственным недостатком было то, что изображение нужно было обучить, чтобы получить для него стиль. Это означает, что для получения стилизованного изображения вам нужно просмотреть исходные изображения несколько раз, прежде чем вы получите лучший результат. В 2016 году в документе Perceptual Losses for Real-Time Style Transfer and Super-Resolution была представлена ​​быстрая передача стилей, что означает, что вы можете стилизовать любое изображение за один проход. Мы собираемся использовать ту же технику с обученными моделями, предоставленными автором.

Теперь нам нужно загрузить модели. Добавьте скрипт в корень проекта с именем download_models.sh :

BASE_URL="https://cs.stanford.edu/people/jcjohns/fast-neural-style/models/"

mkdir -p backend/models/
cd backend/models/
curl -O "$BASE_URL/instance_norm/candy.t7"
curl -O "$BASE_URL/instance_norm/la_muse.t7"
curl -O "$BASE_URL/instance_norm/mosaic.t7"
curl -O "$BASE_URL/instance_norm/feathers.t7"
curl -O "$BASE_URL/instance_norm/the_scream.t7"
curl -O "$BASE_URL/instance_norm/udnie.t7"
curl -O "$BASE_URL/eccv16/the_wave.t7"
curl -O "$BASE_URL/eccv16/starry_night.t7"
curl -O "$BASE_URL/eccv16/la_muse.t7"
curl -O "$BASE_URL/eccv16/composition_vii.t7"

Скачать:

$ sh download_models.sh

Добавьте inferenceфункцию в backend/inference.py :

# backend/inference.py

import config
import cv2


def inference(model, image):
    model_name = f"{config.MODEL_PATH}{model}.t7"
    model = cv2.dnn.readNetFromTorch(model_name)

    height, width = int(image.shape[0]), int(image.shape[1])
    new_width = int((640 / height) * width)
    resized_image = cv2.resize(image, (new_width, 640), interpolation=cv2.INTER_AREA)

    # Create our blob from the image
    # Then perform a forward pass run of the network
    # The Mean values for the ImageNet training set are R=103.93, G=116.77, B=123.68

    inp_blob = cv2.dnn.blobFromImage(
        resized_image,
        1.0,
        (new_width, 640),
        (103.93, 116.77, 123.68),
        swapRB=False,
        crop=False,
    )

    model.setInput(inp_blob)
    output = model.forward()

    # Reshape the output Tensor,
    # add back the mean substruction,
    # re-order the channels
    output = output.reshape(3, output.shape[2], output.shape[3])
    output[0] += 103.93
    output[1] += 116.77
    output[2] += 123.68

    output = output.transpose(1, 2, 0)
    return output, resized_image

Здесь мы загрузили модель Torch, выполнили изменение размера и преобразовали ее в требуемый формат BLOB-объектов. Затем мы передали предварительно обработанное изображение в сеть/модель и получили результат. Постобработанное изображение и изображение с измененным размером возвращаются в качестве вывода.

Наконец, добавьте зависимости в файл требований:

# backend/requirements.txt

fastapi
numpy
opencv-python
pillow
python-multipart
uvicorn

Это все для бэкэнда. Давайте настроим Docker, а затем протестируем его.

Настройка докера

Сначала добавьте Dockerfile в папку «backend»:

# backend/Dockerfile

FROM python:3.10.1-slim

WORKDIR /app

RUN apt-get update
RUN apt-get install \
    'ffmpeg'\
    'libsm6'\
    'libxext6'  -y

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8080

CMD ["python", "main.py"]

«ffmpeg», «libsm6» и «libxext6» требуются для OpenCV.

Из папки «backend» в вашем терминале создайте образ:

$ docker build -t backend .

Запустите контейнер:

$ docker run -p 8080:8080 backend

INFO:     Started server process [1]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)

В браузере перейдите по адресу http://localhost:8080/ . Тебе следует увидеть:

{
  "message": "Welcome from the API"
}

Убейте контейнер, как только закончите.

Интерфейс Streamlit

Для пользовательского интерфейса добавьте файл main.py в папку «frontend»:

# frontend/main.py

import requests
import streamlit as st
from PIL import Image

STYLES = {
    "candy": "candy",
    "composition 6": "composition_vii",
    "feathers": "feathers",
    "la_muse": "la_muse",
    "mosaic": "mosaic",
    "starry night": "starry_night",
    "the scream": "the_scream",
    "the wave": "the_wave",
    "udnie": "udnie",
}

# https://discuss.streamlit.io/t/version-0-64-0-deprecation-warning-for-st-file-uploader-decoding/4465
st.set_option("deprecation.showfileUploaderEncoding", False)

# defines an h1 header
st.title("Style transfer web app")

# displays a file uploader widget
image = st.file_uploader("Choose an image")

# displays the select widget for the styles
style = st.selectbox("Choose the style", [i for i in STYLES.keys()])

# displays a button
if st.button("Style Transfer"):
    if image is not None and style is not None:
        files = {"file": image.getvalue()}
        res = requests.post(f"http://backend:8080/{style}", files=files)
        img_path = res.json()
        image = Image.open(img_path.get("name"))
        st.image(image, width=500)

Обратите внимание на комментарии к коду выше. Проще говоря, мы создали виджет загрузки изображения вместе с раскрывающимся списком выбора, отображающим каждый из стилей из STYLESсловаря. Мы также добавили кнопку, которая при нажатии отправляет изображение на серверную часть в виде полезной нагрузки POST-запроса в http://backend:8080/{style}. При получении пути к изображению в ответе от бэкенда изображение открывается и отображается.

Обратитесь к руководству Streamlit по началу работы и справочнику по API , чтобы получить помощь по отображению текста и данных, а также по добавлению базовых интерактивных функций с помощью виджетов.

Добавьте зависимость Streamlit в файл requirements.txt :

# frontend/requirements.txt

streamlit==1.2.0

Докер Сочинять

Затем давайте Dockerize интерфейс и соединим оба контейнера вместе с Docker Compose.

интерфейс/Dockerfile :

# frontend/Dockerfile

FROM python:3.10.1-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8501

CMD ["streamlit", "run", "main.py"]

докер-compose.yml :

version: '3'

services:
  frontend:
    build: frontend
    ports:
      - 8501:8501
    depends_on:
      - backend
    volumes:
        - ./storage:/storage
  backend:
    build: backend
    ports:
      - 8080:8080
    volumes:
      - ./storage:/storage

Самое главное здесь то, что мы сопоставили хранилище хост-машины с хранилищем каждого контейнера. Это важно для совместного использования пути, а также для сохранения данных при вращении контейнеров.

Таким образом, и бэкенд, и фронтенд могут обращаться к изображениям с одного и того же общего тома:

# backend
name = f"/storage/{str(uuid.uuid4())}.jpg"
cv2.imwrite(name, output)
return {"name": name}

# frontend
img_path = res.json()
image = Image.open(img_path.get("name"))

Для проверки из корня проекта соберите образы и запустите оба контейнера:

$ docker-compose up -d --build

Перейдите по адресу http://localhost:8501 :

Демонстрационное приложение

Обслуживание асинхронной модели

Теперь, когда вы увидели, как использовать FastAPI, Streamlit и OpenCV для передачи стилей, давайте проведем небольшой эксперимент.

Одной из самых мощных функций FastAPI является поддержка асинхронных функций. Итак, давайте воспользуемся асинхронной функцией для преобразования входного изображения в несколько стилей. Мы обработаем первый стиль синхронно, а затем отправим ответ, поскольку остальные модели обрабатываются в фоновом режиме.

Добавьте следующие функции в backend/main.py :

# backend/main.py

async def generate_remaining_models(models, image, name: str):
    executor = ProcessPoolExecutor()
    event_loop = asyncio.get_event_loop()
    await event_loop.run_in_executor(
        executor, partial(process_image, models, image, name)
    )


def process_image(models, image, name: str):
    for model in models:
        output, resized = inference.inference(models[model], image)
        name = name.split(".")[0]
        name = f"{name.split('_')[0]}_{models[model]}.jpg"
        cv2.imwrite(name, output)

Функция generate_remaining_modelsгенерирует каждый из оставшихся стилей с помощью asyncio .

Прочтите статью « Ускорение Python с помощью параллелизма, параллелизма и асинхронности » для получения дополнительной информации об асинхронности.

Добавьте следующий импорт:

import asyncio

from concurrent.futures import ProcessPoolExecutor

from functools import partial

Обновите get_imageфункцию, чтобы она создавала асинхронные задачи перед отправкой ответа:

# backend/main.py

@app.post("/{style}")
async def get_image(style: str, file: UploadFile = File(...)):
    image = np.array(Image.open(file.file))
    model = config.STYLES[style]
    start = time.time()
    output, resized = inference.inference(model, image)
    name = f"/storage/{str(uuid.uuid4())}.jpg"
    cv2.imwrite(name, output)
    models = config.STYLES.copy()
    del models[style]
    asyncio.create_task(generate_remaining_models(models, image, name))
    return {"name": name, "time": time.time() - start}

Как только будет сделан первый прогноз, мы удалим стиль из копии исходных стилей. Затем остальные стили передаются в generate_remaining_models.

Добавьте импорт:

import time

Затем обновите блок следующего ifоператора в frontend/main.py :

# frontend/main.py

if st.button("Style Transfer"):
    if image is not None and style is not None:
        files = {"file": image.getvalue()}
        res = requests.post(f"http://backend:8080/{style}", files=files)
        img_path = res.json()
        image = Image.open(img_path.get("name"))
        st.image(image)

        displayed_styles = [style]
        displayed = 1
        total = len(STYLES)

        st.write("Generating other models...")

        while displayed < total:
            for style in STYLES:
                if style not in displayed_styles:
                    try:
                        path = f"{img_path.get('name').split('.')[0]}_{STYLES[style]}.jpg"
                        image = Image.open(path)
                        st.image(image, width=500)
                        time.sleep(1)
                        displayed += 1
                        displayed_styles.append(style)
                    except:
                        pass

Добавьте импорт вверху:

import time

Итак, после отображения первого стиля мы продолжаем проверять остальные стили, отображая каждый до тех пор, пока все девять не окажутся на странице.

Обновите контейнеры и протестируйте:

$ docker-compose up -d --build

Теперь остальные стили будут отображаться асинхронно, не блокируя первоначальный ответ.

Вывод

FastAPI — это современная асинхронная альтернатива Flask. У него много функций, которых нет у Flask, и он быстрее, чем Flask, поскольку использует Starlette и поддерживает обработчики асинхронных функций. FastAPI имеет множество дополнительных функций, таких как проверка данных, автоматическая документация API, фоновые задачи, а также мощная система внедрения зависимостей. Кроме того, поскольку вы, скорее всего, воспользуетесь подсказками типов Python (чтобы использовать проверку данных), вы сможете двигаться быстрее в разработке благодаря автозаполнению редактора и автоматической проверке ошибок.

Вы можете найти окончательный код в репозитории переноса стилей на GitHub.

Источник:  https://testdriven.io

#machine-learning #fastapi #streamlit #python 

Модель машинного обучения с FastAPI и Streamlit

使用 FastAPI 和 Streamlit 的機器學習模型

機器學習是當前的熱門話題。隨著科技公司朝著人工智能和機器學習的方向發展以儘早兌現,該領域已經變得非常龐大。其中許多公司創建了自己的機器學習解決方案,並使用基於訂閱的模型將它們出售給其他人。

由於大多數機器學習模型都是用 Python 開發的,因此為它們提供服務的 Web 框架通常也是基於 Python 的。一直以來,Flask這個微框架都是goto框架。但這正在改變。一個旨在彌補 Flask 幾乎所有不足的新框架正變得越來越流行。它被稱為 FastAPI。

FastAPI 比 Flask 更快,因為它為錶帶來了異步函數處理程序:

Web 框架基準

來源:TechEmpower Web 框架基準

從上圖可以看出,FastAPI 比 Flask 快了近 3 倍。

FastAPI 也支持通過 pydantic 和自動 API 文檔進行數據驗證。

與此同時,Streamlit 是一個應用程序框架,可讓數據科學家和機器學習工程師輕鬆創建與機器學習模型交互的強大用戶界面。

雖然 Streamlit 可用於生產,但它最適合快速原型製作。通過使用 FastAPI 提供模型,在原型獲得批准後,您可以使用 Dash 或 React 快速遷移到生產就緒的 UI。

有了這個,我們將基於 Perceptual Losses for Real-Time Style Transfer and Super-Resolution 論文和 Justin Johnson 的預訓練模型構建一個風格遷移應用程序。我們將使用 FastAPI 作為後端來提供我們的預測,使用 Streamlit 作為用戶界面,使用 OpenCV 來進行實際預測。Docker 也將被使用。

OpenCV 的深度神經網絡 (DNN) 模塊的一個強大功能是它可以從 Torch、TensorFlow 和 Caffe 加載經過訓練的模型,有效地為我們省去了安裝這些依賴項的麻煩。

目標

在本教程結束時,您將能夠:

  1. 使用 Python 和 FastAPI 開發異步 API
  2. 使用 FastAPI 提供機器學習模型
  3. 使用 Streamlit 開發 UI
  4. 使用 Docker 容器化 FastAPI 和 Streamlit
  5. 利用 asyncio 在請求/響應流之外的後台執行代碼

項目設置

創建一個名為“style-transfer”的項目文件夾:

$ mkdir style-transfer
$ cd style-transfer

然後,使用“style-transfer”創建兩個新文件夾:

$ mkdir frontend
$ mkdir backend

__init__.py文件添加到每個文件夾。

FastAPI 後端

將一個名為main.py的新文件添加到“後端” :

# backend/main.py

import uuid

import cv2
import uvicorn
from fastapi import File
from fastapi import FastAPI
from fastapi import UploadFile
import numpy as np
from PIL import Image

import config
import inference


app = FastAPI()


@app.get("/")
def read_root():
    return {"message": "Welcome from the API"}


@app.post("/{style}")
def get_image(style: str, file: UploadFile = File(...)):
    image = np.array(Image.open(file.file))
    model = config.STYLES[style]
    output, resized = inference.inference(model, image)
    name = f"/storage/{str(uuid.uuid4())}.jpg"
    cv2.imwrite(name, output)
    return {"name": name}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=8080)

這是我們的服務器。FastAPI 創建了兩個端點,一個是虛擬的("/"),一個是為我們的預測服務("/{style}")。服務端點將名稱作為 URL 參數。我們使用九種不同的訓練模型來執行風格遷移,所以路徑參數會告訴我們選擇哪個模型。圖像通過 POST 請求作為文件被接受並發送到inference函數。推理完成後,該文件將存儲在本地文件系統中,並將路徑作為響應發送。

接下來,將以下配置添加到名為backend/config.py的新文件中:

# backend/config.py

MODEL_PATH = "./models/"

STYLES = {
    "candy": "candy",
    "composition 6": "composition_vii",
    "feathers": "feathers",
    "la_muse": "la_muse",
    "mosaic": "mosaic",
    "starry night": "starry_night",
    "the scream": "the_scream",
    "the wave": "the_wave",
    "udnie": "udnie",
}

引入時,風格轉換改變了遊戲規則。唯一的缺點是圖像必須經過訓練才能獲得它的風格。這意味著,要獲得樣式化的圖像,您需要多次遍歷原始圖像才能獲得更好的結果。2016 年,Perceptual Losses for Real-Time Style Transfer and Super-Resolution 論文引入了快速風格遷移,這意味著您可以在一次通過中對任何圖像進行樣式化。我們將對作者提供的訓練模型使用相同的技術。

現在,我們需要下載模型。將腳本添加到名為download_models.sh的項目根目錄:

BASE_URL="https://cs.stanford.edu/people/jcjohns/fast-neural-style/models/"

mkdir -p backend/models/
cd backend/models/
curl -O "$BASE_URL/instance_norm/candy.t7"
curl -O "$BASE_URL/instance_norm/la_muse.t7"
curl -O "$BASE_URL/instance_norm/mosaic.t7"
curl -O "$BASE_URL/instance_norm/feathers.t7"
curl -O "$BASE_URL/instance_norm/the_scream.t7"
curl -O "$BASE_URL/instance_norm/udnie.t7"
curl -O "$BASE_URL/eccv16/the_wave.t7"
curl -O "$BASE_URL/eccv16/starry_night.t7"
curl -O "$BASE_URL/eccv16/la_muse.t7"
curl -O "$BASE_URL/eccv16/composition_vii.t7"

下載:

$ sh download_models.sh

inference函數添加到backend/inference.py

# backend/inference.py

import config
import cv2


def inference(model, image):
    model_name = f"{config.MODEL_PATH}{model}.t7"
    model = cv2.dnn.readNetFromTorch(model_name)

    height, width = int(image.shape[0]), int(image.shape[1])
    new_width = int((640 / height) * width)
    resized_image = cv2.resize(image, (new_width, 640), interpolation=cv2.INTER_AREA)

    # Create our blob from the image
    # Then perform a forward pass run of the network
    # The Mean values for the ImageNet training set are R=103.93, G=116.77, B=123.68

    inp_blob = cv2.dnn.blobFromImage(
        resized_image,
        1.0,
        (new_width, 640),
        (103.93, 116.77, 123.68),
        swapRB=False,
        crop=False,
    )

    model.setInput(inp_blob)
    output = model.forward()

    # Reshape the output Tensor,
    # add back the mean substruction,
    # re-order the channels
    output = output.reshape(3, output.shape[2], output.shape[3])
    output[0] += 103.93
    output[1] += 116.77
    output[2] += 123.68

    output = output.transpose(1, 2, 0)
    return output, resized_image

在這裡,我們加載了 Torch 模型,執行了調整大小,並將其轉換為所需的 blob 格式。然後我們將預處理後的圖像傳遞到網絡/模型中並獲得輸出。後處理的圖像和調整大小的圖像作為輸出返回。

最後,將依賴項添加到需求文件中:

# backend/requirements.txt

fastapi
numpy
opencv-python
pillow
python-multipart
uvicorn

這就是後端。讓我們配置 Docker,然後對其進行測試。

碼頭工人設置

首先,將Dockerfile添加到“後端”文件夾:

# backend/Dockerfile

FROM python:3.10.1-slim

WORKDIR /app

RUN apt-get update
RUN apt-get install \
    'ffmpeg'\
    'libsm6'\
    'libxext6'  -y

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8080

CMD ["python", "main.py"]

OpenCV 需要“ffmpeg”、“libsm6”和“libxext6”。

從終端的“後端”文件夾中,構建圖像:

$ docker build -t backend .

運行容器:

$ docker run -p 8080:8080 backend

INFO:     Started server process [1]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)

在瀏覽器中,導航到http://localhost:8080/。你應該看到:

{
  "message": "Welcome from the API"
}

完成後殺死容器。

流光前端

對於 UI,將m​​ain.py文件添加到“frontend”文件夾:

# frontend/main.py

import requests
import streamlit as st
from PIL import Image

STYLES = {
    "candy": "candy",
    "composition 6": "composition_vii",
    "feathers": "feathers",
    "la_muse": "la_muse",
    "mosaic": "mosaic",
    "starry night": "starry_night",
    "the scream": "the_scream",
    "the wave": "the_wave",
    "udnie": "udnie",
}

# https://discuss.streamlit.io/t/version-0-64-0-deprecation-warning-for-st-file-uploader-decoding/4465
st.set_option("deprecation.showfileUploaderEncoding", False)

# defines an h1 header
st.title("Style transfer web app")

# displays a file uploader widget
image = st.file_uploader("Choose an image")

# displays the select widget for the styles
style = st.selectbox("Choose the style", [i for i in STYLES.keys()])

# displays a button
if st.button("Style Transfer"):
    if image is not None and style is not None:
        files = {"file": image.getvalue()}
        res = requests.post(f"http://backend:8080/{style}", files=files)
        img_path = res.json()
        image = Image.open(img_path.get("name"))
        st.image(image, width=500)

注意上面的代碼註釋。簡而言之,我們創建了一個上傳圖像小部件以及一個顯示STYLESdict 中每種樣式的選擇下拉菜單。我們還添加了一個按鈕,當按下該按鈕時,會將圖像作為 POST 請求有效負載發送到後端http://backend:8080/{style}。在從後端接收到響應中的圖像路徑後,圖像被打開並顯示。

有關顯示文本和數據以及添加與小部件的基本交互性的幫助,請參閱 Streamlit 的入門指南和API 參考。

Streamlit 依賴項添加到requirements.txt文件中:

# frontend/requirements.txt

streamlit==1.2.0

碼頭工人撰寫

接下來,讓我們對前端進行 Dockerize 化,並將兩個容器與 Docker Compose 連接在一起。

前端/Dockerfile

# frontend/Dockerfile

FROM python:3.10.1-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8501

CMD ["streamlit", "run", "main.py"]

碼頭工人-compose.yml

version: '3'

services:
  frontend:
    build: frontend
    ports:
      - 8501:8501
    depends_on:
      - backend
    volumes:
        - ./storage:/storage
  backend:
    build: backend
    ports:
      - 8080:8080
    volumes:
      - ./storage:/storage

這裡最重要的是我們將宿主機的存儲映射到每個容器的存儲。這對於共享路徑以及在關閉容器時保留數據非常重要。

因此,後端和前端都可以從同一個共享卷中訪問圖像:

# backend
name = f"/storage/{str(uuid.uuid4())}.jpg"
cv2.imwrite(name, output)
return {"name": name}

# frontend
img_path = res.json()
image = Image.open(img_path.get("name"))

要測試,從項目根目錄構建圖像並啟動兩個容器:

$ docker-compose up -d --build

導航到http://localhost:8501

演示應用

異步模型服務

現在您已經了解瞭如何使用 FastAPI、Streamlit 和 OpenCV 來執行樣式轉換,讓我們做一個小實驗。

FastAPI 最強大的特性之一是它支持異步函數。因此,讓我們利用異步函數將輸入圖像轉換為多種樣式。我們將同步處理第一個樣式,然後在後台處理其餘模型時發迴響應。

將以下函數添加到backend/main.py

# backend/main.py

async def generate_remaining_models(models, image, name: str):
    executor = ProcessPoolExecutor()
    event_loop = asyncio.get_event_loop()
    await event_loop.run_in_executor(
        executor, partial(process_image, models, image, name)
    )


def process_image(models, image, name: str):
    for model in models:
        output, resized = inference.inference(models[model], image)
        name = name.split(".")[0]
        name = f"{name.split('_')[0]}_{models[model]}.jpg"
        cv2.imwrite(name, output)

該函數使用asynciogenerate_remaining_models生成每個剩餘樣式。

查看使用並發、並行和異步加速 Python 的文章,了解有關異步的更多信息。

添加以下導入:

import asyncio

from concurrent.futures import ProcessPoolExecutor

from functools import partial

更新該get_image函數,使其在發迴響應之前創建異步任務:

# backend/main.py

@app.post("/{style}")
async def get_image(style: str, file: UploadFile = File(...)):
    image = np.array(Image.open(file.file))
    model = config.STYLES[style]
    start = time.time()
    output, resized = inference.inference(model, image)
    name = f"/storage/{str(uuid.uuid4())}.jpg"
    cv2.imwrite(name, output)
    models = config.STYLES.copy()
    del models[style]
    asyncio.create_task(generate_remaining_models(models, image, name))
    return {"name": name, "time": time.time() - start}

一旦做出第一個預測,我們將從原始樣式的副本中刪除該樣式。然後將剩餘的樣式傳遞給generate_remaining_models.

添加導入:

import time

接下來,更新frontend/main.py中以下if語句的塊:

# frontend/main.py

if st.button("Style Transfer"):
    if image is not None and style is not None:
        files = {"file": image.getvalue()}
        res = requests.post(f"http://backend:8080/{style}", files=files)
        img_path = res.json()
        image = Image.open(img_path.get("name"))
        st.image(image)

        displayed_styles = [style]
        displayed = 1
        total = len(STYLES)

        st.write("Generating other models...")

        while displayed < total:
            for style in STYLES:
                if style not in displayed_styles:
                    try:
                        path = f"{img_path.get('name').split('.')[0]}_{STYLES[style]}.jpg"
                        image = Image.open(path)
                        st.image(image, width=500)
                        time.sleep(1)
                        displayed += 1
                        displayed_styles.append(style)
                    except:
                        pass

將導入添加到頂部:

import time

因此,在顯示第一個樣式後,我們繼續檢查剩餘的樣式,顯示每個樣式,直到所有九個樣式都在頁面上。

更新容器並測試:

$ docker-compose up -d --build

現在,其餘樣式將異步顯示,而不會阻塞初始響應。

結論

FastAPI 是 Fl​​ask 的現代異步替代方案。它有很多 Flask 缺乏的特性,並且比 Flask 更快,因為它利用了 Starlette 並支持異步函數處理程序。FastAPI 具有許多附加功能,例如數據驗證、自動 API 文檔、後台任務以及強大的依賴注入系統。此外,由於您很可能會利用 Python 類型提示(因此您可以利用數據驗證),由於編輯器自動完成和自動錯誤檢查,您將能夠更快地進行開發。

您可以在 GitHub 上的樣式轉移存儲庫中找到最終代碼。

來源:  https ://testdriven.io

#machine-learning #fastapi #streamlit #python 

使用 FastAPI 和 Streamlit 的機器學習模型

Modèle D'apprentissage Automatique Avec FastAPI Et Streamlit

L'apprentissage automatique est un sujet brûlant à l'heure actuelle. Avec les entreprises technologiques qui se tournent vers l'intelligence artificielle et l'apprentissage automatique pour encaisser tôt, le domaine s'est énormément développé. Beaucoup de ces entreprises créent leurs propres solutions d'apprentissage automatique et les vendent à d'autres en utilisant un modèle basé sur l'abonnement.

Étant donné que la majorité des modèles d'apprentissage automatique sont développés en Python, les frameworks Web qui les servent sont généralement également basés sur Python. Pendant longtemps, Flask, un micro-framework, était le framework incontournable. Mais cela change. Un nouveau cadre, conçu pour compenser presque tout ce qui manque à Flask, devient de plus en plus populaire. Cela s'appelle FastAPI.

FastAPI est plus rapide que Flask car il apporte des gestionnaires de fonctions asynchrones à la table :

Benchmarks de framework Web

Source : Benchmarks du cadre Web TechEmpower

Comme vous pouvez le voir sur la figure ci-dessus, FastAPI est presque 3 fois plus rapide que Flask.

FastAPI prend également en charge la validation des données via la documentation API pydantic et automatique.

Streamlit, quant à lui, est un cadre d'application qui permet aux scientifiques des données et aux ingénieurs en apprentissage automatique de créer facilement des interfaces utilisateur puissantes qui interagissent avec les modèles d'apprentissage automatique.

Bien que Streamlit puisse être utilisé en production, il est préférable pour le prototypage rapide. En servant le modèle avec FastAPI, vous pouvez rapidement passer à une interface utilisateur prête pour la production avec Dash ou React, une fois le prototype approuvé.

Avec cela, nous allons créer une application de transfert de style basée sur les pertes de perception pour le transfert de style en temps réel et le papier Super-Resolution et les modèles pré-formés de Justin Johnson. Nous utiliserons FastAPI comme backend pour servir nos prédictions, Streamlit pour l'interface utilisateur et OpenCV pour faire la prédiction réelle. Docker sera également utilisé.

Une fonctionnalité puissante du module Deep Neural Networks (DNN) d'OpenCV est qu'il peut charger des modèles formés à partir de Torch, TensorFlow et Caffe, ce qui nous évite efficacement d'avoir à installer ces dépendances.

Objectifs

À la fin de ce didacticiel, vous serez en mesure de :

  1. Développer une API asynchrone avec Python et FastAPI
  2. Servez un modèle d'apprentissage automatique avec FastAPI
  3. Développer une interface utilisateur avec Streamlit
  4. Conteneurisez FastAPI et Streamlit avec Docker
  5. Tirez parti de l'asyncio pour exécuter du code en arrière-plan en dehors du flux de requête/réponse

Configuration du projet

Créez un dossier de projet appelé "style-transfer":

$ mkdir style-transfer
$ cd style-transfer

Ensuite, créez deux nouveaux dossiers avec "style-transfer":

$ mkdir frontend
$ mkdir backend

Ajoutez les fichiers __init__.py à chaque dossier.

Backend FastAPI

Ajoutez un nouveau fichier au "backend" appelé main.py :

# backend/main.py

import uuid

import cv2
import uvicorn
from fastapi import File
from fastapi import FastAPI
from fastapi import UploadFile
import numpy as np
from PIL import Image

import config
import inference


app = FastAPI()


@app.get("/")
def read_root():
    return {"message": "Welcome from the API"}


@app.post("/{style}")
def get_image(style: str, file: UploadFile = File(...)):
    image = np.array(Image.open(file.file))
    model = config.STYLES[style]
    output, resized = inference.inference(model, image)
    name = f"/storage/{str(uuid.uuid4())}.jpg"
    cv2.imwrite(name, output)
    return {"name": name}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=8080)

C'est notre serveur. FastAPI crée deux points de terminaison, un factice ( "/") et un pour servir notre prédiction ( "/{style}"). Le point de terminaison de diffusion prend un nom en tant que paramètre d'URL. Nous utilisons neuf modèles formés différents pour effectuer le transfert de style, donc le paramètre de chemin nous indiquera quel modèle choisir. L'image est acceptée en tant que fichier via une requête POST et envoyée à la inferencefonction. Une fois l'inférence terminée, le fichier est stocké sur le système de fichiers local et le chemin est envoyé en réponse.

Ensuite, ajoutez la configuration suivante à un nouveau fichier appelé backend/config.py :

# backend/config.py

MODEL_PATH = "./models/"

STYLES = {
    "candy": "candy",
    "composition 6": "composition_vii",
    "feathers": "feathers",
    "la_muse": "la_muse",
    "mosaic": "mosaic",
    "starry night": "starry_night",
    "the scream": "the_scream",
    "the wave": "the_wave",
    "udnie": "udnie",
}

Lors de son introduction, le transfert de style a changé la donne. Le seul inconvénient était que l'image devait être formée pour lui donner un style. Cela signifie que pour obtenir une image stylée, vous devez parcourir les images d'origine plusieurs fois avant d'obtenir un meilleur résultat. En 2016, le papier Perceptual Loss for Real-Time Style Transfer and Super-Resolution a introduit le transfert de style rapide, ce qui signifie que vous pouvez styliser n'importe quelle image en un seul passage. Nous allons utiliser la même technique avec les modèles entraînés fournis par l'auteur.

Maintenant, nous devons télécharger les modèles. Ajoutez un script à la racine du projet appelé download_models.sh :

BASE_URL="https://cs.stanford.edu/people/jcjohns/fast-neural-style/models/"

mkdir -p backend/models/
cd backend/models/
curl -O "$BASE_URL/instance_norm/candy.t7"
curl -O "$BASE_URL/instance_norm/la_muse.t7"
curl -O "$BASE_URL/instance_norm/mosaic.t7"
curl -O "$BASE_URL/instance_norm/feathers.t7"
curl -O "$BASE_URL/instance_norm/the_scream.t7"
curl -O "$BASE_URL/instance_norm/udnie.t7"
curl -O "$BASE_URL/eccv16/the_wave.t7"
curl -O "$BASE_URL/eccv16/starry_night.t7"
curl -O "$BASE_URL/eccv16/la_muse.t7"
curl -O "$BASE_URL/eccv16/composition_vii.t7"

Télécharger:

$ sh download_models.sh

Ajoutez la inferencefonction à backend/inference.py :

# backend/inference.py

import config
import cv2


def inference(model, image):
    model_name = f"{config.MODEL_PATH}{model}.t7"
    model = cv2.dnn.readNetFromTorch(model_name)

    height, width = int(image.shape[0]), int(image.shape[1])
    new_width = int((640 / height) * width)
    resized_image = cv2.resize(image, (new_width, 640), interpolation=cv2.INTER_AREA)

    # Create our blob from the image
    # Then perform a forward pass run of the network
    # The Mean values for the ImageNet training set are R=103.93, G=116.77, B=123.68

    inp_blob = cv2.dnn.blobFromImage(
        resized_image,
        1.0,
        (new_width, 640),
        (103.93, 116.77, 123.68),
        swapRB=False,
        crop=False,
    )

    model.setInput(inp_blob)
    output = model.forward()

    # Reshape the output Tensor,
    # add back the mean substruction,
    # re-order the channels
    output = output.reshape(3, output.shape[2], output.shape[3])
    output[0] += 103.93
    output[1] += 116.77
    output[2] += 123.68

    output = output.transpose(1, 2, 0)
    return output, resized_image

Ici, nous avons chargé le modèle Torch, effectué un redimensionnement et l'avons converti au format blob requis. Ensuite, nous avons passé l'image prétraitée dans le réseau/modèle et obtenu la sortie. L'image post-traitée et l'image redimensionnée sont renvoyées en sortie.

Enfin, ajoutez les dépendances à un fichier requirements :

# backend/requirements.txt

fastapi
numpy
opencv-python
pillow
python-multipart
uvicorn

C'est tout pour le backend. Configurons Docker, puis testons-le.

Configuration Docker

Tout d'abord, ajoutez un Dockerfile au dossier "backend":

# backend/Dockerfile

FROM python:3.10.1-slim

WORKDIR /app

RUN apt-get update
RUN apt-get install \
    'ffmpeg'\
    'libsm6'\
    'libxext6'  -y

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8080

CMD ["python", "main.py"]

'ffmpeg', 'libsm6' et 'libxext6' sont requis pour OpenCV.

Depuis le dossier "backend" de votre terminal, construisez l'image :

$ docker build -t backend .

Exécutez le conteneur :

$ docker run -p 8080:8080 backend

INFO:     Started server process [1]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)

Dans votre navigateur, accédez à http://localhost:8080/ . Tu devrais voir:

{
  "message": "Welcome from the API"
}

Tuez le conteneur une fois terminé.

Frontend éclairé

Pour l'interface utilisateur, ajoutez un fichier main.py au dossier "frontend":

# frontend/main.py

import requests
import streamlit as st
from PIL import Image

STYLES = {
    "candy": "candy",
    "composition 6": "composition_vii",
    "feathers": "feathers",
    "la_muse": "la_muse",
    "mosaic": "mosaic",
    "starry night": "starry_night",
    "the scream": "the_scream",
    "the wave": "the_wave",
    "udnie": "udnie",
}

# https://discuss.streamlit.io/t/version-0-64-0-deprecation-warning-for-st-file-uploader-decoding/4465
st.set_option("deprecation.showfileUploaderEncoding", False)

# defines an h1 header
st.title("Style transfer web app")

# displays a file uploader widget
image = st.file_uploader("Choose an image")

# displays the select widget for the styles
style = st.selectbox("Choose the style", [i for i in STYLES.keys()])

# displays a button
if st.button("Style Transfer"):
    if image is not None and style is not None:
        files = {"file": image.getvalue()}
        res = requests.post(f"http://backend:8080/{style}", files=files)
        img_path = res.json()
        image = Image.open(img_path.get("name"))
        st.image(image, width=500)

Prenez note des commentaires de code ci-dessus. En termes simples, nous avons créé un widget d'image de téléchargement avec une liste déroulante de sélection affichant chacun des styles du STYLESdict. Nous avons également ajouté un bouton qui, lorsqu'il est pressé, envoie l'image au backend en tant que charge utile de requête POST à http://backend:8080/{style}​​. Lors de la réception du chemin de l'image dans la réponse du backend, l'image est ouverte et affichée.

Reportez-vous au guide de démarrage de Streamlit et à la référence de l'API pour obtenir de l'aide sur l'affichage du texte et des données ainsi que sur l'ajout d'une interactivité de base avec les widgets.

Ajoutez la dépendance Streamlit à un fichier requirements.txt :

# frontend/requirements.txt

streamlit==1.2.0

Docker Composer

Ensuite, dockerisons l'interface et connectons les deux conteneurs avec Docker Compose.

frontend/Dockerfile :

# frontend/Dockerfile

FROM python:3.10.1-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8501

CMD ["streamlit", "run", "main.py"]

docker-compose.yml :

version: '3'

services:
  frontend:
    build: frontend
    ports:
      - 8501:8501
    depends_on:
      - backend
    volumes:
        - ./storage:/storage
  backend:
    build: backend
    ports:
      - 8080:8080
    volumes:
      - ./storage:/storage

La chose la plus importante ici est que nous avons mappé le stockage de la machine hôte sur le stockage de chaque conteneur. Ceci est important pour partager le chemin et également pour conserver les données lors de la rotation des conteneurs.

Ainsi, le backend et le frontend peuvent accéder aux images du même volume partagé :

# backend
name = f"/storage/{str(uuid.uuid4())}.jpg"
cv2.imwrite(name, output)
return {"name": name}

# frontend
img_path = res.json()
image = Image.open(img_path.get("name"))

Pour tester, à partir de la racine du projet, créez les images et lancez les deux conteneurs :

$ docker-compose up -d --build

Accédez à http://localhost:8501 :

Application de démonstration

Diffusion de modèle asynchrone

Maintenant que vous avez vu comment utiliser FastAPI, Streamlit et OpenCV pour effectuer un transfert de style, faisons une petite expérience.

L'une des fonctionnalités les plus puissantes de FastAPI est qu'elle prend en charge les fonctions asynchrones. Alors, utilisons une fonction asynchrone pour convertir l'image d'entrée en plusieurs styles. Nous traiterons le premier style de manière synchrone, puis renverrons la réponse au fur et à mesure que les modèles restants seront traités en arrière-plan.

Ajoutez les fonctions suivantes à backend/main.py :

# backend/main.py

async def generate_remaining_models(models, image, name: str):
    executor = ProcessPoolExecutor()
    event_loop = asyncio.get_event_loop()
    await event_loop.run_in_executor(
        executor, partial(process_image, models, image, name)
    )


def process_image(models, image, name: str):
    for model in models:
        output, resized = inference.inference(models[model], image)
        name = name.split(".")[0]
        name = f"{name.split('_')[0]}_{models[model]}.jpg"
        cv2.imwrite(name, output)

La generate_remaining_modelsfonction génère chacun des styles restants en utilisant asyncio .

Consultez l' article Accélérer Python avec la concurrence, le parallélisme et l'asyncio pour plus d'informations sur l'asyncio.

Ajoutez les importations suivantes :

import asyncio

from concurrent.futures import ProcessPoolExecutor

from functools import partial

Mettez à jour la get_imagefonction afin qu'elle crée les tâches asynchrones avant de renvoyer la réponse :

# backend/main.py

@app.post("/{style}")
async def get_image(style: str, file: UploadFile = File(...)):
    image = np.array(Image.open(file.file))
    model = config.STYLES[style]
    start = time.time()
    output, resized = inference.inference(model, image)
    name = f"/storage/{str(uuid.uuid4())}.jpg"
    cv2.imwrite(name, output)
    models = config.STYLES.copy()
    del models[style]
    asyncio.create_task(generate_remaining_models(models, image, name))
    return {"name": name, "time": time.time() - start}

Une fois la première prédiction effectuée, nous supprimerons le style d'une copie des styles d'origine. Ensuite, les styles restants sont transmis à generate_remaining_models.

Ajoutez l'importation :

import time

Ensuite, mettez à jour le bloc de l' ifinstruction suivante dans frontend/main.py :

# frontend/main.py

if st.button("Style Transfer"):
    if image is not None and style is not None:
        files = {"file": image.getvalue()}
        res = requests.post(f"http://backend:8080/{style}", files=files)
        img_path = res.json()
        image = Image.open(img_path.get("name"))
        st.image(image)

        displayed_styles = [style]
        displayed = 1
        total = len(STYLES)

        st.write("Generating other models...")

        while displayed < total:
            for style in STYLES:
                if style not in displayed_styles:
                    try:
                        path = f"{img_path.get('name').split('.')[0]}_{STYLES[style]}.jpg"
                        image = Image.open(path)
                        st.image(image, width=500)
                        time.sleep(1)
                        displayed += 1
                        displayed_styles.append(style)
                    except:
                        pass

Ajoutez l'importation en haut :

import time

Ainsi, après avoir affiché le premier style, nous continuons à vérifier les styles restants, en les affichant jusqu'à ce que les neuf soient sur la page.

Mettez à jour les conteneurs et testez :

$ docker-compose up -d --build

Désormais, les styles restants seront affichés de manière asynchrone sans bloquer la réponse initiale.

Conclusion

FastAPI est une alternative moderne et asynchrone à Flask. Il possède de nombreuses fonctionnalités qui manquent à Flask et est plus rapide que Flask car il exploite Starlette et prend en charge les gestionnaires de fonctions asynchrones. FastAPI possède de nombreuses fonctionnalités supplémentaires telles que la validation des données, la documentation automatique de l'API, les tâches en arrière-plan ainsi qu'un puissant système d'injection de dépendances. De plus, puisque vous tirerez probablement parti des conseils de type Python (afin que vous puissiez tirer parti de la validation des données), vous pourrez avancer plus rapidement dans le développement grâce à la saisie semi-automatique de l'éditeur et aux vérifications automatiques des erreurs.

Vous pouvez trouver le code final dans le dépôt de transfert de style sur GitHub.

Source :  https://testdrive.io

#machine-learning #fastapi #streamlit #python 

Modèle D'apprentissage Automatique Avec FastAPI Et Streamlit
Derrick  Ferry

Derrick Ferry

1660247220

Modelo De Aprendizaje Automático Con FastAPI Y Streamlit

El aprendizaje automático es un tema candente en la actualidad. Con las empresas de tecnología moviéndose en la dirección de la inteligencia artificial y el aprendizaje automático para sacar provecho temprano, el campo se ha vuelto tremendamente grande. Muchas de estas empresas crean sus propias soluciones de aprendizaje automático y las venden a otros mediante un modelo basado en suscripción.

Dado que la mayoría de los modelos de aprendizaje automático se desarrollan en Python, los marcos web que los sirven también suelen estar basados ​​en Python. Durante mucho tiempo, Flask, un micro-framework, fue el marco goto. Pero eso está cambiando. Un nuevo marco, diseñado para compensar casi todo lo que le falta a Flask, se está volviendo cada vez más popular. Se llama FastAPI.

FastAPI es más rápido que Flask porque trae controladores de funciones asincrónicas a la mesa:

Puntos de referencia del marco web

Fuente: Puntos de referencia de TechEmpower Web Framework

Como puede ver en la figura anterior, FastAPI es casi 3 veces más rápido que Flask.

FastAPI también admite la validación de datos a través de la documentación API automática y dinámica.

Mientras tanto, Streamlit es un marco de aplicación que facilita a los científicos de datos y a los ingenieros de aprendizaje automático la creación de potentes interfaces de usuario que interactúan con los modelos de aprendizaje automático.

Aunque Streamlit se puede usar en producción, es mejor para la creación rápida de prototipos. Al entregar el modelo con FastAPI, puede pasar rápidamente a una interfaz de usuario lista para producción con Dash o React, una vez que se apruebe el prototipo.

Con eso, vamos a construir una aplicación de transferencia de estilo basada en el documento Perceptual Losses for Real-Time Style Transfer and Super-Resolution y los modelos pre-entrenados de Justin Johnson. Usaremos FastAPI como backend para servir nuestras predicciones, Streamlit para la interfaz de usuario y OpenCV para hacer la predicción real. También se utilizará Docker.

Una característica poderosa del módulo Redes neuronales profundas (DNN) de OpenCV es que puede cargar modelos entrenados de Torch, TensorFlow y Caffe, lo que nos ahorra la molestia de instalar esas dependencias.

Objetivos

Al final de este tutorial, podrá:

  1. Desarrolle una API asíncrona con Python y FastAPI
  2. Sirva un modelo de aprendizaje automático con FastAPI
  3. Desarrollar una interfaz de usuario con Streamlit
  4. Contenga FastAPI y Streamlit con Docker
  5. Aproveche asyncio para ejecutar código en segundo plano fuera del flujo de solicitud/respuesta

Configuración del proyecto

Cree una carpeta de proyecto llamada "transferencia de estilo":

$ mkdir style-transfer
$ cd style-transfer

Luego, crea dos nuevas carpetas con "style-transfer":

$ mkdir frontend
$ mkdir backend

Agregue archivos __init__.py a cada carpeta.

Back-end FastAPI

Agregue un nuevo archivo a "backend" llamado main.py :

# backend/main.py

import uuid

import cv2
import uvicorn
from fastapi import File
from fastapi import FastAPI
from fastapi import UploadFile
import numpy as np
from PIL import Image

import config
import inference


app = FastAPI()


@app.get("/")
def read_root():
    return {"message": "Welcome from the API"}


@app.post("/{style}")
def get_image(style: str, file: UploadFile = File(...)):
    image = np.array(Image.open(file.file))
    model = config.STYLES[style]
    output, resized = inference.inference(model, image)
    name = f"/storage/{str(uuid.uuid4())}.jpg"
    cv2.imwrite(name, output)
    return {"name": name}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=8080)

Este es nuestro servidor. FastAPI crea dos puntos finales, uno ficticio ( "/") y otro para servir nuestra predicción ( "/{style}"). El punto final de servicio toma un nombre como parámetro de URL. Estamos utilizando nueve modelos entrenados diferentes para realizar la transferencia de estilo, por lo que el parámetro de ruta nos dirá qué modelo elegir. La imagen se acepta como un archivo a través de una solicitud POST y se envía a la inferencefunción. Una vez que se completa la inferencia, el archivo se almacena en el sistema de archivos local y la ruta se envía como respuesta.

A continuación, agregue la siguiente configuración a un nuevo archivo llamado backend/config.py :

# backend/config.py

MODEL_PATH = "./models/"

STYLES = {
    "candy": "candy",
    "composition 6": "composition_vii",
    "feathers": "feathers",
    "la_muse": "la_muse",
    "mosaic": "mosaic",
    "starry night": "starry_night",
    "the scream": "the_scream",
    "the wave": "the_wave",
    "udnie": "udnie",
}

Cuando se introdujo, la transferencia de estilo cambió las reglas del juego. La única desventaja era que había que entrenar la imagen para obtener un estilo para ella. Esto significa que, para obtener una imagen con estilo, debe revisar las imágenes originales varias veces antes de obtener un mejor resultado. En 2016, el papel Perceptual Losses for Real-Time Style Transfer y Super-Resolution introdujo la transferencia de estilo rápido, lo que significa que puede diseñar cualquier imagen en una sola pasada. Vamos a utilizar la misma técnica con los modelos entrenados proporcionados por el autor.

Ahora, necesitamos descargar los modelos. Agregue un script a la raíz del proyecto llamado download_models.sh :

BASE_URL="https://cs.stanford.edu/people/jcjohns/fast-neural-style/models/"

mkdir -p backend/models/
cd backend/models/
curl -O "$BASE_URL/instance_norm/candy.t7"
curl -O "$BASE_URL/instance_norm/la_muse.t7"
curl -O "$BASE_URL/instance_norm/mosaic.t7"
curl -O "$BASE_URL/instance_norm/feathers.t7"
curl -O "$BASE_URL/instance_norm/the_scream.t7"
curl -O "$BASE_URL/instance_norm/udnie.t7"
curl -O "$BASE_URL/eccv16/the_wave.t7"
curl -O "$BASE_URL/eccv16/starry_night.t7"
curl -O "$BASE_URL/eccv16/la_muse.t7"
curl -O "$BASE_URL/eccv16/composition_vii.t7"

Descargar:

$ sh download_models.sh

Agrega la inferencefunción a backend/inference.py :

# backend/inference.py

import config
import cv2


def inference(model, image):
    model_name = f"{config.MODEL_PATH}{model}.t7"
    model = cv2.dnn.readNetFromTorch(model_name)

    height, width = int(image.shape[0]), int(image.shape[1])
    new_width = int((640 / height) * width)
    resized_image = cv2.resize(image, (new_width, 640), interpolation=cv2.INTER_AREA)

    # Create our blob from the image
    # Then perform a forward pass run of the network
    # The Mean values for the ImageNet training set are R=103.93, G=116.77, B=123.68

    inp_blob = cv2.dnn.blobFromImage(
        resized_image,
        1.0,
        (new_width, 640),
        (103.93, 116.77, 123.68),
        swapRB=False,
        crop=False,
    )

    model.setInput(inp_blob)
    output = model.forward()

    # Reshape the output Tensor,
    # add back the mean substruction,
    # re-order the channels
    output = output.reshape(3, output.shape[2], output.shape[3])
    output[0] += 103.93
    output[1] += 116.77
    output[2] += 123.68

    output = output.transpose(1, 2, 0)
    return output, resized_image

Aquí, cargamos el modelo Torch, cambiamos el tamaño y lo convertimos al formato de blob requerido. Luego pasamos la imagen preprocesada a la red/modelo y obtuvimos la salida. La imagen posprocesada y la imagen redimensionada se devuelven como salida.

Por último, agregue las dependencias a un archivo de requisitos:

# backend/requirements.txt

fastapi
numpy
opencv-python
pillow
python-multipart
uvicorn

Eso es todo para el backend. Configuremos Docker y luego pruébelo.

Configuración de la ventana acoplable

Primero, agregue un Dockerfile a la carpeta "backend":

# backend/Dockerfile

FROM python:3.10.1-slim

WORKDIR /app

RUN apt-get update
RUN apt-get install \
    'ffmpeg'\
    'libsm6'\
    'libxext6'  -y

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8080

CMD ["python", "main.py"]

Se requieren 'ffmpeg', 'libsm6' y 'libxext6' para OpenCV.

Desde la carpeta "backend" en su terminal, cree la imagen:

$ docker build -t backend .

Ejecute el contenedor:

$ docker run -p 8080:8080 backend

INFO:     Started server process [1]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)

En su navegador, vaya a http://localhost:8080/ . Debería ver:

{
  "message": "Welcome from the API"
}

Mata el contenedor una vez hecho.

Frontend aerodinámico

Para la interfaz de usuario, agregue un archivo main.py a la carpeta "frontend":

# frontend/main.py

import requests
import streamlit as st
from PIL import Image

STYLES = {
    "candy": "candy",
    "composition 6": "composition_vii",
    "feathers": "feathers",
    "la_muse": "la_muse",
    "mosaic": "mosaic",
    "starry night": "starry_night",
    "the scream": "the_scream",
    "the wave": "the_wave",
    "udnie": "udnie",
}

# https://discuss.streamlit.io/t/version-0-64-0-deprecation-warning-for-st-file-uploader-decoding/4465
st.set_option("deprecation.showfileUploaderEncoding", False)

# defines an h1 header
st.title("Style transfer web app")

# displays a file uploader widget
image = st.file_uploader("Choose an image")

# displays the select widget for the styles
style = st.selectbox("Choose the style", [i for i in STYLES.keys()])

# displays a button
if st.button("Style Transfer"):
    if image is not None and style is not None:
        files = {"file": image.getvalue()}
        res = requests.post(f"http://backend:8080/{style}", files=files)
        img_path = res.json()
        image = Image.open(img_path.get("name"))
        st.image(image, width=500)

Tome nota de los comentarios de código anteriores. En pocas palabras, creamos un widget de imagen de carga junto con un menú desplegable de selección que muestra cada uno de los estilos del STYLESdictado. También agregamos un botón que, cuando se presiona, envía la imagen al backend como una carga útil de solicitud POST a http://backend:8080/{style}. Al recibir la ruta de la imagen en la respuesta del backend, la imagen se abre y se muestra.

Consulte la guía de inicio de Streamlit y la referencia de API para obtener ayuda con la visualización de texto y datos, así como para agregar interactividad básica con widgets.

Agrega la dependencia de Streamlit a un archivo requirements.txt :

# frontend/requirements.txt

streamlit==1.2.0

Componer ventana acoplable

A continuación, vamos a dockerizar la interfaz y conectar ambos contenedores con Docker Compose.

interfaz/Dockerfile :

# frontend/Dockerfile

FROM python:3.10.1-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8501

CMD ["streamlit", "run", "main.py"]

docker-compose.yml :

version: '3'

services:
  frontend:
    build: frontend
    ports:
      - 8501:8501
    depends_on:
      - backend
    volumes:
        - ./storage:/storage
  backend:
    build: backend
    ports:
      - 8080:8080
    volumes:
      - ./storage:/storage

Lo más importante aquí es que asignamos el almacenamiento de la máquina host al almacenamiento de cada contenedor. Esto es importante para compartir la ruta y también para conservar los datos cuando se desactivan los contenedores.

Por lo tanto, tanto el backend como el frontend pueden acceder a imágenes desde el mismo volumen compartido:

# backend
name = f"/storage/{str(uuid.uuid4())}.jpg"
cv2.imwrite(name, output)
return {"name": name}

# frontend
img_path = res.json()
image = Image.open(img_path.get("name"))

Para probar, desde la raíz del proyecto, cree las imágenes y active ambos contenedores:

$ docker-compose up -d --build

Navegue a http://localhost:8501 :

Aplicación de demostración

Servicio de modelo asíncrono

Ahora que ha visto cómo usar FastAPI, Streamlit y OpenCV para realizar una transferencia de estilo, hagamos un pequeño experimento.

Una de las características más poderosas de FastAPI es que admite funciones asíncronas. Entonces, aprovechemos una función asíncrona para convertir la imagen de entrada en múltiples estilos. Procesaremos el primer estilo sincrónicamente y luego enviaremos la respuesta mientras los modelos restantes se procesan en segundo plano.

Agrega las siguientes funciones a backend/main.py :

# backend/main.py

async def generate_remaining_models(models, image, name: str):
    executor = ProcessPoolExecutor()
    event_loop = asyncio.get_event_loop()
    await event_loop.run_in_executor(
        executor, partial(process_image, models, image, name)
    )


def process_image(models, image, name: str):
    for model in models:
        output, resized = inference.inference(models[model], image)
        name = name.split(".")[0]
        name = f"{name.split('_')[0]}_{models[model]}.jpg"
        cv2.imwrite(name, output)

La generate_remaining_modelsfunción genera cada uno de los estilos restantes usando asyncio .

Consulte el artículo Aceleración de Python con concurrencia, paralelismo y asyncio para obtener más información sobre asyncio.

Agregue las siguientes importaciones:

import asyncio

from concurrent.futures import ProcessPoolExecutor

from functools import partial

Actualice la get_imagefunción para que cree las tareas asincrónicas antes de devolver la respuesta:

# backend/main.py

@app.post("/{style}")
async def get_image(style: str, file: UploadFile = File(...)):
    image = np.array(Image.open(file.file))
    model = config.STYLES[style]
    start = time.time()
    output, resized = inference.inference(model, image)
    name = f"/storage/{str(uuid.uuid4())}.jpg"
    cv2.imwrite(name, output)
    models = config.STYLES.copy()
    del models[style]
    asyncio.create_task(generate_remaining_models(models, image, name))
    return {"name": name, "time": time.time() - start}

Una vez realizada la primera predicción, eliminaremos el estilo de una copia de los estilos originales. Luego, los estilos restantes se pasan a generate_remaining_models.

Añadir la importación:

import time

A continuación, actualice el bloque de la siguiente ifdeclaración en frontend/main.py :

# frontend/main.py

if st.button("Style Transfer"):
    if image is not None and style is not None:
        files = {"file": image.getvalue()}
        res = requests.post(f"http://backend:8080/{style}", files=files)
        img_path = res.json()
        image = Image.open(img_path.get("name"))
        st.image(image)

        displayed_styles = [style]
        displayed = 1
        total = len(STYLES)

        st.write("Generating other models...")

        while displayed < total:
            for style in STYLES:
                if style not in displayed_styles:
                    try:
                        path = f"{img_path.get('name').split('.')[0]}_{STYLES[style]}.jpg"
                        image = Image.open(path)
                        st.image(image, width=500)
                        time.sleep(1)
                        displayed += 1
                        displayed_styles.append(style)
                    except:
                        pass

Agregue la importación a la parte superior:

import time

Entonces, después de mostrar el primer estilo, continuamos buscando los estilos restantes, mostrando cada uno hasta que los nueve estén en la página.

Actualice los contenedores y pruebe:

$ docker-compose up -d --build

Ahora, los estilos restantes se mostrarán de forma asíncrona sin bloquear la respuesta inicial.

Conclusión

FastAPI es una alternativa moderna y asíncrona a Flask. Tiene muchas características de las que carece Flask y es más rápido que Flask, ya que aprovecha Starlette y admite controladores de funciones asincrónicas. FastAPI tiene muchas características adicionales como validación de datos, documentación API automática, tareas en segundo plano y un poderoso sistema de inyección de dependencia. Además, dado que lo más probable es que aproveche las sugerencias de tipo de Python (para que pueda aprovechar la validación de datos), podrá avanzar más rápido en el desarrollo gracias al autocompletado del editor y las comprobaciones automáticas de errores.

Puede encontrar el código final en el repositorio de transferencia de estilo en GitHub.

Fuente:  https://testdriven.io

#machine-learning #fastapi #streamlit #python 

Modelo De Aprendizaje Automático Con FastAPI Y Streamlit
Callum  Allen

Callum Allen

1660239900

Modelo De Aprendizado De Máquina Com FastAPI E Streamlit

O aprendizado de máquina é um tema quente no momento. Com as empresas de tecnologia se movendo na direção da inteligência artificial e do aprendizado de máquina para lucrar mais cedo, o campo cresceu tremendamente. Muitas dessas empresas criam suas próprias soluções de aprendizado de máquina e as vendem para outras pessoas usando um modelo baseado em assinatura.

Como a maioria dos modelos de aprendizado de máquina é desenvolvida em Python, as estruturas da Web que os atendem geralmente também são baseadas em Python. Por muito tempo, Flask, um micro-framework, foi o goto framework. Mas isso está mudando. Uma nova estrutura, projetada para compensar quase tudo o que falta ao Flask, está se tornando cada vez mais popular. Chama-se FastAPI.

FastAPI é mais rápido que Flask porque traz manipuladores de funções assíncronas para a tabela:

Referências de estrutura da Web

Fonte: TechEmpower Web Framework Benchmarks

Como você pode ver na figura acima, o FastAPI é quase 3x mais rápido que o Flask.

FastAPI suporta validação de dados por meio de documentação de API automática e pydantic também.

Enquanto isso, o Streamlit é uma estrutura de aplicativo que facilita para cientistas de dados e engenheiros de aprendizado de máquina criar interfaces de usuário poderosas que interagem com modelos de aprendizado de máquina.

Embora o Streamlit possa ser usado em produção, é melhor para prototipagem rápida. Ao servir o modelo com FastAPI, você pode mover rapidamente para uma interface de usuário pronta para produção com Dash ou React, depois que o protótipo for aprovado.

Com isso, vamos construir um aplicativo de transferência de estilo baseado no papel Perceptual Losses for Real-Time Style Transfer e Super-Resolution e nos modelos pré-treinados de Justin Johnson. Usaremos FastAPI como back-end para servir nossas previsões, Streamlit para a interface do usuário e OpenCV para fazer a previsão real. O Docker também será usado.

Um recurso poderoso do módulo Deep Neural Networks (DNN) do OpenCV é que ele pode carregar modelos treinados do Torch, TensorFlow e Caffe, efetivamente nos poupando do trabalho de instalar essas dependências.

Objetivos

Ao final deste tutorial, você será capaz de:

  1. Desenvolva uma API assíncrona com Python e FastAPI
  2. Servir um modelo de aprendizado de máquina com FastAPI
  3. Desenvolva uma interface do usuário com o Streamlit
  4. Conteinerize FastAPI e Streamlit com Docker
  5. Aproveite o assíncrono para executar código em segundo plano fora do fluxo de solicitação/resposta

Configuração do projeto

Crie uma pasta de projeto chamada "style-transfer":

$ mkdir style-transfer
$ cd style-transfer

Em seguida, crie duas novas pastas com "style-transfer":

$ mkdir frontend
$ mkdir backend

Adicione arquivos __init__.py a cada pasta.

Back-end FastAPI

Adicione um novo arquivo ao "backend" chamado main.py :

# backend/main.py

import uuid

import cv2
import uvicorn
from fastapi import File
from fastapi import FastAPI
from fastapi import UploadFile
import numpy as np
from PIL import Image

import config
import inference


app = FastAPI()


@app.get("/")
def read_root():
    return {"message": "Welcome from the API"}


@app.post("/{style}")
def get_image(style: str, file: UploadFile = File(...)):
    image = np.array(Image.open(file.file))
    model = config.STYLES[style]
    output, resized = inference.inference(model, image)
    name = f"/storage/{str(uuid.uuid4())}.jpg"
    cv2.imwrite(name, output)
    return {"name": name}


if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=8080)

Este é o nosso servidor. O FastAPI cria dois endpoints, um fictício ( "/") e outro para servir nossa previsão ( "/{style}"). O endpoint de serviço recebe um nome como um parâmetro de URL. Estamos usando nove modelos treinados diferentes para realizar a transferência de estilo, então o parâmetro path nos dirá qual modelo escolher. A imagem é aceita como um arquivo em uma solicitação POST e enviada para a inferencefunção. Quando a inferência estiver concluída, o arquivo será armazenado no sistema de arquivos local e o caminho será enviado como resposta.

Em seguida, adicione a seguinte configuração a um novo arquivo chamado backend/config.py :

# backend/config.py

MODEL_PATH = "./models/"

STYLES = {
    "candy": "candy",
    "composition 6": "composition_vii",
    "feathers": "feathers",
    "la_muse": "la_muse",
    "mosaic": "mosaic",
    "starry night": "starry_night",
    "the scream": "the_scream",
    "the wave": "the_wave",
    "udnie": "udnie",
}

Quando introduzida, a transferência de estilo foi um divisor de águas. A única desvantagem era que a imagem tinha que ser treinada para obter um estilo para ela. Isso significa que, para obter uma imagem estilizada, você precisa percorrer as imagens originais várias vezes antes de obter um resultado melhor. Em 2016, as Perdas Perceptivas para Transferência de Estilo em Tempo Real e Papel Super-Resolução introduziram a transferência de estilo rápido, o que significa que você pode estilizar qualquer imagem em uma única passagem. Vamos usar a mesma técnica com os modelos treinados fornecidos pelo autor.

Agora, precisamos baixar os modelos. Adicione um script à raiz do projeto chamado download_models.sh :

BASE_URL="https://cs.stanford.edu/people/jcjohns/fast-neural-style/models/"

mkdir -p backend/models/
cd backend/models/
curl -O "$BASE_URL/instance_norm/candy.t7"
curl -O "$BASE_URL/instance_norm/la_muse.t7"
curl -O "$BASE_URL/instance_norm/mosaic.t7"
curl -O "$BASE_URL/instance_norm/feathers.t7"
curl -O "$BASE_URL/instance_norm/the_scream.t7"
curl -O "$BASE_URL/instance_norm/udnie.t7"
curl -O "$BASE_URL/eccv16/the_wave.t7"
curl -O "$BASE_URL/eccv16/starry_night.t7"
curl -O "$BASE_URL/eccv16/la_muse.t7"
curl -O "$BASE_URL/eccv16/composition_vii.t7"

Download:

$ sh download_models.sh

Adicione a inferencefunção a backend/inference.py :

# backend/inference.py

import config
import cv2


def inference(model, image):
    model_name = f"{config.MODEL_PATH}{model}.t7"
    model = cv2.dnn.readNetFromTorch(model_name)

    height, width = int(image.shape[0]), int(image.shape[1])
    new_width = int((640 / height) * width)
    resized_image = cv2.resize(image, (new_width, 640), interpolation=cv2.INTER_AREA)

    # Create our blob from the image
    # Then perform a forward pass run of the network
    # The Mean values for the ImageNet training set are R=103.93, G=116.77, B=123.68

    inp_blob = cv2.dnn.blobFromImage(
        resized_image,
        1.0,
        (new_width, 640),
        (103.93, 116.77, 123.68),
        swapRB=False,
        crop=False,
    )

    model.setInput(inp_blob)
    output = model.forward()

    # Reshape the output Tensor,
    # add back the mean substruction,
    # re-order the channels
    output = output.reshape(3, output.shape[2], output.shape[3])
    output[0] += 103.93
    output[1] += 116.77
    output[2] += 123.68

    output = output.transpose(1, 2, 0)
    return output, resized_image

Aqui, carregamos o modelo Torch, realizamos o redimensionamento e o convertemos no formato blob necessário. Em seguida, passamos a imagem pré-processada para a rede/modelo e obtivemos a saída. A imagem pós-processada e a imagem redimensionada são retornadas como saída.

Por fim, adicione as dependências a um arquivo de requisitos:

# backend/requirements.txt

fastapi
numpy
opencv-python
pillow
python-multipart
uvicorn

Isso é tudo para o back-end. Vamos configurar o Docker e testá-lo.

Configuração do Docker

Primeiro, adicione um Dockerfile à pasta "backend":

# backend/Dockerfile

FROM python:3.10.1-slim

WORKDIR /app

RUN apt-get update
RUN apt-get install \
    'ffmpeg'\
    'libsm6'\
    'libxext6'  -y

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8080

CMD ["python", "main.py"]

'ffmpeg', 'libsm6' e 'libxext6' são necessários para o OpenCV.

Na pasta "backend" do seu terminal, crie a imagem:

$ docker build -t backend .

Execute o contêiner:

$ docker run -p 8080:8080 backend

INFO:     Started server process [1]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)

No seu navegador, navegue até http://localhost:8080/ . Você deveria ver:

{
  "message": "Welcome from the API"
}

Mate o recipiente uma vez feito.

Front-end simplificado

Para a interface do usuário, adicione um arquivo main.py à pasta "frontend":

# frontend/main.py

import requests
import streamlit as st
from PIL import Image

STYLES = {
    "candy": "candy",
    "composition 6": "composition_vii",
    "feathers": "feathers",
    "la_muse": "la_muse",
    "mosaic": "mosaic",
    "starry night": "starry_night",
    "the scream": "the_scream",
    "the wave": "the_wave",
    "udnie": "udnie",
}

# https://discuss.streamlit.io/t/version-0-64-0-deprecation-warning-for-st-file-uploader-decoding/4465
st.set_option("deprecation.showfileUploaderEncoding", False)

# defines an h1 header
st.title("Style transfer web app")

# displays a file uploader widget
image = st.file_uploader("Choose an image")

# displays the select widget for the styles
style = st.selectbox("Choose the style", [i for i in STYLES.keys()])

# displays a button
if st.button("Style Transfer"):
    if image is not None and style is not None:
        files = {"file": image.getvalue()}
        res = requests.post(f"http://backend:8080/{style}", files=files)
        img_path = res.json()
        image = Image.open(img_path.get("name"))
        st.image(image, width=500)

Observe os comentários do código acima. Simplificando, criamos um widget de upload de imagem junto com um menu suspenso de seleção exibindo cada um dos estilos do STYLESdict. Também adicionamos um botão que, quando pressionado, envia a imagem para o back-end como uma carga útil de solicitação POST para http://backend:8080/{style}. Ao receber o caminho da imagem na resposta do backend, a imagem é aberta e exibida.

Consulte o guia de introdução do Streamlit e a referência da API para obter ajuda com a exibição de texto e dados, bem como a adição de interatividade básica com widgets.

Adicione a dependência do Streamlit a um arquivo requirements.txt :

# frontend/requirements.txt

streamlit==1.2.0

Composição do Docker

Em seguida, vamos Dockerizar o frontend e conectar os dois contêineres com o Docker Compose.

frontend/Dockerfile :

# frontend/Dockerfile

FROM python:3.10.1-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8501

CMD ["streamlit", "run", "main.py"]

docker-compose.yml :

version: '3'

services:
  frontend:
    build: frontend
    ports:
      - 8501:8501
    depends_on:
      - backend
    volumes:
        - ./storage:/storage
  backend:
    build: backend
    ports:
      - 8080:8080
    volumes:
      - ./storage:/storage

O mais importante aqui é que mapeamos o armazenamento da máquina host para o armazenamento de cada contêiner. Isso é importante para compartilhar o caminho e também para persistir os dados ao girar os contêineres.

Assim, tanto o backend quanto o frontend podem acessar imagens do mesmo volume compartilhado:

# backend
name = f"/storage/{str(uuid.uuid4())}.jpg"
cv2.imwrite(name, output)
return {"name": name}

# frontend
img_path = res.json()
image = Image.open(img_path.get("name"))

Para testar, a partir da raiz do projeto, construa as imagens e gire os dois contêineres:

$ docker-compose up -d --build

Navegue até http://localhost:8501 :

Aplicativo de demonstração

Exibição de modelo assíncrono

Agora que você viu como usar FastAPI, Streamlit e OpenCV para realizar uma transferência de estilo, vamos fazer um pequeno experimento.

Um dos recursos mais poderosos do FastAPI é que ele suporta funções assíncronas. Então, vamos aproveitar uma função assíncrona para converter a imagem de entrada em vários estilos. Processaremos o primeiro estilo de forma síncrona e, em seguida, enviaremos a resposta à medida que os modelos restantes forem processados ​​em segundo plano.

Adicione as seguintes funções a backend/main.py :

# backend/main.py

async def generate_remaining_models(models, image, name: str):
    executor = ProcessPoolExecutor()
    event_loop = asyncio.get_event_loop()
    await event_loop.run_in_executor(
        executor, partial(process_image, models, image, name)
    )


def process_image(models, image, name: str):
    for model in models:
        output, resized = inference.inference(models[model], image)
        name = name.split(".")[0]
        name = f"{name.split('_')[0]}_{models[model]}.jpg"
        cv2.imwrite(name, output)

A generate_remaining_modelsfunção gera cada um dos estilos restantes usando asyncio .

Confira o artigo Acelerando o Python com simultaneidade, paralelismo e assíncrono para obter mais informações sobre assíncrono.

Adicione as seguintes importações:

import asyncio

from concurrent.futures import ProcessPoolExecutor

from functools import partial

Atualize a get_imagefunção para que ela crie as tarefas assíncronas antes de enviar a resposta de volta:

# backend/main.py

@app.post("/{style}")
async def get_image(style: str, file: UploadFile = File(...)):
    image = np.array(Image.open(file.file))
    model = config.STYLES[style]
    start = time.time()
    output, resized = inference.inference(model, image)
    name = f"/storage/{str(uuid.uuid4())}.jpg"
    cv2.imwrite(name, output)
    models = config.STYLES.copy()
    del models[style]
    asyncio.create_task(generate_remaining_models(models, image, name))
    return {"name": name, "time": time.time() - start}

Assim que a primeira previsão for feita, excluiremos o estilo de uma cópia dos estilos originais. Em seguida, os estilos restantes são passados ​​para generate_remaining_models.

Adicione a importação:

import time

Em seguida, atualize o bloco da seguinte ifinstrução em frontend/main.py :

# frontend/main.py

if st.button("Style Transfer"):
    if image is not None and style is not None:
        files = {"file": image.getvalue()}
        res = requests.post(f"http://backend:8080/{style}", files=files)
        img_path = res.json()
        image = Image.open(img_path.get("name"))
        st.image(image)

        displayed_styles = [style]
        displayed = 1
        total = len(STYLES)

        st.write("Generating other models...")

        while displayed < total:
            for style in STYLES:
                if style not in displayed_styles:
                    try:
                        path = f"{img_path.get('name').split('.')[0]}_{STYLES[style]}.jpg"
                        image = Image.open(path)
                        st.image(image, width=500)
                        time.sleep(1)
                        displayed += 1
                        displayed_styles.append(style)
                    except:
                        pass

Adicione a importação ao topo:

import time

Assim, após exibir o primeiro estilo, continuamos a verificar os estilos restantes, exibindo cada um até que todos os nove estejam na página.

Atualize os containers e teste:

$ docker-compose up -d --build

Agora, os estilos restantes serão exibidos de forma assíncrona sem bloquear a resposta inicial.

Conclusão

FastAPI é uma alternativa moderna e assíncrona ao Flask. Ele tem muitos recursos que o Flask não possui e é mais rápido que o Flask, pois aproveita o Starlette e suporta manipuladores de funções assíncronas. O FastAPI possui muitos recursos adicionais, como validação de dados, documentação automática da API, tarefas em segundo plano, bem como um poderoso sistema de injeção de dependência. Além disso, como você provavelmente aproveitará as dicas do tipo Python (para poder aproveitar a validação de dados), poderá avançar mais rapidamente no desenvolvimento devido ao preenchimento automático do editor e às verificações automáticas de erros.

Você pode encontrar o código final no repositório de transferência de estilo no GitHub.

Fonte:  https://testdrive.io

#machine-learning #fastapi #streamlit #python 

Modelo De Aprendizado De Máquina Com FastAPI E Streamlit
César  Cordero

César Cordero

1658731924

How to Build a Python Web Application From Scratch using Streamlit

In this video, We will be showing you how to build a Python web application from scratch using Streamlit. I will also show you how to connect your streamlit app to a database. In particular, we will be using a free NoSQL database from deta. I will cover the entire process from start to finish and show you how easy it is to build your own web application. We will also learn how to create an interactive Sankey chart using Plotly. Additionally, I will show some very nifty streamlit tricks. After this video, you will be confident to develop your own web apps.

 𝗧𝗜𝗠𝗘𝗦𝗧𝗔𝗠𝗣𝗦: 
00:00 – Intro 
01:23 – Dependency installation and basic settings
03:58 – Coding out the entry area 
09:51 – Coding out the plotting area
16:24 – Add a navigation menu
18:32 – Styling of the app
20:12 – Set up a NoSQL database
26:35 – Connect streamlit to a NoSQL database
29:03 – How to handle environment variables on a server
29:38 – Outro

𝗥𝗘𝗦𝗢𝗨𝗥𝗖𝗘𝗦:
Demo Website: https://share.streamlit.io/sven-bo/streamlit-income-expense-tracker/app.py 
Source Code: https://github.com/Sven-Bo/streamlit-income-expense-tracker 
Streamlit cloud – secrets management: https://docs.streamlit.io/streamlit-cloud/get-started/deploy-an-app/connect-to-data-sources/secrets-management 

Subscribe: https://www.youtube.com/c/CodingIsFun/featured 

#python #streamlit 

How to Build a Python Web Application From Scratch using Streamlit
César  Cordero

César Cordero

1658723605

Add a User Authentication Service (Login Form) in Streamlit

In this video, We will show you how to add a user authentication service (login form) in Streamlit so that your users can log in and see the content of your streamlit app. To implement the user authentication, we will use the ‘streamlit-authenticator’ library, a secure authentication module to validate user credentials in a Streamlit application.

Streamlit is an open-source python framework for building web apps for Machine Learning and Data Science. We can instantly develop web apps and deploy them easily using Streamlit. Streamlit allows you to write an app the same way you write a python code. Streamlit makes it seamless to work on the interactive loop of coding and viewing results in the web app.

𝗧𝗜𝗠𝗘𝗦𝗧𝗔𝗠𝗣𝗦: 
00:00 – Introduction 
00:33 – Create the helper file 
02:43 – Implementation in the streamlit app
06:36 – Closing words

𝗥𝗘𝗦𝗢𝗨𝗥𝗖𝗘𝗦:
Demo Website: https://userauth-dashboard.herokuapp.com/ 
Streamlit-Authenticator library: https://github.com/mkhorasani/Streamlit-Authenticator 
Example files from this video: https://github.com/Sven-Bo/streamlit-sales-dashboard-with-userauthentication 

Subscribe: https://www.youtube.com/c/CodingIsFun/featured 

#python #streamlit  

Add a User Authentication Service (Login Form) in Streamlit
Michio JP

Michio JP

1657255203

How to Create Streamlit Data Science and ML Apps in Python

Streamlit_DataScience_Apps

Streamlit Data Science and ML Apps in Python

How to Deploy Streamlit Apps to Heroku

1. Create An Account Heroku by signing up.

2. Install Heroku CLI

3. Create Your Github Repository for your app

4. Build your app

5. Login to Heroku From the CLI

heroku Login

6. Create Your 3 Required Files(setup.sh,Procfile,requirements.txt)

  • Place the code below in their respective files

Code for setup.sh

mkdir -p ~/.streamlit/

echo "\
[general]\n\
email = \"your-email@domain.com\"\n\
" > ~/.streamlit/credentials.toml

echo "\
[server]\n\
headless = true\n\
enableCORS=false\n\
port = $PORT\n\
" > ~/.streamlit/config.toml

Code for setup.sh (Alternate with no credentials.toml)

mkdir -p ~/.streamlit/

echo "\
[server]\n\
headless = true\n\
port = $PORT\n\
enableCORS = false\n\
\n\
" > ~/.streamlit/config.toml

Code For Procfile

web: sh setup.sh && streamlit run your_app.py

7. Create App with CLI

heroku create name-of-your-app

8. Commit and Push Your Code to Github

git add your app 
git commit -m "your comment description"
git push

9. Push To Heroku to Deploy

git push heroku master

Credits:

gabe_maldonado

Streamlit team

Thanks For Your Time

 

Download Details:
 

Author: Jcharis
Download Link: Download The Source Code
Official Website: https://github.com/Jcharis/Streamlit_DataScience_Apps 

#python #streamlit 

How to Create Streamlit Data Science and ML Apps in Python
Helene  Ward

Helene Ward

1655547022

How to Build MultiPage Python App with Streamlit

If you want to make your ML Product prototype or ML Product MVP look no further than Streamlit because now you've got Multi-page support with streamlit. streamlit is a popular python-framework to build web apps especially data apps focused on data science and machine learning.

Today they announced native support Multi-Page Python Web Apps and this tutorial covers it.

⏰ Timestamps:
00:00 - Introduction to Streamlit MultiPage Apps
00:46 - Streamlit MultiPage App Demo
02:28 - Streamlit 1.10 upgrade
03:33 - Streamlit Multipage App Project Folder and Code Walkthrough
06:15 - Pages Reordering 
08:03 - New Multi-page App Live Creation

#streamlit #pythonapp #pythonweb #python

How to Build MultiPage Python App with Streamlit
Helene  Ward

Helene Ward

1653626340

Stlite Project : Streamlit without Server

This is a quick tutorial and walkthrough of an interesting project Stlite by Yuichiro Tachibana (Tsuchiya) whitphx.

stlite project - https://github.com/whitphx/stlite
stlite demo code - https://github.com/amrrs/stlite-demo

#streamlit #python #webassembly #Stlite

Stlite Project : Streamlit without Server