洋食の日記

「だ・である」調ではなく「です・ます」調で書きはじめれば良かったなと後悔してる人のブログです

確率分布に従う乱数によるNumo::NArrayを生成するNumo::Randomを作った

はじめに

正規分布や二項分布などの確率分布に従う(疑似)乱数によるNumo::NArrayを生成するNumo::Randomを作った。アイディア自体はずっと前に考えていたが、腱鞘炎と忙しさで、形にできていなかった。

github.com

動機

Numo::NArray自体にも、一様乱数を生成する rand メソッドと、正規乱数を生成する rand_norm メソッドがある。さらに乱数のシードを指定する srand メソッドがある。シードを固定して、正規分布に従う乱数による大きさ100の配列を作るとすると次の様になる。

require 'numo/narray'

Numo::NArray.srand(42)
Numo::DFloat.new(100).rand_norm

とても簡単で便利だ。ただ、シードを固定する srand メソッドがグローバルに影響するので、あるメソッド内だけでシードを固定したいというのは難しい。

def hoge
  Numo::NArray.srand(42)
  Numo::DFloat.new(100).rand_norm
end

hoge

p Numo::DFloat.new(1).rand
# Numo::DFloat#shape=[1]
# [0.115514]

hoge

p Numo::DFloat.new(1).rand
# Numo::DFloat#shape=[1]
# [0.115514]

また、確率統計や機械学習のためには、一様分布や正規分布以外の確率分布も欲しくなるところ。

使い方

まず、Numo::Randomをインストールする。Numo::NArrayは依存Gemとして、一緒にインストールされる。

$ gem install numo-random

今回は、結果の可視化のために、Numo::Gnuplot(とgnuplot)をインストールする。

$ brew install gnuplot
$ gem install numo-gnuplot

Numo::Randomで、正規分布に従う乱数列の配列を作成する場合、次の様になる。

require 'numo/narray'
require 'numo/gnuplot'

require 'numo/random'

# 乱数生成器をシードを固定した作成する。
rng = Numo::Random::Generator.new(seed: 42)

# 平均0・分散1の正規分布に従う乱数による、大きさ (5000, 2) のNumo::DFloatを生成する。
x = rng.normal(shape: [5000, 2], loc: 0.0, scale: 1.0)

# 結果をプロットする。
Numo.gnuplot do
  set(terminal: 'png')
  set(output: 'normal2d.png')
  plot(x[true, 0], x[true, 1])
end

これを実行すると次の画像が得られる。中心が密で円形にサンプル点が広がっていて、正規分布だなという感じ。

Numo::Randomで作成した正規分布に従う乱数列

シードは乱数生成器のなかにあるので、グローバルな影響はない。

# シードを固定して乱数生成器を作成して、一様分布に従う乱数を得る。
rng_a = Numo::Random::Generator.new(seed: 8)
p rng_a.random
# 0.33322124834504996

# シードの異なる別の乱数生成器を生成しても、もとの乱数生成器が影響受けることはない。
rng_b = Numo::Random::Generator.new(seed: 42)
p rng_b.random
# 0.15802686859384152

rng_b = Numo::Random::Generator.new(seed: 42)
p rng_a.random
# 0.005744143787797302

一様分布や正規分布の他に、二項分布やガンマ分布に従う乱数生成メソッドを用意した。それはAPIドキュメントで。

yoshoku.github.io

実装

乱数生成アルゴリズムにはPermuted Congruential Generator (PCG) を利用した。高速で統計的性質が良いということで採用した。

www.pcg-random.org

PCGのC++実装が公式にあり、C++11から様々な確率分布に従う乱数生成ができるので、あとはこれらをnative extensionsでまとめ上げれば良いと考えた。

en.cppreference.com

Numo::NArrayな配列を生成した乱数で埋める共通ルーチンを書けば、あとは単純作業でメソッドを増やせる。少ないタイプ数で作れるので、腱鞘炎でも大丈夫だ。

おわりに

確率分布に従う乱数によるNumo::NArrayを生成するNumo::Randomを作った。今後のアップデートとしては、PCG以外の乱数生成アルゴリズムに対応することが考えられる。が、自分が欲しいと思っていたものはできたし、あとは手のダメージと相談といったところ。

帯状庖疹は痛いし長引く

