月報 2024/04, 2024/05

Mon 01 Apr 2024

18:51:18 コマンドライン上でコマンドを編集して実行するテク

例えばファイル名の一括変換. カレントディレクトリ直下に A_[0-9]*.jpg が大量にあるときにすべてを B_[0-9]*.jpg に置換したい.

# ファイル一覧を確認
$ ls -1 A_*.jpg

# mv * * っぽいコマンドに整形
$ ls -1 A_*.jpg | sed 's/.*/mv & &/g'

# sed の 2 フラグを使うことで本当に欲しかったコマンドを得る
$ ls -1 A_*.jpg | sed 's/.*/mv & &/g' | sed 's/A/B/2'

# 最後に実行
$ ls -1 A_*.jpg | sed 's/.*/mv & &/g' | sed 's/A/B/2' | sh

例えば xargs なんかが少しだけ近いことが出来るからついそれを使おうとする人を見るが, あれは高級すぎるし, インターフェースも良いとは思わない. 素朴に実行したいだけなら sed ... | sh で良い. コマンドを逐次作りながら, 正しいコマンドが出来たのを確認して初めて sh に流すのがコツ.

いっそエディタを利用してしまう場合. 自分でコマンドを書く. 例えば次のようなスクリプト (edit してから shell に流すので esh と名付けよう) を用意する.

#!/bin/bash

TMP=/tmp/esh
if [ ! -t 0 ]; then
  cat >$TMP
else
  : >$TMP
fi

$EDITOR $TMP
$SHELL $TMP

EDITOR には自分が使い慣れてるエディタを登録しておく.

# ファイル一覧を確認
$ ls -1 A_*.jpg

# ある程度の形は最低限作っておく
$ ls -1 A_*.jpg | sed 's/.*/mv & &/g'

# エディタが起動, ファイルを保存してエディタを終了したら実行される
$ ls -1 A_*.jpg | sed 's/.*/mv & &/g' | esh

ただし書きかけて, やっぱやめたとなってもエディタを終了したら問答無用で実行してしまうので, 途中で辞める場合は空にして保存すること.

Sat 06 Apr 2024

15:17:47

バイクの初回点検ををやった. オイルとオイルフィルターの交換も言われるがままにやった. 初回点検は1年毎、オイル交換も1年毎. オイルフィルターの交換はオイル交換の二回に一回. 多いな.

Mon 08 Apr 2024

14:45:22 Tolass_v4

Tolass4.5 にアップデートしてゼロから作り直す

注意/方針

やることリスト

20:05:36

2月と3月の月報を今ようやく書いた. 内容が無さすぎるね. 今更振り返ってみようか.

20:18:07

こないだの日曜日, これに行ってきた.

123.8km/6.04L = 20.496689 km/L

Tue 09 Apr 2024

18:53:40

バイクの取扱説明書をそういえば今初めて開いて読んでみた. メーターの機能で初めて知ったものがある. ボタンが2つあるのは知ってたんだけどそのうちの一つしか使ってなかったもんな. なんと燃費や航続距離を計測する機能がもう備わってるらしい. いつも給油のタイミングで Trip B を使って自分で割り算してたね. あと時計を12時間制から24時間制にすることが出来るらしい. これは本当に欲しかった機能だ. 自慢じゃないが私は12時間制が苦手で, 午前0時(あるいは午前12時)がいつなのか未だに分かってない. そもそも0時と12時って何か違うんですか?

Thu 11 Apr 2024

20:04:51

具体的にコードを書いてしまう前に五分間だけでもいいから, 自然言語で書き出してまとめるのが大切. 本当に.

Sun 14 Apr 2024

19:03:40

土曜日, 浅草の銭湯に行った. 雷門地下駐車場はバイクが12台停められる. 出かける前にはバイク用の駐輪場を調べるのだが, 一箇所だけだと満車の場合に困るので予備を用意しておきたい. だが浅草周辺にはここ一箇所しか見つからなかった. ここが満車だったら今日の予定は丸々変更するつもりだったが, 3台ほど空いていた. 帰る際には満車だったので, いつも空いているということは全くなくて, 時間帯に依るだけだ.

今日, 近所のオリンピックに文鳥用の止まり木を買いに行った. 一つは今まで使ってたものと全く同じモノを(汚れていたので買い替えたかったのだ), 一つは少し変わってて, 今までより少し細くて, 扇状に90度曲がっている. 水浴び場の前の止まり木をこちらに差し替えてみた. 今のところは怖がって使ってくれる様子が無い. いつものことなので気にしない. そのうち, 何も気にせず使うようになるだろう. 逆に今までと全く同じものを新品に変えた箇所は全く気づいてないようで同じように使ってる. 匂いなんかではなくて見た目で判断してるらしい.

