洋食の日記

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

3ヶ月で体重を5kg減らした

はじめに

また減量することがあればのための備忘録で、自分が行ったことを勧めるわけではなく、ダイエットは自己責任で。

参考にした本とか

何冊か本を読んだが、結局はこの1冊だけで良かったと思う。

発売されたのが2016年で、数年前に減量(その時は健康体を維持しようぐらいのモチベーション)したときに買ったものだが、今回読み直してみて「ここに全部書いてあったわ」と思った。エビデンスが提示されていて、説明もわかりやすい。筋トレの本だが、第3章でダイエットにも言及されていて、ダイエットでは食事のコントロールが重要であることが書かれている。自重筋トレの具体的な内容については、巻末に写真付きで掲載されていて、これを続けている。

著者の比嘉さんは、いろいろなSNSで発信されているが、X(Twitter)では、たまにPubMedへのリンク付きで筋肉に関する研究を紹介されていて、それがおもしろい。

https://twitter.com/Higa_Kazuo

ダイエットや筋トレが、なんとなく研究としてはどういう分野かわかったので論文をあさって、おもしろかったのが以下の論文だ。解説記事のようなもので、4ページと短いのでスグ読める。

www.jstage.jst.go.jp

結論としては「体重を減らしながら筋肉量を増やすのは難しい」ということで、その理由について解説されている。そもそも、筋肉量の定量化が難しいことが示されている(これは前記の本でも触れられていて、体脂肪率などは推定値なので、体重を基準にすることが勧められている)。

そんなわけで、筋トレはするが体重を落とすことを優先して、食事を考え直すことにした。

食事について

カロリーと脂質を抑えて、タンパク質を多くとることにした。糖質制限はしなかった。普通にご飯を食べている。食べているご飯は、カロリーが他と比べて少なく、よく行くスーパーでもそんなに高くない、ぱくぱくのもち麦パックごはんを食べている。米を買って炊いたほうが安くつくのだろうが、パックご飯のほうがカロリーの計算がしやすい。

野菜はタンパク質が多いブロッコリーをよく食べている。業務用スーパーに、すでに切って茹でてある冷凍ブロッコリーがあるのでそれが便利。お肉も、同じく業務用スーパーで、ほぐして茹でてある冷凍鶏ササミを買っている。また、夜に食べ過ぎると、寝てる間に蓄えられてしまうので、夕飯が少なくなるようにしている。

プロテインは、近所のドラックストアで安いものに行き着いた。特にこだわりはない。

プロテインではないが、スリムアップスリムも飲んだ(これも近所のドラッグストアで安くなってた)。置き換えダイエット用のものなので、ある程度の栄養が担保されていて、カロリーの計算がしやすいのが良い。

味はカフェラテ系にしている。インスタントコーヒーを混ぜることで、甘さを調整できるためだ。

おわりに

今年ついに不惑の歳を迎えるので、それに備えて体を作ることにした。どこまで信頼できる数字かとっても怪しいが、体組成計で表示される体内年齢が、実年齢より5歳若い数字になった。「もし5歳若返ったとしたら何をしたいですか?」という、自己啓発系にあるような質問が、リアルに感じられることが、減量して良かったことだと思う。次の3ヶ月では何に挑戦しようかな〜。

2023年はいつのまにか健康になった年だった

ここ数年、腱鞘炎とか帯状疱疹とか腫瘍切ったりとか色々あったけど、2023年はそういう謎の体調不良に襲われることが無かった。

あるとしたら、吐き気がして起きてられないくらいの激しい頭痛に襲われたぐらい。それも、後日、脳神経内科でCTとか丁寧に検査して頂いた結果、特に異常はなくて「肩コリが原因ではないか?」ということだった。頓服薬的な鎮痛剤1シートと、筋肉の緊張を緩める薬を2週間分と、肩コリ軽減の体操を教わって終わった。体操してたら頭痛は起きなくなって、鎮痛剤はほとんど飲まなかった。CTを撮ったのは初めてだったが、頭の中にギッチギチに脳みそが詰まっているのを見て「これは、もっと脳を使わないとダメだ」と前向きな気持になった。

ここ3ヶ月くらいは、疲れが次の日に残ることがなく、さらにこの1ヶ月くらいは、仕事で夕方や夜は疲れてるんだけど、それはそれで別物というか、夜も本を読んだり活動できてる。以前は疲れて起きてられないことが多かった。

