AIアプリをさくっと作るなら、HuggingFace Spaceがおすすめ

# はじめに
この記事では、HugginFace🤗にAIモデルをデプロイして、GradioというUIライブラリで画面を作成するところまでのチュートリアルを紹介します。HuggingFaceを使えば、無料から始められるのでAIモデルは作ったけど、これをどうやって面接などで見せようか、簡単にサービスを試して業務効率化したいけどどうしようという場合などに使えると思います。

# HuggingFaceとは
HuggingFaceとはTransformersを作っている会社であり、MLエンジニアならお世話になった方もいるライブラリでしょう。
モデルだけをアップロードして他の人に使えるようにもできますし、データセットのアップロードもできます。
そして、今回おすすめするSpaceという機能も提供しています。

# Gradioとは
今回使うUIライブラリは[Gradio](https://gradio.app/)です。最初は[Streamlit](https://streamlit.io/)を使っていたのですが、重くてHuggingFaceでの読み込みに時間がかかったので変更しました。

以下のようなセグメンテーションタスクもGradioを使えばすぐUIを作れます。

![スクリーンショット 2023-02-25 12.46.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33329/b4e48f37-a2ee-5aa5-add0-ba496d581a10.png)

# HuggingFace Space

[HuggingFace Space](https://huggingface.co/spaces)とはStreamlit、Gradio、Dockerなどに対応しているアプリを動かすような環境です。
Gradioで書いたコードをそのままpushすれば、UIがHuggingFace Space上に展開されて誰でも触れるようになります。
Private機能ももちろんあり、特定のユーザーだけに触ってもらうこともできます。

# Code

### 環境設定用コード

README.mdに環境設定を書きます。
python_versionはpythonのバージョンです。
sdkはgradioとかstreamlitなどを書きます。
sdk_versionはgradioのバージョンです。
app_fileはメインの起動するためのファイルです。
licenceはライセンスです。今回はmitライセンスにしています。
通常app.pyになります。

```
---
title: abnormal detection
emoji: 🐷
python_version: 3.8
colorFrom: purple
colorTo: pink
sdk: gradio
sdk_version: 3.18.0
app_file: app.py
pinned: true
license: mit
---
```


### app.py

モデルや今回のサンプルアプリである、異常検知のコードが書いています。
gradioについては以下を見てほしいのですが、簡単に説明するとgr.Interfaceで画面の設定を行なっています。
https://gradio.app/

```python
import cv2
import numpy as np
import gradio as gr
from PIL import Image
from io import BytesIO
import base64
# ヘルパーライブラリのインポート.
import numpy as np
import matplotlib.pyplot as plt

import os
import pathlib
import time
import datetime
import glob
import random

from PIL import Image

# TensorFlow と tf.keras のインポート.
import tensorflow as tf
from tensorflow import keras

def fix_image(upload_img):
    image = Image.fromarray(upload_img)
    image = image.convert('L')
    image = image.resize((256, 256))

    generator = Generator(G_input_dim)
    generator.load_weights('checkpoints/cp-10.h5')
    fixed = saved_images(generator, upload_img)
    fixed = np.reshape(fixed, [256, 256, 1])

    image = np.asarray(image)
    image = np.reshape(image, [256, 256, 1])

    diff = np.uint8(np.abs(fixed - image))
    heatmap = cv2.applyColorMap(diff , cv2.COLORMAP_JET)
    heatmap = cv2.cvtColor(heatmap, cv2.COLOR_BGR2RGB)
    heatmap = cv2.resize(heatmap, (256, 256))
    heatmap = Image.fromarray(heatmap)

    heatmap = heatmap.convert('RGB')
    heatmap = np.asarray(heatmap)
    return heatmap

def saved_images(model, input):
    input = Image.fromarray(input)
    input = input.convert('L')
    input = input.resize((256, 256))
    input = np.asarray(input)
    # 入力画像の形式（シェイプ）をtensorflow、kerasで学習,推論するために変換
    input = np.reshape(input, [1, 256, 256, 1])
    # 入力画像をテンソルに変換
    input = tf.cast(tf.convert_to_tensor(np.asarray(input)), dtype=tf.float32) / 255.

    # 入力画像のバッチ.
    batch_input = []
    batch_input += [input]
    batch_input = tf.concat(batch_input, axis=0)

    prediction = model(batch_input, training=False)
    if CHANNEL == 1:
        predict_image = prediction[0].numpy().flatten().reshape(256, 256) # モノクロ画像の場合
        # plt.imsave('predict_images/predict_{}.jpg'.format(number), predict_image, cmap="gray")
    else:
        predict_image = prediction[0].numpy().flatten().reshape(256, 256, 3)
        # plt.imsave('predict_images/predict_{}.jpg'.format(number), predict_image)
    return predict_image


# デコーダーの定義に使用
def upsample(filters, size,  dropout=0.5, max_pool=True, batch_norm=True):
    initializer = tf.random_normal_initializer(0., 0.02)

    result = tf.keras.Sequential()
    result.add(
        # 畳み込み層の追加（アップサンプルに使用）
        tf.keras.layers.Conv2DTranspose(filters, size, strides=2,
                                        padding='same',
                                        kernel_initializer=initializer,
                                        use_bias=False)
    )
    
    # max poolingを行う場合
    if max_pool:
        result.add(tf.keras.layers.MaxPool2D(pool_size=(1, 1), strides=None, padding='same'))

    # バッチノルムを行う場合
    if batch_norm:
        result.add(tf.keras.layers.BatchNormalization())

    # ドロップアウトを行う場合
    if dropout != None:
        result.add(tf.keras.layers.Dropout(dropout))
    result.add(tf.keras.layers.ReLU())

    return result

# エンコーダーの定義に使用
def downsample(filters, kernel_size, strides=2, dropout=0.5, max_pool=True, batch_norm=True):
    initializer = tf.random_normal_initializer(0., 0.02)

    result = tf.keras.Sequential()
    result.add(
        # 畳み込み層の追加
        tf.keras.layers.Conv2D(filters, kernel_size, strides=strides, padding='same',
                                kernel_initializer=initializer, use_bias=False))
    # max poolingを行う場合
    if max_pool:
        result.add(tf.keras.layers.MaxPool2D(pool_size=(1, 1), strides=None, padding='same'))

    # バッチノルムを行う場合
    if batch_norm:
        result.add(tf.keras.layers.BatchNormalization())
    
    # ドロップアウトを行う場合
    if dropout != None:
        result.add(tf.keras.layers.Dropout(dropout))

    result.add(tf.keras.layers.LeakyReLU())
    return result

# ジェネレーターのネットワークの定義
def Generator(image_shape):
    initializer = tf.random_normal_initializer(0., 0.02)
    # 入力画像
    input_image = keras.layers.Input(shape=image_shape, name='input_image')
    x = input_image

    # エンコーダーの定義
    enc1 = downsample(n_E1, kernel_size_E1, stride_E1, DropOut_E1, MaxPooling_E1, BatchNorm_E1)(x) # 正体は単純な畳み込み層
    enc2 = downsample(n_E2, kernel_size_E2 ,stride_E2, DropOut_E2, MaxPooling_E2, BatchNorm_E2)(enc1)
    enc3 = downsample(n_E3, kernel_size_E3, stride_E3, DropOut_E3, MaxPooling_E3, BatchNorm_E3)(enc2)
    enc4 = downsample(n_E4, kernel_size_E4, stride_E4, DropOut_E4, MaxPooling_E4, BatchNorm_E4)(enc3)
    enc5 = downsample(n_E5, kernel_size_E5 ,stride_E5, DropOut_E5, MaxPooling_E5, BatchNorm_E5)(enc4)
    enc6 = downsample(n_E6, kernel_size_E6 ,stride_E6, DropOut_E6, MaxPooling_E6, BatchNorm_E6)(enc5)
    enc7 = downsample(n_E7, kernel_size_E7 ,stride_E7, DropOut_E7, MaxPooling_E7, BatchNorm_E7)(enc6)
    enc8 = downsample(n_E8, kernel_size_E8, stride_E8, DropOut_E8, MaxPooling_E8, BatchNorm_E8)(enc7)

    # デコーダーの定義
    dec1 = upsample(n_E7, kernel_size_E7, DropOut_E7, MaxPooling_E7, BatchNorm_E7) # 正体は単純な畳み込み層
    dec2 = upsample(n_E6, kernel_size_E6, DropOut_E6, MaxPooling_E6, BatchNorm_E6)
    dec3 = upsample(n_E5, kernel_size_E5, DropOut_E5, MaxPooling_E5, BatchNorm_E5)
    dec4 = upsample(n_E4, kernel_size_E4, DropOut_E4, MaxPooling_E4, BatchNorm_E4)
    dec5 = upsample(n_E3, kernel_size_E3, DropOut_E3, MaxPooling_E3, BatchNorm_E3)
    dec6 = upsample(n_E2, kernel_size_E2, DropOut_E2, MaxPooling_E2, BatchNorm_E2)
    dec7 = upsample(n_E1, kernel_size_E1, DropOut_E1, MaxPooling_E1, BatchNorm_E1)
    

    # 画像を拡大する場合コメントアウト
    # zoom = upsample(CHANNEL, 4)

    # バイパスするエンコーダーの特徴量
    enc_value_list = [enc7, enc6, enc5, enc4, enc3, enc2, enc1]

    # バイパスするデコーダーの特徴量
    dec_value_list = [dec1, dec2, dec3, dec4, dec5, dec6, dec7]

    # バイパスを行うか判定するフラグ
    bipass_list = [Bipass_7, Bipass_6, Bipass_5, Bipass_4, Bipass_3, Bipass_2, Bipass_1]

    # バイパス処理のためにエンコーダーの最終出力をxとする
    x = enc8    

    # バイパス処理を行っている部分
    for dec, enc, bipass in zip(dec_value_list, enc_value_list, bipass_list):
        x = dec(x)
        # バイパスを行うかを判定する
        if bipass:
            x = tf.keras.layers.Concatenate()([x, enc]) # バイパスしている行

    # 画像を拡大する場合コメントを外す
    # if expantion:
    #     x = zoom(x)

    # 出力のチャネル数、カラーだと3、モノクロだと1
    OUTPUT_CHANNELS = CHANNEL

    # 層の乱数を初期化するもの
    initializer = tf.random_normal_initializer(0., 0.02)

    # 最終出力層
    last = tf.keras.layers.Conv2DTranspose(OUTPUT_CHANNELS, 4,
                                            strides=2,
                                            padding='same',
                                            kernel_initializer=initializer,
                                            activation='tanh')
    x = last(x)

    return tf.keras.Model(inputs=input_image, outputs=x)

if __name__ == "__main__":
    CHANNEL = 1
    # 入力画像サイズ
    G_input_dim = (256, 256, CHANNEL) 
    EPOCH = 20

    # 第１層目のパラメータ
    n_E1 = 32 # チャネル数
    m_E1 = 128 # 画素数
    stride_E1 = 2 # ストライドのサイズ
    kernel_size_E1 = 4 # カーネルサイズ
    MaxPooling_E1 = True # MaxPoolingの設定（MaxPoolingを行わない場合はNoneにする）🇲
    ActivationFunc_E1 = "Leaky_ReLu" # 活性化関数
    BatchNorm_E1 = True # 　バッチ正規化の設定（正規化を行わない場合はNoneにする）
    DropOut_E1 = 0.5 # ドロップアウトの設定（ドロップアウトを行わない場合はNoneにする）
    Bipass_1 = True # バイパスの設定（バイパスを行わない場合はNoneにする） 

    # 第２層目のパラメーター
    n_E2 = 64 # チャネル数
    m_E2 = 64 # 画素数
    stride_E2 = 2 # ストライドのサイズ
    kernel_size_E2 = 4 # カーネルサイズ
    MaxPooling_E2 = True # MaxPoolingの設定（MaxPoolingを行わない場合はNoneにする）🇲
    ActivationFunc_E2 = "Leaky_ReLu" # 活性化関数
    alfa = 0.2
    BatchNorm_E2 = True # 　バッチ正規化の設定（正規化を行わない場合はNoneにする）
    DropOut_E2 = 0.5 # ドロップアウトの設定（ドロップアウトを行わない場合はNoneにする）
    Bipass_2 = True # バイパスの設定（バイパスを行わない場合はNoneにする） 

    # 第3層目のパラメーター
    n_E3 = 128 # チャネル数
    m_E3 = 128 # 画素数
    stride_E3 = 2 # ストライドのサイズ
    kernel_size_E3 = 4 # カーネルサイズ
    MaxPooling_E3 = True # MaxPoolingの設定（MaxPoolingを行わない場合はNoneにする）🇲
    ActivationFunc_E3 = "Leaky_ReLu" # 活性化関数
    alfa = 0.2
    BatchNorm_E3 = True # 　バッチ正規化の設定（正規化を行わない場合はNoneにする）
    DropOut_E3 = 0.5 # ドロップアウトの設定（ドロップアウトを行わない場合はNoneにする）
    Bipass_3 = True # バイパスの設定（バイパスを行わない場合はNoneにする） 

    # 第4層目のパラメーター
    n_E4 = 256 # チャネル数
    m_E4 = 256 # 画素数
    stride_E4 = 2 # ストライドのサイズ
    kernel_size_E4 = 4 # カーネルサイズ
    MaxPooling_E4 = True # MaxPoolingの設定（MaxPoolingを行わない場合はNoneにする）🇲
    ActivationFunc_E4 = "Leaky_ReLu" # 活性化関数
    alfa = 0.2
    BatchNorm_E4 = True # 　バッチ正規化の設定（正規化を行わない場合はNoneにする）
    DropOut_E4 = 0.5 # ドロップアウトの設定（ドロップアウトを行わない場合はNoneにする）
    Bipass_4 = True # バイパスの設定（バイパスを行わない場合はNoneにする） 

    # 第5層目のパラメーター
    n_E5 = 512 # チャネル数
    m_E5 = 512 # 画素数
    stride_E5 = 2 # ストライドのサイズ
    kernel_size_E5 = 4 # カーネルサイズ
    MaxPooling_E5 = True # MaxPoolingの設定（MaxPoolingを行わない場合はNoneにする）🇲
    ActivationFunc_E5 = "Leaky_ReLu" # 活性化関数
    alfa = 0.2
    BatchNorm_E5 = True # 　バッチ正規化の設定（正規化を行わない場合はNoneにする）
    DropOut_E5 = 0.5 # ドロップアウトの設定（ドロップアウトを行わない場合はNoneにする）
    Bipass_5 = True # バイパスの設定（バイパスを行わない場合はNoneにする）

    # 第6層目のパラメーター
    n_E6 = 512 # チャネル数
    m_E6 = 512 # 画素数
    stride_E6 = 2 # ストライドのサイズ
    kernel_size_E6 = 4 # カーネルサイズ
    MaxPooling_E6 = True # MaxPoolingの設定（MaxPoolingを行わない場合はNoneにする）🇲
    ActivationFunc_E6 = "Leaky_ReLu" # 活性化関数
    alfa = 0.2
    BatchNorm_E6 = True # 　バッチ正規化の設定（正規化を行わない場合はNoneにする）
    DropOut_E6 = 0.5 # ドロップアウトの設定（ドロップアウトを行わない場合はNoneにする）
    Bipass_6 = True # バイパスの設定（バイパスを行わない場合はNoneにする）

    # 第7層目のパラメーター
    n_E7 = 512 # チャネル数
    m_E7 = 512 # 画素数
    stride_E7 = 2 # ストライドのサイズ
    kernel_size_E7 = 4 # カーネルサイズ
    MaxPooling_E7 = True # MaxPoolingの設定（MaxPoolingを行わない場合はNoneにする）🇲
    ActivationFunc_E7 = "Leaky_ReLu" # 活性化関数
    alfa = 0.2
    BatchNorm_E7 = True # 　バッチ正規化の設定（正規化を行わない場合はNoneにする）
    DropOut_E7 = 0.5 # ドロップアウトの設定（ドロップアウトを行わない場合はNoneにする）
    Bipass_7 = True # バイパスの設定（バイパスを行わない場合はNoneにする）


    # 第8層目のパラメーター
    n_E8 = 512 # チャネル数
    m_E8 = 512 # 画素数
    stride_E8 = 2 # ストライドのサイズ
    kernel_size_E8 = 4 # カーネルサイズ
    MaxPooling_E8 = True # MaxPoolingの設定（MaxPoolingを行わない場合はNoneにする）🇲
    ActivationFunc_E8 = "Leaky_ReLu" # 活性化関数
    alfa = 0.2
    BatchNorm_E8 = True # 　バッチ正規化の設定（正規化を行わない場合はNoneにする）
    DropOut_E8 = 0.5 # ドロップアウトの設定（ドロップアウトを行わない場合はNoneにする）

    # 画像拡大パラメータ
    expantion = True # 画像をconv2dで２倍に拡大する
    
    # 入力と出力の画像のサイズを定義
    input_size = (256, 256)
    output_size = (256, 256)
    demo = gr.Interface(fix_image, inputs="image", outputs="image",input_size=input_size, output_size=output_size)   
    demo.launch()
```

checkpoints/cp-10.h5はHuggingFaceに置いています。

これを次にHuggingFaceにアップロードします。

Spacesのページに行くと次のような画面になるので右端のCreateNewSpaceでSpaceを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33329/3cc14e22-873c-afee-e808-25c28e70262b.png)

次に環境の設定です。
SpaceNameを書いてください。これがGitHubでいうリポジトリ名になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33329/1e801aa0-8188-5ac5-6940-45bb3a9537a7.png)

Select the space Hardwareはアップロードするコードを動かすCPU,GPUを定義するところです。
アップグレードは有料です。今回はT4の一倍安いモデルを使ってます。

そして、ローカルにgit cloneします。コード以外の画像やモデルなどの大きいファイルをpushするにはgit lfsが必要になる点も注意してください。


## 今回作ったアプリ

https://huggingface.co/spaces/oshita-n/abnormal_detection

Unetベースの異常検知が試せます。ちなみにbottleの画像しか対応していません。

![スクリーンショット 2023-02-25 13.15.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33329/e64a2d81-cec6-e8e7-d055-675f98a1b7e7.png)

HuggingFaceを使って、AIアプリをたくさん作りましょう！！