皇居を内回りに一周して給油して帰宅した. 桜はすっかり散って葉桜になっていた.

Tue 16 Apr 2024

19:13:45

Netflix の論文.

コサイン類似度を取って類似度とすることはよくあるが気をつけないといけない. 特に, 高次元ベクトルでのコサイン類似度と低次元ベクトルでのコサイン類似度は振る舞いが違う. 内積を取るだけのが良いということはよくある. 線形モデル(行列分解など)の場合を解析してみる.

\(n\) 点のデータを \(p\) 次元特徴ベクトルで表現したものである \(n \times p\) 行列 \(X\) を行列分解することを考える. 例えば \(X\) は user vs item の行列. \(p\) より小さな \(k\) という値を選んで, \(p \times k\) 行列 \(A, B\) を用意して

\[X = X A B^t\]

という近似を考える. これは \(p\) 次元ベクトルを auto-encode した形式になってる. このとき \(XA\) の各行はデータ (user) の情報をより良く圧縮表現したベクトルになってる. 一方で \(B\) の各行は item の特徴ベクトルになってる. これらの行列積(つまり内積)を取るともとに戻るので, user vs item の適合度になる.

\(X = WH\) みたいな分解もあるけど, 以降の議論はこれらでも同じことが言えると思います.

しかし一方でよくやるのは, \(XA\) の行同士のコサイン類似度を取ってユーザーの類似度としたり, \(B\) の行同士のコサイン類似度を取ってアイテムの類似度にしたりする.

さて \(X = X A B^t\) が得られたとする. 任意の回転行列 \(R\) を考える. 回転行列というのは回転行列のことだけど, \(R R^t = 1\) が成り立つ. なので \(A, B\) の代わりに \(AR, BR\) というのを考えてもこれも行列分解の解として等価. すべてが同じように回転されるので, この \(R\) を掛けるという操作はコサイン類似度も内積も保存する.

次にスカラー倍について考える. 対角行列 \(D\) を掛けて \(A\) の代わりに \(AD\) にすることは各 user ベクトルを \(D\) 倍することになる. \(X = X A B^t = X A D D^{-1} B^t\) なので, \(A, B\) の代わりに \(AD, BD^{-1}\) を使ってもこれも解になってる. こちらは内積もコサイン類似度も全く保存しない.

じゃあどうしたらいいか?

そもそも学習は内積なのにコサイン類似度を使ってるのがダメ やれることは2つあって

  1. はじめからコサイン類似度で学習する
  2. 埋め込み空間で測るのをやめる

1 については Layer Normalization を見ろ.

2 について. 行列分解というのは掛け算すれば初めの空間に戻すことが出来る. つまり, \(X = XAB\) の場合は右辺の \(XAB\) を使えば良い. これは raw data \(X\) に対する smoothed version だと言える. \(XAB\) の各行が user feature vector だし, 各列が item feature vector だ. この空間でならコサイン類似度に意味がある (かもしれない).

実験 Figure 1. が実験結果. user-item のインタラクションデータをダミーで作る. item はあらかじめクラスタに分けられていて, 同じクラスタに所属する item は 100% 類似していて, user は同じ確率でインタラクトしてる. 一番左の図は理想的な item-item 類似度. 同じクラスタに所属していれば類似度 100%.

真ん中の3枚は Eq.1 による item-item コサイン類似度. Eq.1 をどうやって解いたかというと SVD 分解をした. \(V\) というのが truncated SVD によるもの. \(X = USV\) にした. ここから B を作る方法が何通りかあるのでそれらを試した感じ. 大雑把に書くと \(B = V S^{1/2}\) にするのと \(B=V\) にするのと, \(B = VS\) にするのがある (表記は適当です). これは自由に re-scaling 出来るので行列分解の解としてはどれでも問題ない.

一番右は Eq.2 によるもの. これは解き方としてただ一通りしか得られない.

意外に使えるようなモノもあるので不思議だね. 一般に「行列分解の解だから, コサイン類似度が使える」は成り立たない, という話でしかない.

Fri 19 Apr 2024

16:42:45

Python だけで書く web フレームワークというものがある. 「だけで」というのは本当に「だけで」という意味.

簡単な例. value という変数があってこれをテキストとして表示する. ボタンを押すとこの値を変更して表示するテキストも変更したい.

import streamlit

value: int = 0
if streamlit.button('Click'):
    value = 42
streamlit.write(value)