帯状庖疹になった。これを書いてる時点で、発症から2週間ちょっとだが、ブツブツはあるし痒みと寝起きとかに若干の痛みがある。背中と腰にブツブツができた当初は「あせもができたかな?」と思ったが、すぐにロキソニンを飲まないと眠れないほどの痛みが起きて、皮膚科を受診すると帯状庖疹と診断された。「ストレスのかかるようなことがあったか?」と聞かれ、思い当たることが多すぎて「仕事がずっと忙しくて...」としか答えられなかった。実際に、外部要因でデスマの様になったり、夜間作業が多かったり、まあ色々あった。会社が吸収合併される前は、残業しないことが社の行動指針として掲げられていたのだが、今となってはただただ懐かしい。病院では、抗ウィルス薬と痛み止めを処方してもらった。抗ウィルス薬のおかげで症状の悪化は防げたし、痛みは5日間ぐらいで痛み止めを飲まなくてもまあ大丈夫ぐらいになった。安静にすることを勧められているので、本を読んで過ごしている。

OSS活動などプライベートの開発は、当然、超スローペース&最小限になっている。最近作ったのは、ランダム化特異値分解(randomized singular value decomposition)を行うnumo-linalg-randsvdで、これは5年くらい前にNMatrix向けに作ったモノのNumo::NArray向け移植になる。というのも、NMatrixがRuby 3に(というかRuby 2.7.xのdeprecation warningですら)対応してないので、いつかNumo::NArrayに移植したいと考えていた。ランダム化特異値分解に、どれだけ需要があるかはわからないが、大きな行列の打ち切り特異値分解(truncated singular value decomposition)を行う場合には、普通の特異値分解である Numo::Linalg.svd を実行して必要な特異値の数だけ抜き出すよりも速く動作する。

github.com

そんな近況でした。

ひどい腱鞘炎になると一年たってもなおらない

4月になった。手に痛みを感じて湿布貼ったりし始めたのが去年の4月頃なので、腱鞘炎になってだいたい1年がたった。個人的には、痛みを和らげる塗り薬よりも、サポーターでガチガチに固めるのとストレッチが効果があった。もちろん、休むのが大事で、土日にバリバリコードを書くのは控えている。サポーターは、腕も痛むようになったので、ひじ用のモノも買った。痛むのは、肘より下の部分だが、効果があった。

ストレッチは、腕を伸ばすのと、指を1本ずつ引っ張るのをやってる。↓の倉敷平成病院のPDFでいうと①④⑤になる。毎日続けてたら、ピーク時に比べて、かなりラクになった。

http://www.heisei.or.jp/wp-content/uploads/2015/05/nagomi201510reha.pdf

以上、近況でした。

ひどい腱鞘炎になると半年たってもなおらない

はじめに

今後のため記録として残す。タイトルのとおりで、手が全て腱鞘炎という、ひどい腱鞘炎になって半年以上が経ったが、完治する気配がない。今でも寝起きや仕事終わりでは、人差し指や小指は曲げると激痛が走る。原因は手指の使いすぎなので、今の仕事を辞めてしまって、3ヶ月とか半年とかのんびりすれば治るだろうが、長く無職できるほどの貯蓄はないので、働きながら治すよりほかない。

対策

まずサポーターをつけるようにした。痛みの軽減にも効果があるのかもしれないが、とにかくタイポングが楽になった。指に強い痛みがある場合は、指にもサポーターをつけて、手をガチガチに固定する。指が曲げられなくなるので、キーボードに指を突き刺すようにタイプする。タイポが増えるので、指サポーターは、我慢できないほど痛いときのみにしている。

中学生の頃からトラックボールを使っている。少し高いが、Logicoolの傾くものが一番よかった。同じLogicoolのグレードを落とした5,000円くらいのものを使っていたが、思っていた以上に、傾くことが重要だった。手の甲の痛みが、かなり軽減した。

キーボードはHHKBの静音のものに落ち着いた。赤軸の分割キーボードにしたことがあったが、キースイッチの跳ね返りが違うのか、指の関節が痛むようになった。HHKBの静音スイッチが、自分の指にはあっていたようだ。いまは、さらに衝撃を軽減するため、HHKBの下にタオルを敷いている。

パームレストは、FILCOの分割タイプのものを、キーボード用とトラックボール用で2セット買って使っている。分割タイプのほうが、置き方の融通がきいて良い。

プログラミング

