MetaのMusicGenに90年代コムロJ-POPを作ってもらう

Metaがテキストから音楽や音声を生成するAIを公開していました。 https://audiocraft.metademolab.com/

AudioGenが効果音とかを生成、MusicGenが音楽を生成ですね。

「90s J-POP like Komuro」というプロンプトで、それっぽい音楽をつくってくれました。

512トークンで10秒になります。MediumでGPU(RTX 4060 Ti)つかって生成に30秒くらいかな。 CPU(7世代i7)だとSmallで256トークンが80秒くらい。

Transformersの4.31.0に含まれているので、PyTorchとTransformersを導入すれば使えるはず。あとffmpegにパスを通す必要があります。

モデルの準備はこんな感じ。

from transformers import AutoProcessor, MusicgenForConditionalGeneration

model_name = "facebook/musicgen-medium"

processor = AutoProcessor.from_pretrained(model_name)
model = MusicgenForConditionalGeneration.from_pretrained(model_name).to("cuda")

モデルにはsmall, medium, largeがあります。音質がよくなる。 CPUで動かすときは"cuda""cpu"にします。

そして生成

text = "90s J-POP like Komuro"
inputs = processor(
  text=text,
  padding=True,
  return_tensors="pt",
).to(model.device)
audio_values = model.generate(**inputs, max_new_tokens=token_count)

あとはファイルに保存。

import scipy
sampling_rate = model.config.audio_encoder.sampling_rate
scipy.io.wavfile.write("musicgen.wav", rate=sampling_rate, data=audio_values.cpu().numpy())

GradioのUIを作ったソースはこちら
https://gist.github.com/kishida/10ca95df2b6566c0acfdbcd59dde2abd

コミュニティノートがTwitterを壊している

コミュニティノート、案の定暴走している。
どんな改悪、利用制限よりも大きくTwitter*1を壊してるんじゃなかろうか。

※ 2024/3/12追記 コミュニティノートの、「追加の背景情報が必要ない理由を説明するノート」がうまくまわって、初期に見られた正義の暴走のようなノートは表示されないようになってきています。

コミュニティノートは、多数派に有利な仕組みです。 「コミュニティノートでは、さまざまな視点を持つユーザーにとって役に立つノートが特定されます」 となっていますが、多数派であればさまざまな視点を持つユーザーが確保しやすく、逆に少数派は視点が収束する傾向があるので不利になります。

そのため、なんらかの不満をもっているけどその不満を表明して言葉にするとだいたい間違っているという層には非常に居づらくなっています。

「間違ったツイートをしなければいい」のような発言をみかけるけど、裏を返せば「間違ったツイートをしたならばなにをされても仕方ない」ということにもなっていて、正義であればなにをしても許されるというような「正義の暴走」になっているように見えます。 特に間違っていないのに、単に反対意見の表明がついていることもありますが、多数派にとって心地いいのでそのまま掲載されたりします。
嫌われ者が叩かれているのをみて留飲をさげるような人にとってはいい機能かもしれないけど、それもまた正義が暴走しています。

そうすると、そういった少数派はTwitterから離れて、ThreadsやBlue Skyなど対抗SNSに移行していくと思います。そんな感じで、分断が強くなるんではないかと。

そして、こういったリプライで十分な情報にもコミュニティノートがついていたりします。役にたつかもしれないけど、ツイートの面白さは失われています。

コミュニティノートは、つけられた側からすると間違いや無知がさらされツイートの価値を貶めるものになってしまっています。コントロールもできずツイートを訂正できるわけでもないので、うれしくない。
そして、見る側からも特にうれしくないものも増えています。
一方で、コミュニティノートを書く側としては、自分の文章が有名ツイートにぶらさがって、そしてほとんどの場合はツイートよりも面積をとって目立つようになり、多くの人が話題にするので、自己顕示欲がみたされます。
いままで見たなかで、手軽に最も承認欲求が満たされるツールになっているように思います。YouTubeで炎上動画をとっても、他の人が同じようなことをすると埋もれていきますが、コミュニティノートは有名ツイートにのっかる形なので埋もれることはありません。 おそらく、コミュニティノート承認欲求お化けのような人を生産してるはず。

