LoginSignup
18
11

More than 3 years have passed since last update.

ruby-dnnでディープラーニング

Last updated at Posted at 2019-12-03

はじめに

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でディープラーニングやってみたい人に試してもらえたら幸いです。

18
11
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
11