streamlit.button を呼んだ時点でボタンをページに表示する. クリックしたことのイベントに相当するのが if 文. if の中身はクリックされたときの処理を書く. 最後に streamlit.write でデータを出力してくれる. ちなみにデータの型は何でも良くて, 型によってよしなに見た目を変えてくれる.

普通に Python コードとして読めば分かるように, if の如何に関わらず最後の streamlit.write は発動する. ボタンをクリックすると「勝手に」 if の箇所から処理がやり直されるので, streamlit.write が勝手にやり直される. なので value が変われば勝手にテキストも変更される.

実行するには streamlit コマンドを使う. streamlit run main.py と実行することでサーバが立ち上がる.

一方で nicegui の例.

from nicegui import ui

value: int = 0


def update():
    value = 42
    label.set_text(str(value))


ui.button("Click", on_click=update)
label = ui.label(str(value))

ui.run(port=8081)

自分でボタンクリックイベントの処理 update を書かないといけない. ui.label がテキストの出力. これは勝手に中身を見て書き換えるようなことはないので, update の中で上書きする必要がある.

どうしても, 後で定義した label という変数を上で参照しないといけないので, コードを下から上にも読まないといけないのがある.

手元でやってる実験の可視化, デモを作るなら streamlit, web アプリとして作り込みたいなら nicegui かもしれない. ただしコントロールフローに関する作り方は全く異なるので, streamlit で書いてしまったものを nicegui で書き直すというのはややコストが高いと思う.

Sun 05 May 2024

14:04:24

X100VI が買えないので, 手持ちのX100F でまだ戦わなければいけない. 実は壊れてて, フォーカスモードがマニュアルにできない (AF-Cになる) のと, ファインダーをOVFに切り替えられない. 前回も一度壊れたことがあるのだが, 全く同じ壊れ方をしている. 前回の修理が不完全だったのか, もうこういう壊れ方をしてしまう性質なのか分からない. X100VI を(買えるとしたら)買うよりはずっと安いので修理に出すか, とインターネットで申し込もうと思ったら, ちょうど今年の三月でサポート終了したのを見つけてしまった.


修理受付終了製品 | FUJIFILM X Series & GFX - Japan
修理受付終了製品 2024年9月1日現在 修理受付終了製品一覧 Xシリーズ レンズ交換式カメラ X-A1(2021年6月)/ X-A2(2022年6月)/ X-A3(2024年3月)/ X-E1(2020年6月)/ X-E2(2021年6月)/ X-M1(2021年6月)/ X-Pro1(2021年6月)/ X-Pro2(2024年6月) X-T1(2023年2月)/ X-T2(2024年6月)/ X-T10(2022年6月)/ X-T20(2024年3月) Xマウント ズームレンズ XC16-50mmF3.5-5.6 OIS(2022年6月)/ XC50-230mmF4.5-6.7 OIS(2022年6月) コンパクトカメラ X10(2019年6月)/ X20(2020年6月)/ X30(2022年6月)/ X70(2023年2月)/ X100(2019年6月)/ X100S(2020年6月)/ X100T(2022年6月)/ X100F(2024年3月) XF1(2019年6月)/ XQ1(2020年6月)/ XQ2(2021年6月)/ X-S1(2019年6月) FinePixシリーズ A / AV / AX シリーズ A100 / A101 / A170 / A201 / A202 / A203 / A210 / A220 / A303 / A310 / […]
 
fujifilm-x.com/ja-jp/support/repair/closed/

おそらくは, 最新の二世代までしかサポートしてないんだろう. しかし, 勝手に最新作を出しておいて, しかもそれが買えない状態で, 勝手に古いのをサポート終了するのは腹立たしい.

Tue 07 May 2024

17:40:33


VB-Audio Licensing

 
vb-audio.com/Services/licensing.htm

買ったもの

正直これらは Windows が標準で実装すべきだと思うんだけどな.

運用

Windows11 なら 設定 > システム > サウンド > 音量ミキサー でアプリごとのマイク入力/スピーカー出力のデバイスが選べるので, ここで CABLE-A と CABLE-B を切り分ける.

わざわざ Voicemeeter を経由させる良さがあって, 複数の入力を複数の出力に自由に合流/分岐ができる (N-in, M-out). 例えばデスクトップ上で流す音楽を VRC へのマイク入力にしながら自分はヘッドセットで聞くとか. Voicemeeter は Banana という無料お試しバージョンもあるが, その扱えるデバイスの個数 (N, M) に制限がある. 有料版の Potato でも N=M=5 という上限があるけどね.

Wed 15 May 2024

19:51:13 Cover の定理