少数派がTwitterから離れるというのをみて、「嫌われ者は出ていけばええんや」みたいなことを思った人がいるかもしれませんが、コミュニティノートというハンマーを手にした承認欲求お化けにとってすべてのツイートは釘に見えるようになっているので、その時点での少数派に矛先が向かっていくのではないかと思います。

あと、コミュニティノートはある程度バズらないと対象にならないので、有名人ほどノートがつきやすいのだけど、つまり有名人ほどコミュニティノートに嫌気さして抜けていく確率が高くなってしまう。
有名人いなくなれば、それを目当てに入ってくる人もいなくなるので、代謝も悪くなっていく。

こうやってTwitterが本格的に壊れる未来がみえてしまった・・・

こういう話は伝わらない人には伝わらないと思うけど、とりあえず倫理学の入門書をおいておきます。

※ 9/4追記 コミュニティノートに関する調査研究がありますね。
効果がでたという証拠は見つけられず、最も拡散される初期段階に対処するには遅すぎることを示唆、という結果。
https://arxiv.org/abs/2307.07960

*1:TwitterだったサービスをそのままTwitterと表記します

rinnaの画像対話モデルをUIで試す。あとGradioの練習。

おうちで日本語で画像を使った会話ができるようになりましたよ、ということで、試してみます。

rinna 画像対話モデル

おとといrinnaから日英バイリンガルモデルが発表されました。
rinna、日英バイリンガル大規模言語モデルをオープンソースで公開|rinna株式会社

このモデルには画像対話モデルも含まれています。つまり、画像についてrinnaとチャットができるようになっています。
https://huggingface.co/rinna/bilingual-gpt-neox-4b-minigpt4

この画像対話モデルは、MiniGPT-4をベースにしています。
https://github.com/Vision-CAIR/MiniGPT-4

MiniGPT-4のVicuna + BLIP-2をrinna 3.6b + BLIP-2にした感じ。
Japanese MiniGPT-4: rinna 3.6bとBLIP-2を組み合わせてマルチモーダルチャットのモデルを作る

動かすとこんな感じ。大きく外れてはないな、という返答です。あと、簡潔。

メモリは11.4GBくらい使うので、12GB GPUだときつそう。

導入

導入として公式で書いてある手順はこんな感じ。MiniGPT-4をGitHubからcloneして22d8888ブランチを使う、と。

git clone https://github.com/Vision-CAIR/MiniGPT-4.git
cd ./MiniGPT-4
git checkout 22d8888 # latest version as of July 31, 2023.

そして、rinna用にカスタマイズしたMiniGPT-4のスクリプトとBLIP2-LLMのcheckpoint.pthをダウンロード。

wget https://huggingface.co/rinna/bilingual-gpt-neox-4b-minigpt4/resolve/main/customized_mini_gpt4.py
wget https://huggingface.co/rinna/bilingual-gpt-neox-4b-minigpt4/resolve/main/checkpoint.pth

Windowsの場合はwgetを使うにはPowerShellを使うのだけど、オプションなしだと単にネットからデータを読むだけになってしまうので-OutFileを付けて保存先を指定する。
そうすると「書き込んでいます」が出る。「読み込んでいます」だと読み込むだけになる。

あとはCUDAとかPyTorchとかTransformersとかsentencepieceとか、rinna 3.6bが動く状態を作っておく。

bitsandbytesを使っているのだけど、Windowsの場合はpipからインストールしても使えないので、こちらを。
https://github.com/jllllll/bitsandbytes-windows-webui

0.39.1を使っていますが、新しいものでも大丈夫だと思う。

python -m pip install bitsandbytes==0.39.1 --prefer-binary --extra-index-url=https://jllllll.github.io/bitsandbytes-windows-webui

あと、いままで使ってなかったものとして、omegaconf、iopath、timm、webdataset、decordのインストールが必要だった。

動かす

そしたらHow to use the model のところのコードが動かせるはず。
No module named 'decord'のようなエラーが出たらpip installしていく。

