NAIST博士後期課程を修了した

もう2週間以上たってしまったけれど、先日奈良先端科学技術大学院大学の学位授与式があり、正式に博士の学位を得た。博士論文を図書館に提出した時点でほぼ確定していたとはいえ、実際に学位記をもらわないと実感がわかないものだ。

在籍期間は23年の10月から26年3月までの2年半。論文誌掲載4報,国際会議発表4件(口頭2,デモ1,ポスター1), 表彰2という業績は、フルタイムで働きながら、子育てしながらのなかでは頑張ったほうだと思いたい。トップカンファレンスにフルペーパーを通せなかったのは心残りだけれど、まあ仕方がない。

20年以上も前、学部生だったころに博士課程の先輩をかっこいいな。と思って以来、博士を取ることはひとつの大きい目標だった。株式会社はてなで会った人たちに、博士進学者が多かったのもある。

ずっと気になっていたことが解決してホッとする気持ちと、次の個人的なプロジェクトを早く軌道に載せたい気持ちで正直なところ少しフワフワしている。幸いやりたいことはあるので、それを進めるだけなのだが進捗はあまりよくない。すこしくらいゆっくりしても良いのかも知れない。

博士課程在籍中は、妻に多大な迷惑をかけたし大きな支えになってもらった。とくに23年, 24年は妻も修士の学生で、家族全員学生だぜ!というのは楽しかったけれど、妻には大変だったと思う。”修士の間は支えるね”と言った数カ月後に、自分も博士過程に行きたいんだけどと言い出したのは本当に申し訳なかった。

この2.5年間はちょうどLLMが台頭してきた時期でもあり、自分の研究対象もLLMの利用を前提にしたものだ。研究の進め方にもLLMは必須になっていて面白い時期に研究活動ができたと思っている。一方でLLMがどんなに進歩しても研究とはなにかという問いに答えを出してくれる気はしない。様々な分野の研究があり、様々な成果の出し方がある。それが学べたことは大きい収穫だったしこれからも何かしら研究をつづけて行けるような気がしている。

博士課程在籍中は指導教官を始めとても多くの人々にお世話になった。あらためて感謝申し上げたい。

劇場版 Onishi Half Century Conference に参加してきた

先週末 劇場版 Onishi Half Century Conference という誕生日会があった。

映画館でLTできるというのが面白すぎてうっかり登壇申し込みをしてしまい、話がうけるかどうか心配で先週はわりとずっと気が重くてしかたなかった。

LTの内容としては、気づいたら18年に渡って、シアターキノの上映リストをメンテナンスしてたわ。という話をしてきた。劇場のプロジェクタが使えるので、上下をちょっと黒く塗って2.35:1のシネスコでプレゼンしてみた。参加者リストに、セキュリティの怖い人がいたので前日にあわててCSPを設定するなどしたけれど、うれしい感想もいただけたので発表としてはそこそこ上手く行ったほうだろう。

id:onishi さんの50歳のお祝いということで、会の主人公である onishi さんとは、2008年のはてなインターンで会って以来なので17年ちょっとのお知り合い。8年ほど先を歩いている人生の先輩であり、はてな退職後も時々人生相談でお世話になっている。

本編も懇親会も2次会も非常に楽しく良い会だった。幹事のみなさんに深く感謝したい。 2次会で20年近くにわたって一方的に知っているだけだった id:mala さん、id:sora_h さんとセキュリティ談義ができて感慨ぶかかった。

はてなに在籍していたのはもう10年以上前なのだが、あいかわらず仲の良い人ははてな関係者が多い。今回のイベントもそうだけれど、在籍が被ってなくても、なんならはてなに在籍したことがなくても、はてなという会社をコアにして集まって仲良く話せるコミュニティというのは他にあまり見たことがない。

まちがいなく、onishi さんを始めとしてはてな社の人たちがずっと作ってきたカルチャーなのだと思う。

