洋食の日記

記事をです・ます調で書き始めれば良かったと後悔している人のブログです

native extensionを別なディレクトリに配置するディストリビューションがあるのでrequire_relativeするとLoadErrorになる

Rocky LinuxでdnfでインストールしたRubyで、 gem install native_extesionsなgem すると、native extensionsを/usr/local/lib64/gems以下に、通常のRubyスクリプトを/usr/local/share/gems以下に分けて配置される。そのため、Rubyスクリプトからnative extensionsをrequire_relativeで読み込もうとすると、全然違うディレクトリに配置されてるので見つけられずに、LoadErrorになる。おそらく他のRedHat系のLinuxもそうなんだろう。miseでローカルにインストールしてるRubyとかなら、同じ場所に配置される。なので、native exteionsはrequire_relativeじゃなくて、requireしよう。bundle gem --ext=c hoge で作られるスクリプトでは、require_relativeを使っているので要注意だ。

追記 2025/12/10

pull requesetを送りましてbundlerのv4.0.1で修正されてます: https://github.com/ruby/rubygems/blob/master/bundler/CHANGELOG.md#401-2025-12-09

Numo::NArrayをforkしました

Numo::NArrayは、PythonでいうところのNumpyで、Ruby機械学習・データ分析するのに基盤となるgemである。Numo::NArrayは、2022年8月20日にリリースされた0.9.2.1以降、新しいリリースがない(2025年10月9日現在)。リポジトリを見ると全く開発が停止しているわけではないが、新しいバージョンがリリースされる様子はない。

私自身、数年前にひどい腱鞘炎を患って、趣味の開発が大きく滞ったことがあった。人生ホントいろんなことが起きる。OSS作者は自身の生活を大切にするべきで、無理のないペースで開発すれば良いと思っている。*1

一方で、私が開発・公開してるgemの多くがNumo::NArrayに依存している。Numo::NArrayが、例えば今後のRubyの変更に対応しなかったら、Numo::NArrayの代わりとなるgemがないので、それで全滅となる。また、Pythonでnanobind経由でEigenを使うことがあるが、こちらも、つい最近3.4.1と5.0.0がリリースされるまで4年間ぐらいリリースがなくて、使い続けて大丈夫かと不安があった。

そういった事情で、Numo::NArrayをforkして自分でメンテすることにした。作者の田中先生にことわりを入れることができたら良かったが、private connectionはないし、SNSで突然DMするのも違うしで、黙ってforkしました。申し訳ありません。

github.com

Numo::NArrayはERBを駆使して、C言語ジェネリクス的なことをしながら、ビルド時にコードを生成している。そのため、C言語のなかにERBが含まれていて、clang-formatとかのlinterをどう使えば良いか謎だったので、ERBによるコード生成をやめた。それ以外に、環境依存のbugfixとかを入れて0.9.3としてリリースして、また別の環境依存でsegment faultが起きるのを見つけて、それを修正して0.9.4としてリリースして、covメソッド実装するために、Numo::Int32とか整数型のものにmeanメソッドを追加したものを0.9.5としてリリースした。また、テストを増やしたりもした。

Numo::LinalgもRumaleでは重要なので、これもforkしました。こちらは、Numo::Linalg自体ではなく、Numo::Linalgのnative extensionsなコードを、C言語+ERBではなくC++で実装できるんじゃないか?と思って実験的に作っていた自身のNumo::TinyLinalgからforkした。Numo::TinyLinalgを作ってる時から、これぐらいならC言語のマクロで良いかも、と思ってたので、native extensionsなコードをC言語で書き直している。

github.com

Rumaleをはじめ、Numo::NArrayに依存する自身のgemを、numo-narray-altに移行した。

https://github.com/yoshoku/rumale github.com

Numo::NArrayやNumo::Linalgの開発・リリースが活発になるのが理想的で、それまでの間、forkしたものをメンテしていく。コミュニティの皆様、この度は、勝手なことをしてすみませんでした。

*1:全然関係ないし、偏見ですけど、バンドやアイドルグループが活動休止したり解散したりすると「お疲れ様」とか「ありがとう」とかねぎらいがあるのに、OSSの開発が休止したり終わったりしても、そういうのって少なくて、ひどいと「Windowsで動きません」とか「IE11で動きません」とか事情全無視issueが立ったりしますよね。私は、例えばjQueryはずしてVanilla JSにするとか、OSSをはずすというPull Requestには「今までありがと〜」とコメントする人間です。余談でした。

llama_cpp.rbのネイティブ拡張のコードをC言語に書き換えた

タイトルのとおり、llama.cppのRubyバインディングであるllama_cpp.rbのネイティブ拡張のコードを、C++からC言語に書き換えて、v0.18.0としてリリースした。

rubygems.org

llama.cppは、本体はC++で書かれているが、llama.hは、C言語でも呼び出せる形で書かれている。libllama.soをリンクして、llama.hをincludeするみたいな使い方ならC言語で問題ない。llama_cpp.rbは、以前はllama.cppのコードを同梱する形を取っていたが、いまは共有ライブラリを探してリンクする形にしてあるので、ネイティブ拡張のコードをC言語で書き換えることができる。

今回、C言語で書き換えたのは、開発にひとつのケリをつけるためである。Rubyのネイティブ拡張をC++で書くのは、王道ではないので、forkして改造するにもコードリーディングからして大変だったと思う。今回、C言語で教科書どおりのネイティブ拡張に書き換えた。テクいことしなければbindingが書けないと思った関数は、そもそもbindingを実装しなかった。これで、Rubyのネイティブ拡張の基礎知識があれば、llama_cpp.rbを改造できると思う。