7.74GBのモデルのダウンロードなどがあったあと、こんな感じで表示される。

Loading VIT
freeze vision encoder
Loading VIT Done
Loading Q-Former
freeze Qformer
Loading Q-Former Done
Loading LLM
Loading LLM Done
Load BLIP2-LLM Checkpoint: ./checkpoint.pth
ユーザー: <Img><ImageHere></Img> What can you see?
システム: a cat on a table with a laptop
ユーザー: 猫はどんな体勢をしていますか?
システム:
寝ています

コードを見る

モデルをCustomizedMiniGPT4として定義してますね。

model = CustomizedMiniGPT4(gpt_neox_model="rinna/bilingual-gpt-neox-4b")
tokenizer = model.gpt_neox_tokenizer

そしてBIP2-LLMを読み込み

ckpt = torch.load(ckpt_path, map_location="cpu")
model.load_state_dict(ckpt['model'], strict=False)

会話に使う画像を読み込み。

image_url = "https://huggingface.co/rinna/bilingual-gpt-neox-4b-minigpt4/resolve/main/sample.jpg"
raw_image = Image.open(requests.get(image_url, stream=True).raw).convert('RGB')

そしてBLIP2-LLMを使って埋め込みベクトルに変換

vis_processor = Blip2ImageEvalProcessor()
image = vis_processor(raw_image).unsqueeze(0).to(model.device)
image_emb = model.encode_img(image)

プロンプトと画像埋め込みを結合した埋め込みベクトルを取得

embs = model.get_context_emb(prompt, [image_emb])

で、生成

output_ids = model.gpt_neox_model.generate(
    inputs_embeds=embs,
    max_new_tokens=512,
    do_sample=True,
    temperature=1.0,
    top_p=0.85,
    pad_token_id=tokenizer.pad_token_id,
    bos_token_id=tokenizer.bos_token_id,
    eos_token_id=tokenizer.eos_token_id
)

output = tokenizer.decode(output_ids.tolist()[0], skip_special_tokens=True)

UIをつける

いろいろ試したいのでGradioでUIを作る

with gr.Blocks() as demo:
  gr.Markdown("## multi modal rinna")
  imgIn = gr.Image(lambda: raw_image, type="pil")
  with gr.Row():
    upload = gr.Button("Upload", variant="primary")
    def_img = gr.Button("Default")
    upload.click(load_image, inputs=imgIn)
    def_img.click(init_image, outputs=imgIn)
  with gr.Row():
    with gr.Column():
      question = gr.Textbox(lines=3, placeholder="質問を")
      submit = gr.Button("Submit", variant="primary")
      with gr.Row():
        default = gr.Button("Default")
        clear = gr.Button("Clear")
        default.click(lambda: "画像を説明して", outputs=question)
        clear.click(lambda: "", outputs=question)
    answer = gr.Textbox(lines=3)
    submit.click(generate, inputs=question, outputs=answer)

demo.launch()

そしたら冒頭のUIができる。画面を横分割して画像を左側でもよかったな。

ソース全体はこちら。動かすとき、画像をD&DしただけではLLMに送られていないので気をつけてください。Uploadボタンを押す必要があります。
https://gist.github.com/kishida/ee107e002546ce2ab26c5d9a6eac33b1

分厚く難しそうな技術書を素敵だと思ったら今必要なそうでも買っておくべき

鈍器が届いたんですよ。
カード履歴みてたら昨日13,200円の見覚えのないAmazonからの請求があって、「本でもなさそうだしAmazon見ても履歴ないし謎い」とか思ったら宅急便がきて。。。

まあ、注文してた「コンパクトデータ構造」が来てたわけだけど。
asin406512476X:detail

予約したのが6/16で直近の履歴を見てもなくて、そして今日が発売日なので昨日決済されて発送された、ってことらしい。

紙が固めでめくりやすい。というか、めくるのが楽しい。変なデータ構造がいっぱい載ってるのもよい。図も多いので、絵本みたい。内容的には読むの大変そうだけど、落ち着いてゆっくり読めば大丈夫そう。

