詩と創作・思索のひろば

ドキドキギュンギュンダイアリーです!!!

Fork me on GitHub

ポケモンタイプ相性チェッカをSvelteで作り直した

タイプ相性チェッカとは昔作ったこれ。

ポケモンタイプ相性チェッカ(SV対応)

なのだけど前回の記事が2014年で大昔だった。このあととくにメンテもせず、「サンムーン対応」、「剣盾対応」……などと新タイトルにあわせて title タグだけ更新する日々を送っていた(タイプ相性が変わらないので対応してるのは間違いない)。当時は AngularJS を使っていて、流石に古いので更新しなきゃなーと思っていたところだったので、試してみるいいきっかけだった。

GitHub - motemen/pokemon-type-effects at svelte

Svelte は軽量で、ちょっとしたものを作るにはいいが、常用するかなー、どうだろう。TypeScript が普通に使えるのは快適(これはもう必須と言っていい)だけど、.svelte ファイルが必要なこととか、$: によるリアクティビティ(すぐに慣れた)は別の言語を触っているような感じがあって、これに乗っちゃっていいのかで少し迷ってしまう。全体的にはエディタの支援で問題なく書けたけど、逆にこれがないと手も足も出ない感もあった。

GPT-2でブログ記事のタイトルをTogetterまとめ風にする「面白いのでやってみて」

オレ定義だけど Togetter まとめ風というのはこういうやつ。

散歩で急にシロクマと会っても食べるのは肉だけにしたほうがいい「肝臓1gに含まれるビタミンAが致死量を超える」 - Togetter

まとめタイトルの終わりに誰かのツイートの引用を挿入する、という形式。よくできたもので、誰かの生の声が入っているだけで、感想やハイライトを抽出し、ちょっと気を引くことができる。まあ一種の演出で、ニュースサイトがやってることもある。

タイトルでアテンションを奪い合わなければならない宿命におけるクリック最適化の手法ということだろう。今回はこれを真似してみることにする。すでに書かれた自分のブログ記事に、括弧書きでセリフっぽいものの引用を捏造して付け加えることで魅力がアップするのか、という実験だ。

こういう生成系のタスクも、とりあえず HuggingFace+Google Colaboratory でやっちゃうか、で十分なクオリティでできちゃうので大変ありがたい。今回は日本語版 GPT-2 の定番、rinna/japanese-gpt2-medium をファインチューニングする。 教師データとするのはここ一年くらいの Togetter の人気まとめで、タイトルが /「.+」$/ な感じで終わっているもの。数えてみたところ 1/3 くらいがオレ定義の Togetter まとめ風タイトルだった。

このタイトルを、たとえば最初のまとめであれば以下のように分解する。

<s>散歩で急にシロクマと会っても食べるのは肉だけにしたほうがいい</s>[SEP]<s>「肝臓1gに含まれるビタミンAが致死量を超える」</s>

そして最初の文から2つ目の文を生成する、というタスクとして学習させる。このへんも transformers にサンプルスクリプトがあるのでとくにコードを書かずにできてしまう。

Colab のリソースの限られた環境でやるならこんな感じ。(最後の方にノートブックを貼っています)

!python ./examples/pytorch/language-modeling/run_clm.py \
    --model_name_or_path=rinna/japanese-gpt2-medium \
    --train_file=../train.txt \
    --do_train \
    --num_train_epochs=5 \
    --save_steps=1000 \
    --save_total_limit=3 \
    --per_device_train_batch_size=1 \
    --per_device_eval_batch_size=1 \
    --output_dir=output \
    --overwrite_output_dir \
    --use_fast_tokenizer=False \
    --block_size=256

10分くらいで終わる。そしたらこうして

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

tokenizer = AutoTokenizer.from_pretrained("rinna/japanese-gpt2-medium")
tokenizer.do_lower_case = True
model = AutoModelForCausalLM.from_pretrained('./output')
model.to(device)
model.eval()

こうじゃ

input_text = '<s>進化中のニャオハに屈伸煽りすると立つ</s>[SEP]';
input_ids = tokenizer.encode(input_text, return_tensors='pt').to(device)
out = model.generate(input_ids, do_sample=True, top_p=0.95, top_k=100, 
                     num_return_sequences=10, max_length=64)

for sent in tokenizer.batch_decode(out, skip_special_tokens=True):
  print(sent)