Cover's theorem - Wikipedia

 
en.wikipedia.org/wiki/Cover%27s_theorem

大雑把にいえば、高次元空間に非線形に移せば線形分離できるようになるよ、という定理らしい. 詳しくはまだ分かってない.

元論文は

一旦 Wikipedia だけ読むか.

\(D\) 次元空間に一般的な位置にある \(N\) 点についてありえる線形分離の方法 (同じように点を分離する平面は同じとみなした場合の平面の個数) を \(C(N,D)\) 個だとする.

\(C\) について次がなりたつ.

\[C(N+1,D) = C(N,D) + C(N,D-1)\]

このことの帰結として次を得る.

得られる知見

論文もちらっと読むか.

\(C\) は陽に書き下すことが出来て

\[C(N,D) = 2 \sum_{k=0}^{D-1} \left( N-1; k \right)\]

ここで \(\left( a ; b \right)\) は二項係数です. \(C(N+1,D) = C(N,D) + C(N,D-1)\) もじゃあ展開すればたぶん確かめられる.

Mon 20 May 2024

15:14:09

これは五億回言ってることだが join と split って, あたかも逆関数に見えてしまう.

join は文字列の列 \(A\) と, 区切り文字として使う文字 \(d\) を受け取って, \(A\) の各値を \(d\) で接続した一つの文字列 \(x\) を返す.

\[[S] \times S \to S\] \[(A, d) \mapsto x\]

\(S\) は文字列という型で \([S]\) はそのリストというつもりで書いた.

split は一つの文字列 \(x\) と区切り文字とする文字 \(d\) を受け取って, \(x\) を \(d\) で区切って出来た文字列のリスト \(A\) を返す.

\[S \times S \to [S]\] \[(x, d) \mapsto A\]

区切り文字をお行儀よくしていれば, 大抵の場合はちょうど逆のことをしてくれる.

## Python

>>> ','.join(['a', 'b', 'c'])
'a,b,c'

>>> 'a,b,c'.split(',')
['a', 'b', 'c']

区切り文字 \(d\) は暗に固定するとして, 実際

\[\mathrm{join}(\mathrm{split}(x)) = x\]

が成り立つ. たぶん成り立ちそう.

逆は全く成り立たない ( \(\mathrm{split}(\mathrm{join}(x)) \ne x\) ).

# A の値に区切り文字 d が入ってる場合
>>> ','.join(['a,b']).split(',')
['a', 'b']

# A が空の場合
>>> ','.join([])
''
>>> ','.join([]).split(',')
['']

特に空文字列の split が空リストにならないのはなんだか直感に反する. 結合という意味で文字列の空とリストの空はそれぞれの単位元なのに, 単位元どうしが対応付いていないのって自然じゃないなあって.

ちなみにここからは Python の場合だけど, 区切り文字 \(d\) は省略可能で, ドキュメントの言葉を使えばこのときには全く違うアルゴリズムが動く.


Built-in Types
The following sections describe the standard types that are built into the interpreter. The principal built-in types are numerics, sequences, mappings, classes, instances and exceptions. Some colle...
 
docs.python.org/3/library/stdtypes.html#str.split
>>> '1   2 3'.split()
['1', '2', '3']

>>> ''.split()
[]

Sat 25 May 2024

17:16:12

八王子のオフハウス行ってきた. ハードオフにゲーム筐体が丸々置いてあってすごい. オフハウスはお皿とかが安い. ブックオフは小さくて残念だった.

160.1 km / 6.9 L => 23.2 km/L

Sun 26 May 2024

13:04:37

chatGPT を使ったコマンドツール

GPT なので, 翻訳/言い換え/補完 であれば普通は出来ないことが容易にできる. ChatGPT 自体はチャット形式でAPIが公開されているが, 毎回新しいチャットを開いて利用するのも馬鹿らしいのでコマンドとして用意してる.

この手の簡単な (エイリアス以上スクリプト以下くらいの) コマンドを私は


GitHub - cympfh/bin: ~/bin
~/bin. Contribute to cympfh/bin development by creating an account on GitHub.
 
github.com/cympfh/bin

で管理してる.

翻訳

タスクとしては一番素直. 初め DeepL を使ってたのでその名残がまだあるがもう ChatGPT でしか使ってない. 翻訳元 (ソース) の言語が何であるかを指定できるようになってるが, ChatGPT は賢いので指定する必要がない. 一応その場合には「自分で推測してくれ」と言ってる.

$ translate -t zh '北京ダックを朝ご飯に食べた'
{
    "detected_source_language": "JA",
    "target_language": "ZH",
    "model": "gpt",
    "text": "早餐吃了北京烤鸭"
}