内容としては、圧縮した状態でいろいろ操作ができるデータ構造を考えようぜ、という感じ。
Wikipediaだと「簡潔データ構造」の項目を見ると載ってる。

と思ったら、こういう本があった。

そして買っていた?

しかし家を探してもデータ化したフォルダ探しても見当たらない。記憶もないな。で、購入履歴を確認したら、だれかの欲し芋で送ってたっぽい。

ところで本題。
こういう分厚くて難しそうで素敵な本は、読むかなーどうかなーと悩むのなら買っておくほうがいい。
というのも、だいたいすぐ絶版して、中古も値上がりして気軽に読めないになる。

例えば「コンピュータプログラミングの概念・技法・モデル」はプログラミング言語の機能をいろいろ分類した本で、オブジェクト指向言語とはどういう位置づけになるのかという話をするときに確認してもらいたいんだけど、いま中古で4万円。

アルゴリズムデザイン」も一時絶版状態で、12万とかで売られてるのを見た。
いまは在庫復活して17,000+税になっているけど、買ったときは15,000+税だったな。Amazonの書影の裏表紙見るとわかります。
これも広範囲に網羅されてるし読みやすいし、同じ内容を埋めようとして4000円くらいの本を5冊買うとかを考えると割にあう。

まあ、そんな感じで、よさげと思った本は買っておけ、という話ですね。

実はサーブレットの勉強もSpring Bootを使うほうが楽なのでは

サーブレットで面倒なのはTomcatとの付き合いです。
Spring BootでSpring Webを使うと組み込みTomcatが動くので、Tomcatのことを気にしなくてよくなりますね。
そこでサーブレット動かすと勉強しやすいんでは、と思ったので試してみます。

まず、spring initializrでSpring Webを追加したプロジェクトを作ります。
https://start.spring.io/

このリンクから、設定済みのspring initializrを開けます。Mavenにしているので、Gradleがいい人は選択しなおしてください。
https://start.spring.io/#!type=maven-project&language=java&platformVersion=3.1.1&packaging=jar&jvmVersion=17&groupId=com.example&artifactId=demo&name=ServletDemo&description=Servlet%20Demo%20project%20for%20Spring%20Boot&packageName=com.example.servletdemo&dependencies=web

[GENERATE]ボタンを押してダウンロードしたzipを解凍します。こんな感じになっているはず。

アプリケーションクラスに @ServletComponentScan を付けます。

package com.example.servletdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@ServletComponentScan
public class ServletDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServletDemoApplication.class, args);
    }

}

そしてサーブレットクラスを定義します。
サーブレットのパッケージがjakartaになってるのは感慨深さ。

package com.example.servletdemo;

import java.io.IOException;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = "/hello")
public class SampleServlet extends HttpServlet{

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.getWriter().println("""
            <h1>Hello Servlet</h1>
            Hello!
            """);
    }
    
}

アプリケーションクラスを実行させると、Spring Bootが起動します。

http://localhost:8080/hello にアクセスすると・・・

できました!
これでいいのでは。オートリロードなども使えると思うので、かなり楽ができそう。

ただし、JSPを動かそうとすると結構めんどいので、それなら最初からJakartaEEを使うほうがいいかも。これもアプリサーバーのインストールや起動、デプロイなどをやらなくていいようになっています。
起動やアクセスポートはランタイムごとに違うので、ダウンロード時についてくるreadmeを確認する必要があります。
https://start.jakarta.ee/

サーブレットを「JavaでのWebアプリケーションの基礎」として最初に勉強させるのをやめてあげてほしい

研修がはじまるという画像でサーブレットJSPの本が並んでて、サーブレットを最初に勉強させるのをやめてあげてほしいと思った話。
オブジェクト指向もそうなんだけど、現状で使わなくなっているにもかかわらず情報更新がされずオブジェクト指向サーブレットJSPが教えられ続け本が売り続けられるという現状がある。
でももうさすがに変わってほしさ。
ただ、JSPはそこまで悪くないので、サーブレットで話を進める。(ただし、サーブレットが動かない環境ではJSPは動かない)

使われていない

