今年の秋、人びとは自分が去年までとは変わってしまったことに気付く

秋の夜長、虫の鳴き声が聞こえだすと、夏が終わったなぁと感慨にふけることもあると思います。
しかし、今年の秋には、今までになかった感情が芽生えていることに気づくかもしれません。

秋の夜に鳴く虫というと、鈴虫が最初に思い浮かびますが、エンマコオロギが結構多いのではないかと思います。

ツヅレサセコオロギもよく聞きますね。

そして、コオロギと言えば昆虫食。
つまり今年の秋に虫の鳴き声を聞くと、今までになかった「あ、まわりに食料がある」という感覚を持つことになるのです。

食欲の秋が深まりますね。
※ 効果には個人差があります

ChatGPTより賢く質問に答えれるチャットBotを作る(誇張表現)

ChatGPTは2021年9月までのWebテキストで学習しているので2022年の知識を持っておらず、ワールドカップ2022年大会の優勝者を知らなかったりします。
BingではWeb検索と組み合わせることで解決しているので、それを自分で作ってしまえばいいのでは、とやってみました。

ちなみにやっていることは次のような流れ。

  1. GPTで質問を検索キーワードに変換
  2. そのキーワードでGoogle検索
  3. 検索結果1位のサイトにアクセス
  4. アクセス結果をGPTで要約

GPTを使う部分を説明すると、1.ではEdit APIを使っています。

var editReq = EditRequest.builder().input(text)
        .instruction("質問に答えるためのWeb検索キーワードに変換")
        .temperature(0.4)
        .model("text-davinci-edit-001").build();
EditResult keyResult = service.createEdit(editReq);
var keyword = keyResult.getChoices().get(0).getText().trim();

そして4. ではCompletionを使います。

CompletionRequest completionRequest = CompletionRequest.builder()
        .model("text-davinci-003") // davinci-003, curry, babbage, ada
        .prompt("「%s」という質問に答えるよう要約してください\n\n%s\n"
                .formatted(text, cArticle))
        .echo(false)
        .temperature(0.5)
        .user("testing")
        .maxTokens(500)
        .build();
CompletionResult result = service.createCompletion(completionRequest);  
var resultText = result.getChoices().get(0).getText(); 

ここでGPTに与える指示は完全に日本語であるのが面白いと思います。

2.のGoogle検索はこんな感じで。Jacksonがrecordに対応したのでマッピング作るのが楽になりましたね。

public class GoogleSearch {
    static String getApiKey() {
        return System.getenv("GOOGLE_API_KEY");
    }
    static String getCx() {
        return System.getenv("GOOGLE_SEARCH_CX");
    }

    record SearchItem(String kind, String title, String htmlTitle, String link) {}
    record SearchUrl(String type, String template){}
    record SearchResult(String kind, SearchUrl url, List<SearchItem> items){}
    
    static SearchItem search(String keyword) {
        try {
            var om = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            var url = "https://www.googleapis.com/customsearch/v1?key=%s&cx=%s&q=%s".formatted(
                    getApiKey(), getCx(), URLEncoder.encode(keyword, "utf-8"));
            SearchResult result = om.readValue(new URL(url), SearchResult.class);
            return result.items().get(0);
        } catch(IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }
}

3.のサイトにアクセスする部分は数日前のブログに書いたコードでサイトの本文を取り出します。
Web記事の本文をJavaで抽出する - きしだのHatena

あとは、画面をこんな感じで作っておしまい。

public static void main(String[] args) {
    var f = new JFrame("AI検索");
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setSize(800, 600);

    var p = new JPanel(new BorderLayout());
    f.add(p, BorderLayout.NORTH);
    var t = new JTextField(30);
    p.add(t);
    var b = new JButton("検索");
    p.add(b, BorderLayout.EAST);
    var out = new JTextArea();
    out.setLineWrap(true);
    f.add(new JScrollPane(out));

    f.setVisible(true);
    OpenAiService service = new OpenAiService(getToken(), Duration.ZERO);
    b.addActionListener(al -> {
        b.setEnabled(false);
        new Thread(() -> {try {
            search(service, t.getText(), out);
        } finally {
            b.setEnabled(true);
        }}).start();
    });
}

こんな感じで動きます。2022年のワールドカップの優勝国を答えれていますね。

まあ、一番うまくいった事例という感じなので、基本的には使い物にならないんだけど、少ないコードでそれっぽいものができておもしろいです。
あと、GPTは前処理後処理に使って、前処理として自然言語DSLに変換、後処理として処理結果を自然言語に変換のようにするのがいいなと思いました。

ソースコード全体はこちらに
https://gist.github.com/kishida/1f6a61679dc3c9b4ecf2514aef81c02c

オブジェクト指向はコードを複雑に読みにくくする

オブジェクト指向するとプログラムが読めなくなるから禁止」のような話は昔からあって、新しい技術についてこれない人を揶揄するようなニュアンスで使われていましたが、実際にはこれはオブジェクト指向迷路にうんざりした現場での率直な意見だと思います。
オブジェクト指向は、まじめにやるほどプログラムを読みにくくするという性質をもっています。
※ 使い方次第というコメントついてますが、だからこそちゃんと性質をしっておく必要があると思います。

オブジェクト指向の代表的な指針を3つあげると次のようなものがあります。

