本記事は、20歳のAIエンジニア/フルスタックデベロッパFathy Rashad氏による「How I built an AI Text-to-Art Generator」(2021年10月2日公開)の和訳を、著者の許可を得て掲載しているものです。
#僕がAI Text-to-Artジェネレータを1週間で作った方法
Text2Art.comの作成方法について、順を追って詳しく説明します。
Text2Art 生成アートのギャラリー(著者による画像)
##概要
これは、僕がどうやって1週間でText2Art.comを作成したかを説明する記事です。Text2Artは、VQGAN+CLIPベースのAIアートジェネレータです。テキストを入力するだけで、ピクセルアート、線画、水彩画・油彩画など、あらゆる種類のアートを生成できます。この記事では、VQGAN+CLIPの実験から、Gradioを使用したシンプルなUIの構築、モデル提供のためのFastAPIへの移行、そして最後に、Firebaseを使用したキューシステムまで、僕の思考プロセスを追います。興味のある部分までスキップしてください。
text2art.comでお試しいただけます。ここにソースコードがあります。
(リポジトリにぜひ星を付けてください)
Text2Artデモ(更新情報:現ユーザー数1.5K超)
##目次
- はじめに
- 仕組み
- CodeVQGAN+CLIPを使用したコードによるアート生成
- Gradioを使用したUI構築
- FastAPIを使用したML提供
- Firebaseを使用したキューシステム
##はじめに
少し前に、ジェネレーティブアートとNFTが世界を席巻しました。これを可能にしたのは、OpenAIがtext-to-image生成を大きく進歩させたことです。今年の初めにOpenAIは、優れた機能を有する、強力なtext-to-imageジェネレータDALL-Eを発表しました。以下は「キリンとドラゴンのキメラ/ドラゴンを模したキリン/ドラゴンでできたキリンのプロの高品質イラスト」というテキストプロンプトでDALL-Eが生成した画像です。DALL-Eがいかに優れているか分かります。
「キリンとドラゴンのキメラ/ドラゴンを模したキリン/ドラゴンでできたキリンのプロの高品質イラスト」テキストプロンプトによるDALL-E生成画像(OpenAI MITライセンス画像)
残念ながらDALL-Eは一般公開されていません。しかし幸いなことに、DALL-Eの魔法の背後にあるモデルであるCLIPが代わりに公開されました。CLIP(Contrastive Image-Language Pretraining:対照言語画像事前学習)は、テキストと画像を組み合わせたマルチモーダルネットワークです。つまり、画像とキャプションの一致度をスコア化できます。これは、入力テキストと完全一致する画像を生成するようにジェネレータを誘導するのにとても役立ちます。DALL-Eでは、CLIPを使用して、生成された画像をランク付けし、スコアが最も高い画像(テキストプロンプトに最も近い画像)を出力します。
画像とキャプションをスコア化するCLIPの例(著者による画像)
DALL-Eの発表から数ヶ月後、VQGAN(Vector Quantized GAN:ベクトル量子化GAN)という新しい変形画像ジェネレータが公開されました。VQGANとCLIPを組み合わせると、DALL-Eに近い品質が得られます。学習済みVQGANモデルが公開されてから、沢山の素晴らしいアートがコミュニティで生成されました。
街の港の夜景と沢山の船の絵、戦争中の難民の絵(著者による画像)
この結果にとても驚き、友人と共有したいと思いました。しかし、アート生成のコードに没頭してくれる人はあまりいないので、コードを見ることなくプロンプトを入力するだけで、誰でもすぐに欲しい画像を生成できるウェブサイト、Text2Art.comを作成することにしました。
##仕組み
VQGAN+CLIPはどのように動作するのでしょうか。簡単に言うと、ジェネレータが画像を生成し、CLIPがその画像との一致度を測定します。次に、ジェネレータはCLIPモデルからのフィードバックを使用して、より「正確な」画像を生成します。CLIPスコアが十分に高くなり、生成された画像がテキストと一致するまで、何度もイテレートします。
VQGANモデルが画像を生成し、CLIPがその処理を導く。ジェネレータがより「正確な」画像を生成するまで、何度もイテレートする(出典:LJ MirandaによるVQGAN図解)
VQGANやCLIPの内部動作については、この記事の焦点ではないため、ここでは触れません。VQGAN、CLIP、DALL-Eについてさらに説明が必要な場合は、以下の素晴らしい資料を参照してください。
- LJ MirandaによるVQGAN図解:素晴らしいイラスト付きVQGAN解説
- Charlie SnellによるDALL-E解説:基礎からの素晴らしいDALL-E解説
- Yannic KilcherによるCLIPペーパー解説動画:CLIPペーパー解説
###X+CLIP
VQGAN+CLIPは、画像ジェネレータとCLIPの組み合わせでできることの一例に過ぎません。VQGANはどんな種類のジェネレータとも置き換えられ、ジェネレータによってはとてもうまく機能します。X+CLIPには、StyleCLIP(StyleGAN+CLIP)、CLIPDraw(ベクターアートジェネレータ使用)、BigGAN+CLIPなど、多くのバリエーションがあります。画像の代わりに音声を使用するAudioCLIPもあります。
StyleCLIPを使用した画像編集(出典:StyleCLIP Paper)
##VQGAN+CLIPを使用したコードによるアート生成
VQGAN+CLIPを使用したアート生成を数行のシンプルなコードで実現した、dribnetのclipitリポジトリのコードを使用しています(更新情報:clipitはpixrayに移行されました)。
VQGAN+CLIPはかなりのGPUメモリを必要とするため、Google Colabで実行することをお勧めします。ここにColabノートブックがあります。これに沿って進めてください。
まず、Colabで実行する場合は、ランタイムタイプをGPUに変更してください。
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
にオプションも指定できます。使用可能なオプションの詳細については、ソースコードを確認してください。
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)
VQGAN+CLIPを使用してアートを生成するコード
コードを実行すると、画像が生成されます。イテレーションする度に、画像はテキストプロンプトに近付きます。
「水中都市」画像生成における、イテレーションによる結果改善(著者による画像)
###イテレーション回数の増加
イテレーション回数を増やしたい場合は、iterations
オプションを使用して、必要なだけ設定します。例えば、500回イテレーションしたい場合は、以下のように設定します。
clipit.add_settings(iterations=500)
###動画の生成
イテレーションする度に画像を生成する必要があるため、その画像を保存して、AIの画像生成方法のアニメーションを作成できます。設定の適用前にmake_video=True
を追加するだけです。
clipit.add_settings(make_video=True)
以下の動画が生成されます。
生成された「水中都市」GIF(著者による画像)
###画像サイズのカスタマイズ
size=(width, height)
オプションを追加して、画像を変更することもできます。例えば、ここでは解像度800x200のバナー画像を生成します。解像度が高いほど、多くのGPUメモリが必要になることに注意してください。
clipit.add_settings(size=(800, 200))
「空想の王国 #artstation」プロンプトで800x200の画像を生成(著者による画像)
###ピクセルアートの生成
clipitには、ピクセルアートを生成するオプションもあります。CLIPDrawのレンダラーをシーンの裏で使用し、パレットの色の制限やピクセル化など、ピクセルアートのスタイルを強制します。ピクセルアートオプションを使用するには、use_pixeldraw=True
オプションを有効にするだけです。
clipit.add_settings(use_pixeldraw=True)
左「鎧の騎士 #pixelart」と右「中国の空想のゲームの世界 #pixelart」プロンプトで生成された画像(著者による画像)
###VQGAN+CLIP キーワード修飾子
CLIPにはバイアスがあるため、プロンプトに特定のキーワードを追加すると、生成画像に特定の効果が生じる場合があります。例えば、テキストプロンプトに「unreal engine」を追加すると、リアルなスタイルやHDスタイルが生成される傾向があります。また、「deviantart」「artstation」「flickr」など特定のサイト名を追加すると、通常、結果がより美しくなります。最高のアートを生成する「artstation」というキーワードが気に入っています。
キーワードの比較(kingdomakrillicによる画像)
さらに、キーワードを使用してアートスタイルを調整することもできます。例えば、「鉛筆スケッチ」「ローポリゴン」などのキーワードや、「Thomas Kinkade」「James Gurney」などのアーティスト名です。
アートスタイルのキーワード比較(kingdomakrillicによる画像)
さまざまなキーワードの効果の詳細については、kingdomakrillicによる包括的な実験結果を確認してください。同じ4つのテーマで200以上のキーワードを試しています。
##Gradioを使用したUI構築
最初の予定では、MLモデルのデプロイにGradioを使用するつもりでした。Gradioは、MLデモを数行のコードだけで簡単に作成できるPythonライブラリです。Gradioなら10分未満でデモを作成できます。さらに、ColabでGradioを実行すると、Gradioドメインを使用して共有可能なリンクが生成されます。このリンクをすぐに友人や一般の人と共有し、デモを試してもらうことができます。Gradioにはまだ制約がいくつかありますが、単一機能のデモをしたい場合には、最適なライブラリだと思います。
Gradio UI(著者による画像)
以下は、Text2ArtアプリのシンプルなUIを構築するコードです。コードを見れば一目瞭然だと思いますが、さらに説明が必要な場合は、Gradioドキュメントを参照してください。
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)
Gradio UIを構築するコード
これをGoogle Colabやローカルで実行すると、共有可能なリンクが生成され、デモを公開できます。NgrokなどのSSHトンネリングを使用しなくても自分のデモを共有できるので、とても便利です。さらにGradioは、月額わずか7ドルでデモを永久にホストできるホスティングサービスも提供しています。
Gradioデモの共有可能なリンク(著者による画像)
ただしGradioは、単一機能のデモにだけ適しています。ギャラリー、ログイン、カスタムCSSなどの機能を追加してカスタムサイトを作成することは、かなり制限されているか、全くできません。
手っ取り早い解決策としては、GradioのUIとは別にデモサイトを作成することです。次に、iframe要素を使用して、そのサイトにGradio
UIを埋め込みます。最初にこの方法を試してみましたが、重要な欠点に気が付きました。それは、MLアプリ自体と対話する必要がある部分をパーソナライズできないことです。例えば、入力検証やカスタムプログレスバーなどはiframeでは使用できません。そこで、代わりにAPIを構築することにしました。
##FastAPIを使用したMLモデル提供
APIを迅速に構築するために、FlaskではなくFastAPIを使用しています。主な理由は、FastAPIの方が記述が早い(コードが少ない)こと、(Swagger UIを使用して)ドキュメントを自動生成するため、基本的なUIでAPIをテストできることです。さらに、FastAPIは非同期関数をサポートしており、Flaskより高速だと言われています。
URLに/docs/を追加して、Swagger UIにアクセスする(著者による画像)
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")
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)
サーバーを実行して公開するコード
サーバーを実行したら、(生成されたngrok URLに/docsを追加して)Swagger UIにアクセスし、APIをテストすることができます。
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>.'})
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"}
このコードを使用すると、サーバーは、生成処理の終了を待って画像を返信するのではなく、リクエストに迅速に「タスクはバックグラウンドで処理中です」というメッセージを返信します。
処理が終了すると、サーバーはユーザーにメールで結果を送信します。
ユーザーにメールで送信する画像・動画の結果(著者による画像)
すべてうまく機能しているように見えたので、フロントエンドを構築し、サイトを友人と共有しました。しかし、複数ユーザーでテストすると、同時実行の問題があることが分かりました。
最初のタスクの処理中に、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設定を行っていることを確認してください。
db.collection("queue").add({
prompt: prompt,
email: email,
quality: quality,
type: type,
aspect: aspect,
created_at: firebase.firestore.FieldValue.serverTimestamp(),
})
完成です!これで、ユーザーがフロントエンドでアートを生成しようとすると、キューに新しいタスクが追加され、Colabサーバーのワーカースクリプトは、キューのタスクを1つずつ処理します。
コードの詳細については、GitHubリポジトリをご覧ください。
(リポジトリにぜひ星を付けてください)
フロントエンドでキューに新しいタスクを追加する(著者による画像)
Firebaseのキューの内容(著者による画像)
##おわりに
この記事が気に入ったら、他の記事もぜひチェックしてください!
AIで自分をディズニーキャラクターとしてアニメ化する
デジタルアートの未来を覗く
towardsdatascience.com
StyleGAN2でアニメキャラクターを生成する
クールなアニメ顔の補間を生成する方法を学ぶ
towardsdatascience.com
Linkedinでもお気軽にご連絡ください。
Muhammad Fathy Rashad テクニカルライター Medium | LinkedIn
16歳で大学の最年少学生として学位を取得し、2K超インストールされたモバイルゲーム2つを発表…
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 (Linkedin, https://www.mfrashad.com/)
Original Article: How I built an AI Text-to-Art Generator
Thank you for letting us share your knowledge!
選定担当: @gracen
翻訳担当: @gracen
監査担当: -
公開担当: @gracen
##ご意見・ご感想をお待ちしております
今回の記事はいかがでしたか?
・こういう記事が読みたい
・こういうところが良かった
・こうした方が良いのではないか
などなど、率直なご意見を募集しております。
頂いたお声は、今後の記事の質向上に役立たせて頂きますので、お気軽に
コメント欄にてご投稿ください。Twitterでもご意見を受け付けております。
皆様のメッセージをお待ちしております。