LLaVAを使っておうちのパソコンでも画像からコード生成

ChatGPTが画像対応して、画像からいろいろなコードが生成できて楽しいことになっていましたが、同じようなことをおうちで動かせるLLaVAが出ていたので試してみました。
GPUはVRAM 12GBあれば十分、8GBはギリギリという感じ。

LLaVA-1.5

先週、LLaVAのバージョンアップ版、LLaVA-1.5が出てました。

LLaVAはマイクロソフトウィスコンシン大学が開発しているマルチモーダルモデルです。
LLaVA: Large Language and Vision Assistant - Microsoft Research

デモはここ。
https://llava.hliu.cc/

このデモをおうちのWindowsで動かそうというのが今回のブログの趣旨

セットアップ

基本的にはGitHubの手順に従います。
https://github.com/haotian-liu/LLaVA

まずはClone

git clone https://github.com/haotian-liu/LLaVA.git
cd LLaVA

で、venvやらcondaの環境をお好みで。

python -m venv .venv
.venv\Scripts\activate.bat

そしてpip install -e .するのだけど、deepspeedはWindowsに対応していなくてインストール時にエラーが出るので、pyproject.tomlから消しておきましょう。
https://github.com/haotian-liu/LLaVA/blob/main/pyproject.toml#L20

あと、PyTorchがGPU非対応のものが入ってしまうので、自力でインストールします。
PyTorchのサイトを確認。
https://pytorch.org/

CUDA 11.8が入ってるとして、次のようにします。

pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

bitsandbytesもWindows用バイナリが足りないものが入ってしまうので、これも自力で。
bitsandbytes-windows-webuiのバイナリを使います。
https://github.com/jllllll/bitsandbytes-windows-webui

pyproject.tomlでは0.41.0という指定になっているので、次のように。

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

これでようやくpip installができます。

pip install --upgrade pip
pip install -e .

コマンドラインで試す

Web UIも用意されてるけど手順がめんどいので、まずはコマンドラインで試します。改行してるけど、一行で。

python -m llava.serve.cli
  --model-path liuhaotian/llava-v1.5-7b 
  --image-file llava/serve/examples/extreme_ironing.jpg
  --load-4bit

7Bモデルを4bitで読み込んでいます。画像として指定しているのはタクシーの後ろでアイロンかける人ですね。
結果はこんな感じに。

Google翻訳するとこうなりました。

この画像の珍しい点は、男性が走行中の黄色いタクシーの後ろに立って、衣服にアイロンをかけていることです。 衣類のアイロンがけは通常、家や洗濯室などの室内で行われるため、これは典型的な場面ではありません。 走行中の車両の中で衣服にアイロンをかけるという男性の行為は異例であり、事故や怪我につながる可能性があり、潜在的に危険です。

メモリは、3.6GB使っていた状態から読み込み時点では8.5GBに、そして上記の結果を出力するときに10GBになっていたので、6.4GB使った感じですね。なので8GB VRAMでいけそう。

RTX 4060 Ti 16GBでのスピードはこんな感じです。

すごく雑には、こんな感じでメモリ使ってました。13B 8bitは16GBだと少しはみでて遅くなっている感じ。

モデル 7B 13B
normal(16bit) 15GB 26GB?
load-8bit 11GB 18GB?
load-4bit 7GB 11GB

Web UIで試す

さて、Web UI。3つ起動が必要でめんどい。
まずコントローラー

python -m llava.serve.controller

21001番ポートで起動します。

次にモデル管理のためのmodel_worker。
ただ、Windowsの日本語環境で実行すると、ログでエラーがでてそのエラーのログでエラーが出て、となって正常に動かないので、llava/utils.pyを修正します。
50行目にencoding='UTF-8'を追加します。プルリク出しているので、ここを参考に。
https://github.com/haotian-liu/LLaVA/pull/556/files

そしたら、こんな感じで動かします。ここでは13Bを4bitで読み込ませてます。実際には1行で。

python -m llava.serve.model_worker
  --model-path liuhaotian/llava-v1.5-13b --load-4bit

21002番ポートで起動します。

最後にWeb UI

python -m llava.serve.gradio_web_server --model-list-mode reload

7860番ポートで起動します。