まだ始めて数週間だけど、食事も改善しようとしていて、スナック菓子とかコンビニ弁当とか冷凍チャーハンとか全然食べてない。カロリーとか炭水化物とかタンパク質とかを気にして食事をとっている。ちなみにCOMPの抹茶味をアーモンドミルク100mlと水150mlで溶かすのが最近のお気に入り。

特別な努力はしてなくて健康になったので、年齢とは合ってないが、2021年が前厄・2022年が本厄・2023年が後厄という感じで、厄年が前倒しでやってきたと思うことにした(雑)。健康不安がなくなり、気持ちが前向きになっているので、2024年はなにか大きな成果を出したい。

RumaleにHessian Eigenmapsによる次元削減を追加した

はじめに

最近、Rumaleのリリース記事を書くことをすっかり忘れていた。最近リリースしたv0.28.1では、多様体学習の非線形次元削減手法であるHessian Eigenmaps(a.k.a. Hessian Locally Linear Embedding, HLLE)を実装した。HLLEは、各点の十分に小さい近傍では、多様体上の近くの点までの測地線距離が、対応するパラメータ点間のユークリッド距離と同一となるという、Local isometryをもとにデータ分布の構造を捉える。

Donoho, D. L., and Grimes, C., "Hessian eigenmaps: Locally linear embedding techniques for high-dimensional data," In Proc. Natl. Acad. Sci. USA, vol. 100, no. 10, pp. 5591-5596, 2003.

インストール

HLLEは、Rumale::Manifoldに含まれる。HLLEだけが必要な場合は、以下でインストールできる。

gem install rumale-manifold

HLLEのアルゴリズム固有値分解を必要とするので、Numo::TinyLinalgもインストールする。

gem install numo-tiny_linalg

簡単な実験

多様体学習の例題としてよく用いられる、長方形がくるりと巻かれた三次元のスイスロールデータを、二次元空間に展開することを試す。データ点のプロットに、Numo::Gnuplotを使いたいので、これをインストールする。

brew install gnuplot
gem install numo-gnuplot

多様体学習の代表的な手法であるLocally Linear Embedding(LLE)とHLLEによりスイスロールデータを二次元空間に展開するスクリプトは以下のようになる。

# frozen_string_literal: true

require 'numo/gnuplot'
require 'numo/tiny_linalg'
Numo::Linalg = Numo::TinyLinalg unless defined?(Numo::Linalg)

require 'rumale/manifold/locally_linear_embedding'
require 'rumale/manifold/hessian_eigenmaps'
require 'rumale/utils'

def make_swiss_roll(n_samples: 1000, random_seed: nil)
  rng = Random.new(random_seed || srand)
  theta = 1.5 * Math::PI * (1 + 2 * Rumale::Utils.rand_uniform(n_samples, rng))
  y = 20 * Rumale::Utils.rand_uniform(n_samples, rng)
  x = theta * Numo::NMath.cos(theta)
  z = theta * Numo::NMath.sin(theta)
  data = Numo::NArray.vstack([x, y, z]).transpose.dup
  [data, theta]
end

x, y = make_swiss_roll(n_samples: 2000, random_seed: 20231224)
y = Numo::Int32.cast(y)

plots = y.to_a.uniq.sort.map { |l| [x[y.eq(l), 0], x[y.eq(l), 1], x[y.eq(l), 2], { t: l.to_s }] }

Numo.gnuplot do
  set(terminal: 'png')
  set(output: 'swissroll3d.png')
  set(ticslevel: 0)
  set(view: [84, 11, 1, 1])
  splot(*plots)
end

# trn = Rumale::Manifold::LocallyLinearEmbedding.new(n_components: 2, n_neighbors: 10)
trn = Rumale::Manifold::HessianEigenmaps.new(n_components: 2, n_neighbors: 10)
z = trn.fit_transform(x)

plots = y.to_a.uniq.sort.map { |l| [z[y.eq(l), 0], z[y.eq(l), 1], { t: l.to_s }] }

Numo.gnuplot do
  set(terminal: 'png')
  set(output: 'swissroll2d.png')
  set(ticslevel: 0)
  plot(*plots)
end

これを実行した結果が次のようになる。まずは元データ、

3次元のスイスロールデータ

次にLLEにより二次元空間に展開したデータ、

LLEにより2次元に埋め込まれたスイスロールデータ

そしてHLLEにより二次元空間に展開したデータ、

HLLEにより2次元に埋め込まれたスイスロールデータ

LLEでは、もとの長方形が歪んだ形で二次元空間に展開されているが、HLLEは歪むことなく展開されている。スイスロールの他のデータでも、HLLEはLLEよりも、非線形なデータ分布を捉えることができる。

おわりに