YAPC::Fukuoka 2025 に参加してきた

もう2週間も経ってしまったがYAPC::Fukuoka に参加してきた。

yapcjapan.org

主な目的は、LT登壇と所属先がYAPCをスポンサーしているのでそのアピールである。

東京大学で開催された YAPC::Asia 2012 以来の参加。13年前は、ここでいつか発表してみたいものだなあと思う若手エンジニアだったわけだが、その後子供が生まれたり、転職したり、さらに転職したり、コロナ禍があったりとずっと参加できずにいた。そんな中で、会社にかけあってスポンサーもできたし、LTもできたし、10年以上かかったけれど、やりたかったことが一つできたと思っている。

いまの職場ではエンジニア組織の立ち上げを行っていて、様々な環境構築やら、社内の折衝やらと忙しくしているのだけれど、とにかく採用は時間もかかるし認知を広げないとどうにもならない。そんな中でYAPC::Fukuoka がちょうどスポンサーを募集していて、YPACに来るようなシニアで何でもできるエンジニアに来てほしい!ということでスポンサーを申し込んだ次第である。

詳細は話せないけれど、おかげさまで沢山の応募がありYAPCのスポンサーになったことはかなり効果が高かった。来年もスポンサーになって、トークもより技術的な話をできると良いなと目論んでいる。

懇親会も久々に会う方々といろいろ話せてとても楽しかった。未踏ユースの同期に実は同期なんですよ覚えてます?という話をできたのも面白かった。当然先方は覚えてなかったけど!

Kayac さんが行っていたコードゴルフ大会でも2位になれたのは良かった。perlruby も仕事ではほとんど書いてないけれど案外忘れないものだ。ruby で書いたホール2は、もう少し上位に詰め寄りたかったけれど上位回答を見てどうやっても思いつけなかったなという感想である。TRICK参加の人々が流石すぎる。

オールナイト万博の思い出

ライトアップの無い貴重なガンダム

まとめ

  • 会場内は平和で僕は楽しかった
  • もうちょい情報公開が早かったらよかったかもね
  • 万博現地や輸送機関のスタッフのみなさんめっちゃ丁寧親切ですばらしかった

万博が非常に楽しくこのところ毎週家族でお出かけしているのだが、昨日の大阪メトロ中央線運転見合わせに巻き込まれ深夜まで万博会場に滞在することになった。とくにパビリオンに入って休憩などはしなかったけれど、めったにない体験と光景で僕は楽しんでしまったというのが正直なところ。対応してくれた現地のスタッフさんたちは深夜労働にもかかわらずみんな丁寧で尊敬に値する。ふだんも万博のスタッフさんたちは雰囲気が良く、この祝祭をもりあげている一因な気がする。

さて時系列で振り返ってみる。

午後 9:00 東ゲート出場

昨日12日の天候の影響で、本日13日もドローンショーが無いというのでいつもよりは早めにゲートを出る。先週の経験からゲート出てから地下鉄に乗れるまで大体30分くらいかなと思っていた。ゲートから何度も折り返す整列路を程々の速度で歩いて進んでいた。

午後 9:30 行列停止

夢洲駅の裏側、エレベータ入口付近で行列が止まる。普段ならここから10分くらいで乗れるのだが、というところ。ネットの情報で地下鉄が止まった事をしる。この時点で長期戦になりそうな予感がする。列から抜けるのも大変なので、そこからしばらく列に座り込んだりして待つ。水分を切らしてたので、子どもが非常に心配だった。一番困っていたのはこの時間帯。なんか喧嘩してる人がいるとかで周りがざわつく。

午後 10:15 頃 コスモスクエア駅まで再開との噂

駅構内の方から歓声が聞こえてきて、ネットの情報によると一駅ぶんだけ動くようになったという噂。しかし列はちっとも動かない。

午後 10:45 頃 動かない列から離脱