llama.cppの変更は早く、APIもコロコロかわる。最近もllama_vocabというのが導入されて(以前から予告はされてた)、わりとインターフェースが大きく変わった。bindingライブラリを作るのに、はりついて変更を見守る必要があり、結構たいへんなのである。というわけで、llama_cpp.rbの継続的な開発を諦めた。今後は「気が向いたら」最新版に追従するぐらいの気持ちでいる。

llama_cpp.rbの開発を続けたいという人が出てきたときのために、今回、C言語でネイティブ拡張の作法にのっとった感じで実装しなおした。インターフェースも、クラスからメソッドが生えてるオブジェクト指向っぽいものから、C言語そのままの構造体を関数に渡す感じ(構造体をラップしたクラスをモジュール関数に与える感じ)のものにした。llama.cppに新しい関数が追加されても、わりとそのままbindingを書けばよい。

これで、一つ、今年やりたいことを達成できました。

yoshoku.hatenablog.com

Rumaleのバージョン1.0.0をリリースした

はじめに

RumaleにVariable-Random Trees(VR Trees)を実装して、バージョン1.0.0としてリリースした。本来であれば、0.30.0相当の変更なのだが、マイナーバージョンをひたすら大きくしていくのもアレだし、Rumaleも前身のSVMKitを含めると5年以上開発していることもあって、バージョン1.0.0とした。 VR Treesは、普通の決定木と、ランダムに分割を決定するランダム木を、混ぜ合わせるようなアンサンブル手法である。データやタスクによっては、Random ForestやExtremely Randomized Treesを超える性能が得られることから、実装してみた。

Liu, F. T., Ting, K. M., Yu, Y., and Zhou, Z. H., "Spectrum of Variable-Random Trees," Journal of Artificial Intelligence Research, vol. 32, pp. 355–384, 2008.

インストール

VR Treesは、Rumale::Ensembleに含まれる。VR Treesだけを試したい場合は、以下でインストールできる。

gem install rumale-ensemble

使い方

VR Trees特有の使い方はなく、fitで学習してpredictで予測する形になる。LIBSVM Dataのabaloneデータセットで回帰分析をしてみる。

wget https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/regression/abalone_scale
require 'rumale/ensemble/random_forest_regressor'
require 'rumale/ensemble/vr_trees_regressor'
require 'rumale/model_selection'
require 'rumale/dataset'

# データセットを読み込む.
x, y = Rumale::Dataset.load_libsvm_file('abalone_scale')

# 訓練とテストに分割する.
x_train, x_test, y_train, y_test = Rumale::ModelSelection.train_test_split(x, y, test_size: 0.2, random_seed: 42)

# Random Forestを学習する.
reg = Rumale::Ensemble::RandomForestRegressor.new(random_seed: 42)
reg.fit(x_train, y_train)

# 決定係数 (1に近づくほどよい) により回帰の性能を評価する.
puts('R2-Scores')
puts(format("Random Forest: %.4f", reg.score(x_test, y_test)))

# 同様にVR Treesを学習して, 回帰の精度を評価する.
reg = Rumale::Ensemble::VRTreesRegressor.new(random_seed: 42)
reg.fit(x_train, y_train)
puts(format("VR Trees: %.4f", reg.score(x_test, y_test)))

これを実行した結果は、次のようになる。わずかにVR Treesのほうがスコアが良い。

R2-Scores
Random Forest: 0.4775
VR Trees: 0.4881

単純なホールドアウト法なのと、ハイパーパラメータを調整していないのとで、コレをもってRandom ForestよりVR Treesが良いとはいえないが、試してみるのには良いんじゃないでしょうか。

おわりに

RumaleにVR Treesを追加して、バージョン1.0.0とした。このバージョンアップにともない、ZenodoのDOIを取得した。開発ガンバっていきます。

github.com

2024年は忙しかったんでしょうね

2024年は忙しかったみたいで、転職エントリーしか書いてませんでした。人生において、年収の減る転職が多いのですが、今回も約100万円くらい減りました。くわえて、前職は、会社の1年が10月始まりで、9月末で辞めたのでキレイに1年働いた形なのですが、ボーナスの支給は12月なので、ボーナスもらえなかったのも、家計としては痛いところです。私にキャリア相談はしないほうが良いです。

OSS活動に関しては、llama.cppのRuby bindingsは、本体のllama.cppの変更が多いので、それに合わせて頻繁にリリースしてたのですが、9月頃に大きくAPIが変更になり、そこで力尽きてしまいました。これに関しては、以前の大きなAPI変更のときに「ギブアップするかもしれない」とCHANGELOGに書いていたので、許して欲しい気持ちです。ただ、未だにGitHub Repositoryには星がつくので、できるだけシンプルなnative extensionsに書き直して、それをもって「後は好きにしてね」という形にしようかと思ってますが、llama.cppも大きくなってるので、それも難しいかもしれません。

潰瘍性大腸炎は、薬を飲み始めて半年ですが、血液検査とかの結果は変わらずです。原因がハッキリしていない病気なので、スグには治りませんね。そんな調子ですが「ガンバったるぞ!!」の気持ちだけはあるので、2025年は目に見えるアウトプットを増やしたい所存です。