僕がAI Text-to-Artジェネレータを1週間で作った方法

本記事は、20歳のAIエンジニア／フルスタックデベロッパ[Fathy Rashad](https://medium.com/@mfrashad)氏による「[How I built an AI Text-to-Art Generator](https://towardsdatascience.com/how-i-built-an-ai-text-to-art-generator-a0c0f6d6f59f)」（2021年10月2日公開）の和訳を、著者の許可を得て掲載しているものです。

#僕がAI Text-to-Artジェネレータを1週間で作った方法
>Text2Art.comの作成方法について、順を追って詳しく説明します。

![](https://miro.medium.com/max/1010/1*ukOiybiWShMNiKRJhkJkFA.gif)
>Text2Art 生成アートのギャラリー（著者による画像）

##概要

これは、僕がどうやって1週間で[Text2Art.com](https://text2art.com/)を作成したかを説明する記事です。Text2Artは、VQGAN+CLIPベースのAIアートジェネレータです。テキストを入力するだけで、ピクセルアート、線画、水彩画・油彩画など、あらゆる種類のアートを生成できます。この記事では、VQGAN+CLIPの実験から、Gradioを使用したシンプルなUIの構築、モデル提供のためのFastAPIへの移行、そして最後に、Firebaseを使用したキューシステムまで、僕の思考プロセスを追います。興味のある部分までスキップしてください。

>[text2art.com](http://text2art.com/)でお試しいただけます。ここに[ソースコード](https://github.com/mfrashad/text2art)があります。
>（リポジトリにぜひ星を付けてください）

[![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/6V433B53hog/0.jpg)](http://www.youtube.com/watch?v=6V433B53hog)
>Text2Artデモ（更新情報：現ユーザー数1.5K超）

##目次

-   はじめに
-   仕組み
-   CodeVQGAN+CLIPを使用したコードによるアート生成
-   Gradioを使用したUI構築
-   FastAPIを使用したML提供
-   Firebaseを使用したキューシステム

##はじめに

少し前に、ジェネレーティブアートとNFTが世界を席巻しました。これを可能にしたのは、OpenAIがtext-to-image生成を大きく進歩させたことです。今年の初めにOpenAIは、優れた機能を有する、強力なtext-to-imageジェネレータ[DALL-E](https://openai.com/blog/dall-e/)を発表しました。以下は「キリンとドラゴンのキメラ／ドラゴンを模したキリン／ドラゴンでできたキリンのプロの高品質イラスト」というテキストプロンプトでDALL-Eが生成した画像です。DALL-Eがいかに優れているか分かります。

![](https://miro.medium.com/max/700/0*DafIRlTrsgGkC0y7.png)
>「キリンとドラゴンのキメラ／ドラゴンを模したキリン／ドラゴンでできたキリンのプロの高品質イラスト」テキストプロンプトによるDALL-E生成画像（[OpenAI](https://en.wikipedia.org/wiki/DALL-E#/media/File:DALL-E_sample.png) MITライセンス画像）

残念ながらDALL-Eは一般公開されていません。しかし幸いなことに、DALL-Eの魔法の背後にあるモデルである[CLIP](https://openai.com/blog/clip/)が代わりに公開されました。CLIP（Contrastive Image-Language Pretraining：対照言語画像事前学習）は、テキストと画像を組み合わせたマルチモーダルネットワークです。つまり、画像とキャプションの一致度をスコア化できます。これは、入力テキストと完全一致する画像を生成するようにジェネレータを誘導するのにとても役立ちます。DALL-Eでは、CLIPを使用して、生成された画像をランク付けし、スコアが最も高い画像（テキストプロンプトに最も近い画像）を出力します。

![](https://miro.medium.com/max/590/1*YUfLMCLNsRfGKW_O7EHCbQ.png)
>画像とキャプションをスコア化するCLIPの例（著者による画像）

DALL-Eの発表から数ヶ月後、[VQGAN](https://compvis.github.io/taming-transformers/)（Vector Quantized GAN：ベクトル量子化GAN）という新しい変形画像ジェネレータが公開されました。VQGANとCLIPを組み合わせると、DALL-Eに近い品質が得られます。学習済みVQGANモデルが公開されてから、沢山の素晴らしいアートがコミュニティで生成されました。

![](https://miro.medium.com/max/800/0*4MXzMxBaH_s_25a0.png) ![](https://miro.medium.com/max/992/0*do9DpWUFqgjw9u-X.png)
>街の港の夜景と沢山の船の絵、戦争中の難民の絵（著者による画像）

この結果にとても驚き、友人と共有したいと思いました。しかし、アート生成のコードに没頭してくれる人はあまりいないので、コードを見ることなくプロンプトを入力するだけで、誰でもすぐに欲しい画像を生成できるウェブサイト、Text2Art.comを作成することにしました。

##仕組み

VQGAN+CLIPはどのように動作するのでしょうか。簡単に言うと、ジェネレータが画像を生成し、CLIPがその画像との一致度を測定します。次に、ジェネレータはCLIPモデルからのフィードバックを使用して、より「正確な」画像を生成します。CLIPスコアが十分に高くなり、生成された画像がテキストと一致するまで、何度もイテレートします。

![](https://miro.medium.com/max/700/0*WeVqITaPf0a-mlIY.png)
>VQGANモデルが画像を生成し、CLIPがその処理を導く。ジェネレータがより「正確な」画像を生成するまで、何度もイテレートする（出典：[LJ MirandaによるVQGAN図解](https://ljvmiranda921.github.io/notebook/2021/08/08/clip-vqgan/)）

VQGANやCLIPの内部動作については、この記事の焦点ではないため、ここでは触れません。VQGAN、CLIP、DALL-Eについてさらに説明が必要な場合は、以下の素晴らしい資料を参照してください。

-   [LJ MirandaによるVQGAN図解](https://ljvmiranda921.github.io/notebook/2021/08/08/clip-vqgan/)：素晴らしいイラスト付きVQGAN解説
-   [Charlie SnellによるDALL-E解説](https://ml.berkeley.edu/blog/posts/vq-vae/)：基礎からの素晴らしいDALL-E解説
-   [Yannic KilcherによるCLIPペーパー解説動画](https://youtu.be/T9XSU0pKX2E)：CLIPペーパー解説

###X+CLIP

VQGAN+CLIPは、画像ジェネレータとCLIPの組み合わせでできることの一例に過ぎません。VQGANはどんな種類のジェネレータとも置き換えられ、ジェネレータによってはとてもうまく機能します。X+CLIPには、[StyleCLIP](https://github.com/orpatashnik/StyleCLIP)（StyleGAN+CLIP）、[CLIPDraw](https://arxiv.org/abs/2106.14843)（ベクターアートジェネレータ使用）、BigGAN+CLIPなど、多くのバリエーションがあります。画像の代わりに音声を使用する[AudioCLIP](https://github.com/AndreyGuzhov/AudioCLIP)もあります。

![](https://miro.medium.com/max/700/0*NL6r1DKhDgZhCwdc.png)
>StyleCLIPを使用した画像編集（出典：[StyleCLIP Paper](https://github.com/orpatashnik/StyleCLIP)）

##VQGAN+CLIPを使用したコードによるアート生成

VQGAN+CLIPを使用したアート生成を数行のシンプルなコードで実現した、dribnetの[clipit](https://github.com/dribnet/clipit)リポジトリのコードを使用しています（更新情報：[clipit](https://github.com/dribnet/clipit)は[pixray](https://github.com/dribnet/pixray)に移行されました）。

VQGAN+CLIPはかなりのGPUメモリを必要とするため、Google Colabで実行することをお勧めします。ここに[Colabノートブック](https://colab.research.google.com/github/mfrashad/text2art/blob/main/text2art.ipynb)があります。これに沿って進めてください。

まず、Colabで実行する場合は、ランタイムタイプをGPUに変更してください。

![](https://miro.medium.com/max/1022/1*iAHXIsRGdeK_Tb_0OmWrBg.png) ![](https://miro.medium.com/max/796/1*v15OK9hyh_JYjnLOoj1_wQ.png)
>ColabのランタイムタイプをGPUに変更する手順（著者による画像）

次に、まずコードベースと依存関係を設定する必要があります。

```
from IPython.utils import io
with io.capture_output() as captured:
    !git clone https://github.com/openai/CLIP
    # !pip install taming-transformers
    !git clone https://github.com/CompVis/taming-transformers.git
    !rm -Rf clipit
    !git clone https://github.com/mfrashad/clipit.git
    !pip install ftfy regex tqdm omegaconf pytorch-lightning
    !pip install kornia
    !pip install imageio-ffmpeg
    !pip install einops
    !pip install torch-optimizer
    !pip install easydict
    !pip install braceexpand
    !pip install git+https://github.com/pvigier/perlin-numpy

    # ClipDraw deps
    !pip install svgwrite
    !pip install svgpathtools
    !pip install cssutils
    !pip install numba
    !pip install torch-tools
    !pip install visdom

    !pip install gradio

    !git clone https://github.com/BachiLi/diffvg
    %cd diffvg
    # !ls
    !git submodule update --init --recursive
    !python setup.py install
    %cd ..

    !mkdir -p steps
    !mkdir -p models
```

(注意：「!」はGoogle Colabの特殊コマンドで、Pythonではなくbashでコマンドを実行することを意味する)

ライブラリをインストールしたら、`clipit`をインポートし、以下の数行のコードを実行するだけで、VQGAN+CLIPを使用したアート生成ができます。テキストプロンプトを好きなものに変更するだけです。さらに、イテレーション回数、幅、高さ、ジェネレータモデル、動画生成の有無など、`clipit`にオプションも指定できます。使用可能なオプションの詳細については、[ソースコード](https://github.com/mfrashad/clipit/blob/master/clipit.py)を確認してください。

```
import sys
sys.path.append("clipit")
import clipit

# To reset settings to default
clipit.reset_settings()

# You can use "|" to separate multiple prompts
prompts = "underwater city"

# You can trade off speed for quality: draft, normal, better, best
quality = "normal"

# Aspect ratio: widescreen, square
aspect = "widescreen"

# Add settings
clipit.add_settings(prompts=prompts, quality=quality, aspect=aspect)

# Apply these settings and run
settings = clipit.apply_settings()
clipit.do_init(settings)
cliptit.do_run(settings)
```
[clipit.py](https://gist.github.com/mfrashad/ff24a042a01aab986a1b1744f3d33b11#file-clipit-py)
>VQGAN+CLIPを使用してアートを生成するコード

コードを実行すると、画像が生成されます。イテレーションする度に、画像はテキストプロンプトに近付きます。

![](https://miro.medium.com/max/700/1*af0aFZegZOJ73imceppK9A.png)
>「水中都市」画像生成における、イテレーションによる結果改善（著者による画像）

###イテレーション回数の増加

イテレーション回数を増やしたい場合は、`iterations`オプションを使用して、必要なだけ設定します。例えば、500回イテレーションしたい場合は、以下のように設定します。

```
clipit.add_settings(iterations=500)
```

###動画の生成

イテレーションする度に画像を生成する必要があるため、その画像を保存して、AIの画像生成方法のアニメーションを作成できます。設定の適用前に`make_video=True`を追加するだけです。

```
clipit.add_settings(make_video=True)
```

以下の動画が生成されます。

![](https://miro.medium.com/max/400/1*tASkZrx_poGKZL4nl6PN1Q.gif)
>生成された「水中都市」GIF（著者による画像）

###画像サイズのカスタマイズ

`size=(width, height)`オプションを追加して、画像を変更することもできます。例えば、ここでは解像度800x200のバナー画像を生成します。解像度が高いほど、多くのGPUメモリが必要になることに注意してください。

```
clipit.add_settings(size=(800, 200))
```

![](https://miro.medium.com/max/700/1*MbsxDOoF23m2yGdgNLa_WQ.png)
>「空想の王国 #artstation」プロンプトで800x200の画像を生成（著者による画像）

###ピクセルアートの生成

clipitには、ピクセルアートを生成するオプションもあります。CLIPDrawのレンダラーをシーンの裏で使用し、パレットの色の制限やピクセル化など、ピクセルアートのスタイルを強制します。ピクセルアートオプションを使用するには、`use_pixeldraw=True`オプションを有効にするだけです。

```
clipit.add_settings(use_pixeldraw=True)
```

![](https://miro.medium.com/max/1000/0*qeAm5xD0krzDzGA0.png) ![](https://miro.medium.com/max/800/0*MV61P5WAYCz2cZe5.png)
>左「鎧の騎士 #pixelart」と右「中国の空想のゲームの世界 #pixelart」プロンプトで生成された画像（著者による画像）

###VQGAN+CLIP キーワード修飾子

CLIPにはバイアスがあるため、プロンプトに特定のキーワードを追加すると、生成画像に特定の効果が生じる場合があります。例えば、テキストプロンプトに「unreal engine」を追加すると、リアルなスタイルやHDスタイルが生成される傾向があります。また、「deviantart」「artstation」「flickr」など特定のサイト名を追加すると、通常、結果がより美しくなります。最高のアートを生成する「artstation」というキーワードが気に入っています。

![](https://miro.medium.com/max/1400/1*yVHBKcdoGDs7SNZ21r94Bw.jpeg)
>キーワードの比較（[kingdomakrillicによる画像](https://imgur.com/a/SALxbQm)）

さらに、キーワードを使用してアートスタイルを調整することもできます。例えば、「鉛筆スケッチ」「ローポリゴン」などのキーワードや、「Thomas Kinkade」「James Gurney」などのアーティスト名です。

![](https://miro.medium.com/max/700/1*_vwNbasrHuw5ScVIugi4aw.jpeg)
>アートスタイルのキーワード比較（[kingdomakrillicによる画像](https://imgur.com/a/SALxbQm)）

さまざまなキーワードの効果の詳細については、[kingdomakrillicによる包括的な実験結果](https://imgur.com/a/SALxbQm)を確認してください。同じ4つのテーマで200以上のキーワードを試しています。

##Gradioを使用したUI構築

最初の予定では、MLモデルのデプロイに[Gradio](https://gradio.app/)を使用するつもりでした。Gradioは、MLデモを数行のコードだけで簡単に作成できるPythonライブラリです。Gradioなら10分未満でデモを作成できます。さらに、ColabでGradioを実行すると、Gradioドメインを使用して共有可能なリンクが生成されます。このリンクをすぐに友人や一般の人と共有し、デモを試してもらうことができます。Gradioにはまだ制約がいくつかありますが、単一機能のデモをしたい場合には、最適なライブラリだと思います。

![](https://miro.medium.com/max/700/1*L_PqJ6neCcCPmOkn-XOxVQ.gif)
>Gradio UI（著者による画像）

以下は、Text2ArtアプリのシンプルなUIを構築するコードです。コードを見れば一目瞭然だと思いますが、さらに説明が必要な場合は、[Gradioドキュメント](https://gradio.app/docs)を参照してください。

```
import gradio as gr
import torch
import clipit

# Define the main function
def generate(prompt, quality, style, aspect):
    torch.cuda.empty_cache()
    clipit.reset_settings()

    use_pixeldraw = (style == 'pixel art')
    use_clipdraw = (style == 'painting')
    clipit.add_settings(prompts=prompt,
        aspect=aspect,
        quality=quality,
        use_pixeldraw=use_pixeldraw,
        use_clipdraw=use_clipdraw,
        make_video=True)

    settings = clipit.apply_settings()
    clipit.do_init(settings)
    clipit.do_run(settings)

    return 'output.png', 'output.mp4'

# Create the UI
prompt = gr.inputs.Textbox(default="Underwater city", label="Text Prompt")
quality = gr.inputs.Radio(choices=['draft', 'normal', 'better'], label="Quality")
style = gr.inputs.Radio(choices=['image', 'painting','pixel art'], label="Type")
aspect = gr.inputs.Radio(choices=['square', 'widescreen','portrait'], label="Size")

# Launch the demo
iface = gr.Interface(generate, inputs=[prompt, quality, style, aspect], outputs=['image', 'video'], enable_queue=True, live=False)
iface.launch(debug=True)
```

[text2art_gradio.py](https://gist.github.com/mfrashad/114f0f5a03da1492c3b8f83452554207#file-text2art_gradio-py)
>Gradio UIを構築するコード

これをGoogle Colabやローカルで実行すると、共有可能なリンクが生成され、デモを公開できます。NgrokなどのSSHトンネリングを使用しなくても自分のデモを共有できるので、とても便利です。さらにGradioは、月額わずか7ドルでデモを永久にホストできるホスティングサービスも提供しています。

![](https://miro.medium.com/max/700/1*x_u47xwrEN6J9jKAlAvu8w.png)
>Gradioデモの共有可能なリンク（著者による画像）

ただしGradioは、単一機能のデモにだけ適しています。ギャラリー、ログイン、カスタムCSSなどの機能を追加してカスタムサイトを作成することは、かなり制限されているか、全くできません。

手っ取り早い解決策としては、GradioのUIとは別にデモサイトを作成することです。次に、iframe要素を使用して、そのサイトにGradio
UIを埋め込みます。最初にこの方法を試してみましたが、重要な欠点に気が付きました。それは、MLアプリ自体と対話する必要がある部分をパーソナライズできないことです。例えば、入力検証やカスタムプログレスバーなどはiframeでは使用できません。そこで、代わりにAPIを構築することにしました。

##FastAPIを使用したMLモデル提供

APIを迅速に構築するために、FlaskではなくFastAPIを使用しています。主な理由は、FastAPIの方が記述が早い（コードが少ない）こと、（Swagger UIを使用して）ドキュメントを自動生成するため、基本的なUIでAPIをテストできることです。さらに、FastAPIは非同期関数をサポートしており、[Flaskより高速](https://fastapi.tiangolo.com/benchmarks/)だと言われています。

![](https://miro.medium.com/max/700/1*HUSz7taTaiCgO63czf6icw.png)
>URLに/docs/を追加して、Swagger UIにアクセスする（著者による画像）

![](https://miro.medium.com/max/488/1*Zyl9xH9Tlne-V1A-V89mcw.png)
>Swagger UIでAPIをテストする（著者による画像）

以下は、FastAPIサーバーとしてMLの機能を提供するコードです。

```
import clipit
import torch
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi import FastAPI, File, UploadFile, Form, BackgroundTasks
from fastapi.responses import FileResponse

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=['*'],
    allow_credentials=True,
    allow_methods=['*'],
    allow_headers=['*'],
)

@app.get('/')
async def root():
    return {'hello': 'world'}

@app.post("/generate")
async def generate(
        seed: int = Form(None),
        iterations: int = Form(None),
        prompts: str = Form("Underwater City"),
        quality: str = Form("draft"),
        aspect: str = Form("square"),
        scale: float = Form(2.5),
        style: str = Form('image'),
        make_video: bool = Form(False),
    ):
    torch.cuda.empty_cache()
    clipit.reset_settings()

    use_pixeldraw = (style == 'Pixel Art')
    use_clipdraw = (style == 'Painting')
    clipit.add_settings(prompts=prompts,
        seed=seed,
        iterations=iterations,
        aspect=aspect,
        quality=quality,
        scale=scale,
        use_pixeldraw=use_pixeldraw,
        use_clipdraw=use_clipdraw,
        make_video=make_video)

    settings = clipit.apply_settings()
    clipit.do_init(settings)
    clipit.do_run(settings)

    return FileResponse('output.png', media_type="image/png")
```

[text2art_fastapi_1.py](https://gist.github.com/mfrashad/1f50c09e9da743dd0c51dbc06601c639#file-text2art_fastapi_1-py)
>APIサーバーのコード

サーバーを定義したら、uvicornを使用して実行できます。さらに、Google ColabはColabインターフェース経由のサーバーアクセスしか許可していないので、FastAPIサーバーを公開するにはNgrokを使用する必要があります。

```
import nest_asyncio
from pyngrok import ngrok
import uvicorn

ngrok_tunnel = ngrok.connect(8000)
print('Public URL:', ngrok_tunnel.public_url)
print('Doc URL:', ngrok_tunnel.public_url+'/docs')
nest_asyncio.apply()
uvicorn.run(app, port=8000)
```

[fastapi_ngrok.py](https://gist.github.com/mfrashad/5a755c454d790cea3204c5c385c25e9f#file-fastapi_ngrok-py)
>サーバーを実行して公開するコード

サーバーを実行したら、（生成されたngrok URLに/docsを追加して）Swagger UIにアクセスし、APIをテストすることができます。

![](https://miro.medium.com/max/675/1*Ql3BPECZ8yuKas6Vpq7VMA.png)
>FastAPI Swagger UIを使用して「水中城」を生成する（著者による画像）

APIテスト中、品質やイテレーションにもよりますが、推論に約3-20分かかることがあると分かりました。3分という時間は、HTTPリクエストとしては既にとても長いとみなされていて、ユーザーはサイト上でそれだけ長く待つことを望まないでしょう。そこで、推論時間が長いため、推論をバックグラウンドタスクとして設定し、結果が出たらユーザーにメールで通知する方が、タスクに適しているかもしれないと判断しました。

方針が決まったので、まずメール送信機能を記述します。最初はSendGridメールAPIを使用していましたが、無料使用枠（1日100通）を使い切ったため、Mailgun APIに切り替えました。これはGitHub Student Developer Packに含まれていて、学生でも月20,000通のメール送信が可能です。

以下は、Mailgun APIを使用して画像を添付したメールを送信するコードです。

```
import requests
def email_results_mailgun(email, prompt):
    return requests.post("https://api.mailgun.net/v3/text2art.com/messages",
        auth=("api", "YOUR_MAILGUN_API_KEY"),
        files=[("attachment",("output.png", open("output.png", "rb").read() )),
            ("attachment", ("output.mp4", open("output.mp4", "rb").read() ))],
        data={"from": "Text2Art <YOUR_EMAIL>",
            "to": email,
            "subject": "Your Artwork is ready!",
            "text": f'Your generated arts using the prompt "{prompt}".',
            "html": f'Your generated arts using the prompt
<strong>"{prompt}"</strong>.'})
```

[send_email_mailgun.py](https://gist.github.com/mfrashad/ddc1dcb0cb4316e794fb17c1414708f5#file-send_email_mailgun-py)
>Mailgun APIを使用してメールを送信するコード

次に、FastAPIでサーバーコードをバックグラウンドタスクに変更し、バックグラウンドで結果をメール送信します。

```
#@title API Functions
import clipit
import torch
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi import FastAPI, File, UploadFile, Form, BackgroundTasks
from fastapi.responses import FileResponse

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=['*'],
    allow_credentials=True,
    allow_methods=['*'],
    allow_headers=['*'],
)

# define function to be run as background tasks
def generate(email, settings):
    clipit.do_init(settings)
    clipit.do_run(settings)

    prompt = " | ".join(settings.prompts)

    email_results_mailgun(email, prompt)

@app.get('/')
async def root():
    return {'hello': 'world'}

@app.post("/generate")
async def add_task(
        email: str,
        background_tasks: BackgroundTasks,
        seed: int = Form(None),
        iterations: int = Form(None),
        prompts: str = Form("Underwater City"),
        quality: str = Form("draft"),
        aspect: str = Form("square"),
        scale: float = Form(2.5),
        style: str = Form('image'),
        make_video: bool = Form(False),
    ):
    torch.cuda.empty_cache()
    clipit.reset_settings()

    use_pixeldraw = (style == 'Pixel Art')
    use_clipdraw = (style == 'Painting')
    clipit.add_settings(prompts=prompts,
        seed=seed,
        iterations=iterations,
        aspect=aspect,
        quality=quality,
        scale=scale,
        use_pixeldraw=use_pixeldraw,
        use_clipdraw=use_clipdraw,
        make_video=make_video)

    settings = clipit.apply_settings()

    # Run function as background task
    background_tasks.add_task(generate, email, settings)

    return {"message": "Task is processed in the background"}
```

[fastapi_background.py](https://gist.github.com/mfrashad/eef5104fd88ab64f724367159f2ddb74#file-fastapi_background-py)

このコードを使用すると、サーバーは、生成処理の終了を待って画像を返信するのではなく、リクエストに迅速に「タスクはバックグラウンドで処理中です」というメッセージを返信します。

処理が終了すると、サーバーはユーザーにメールで結果を送信します。

![](https://miro.medium.com/max/611/1*PFsSfcp0k2cgQsmlF_R7xQ.png)
>ユーザーにメールで送信する画像・動画の結果（著者による画像）

すべてうまく機能しているように見えたので、[フロントエンド](http://text2art.com/)を構築し、サイトを友人と共有しました。しかし、複数ユーザーでテストすると、同時実行の問題があることが分かりました。

最初のタスクの処理中に、2番目のユーザーがサーバーにリクエストすると、なぜか2番目のタスクが並列プロセスやキューを作成せずに現在のプロセスを終了してしまいます。原因は分かりませんでした。もしかしたらclipitコードでグローバル変数を使用していることが原因かもしれません。代わりにメッセージキューシステムを実装する必要があると分かったので、デバッグにはあまり時間をかけませんでした。

メッセージキューシステムについてGoogle検索したところ、ほとんどがRabbitMQやRedisを勧めていました。しかし、RabbitMQやRedisは`sudo`権限が必要なようで、Google Colabにインストールできるか分かりませんでした。最終的には、プロジェクトをできるだけ早く終わらせたかったこと、Firebaseが一番使い慣れていることから、代わりにGoogle Firebaseをキューシステムとして使用することにしました。

簡単に言えば、ユーザーがフロントエンドでアートを生成しようとすると、キューというコレクションにタスク（プロンプト、画像タイプ、サイズなど）を記述したエントリが追加されます。並行して、Google Colabでスクリプトを実行します。このスクリプトは、キューコレクションの新しいエントリを継続的にリッスンし、タスクを1つずつ処理します。

```
import torch
import clipit
import time
from datetime import datetime
import firebase_admin
from firebase_admin import credentials, firestore, storage

if not firebase_admin._apps:
    cred = credentials.Certificate("YOUR_CREDENTIAL_FILE")
    firebase_admin.initialize_app(cred, {
        'storageBucket': 'YOUR_BUCKET_URL'
    })

db = firestore.client()
bucket = storage.bucket()

def generate(doc_id, prompt, quality, style, aspect, email):
    torch.cuda.empty_cache()
    clipit.reset_settings()

    use_pixeldraw = (style == 'pixel art')
    use_clipdraw = (style == 'painting')
    clipit.add_settings(prompts=prompt,
        seed=seed,
        aspect=aspect,
        quality=quality,
        use_pixeldraw=use_pixeldraw,
        use_clipdraw=use_clipdraw,
        make_video=True)

    settings = clipit.apply_settings()
    clipit.do_init(settings)
    clipit.do_run(settings)

    data = {
        "seed": seed,
        "prompt": prompt,
        "quality": quality,
        "aspect": aspect,
        "type": style,
        "user": email,
        "created_at": datetime.now()
    }
    db.collection('generated_images').document(doc_id).set(data)
    email_results_mailgun(email, prompt)

transaction = db.transaction()

@firestore.transactional
def claim_task(transaction, queue_objects_ref):
    # query firestore
    queue_objects = queue_objects_ref.stream(transaction=transaction)

    # pull the document from the iterable
    next_item = None
    for doc in queue_objects:
        next_item = doc

    # if queue is empty return status code of 2
    if not next_item:
        return {"status": 2}

    # get information from the document
    next_item_data = next_item.to_dict()
    next_item_data["status"] = 0
    next_item_data['id'] = next_item.id

    # delete the document and return the information
    transaction.delete(next_item.reference)
    return next_item_data

# initialize query
queue_objects_ref = (
    db.collection("queue")
    .order_by("created_at", direction="ASCENDING")
    .limit(1)
)

transaction_attempts = 0
while True:
    try:
        # apply transaction
        next_item_data = claim_task(transaction, queue_objects_ref)
        if next_item_data['status'] == 0:
            generate(next_item_data['id'],
                next_item_data['prompt'],
                next_item_data['quality'],
                next_item_data['type'],
                next_item_data['aspect'],
                next_item_data['email'])
            print(f"Generated {next_item_data['prompt']} for {next_item_data['email']}")

    except Exception as e:
        print(f"Could not apply transaction. Error: {e}")
        time.sleep(5)
        transaction_attempts += 1
        if transaction_attempts > 20:
            db.collection("errors").add({
                "exception": f"Could not apply transaction. Error: {e}",
                "time": str(datetime.now())
            })
            exit()
```

[firebase_worker.py](https://gist.github.com/mfrashad/2fb66f1c25e40bc046464db736b11a33#file-firebase_worker-py)
>タスクを処理し、キューを継続的にリッスンするバックエンドコード

フロントエンドでは、キューに新しいタスクを追加するだけです。ただし、フロントエンドで適切な[Firebase設定](https://firebase.google.com/docs/web/setup)を行っていることを確認してください。

```
db.collection("queue").add({
    prompt: prompt,
    email: email,
    quality: quality,
    type: type,
    aspect: aspect,
    created_at: firebase.firestore.FieldValue.serverTimestamp(),
})
````

完成です！これで、ユーザーがフロントエンドでアートを生成しようとすると、キューに新しいタスクが追加され、Colabサーバーのワーカースクリプトは、キューのタスクを1つずつ処理します。

>コードの詳細については、[GitHubリポジトリ](https://github.com/mfrashad/text2art)をご覧ください。
>（リポジトリにぜひ星を付けてください）

![](https://miro.medium.com/max/700/1*0Gdpcj0tCfAFiI7pyb-lcw.png)
>フロントエンドでキューに新しいタスクを追加する（著者による画像）

![](https://miro.medium.com/max/700/1*NMvUBobCW7UzmYxd8QgojA.png)
>Firebaseのキューの内容（著者による画像）

##おわりに

この記事が気に入ったら、他の記事もぜひチェックしてください！

>**AIで自分をディズニーキャラクターとしてアニメ化する**
>デジタルアートの未来を覗く
>[towardsdatascience.com](https://towardsdatascience.com/animating-yourself-as-a-disney-character-with-ai-78af337d4081)

###

>**StyleGAN2でアニメキャラクターを生成する**
>クールなアニメ顔の補間を生成する方法を学ぶ
>[towardsdatascience.com](https://towardsdatascience.com/generating-anime-characters-with-stylegan2-6f8ae59e237b)

Linkedinでもお気軽にご連絡ください。

### 

>**Muhammad Fathy Rashad　テクニカルライター　Medium | LinkedIn**
>16歳で大学の最年少学生として学位を取得し、2K超インストールされたモバイルゲーム2つを発表…
>[https://www.linkedin.com/in/mfathyrashad/](https://www.linkedin.com/in/mfathyrashad/)

##参照

[1] <https://openai.com/blog/dall-e/>
[2] <https://openai.com/blog/clip/>
[3] <https://ljvmiranda921.github.io/notebook/2021/08/08/clip-vqgan/>
[4] <https://github.com/orpatashnik/StyleCLIP>
[5] <https://towardsdatascience.com/understanding-flask-vs-fastapi-web-framework-fe12bb58ee75>

##翻訳協力

この記事は以下の方々のご協力により公開する事ができました。改めて感謝致します。

Original Author: [Fathy Rashad](https://medium.com/@mfrashad) ([Linkedin](https://www.linkedin.com/in/mfathyrashad/), <https://www.mfrashad.com/>)
Original Article: [How I built an AI Text-to-Art Generator](https://towardsdatascience.com/how-i-built-an-ai-text-to-art-generator-a0c0f6d6f59f)
Thank you for letting us share your knowledge!

選定担当: @gracen
翻訳担当: @gracen
監査担当: -
公開担当: @gracen

##ご意見・ご感想をお待ちしております
今回の記事はいかがでしたか？
・こういう記事が読みたい
・こういうところが良かった
・こうした方が良いのではないか
などなど、率直なご意見を募集しております。
頂いたお声は、今後の記事の質向上に役立たせて頂きますので、お気軽に
コメント欄にてご投稿ください。[Twitter](https://twitter.com/BabyDegu?lang=ja)でもご意見を受け付けております。
皆様のメッセージをお待ちしております。