Gradio로 나만의 AI 모델 사용 서버 만들기

안녕하세요.

HuggingFace 10기 파트너 정정민입니다.


HuggingFace의 Space를 둘러보면 이런 느낌의 레이아웃의 화면을 많이 보게됩니다.


그리고 아래를 보면 “gradio로 제작되었습니다” 라는 문구를 볼 수 있습니다.


오늘은 Gradio를 살펴보고,

이것을 이용해 지난 시간에 만들어본 인도 음식 예측 모델을 추론하는 사이트를 만들어보려합니다.



Gradio (링크)

란, AI 머신러닝 모델을 쉽게 데모로 보여주기 위한 파이썬 라이브러리입니다.

만들어진 모델을 웹 어플리케이션 형태로 손쉽게 변환해

다른 사람들이 이 인터페이스를 통해 모델을 테스트해볼 수 있어요.


다양한 시각화 인터페이스를 제공해주고

몇 줄 안되는 코딩으로 그럴듯한 데모 페이지를 만들 수 있습니다!


HuggingFace(이하, HF)는 이 Gradio를 2022년에 인수했습니다.

많은 사람들이 머신러닝 모델을 즐기고 사용할 수 있게 하기 위한 목적을 갖고 있는 HF 입장에서 매우 탐나는 패키지겠죠.

이를 통해 Space라는 멋진 플랫폼이 소개됩니다.



Gradio! Hello world

Gradio에서 공식적으로 제공하는 Quickstart 앱(링크)을 실행시켜보겠습니다.


import gradio as gr

def greet(name, intensity):
    return "Hello, " + name + "!" * int(intensity)

demo = gr.Interface(
    fn=greet,
    inputs=["text", "slider"],
    outputs=["text"],
)

demo.launch()


터미널에서 python app.py를 실행하고, 웹에서 http://127.0.0.1:7860/ 를 입력하면 접근 가능합니다.

아래는 결과 페이지입니다.

gr.Interface를 사용해 웹 페이지의 기본 아웃라인 (왼쪽에 입력, 오른쪽에 출력)을 만들 수 있습니다.


inputs은 레이아웃 왼쪽 입력에는 뭘 넣어줄지,

  • 여기서는 name과 intensity 이고

  • 각각 text box와 slider 타입의 객체네요

  • WOW point는 greet 함수의 입력 arg의 이름이 자동으로 레이아웃에 보인다는 점입니다!


outputs은 오른쪽 출력에는 뭘 보여줄지를,


그리고, 마지막 fn 부분은 이 데모에서 돌아갈 최상위 level의 추상화 실행 함수를 넣어줍니다.

  • 여기서는 greet라는 함수를 넣어주었고

  • name과 intensity를 받아 조합된 text 를 출력하네요.


아래는 실행 결과입니다.


웹 개발을 바닥부터 배우신 분들이나, 배우고 계시는 분들 입장에서는

헛웃음이 나오는 지경일겁니다 🤣


만약 다른 사람들에게 공유할 수 있는 Public URL을 만들고 싶다면

코드를 요렇게만 수정하면 됩니다.

demo.launch()
    ↓
demo.launch(share=True)


그러면, 터미널 내에 공유 가능한 링크가 만들어집니다.

Running on local URL:  http://127.0.0.1:7860
Running on public URL: https://0118ea6d96472124ee.gradio.live


링크는 3일간 유효하고, 그 이후에는 자동 만료 된다고 합니다.


인도 음식 예측 모델 올리기


제가 설계한 시스템의 큰 틀은 이렇습니다.

음식 url을 입력하면

음식 사진이 보이고, 그게 어떤 음식인지 예측한 결과가 나온다


제일 상위의 입력과 출력이 정해졌습니다.

그리고 그것을 행해줄 함수를 infer라고 해볼까요?

그러면 코드는 대충 이런 형태일겁니다.


demo = gr.Interface(
    fn=infer,
    inputs=["이미지URL"],
    outputs=["이미지", "예측 결과"],
)


이제 저 infer만 잘 구성해주면 됩니다.

infer에는 이러한 행동이 필요합니다.

  • 먼저, url을 통해 이미지를 가져옵니다(fetch).

  • 그리고 사용할 AI 모델과 전처리기를 가져와야겠죠? (이 과정은 이전 포스팅 참조)

  • 마지막으로 실제 이미지 추론을 시작합니다.