The attention mask and the pad token id were not set. As a consequence, you may observe unexpected behavior. Please pass your input's `attention_mask` to obtain reliable results.
Setting `pad_token_id` to `eos_token_id`:2 for open-end generation.
A decoder-only architecture is being used, but right-padding was detected! For correct generation results, please set `padding_side='left'` when initializing the tokenizer.
進化中のニャオハに屈伸煽りすると立つ 「気を付けないと・・・」
進化中のニャオハに屈伸煽りすると立つ 「猫が立ってた」
進化中のニャオハに屈伸煽りすると立つ 「そんな事気にする事ない」
進化中のニャオハに屈伸煽りすると立つ 「可愛すぎる......」
進化中のニャオハに屈伸煽りすると立つ 「の技見たいに決まってんだろ!見てみろよ!」
進化中のニャオハに屈伸煽りすると立つ 「ニャオハには負けるな、私にも...」
進化中のニャオハに屈伸煽りすると立つ 「この技を使っても倒せない」
進化中のニャオハに屈伸煽りすると立つ 「ああ...」
進化中のニャオハに屈伸煽りすると立つ 「ネコ科になるとこのポーズになる」
進化中のニャオハに屈伸煽りすると立つ 「お前ら全員ヤバい奴だって分かってるんだから」

それっぽくなりましたね!

こんな調子で過去エントリにもセリフをつけてみる 「なるほどなぁ...」

自作したRISC-V向けCコンパイラでセルフホストまでこぎつけた - 詩と創作・思索のひろば

自作したrisc-v向けcコンパイラでセルフホストまでこぎつけた 「すごいなぁ」
自作したrisc-v向けcコンパイラでセルフホストまでこぎつけた 「コンパイラを作ったことある人、ぜひ教えて」
自作したrisc-v向けcコンパイラでセルフホストまでこぎつけた 「何のためにこれをやっているのか」
自作したrisc-v向けcコンパイラでセルフホストまでこぎつけた 「本業をほったらかしにして(笑)」
自作したrisc-v向けcコンパイラでセルフホストまでこぎつけた 「自前の環境構築に手間取ったので、記事としてまとめる」
自作したrisc-v向けcコンパイラでセルフホストまでこぎつけた 「あの時あのコードを書いていれば」
自作したrisc-v向けcコンパイラでセルフホストまでこぎつけた 「コードの可読性は大切」
自作したrisc-v向けcコンパイラでセルフホストまでこぎつけた 「夢のよう」
自作したrisc-v向けcコンパイラでセルフホストまでこぎつけた 「いい時代になったもんだ」
自作したrisc-v向けcコンパイラでセルフホストまでこぎつけた 「気が狂ったかと思った」

Chrome仕事術 - 詩と創作・思索のひろば

chrome仕事術 「デキる人、時間の使い方上手い」
chrome仕事術 「chromeは使いにくいが、dockは最強」
chrome仕事術 「chromeの拡張機能は必要経費だ」
chrome仕事術 「仕事なんて必要ないから早く寝なさい、早く準備して家事を終わらせなさい」
chrome仕事術 「本当にこれがいいんですか?」
chrome仕事術 「chromeにパスワードを保存しておけば、ログインのたびにパスワードを入力しなくてすむので楽」
chrome仕事術 「一度教えられたことは自分で調べる癖をつけないと忘れる」
chrome仕事術 「デコメイラストは一枚描くだけで満足してて全然仕事しないよね」
chrome仕事術 「初心者に優しい設計です」
chrome仕事術 「本当にこれだけでいいのか...」

今年の進捗プログレスバーSVG - 詩と創作・思索のひろば

今年の進捗プログレスバーsvg 「1〜100を表にしてる」
今年の進捗プログレスバーsvg 「このペースでいけば1年後には...
今年の進捗プログレスバーsvg 「いや、あれはあれですごいんだよ」
今年の進捗プログレスバーsvg 「もし、プロジェクト全体が10年以上かけて出来上がっていたら」
今年の進捗プログレスバーsvg 「水星の日は何をしましょうか」
今年の進捗プログレスバーsvg 「来年は月でプログレスバーみたいにしてみようかなぁ」
今年の進捗プログレスバーsvg 「プロジェクトの開始時の進捗は何かを記録する良い指標になる」
今年の進捗プログレスバーsvg 「どこまで進んでいるんだw」
今年の進捗プログレスバーsvg 「こんなに早く結果が出るとは...」
今年の進捗プログレスバーsvg 「やっとこさシステム要件が通って契約書締結できた」

Baba Is You をクリアした - 詩と創作・思索のひろば