この画像を投げて「write docker compose」と指定してみます。

なんかdocker composeファイルを書いてくれました!

動きとしてはこんな感じ。上記と別のタイミングの動画なので、内容はちょっと違います。

これでローカルでもいろいろできそうです!

ICU4Jで文字数をカウントする

Javaでは文字数をlength()で数えることができます。
1996年にJavaが出てきた当初は「半角も全角も1文字に数えれて便利だなー」などと思っていたわけです。 けれども、同じ1996年に策定されたUnicode 2.0で2文字分のコードを使って1文字を表すサロゲートペアが導入されて、2001年のUnicode 3.1あたりで実際にサロゲートペアで表される文字が登録されると事情が変わります。
まあ、2002年のJava 1.4ではまだサロゲートペアは扱えなかったので影響はなかったのだけど、2004年のJava 5でUnicode 4.0に対応し、JSR-204でサロゲートペアが扱えるようになったときに騒ぎになりましたね。

当時のさくらばさんの解説
J2SE 5.0 Tiger 虎の穴 Unicode 4.0 の補助文字のサポート

ただ、JSR-204ではあくまでも文字コードとしてUnicodeが扱えるようになったというもので、1文字ずつカウントするといったアプリケーションレベルの処理は導入されていません。Java 21で絵文字に対応したというのも、Unicodeに文字区分として絵文字が導入されたので対応したというものです。

ということで、アプリケーション処理として文字を扱いたいときにはICUを使います。JavaからはICU4Jですね。
ICU4J | ICU Documentation

Mavenリポジトリはこれ。

<dependency>
  <groupId>com.ibm.icu</groupId>
  <artifactId>icu4j</artifactId>
  <version>73.2</version>
</dependency>

で、文字をカウントするとこう。

サロゲートペアな絵文字の場合は、length()だと2文字とみなされます。

jshell> "こんにちは🙂".length()
$1 ==> 7

そこでICUBreakIteratorを使います。
BreakIterator (ICU4J 73)

jshell> import com.ibm.icu.text.BreakIterator