まず、いまの案件の多くがSpring / Spring Bootになってて、サーブレットをさわるということは少ない。

2020年のJetBrainsの調査ではこんな感じ
https://blog.jetbrains.com/ja/idea/2020/10/a-picture-of-java-in-2020-ja/

2021年のフリーランス案件だけど、日本でこういう状況
Springというのも実際はSpring Bootってことも多いんじゃなかろうか。

https://freelance-start.com/articles/68

もちろんサーブレットを使い続けるシステムの保守案件はある。
けど、逆にサーブレットだけを勉強することでそういう保守案件に優先してまわされるようになるというのも、それはそれで・・・

自社のシステムがサーブレットJSPだから必要というのであれば、それは「JavaでのWebアプリケーションの基礎」としてではなく「自社業務のための必須知識」として教えているのでいいと思うのだけど、職業訓練として「Web開発の知識が必要だよね」というのでサーブレットJSPをやって行き先がレガシー案件優先となるのはちょっと・・・

サーブレットでのWebアプリケーションの学習は効率が悪い

最初に勉強するべきは「Webアプリケーションとはどういうものか」のはず。
そうすると、サーブレットの場合はサーブレット固有の仕様を知ることやTomcatのお守りをすることに時間をとられすぎて効率が悪い。

とくに講習の場合は時間が限られていて、そして15人から20人くらいいると、だれかが変なところではまって止まるということも少なくない。

※ 追記 7/16 もしサーブレットの勉強をするにしても、Spring Bootを使うほうが楽かも。
実はサーブレットの勉強もSpring Bootを使うほうが楽なのでは - きしだのHatena

JavaのWebアプリケーションの基礎」ではない

誤解されがちなのが、サーブレットJavaのWebアプリケーションの基礎というところ。
たしかにStrutsなんかはサーブレットのラッパだし、Springも多くの場合はサーブレット上で動いている。
でも、QuarkusやMicronautなど新しいフレームワークサーブレットを使わない。Spring BootでもWebFluxを使えばサーブレットに依存しない。

「でもJava標準の場合は・・・」という人もいるかもしれないけど、Jakarta EE 10でCore Profileというのができて、ここからはサーブレットJSPは外れている。
https://jakarta.ee/release/10/

そもそもJakarta EEはJava標準なのか、という話もあるけど、それは置いておいて。

そして、MicroProfileではCore Profileに依存しているので、ここでもサーブレットJSPは含まれていない。
https://microprofile.io/

Quarkusは以前はMicroProfileに準拠していたけど、いまは違うようだ
Quarkusも対応していて、Compatible Implementationに載るのが遅れているだけっぽい。

Spring BootをSpring Webで使ってサーブレットに依存しても、実際にサーブレットを気にする場面もほとんどなくなっている。
もちろんサーブレットが必要になる場面はあるけど、研修でそういうエッジケースに時間をかけても忘れるし、普通にWebアプリが作れるようになるために時間をかけたほうがいいと思う。

まあ、サーブレットJavaのWebアプリケーションの基礎ではない、という話。
※ HttpServletRequestやFilterやListenerが必須ではないという話です。依存しないというのは、そもそもHttpServletRequestがないということです。また、それが必須であると勘違いさせてしまうのもよくないんではないかと。

サーブレットとはなんだったのか

サーブレットは、サーバーの小さい断片という感じの意味でつけられた名前ですね。そしてここでは「何のサーバーか」ということは指定されてません。
実際に使うのはHttpServletだけど、これはGenericServletを継承していて、JavaDocには「プロトコルに依存しないサーブレットを定義します」と書いてあります。
そう、サーブレットプロトコルに依存しないのです。なので、初期にはFtpServletだったりSmtpServletだったりいろいろなプロトコルに対応するサーブレットを作る人がいましたね。
ただ、そういうプロトコル非依存の仕組みなので、HTTPを扱うには煩雑すぎたりします。ひとつのエンドポイントにひとつのクラスというところからして面倒ですね。なので、何がプロトコルの話で何がサーブレットの仕組みの話なのかわかりにくくなっていて、プロトコルの理解もやりにくいです。だから、サーブレット自体がフレームワークなのに、使いにくすぎるのでその上にさらにフレームワークが必要ということになってしまってます。
そういう意味では、「Webアプリの基盤」として勉強するにはあまり適していないといえます。