baba is you をクリアしたが描いた絵に驚く
baba is you をクリアした
baba is you をクリアしたさんが『#スプラトゥーン2』で「イカれたイカれたプレイ」を披露している
baba is you をクリアした感想...「に落ちた」
baba is you をクリアしたさん
baba is you をクリアしたが「大好き」と言った曲→
baba is you をクリアしたから得るもの
baba is you をクリアしたが最高にカッコイイ
baba is you をクリアしたが「みんながみんなそうじゃないのよ」と言っていた
baba is you をクリアした 「でも全然わからん」

コードレビューのときに見ているところ - 詩と創作・思索のひろば

コードレビューのときに見ているところ 「コードを記述すればきれいなコードを書くことができるが、何も書かないと汚いコードを書くことになる」
コードレビューのときに見ているところ 「今まで気づかなかった」
コードレビューのときに見ているところ 「何でも知っている...」
コードレビューのときに見ているところ 「caffeoのレビューとかで見たやつ。このコード俺も書きたくなった」
コードレビューのときに見ているところ 「本当にこれは勉強になる」
コードレビューのときに見ているところ 「みんなこの検査結果みてどう思う?」
コードレビューのときに見ているところ 「どこが一番良いのか」
コードレビューのときに見ているところ 「なぜそんなにわかるのか?」
コードレビューのときに見ているところ 「コードのバグ報告とコードに対する指摘の違い」
コードレビューのときに見ているところ 「どのコードも大した改善じゃないのに、全員のテストが通るとこを見ると開発のすごさを感じる」

Baba Is You のエントリだけちょっと妙だけど、それ以外は「括弧書きでセリフをつける」というレギュレーションを普通にクリアしている。おそるべし。

実際につかった Colab のノートブック(の一部)はこれ。

https://gist.github.com/motemen/fb8682fcbaca3ea65d73ed8ed885be9d

おまけ: ライブデモ

このエントリで使ったのは medium 版だが、small 版を以下で試せます。そのうち消す。

自作したRISC-V向けCコンパイラでセルフホストまでこぎつけた

低レイヤを知りたい人のためのCコンパイラ作成入門

まさに低レイヤのことが分かっておらず、以前から気になっていたこの本。取り掛かってみたところ思いのほかスイスイ進められて、勢いに乗ってセルフホスト(自分が書いたコンパイラで自分自身をコンパイルするところ)までいけたので記念に書いておく。正確には C コンパイラのサブセットです。

GitHub - motemen/mocc

全体的な進め方は、

  • 上記の本の通りに進めていく。
  • それ以降は自作の 8queen が普通に書けるように機能を強化。
  • それ以降はセルフホストを目標に進める。
    • プリプロセッサやリンカは作らず、C からアセンブリまで。

という感じ。自分は手を動かさないと進んでる気がしないので、まずは書いてみつつわからない所があれば調べる、というスタンスでいく。

あと、せっかくなので RISC-V の勉強もしたかったのでこれ向けに書く。なので実行はエミュレータ上ということになっている。機械と会話できてる感は結局少ないので、実機がほしいところだ。

よく参考にしたのは:

とか。

ちなみに GitHub Copilot を有効にしたら補完されまくってモリモリ書けたのだが、これはたぶん本家の 9cc や chibicc 由来のコードなので細かいテクは自力では書いてないのであった。

一方でネタバレはいかんよな、と思って、やっている間はあまり他の挑戦者の記録を読んでなかったのだけど、セルフホストCコンパイラaqcc 開発記がとても面白かったので自分もログを思い出せるぶんだけ書いておくことにする。最初の方は Scrapbox にメモを取りつつやってたのだけど、なんとなくどう進めればよいかがわかるようになってからはコードを書く方に集中していたのであまりログがない。コミットログから思い出しつつ書く。

2022-10-XX (day 0)

  • まず RISC-V の開発環境を用意した。Docker で Linux 環境を用意するつもりだったがコンパイルに時間がかかりすぎて試行錯誤もままならず、結局 macOS に Homebrew でインストールすることにした: https://github.com/riscv-software-src/homebrew-riscv。なんかいろいろと楽ちんである。
  • spike /opt/homebrew/opt/riscv-pk/riscv64-unknown-elf/bin/pk <program> のようにすると実行できる。
  • この際どうも stderr への出力も stdout に出てる気がする。
  • あと最初に "bbl loader" と出るのも消せなそうなんでなんとかする。
  • なんだかんだでいちおう Ubuntu の devcontainer も用意したけど、結局 macOS でやるのが早くてそちらでしか書いてない。今も壊れている。