ちょっとだけ列が動いて希望が見える。が、その後全く動かなくなり、万博会場にもどれというアナウンスも聞こえたりして判断に悩む。待機列から離れる方法が見つかったので、列を出て万博会場に戻ることにする。

午後 11:00 頃 万博会場再入場

東ゲート付近で休んでいる人もいた

東ゲートから再入場。とりあえずトイレに入れてホッとする。男子トイレはほぼ並んでいない。自動販売機がちょっと混んでいたので、中央部から西の方が空いてるだろうと、静けさ森の方へ。予想があたって、ちょっとだけ並んで自販機で水とスポーツ飲料水を確保。その後西ゲートまで行って交通手段が無いか調べる。バスもタクシーも無く、タクシーアプリで送迎を呼べた人だけが出場しているということだった。危険なので予約のない人は会場から出ないでほしいというアナウンス。

自販機の行列

西ゲート付近は割とすいていて、ミャクミャク様も写真撮りやすかったりして普段は見れない景色を楽しむ。

人のいないコモンズ
ベルギー館のクッションが人気

午前 0:00 頃 よしもとパビリオン付近で仮眠

西ゲート付近で待機することにして、よしもとのパビリオン付近の床が柔らかいのでそこで雑魚寝することにする。まわりも似たような家族連れが多い。普段でも万博会場は午後8時くらいから食べ物が手に入らなくなるので、多少食料を持ってきたり軽食を取ってから来場していた。お陰で空腹感があまりなかったのは救いだった。そこからは、仮眠したりネットで情報収集しつつ過ごす。

午前 1:00 頃 バスが来るらしい

バスを手配するって知事は言ってるしなあと西ゲートに偵察にいくと臨時バスが来るという。いくつか開いてるパビリオンがあるというし、行ってみたい気もするけど明日も仕事だし子どもたちも大変だろうということで帰宅を目指すことにする。名残惜しいのでいろいろ写真をとる。

午前 1:30 頃 バスに乗る

弁天町までは行くがその後はわからんという条件でバスにのる。無料。JR西の車両位置情報を見ると弁天町の手前で待っている列車がいるので、なんとなく待ち合わせしてくれる期待がある。バス乗り場で阪急系列のスタッフさんが遅くまでお疲れ様でしたと声をかけてくれて、そちらも大変でしょうになどと思う。

午前 2:00 頃 弁天町到着

環状線の内回りも外回りも一本だけ臨時列車が出るとのこと。ただし、万博から脱出している臨時バスをすべて待つのでしばらく発車しないらしい。タクシーもタクシーアプリですぐ捕まえられそう。面白いので臨時列車を待つことにする。みんな座る席はあるので、子どもたちは寝ている。携帯で暇つぶし。

午前3:00 頃 臨時列車発車

最後のバスが到着して発車。大阪駅到着が 3:10。始発の表示が見える。その後最寄り駅で精算したときも、駅員さんがすごく優しくて、こんなの初詣みたいだよねえ。と笑って話して分かれる。みんな大変でしょうに。それから、いろいろ身の回りを整えて就寝。

latexdiff tips

3行で

  • tex で書いた文書の差分を可視化したいなら、overleaf でも使える latexdiff が便利
  • latexdiff の出力がコンパイルできないときは option オプションを調整するとよい
  • 差分をハイライトする方法を作ったので紹介する

論文の再提出で差分をハイライトしたPDFを提出せよ。と言われて色々工夫が必要だったのでメモを残す。同じように困る人もいそうなので。

latexdiff と overleaf

latex で書いた文章の差分を可視化するツールとして latexdiff というコマンドがある。削除した部分を赤字かつ取り消し線で表示し、追加した部分を青字して表示するというような事ができる。出力は tex ファイルとして出てくるので、latexコンパイルして表示する。

