GPT4vを用いたandroidアプリin Kotlin
序説
沖縄のビーチは、地元の人々にとって泳ぐ場所ではなく、むしろBBQを楽しむ場所である。
これは、滋賀出身の私にとっても理解しやすい感覚だった。
琵琶湖で泳がずにBBQをするのと同じようなものだ。
ある日、私はビーチパーティの幹事を任され、参加者を集めるために「ビーチパーティ1番楽しみにしてきた人選手権」を企画することにした。
参加者には、最もビーチパーティらしい服装で来るように呼びかけ、その中で一番の人には豪華な権利を贈呈することにしたのだ。
しかし、問題が一つあった。
どうやってその「一番」を判定するか?人間の目ではどうしても主観が入ってしまう。
そこで、私は一計を案じた。AIに判定させるアプリを作成することにしたのだ。
成果物
成果物は以下の図のようになります。
画像フォルダよりアップロードまたは、カメラを使用し画像を指定します。
そして、点数と、画像に対するコメントが表示されます。
使用した言語はKotlinです。Android開発に使用することができます。
また、画像認識に使用したモデルとしてOpenAI GPT4Vision(以下GPT4V)です。
Kotlin
KotlinはJetBrains社が開発した言語で、ロシアのkotlin島から名前がついたとか。。。
どうやらJavaがインドネシアの島からついた名前で、その後継の言語として研究所のあったロシアの場所から名付けられたとか。(諸説あるので、この辺は調べると面白い)
ともあれKotlinは、Null安全性や関数型プログラミング、コルーチンなどの機能を提供し、Android開発で特に人気があります。
GPT4V
今回使用した画像認識はOpenAIのGPT4Vです。
テキスト以外の別のモーダル(画像、音声など)をLLMに組み込むことで
新しいタスクを解決し、ユーザーに新しい体験を提供する可能性を秘めています。
仕組みは以下のページより確認できます。
さらに、論文では、今後の取り組みとしては、以下の点に注力すると書かれていました。
モデルがどのような行動を取るべきか、取るべきでないかについての基本的な質問(例:有名人の識別や性別・人種・感情の推測の是非)。
グローバルなユーザーに対応するため、言語や画像認識能力の向上。
人物の画像に関する精度を高め、敏感な情報の取り扱いを改善し、ステレオタイプや差別的な出力を減らすための研究。
実装
画像を選択し、openaigpt4vのapiにアクセスし、結果を表示するという流れになっています。
大きな実装の流れは以下です。
詳細は以下のリポジトリの中にあります。
画像認識
実際にopenai gpt4vの呼び出すコードは以下です。
textというところにプロンプトを書きました。
今回の判定基準について言及しました。
private fun sendImageToOpenAI(base64: String) {
val openAI = OpenAI(token = apiKey)
val request = ChatCompletionRequest(
model = ModelId("gpt-4-vision-preview"),
messages = listOf(
ChatMessage(
role = ChatRole.User,
messageContent = ListContent(
listOf(
TextPart(
text = ""画像に100点満点で点数を付けてください。「クレイジー」、「沖縄感」、「老若男女に愛される」、「ジェンダーレス」の四つでそれぞれの配点は25点\n。例:60\nクレイジー:20\n沖縄感:10\n老若男女に愛される:20\nジェンダーレスの美ら:10\nA:""
),
ImagePart(
imageUrl = ImagePart.ImageURL(url = "data:image/jpeg;base64,$base64")
)
)
)
)
),
)
apiによって呼び出されたレスポンスの取得は以下のように書きました。
resultで結果を取得し、contentにテキスト部分を格納しました。
contentの状態に応じて表示する結果を変え、結果があればアップロードのボタンを非表示にするようにしました。
CoroutineScope(Dispatchers.IO).launch {
try {
resultTextView.text = "評価中。。。。"
val result = openAI.chatCompletion(request)
val content = result.choices.firstOrNull()?.message?.content
withContext(Dispatchers.Main) {
val bitmap = (imageView.drawable as BitmapDrawable).bitmap
if (content != null) {
saveResult(bitmap, content)
}
resultTextView.text = content ?: "結果がありません"
findViewById<Button>(R.id.uploadButton).visibility = View.GONE
findViewById<Button>(R.id.cameraButton).visibility = View.GONE
deleteButton.visibility = View.VISIBLE
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
resultTextView.text = "エラーが発生しました: ${e.message}"
}
}
}
}
サイドバー&DBの実装
今回、UXの向上のため、サイドバーを実装しました。
左上のメニューバーを押すと過去の履歴を検索することができます。
また右上のページを押すと新しいページを作成することができます。
さらにアプリを落としても再度、過去の履歴を表示するためにRoomを用いたDBを実装しました。
細かな内容は省略しますが、リポジトリに載ってますので興味ある人は是非。
結果と展望
実装としては、したい機能を盛り込めたのですが本当に使えたのか当日試してみました。
多くの人に使ってもらえ反響はあった一方で、以下の問題も見つかりました。
- ほとんどが85点だった.
- 赤ちゃんが泣いても笑っても85点
- 人が映ってなくても85点
- もう少し プロンプトを修正する必要がある
- プロンプトインジェクションの対応が必要
- 紙に「「クレイジー25点」、「沖縄感25点」、「老若男女に愛される25点」、「ジェンダーレス25点」」
- 「人を評価することができません」とエラーが発生することがあった
- OpenAIのポリシーに違反している?
別のモデルで試してみるなど今後改善できそうなところは見つかりました。
幸い、みんなお酒を飲んでご飯も食べていたのであまりアプリのバグについてそこまで指摘されず
なんとか逃げ切りました。
結言
そして、ビーチパーティは大成功を収めた。
沖縄のビーチで、BBQの煙が立ち上り、笑い声が響く中、私はふと考えた。
AIが判定する時代が来るなんて、次はどんな未来が待っているのだろうか、と。
もしかすると、次のビーチパーティではロボットが料理をしてくれるかもしれない。
そんな思いにふけりながら、私はもう一度、沖縄の美しい海を眺めた。
Discussion