2022-10-27 (day 1)

  • とりあえずコード 42 で終了するプログラムを作るらしい。
  • なるほどコンパイラを作るって Hello, world からやるのかと思っていたけど最初はここからなのか、と道程を思って興奮する。
  • とはいえそもそもアセンブリがまずわからないのでコンパイル結果をばらすところから始める。gcc -S でアセンブリが出力できることを知る。このときは使いこなせてなかったけど、のちのち Compiler Explorer を多用するようになった。
  • そしてスタックがわからない。正確にはアセンブリでどう書くのかがわからない。このへんは最終的に Calling Convention をキーワードに、大学の講義資料などをあさってなんとなく掴んでいった。
  • RISC-V の ISA (Instruction set architecture) には poppush みたいな便利なものはないので自分で書く。
  • とりあえずスタックマシンができればスムーズで、この日は 比較演算子 あたりまで進む。
  • 最初の数コミットだけかっこつけて英語で書いてたが、自分のためのプロジェクトなので日本語で書くことにした。

2022-10-28 (day 2)

  • ローカル変数を実装。スタックがなんとなくわかってきた。いよいよメモリに値を書き込めるとなると不思議な感じだ。
  • 制御構造を実装。j でジャンプとかするとアセンブリ書いてる感じがする。構造化されたプログラムをフラットにしていく過程なのね。
  • 関数呼び出しも作っちゃう。再帰をともなう階乗まで書けて感激する。
  • スタックに push したものを pop し忘れることで実行時に例外が起きていてちょっとハマった。こういうときはアセンブリを一行一行読んでいく作業……。
  • 関数とローカル変数 まで到達。

2022-10-31 (day 3)

  • ポインタの実装。ここまでに左辺値を扱っていたのでその延長でアドレスを取得できるのはなるほど。
  • mov rax, [rax] を RISC-V でどう書くのか分からなかったけど、ld t0, 0(t0) のようだった。
  • 配列にもトライ。C の配列って要はポインタのことでしょ? という雑な理解をしていたが実装してみると難しい。構文解析時に a が配列変数だったら &a であるとしてみることにする。このコードはあとで捨てた。
  • 配列周りでなんだかアドホックなコードが増えてきて嫌になってくる。
  • ローカル変数がプログラム全体のスコープを持っていたのを関数スコープにする。

2022-11-01 (day 4)

  • テストが一つでも失敗したら残りのテストが走らないようになっていたけど、それだとエンバグしている箇所がひとつしか見つけられないので全て走らせるように変更。具体的には TAP を吐いて prove に食わせるようにする。使い慣れた感じだ。
  • 配列の実装を変更。a&a にしたときに、こちらの都合で生成した擬似的なノードであることをマークする。このコードも捨てた。
  • 結局変数の評価時に配列だったら特別な扱いをするようにしたらしい。

2022-11-02 (day 5)

  • グローバル変数を実装。LVar とほとんど同じ GVar が登場。データ構造も操作の関数もコピペで作る。せめてリストの操作だけでも同一にしたいと思うが、あまり込み入ったことをするとセルフホストが遠のきそうでできない(結局いまもできていない)。
  • 関数の宣言とグローバル変数の宣言の構文解析が混ざって読みにくくなった。
  • 文字列リテラルを実装。エスケープシーケンスとかを解釈せずにアセンブリに吐き出しても分かってくれる。それで疑問に思ったけどアセンブリの文字列リテラルの仕様はどこにあるんだ?
  • ファイルからの読み込みを実装。これはコピペ。
  • ステップ26: 入力をファイルから読む まで到達。

2022-11-03 (day 6)

  • ステップ27: 行コメントとブロックコメント まで到達。ここから先は自分で目標を持ってやっていく。8queen と、セルフホストである。
  • 8queen を動かすことにはあっさり成功。for の中で変数宣言できないし、変数宣言と同時に代入もできないし、文字リテラルがないので putchar() に数字を渡さなきゃいけないし、とひどいもんだがちゃんと動いている。なかなか愛着が湧いてきた。

2022-11-04 (day 7)

  • 1テストずつコンパイル→実行していた test.sh をひとつのプログラムである test/test.c に移行。自分のコンパイラがある程度は信頼できるようになってきたのでこういうこともできる。どこが壊れてるかわかんないのにテストをひとつのプログラムにまとめることなんてできないでしょ、と最初は思っていたものだけど。
  • これも TAP でテスト結果を出力するものである。
  • 8queen が通ってホッとしたので、ちょっとリファクタリングする。
  • 変数がスコープとして関数名を持っていたのを、Node にローカル変数を持たせるように変更。スコープという概念がここでまともな形で登場する。
  • セルフホストに必要な項目を書き出す。