今回、多様体学習の手法の中でHessian Eigenmaps(HLLE)を選択したのは、以下のNeurIPSのproceedingsで使われていたためである。

Horan, D., Richardson, E., and Weiss, Y., "When Is Unsupervised Disentanglement Possible?," NeurIPS'21, Vol. 34, pp. 5150--5161, 2021.

タイトルの通りで、教師なしのdisentaglement(和訳がわからないが高次元空間で入り組んでるデータから本質的な要素を取り出そうという感じ)問題に挑戦したものである。HLLEで非線形次元削減して、FastICAで独立な成分を求めることで、平均相関係数を評価尺度に用いた実験では、Auto-endcoderなどよりも良い結果を得ている。今では古典的な手法となったHLLEとFastICAで、distangledな表現が得られるというのがおもしろい。

古典的機械学習アルゴリズムは、いわゆるプロ驚き屋が話題にしないので影が薄い感じだが、毎年なにかしら有名な国際会議で研究発表がされている。そんなわけで、2024年もRumaleの開発は続いていく。

github.com

Rumale::SVMにClustered SVMによる分類器を追加した

はじめに

Clustered Support Vector Machine(CSVM)は、データをクラスタリングし、各クラスターごとに線形SVMを学習することで、非線形な分類器を実現する手法である。また、Rumale::SVMは、Ruby機械学習ライブラリであるRumaleと同様のインターフェースで、様々なSVMアルゴリズムを利用できるGemである。このRumale::SVMに、CSVMを実装した。

Gu, Q., and Han, J., "Clustered Support Vector Machines," In Proc. AISTATS'13, pp. 307--315, 2013.

インストールと使い方

Rumale::SVMのインストールは、gemコマンドで行う。

gem install rumale-svm

例として、LIBSVM Dataにあるletterデータセットの分類を行う。

wget https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass/letter.scale.tr
wget https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass/letter.scale.t

分類性能の評価のためrumale-evaluation_measureをインストールする。

gem install rumale-evaluation_measure

letterのデータを分類して、その分類の正確度を出力するスクリプトは以下の様になる。

require 'rumale/dataset'
require 'rumale/evaluation_measure'
require 'rumale/svm'

# letterデータを読み込む.
x_train, y_train = Rumale::Dataset.load_libsvm_file('letter.scale.tr')
x_test, y_test = Rumale::Dataset.load_libsvm_file('letter.scale.t')

# CSVMによる分類器を生成する.
# パラメータであるクラスタ数は128, 大域正則化パラメータは0.1, 正則化パラメータは10.0とした.
classifier = Rumale::SVM::ClusteredSVC.new(n_clusters: 128, reg_param_global: 0.1, reg_param: 10.0, random_seed: 42)

# 学習データで学習する.
classifier.fit(x_train, y_train)

# テストデータのラベルを推定する.
y_pred = classifier.predict(x_test)

# 推定結果の正確度を出力する.
acc = Rumale::EvaluationMeasure::Accuracy.new
puts format('Accuracy: %.1f%%', (100.0 * acc.score(y_test, y_pred)))

これを実行すると正確度は92.3%となった。

$ ruby example.rb
Accuracy: 92.3%

同様にして、線形SVMの正確度は69.7%、RBFカーネルカーネルSVMだと正確度は94.6%となった。CSVMが、線形SVMをベースとしているにも関わらず、非線形な分類器であるカーネルSVMに匹敵する結果を得られることがわかる。

アルゴリズムの説明

CSVMは、データをクラスタリングして、各クラスターごとに線形SVMを学習するというコンセプトだが、大域正則化というデータ全体での重みベクトルも学習することで、実際には一つに線形SVMの学習にたどり着く。

n個のラベルy_{i}が付与されたデータ\lbrace\mathbf{x}_{1}\ldots\mathbf{x}_{n}\rbraceが与えられ、クラスタリング手法によりk個のクラスタ\lbrace\mathbf{C}_{1}\ldots\mathbf{C}_{k}\rbraceに分けられたとする。また、l番目のクラスターに含まれるデータ数をn_{l}とする。このとき、CSVMの目的関数は次の様になる。

 \displaystyle
\text{argmin}_{\mathbf{w},\mathbf{w}_{l},\xi_{i}^{l}\leq0}\frac{\lambda}{2}||\mathbf{w}||^{2}+\frac{1}{2}\sum_{l=1}^{k}||\mathbf{w}_{l}-\mathbf{w}||^{2}+C\sum_{l=1}^{k}\sum_{i=1}^{n_{l}}\xi_{i}^{l}

 \displaystyle