  • オブジェクト同士の連携としてプログラムを組む
  • 単一責務の原則
  • インタフェースと実装の分離

まず、オブジェクト同士の連携でプログラムを組むと、コードが飛びまくって追いにくくなります。そして単一責務の原則により、小さいクラスが大量に生成されて、追いにくさがさらにあがっていきます。
ダイクストラ先生がGoTo Hellといってあちこち飛び回るコードは地獄だと、構造化手法を提示してくださったのに、あらたなGoToを生み出してしまっていたのです。
しかも、オブジェクトは状態を持ち、その状態によって分岐するということもあります。
さらにそれをややこしくするのがインタフェースと実装の分離です。そうすると、メソッド呼び出しを追っていったら行き止まりであったり、一生懸命おっかけたメソッドが「そいつは残像だ」ということで実際はオーバーライドした別メソッドが呼ばれているということもあります。 そうすると、実際に動かしながらデバッガで追うか、メソッドの呼び出しと状態変更の両方を追っていく必要があります。デバッガの場合、目的とした状態を正しく追えているかの確認も必要になったりするので、結局は状態変更も追わないといけなかったりします。

そして、問題を大きくしたのは、オブジェクト指向をしていればよいプログラムができ、オブジェクト指向しなければいいプログラムはできないという感覚です。
それは過去のブログで「オブジェクト指向やめよう」のようなことを書くと「メソッドひとつに全部の処理を書くのか」「メンテナンスしないならそれでいい」のような、オブジェクト指向をやめるとまともなプログラムが書けないかのようなコメントがついていることからもわかります。

これは、オブジェクト指向を誤って使うのが悪いというよりも、オブジェクト指向の求めるものがその方向であったことにもあります。マーチンファウラーの「ドメインモデル貧血症」がその典型です。
このアンチパターンは、ドメインモデルがデータコンテナになってしまっていることを嘆くものですが、次のように、「オブジェクト指向ではないからよくない」という表現になっていて、どのような場合には許されるのかということは書かれていません。

「このアンチパターンが根本的に怖いのは、オブジェクト指向設計の基本概念(データと処理を一緒にする)の真逆だということです」
「貧血オブジェクト(Anemic Object) が本物のオブジェクトだと思っているひとがたくさんいます。 つまり、オブジェクト指向設計が何たるかをまったく分かっていないということなんです」
https://bliki-ja.github.io/AnemicDomainModel/

このようにして、「オブジェクト指向する」とどんどんプログラムが追いにくくなっていって、禁止ということが行われたのではないかと思います。

今ではなるべく階層化を行うことや状態をなるべく持たないようにすることが定着しているので迷路化が避けれますが、こういった節度はオブジェクト指向の文脈では語られておらず、オブジェクト指向とは別の文脈で語られるため、まともにプログラムを組むにはオブジェクト指向から離れる必要があるということになります。
オブジェクト指向でうまくやってる」という人もオブジェクト指向以外で学んだことを自分なりにとりいれた、再現性のない「オブジェクト指向」であることが多いように思います。そしてその場合には「オブジェクト指向を取り入れる」くらいで「オブジェクト指向する」ではないはずです。オブジェクト指向を目的にしてはいけない。

その根底には、コンサルが自分たちの開発ツールを売るために業務アプリ屋さんに手法を売っていたというオブジェクト指向ビジネスモデルにあって、手法を売るために絶対化しつつ難しくなっていくし、自分たちがツールを作るときに発見した手法では業務アプリにフィットしにくいというところに原因があったと思っています。

もっと定義とか個別機能の話を、という方はこちらの特集を。

※ ここに書いたこととだいたい同じなんだけど、だいぶ大人になった気がする
オブジェクト指向は禁止するべき - きしだのHatena

※ 追記 2023/6/2 最近は、処理とデータをわけましょうという「データ指向プログラミング」という考え方がでてきています。(昔あったデータ中心アプローチ(DOA)とは別概念です)
なぜオブジェクト指向でコードが読みにくくなることがあるかということも含めて、WEB+DB PRESS Vol.134の連載記事で説明しているので、興味あればご一読を。
簡単に言えば、現在のアプリケーションはブラウザ+マイクロサービスですでにモジュール化されているので、モジュール化技術であるオブジェクト指向を使っても不要な分割になり読みにくくなる、アプリケーションはデータ指向で書きましょうという話。

データ指向プログラミングについてもっと知りたい人は、この本を。4月発売なのでWEB+DB PRSSS記事の執筆時には未発売で知らなかったのだけど、ぼくがこれまでオブジェクト指向について書いてたことが結構そのまままとまっています。

Web記事の本文をJavaで抽出する

指定したURLの記事本文を取ってくる処理がほしいなぁと思って、しかしいろいろ考えるのは面倒と思ったけど、少なくともJavaで簡単に使えるものがなさそうなので実装した。

参考にしたのはこちら。
HTMLからの本文自動抽出 - アドファイブ日記(ミラー版)

テキストが長いタグは本文、直下のタグは本文への寄与が多い、のようにしてスコアを求め、そして全体テキストに対してスコアが高ければ本文だろう、という感じ。

上記サイトと変えたところは、<li>タグの点数をさげているところ。読売の記事が最後に各カテゴリの説明をもっていて、記事より長くなりがちでそちらが抽出されることが多そうなので、点数をさげた。

前回のブログ記事で試すとちゃんと取れている。

ソースはこんな感じ。HTML処理にはjsoupを使っている。
https://jsoup.org/

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Map;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;

public class ArticleFinder {
    