2022-11-06 (day 8)

  • グローバル変数を初期化可能に。定数値の計算のコードを書くのは面倒だな?
  • ローカル変数も初期化。こちらは代入がなされたかのようにノードを生成する。
  • break を実装。親 while に対応するラベルに j する。break LABEL があったらもっと面倒だったに違いない。
  • あわせて、構文解析時にスコープを push/pop するようになる。

2022-11-07 (day 9)

  • continue を実装。
  • 自分自身をコンパイルさせようとすると最初に引っかかるところだからだと思うが、構造体に手を付けていく。parse_type() がめちゃくちゃになっていく……。
  • 構造体のメンバは LVar を再利用できそう。オフセットの計算がちょっと違うようだが。
  • というか構造体のメンバのアラインメントは ABI で定められていないの?

2022-11-08 (day 10)

  • 9cv という名前を mocc に変更した。文字の高さが揃って、可愛らしい。
  • LVar, GVar を Var に一本化。
  • struct A; みたいなのを許す。
  • 単項 ! に対応。
  • for (int i = 0;;) と宣言ができるようにした。for がスコープを持つようになった。
  • あわせて、codegen 時に for が始まったときにスタックを積むようになった。これは失敗だったのであとでやめた。

2022-11-09..11 (day 11..13)

  • enum に対応。enum で宣言されたアイテムは、その後の登場では定数値に置き換えるような実装。
  • typedef に対応。TY_INT や TY_ARRAY、TY_STRUCT と並んで TY_TYPEDEF という類を作る。
  • 可変長引数に対応。va_start の存在が謎すぎて実装できる気がしなかったが、簡単な場合についてはなんとなく分かった。スタックの根本のほうに引数たちを積んでおき、va_list ap はそこへのポインタになっているというわけだ。va_start の呼び出しだけ特別に処理をすることにする。
  • switch を実装してみる。switch の側から case を見つけに行ってるが、これ逆のほうがいいなあ。
  • extern に対応。

2022-11-16..17 (day 14..15)

  • ++ に対応。後置はやや面倒。
  • extern が Type の性質のように実装していたのを Var のものに変更。
  • ローカル変数のスコープの管理のため、スコープごとにスタックポインタを移動していた (day 10) のが break などと絡むと複雑になりすぎるので、構文解析時にスコープを関数のスコープにマージするよう変更。
  • break などのジャンプ先も、コード生成時に行っていたのを構文解析時にやるように変更してコード生成ではスコープをほとんど意識しなくていいようにした。
  • 空の return; に対応。
  • 一箇所でしか使っていなかった型キャストの実装をあきらめることにして、ソースコードから除いた。
  • 構造体の初期化も勢いで実装する。

2022-11-18 (day 16)

  • 関数の返り値の型をようやくチェックするように。
  • ヘッダファイルにセルフホスト向けの関数や変数宣言を無理やり書いていく。-D__mocc_self__ したときは #include をせずに自前の定義を使うような形だ。
  • この時点でセルフホストが達成できていそう。clang でコンパイルした mocc (stage1) でコンパイルした mocc (stage2) でテストが通るようになった。

2022-11-20 (day 17)

  • このエントリを書いていたら break の実装がおかしかったのを発見したので修正。
  • これで clang でコンパイルした mocc (stage1) でコンパイルした mocc (stage2) と、これでコンパイルした mocc (stage3) がまったく一致するようになった!!!

やってみて

  • この本ではとにかくまずはすばやく最終的な結果を出すことを目指し、早すぎる抽象化をせずに進めていくスタイルを強調していて、その通りにするとほんとうに進むのでめちゃくちゃ気持ちがいい。
  • 関数ポインタは未実装。あの奇怪な型宣言をどうパーズするのかを考えるとおもしろそうなのでやってみようかな。
  • 写経成分も多めでかなり端折りながらではあったけど、なかなか達成感があるし、勉強になった。自分は手触りがないとよく理解できないタチなのだけど、ABI (Application Binary Interface) がなんなのかということが肌でわかった部分は大きい。あと関数呼び出しの引数を後ろから評価する処理系がある気持ちもわかった……。
    • メチャ昔に JavaScript の処理系を書いたときは、適当に作ったらダイナミックスコープが生じてしまってナルホドと思ったものだけど、やってみないとわからないというのはこういうことだ。
  • あと VSCode + clangd のおかげで C を書く体験はかなり快適だった。15年前にバイトで書いてたころとはぜんぜん違う。
  • このあとはぼちぼちコードを綺麗にしていくことをしつつ余生を過ごしたい。

はてなで一緒に働きませんか?