プログラムコードの生成

自然言語で指示してプログラムコードに変換してもらう. これもよくあるタスク. 補完モードとチャットモードを用意したが専らチャットモードで使ってる.

$ codegpt chat -L python 'fizzbuzz コード書いて'
Running...
for i in range(1, 101):
    if i % 15 == 0:
        print("FizzBuzz")
    elif i % 3 == 0:
        print("Fizz")
    elif i % 5 == 0:
        print("Buzz")
    else:
        print(i)

でも, 実際にはコマンドの使い方を聞くようなケースが多い. Stackoverflow とか見ることが無くなった.

$ codegpt chat -L kubectl 'Evicted された pod を全列挙して'
Running...
get pods --field-selector=status.phase=Failed -o json | jq -r '.items[] | select(.status.reason=="Evicted") | .metadata.name'

論文の翻訳/要約

一発で翻訳して要約してもらってそれを読む, というスタイルも試したが私は結局こういうことにした.

  1. タイトルと Abstract を読ませる
  2. 以下ループ
    • 論文の断片をコピペして渡す
    • 「タイトル + Abstract + 断片」で断片部分を翻訳して要約してもらう

この 2 の部分をループで回す. 素直に使うと ChatGPT はチャットなので会話は普通は積み上げられるものだが, ループの中では直前の会話は毎回捨ててるので今渡した断片以外の部分は知らない.

一発で上手い要約は難しい. どうしても要約から漏れてる箇所を見つけてしまって追加でそこを質問したくなる. 或いは勝手に自分の持ってる知識を付け足して論文に書かれてないことを言い出す. それよりは一度に与える文章を少なくしてそこだけに集中させたほうがマシ.

中国語入力

いま中国語の学習をしており, PCで中国語の入力をたまにガッとやることがある. 日常的にやるならさすがに Microsoft Pinyin IME とか使うべきなんだけど, あくまでたまにだし, あとピンインを完璧に覚えてもいないので辛い. 毎回辞書でピンインを覚えてそれを入力するというのもある意味学習になるけど, それ辞書で調べるのと入力するというのを兼ねれば良いよね.

$ zhcomp 'nandao ni dou wang le ma?'
{ "completed": {"text": "难道你都忘了吗?", "pinyin": "nán dào nǐ dōu wàng le ma?" } }

# ピンインと日本の漢字を混ぜても無理やり中国語に修正してくれる
$ zhcomp 'keyi 部分含 日本的 hanzi'
{ "completed": {"text": "可以部分含日本的汉字", "pinyin": "kě yǐ bù fèn hán rì běn de hàn zì" } }

もっともピンインをASCII文字だけで書いても声調まで言わないと曖昧だし, なんなら声調を明記しても曖昧性がある (どの漢字を使うか一つに定まらない) ので, 欲しい漢字が出力されるまでたまに無駄な苦労をしないといけないことがある.

$ zhcomp 'ta hen gao'
{ "completed": { "text": "他很高", "pinyin": "tā hěn gāo" } }


$ zhcomp 'ta(女) hen gao'
{ "completed": { "text": "她很高", "pinyin": "tā hěn gāo" } }

他 (彼) も 她 (彼女) もどちらも同じ ta なので無理やり補足した.

そういえば ChatGPT は回答候補を複数出力させられる ので, それを使っても良いかもしれない. その分お金が掛かるけど.

Mon 27 May 2024

19:03:19

\(n\) 個の \(1\) の和は \(n\) であることの証明.

\(F_n = n\) とする. 差分 \(F_{n+1} - F_n\) を \(f_n\) と定義する. \(f_n = (n+1) - n = 1\) . 次の和を考える.

\[G = \sum_{k=1}^n f_k\]

\(f_n = F_{n+1}-F_n\) を代入してこれを展開すれば,

\[\begin{align*} G & = \sum_{k=1}^n (F_{k+1} - F_k) \\ & = \sum_{k=1}^n F_{k+1} - \sum_{k=1}^n F_{k} \\ & = F_{n+1} + \sum_{k=2}^n F_{k} - F_1 - \sum_{k=2}^n F_{k} \\ & = F_{n+1} - F_1 \\ & = (n+1) - 1 \\ & = n \\ \end{align*}\]

一方で, \(G = \sum_{k=1}^n f_n = 1 + 1 + \cdots + 1\) というように \(G\) は \(n\) 個の \(1\) の和でもある. 以上から \(1\) を \(n\) 個足すと \(n\) になることが示された.

同様に \(F_n=1\) からスタートすると \(0\) は \(n\) 個足しても \(0\) であることが示せる. すごいね.