overleaf はオンラインで共著者と共同編集しながら tex を書いてコンパイルまでできる Web サービス。自分が学部生のころはこんな便利なものはなかったが、今はデファクトスタンダードのようだ。

local に tex 環境を用意しなくても overleaf で事足りるので、可能なら latexdiff も overleaf 内で完結したい。overleaf 公式もやり方を紹介していて、これに従えばほぼ問題なく使える。

この記事で紹介されている方法は、latexmkrc というlatexコンパイル時に読まれる設定ファイルを書き換えることで、コンパイル対象を latexdiff が生成した main-d.tex ファイルに変更している。この main-d.tex ファイルが正しくコンパイルできるなら何も問題はない。しかし時々 latexdiff はコンパイルできない tex を吐き出すので、なにも表示されず中間ファイルである main-d.tex も見ることができず、手詰まりになってしまう。

latexdiff のオプション

latexdiff はものすごい高機能なソフトウェアで、起動オプションもたくさんある。なるべくシンプルな diff を出すようにオプションを指定すると無事コンパイルできることがある。経験的にわかったおすすめオプションは以下のように -t CFONT だけつける。

latexdiff -t CFONT main_prev.tex main.tex > main-d.tex; pdflatex %O  main-d

完全に想像で書くけれど、マークアップ入れ子構造が壊れやすいようなオプションがあり、複雑な変更のdiffを出そうとしたときにうまく動かないように見える。上のおすすめオプションは比較的入れ子構造が壊れにくいのだろう。

diff をハイライトする

さて本題である。削除部分は表示せず、追加部分をハイライトするという diff を PDF にしたい。先に解決策を書こう。まずは、latexmkrc の中身。

$pdflatex = "latexdiff --no-del --graphics-markup=0 -p latexdiff_preamble.tex main_prev.tex main.tex > main-d.tex; pdflatex %O main-d"

--no-del は削除部分を表示しないオプション --graphics-markup=0 は追加した図表が青い枠で囲まれるのを防いでいる -p オプションで読み込んでいる latexdiff_preamble.tex は以下のような、tex ファイルの断片だ。このファイルも overleaf の中に作成しておく。

