ChatGPTを活用したDjangoアプリの実装方法を学ぶレシピ
はじめに
このレシピではChatGPTのAPIを活用したアプリ機能をDjangoで実装する方法を解説します。
「参考になった。面白かった。」と思った方はぜひSNS等でシェアいただけると嬉しいです。
以下の2つの機能の実装について説明します。
文章校正機能
画面上で校正したい文章を入力して「校正」ボタンを押すと、ChatGPTによる文章の校正結果を表示します。
校正結果は、上図のように削除された部分は取り消し線を追加、追加された部分は背景色を黄色&赤色のアンダーラインをつけて表示させるようにします。
テキスト文から画像を生成する機能
2つ目は、画面上で生成したい画像のイメージまたは簡単な説明文を入力し、其の説明文を元にChatGPTに表現豊かな説明文(英文)を生成させます。
ChatGPTが生成した説明文を元にStable Diffusion(画像生成モデル)を使ってテキストから画像を生成して画面上に生成された画像を示させる機能(下図)を実装します。
ソースコードについて
このレシピで開発するソースコードは以下のGithubリポジトリに登録されています。
必要に応じてご利用ください。
1.openAIのAPI keyの準備
ChatGPTのAPIを利用するためには、API keyを取得する必要があります。
以下の手順でAPI keyを取得しましょう。
OpenAIのWEBサイトにアクセスします。
以下の画面が表示されるので、画面上のメニュー「API」をクリックします。
「SIGN UP」からユーザ登録を行いましょう。
以下の画面が表示されたら、Googleアカウント等でユーザ登録を行います。
(以下はGoogleアカウントでの登録例)
携帯電話によるコード認証が必要なので携帯番号を入力してSMSで届いた番号を入力しましょう。
利用用途を聞かれるので、以下の通り個人利用を選択しておけばよいでしょう。
次に、画面右上の「Personal」→「View API keys」をクリックします。
「Create new secret key」をクリックしてAPIキーを発行します。
以下の様にAPIキーが発行されたらコピーして控えておきましょう。
2.事前準備
まず、開発に必要な事前準備を行います。
以下のような構成で仮想環境の作成、Djangoプロジェクトの作成、アプリケーションの作成まで行いましょう。
任意のディレクトリ上で以下のコマンドを実行して、アプリケーション作成まで完了させてください。
仮想環境の作成とアクティベート
python -m venv django-chat-gpt
django-chat-gpt\scripts\activate
次に、djangoとopenaiモジュールをインストールします。
pip install django openai
Djangoプロジェクトの作成
以下のコマンドを実行してプロジェクトを作成します。
django-admin startproject config .
以上で事前準備は完了です。
CHATGPTのAPIを使ってみる
まずは、pythonコード上からCHATGPTのAPIを使ってみましょう。
まず、manage.pyファイルがあるディレクトリ上にtest.pyを作成して以下のコードを記載しましょう。
import openai
APK_KEY = "自分のAPK KEYをここに記載する"
def chat_gpt(prompt):
openai.api_key = APK_KEY #API KEYをセット
openai.Model.list() #OpenAIのインスタンスを生成
#APIを使ってリクエストを投げる
response = openai.Completion.create(
model = "text-davinci-003",
prompt= prompt,
temperature=0,
max_tokens=300,
top_p=1.0,
frequency_penalty=0.0,
presence_penalty=0.0
)
return response
OpenAIのChatGPTを使うにはまず、openaiをインポートします。
import openai
次にchat_gpt関数を定義します。
引数にはprompt(ChatGPTのモデルにInputする質問文)を指定します。
続いて、openai.api_keyに自身のAPI keyを指定します。
openai.api_key = APK_KEY #API KEYをセット
次にOpenAIのインスタンスを以下のコードで生成します。
openai.Model.list() #OpenAIのインスタンスを生成
ChatGPTに対してリクエストを投げるコードが以下の部分です。
#APIを使ってリクエストを投げる
response = openai.Completion.create(
model = "text-davinci-003",
prompt= prompt,
temperature=0,
max_tokens=300,
top_p=1.0,
frequency_penalty=0.0,
presence_penalty=0.0
)
openai.Completion.createにパラメータを指定することでOpenAIのモデルにリクエストを投げることができます。
各パラメータの意味は以下の通りです。
また、モデルについては以下のような種類があります。(2023/2時点)
※以下の公式HPに記載があります。
今回は、ChatGPTと同等のtext-davinci-003というモデルを指定します。
それでは、先ほど定義したchat_gpt関数を使ってChatGPTを使ってみましょう。
以下のコマンドを実行してDjangoのシェルモードを起動します。
> python manage.py shell
Python 3.8.3 (default, Jul 2 2020, 17:30:36) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>
次に、test.pyに定義したchat_gpt関数をインポートします。
>>> from test import chat_gpt
ここでは、質問文「"pythonベースのDjangoとは何ですか?"」をChatGPTに投げてみます。
>>> prompt = "pythonベースのDjangoとは何ですか?"
>>> response = chat_gpt(prompt)
>>> print(response)
{
"choices": [
{
"finish_reason": "stop",
"index": 0,
"logprobs": null,
"text": "\n\nDjango\u306fPython\u30d9\u30fc\u30b9\u306e\u30aa\u30fc\u30d7\u30f3\u30bd\u30fc\u30b9Web\u30d5\u30ec\u30fc\u30e0\u30ef\u30fc\u30af\u3067\u3059\u3002Web\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u958b\u767a\u3059\u308b\u305f\u3081\u306e\u30c4\u30fc\u30eb\u3092\u63d0\u4f9b\u3057\u3001\u958b\u767a\u8005\u304c\u52b9\u7387\u7684\u306bWeb\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u69cb\u7bc9\u3067\u304d\u308b\u3088\u3046\u306b\u3057\u307e\u3059\u3002Django\u306f\u3001MVC\uff08Model-View-Controller\uff09\u30a2\u30fc\u30ad\u30c6\u30af\u30c1\u30e3\u3092\u63a1\u7528\u3057\u3066\u304a\u308a\u3001\u958b\u767a\u8005\u304c\u30c7\u30fc\u30bf\u30e2\u30c7\u30eb\u3092\u5b9a\u7fa9\u3057\u3001\u305d\u308c\u3092\u4f7f\u7528\u3057\u3066Web\u30da\u30fc\u30b8\u3092\u69cb\u7bc9\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002\u307e\u305f\u3001Django\u306f\u3001\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3078\u306e\u30a2\u30af\u30bb\u30b9\u3001\u30d5\u30a9\u30fc\u30e0\u306e\u4f5c\u6210\u3001\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u306a\u3069\u3001Web\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u958b\u767a\u306b\u5fc5\u8981\u306a\u591a\u304f\u306e\u6a5f\u80fd\u3092\u63d0\u4f9b\u3057\u307e\u3059\u3002"
}
],
"created": 1676260521,
"id": "cmpl-6jKDxb9sxEYCSNHQklWcf2FJH1jkW",
"model": "text-davinci-003",
"object": "text_completion",
"usage": {
"completion_tokens": 271,
"prompt_tokens": 18,
"total_tokens": 289
}
}
responseをprintで表示すると上記の通り様々な情報が返されていることがわかります。
この中で、choicesの中のtextに回答文章が格納されています。
回答文を取り出すには以下のコードを実行します。
>>> response["choices"][0]["text"].strip()
'DjangoはPythonベースのオープンソースWebフレームワークです。
Webアプリケーションを開発す るためのツールを提供し、開発者が効率的にWebアプリケーション
を構築できるようにします。Djangoは、MVC(Model-View-Controller)アーキテクチャを採用
しており、開発者がデータモデルを 定義し、それを使用してWebページを構築することができます。
また、Djangoは、データベースへ のアクセス、フォームの作成、セキュリティなど、Webアプリケ
ーション開発に必要な多くの機能 を提供します。'
上記の通り、ChatGPTが回答した文章が取得できていることが確認できます。
3.ChatGPTを使った文章校正機能を実装する
OpenAIが提供するモデルでは様々なタスクを扱うことができます。
どのようなタスクを扱えるかは以下のページで説明がありますので詳細は各自確認ください。
ここでは、上記ページの「Grammar correction」で説明されている文法修正を使って文章の校正機能を実装してみたいと思います。
上記ページでは、以下のような入力を与えると・・・
以下のようなレスポンスが返ってくる例が記載されています。
文章校正を試してみる
一旦、ChatGPTで日本語文章の校正を行ってみましょう。
質問文(promt)に「次の文章を校正してください。\n <質問文>」という形式でリクエストを投げてみましょう。
>>> prompt = "次の文章を校正してください。\n 接客や調理になれたら、お店の店超候補の方にはすこしづつ、店長として必要な管理業務や仕入れ業夢、スタッフの育成をはじめ、店鋪運営に必要な業務を教えす。"
>>> response = chat_gpt(prompt)
>>> response["choices"][0]["text"].strip()
'接客や調理になれたら、お店の店長候補の方にはすこしづつ、店長として必要な管理業務や仕入れ業務、スタッフの育成をはじめ、店舗 運営に必要な業務を教えします。'
上記の通り、「'接客や調理になれたら、お店の店長候補の方にはすこしづつ、店長として必要な管理業務や仕入れ業務、スタッフの育成をはじめ、店舗 運営に必要な業務を教えします。'」といった文章が返ってきました。
この例だと最後の「教えし」がおかしいままになっています。
有料版のChatGPT-plusを使えばより高い精度の結果が得られると思いますが、無料版でも精度を向上させるコツがあります。
以下の様に、マークダウン形式でリクエストを投げてみましょう。
冒頭でChatGPTに対して「あなたは文書校正のプロ」であることを認識させます。
制約条件には文章校正における注意点などを記載するとよいと思います。
以下が実行結果です。
prompt = """
あなたは、文書校正のプロです。
以下の制約条件に従い、入力文の間違いを修正してください。
#制約条件:
・修正後の文章のみ出力すること。
・重要な間違いを見逃さないこと。
・文法上おかしい部分を修正すること。
・前後のつながりを考慮して明らかにおかしい部分を修正すること。
#入力文:
接客や調理になれたら、お店の店超候補の方にはすこしづつ、店長として必要な管理業務や仕入れ業夢、スタッフの育成をはじめ、店鋪運営に必要な業務を教えす。
#出力文:
"""
response = chat_gpt(prompt)
response["choices"][0]["text"].strip()
'接客や調理になれたら、お店の店長候補の方にはすこしづつ、店長として必要な管理業務や仕入れ業務、スタッフの育成をはじめ、店舗 運営に必要な業務を教えていく。'
回答をみると、以下の通り「教えし」だった部分が「教えていく」に修正されていることが確認できます。
「'接客や調理になれたら、お店の店長候補の方にはすこしづつ、店長として必要な管理業務や仕入れ業務、スタッフの育成をはじめ、店舗 運営に必要な業務を教えていく。'」
【注意】
無料版のChatGPTでは文章校正では正しく修正されないパターンが割と多い印象なので、高精度を希望する場合は有料版を使ってみましょう。
アプリケーションの作成
以下のコマンドを実行して文章校正用のDjangoアプリケーション(proofreading)を作成しましょう。
python manage.py startapp proofreading
作成したアプリケーションを認識させるためにconfig/settings.pyのINSTALLED_APPSにエントリーを追加します。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'proofreading.apps.ProofreadingConfig', #追加
]
また日本語にするためconfig\settings.pyの言語とタイムゾーンを以下の通り変更しておきます。
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
部品関数の定義
先ほどテストで利用したChatGPTにリクエストを投げるchat_gpt関数をproofreading\utils.pyに定義しておきましょう。
import openai
import os
from django.conf import settings
APK_KEY = "ここに自分のAPIキーを入力する"
def chat_gpt(prompt):
openai.api_key = APK_KEY #API KEYをセット
openai.Model.list() #OpenAIのインスタンスを生成
#APIを使ってリクエストを投げる
response = openai.Completion.create(
model = "text-davinci-003",
prompt= prompt,
temperature=0,
max_tokens=300,
top_p=1.0,
frequency_penalty=0.0,
presence_penalty=0.0
)
response = (response["choices"][0]["text"]).strip()
return response
def create_prompt(input_text, file_name):
prompt_file = os.path.join(settings.BASE_DIR, 'template', file_name)
with open(prompt_file, encoding="utf-8") as f:
file_read = f.read()
#Chat-GTPへ投げるフォーマットに入力文をセットする。
prompt = file_read.replace("[input]", input_text)
return prompt
chat_gpt関数は先ほど定義した内容そのままを記載しています。
また、新たにcreate_prompt関数を定義しています。
これは、画面の入力文を元に以下のフォーマットデータを生成する関数です。
このデータをChatGPTに対して投げます。
上記のフォーマットファイルは、Djangoプロジェクト直下にtemplateフォルダを作成して、GrammarCorrection.txtとして配置しておき、以下のコードでファイル内容を読み込みます。
prompt_file = os.path.join(settings.BASE_DIR, 'template', file_name)
GrammarCorrection.txtの中身は以下を記載しておきます。
ビューの作成
「Web画面上から入力文を受け付け、ChatGPTにリクエストを投げて校正された文章(レスポンス)を画面に表示する機能」を実装します。
「Web画面上に入力した入力文を取得しChatGPTにリクエストを投げた結果をテンプレートに返す関数」を定義します。
chat_gpt\views.pyを開いて以下のコードを記載ましょう。
from django.shortcuts import render
from django.views import View
from .utils import chat_gpt, create_prompt
# Create your views here.
class GrammarCorrectionView(View):
def get(self, request):
return render(request, 'proofreading/grammar_correction.html')
def post(self, request):
# 画面に入力した文章を取得
input_text= request.POST['input_text']
if input_text:
# Chat-GPTに投げる命令文を生成
prompt = create_prompt(input_text, "GrammarCorrection.txt")
# Chat-GPTへリクエストを投げる
response = chat_gpt(prompt)
# 辞書型データを作成する
context = {'input_text': input_text,
'response': response,
}
# テンプレートにデータを渡す
return render(request, 'proofreading/grammar_correction.html', context)
else:
return render(request, 'proofreading/grammar_correction.html')
ここでは、Viewクラスを継承してクラスベースビューを定義しています。
最初に入力画面にアクセスした際にCallされるgetメソッド(単にテンプレートを返す)と、ChatGPTにリクエストを投げるpostメソッドを定義しておきます。
コードの内容は特に難しいことは行っていませんので、コメント欄を見ていただければ実施していることは理解できると思います。
画面に入力した文章(input_text)とChatGPTによる校正結果(response)をテンプレートに返すために以下を追加しています。
context = {'input_text': input_text,
'response': response,
}
URLパターンの定義
次に、文章を入力する画面のURLを定義します。
proofreading\urls.pyファイルを新規に作成し、以下のコードを定義します。
from django.urls import path
from .views import GrammarCorrectionView
urlpatterns = [
path('grammar_correction', GrammarCorrectionView.as_view(), name="grammar_correction"),
]
また、config\urls.pyを以下の通り修正します。
from django.contrib import admin
from django.urls import path, include # includeを追加
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('proofreading.urls')), #追加
]
テンプレートの作成
最後文章を入力する画面(テンプレート)を定義します。
proofreading\templates\proofreadingフォルダを作成して、以下の3つのテンプレートを作成します。
base.html(共通テンプレート)
grammar_correction.html(文章校正の画面)
まず、base.htmlに以下のコードを記載しましょう。
{% load static %}
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Django & Chat-GPT</title>
<link href="https://fonts.googleapis.com/css?family=M+PLUS+1p:400,500" rel="stylesheet">
<link href="{% static 'css/style.css' %}" rel="stylesheet">
</head>
<body>
<header>
<div class="container">
<h1>Django & Chat-GPT</h1>
</div>
</header>
<main>
<div class="container">
{% block content %}{% endblock %}
</div>
</section>
</main>
<footer>
<p>Django & Chat-GPT</p>
</footer>
</body>
</html>
次にgrammar_correction.htmlに以下のコードを記載しましょう。
{% extends './base.html' %}
{% load static %}
{% block content %}
<section>
<h3>ChatGPTによる文章校正</h3>
<br />
<p>校正したい文章を以下に入力してください。<br>
<form method=POST action="{% url 'grammar_correction' %}">
{% csrf_token %}
<div class="mb-3">
<textarea style="border:4px solid rgb(118, 230, 13)" class="form-control"
placeholder="校正したい文章をここに入力" name="input_text" rows="5" cols="100"></textarea>
</div>
<br />
<button type="submit" class="btn btn-primary btn-sm">校正する</button>
</form>
<br />
</section>
<hr>
{% if response %}
<section>
<p><strong>入力した文章:</strong><br>
<div class="box">
{{input_text}}
</div><br>
<strong>ChatGPTによる校正結果:</strong><br>
<div class="box">
{{response}}
</div>
<br><br>
</section>
{% endif %}
{% endblock %}
文章の入力欄を以下のformタグで定義します。
<form method=POST action="{% url 'grammar_correction' %}">
{% csrf_token %}
<div class="mb-3">
<textarea style="border:4px solid rgb(118, 230, 13)" class="form-control"
placeholder="校正したい文章をここに入力" name="input_text" rows="5" cols="100"></textarea>
</div>
<br />
<button type="submit" class="btn btn-primary btn-sm">校正する</button>
</form>
「構成する」ボタンを押した際にpostメソッドでURLパターン「grammar_correction」を逆参照するようにしています。
また、name属性に「input_text」を指定してビューのpostメソッド内の「request.POST['input_text']」で入力文を取得できるようにしておきます。
校正結果は以下で描画します。
{% if response %}
<section>
<p><strong>入力した文章:</strong><br>
<div class="box">
<p class="text_height" id="input_sentence">{{input_text}}</p>
</div><br>
<strong>ChatGPTによる校正結果:</strong><br>
<div class="box">
<p class="text_height" id="gpt-response">{{response}}</p>
</div>
<br><br>
</section>
{% endif %}
静的ファイルの配置
リポジトリからstatic\cssフォルダを\proofreading\staticにコピーしておきましょう。
画面デザインを整えるためのcssファイルですが、デザインに関する部分なので説明は割愛します。
以上で設定が完了したので、http://127.0.0.1:8000/grammar_correctionにアクセスし、以下のような画面が表示されることを確認します。
試しに「接客や調理になれたら、お店の店超候補の方にはすこしづつ、店長として必要な管理業務や仕入れ業夢、スタッフの育成をはじめ、店鋪運営に必要な業務を教えす。」と入力して「校正する」ボタンを押してみましょう。
以下のように入力した文章と、ChatGPTが校正した文章が画面に表示されます。
以上で、基本的にはChatGPTを使った文章校正機能の実装は完了です。
文章校正箇所を見やすく可視化する
文章校正機能は実装できましたが、このままだとどこが修正されたかパッとわからないので、下記例のように削除された部分に取り消し線を追加し、追加された部分の背景を黄色&アンダーラインをつけて見やすく可視化する機能を追加してみましょう。
上図のような可視化を行うためには、校正前後の文章の差異を検出する必要があります。
今回はPython標準のdifflibというライブラリーを使って文字列の差異を検出することにします。
difflib.ndiff(a, b)を使うことでa と b (文字列のリスト) を比較し、差分 (差分形式の行を生成する ジェネレータ) を、 Differ のスタイルで返してくれます。
Djangoのシェルモード上で以下のコマンドを実行して、実際にdifflib.ndiffの動作を確認してみましょう。
ここでは、質問文を以下の様にします。
ChatGPTからレスポンスを以下とします。
変数aとbに上記の文章を格納します。
>>> a = "接客や調理に慣れてきたら、店長候補の方には少しづつ、店長として必要な管理業務や仕入
れ業夢、スタッフの育成をはじじめ、店補運営に必要な業務を教えす。"
>>> b = "接客や調理に慣れてきたら、店長候補の方には少しづつ、店長として必要な管理業務や仕入
れ業務、スタッフの育成をはじめ、店舗運営に必要な業務を教えていく。"
以下の通りdifflib.ndiff(a, b)をforループで回して結果を表示すると以下の様になります。
>>> for i,s in enumerate(difflib.ndiff(a, b)):
... print(i,s) "
...
0 接
1 客
2 や
3 調
4 理
5 に
6 慣
7 れ
8 て
9 き
10 た
11 ら
12 、
13 店
14 長
15 候
16 補
17 の
18 方
19 に
20 は
21 少
22 し
23 づ
24 つ
25 、
26 店
27 長
28 と
29 し
30 て
31 必
32 要
33 な
34 管
35 理
36 業
37 務
38 や
39 仕
40 入
41 れ
42 業
43 - 夢
44 + 務
45 、
46 ス
47 タ
48 ッ
49 フ
50 の
51 育
52 成
53 を
54 は
55 じ
56 - じ
57 め
58 、
59 店
60 - 補
61 + 舗
62 運
63 営
64 に
65 必
66 要
67 な
68 業
69 務
70 を
71 教
72 え
73 - す
74 + て
75 + い
76 + く
77 。
>>>
ここで、「+」または「-」が表示されている箇所がありますがここが、2つの文字列の差分になります。
具体的には、「+」の箇所が「文字列が追加された部分」、
「-」の箇所が「文字列が削除された部分」になります。
sは配列になっていて、「+」または「-」はs[0]として取得できます。
また、文字列はs[2]で取得できます。
s[0]が「+」でも「-」でもない(=空白)部分は変更がない文字列です。
この処理結果を使うことで、削除された文字列に取り消し線を、追加された文字列をハイライト表示させるといったことが可能になります。
それでは、文字列の差異を可視化するためのコードを追加していきます。
utils.pyに以下のコードを記載しましょう。
import openai
import difflib #追加
def highlight(word, identifier):
"""
質問文と回答文の差異を可視化する
追加された部分→ 背景を黄色&アンダーラインを引く
削除された部分→ 取り消し線を引く
"""
if identifier == '+':
return '<span class="lint-mark-warning">{}</span>'.format(word)
elif identifier == '-':
return '<span class="lint-mark-del">{}</span>'.format(word)
def mk_html(question, response):
#HTMLデータを作成する
html = ''
for difference_data in difflib.ndiff(question, response):
if difference_data[0]==' ':
html += difference_data[2]
elif difference_data[0]=='+':
html += highlight(difference_data[2], '+')
elif difference_data[0] == '-':
html += highlight(difference_data[2], '-')
return html
次にviews.pyのGrammarCorrectionViewを以下の通り修正します。
from django.shortcuts import render
from django.views import View
from .utils import chat_gpt, create_prompt, mk_html # mk_htmlを追加
# Create your views here.
class GrammarCorrectionView(View):
def get(self, request):
return render(request, 'proofreading/grammar_correction.html')
def post(self, request):
# 画面に入力した文章を取得
input_text= request.POST['input_text']
if input_text:
# Chat-GPTに投げる命令文を生成
prompt = create_prompt(input_text, "GrammarCorrection.txt")
# Chat-GPTへリクエストを投げる
response = chat_gpt(prompt)
# htmlデータを生成
response = mk_html(input_text,response) #追加
# 辞書型データを作成する
context = {'input_text': input_text,
'response': response,
}
# テンプレートにデータを渡す
return render(request, 'proofreading/grammar_correction.html', context)
else:
return render(request, 'proofreading/grammar_correction.html')
mk_html関数をインポートして、以下のコードをpostメソッドに追加しています。
# htmlデータを生成
response = mk_html(input_text,response) #追加
mk_html関数の引数に校正前の文章と校正後の文章を渡して、ハイライトするためのhtmlコードを生成します。
最後にgrammar_correction.htmlを以下の通り修正します。
{% extends './base.html' %}
{% load static %}
{% block content %}
<!-- ここから下を追加 -->
<style>
.lint-mark-warning {
padding: 0 0;
margin-bottom: 0.0rem;
border-bottom: 3px solid #E94C83;
font-weight: bold;
color:#443a3f;
background-color: #ffff66;
}
.lint-mark-del {
background-image: linear-gradient(#fe3464, #fe3464);
background-position: 0 50%;
background-size: 100% 2px;
background-repeat: repeat-x;
color: #888;
margin: 0 0.4em;
text-decoration: none;
}
</style>
<!-- ここまでを追加 -->
<section>
<h3>ChatGPTによる文章校正</h3>
<br />
<p>校正したい文章を以下に入力してください。<br>
<form method=POST action="{% url 'grammar_correction' %}">
{% csrf_token %}
<div class="mb-3">
<textarea style="border:4px solid rgb(118, 230, 13)" class="form-control"
placeholder="校正したい文章をここに入力" name="input_text" rows="5" cols="100"></textarea>
</div>
<br />
<button type="submit" class="btn btn-primary btn-sm">校正する</button>
</form>
<br />
</section>
<hr>
{% if response %}
<section>
<p><strong>入力した文章:</strong><br>
<div class="box">
{{input_text}}
</div><br>
<strong>ChatGPTによる校正結果:</strong><br>
<div class="box">
<!-- ここから下を修正 -->
{{response |safe |linebreaksbr}}
<!-- ここまでを修正 -->
</div>
<br><br>
</section>
{% endif %}
{% endblock %}
以下の部分で、追加された部分の背景を黄色&アンダーラインでハイライトするlint-mark-warningクラスを、削除された部分に取り消し線を追加するlint-mark-delクラスのcssを定義しています。
<style>
.lint-mark-warning {
padding: 0 0;
margin-bottom: 0.0rem;
border-bottom: 3px solid #E94C83;
font-weight: bold;
color:#443a3f;
background-color: #ffff66;
}
.lint-mark-del {
background-image: linear-gradient(#fe3464, #fe3464);
background-position: 0 50%;
background-size: 100% 2px;
background-repeat: repeat-x;
color: #888;
margin: 0 0.4em;
text-decoration: none;
}
</style>
以上で、以下の様に修正された箇所がわかりやすく表示されるようになります。
4.テキスト文から画像を生成する機能を実装する
冒頭で説明した通り、下図のようなロジックで画像生成機能を実装します。
Web画面上で生成したい画像を端的に表現した説明文を入力する
1で入力した説明文を元にChatGPTにより表現豊かな説明分を英語で生成させる。
2で生成した英語の説明文をStable Diffusion(画像生成AIモデル)に与え、画像を生成する。
利用するAIモデルについて
ここではテキストから画像を生成することが可能なStable DiffusionのMidjourney v4というモデルを使って実装します。
このモデルは特にAPI Keyの取得など不要でいきなり利用することができるので便利です。
AIモデルの詳細を理解しなくてもモデルの利用は簡単にできますので、ここではモデルの詳細については割愛します。
環境構築
まず最初に、Stable DiffusionのMidjourney v4を利用するための環境を構築します。
GPU環境のPCが望ましいですが、私自身もGPUを搭載したPCを保有していないため、WindowsのCPUで実行可能な環境を構築します。
※GPU環境とは一部手順が異なる部分があります。
以下のモジュールを追加でインストールしていきます。
Pytorch
Transformers
Diffusers
accelerate
まず最初に以下のコマンドを実行してpipツールをアップデートしておきます。
python -m pip install --upgrade pip setuptools
続いてWindows CPU版のpytorch をインストールします。
pip3 install torch torchvision torchaudio
続いて、Transformersのインストールを行います。
pip install transformers
一旦この時点で、Transformersの動作確認を行いましょう。
Djangoのシェルモードを起動後に、is以下のコードを実行して感情分析の結果が表示されればOKです。
python manage.py shell
from transformers import pipeline
classifier = pipeline('sentiment-analysis')
results = classifier(["We are very happy to show you the 🤗 Transformers library.","We hope you don't hate it."])
print(results)
以下のような結果が表示されればOKです。
[{'label': 'POSITIVE', 'score': 0.9997795224189758}, {'label': 'NEGATIVE', 'score': 0.5308623313903809}]
続いてDiffusersのインストールを行います。
pip install diffusers
2023/2/14時点では以下のようなバージョンがインストールされます。
(うまく動作しない場合はバージョンを揃えるとよいと思います)
>> pip freeze
aiohttp==3.8.4
aiosignal==1.3.1
asgiref==3.6.0
async-timeout==4.0.2
attrs==22.2.0
backports.zoneinfo==0.2.1
certifi==2022.12.7
charset-normalizer==3.0.1
colorama==0.4.6
diffusers==0.13.0
Django==4.1.7
filelock==3.9.0
frozenlist==1.3.3
huggingface-hub==0.12.1
idna==3.4
importlib-metadata==6.0.0
multidict==6.0.4
numpy==1.24.2
openai==0.26.5
packaging==23.0
Pillow==9.4.0
PyYAML==6.0
regex==2022.10.31
requests==2.28.2
sqlparse==0.4.3
tokenizers==0.13.2
torch==1.13.1
torchaudio==0.13.1
torchvision==0.14.1
tqdm==4.64.1
transformers==4.26.1
typing_extensions==4.5.0
tzdata==2022.7
urllib3==1.26.14
yarl==1.8.2
zipp==3.14.0
ここで、Midjourney v4 Diffusionの動作確認を行いましょう。
Midjourney v4 Diffusionはテキスト文から画像を生成できるモデルです。
以下のコードをDjangoのシェルモード上で実行して「"mdjrny-v4 style,a photo of an astronaut riding a horse on mars"」というテキスト文から画像が生成されることを確認します。
from diffusers import StableDiffusionPipeline
MODEL_ID = "prompthero/midjourney-v4-diffusion"
DEVICE = "cpu"
pipe = StableDiffusionPipeline.from_pretrained(MODEL_ID)
pipe = pipe.to(DEVICE)
prompt = "mdjrny-v4 style,a photo of an astronaut riding a horse on mars"
image = pipe(prompt).images[0]
image.save("astronaut_rides_horse.png")
以下のコードを実行すると画像生成処理が開始します。
image = pipe(prompt).images[0]
CPU環境だと10~20分程度はかかるのでしばらく終わるまで待機してください。
Djangoプロジェクトフォルダ直下に以下のような画像ファイル(astronaut_rides_horse.png)が生成されれば動作確認はOKです。
※生成される画像は都度異なるので、以下とは違う画像でも問題ありません。
※以下のようなエラーが表示された場合は、accelerateをインストールしましょう。
pip install accelerate
アプリケーションの作成
画像生成用のDjangoアプリ(image_gen)を新規に作成します。
python manage.py startapp image_gen
settings.pyのINSTALLED_APPSに以下を追加しておきます。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'proofreading.apps.ProofreadingConfig',
'image_gen.apps.ImageGenConfig', #追加
]
部品関数の定義
image_gen\utils.pyに以下のコードを定義します。
import openai
import os
from django.conf import settings
from diffusers import StableDiffusionPipeline
APK_KEY = "ここに自分のAPIキーを入力する"
MODEL_ID = "prompthero/midjourney-v4-diffusion"
DEVICE = "cpu"
def chat_gpt(prompt):
openai.api_key = APK_KEY #API KEYをセット
openai.Model.list() #OpenAIのインスタンスを生成
#APIを使ってリクエストを投げる
response = openai.Completion.create(
model = "text-davinci-003",
prompt= prompt,
temperature=0,
max_tokens=300,
top_p=1.0,
frequency_penalty=0.0,
presence_penalty=0.0
)
response = (response["choices"][0]["text"]).strip()
return response
def create_prompt(input_text, file_name):
prompt_file = os.path.join(settings.BASE_DIR, 'template', file_name)
with open(prompt_file, encoding="utf-8") as f:
file_read = f.read()
#Chat-GTPへ投げるフォーマットに入力文をセットする。
prompt = file_read.replace("[input]", input_text)
return prompt
def stable_diffusion(prompt):
pipe = StableDiffusionPipeline.from_pretrained(MODEL_ID)
pipe = pipe.to(DEVICE)
image = pipe(prompt).images[0]
return image
chat_gpt関数とcreate_prompt関数は文章校正で定義したものと同じ関数です。
新規に画像生成用のstable_diffusion関数を追加しています。
stable_diffusion関数の中身は、先ほどMidjourney v4 Diffusionを使って画像を生成した際の画像生成用のコードを記載しており、処理結果をimageとして返すようにしています。
また、template\CreateImage.txtを作成して以下の内容を記載しておきます。
【注意】
CreateImage.txtに記載するフォーマットは上記とは違っていてもOKです。
このフォーマットをいろいろ変更することでChatGPTが生成する画像を説明する文章が違ってきます。
その結果、生成される画像のタッチや精度も大きく変わってきますのでいろいろ試してみると面白いと思います。
ビューの定義
以下の処理を実行するCreateImageView関数を定義します。
Web画面から画像の説明文章を受け取る。
ChatGPTにインプットする文章を生成する。
ChatGPTから回答を取得する。
(1の文章を元により画像を生成するためのより表現豊かな説明分を英語で受け取る)3で受け取った文章をChatGPTで日本語に翻訳する。
3の英文をstable diffusionモデル与えて画像を生成する。
生成された画像データをバイナリデータに変換してテンプレートに渡す。
上記処理を実行するCreateImageView関数をimage_gen\views.pyに以下の通り定義しましょう。
from django.shortcuts import render
from django.views import View
from .utils import chat_gpt, create_prompt, stable_diffusion
import io, base64
from django.http import JsonResponse
class CreateImageView(View):
def get(self, request, *args, **kwargs):
return render(request, 'image_gen/create_image.html')
def post(self, request, *args, **kwargs):
input_sentence = request.POST.get('input_sentence', None) #画面で入力した説明文を取得
if input_sentence:
# Chat-GPTに投げる命令文を生成
prompt = create_prompt(input_sentence, "CreateImage.txt")
response = chat_gpt(prompt) # Chat-GPTから回答を取得する。
tranlate_sentence = "次の文章を日本語に翻訳してください。\n" + response
response_jp = chat_gpt(tranlate_sentence) # chat-gptで日本語に変換
image = stable_diffusion(response) # 画像生成処理を実行する。
# 画像データをエンコード
buffer = io.BytesIO()
image.save(buffer, format="PNG") # <class 'PIL.Image.Image'>
img_str = base64.b64encode(buffer.getvalue()) # <class 'bytes'> img_str= b'iVBORw~~~~
img_str = str(img_str)[2:-1] # img_strの先頭にある「b'」を除外する。
translated_img = 'data:' + 'image/jpeg' + ';base64,' + img_str
context = {
'input_sentence':input_sentence,
'response': response_jp,
'img_str' : translated_img,
}
return JsonResponse(context)
else:
return render(request, 'image_gen/create_image.html')
Viewクラスを継承してCreateImageViewクラスベースビューを定義します。
画像生成用ページを表示するためのgetメソッドと画像生成を行うためのpostメソッドを定義します。
画面上で入力した文章を以下のコードで取得します。
input_sentence = request.POST.get('input_sentence', None)
後ほど説明しますが、この入力文(input_sentence)はcreate_image.htmlのajaxのPostメソッドでビューに送られてきます(以下の部分)
var TextValue = document.getElementById("prompt");
$.ajax({
url: '{% url "create_image" %}',
type: 'POST',
data: {"input_sentence": TextValue.value},
}).
input_sentenceが空でない場合以下のコードを実行します。
# Chat-GPTに投げる命令文を生成
prompt = create_prompt(input_sentence, "CreateImage.txt")
response = chat_gpt(prompt) # Chat-GPTから回答を取得する。
tranlate_sentence = "次の文章を日本語に翻訳してください。\n" + response
response_jp = chat_gpt(tranlate_sentence) # chat-gptで日本語に変換
image = stable_diffusion(response) # 画像生成処理を実行する。
prompt = create_prompt(input_sentence, "CreateImage.txt")
上記コードで、事前に用意したCreateImage.txtの入力文の[input]を画面で入力された文章に置換します。
以下のコードでpromptをChatGPTに投げて画像を説明する文章の回答を取得します。
response = chat_gpt(prompt) # Chat-GPTから回答を取得する。
ChatGPTが回答した文章を日本語に翻訳して画面表示するため、以下でChatGPTを使って日本語に翻訳しておきます。
tranlate_sentence = "次の文章を日本語に翻訳してください。\n" + response
response_jp = chat_gpt(tranlate_sentence) # chat-gptで日本語に変換
以下で、ChatGPTが生成した説明文(英語)をStable Diffusionモデルに与えて画像を生成します。
image = stable_diffusion(response) # 画像生成処理を実行する。
imageに画像データが返されるので、image.save("ファイル名.png")とすれば画像ファイルを保存することができますが、ここでは画像データをWeb画面上に表示したいので、画像データをbase64エンコーディングしてHTMLに渡すようにします。
htmlに画像を埋め込む場合はimgタグのsrc属性の値に、エンコードした画像データを以下の様に指定すればOKです。
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAA~以下略~">
画像データをエンコードしているのが以下の部分です。
# 画像データをエンコード
buffer = io.BytesIO()
image.save(buffer, format="PNG") # <class 'PIL.Image.Image'>
img_str = base64.b64encode(buffer.getvalue()) # <class 'bytes'> img_str= b'iVBORw~~~~
img_str = str(img_str)[2:-1] # img_strの先頭にある「b'」を除外する。
translated_img = 'data:' + 'image/jpeg' + ';base64,' + img_str
最後に、テンプレートに渡したいデータをcontext変数に格納してテンプレートに渡すようにします。
context = {
'input_sentence':input_sentence,
'response': response_jp,
'img_str' : translated_img,
}
return JsonResponse(context)
URLパターンの定義
image_gen\urls.pyを新規に作成して以下のコードを定義します。
from django.urls import path
from .views import CreateImageView
urlpatterns = [
path('create_image', CreateImageView.as_view(), name="create_image"),
]
config\urls.pyを以下の通り修正します。
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('proofreading.urls')),
path('', include('image_gen.urls')), #追加
]
テンプレートの作成
image_gen\templates\image_genフォルダを作成して、フォルダ内に共通テンプレート(base.html)を作成して以下の通り定義します。
{% load static %}
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Demo Apps</title>
<link href="https://fonts.googleapis.com/css?family=M+PLUS+1p:400,500" rel="stylesheet">
<link href="{% static 'css/style.css' %}" rel="stylesheet">
<link href="{% static 'css/loading_style.css' %}" rel="stylesheet">
</head>
<body>
<header>
<!-- Ajax通信時にloading画面表示 start-->
<div id="overlay" class="loading">
<div class="spinner-wrapper">
<span class="spinner-text">Creating image ... </span>
<span class="spinner"></span>
</div>
</div>
<!-- Ajax通信時にloading画面表示 end -->
<div class="container">
<h1>Chat-GPTとStabule Diffusionを使った画像生成</h1>
</div><!-- /.container -->
</header>
<main>
<div class="container">
{% block content %}{% endblock %}
</div>
</section>
</main>
<footer>
<p>Django & Chat-GPT & Stabule Diffusion</p>
</footer>
</body>
</html>
また、create_image.htmlを作成して以下を定義します。
{% extends './base.html' %}
{% load static %}
{% block content %}
<section>
<p>生成したい画像のイメージ、簡単な説明文を1文で入力してください。<br>
CPU環境(Corei7 4コア16GBメモリ)で1実行あたり約25-30分程度かかります。</p>
<form method=POST id="imgForm">
{% csrf_token %}
<div class="mb-3">
<input id="prompt" class="textbox"
placeholder="ここに作成したい画像の文章を入力">
</div>
<br />
<button type="submit" class="btn btn-sm btn-primary">生成する</button>
</form>
<br />
</section>
<section>
<hr>
<!-- 画像表示 -->
<p><strong>入力文:</strong><br>
<div class="box">
<p class="text_height" id="input_sentence">{{input_sentence}}</p>
</div><br>
<strong>ChatGPTが生成した説明文:</strong><br>
<div class="box">
<p class="text_height" id="gpt-response">{{response}}</p>
</div>
<br><br>
<table cellpadding="5">
<tr>
<strong>生成された画像:</strong><br>
<td class="box6" style="text-align: center"><img id="img1" width="490px" height="490px" ></td>
</tr>
</table>
</section>
<script src="http://code.jquery.com/jquery-3.2.1.min.js"></script>
<!-- javascript -->
<!-- セキュリティ対応-->
<script>
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
// セキュリティ対応
// 画像生成処理の実行(生成するボタンをクリック時に呼ばれる)
//Ajax通信中にローディングを表示
$('#imgForm').on('submit', e => {
// デフォルトのイベントをキャンセルし、ページ遷移しないように!
e.preventDefault();
$(document).ajaxSend(function() {
$("#overlay").fadeIn(300);
});
var TextValue = document.getElementById("prompt");
$.ajax({
url: '{% url "create_image" %}',
type: 'POST',
data: {"input_sentence": TextValue.value},
}).done( response => {
$("#img1").attr("src", response.img_str); //画像データをimgタグのsrc属性にセット
$("#input_sentence").text(response.input_sentence);
$("#gpt-response").text(response.response);
$("#prompt").val('');
setTimeout(function(){
$("#overlay").fadeOut(300);
},500);
})
.fail((ata, textStatus, xhr) => {
alert(xhr);
});
});
</script>
{% endblock %}
テキスト入力フォームを以下で定義します。
<form method=POST id="imgForm">
{% csrf_token %}
<div class="mb-3">
<input id="prompt" class="textbox"
placeholder="ここに作成したい画像の文章を入力">
</div>
<br />
<button type="submit" class="btn btn-sm btn-primary">生成する</button>
</form>
「生成する」ボタンを押すと、以下のajaxが実行されて、id="promptのinputタグで入力された文字列を取得して、URLパターン(create_image)に対して入力文章(TextValue.value)をデータとして渡してpostリクエストを実行します。
$.ajax({
url: '{% url "create_image" %}',
type: 'POST',
data: {"prompt": TextValue.value},
})
処理結果を以下で表示します。
<!-- 画像表示 -->
<p><strong>入力文:</strong><br>
<div class="box">
<p class="text_height" id="input_sentence">{{input_sentence}}</p>
</div><br>
<strong>ChatGPTが生成した説明文:</strong><br>
<div class="box">
<p class="text_height" id="gpt-response">{{response}}</p>
</div>
<br><br>
<table cellpadding="5">
<tr>
<strong>生成された画像:</strong><br>
<td class="box6" style="text-align: center"><img id="img1" width="490px" height="490px" ></td>
</tr>
</table>
入力した文章は{{input_sentence}}で、ChatGPTが生成した画像の説明文を日本語に翻訳した文章を{{response}}で表示します。
また、生成された画像は以下のimgタグで表示されます。
<img id="img1" width="490px" height="490px" >
CreateImageViewのpostメソッド画像生成が完了して以下のreturnが実行されると、テンプレート側に非同期処理で処理結果が渡されます。
return JsonResponse(context)
その際、以下のajax処理のdoneメソッド以下が実行されます。
// 画像生成処理の実行(生成するボタンをクリック時に呼ばれる)
//Ajax通信中にローディングを表示
$('#imgForm').on('submit', e => {
// デフォルトのイベントをキャンセルし、ページ遷移しないように!
e.preventDefault();
$(document).ajaxSend(function() {
$("#overlay").fadeIn(300);
});
var TextValue = document.getElementById("prompt");
$.ajax({
url: '{% url "create_image" %}',
type: 'POST',
data: {"prompt": TextValue.value},
}).done( response => {
$("#img1").attr("src", response.img_str); //画像データをimgタグのsrc属性にセット
$("#input_sentence").text(response.input_sentence);
$("#gpt-response").text(response.response);
$("#prompt").val('');
setTimeout(function(){
$("#overlay").fadeOut(300);
},500);
})
.fail((ata, textStatus, xhr) => {
alert(xhr);
});
});
以下の部分で、それぞれ画像データ、入力文章、ChatGPTが生成した文章をHTMLに差し込んで結果を画面に表示させます。
$("#img1").attr("src", response.img_str); //画像データをimgタグのsrc属性にセット
$("#input_sentence").text(response.input_sentence);
$("#gpt-response").text(response.response);
静的ファイルの配置
リポジトリからstatic\cssフォルダをimage_gen\staticにコピーしておきましょう。
画面デザインを整えるためのcssファイルですが、デザインに関する部分なので説明は割愛します。
動作確認
http://127.0.0.1:8000/create_imageにアクセスすると以下のような画面が表示されるので、黄色いテキストボックス欄に生成したい画像の説明分を入力して「生成する」ボタンを押してみましょう。
画像の生成処理が始まると、以下の通りローディング画面が表示されます。
1実行あたり20分前後かかると思います。
コンソール画面を見ると、以下の様に画像生成の進捗率が表示されるので、左側のパーセンテージが100%になるまで待ちましょう。
処理が終わると、以下の様に、入力文、ChatGPTが生成した画像の説明文とそれを元に生成された画像が画面に表示されます。
以上で、本レシピは完了です。
「参考になった。面白かった。」と思った方はぜひSNS等でシェアいただけると嬉しいです!
主にITテクノロジー系に興味があります。 【現在興味があるもの】 python、Django,統計学、機械学習、ディープラーニングなど。 技術系ブログもやってます。 https://sinyblog.com/