Java EEとはなんだったのか

サーブレットを動かすにはサーブレットコンテナが必要、そして代表的なサーブレットコンテナがTomcatですね。
コンテナ?そうコンテナなんです。
Java EEは、いまでいうDockerやKubernetesのようなことをやりたかった技術で、単にWebフレームワークだけの仕様じゃありません。
なので、JNDIのようなネーミングサービスがあったりするわけです。リリースされなかったけど、GlassFishではサーバーの伸縮を実装しようともしてました。
つまり、warファイルはDockerイメージのようなもので、TomcatはDockerサーバーのようなもの。 ということで、Tomcatやwarファイルの作成やweb.xmlやデプロイみたいな話は、Webアプリケーションを勉強したいたけなのにDockerまわりまで勉強しないといけなくなってめんどい、というのと似た状況になっていて、「効率が悪い」という話につながるのです。
いまはコンテナ機能はDockerにまかせてWebフレームワークはWebのことだけ考えればいいという感じになっていますね。
なので、JEEの仕様にはメッセージングや認証、セキュリティ機能もあったりするけど、そういうのはサイドカーなどにまかせるので不要になって、Dockerがある前提で必要なものを組み立てなおしたらMicroProfileになったという感じです。そして、MicroProfileで使ってるものをJEEから選んだものがJEE Core Profileですね。
MicroProfile、ぜんぜんMicroじゃないじゃんって思うけど、これはマイクロサービス用プロファイルという意味なのです。わかりにくいね。

おすすめ書籍は?

いまJavaフレームワークを勉強するならSpring Boot一択なわけですが、じゃあ本は?となりますね。
QuarkusとかMicronautとか最初に勉強すると素敵と思うのだけど、教本が皆無。
で、最近、Springのいい本が出てますね。

ただ、前提知識として「Java SE・・・を使ったWebアプリケーションを作成またはソースコードを解読できる」「SQLを使って簡単なSELECT・UPDATE・INSERT・DELETE文を記述できる」とあるので、一冊でJavaの基本からSpring Bootの基本まで勉強できる「プロになるJava」を読んでおくとよさげ。
「Springから始めるとHTTPが・・・」という人のために、SocketでWebサーバー・クライアント作ったあとでSpring Bootに入っているので安心です。

あと、Springの仕組みを知りたい場合は、このあたりを読むといいと思います。
作って理解するDIコンテナ - きしだのHatena
作って理解するWebフレームワーク - きしだのHatena

ChatGPTなどLLMを使わずに自然言語でツールを操作する

OpenAIのFunction Callingが出たときに、GPTを使って自然言語でツールを操作するというのをやったんだけど、この程度にGPT使う必要なくない?という感じもしたので、GPTなどLLM使わずに実装してみました。
LLM使わずに実現できることはLLM使わないほうがよさげ。

前回のブログ、これです。

こんな感じで動くようになっています。

GPT4使ったときはこんな感じ

今回は格フレームという考え方をベースに実装してます。
格フレームは、アプリケーションから利用する観点だと、「動詞が決まったら単語の役割きまるよね」というものです。
例えば「ぼく」「うどん」「博多駅」「食べる」という単語があれば、文法構造なくても「ぼくは博多駅でうどんを食べる」のような意味が浮かぶわけで、主語になりそうな単語だとか場所になりそうな単語とかは決まるよね、みたいな感じ。
ちゃんとした説明は、放送大学の自然言語処理の教科書などを見てください。

今回は位置サイズを変えるか色を変えるかという動作があるので、対応するインタフェースを用意します。

    interface GeometoryCommand {
        void execute(FunctionApiSample.GraphObject obj, Degree degree, Dimension fieldSize);
    }
    interface ColorCommand {
        void execute(FunctionApiSample.GraphObject obj, String color);
    }