\RequirePackage{xcolor}
\providecommand{\DIFadd}[1]{{\protect\color[HTML]{1C2833} #1}}
\providecommand{\DIFdel}[1]{{\protect\color{red} \scriptsize #1}}

\providecommand{\DIFaddbegin}{}
\providecommand{\DIFaddend}{}
\providecommand{\DIFdelbegin}{}
\providecommand{\DIFdelend}{}
\providecommand{\DIFmodbegin}{}
\providecommand{\DIFmodend}{}

\providecommand{\DIFaddFL}[1]{\DIFadd{#1}}
\providecommand{\DIFdelFL}[1]{\DIFdel{#1}}
\providecommand{\DIFaddbeginFL}{}
\providecommand{\DIFaddendFL}{}
\providecommand{\DIFdelbeginFL}{}
\providecommand{\DIFdelendFL}{}

この -p オプションで、main-d.tex にどんなマークアップが適応されるか事細かに制御できる。詳細はマニュアルを見てほしい。簡単に説明すると、\DIF で始まるコマンドが変更箇所に差し込まれるので、そのコマンドの定義を latexdiff_preamble.tex に書いて渡している。ほとんどのコマンドは何もしない様になっていて、DIFadd と DIFdel だけが定義されている。削除部分は--no-delオプションで消してしまうので実質的には意味がない。DIFadd で追加部分が #1C2833 というかなり濃い灰色のテキストになるように指定している。

この latexmkrc で変更されたコンパイルが無事成功すると、ほぼ完成原稿と変わらない PDF が生成される。追加された箇所は濃い灰色なので目を凝らさないと他の文と見分けられないだろう。

さらにハイライトをこのファイルに施す。以下のような pythonスクリプトを使う。ChatGPT に作ってもらったものを少し改変している。

import sys

import fitz  # PyMuPDF


def main():
    # PDFを再度読み込み(赤文字のまま、ハイライトのみ追加)
    input_pdf_path = sys.argv[1]
    output_pdf_path = sys.argv[2]
    target_color = 0x1C2833
    doc = fitz.open(input_pdf_path)

    for page in doc:
        blocks = page.get_text("dict")["blocks"]
        for block in blocks:
            for line in block.get("lines", []):
                for span in line.get("spans", []):
                    if span["color"] == target_color:
                        rect = fitz.Rect(span["bbox"])
                        # 黄色でハイライトのみ追加(文字の色は変更しない)
                        page.add_highlight_annot(rect)

    # 保存
    doc.save(output_pdf_path)
    doc.close()


if __name__ == "__main__":
    main()

別案

別案として latexdiff_preamble.tex の冒頭を次のように書き換える手があり得る。

\RequirePackage{soul}
\providecommand{\DIFadd}[1]{\hl{#1}}

最初に紹介した方法のような回りくどいことをせずに直接的に追加部分をハイライトしてしまう方法だ。これでうまくいくならシンプルで良いのだが、差分が複雑だと大量にエラーのある main-d.tex が出力されてしまう。少なくとも私の環境ではうまくいかなかった。

年始からお手軽ベクトル検索を作る

あけましておめでとうございます。

今年は、もうちょっとblogを書こうかなということで、三が日のうちに1つ出してみようと思います。

さて、2023年はLLMの利用と同時にベクトル検索が急に利用されるようになった年でした。 Retrieval-Augmented Generation(RAG)をみんな使い出したのと、OpenAI の embedding APIの性能が思った以上に良かったことが主な理由だと思います。

ベクトル検索は faiss でもchromaでも、qdrant でも何を使ってもよいと思いますが、numpy を使えば数行で実装できるし性能も悪くないことがわかったので書き残しておきます。

import numpy as np
class SimpleVecSearch():
    def add(self, ndarray):
        self._ndarray = ndarray

    def search(self, vec, topk=10):
        scores = np.matmul(self._ndarray, vec)
        idx = np.argsort(scores)[-1:-(topk+1):-1]
        return [scores[i].item() for i in idx], list(idx)

if __name__ == '__main__':
    def gen_vecs(n, dtype='float32'):
        tmp = np.random.rand(n, 1536).astype(dtype)
        return tmp / np.array([[i] for i in np.linalg.norm(tmp, axis=1)])

    raw_index = gen_vecs(10**5)
    simple_index = SimpleVecSearch()
    simple_index.add(raw_index)

    vec = np.random.rand(1536)
    d, i = mlx_index.search(vec, 1)

if 文以下はテスト用コードなので、実装自体は8行です。faiss に同じ入力をして結果が一致することも確認しています。インターフェースも faiss にそろえています。

性能比較

faiss vs numpy
クエリに一番近いベクトルを見つけるのに必要だった時間を、データ量毎に計測しました。100回の検索を行った平均値で単位は秒です。 総当り探索なので検索対象のデータ量(横軸のデータ数)が増えると線形に計算時間が増えます。n=1000までは numpy の方が速く、その後 faiss に抜かれますが実用の範囲だと思います。どのみち、query 用のベクトルを取得するのに OpenAI の API を呼び出すのに数10msecから数100msecかかるはずなので。 まあそれに誤差もデカいのであまり差が無いという以上のことが言える差では無さそうです。

テストコードの全体はこちらにあります。

背景

OpenAI の embedding API は、文章を 1536次元のベクトルに変換して返してくれます。 大雑把に言うと、このベクトルを比較して文章が似ているかどうかを判定し一番似ている文章を返すというのがベクトル検索のキモです。

似ている度合いの計算方法は色々ありますが、OpenAI は コサイン類似度の利用を推奨しています。 コサイン類似度の定義は以下のとおりです。

 \cos \theta =  \frac{\langle x,y\rangle}{||x|| ~||y||}

x, y はベクトルで、 \langle x,y\rangle は xとy の内積 ||x|| はベクトル x の大きさです。 そして、Open AI の embedding API はベクトルを大きさ1に正規化して返すので、コサイン類似度の分母は常に1になります。 ということは、ベクトルの内積を計算するだけで類似度が計算できます。

コサイン類似度はベクトルとベクトルの内積によって計算されますが、ベクトル検索で行いたいのは多数のベクトル(本稿ではindexと呼ぶ)から、あるベクトル(本稿ではqueryと呼ぶ)に一番近いベクトルを見つけることです。なので、index の方を行列として保持しておいて、行列とベクトルの掛け算で一気に類似度を計算してしまおうというのが最初のコードの内容です。

高速化

上記の通りindex にある全てのベクトルとqueyrのベクトル比較する総当りアルゴリズムなので、正確ですが速くはないはずです。 が、faiss の IndexFlatIP インデックスも同じ手法なので速度的にはあまり変わりません。

nが大きいとfaiss のほうがちょっと速いんだし、faiss でええやんという話ではあるので高速化を試みます。 ちょうど昨年末に appleMLX という apple silicon 向けの numpy 互換ライブラリを出しました。apple silicon の GPU を使って線形代数計算を高速に計算するものですので、これを使ってみます。 numpy を mlx.core に書き換えるだけなので、コードはほぼ変わりません。

import mlx.core as mx
class MLXVecSearch():
    def __init__(self, stream=mx.gpu):
        self._stream = stream

    def add(self, ndarray):
        self._ndarray = mx.array(ndarray)

    def search(self, _vec, topk=10):
        vec = mx.array(_vec)
        scores = mx.matmul(self._ndarray, vec, stream=self._stream)
        idx = mx.argsort(scores, stream=self._stream)[-1:-(topk+1):-1]
        return [scores[i].item() for i in idx], [i.item() for i in idx]

コンストラクタで stream を指定できるようにしていて、mx.gpu と mx.cpu を切り替えると GPU計算 CPU 計算を切り替えられます。 で、肝心の性能はこんな感じ

faiss vs numpy vs mlx

n=105 以上ではfaissを抜いて最速です。(ただし精度的な問題があるので後述します。) では、n=107 以上では?と思うところですが、メモリ不足のため私の環境では実験出来ませんでした。 これは faiss でも同じで、そこそこ巨大な行列をメモリ上に展開するので、メモリが確保できなくて落ちます。 OpenAI の返すベクトルの次元を大雑把に 2*103 次元と考えると、1つの次元を表すのに、float32 = 32bit = 4 Byte が必要ですから、ベクトル全体では 4 Byte * 2 * 103 で 8 kB です。 そのベクトルが 107 個有ると 8kB * 10 ^7 = 80 GB となるので、24GB しか積んでない私の MacBook Air M2 では確保できないのは道理ですね。

107 を超えるような文書数があるならば、もっと高尚なアルゴリズムを使うべきで、この資料の案内が頼りになります。

speakerdeck.com

実用的にはRAG に入れたい文書数が1000万文書もあるというのは相当稀なことで、あって数千というところなのではと想像するところです。 その程度であれば最初に掲載した numpy 実装でも十分なはずで、使う機会も有るかなと思います。

MLX の謎挙動

手軽に numpy を高速化してくれるので、MLX いいね!と言いたいところなのですが、微妙に計算結果が numpy と異なることがあります。 これは stream を GPU にした時だけに起こるもので、CPU を指定したときには起きません。追いかけると楽しいかもですが、現時点ではよく分からず。 精度の違いも疑ったのですが、numpy も MLX も float32 を利用しており精度の問題では無さそう。アーキテクチャが違う計算機なんだからこのぐらい誤差も有るでしょという話かもしれません。 RAG で使う分には問題にはならない気もしますが、numpy や faiss と一致しないことは覚えて置いたほうが良いでしょう。

計算が一致しない例の再現コード

matrix = [[0.58551717, 0.13656957],
                 [0.12464499, 0.09199308]]

np_matrix = np.array(matrix, dtype='float32')
mx_matrix = mx.array(matrix, dtype=mx.float32)

a = np.matmul(np_matrix[0],np_matrix[1]).item()
b = mx.matmul(mx_matrix[0], mx_matrix[1],stream=mx.gpu).item()
print(a) #  => 0.08554524183273315
print(b) #  => 0.08554523438215256

本稿の詰めが甘い部分

numpy が利用している BLAS ライブラリがもっと高速なら、numpy でも fiass に勝てるのでは? とか、ARM64 の SIMD 命令を直接呼べばもっと高速なのでは? など追いかけると面白そうなところは色々あるのですが、今回はお手軽に実装してみるのがテーマなので深掘りはやめておきます。

まとめ

  • 数行のコードで実用的なベクトル検索が実装出来ることを示しました
  • MLX は速いけど謎挙動があります。まだ0.0.6だしね。
  • ことしはもうちょい blog 書くぞ

GPT-4 は高度情報処理技術者試験(午前I)に合格する

GPT-4は医師国家試験に合格するという研究結果が発表されて話題だったので、我々も馴染み深い IPA の試験にGPT-4は合格できるのか試してみた。 高度情報処理技術者試験の 午前I に限って言えば合格しているので、レポートをこちらに置いておく。

github.com

まとめ

  • 高度情報処理技術者試験の共通科目である午前I に 合格できる解答(正答率6割を超える)をGPT-4は生成する
  • GPT-3.5 では合格できない。GPT-4 の賢さが際立つ
  • ちなみに図表読み取り問題は入力できないので、すべて不正解扱いした

やりかた

  • IPA の Webサイトから、2022年度秋試験の午前I問題のPDFを取得 (PDF)
  • Google DocsOCR 機能でテキスト取得
  • 手でコピペして整形
  • 整形したファイルは こちら にある
  • OpenAI の API に問い合わせて解答を取得。スクリプトこちら
  • 追試験に必要なものは、ぜんぶリポジトリに有るつもりなので、興味のある人は試してほしい。

感想

ためしに、ChatGPT(GPT-3.5) の Web UI で問題を2つ3つ出してみたところ、どれも正解したので、これはもしやと思ってちゃんと実験環境を整えてみた。GPT-3.5 は不得意な問題が結構あって結果としては正解率50%で惨敗(レポートはこちら)。 GPT-4 はお値段高いし、遅いし、動作も不安定だが驚異の正答率96%を誇った。もちろんこの数字は、入力が可能な問題に限った話で図表問題は含んでいない。とは言え図表も入力方法が無いわけではないので、時間をかけて入力フォーマットを整えれば正解してしまうかもしれない。

GPT-3.5 は一見正しそうな解説から間違った答えを導いていて読んでいて微笑ましい。解説をせよという指示を省いて、解答だけを求めると正答率があがるという挙動もあり、解説を作りながら間違った方向へ文章を並べているような印象がある。この挙動の確認のコードはリポジトリには含めていないので興味のある人はコードを改造して試して欲しい。GPT-4 では、解答可能な問題の中で唯一間違った問題(問8)でさえ解説は正しい文章になっており、なぜ最後にその解答を選んだ...という面白さがある。

基本的に GPT シリーズは算数が苦手なのだが、GPT-4 ではそこもかなり改善されているようだ。

午前I の問題 30問は、応用情報の午前問題 80問 からの抜粋なので、応用情報の問題を全部整形して、GPT-4 は応用情報に合格できるぞ! などと言ってみたかったのだが、午後問題の入力の面倒くささの前に挫折してしまった。チャレンジする人を待ちたい。