    private record Score(int vol, double score, Element elm){
        double rate() {
            if (vol == 0) return 0;
            return score * score / vol;
        }
    }
    
    private static final double RATE = 0.7;
    private static final Map<String, Double> customRate = 
            Map.of("p", 1., "li", 0.3);
    private Score longest;
    private ArticleFinder() {
    }
    private Element findLongest(Element elm) {
        longest = null;
        calc(elm);
        return longest == null ? null : longest.elm();
    }
    private Score calc(Element elm) {
        int textlen = elm.ownText().length();
        double score = textlen;
        for (Element child : elm.children()) {
            var chScore = calc(child);
            textlen += chScore.vol();
            score += chScore.score() * 
                customRate.getOrDefault(child.tagName().toLowerCase(), RATE);
        }
        var result = new Score(textlen, score, elm);
        if (longest == null || longest.rate() < result.rate()) {
            longest = result;
        }
        return result;
    }
    
    public static String findArticle(String url) {
        try {
            var doc = Jsoup.connect(url).get();
            var body = doc.body();
            var elm = findArticleElement(body);
            return elm == null ? "" : elm.text();
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }
    public static Element findArticleElement(Element elm) {
        var fa = new ArticleFinder();
        return fa.findLongest(elm);
    }
    
    public static void main(String[] args) throws IOException {
        var url = "https://nowokay.hatenablog.com/entry/2023/02/15/221559";
        var text = findArticle(url);
        System.out.println(text);
    }
}

しかしもう、こういう処理は完全に「Pythonでやれ」という世界になってますな。

Stable Diffusionの「呪文」の与え方

最近はブログにStable Diffusionが生成した画像を出すようにしています。Stable Diffusionなど画像生成AIでは、パラメータとして与えるプロンプトに呪文のようなことを書かないといけないので、呪文詠唱スキルみたいなのが求められるようになってますが、その呪文についてまとめてみます。
というか、ツール使うだけ。

まず、今回の↑の画像の作り方。
最初に、森と城なのでforestとcastleを与えてみるんだけど、ここでfantasy与えておくとよさげな絵になるので付け加えてみます。 「forest, castle, fantasy」で生成するとこんな感じに。

今回fantasyを付け加えたけど、このあたりの付け足すとよさげな単語をいろいろ知っておくというのは割と大変です。

そこでこんなツールが。Stable Diffusionに与えるプロンプトを最適化してくれるツールです。
Microsoft製。これもなんかすごいですね。
https://huggingface.co/spaces/microsoft/Promptist

で、こんなプロンプトにすればいいって言われたのでやってみます。
「forest, castle, fantasy, intricate, elegant, increasingly detailed, digital painting, artstation,」

まあなんか、fantasyが結構効いているので、そんなに変わらない気もするけど、increasingly detailedのおかげか、ちょっとディティールが細かくなってる気がします。実は最初に加えたfantasyも、こうやってpromptistで試すなかで知った単語でした。

じゃあ、ノートパソコン出したい、ということでlaptop pcで試しましょう。
laptop pcだけだとこんな感じ。

Promptistを試すと「laptop pc, ultra realistic, concept art, intricate details, eerie,」というプロンプトをオススメされます。

で、試してみると・・・

お、おう

これ、eerie(不気味)が入ってるからですね。

てことで外して「laptop pc, ultra realistic, concept art, intricate details,」で試すとこんな感じ。

かっこよくしようという気概を感じる!

まあ、生成したいものがはっきりしている場合には、プロンプトは簡素なほうがいい感じになりがちな気がします。

けど、こういった「AI」に与えるパラメータを「AI」で作るというのは面白いなと思います。
Stable Diffusionというのは、ノイズをごちゃごちゃやってるとそれっぽい画像になるという仕組みなのですが、Promptistはパラメータにもノイズを加えるという感じになっていますね。

ChatGPTによって世の中の「AI」の理解がすすんだ

もうなんかどこもかしこもChatGPT、という感じで流行ってますね。といいつつ、ぼくも割とChatGPTのブログ書いてます。だっておもしろいもん。
そして、多くの人が触って、今のAIの特性みたいなものに気づく人が多くなってるように思います。

世の中でAIが流行りだして画像認識だったり音声認識だったり、データ認識系がまず流行りました。
画像に映ってるものがなにかを識別してくれるというのは当時はすごいなと思ったものの、その結果をみても「うまく認識できないものもあるね」という感じでした。
「あぁこういう間違いするんだ」というのもあったけど「錯覚しやすいのね」くらいの感じだったと思います。「錯覚」するのがすごかったりするのだけど。

そもそもとして、画像認識や音声認識を実際に直接触るのは技術者くらいのものなので、普通の人は「alexaがテレビの声に反応してる」みたいなアプリケーションが失敗するという認識で、それがAIの特性というところまでは及ばなかったと思います。

それから拡散モデルの登場から画像生成など生成系が流行りだして、AIすごいというかAIこわいみたいな話になってきました。
画像生成になると、AIの出力がそのまま出回ります。ただ、それでも画像生成を実際に使うのは一部の人で、多くの人は実際にどんなものかを感じれてはいないように思います。
実際に目にするのは、たとえば↑のヘッダー画像もStable Diffusionで生成していますが、だれかが試行錯誤して何十枚何百枚と生成したなかから選ばれた一枚になります。

画像生成では、失敗作って単にキモイだけだったりただボヤボヤした画像だったり、見てもあまりおもしろくないのですよね。なので、あまりネットにあがることもない。
そうすると完成度が高いものだけ目にするので、AI怖い全部AIになっちゃうってなりがちです。

けど、ChatGPTで状況がかわりました。
画像生成欲がある人はそんなに多くないのですが、言葉でやりとりしたい欲は人間活動そのものだったりして、みんなが持っているわけです。
それで多くの人が試すようになって、いまの盛り上がりになっていると思います。

そして、ChatGPTの場合、AIの出力がそのまま表示されます。言葉での応答というのは判別がやりやすいので、AIの特色がわかりやすいです。
Stable Diffusionでの結果は出処がよくわからずネットで収集したデータをもとに作られていることを見過ごしがちですが、ChatGPTの場合は身近なことを問いかけて、そしてそういったことはある程度 自分でぐぐって生のWebデータを見たことがあったりするので、なんとなく学習元データとの対応が想像できたりします。

あと、ChatGPTの失敗っておもしろいので、結構とりあげられてますよね。
そんな感じで、結構うまくいかないのだなーというのがわかりやすく共有されていると思います。

ということで、AIといっても学習データの最頻値を出してるだけだなーみたいなのが実感しやすくなっていると思います。 ちょっとニッチになったり条件が複雑になるとちゃんと答えてくれないとか、それっぽいけどそれぽいだけだなとか、いまの大量データの統計をもとにしたAIのクセが説明しやすいしわかりやすい。
そんな感じで、ChatGPTをきっかけに世の中のAIの理解が進んだなと思ったのでした。

もっと理解したい人は、ChatGPTやStable Diffusionなどのキーになる技術、Transformerについて解説したこちらがおすすめ。
2022年9月出版なのでChatGPTこそ触れられていませんが、GPTにも少し触れられています。

プログラミング言語へのMicrosoftの影響力がヤバい

Tiobe indexを眺めながら、C#VBが入っていてMicrosoftは強いなーと思ったのだけど、よくみると他の言語もMicrosoftの影響力すごいのではとなったので調べてみた。
https://www.tiobe.com/tiobe-index/

Python

例えば1位のPythonMicrosoftはかなり力をいれている。象徴的なのが、Pythonのオリジナル開発者の入社

C/C++

Windows上でのC/C++コンパイラではMS C/C++が圧倒的シェアであるし、C++の標準化団体にMicrosoftはGold Memberとして参加している。
https://isocpp.org/about

Java

4位のJavaに関してもかなりリソースは割いているわけですが、全体の影響度としてそこまで大きくはないですね。
昨日のブログにも書いたようにOpenJDKビルドを出しているし、独自の改善も行ったりしているけど、今のところ自分とこの環境でうまく動くようにという感じで、これからです。
Microsoft OpenJDKのエスケープ解析改善を試したら割と効いてる - きしだのHatena

C#VB

5位C#、6位VBは完全にMicrosoft製品ですね。

JavaScript

7位JavaScriptに関しては、OpenJSのメンバーになっていますね。掲載順はアルファベット順と思われます。
https://openjsf.org/

設立時に名前があがる程度に影響力は大きいと思います。
Node.js FoundationとJS Foundationが合併しOpenJS Foundationを設立 - The Linux Foundation

コメントで知りましたがnpmも買収していますね。
マイクロソフト傘下GitHub、JavaScriptパッケージ管理のnpmを買収へ - ZDNET Japan

また、TypeScriptを持っているので、そこからの影響も強いです。たとえば、TypeScriptの型構文をJavaScriptに導入する提案も行っています。
A Proposal For Type Syntax in JavaScript - TypeScript

SQL

8位SQLも、シェア3位のSQL Serverとシェア10位(RDBMSとしては7位)のAccessを持っています。
SQLプログラミング言語なのかというコメントがついているけど、チューリング完全だと指摘されて2018から載せるようになった、とtiobeに注釈があります。
https://db-engines.com/en/ranking

Assembly

9位のAssemblyに関しては、これは単一の言語なのか?という疑問がありますが、WindowsIntelの組み合わせがWintelと言われた時代もあるように、x86命令セットには大きく影響を与えてると思います。

PHP

10位PHPに関してはPHP8以降はサポートしていないので、7.2のサポートが終わった2022/11/28にWindowsサポートを終了していますね。

We are not, however, going to be supporting PHP for Windows in any capacity for version 8.0 and beyond

https://news-web.php.net/php.internals/110907

まとめ

一時はGoogleがこのように開発者コミュニティを引っ張るように見えましたが、いまはそこまで積極的ではないように見えます。
自分たちで使うものに投資している感じ。というかそれは元からで、自分たちが使ってるものを外に出してみんなで使えるようにするという感じですね。
Pythonのヴァンロッサムは2005年にGoogleに入社していましたが2012年に退社しています。
Javaの父と言われるJames Goslingも入社したことがあるけどすぐ退社しています。

現時点では、Microsoftほど開発者に投資している会社はないように思います。GitHubも傘下だし、いま話題のChatGPTを開発しているOpenAIにも出資しています。
MicrosoftWindowsXBoxといったコンピューティングプラットフォームが売上の1/3、AzureやGitHubを含む開発者向けサービスが売上の1/3で、開発者を支援することがビジネスになっていて、そしてWindowsのようなPC向けOSでは開発技術の囲い込みが難しいことから、広く開発技術に投資することが大切になっているんだと思います。

しかし、Microsoftが力をいれるからTiobe index順位があがるのか、Tiobe index順位があがるからMicrosoftが力をいれるのか・・・