あとはそれらの操作と操作対象オブジェクトを覚えておくフィールドを用意。

    GeometoryCommand geometoryCommand = null;
    ColorCommand colorCommand = null;
    FunctionApiSample.GraphObject obj = null;

それぞれの動作はこんな感じでメソッドを書いておきます。

    void toLeft(FunctionApiSample.GraphObject obj, Degree degree, Dimension fieldSize) {
        obj.left -= fieldSize.width / (degree == Degree.LITTLE ? 10 : 5);
        obj.left = Math.max(obj.left, 0);
    }
    void toRight(FunctionApiSample.GraphObject obj, Degree degree, Dimension fieldSize) {
        obj.left += fieldSize.width / (degree == Degree.LITTLE ? 10 : 5);
        obj.left = Math.min(obj.left, fieldSize.width - obj.width);
    }

色を変える操作は、指定した色を保持したラムダを返すように。

    ColorCommand colorCommand(String color) {
        return (obj, c) -> obj.color = color;
    }

意味に該当する単語を並べておきます。

    static Map<String, List<String>> normalizeData = Map.ofEntries(
        Map.entry("left", List.of("左")),
        Map.entry("right", List.of("右")),
        Map.entry("up", List.of("上")),
        Map.entry("down", List.of("下")),
        Map.entry("center", List.of("中央", "真ん中")),

実際には単語から意味をひくので転置。

    static Map<String, String> normalize = normalizeData.entrySet().stream()
        .flatMap(e -> e.getValue().stream().map(v -> Map.entry(v, e.getKey())))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

これ、entrySetまで書くとCopilotが続きを全部書いてくれています。こういう、定型だけど実際に書くとめんどいコードを自動で書いてくれるのがとてもよい。

あとは、Kuromojiで形態素解析してそれぞれの単語に該当するフレームを埋めていきます。

        var tokens = tokenizer.tokenize(input);
        Degree degree = Degree.NONE;
        for (var token : tokens) {
            System.out.println(token.getAllFeatures());
            var baseForm = token.getBaseForm();
            var command = normalize.get(baseForm);
            if (command == null) {
                continue;
            }
            System.out.println("command:" + command);
            switch (command) {
                case "rect", "triangle", "image":
                    obj = objectMap.get(command);
                    break;
                case "red", "blue", "yellow", "green", "black", "white":
                    colorCommand = colorCommand(command);
                    geometoryCommand = null;
                    break;
                case "left":
                    geometoryCommand = this::toLeft;
                    colorCommand = null;
                    break;

Kuromojiはここで使ってる形態素解析器です。

最後に、対象と操作が埋まってたら実行。

        if (obj != null) {
            if (geometoryCommand != null) {
                geometoryCommand.execute(obj, degree, fieldSize);
            } else if (colorCommand != null) {
                colorCommand.execute(obj, "red");
            }
        }

ここで、埋まったフレームをそのまま保持することで、次回のコマンドで対象などが省略されたときにそれを使うようにして、コンテキストを持っているように動作させてます。

そして、前回サンプルでボタンが押されたときの処理を書き替え。

static CaseFrameGrammer caseFrameGrammer = new CaseFrameGrammer();
static void goPrompt() {
    String prompt = textField.getText();
    caseFrameGrammer.parse(prompt, objectMap, new Dimension(800, 600));
    // 画面を再描画
    Graphics2D g = image.createGraphics();
    draw(g);
    g.dispose();
    imageLabel.repaint();
    textField.setText("");
}

やることが増えると、それぞれの単語や操作が増えて、あともう少し丁寧に構文解析してあげると、割と実用になる気がします。 実際にアプリケーションでLLMを利用するときにも、既存の自然言語処理で可能な部分はなるべくロジカルに書いておいて、ユーザー入力の解析や結果出力時の要約などLLMじゃないとできないことだけLLMを呼び出すのがいいんではないかと思いました。

確かにGPT使うほうが柔軟な対応ができるけど、遅いし高いのと、動きの安定感がないので、自力でやったほうがいい場合が多い気がします。

ソースはこちら。
https://gist.github.com/kishida/c0a7ae4e7a5db7e7e440fbde1886a5f6