とにかく手を使うのをやめることにした。土日は、映画を見るか、本を読むかになった。子どもの頃からプログラミングをしていたので、それを封じられるのが意外としんどい。深追いをせず最低限もので、それでいて身になることをやることにした。Next.jsとかのTutorialを潰していく生活をしている。そのなかでReact Nativeがだいぶおもしろかった。過去にモバイルアプリ開発はやっているが、IDE縛りがしんどかったりして、あまり開発が楽しい印象はなかった。React Nativeは、普段のVimでの開発と変わらないスタイルで、JS/TSでReactを書いていればモバイルアプリが作れる。Expoが優秀というか、これが煩わしい何もかもを吸収してる。外部ライブラリも色々あるので、やりたいことに対して、だいたい誰かが何か作っていて、それを使えば良い感じになっている。入門書では、以下のものが良かった。ひととおり、できるようになる。

映画

新型コロナで自粛生活なこともあってか、旅情感のあるものを欲するようになった。石坂浩二さんが演じる金田一耕助の獄門島は、音楽も映像の雰囲気も良い。瀬戸内海の島で連続殺人事件が起きるのだが、なんだか避暑地への旅行の雰囲気がある。

砂の器も、物語は重厚なものだが、捜査の過程に旅情感がある。前半、寺の門前で瓜を割って食べるシーンが良い。

メンタル

半年以上ケガしてる様なものなので、当然良くない。プログラミングを封じられてるのが良くない。以前は、実装が追いつかないほど、あれ作ろう、これを改良しようという感じだったが、今はなにも思いつかない。これは意外だった。最初は、アイディアを溜め込んでおいて、手が治ったら着手するイメージを持っていた。しかし、開発ができないと次第にやる気が失われ、アイディアも出なくなった。人生で一番空っぽな感覚がある。ただ大人しくはできてるので、腱鞘炎の治療としては良いのかもしれない。あと本もよく読んでいるので、インプットな時期と割り切っている。不思議と闘志みたいなものは失われないもので「手が治ったらデカい事やったるぞ(できるとは言ってない)」みたいなのは漠然とある。

おわりに

ひどい腱鞘炎になって、自分の体にリミットがあるというのを知った。年齢的なこともあるのだろう。手が動かせるようなったら、あれやこれやではなく、大きなことに集中して取り組みたい(漠然)。結局のところ、3ヶ月以上の休暇と、そのための財が欲しい。

近似最近傍探索ライブラリHnswlibのRuby bindingを作った

はじめに

Hnswlibは、C++で書かれたHierarchical Navigable Small World graphsによる近似最近傍探索ライブラリである。近似最近棒探索のベンチマークでも上位に登場する。Ruby bindingがなかったので作成した。

hnswlib | RubyGems.org | your community gem host

使い方

インストールは、普通にgemコマンドでインストールできる。外部ライブラリもPythonも必要ない。

$ gem install hnswlib

APIは単順にバインドしたものと、それらをラップしたAnnoyライクなHnswIndexを用意した。 検索インデックスの作成は、以下のようになる。データを追加すれば、それでグラフ構造が内部で作られるので、build_indexみたいなメソッドはない。 データベクトルはRuby Arrayで表す。

require 'hnswlib'

n_features = 40 # 検索インデックスに追加するベクトルの次元数
max_item = 1000 # 検索インデックスに追加するデータ数

t = Hnswlib::HnswIndex.new(n_features: n_features, max_item: max_item)

# ランダムな値からなるベクトルを1000件追加する.
max_item.times do |i|
  v = Array.new(n_features) { rand }
  t.add_item(i, v)
end

# インデックスを保存する.
t.save('foo.ann')

検索は、検索インデックス中のベクトルを番号で指定し、それをクエリとして検索を行うget_nns_by_itemと、クエリとして任意のベクトルを与えて検索を行うget_nns_by_vectorを用意した。

require 'hnswlib'

# 作成した検索インデックスを読み込む.
n_features = 40
max_item = 1000
u = Hnswlib::HnswIndex.new(n_features: n_features, max_item: max_item)
u.load('foo.ann')

# アイテム番号0番に近い10件を検索する.
# 近傍とされるアイテムの番号がRuby Arrayで返る.
neighbor_ids = t.get_nns_by_item(0, 10)

# 任意のベクトルに近い10件を検索する.
# include_distancesにtrueを与えると, 距離も返す.
q = Array.new(n_features) { rand }
neighbor_ids, neighbor_dists = t.get_nns_by_vector(1, 10, include_distances: true)

おわりに

Hnswlibは、ベンチマークではAnnoyよりも良い検索性能を得ているので、Annoyでイマイチなときに使ってみると良いかもしれない。

github.com

もう3ヶ月ぐらいヒドい腱鞘炎に悩まされていて、長時間の作業は難しく、オリジナルなものを作るのはシンドイので、bindingライブラリを作った。