이를 코드로 보면 이렇게 됩니다.

def infer(url):
    mage = fetch_image(url)
    model, image_processor = load_model_and_preprocessor(target_folder)
    res =  infer_image(image, model, image_processor)
    return image, res 

여기서 target_folder는 제 local에 있는 학습 결과 폴더입니다.


그리고 각각의 함수를 잘 작성해주면 됩니다.

대부분의 코드는 이전 포스팅에서 다룬 코드를 거의 그대로 사용합니다.


그리고 url을 통해 이미지를 가져오는 과정에서의 에러가 많아

이 부분을 예외적으로 처리해주는 코드를 넣어주고

이런저런 꾸밈도 같이 넣어줍니다.


공식 docs를 다소 참고했으며

아래는 전체 코드입니다.

import torch
import requests
import gradio as gr
from PIL import Image

from transformers import AutoImageProcessor, ResNetForImageClassification

target_folder = "results"

def load_model_and_preprocessor(target_folder):
    model = ResNetForImageClassification.from_pretrained(target_folder)
    image_processor = AutoImageProcessor.from_pretrained(target_folder)
    return model, image_processor

def fetch_image(url):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
    }
    image_raw = requests.get(url, headers=headers, stream=True).raw
    image = Image.open(image_raw)

    return image

def infer_image(image, model, image_processor, k):
    processed_img = image_processor(images=image.convert("RGB"), return_tensors="pt")

    with torch.no_grad():
        outputs = model(**processed_img)
        logits = outputs.logits
    
    prob = torch.nn.functional.softmax(logits, dim=-1)
    topk_prob, topk_indices = torch.topk(prob, k=k)   
    
    res = ""
    for idx, (prob, index) in enumerate(zip(topk_prob[0], topk_indices[0])):
        res += f"{idx+1}. {model.config.id2label[index.item()]:<15} ({prob.item()*100:.2f} %) \n" 
    return res

def infer(url, k, target_folder=target_folder):
    try : 
        image = fetch_image(url)
        model, image_processor = load_model_and_preprocessor(target_folder)
        res =  infer_image(image, model, image_processor, k)
    except : 
        image = Image.new('RGB', (224, 224))
        res = "이미지를 불러오는데 문제가 있나봐요. 다른 이미지 url로 다시 시도해주세요."
    return image, res 

demo = gr.Interface(
    fn=infer,
    inputs=[
        gr.Textbox(value="https://i.namu.wiki/i/XQznKj51oCpN5HKkUBe6o2R_fRb4TSbU6JTZk52zYJbbjH_1B0BFHM5uYQMfsFzQOLRHG3mhR8xhqPG_UbeA0w.webp", 
                   label="Image URL"),
        gr.Slider(minimum=0, maximum=20, step=1, value=3, label="상위 몇개까지 보여줄까요?")
    ],
    outputs=[
        gr.Image(type="pil", label="입력 이미지"),
        gr.Textbox(label="종류 (확률)")
    ],
)

demo.launch()


아래는 실행 & 결과 화면이에요


훌륭하네요 ㅎㅎ.

캠프에서는 Public URL로 라이브 데모를 해봐도 좋겠습니다 ^^



마무리

상당히 빠른 시간에 그럴듯한 데모를 만들었습니다.

적은 코드로도 많은 기능이 가능했고,

코드 자체도 매우 추상적으로 쉽게 작성이 가능했어요.


보기 좋은 음식이 맛도 좋다는 말이 있잖아요?

간단한 AI 모델을 더욱 먹음직스럽게 만들어보면 어떨까요!!??


아! 추가로 chatGPT에게 gradio 관련 코드를 물어보니

생각보다 답을 잘 해줬습니다.

아무래도 극최신 패키지가 아니라 어느정도 학습 데이터에 포함된 모습이네요.

다만 아쉬운점으로, argument 이름 등에서의 실수가 있었습니다.

큰 코드의 틀을 잡고 에러가 나오면 docs에서 찾아보면서 디버깅 하면 좋을 것 같아요!


감사합니다.

#10기HuggingFace #10기 #HuggingFace



작성자 : 정정민

블로그 : 링크

4

👉 이 게시글도 읽어보세요

모집 중인 AI 스터디