\text{s.t.} \quad y_{i}^{l}\mathbf{w}_{l}^{\top}\mathbf{x}_{i}^{l}\leq 1-\xi_{i}^{l}, i=1,\ldots,n_{l},\forall l

ここで、\mathbf{w}_{l}l番目のクラスターでの線形分類するための重みであり、\mathbf{w}はデータ全体での線形分類するための重みである。\xi_{i}^{l}はスラック変数である。目的関数の第2項\frac{1}{2}\sum_{l=1}^{k}||\mathbf{w}_{l}-\mathbf{w}||^{2}は、大域正則化項で、各クラスターに過学習することを防ぐ。いま、\mathbf{v}_{l}=\mathbf{w}_{l}-\mathbf{w}とおくと、目的関数は次の様に書き直せる。

 \displaystyle
\text{argmin}_{\mathbf{w},\mathbf{v}_{l},\xi_{i}^{l}\leq0}\frac{\lambda}{2}||\mathbf{w}||^{2}+\frac{1}{2}\sum_{l=1}^{k}||\mathbf{v}_{l}-\mathbf{w}||^{2}+C\sum_{l=1}^{k}\sum_{i=1}^{n_{l}}\xi_{i}^{l}

 \displaystyle
\text{s.t.} \quad y_{i}^{l}(\mathbf{v}_{l}+\mathbf{w})^{\top}\mathbf{x}_{i}^{l}\leq 1-\xi_{i}^{l}, i=1,\ldots,n_{l},\forall l

さらに、問題を簡単にするため、データと重みベクトルをもとに、以下を定義する。

 \displaystyle
\tilde{\mathbf{w}}=\big\lbrack \sqrt{\lambda}\mathbf{w}^{\top},\mathbf{v}_{1}^{\top},\ldots,\mathbf{v}_{k}^{\top} \big\rbrack^{\top}

 \displaystyle
\tilde{\mathbf{x}}_{i}^{l}=\big\lbrack \frac{1}{\sqrt{\lambda}}\mathbf{x}_{i}^{l \top},\mathbf{0}^{\top}\ldots,\mathbf{x}_{i}^{l \top}\dots,\mathbf{0}^{\top}  \big\rbrack^{\top}

ここで、\tilde{\mathbf{x}}_{i}^{l}(l+1)番目の要素は\mathbf{x}_{i}^{l}となる。これにより、目的関数は一般的な線形SVMと同様のものに簡単化できる。

 \displaystyle
\text{argmin}_{\tilde{\mathbf{w}},\xi_{i}^{l}\leq0}\frac{\lambda}{2}||\tilde{\mathbf{w}}||^{2}+C\sum_{l=1}^{k}\sum_{i=1}^{n_{l}}\xi_{i}^{l}

 \displaystyle
\text{s.t.} \quad y_{i}^{l}\tilde{\mathbf{w}}^{\top}\tilde{\mathbf{x}}_{i}^{l}\leq 1-\xi_{i}^{l}, i=1,\ldots,n_{l},\forall l

このように、CSVMでは、各クラスタごとに線形SVMを学習するというところから、特徴変換により単一の線形SVMの問題にたどり着く。

よくある2-moonsデータで、CSVMの決定境界を示すと次のようになる。クラスタ数は8とした。

CSVMによる2-moonsデータの決定境界

非線形な決定境界が得られているのがわかる。クラスタ数を32と増やすとより滑らかな決定境界が得られる。

クラスタ数を32に増やしたCSVMによる2-moonsデータの決定境界

おわりに

Clustered Support Vector Machine(CSVM)は、各クラスタごとに線形SVMを学習することで、非線形な分類を実現しようという手法である。その最適化問題は、大域正則化を取り入れることで、特徴変換と単一の線形SVMの学習にいたるのがおもしろい。ただ、特徴変換後の特徴ベクトルの次元数は、クラスタ数に依存する。非線形でなめらかな決定境界を得ようとして、クラスタ数を大きくしたくなるが、そうすると、特徴変換後の特徴ベクトルの次元数が大きくなりすぎる。特に、元の特徴ベクトルの次元数が大きいと、安易にクラスタ数を増やすことはできない。

Rumale::SVMをライブラリとして充実させるために、2010年代前半頃の「カーネルSVMを大規模データがで学習するのが大変なので、なにかしら工夫して、線形SVM非線形分類器を実現しよう。」という手法を実装してきたが、そろそろこれぐらいで打ち止めにしようと思う。

github.com

Rumale::SVMにRandom Recursive SVMによる分類器を追加した

