はじめに
Ruby Advent Calendar 2019 4日目の記事です。
今回は、Ruby用のディープラーニングライブラリ「ruby-dnn」を紹介したいと思います。
ruby-dnn
ruby-dnnは、Ruby用のディープラーニングライブラリです。
Python用の本格的なライブラリに比べると非常にシンプルですが、全結合ネットワーク、畳み込みネットワーク、再帰型ネットワークといった基本的な機能は実装されています。
特徴は、行列計算にNumo::NArrayを使用することで、全てのディープラーニングのアルゴリズムをRubyで実装していることです。そのため、可読性が高いコードになっている(はず)と思います。
インストール
gem install ruby-dnn
MNIST
機械学習のHello Worldこと、MNISTを用いた手書き数字の認識を通してruby-dnnの使い方を説明したいと思います。
ライブラリの読み込み
require "dnn"
require "dnn/datasets/mnist"
# require "numo/linalg/autoloader" # numo-linalgを入れると高速化できます。
# 名前空間のインクルード
include DNN::Models
include DNN::Layers
include DNN::Optimizers
include DNN::Losses
データの前処理
# MNISTデータの読み込み
x_train, y_train = DNN::MNIST.load_train
x_test, y_test = DNN::MNIST.load_test
# MNISTデータを[画像枚数, 28 * 28]の形状に変形する
x_train = x_train.reshape(x_train.shape[0], 784)
x_test = x_test.reshape(x_test.shape[0], 784)
# 画像データを0~1の範囲に正規化
x_train = Numo::SFloat.cast(x_train) / 255
x_test = Numo::SFloat.cast(x_test) / 255
# ラベルデータをone-hotベクトルに変換する
y_train = DNN::Utils.to_categorical(y_train, 10, Numo::SFloat)
y_test = DNN::Utils.to_categorical(y_test, 10, Numo::SFloat)
モデルの作成
[784, 256, 256, 10]のシンプルな全結合ネットワークを作成します。
# モデルの作成
model = Sequential.new
# 入力層を追加
model << InputLayer.new(784)
# 中間層を追加
model << Dense.new(256)
model << ReLU.new
# 中間層を追加
model << Dense.new(256)
model << ReLU.new
# 出力層を追加
model << Dense.new(10)
# モデルのセットアップ(最適化にAdamオプティマイザ、損失関数にSoftmaxCrossEntropyを使用)
model.setup(Adam.new, SoftmaxCrossEntropy.new)
学習開始
trainで学習を開始します。今回は10エポックをバッチサイズ128で学習します。
model.train(x_train, y_train, 10, batch_size: 128, test: [x_test, y_test])
accuracy, loss = model.evaluate(x_test, y_test)
puts "loss: #{loss}"
puts "accuracy: #{accuracy}"
このプログラムを実行すると、次のような結果になります。
認識率は実行するたびに異なりますが、大体98%ぐらいになると思います。
【 epoch 1/10 】
======================================== 60000/60000 loss: 0.1141 accuracy: 0.9604, test_loss: 0.1257
【 epoch 2/10 】
======================================== 60000/60000 loss: 0.0660 accuracy: 0.9700, test_loss: 0.0920
【 epoch 3/10 】
======================================== 60000/60000 loss: 0.0666 accuracy: 0.9736, test_loss: 0.0810
【 epoch 4/10 】
======================================== 60000/60000 loss: 0.0441 accuracy: 0.9735, test_loss: 0.0799
【 epoch 5/10 】
======================================== 60000/60000 loss: 0.0330 accuracy: 0.9797, test_loss: 0.0680
【 epoch 6/10 】
======================================== 60000/60000 loss: 0.0415 accuracy: 0.9780, test_loss: 0.0760
【 epoch 7/10 】
======================================== 60000/60000 loss: 0.0026 accuracy: 0.9805, test_loss: 0.0641
【 epoch 8/10 】
======================================== 60000/60000 loss: 0.0468 accuracy: 0.9801, test_loss: 0.0707
【 epoch 9/10 】
======================================== 60000/60000 loss: 0.0735 accuracy: 0.9796, test_loss: 0.0814
【 epoch 10/10 】
======================================== 60000/60000 loss: 0.0396 accuracy: 0.9806, test_loss: 0.0780
loss: 0.9806
accuracy: 0.07885340588234832
学習結果の保存と読み込み
saveとloadを使用することで、学習したモデルの保存と読み込みができます。
model.save("trained_mnist.marshal")
new_model = Sequential.load("trained_mnist.marshal")
accuracy, loss = new_model.evaluate(x_test, y_test)
puts "loss: #{loss}"
puts "accuracy: #{accuracy}"
CIFAR10
次はMNISTから難易度を上げて、CIFAR10で動物や乗り物の画像認識にチャレンジします。
畳み込み層6 + 全結合層2の計8層のモデルを使用します。
このコードで学習させると、認識率は大体70%を超えるぐらいになると思います。
require "dnn"
require "dnn/datasets/cifar10"
require "numo/linalg/autoloader"
include DNN::Models
include DNN::Layers
include DNN::Optimizers
include DNN::Losses
# CIFAR10データの読み込み
x_train, y_train = DNN::CIFAR10.load_train
x_test, y_test = DNN::CIFAR10.load_test
# 画像データを0~1の範囲に正規化
x_train = Numo::SFloat.cast(x_train) / 255
x_test = Numo::SFloat.cast(x_test) / 255
# ラベルデータをone-hotベクトルに変換する
y_train = DNN::Utils.to_categorical(y_train, 10, Numo::SFloat)
y_test = DNN::Utils.to_categorical(y_test, 10, Numo::SFloat)
# モデルの作成(畳み込み層6 + 全結合層2 = 計8層)
model = Sequential.new
model << InputLayer.new([32, 32, 3])
model << Conv2D.new(32, 3, padding: true)
model << Dropout.new(0.25)
model << ReLU.new
model << Conv2D.new(32, 3, padding: true)
model << BatchNormalization.new
model << ReLU.new
model << MaxPool2D.new(2)
model << Conv2D.new(64, 3, padding: true)
model << Dropout.new(0.25)
model << ReLU.new
model << Conv2D.new(64, 3, padding: true)
model << BatchNormalization.new
model << ReLU.new
model << MaxPool2D.new(2)
model << Conv2D.new(128, 3, padding: true)
model << Dropout.new(0.25)
model << ReLU.new
model << Conv2D.new(128, 3, padding: true)
model << BatchNormalization.new
model << ReLU.new
model << Flatten.new
model << Dense.new(512)
model << BatchNormalization.new
model << ReLU.new
model << Dense.new(10)
# モデルのセットアップ
model.setup(Adam.new, SoftmaxCrossEntropy.new)
# 学習を開始
model.train(x_train, y_train, 20, batch_size: 128, test: [x_test, y_test])
accuracy, loss = model.evaluate(x_test, y_test)
puts "loss: #{loss}"
puts "accuracy: #{accuracy}"
# 学習結果を保存
model.save("trained_cifar10.marshal")
その他できること
Chainer風のモデル定義
先ほどまで、モデルにレイヤーを追加する書き方をしましたが、Chainer風にモデルを定義することもできます。
この場合、define by runで実行することができるので、デバッグが楽になるメリットがあります。
class MLP < Model
def initialize
super
@l1 = Dense.new(256)
@l2 = Dense.new(256)
@l3 = Dense.new(10)
end
def forward(x)
x = InputLayer.new(784).(x)
x = @l1.(x)
x = ReLU.(x)
x = @l2.(x)
x = ReLU.(x)
x = @l3.(x)
x
end
end
model = MLP.new
model.setup(Adam.new, SoftmaxCrossEntropy.new)
model.train(x_train, y_train, 10, batch_size: 128, test: [x_test, y_test])
画像の読み込み/書き込み
ruby-dnnでは、C言語の画像処理ライブラリ「stb_image」のラッパーが付属しており、それを使うことで簡単な画像の読み込み/書き込み/リサイズができます。
require "dnn/image"
# 画像の読み込み
img = DNN::Image.read("image.png")
# 画像サイズを64*64に拡大
new_img = DNN::Image.resize(img, 64, 64)
# リサイズした画像を書き込み
DNN::Image.write("new_image.png", new_img)
おわりに
ruby-dnnが実用的になる為には、まだまだ足りていない機能が数多くありますが、一応、今の時点でもDCGANで画像を作れるぐらいの性能はあります。そのため、Rubyでディープラーニングやってみたい人に試してもらえたら幸いです。