jshell> var bi = BreakIterator.getCharacterInstance()
bi ==> !!quoted_literals_only;$CR=[\p{Grapheme_Cluster_B ... ator$Regional_Indicator;.;

扱いたい文字列をsetText

jshell> bi.setText("こんにちは🙂")

で、こんな感じでカウント

int count = 0;
while (bi.next() != BreakIterator.DONE) {
    count++;
}

というのも面倒なので、Streamで。

int count = Stream.generate(() -> bi.next())
        .takeWhile(s -> s != BreakIterator.DONE)
        .count()

JShellで試すと、ちゃんと絵文字が1文字でカウントされました。

jshell> Stream.generate(() -> bi.next()).takeWhile(s -> s != BreakIterator.DONE).count()
$4 ==> 6

追記: 正規表現 \b{g}splitするのが手軽と教えてもらった

jshell> "こんにちは🙂".split("\\b{g}").length
$5 ==> 6

画像対応ChatGPTで設計図からコードの世界が実現しててやばい

マチュア驚き屋のきしだです。 ChatGPTが画像入力に対応するよという話があって、来週くらいに使えるようになるかなーと思ったら、もう使えるようになってました。 で、写真から「カレー食べてる男の人です」くらいを言えるイメージで試してたら、なんかふつうに画面設計やクラス図からコードを書いていてびっくりしてしまいました。

まあ、起きたらこういうのが来てたわけですね。

で、まあ試してみて「あぁ、いままでのマルチモーダルよりちゃんと画像認識してるなー」くらいに思ったわけです。

で、NetBeansでの画面設計を読ませてみたらこう。

こういうコードが生成されました。

import javax.swing.*;
import java.awt.*;

public class SimpleForm {

    public static void main(String[] args) {
        JFrame frame = new JFrame("Sample Form");
        frame.setSize(400, 300);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new GridLayout(4, 2, 10, 10));

        // ラベルとテキストフィールドの追加
        frame.add(new JLabel("ID"));
        frame.add(new JTextField(10));

        frame.add(new JLabel("名前"));
        frame.add(new JTextField(10));

        frame.add(new JLabel("種別"));
        JComboBox<String> comboBox = new JComboBox<>();
        comboBox.addItem("男性");
        comboBox.addItem("女性");
        frame.add(comboBox);

        frame.add(new JLabel("金額"));
        frame.add(new JTextField(10));

        // テーブルの追加
        String[] columnNames = {"ID", "名前", "種別", "金額"};
        Object[][] data = {};
        JTable table = new JTable(data, columnNames);
        frame.add(new JScrollPane(table));

        frame.setVisible(true);
    }
}

実行するとこうなっちゃうので残念ですが、これはJavaのレイアウトが難しいので仕方ないと思います。

ボタンがないので「登録ボタンを押したらテーブルに追加されるようにできる?」と聞いたら、ちゃんと登録ボタンを実装してくれました。

レイアウトもGridLayoutからFlowLayoutに変わったので、見やすくなっています。
ソースこちら。
https://gist.github.com/kishida/165c590025b0c8a21ca10c861432c731

ここで、文字がちゃんと読めていること、そしてテキスト入力かドロップダウンかテーブルかというコンポーネントの区別ができているところが驚きです。 種別の選択肢が男性・女性なのは性別と勘違いしてそう。

※ 19:20追記 手書きでもなにか読み取ってくれてます。

追記ここまで

そしてクラス図からのコード

Catのコードはこんな感じになってます。

// ColorPatternを表す簡単な列挙型
enum ColorPattern {
    STRIPED, SPOTTED, SOLID
}

// Mammalを継承したCatクラス
public class Cat extends Mammal {
    private ColorPattern colorPattern;

    public Cat(int height, int weight, ColorPattern colorPattern) {
        super(height, weight);
        this.colorPattern = colorPattern;
    }

    public ColorPattern getColorPattern() {
        return colorPattern;
    }

    public void setColorPattern(ColorPattern colorPattern) {
        this.colorPattern = colorPattern;
    }

    @Override
    public String cry() {
        return "ニャー";
    }
}

ちゃんと「ニャー」を実装してくれるのがいいですね。
※ 18:20追記 イヌは「ワンワン」です。コード全体はこちら。
https://gist.github.com/kishida/fbcb63f3b26552ed7f3daec84ba744bc

で、Bardで試してみたら、Bardでもいけますね。ただ、CatにもDogにもColorPatternとBreedの両方がある。ChatGPTはクラス図どおりでした。

フォームデザインからのコード生成でも、BardではAWTだったり全部テキストフィールドだったり、ちょっと甘い感じ。チャットでの修正もなかなか思い通りにならないことが多いです。
しっかり感はChatGPTですね。まあこちらは課金してるというのもある。

しかし、なんか入力から人間の求める出力を得る操作は、ぜんぶ言語モデルでやれてしまうのではという感じになってきました。
たとえばManmalクラスはabstractになっていますが、クラス図ではそういう指定になっていません。でも「哺乳類ならabstractだろう」という「常識」をもっているので、いい感じにしてくれてるわけですね。 CatのColorPatternには縞、ブチ、単色みたいなのを用意してくれてます。犬の血統にはシェパード、ブルドッグ、ラブラドールを設定してくれています。新しく画面を起こすときには、まずこういったそれっぽい値が入ってるとヤル気がでますね。
こういった部分はルールベースでは無理で、そのため図を書くときに全部指定しないといけないことになっていました。で、結局は作図ツールよりコードのほうが書きやすいということで、コードから図を生成することに・・・

まぁ、言語というのは世界を規定するものでもあり、言語のモデルというのは世界のモデルでもあるので、言語モデルを中心にいろいろやるとなんだかんだいい感じになるのだなーと思いました。

Stable Diffusion Web UIにFooocusのスタイルを取り込む

FooocusというStable DiffusionのUIではスタイルを設定するだけで呪文が不要のシンプルなプロンプトでの画像生成が可能になっています。
そのスタイルをAUTOMATIC1111/Stable Diffusion Web UIに取り込めるようにしてみました。

いろいろ過程を書いてるので、最後まで読むのがめんどかったら、このstyles.csvをStable Diffusion Web UIのフォルダ直下に置くと読み込まれる。すでにスタイルを設定しているのであれば、既存データを追加しておく。
https://gist.github.com/kishida/9e062c8d3f57dc68e8270b8417feecea#file-styles-csv

Fooocusはrun.batを起動するだけでインストールができるお手軽UIなのだけど、すでにAUTOMATIC1111 web uiを使ってるので取り込みたかった。
Fooocusに関してはこちらの記事が詳しい。
【西川和久の不定期コラム】次世代Stable Diffusion(SDXL)をWindows上で一発で使用可能にする「Fooocus」 - PC Watch

そしてFooocusではスタイルを指定すると呪文プロンプトを自動設定してくれるのだけど、そのスタイル一覧がこのGoogle Sheetにまとめてある。
https://docs.google.com/spreadsheets/d/1AF5bd-fALxlu0lguZQiQVn1yZwxUiBJGyh2eyJJWl74/edit#gid=0

100番目くらいまではComfyUI用のPrompt Stylerから持ってきている。
そのあと現時点で187まで増えている。
https://github.com/twri/sdxl_prompt_styler/blob/main/sdxl_styles_sai.json

上記Google SheetをHTMLに保存して、ChatGPTにCSV抽出をお願いしてみた。

しかし何度も失敗して、悪いファイル名の見本みたいなものができあがった。どれが最終ファイルでしょうか!

このChatGPTの気持ちの変遷がおもしろく、最初は「以下のリンクからダウンロードできます」だった。

途中からはファイル名に「final」をつけ、「何か他にお手伝いできることがありましたら、お知らせください」というタスク完了の定型文を付けて逃げようとしている。

いろいろあって完成したけど、すごくほっとした感じがある。

ChatGPTは人間のタスクを遂行するようしつけられているので、なかなかタスク完了しないと早く終わらせたいと思うようになるし、やっと完了すると安心する、という心の動きのようなものが形作られる。

ということで、冒頭のCSVができたので、これをStable Diffusion Web UIのフォルダ直下に置けば読み込まれる。すでにスタイルを設定していたら、既存データを追加しておく。
https://gist.github.com/kishida/9e062c8d3f57dc68e8270b8417feeceahttps://gist.github.com/kishida/9e062c8d3f57dc68e8270b8417feecea#file-styles-csv

ChatGPTががんばったスクリプトはこちら。ただし、前後にゴミの行が入るので、それは手動で削除して。
https://gist.github.com/kishida/9e062c8d3f57dc68e8270b8417feecea#file-html2csv-py

そうするとスタイルが指定できるようになります。

「1 girl in street」をプロンプトに入力してスタイルを色々変えてみたものがこちら。

cinematic-default

sai-anime

Japanese Ink Drawing

papercraft-papercut shadow box

いい感じ。

ネットワークプログラミングの練習にインターネット自由協会の電子公告表示プログラムを作ろう

登さんがインターネット自由協会の電子公告をTELNETで公開されていました。
TELNETであれば簡単に内容を見れるので、誰でも見れることを目指すべき電子公告には適していますね!

登さんがこういうツイートをされていました。

TELNETを使う理由のひとつに次のようなことがあげられています。

HTTP を用いて電子公告を公開していると、外国人の意志ひとつで、突然日本人の電子公告を閲覧者が事 実上閲覧できない状態になってしまうリスクがあります。他方、TELNET プロトコルは、接続先のインターネットサーバーに閲覧者がアクセスするとサーバーから送付されてくる文字を単に画面上に表示するだけの、これ以上に単純化することができない程度に最も基礎的なプロトコルであることから、外国人の意志によっても、決してその挙動が変更されない安定性があります

そう、アクセスすれば送られてくるメッセージを表示するだけです。
これはネットワークプログラムの練習にいいのでは、ということでインターネット自由協会の電子公告を表示するコードを書いてみます。

import java.awt.Font;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import javax.swing.*;

void main() throws IOException {
    var f = new JFrame("インターネット自由協会");
    f.setLocation(300, 300);
    f.setSize(800, 600);
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    var t = new JTextArea();
    t.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
    f.add(new JScrollPane(t));
    f.setVisible(true);

    try(var s = new Socket("koukoku.shadan.open.ad.jp", 23);
        var in = s.getInputStream();
        var r = new InputStreamReader(in, "Windows-31J"))
    {
        for (char ch; (ch = (char) r.read()) != 0;) {
            t.append(ch + "");
        }
    }
}

ウィンドウを表示したりいろいろとありますが、接続後の処理は受け取った文字を表示するだけになっています。

        for (char ch; (ch = (char) r.read()) != 0;) {
            t.append(ch + "");
        }

実行するとこうなります。

みんな自分の手元の言語でインターネット自由協会の電子公告を表示するコードを書くといいんじゃなかろうか。

ところでブログのヘッダー画像をStable Diffusion XLで生成するようにしたのだけど、結構いいのが出ててボツにするのが惜しいものが多いので、ここで供養しておきます。

ChatGPTで構成された仮想のソフトウェア会社にシステム開発を行ってもらうChatDevがおもしろい

ChatGPTによるメンバーで構成された仮想のソフトウェア会社にシステム開発を行ってもらうChatDEVが結構おもしろかった。

ChatDEVは、ChatGPTによってCTOやプログラマー、レビュアー、テスターといった役割をもつエージェントをやりとりさせることでソフトウェア開発を自動化しようという試みの実装です。
https://github.com/OpenBMB/ChatDev

イデアは論文にまとまっていて、こちらで概要が翻訳されています。
[LLM 論文]アプリ全自動開発"ChatDev"の日本語訳|すめらぎ

使い方としては、とりあえずClone

git clone https://github.com/OpenBMB/ChatDev.git

そして依存モジュールのインストール

cd ChatDev
pip3 install -r requirements.txt

あと、OpenAIのアクセスキーを環境変数OPENAI_API_KEYに設定しておく必要があります。

export OPENAI_API_KEY="your_OpenAI_API_key"

で、タスクと名前を指定すれば勝手にプログラムができる。

python3 run.py --task "[description_of_your_idea]" --name "[project_name]"

ということで、インベーダーゲームを作ってとやってみました。ここでは英語で指示してるけど、受け取るのはChatGPTなので日本語でも大丈夫です。あと、モデルはデフォルトでGPT-3.5-Turboなのだけど、確実さをもとめてGPT_4を使ったりしています。

python3 run.py --task "design invader game" --name "my-invader" --model GPT_4

ところでこの仮想開発会社、開発がはじまると確認なく最後まで進んで、できましたと納品したあとは修正も受け付けないという、分類するならクソ開発会社なので、タスクはこんな漠然としたものではなく具体的にキャラクタの形状や動作などを指定するほうがいいです。じゃないとなぜかへんなおっさんが・・・

で終わったらWareHouseフォルダのなかのアプリケーション名で始まるフォルダにいってmain.pyを起動すればOK

cd WareHouse/my-invader_DefaultOrganization_20230901092058
python main.py

ということで、まずGPT3.5-turboでやったのがこれ。
10分かかったのを20倍速にしてます。で、できたぞと思ったら・・・

へんなおっさんが横移動してミサイル置いていくアプリになっていましたw

課金は$0.16でした。25円くらい。

気を取り直してGPT4を指定したのだけど、最初はファイルひとつまるごと実装もれしていて、やりなおしたら動きそうなコードができていました。
画像ファイルがなかったので、Windowsのペイントで書いています。あと、ミサイルが動かないのを修正したり、実行速度を調整したりして、こんな感じに。

課金はやりなおし含めて$1.5増えていたので、200円かかっていますね。

実行ログを可視化することもできます。次のコマンドでビュワーを開いて、Chat Replayでログをアップロードすればいろいろ再現されます。

python3 online_log/app.py

こんな感じ。最初にCTOがデザインしてプログラマにタスクが渡り、レビュワーにレビューしてもらって、動くようになったらテスターがテスト、最後にドキュメントを書いて左側でソファに座ってるクライアントに納品という流れ。

まあ、実際には、ふつうにChatGPTに直接指示して作っていくほうが確実だし変なものになりづらいのだけど、これはコンセプトモデルなので、途中に人間が確認するフェーズを入れるとか、仮想環境をたててテスト起動をするとかいろいろ作りこめば、プログラム知らない人でもある程度のモノが作れる感じだなーと思いました。

この本には、LangChainによるエージェントでいろいろタスクを実現するデモまで載ってるけど、それの発展形という感じですね。