はじめに

Random Recursive Support Vector Machine(R2SVM)は、ランダム射影による特徴変換を繰り返すことで、線形SVM非線形な分類器を実現する手法である。また、Rumale::SVMは、Ruby機械学習ライブラリであるRumaleと同様のインターフェースで、様々なSVMアルゴリズムを利用できるGemである。このRumale::SVMに、R2SVMを実装した。

Vinyals, O., Jia, Y., Deng, L., and Darrell, T., "Learning with Recursive Perceptual Representations," In Proc. NIPS’12, pp. 2825–2833, 2012.

インストールと使い方

Rumale::SVMのインストールは、gemコマンドで行う。

gem install rumale-svm

例として、LIBSVM Dataにあるletterデータセットの分類を行う。

wget https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass/letter.scale.tr
wget https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass/letter.scale.t

分類性能の評価のためrumale-evaluation_measureをインストールする。

gem install rumale-evaluation_measure

letterのデータを分類して、その分類の正確度を出力するスクリプトは以下の様になる。

require 'rumale/dataset'
require 'rumale/evaluation_measure'
require 'rumale/svm'

# letterデータを読み込む.
x_train, y_train = Rumale::Dataset.load_libsvm_file('letter.scale.tr')
x_test, y_test = Rumale::Dataset.load_libsvm_file('letter.scale.t')

# R2-SVMによる分類器を生成する.
# パラメータである隠れ層の数は2, 重みパラメータは0.9, 正則化パラメータは1.0とした.
classifier = Rumale::SVM::RandomRecursiveSVC.new(n_hidden_layers: 2, beta: 0.9, reg_param: 1.0, random_seed: 42)

# 学習データで学習する.
classifier.fit(x_train, y_train)

# テストデータのラベルを推定する.
y_pred = classifier.predict(x_test)

# 推定結果の正確度を出力する.
acc = Rumale::EvaluationMeasure::Accuracy.new
puts format('Accuracy: %.1f%%', (100.0 * acc.score(y_test, y_pred)))

これを実行すると正確度は72.6%となった。

$ ruby example.rb
Accuracy: 72.6%

比較のために線形SVMを試してみる。分類器の生成の箇所を以下で置き換えればよい。

classifier = Rumale::SVM::LinearSVC.new(reg_param: 1, random_seed: 42)

結果として、線形SVMの正確度は69.7%となった。R2SVMのほうが、良い分類性能を得ている。

$ ruby example.rb
Accuracy: 69.7%

ちなみに、RBFカーネルカーネルSVMだと正確度は94.6%と、R2SVMより良い数字が得られる...まあ合う合わないがあるようだ。

アルゴリズムの説明

R2SVMについて簡単に説明する。あるD次元の入力データを\mathbf{x}_{1}\in\mathbb{R}^{D}とする。データはC個のクラスに分けられている。また、この入力データに対する線形SVMの出力を\mathbf{o}_{1}\in\mathbb{R}^{C}とする。これらと、正規分布N(0,1)に従うランダム射影行列をW_{2,1}\in\mathbb{R}^{D\times C}を用いて、以下のように特徴変換を行う。

 \displaystyle
\mathbf{x}_{2}=\sigma(\mathbf{x}_{1}+\beta W_{2,1}\mathbf{o}_{1})

ここで、\sigma(\cdot)シグモイド関数であり、\betaは元のデータ\mathbf{x}_{1}をどれくらい移動するかを制御する重みパラメータとなる。 こうして得られた\mathbf{x}_{2}を入力として線形SVMを学習して出力\mathbf{o}_{2}を得て、ランダム射影行列で...と同様の変換をフィードフォワード的に繰り返す。あるl層の変換は次の様になる。

 \displaystyle
\mathbf{x}_{l+1}=\sigma(\mathbf{x}_{1}+\beta \sum_{i=1}^{l}W_{i+1,i}\mathbf{o}_{i})

出力を連結するのではなく、ランダム射影して足し合わせるのが面白い。

よくある2-moonsデータで、R2SVMの決定境界を示すと次のようになる。

半円の端が誤分類されているが、まあデータ分布に沿った非線形な決定境界となっている。

おわりに

Random Recursive SVM(R2SVM)は、線形SVMをdeepにする方法の一つである。隠れ層の変換は、線形SVMに限らず、各クラスに対する予測スコア的なものを出力できる学習アルゴリズムであれば適用できる。発想は面白いが、手元にあるデータでは、カーネルSVMを超えるようなことはなかったので、実用性はちょっとアレかもしれない。

github.com