nn 1.8 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac474651871e2134d4e2372cf8254d21f3e2e6e53173d9b0a2897e72a5c20979
4
- data.tar.gz: fe0813922ccb9f7a1351d9bc4a7377ed99c7ff4e4140b537074a7540afe8ed69
3
+ metadata.gz: 8f77c817ea492d035851bf8552ad2a97928f6762acb455ae23de0e3ee8f40871
4
+ data.tar.gz: 1f162719087671733c8afd5279bca59859474dd366677acbcf79032a9fff5eba
5
5
  SHA512:
6
- metadata.gz: 9b84f7be4c0aa1b0bf00d24b3e5e7df1f47cc4c73174a4b54c9d8b1e691db414c95ed174213b3aa7275d26752684cb9cd927d6b2e879951e1f278deb15727010
7
- data.tar.gz: 7b50ce21afdb88cd306d1e38fa1c7718740cdc313e2ed669558f1d251c439cc939d996fe85cf76b4b16d9b902ebacecf343b81065fc4b0ccc84578d64400d948
6
+ metadata.gz: 492e639590f4b81083a669f51ee192cb9a758ee0bbe950539c74367322ab78a9ad77e6075f90e56f28eecbc57ba42df91455c88e109fe2ec5565ceb77730bafc
7
+ data.tar.gz: d8745f38ed5ca0d75da462c6a8cf1233ea8e9c69e10918b87f37c23c8344690075bba1f6c13cf16267f51a92993e1fe58c0297251c309adcbbd3c4c85f339221
data/README.md CHANGED
@@ -14,4 +14,4 @@ MNISTで98%以上の精度を出せるぐらいの性能はあります。
14
14
 
15
15
  ## ライセンス
16
16
 
17
- この宝石は、[MITライセンス](https://opensource.org/licenses/MIT)の条件でオープンソースとして入手できます。
17
+ このgemは、[MITライセンス](https://opensource.org/licenses/MIT)の条件でオープンソースとして入手できます。
data/document.txt CHANGED
@@ -13,6 +13,11 @@ class NN
13
13
 
14
14
  <クラスメソッド>
15
15
  load(file_name) : NN
16
+ Marshal形式で保存された学習結果を読み込みます。
17
+ String file_name 読み込むMarshalファイル名
18
+ 戻り値 NNのインスタンス
19
+
20
+ load_json(file_name) : NN
16
21
  JSON形式で保存された学習結果を読み込みます。
17
22
  String file_name 読み込むJSONファイル名
18
23
  戻り値 NNのインスタンス
@@ -52,14 +57,7 @@ initialize(num_nodes,
52
57
  Float dropout_ratio ドロップアウトさせるノードの比率
53
58
  bool use_batch_norm バッチノーマライゼーションを使用するか否か
54
59
 
55
- train(x_train, y_train, x_test, y_test, epochs,
56
- learning_rate_decay: 0,
57
- save_dir: nil,
58
- save_interval: 1,
59
- test: nil,
60
- border: nil,
61
- tolerance: 0.5,
62
- &block) : void
60
+ train(x_train, y_train, x_test, y_test, epochs, func = nil, &block) : void
63
61
  学習を行います。
64
62
  Array<Array<Numeric>> | SFloat x_train トレーニング用入力データ。
65
63
  Array<Array<Numeric>> | SFloat y_train トレーニング用正解データ。
@@ -71,8 +69,9 @@ train(x_train, y_train, x_test, y_test, epochs,
71
69
  nilを指定すると、エポックごとにテストを行いません。
72
70
  Float border 学習の早期終了判定に使用するテストデータの正答率。
73
71
  nilの場合、学習の早期終了を行いません。
74
- Proc &block(SFloat x, SFloat y) : Array<SFloat> 入力層のミニバッチを取得します。ブロックの戻り値は、ミニバッチを[x, y]の
75
- 形で指定してください。入力層をミニバッチ単位で正規化したい場合に使用します。
72
+ Proc func(SFloat x, SFloat y) : Array<SFloat> 入力層のミニバッチを取得します。ブロックの戻り値は、ミニバッチを[x, y]の
73
+ 形で指定してください。入力層をミニバッチ単位で正規化したい場合に使用します。
74
+ Proc block(Integer epoch) : void 1エポックの学習が終わった後で行いたい処理を、ブロックで渡します。
76
75
 
77
76
  test(x_test, y_test, tolerance = 0.5, &block) : Float
78
77
  テストデータを用いて、テストを行います。
@@ -95,7 +94,7 @@ accurate(x_test, y_test, tolera)
95
94
  戻り値 テストデータの正答率。
96
95
 
97
96
  learn(x_train, y_train, &block) : Float
98
- 入力データを元に、1回だけ学習を行います。途中で学習を切り上げるなど、柔軟な学習を行いたい場合に使用します。
97
+ 入力データを元に、1回だけ学習を行います。柔軟な学習を行いたい場合に使用します。
99
98
  Array<Array<Numeric>> | SFloat x_train 入力データ
100
99
  Array<Array<Numeric>> | SFloat y_train 正解データ
101
100
  Proc &block(SFloat x, SFloat y) : Array<SFloat> 入力層のミニバッチを取得します。ブロックの戻り値は、ミニバッチを[x, y]の
@@ -106,42 +105,23 @@ learn(x_train, y_train, &block) : Float
106
105
 
107
106
  run(x) : Array<Array<Numeric>>
108
107
  入力データから出力値を二次元配列で得ます。
109
- Array<Array<Float>> | SFloat x 入力データ
108
+ Array<Array<Float>> x 入力データ
109
+ 戻り値 出力ノードの値
110
+
111
+ run(x) : SFloat
112
+ 入力データから出力値をSFloat形式で得ます。
113
+ SFloat x 入力データ
110
114
  戻り値 出力ノードの値
111
115
 
112
116
  save(file_name) : void
117
+ 学習結果をMarshal形式で保存します。
118
+ String file_name 書き込むMarshalファイル名
119
+
120
+ save_json(file_name) : void
113
121
  学習結果をJSON形式で保存します。
114
122
  String file_name 書き込むJSONファイル名
115
123
 
116
124
 
117
- [サンプル1 XOR]
118
-
119
- #ライブラリの読み込み
120
- require "nn"
121
-
122
- x = [
123
- [0, 0],
124
- [1, 0],
125
- [0, 1],
126
- [1, 1],
127
- ]
128
-
129
- y = [[0], [1], [1], [0]]
130
-
131
- #ニューラルネットワークの初期化
132
- nn = NN.new([2, 4, 1], #ノード数
133
- learning_rate: 0.1, #学習率
134
- batch_size: 4, #ミニバッチの数
135
- activation: [:sigmoid, :identity] #活性化関数
136
- )
137
-
138
- #学習を行う
139
- nn.train(x, y, 20000)
140
-
141
- #学習結果の確認
142
- p nn.run(x)
143
-
144
-
145
125
  [MNISTデータを読み込む]
146
126
  MNISTをRubyでも簡単に試せるよう、MNISTを扱うためのモジュールを用意しました。
147
127
  次のリンク(http://yann.lecun.com/exdb/mnist/)から、
@@ -156,54 +136,10 @@ MNIST.load_trainで学習用データを読み込み、MNIST.load_testでテス
156
136
  (RubyでのMNISTの読み込みは、以下のリンクを参考にさせていただきました。)
157
137
  http://d.hatena.ne.jp/n_shuyo/20090913/mnist
158
138
 
159
-
160
- [サンプル2 MNIST]
161
-
162
- #ライブラリの読み込み
163
- require "nn"
164
- require "nn/mnist"
165
-
166
- #MNISTのトレーニング用データを読み込む
167
- x_train, y_train = MNIST.load_train
168
-
169
- #y_trainを10クラスに配列でカテゴライズする
170
- y_train = MNIST.categorical(y_train)
171
-
172
- #MNISTのテスト用データを読み込む
173
- x_test, y_test = MNIST.load_test
174
-
175
- #y_testを10クラスにカテゴライズする
176
- y_test = MNIST.categorical(y_test)
177
-
178
- puts "load mnist"
179
-
180
- #ニューラルネットワークの初期化
181
- nn = NN.new([784, 100, 100, 10], #ノード数
182
- learning_rate: 0.1, #学習率
183
- batch_size: 100, #ミニバッチの数
184
- activation: [:relu, :softmax], #活性化関数
185
- momentum: 0.9, #モーメンタム係数
186
- use_batch_norm: true, #バッチノーマライゼーションを使用する
187
- )
188
-
189
- #学習を行う
190
- nn.train(x_train, y_train, 10, test: [x_test, y_test]) do |x_batch, y_batch|
191
- x_batch /= 255 #ミニバッチを0~1の範囲で正規化
192
- [x_batch, y_batch]
193
- end
194
-
195
- #学習結果のテストを行う
196
- nn.test(x_test, y_test) do |x_batch, y_batch|
197
- x_batch /= 255 #ミニバッチを0~1の範囲で正規化
198
- [x_batch, y_batch]
199
- end
200
-
201
-
202
139
  [お断り]
203
140
  作者は、ニューラルネットワークを勉強し始めたばかりの初心者です。
204
141
  そのため、バグや実装のミスもあるかと思いますが、温かい目で見守っていただけると、幸いでございます。
205
142
 
206
-
207
143
  [更新履歴]
208
144
  2018/3/8 バージョン1.0公開
209
145
  2018/3/11 バージョン1.1公開
@@ -213,3 +149,6 @@ end
213
149
  2018/3/22 バージョン1.5公開
214
150
  2018/4/15 バージョン1.6公開
215
151
  2018/5/4 バージョン1.8公開
152
+ 2018/5/16 バージョン2.0公開
153
+ 2018/6/10 バージョン2.0.1公開
154
+ 2018/6/10 バージョン2.1.0公開
data/lib/nn.rb CHANGED
@@ -2,7 +2,7 @@ require "numo/narray"
2
2
  require "json"
3
3
 
4
4
  class NN
5
- VERSION = "1.8"
5
+ VERSION = "2.0"
6
6
 
7
7
  include Numo
8
8
 
@@ -64,37 +64,19 @@ class NN
64
64
  nn
65
65
  end
66
66
 
67
- def train(x_train, y_train, epochs,
68
- learning_rate_decay: 0,
69
- save_dir: nil,
70
- save_interval: 1,
71
- test: nil,
72
- border: nil,
73
- tolerance: 0.5,
74
- &block)
67
+ def train(x_train, y_train, epochs, func = nil, &block)
75
68
  num_train_data = x_train.is_a?(SFloat) ? x_train.shape[0] : x_train.length
76
69
  (1..epochs).each do |epoch|
77
70
  loss = nil
78
71
  (num_train_data.to_f / @batch_size).ceil.times do
79
- loss = learn(x_train, y_train, &block)
72
+ loss = learn(x_train, y_train, &func)
80
73
  if loss.nan?
81
74
  puts "loss is nan"
82
75
  return
83
76
  end
84
77
  end
85
- if save_dir && epoch % save_interval == 0
86
- save("#{save_dir}/epoch#{epoch}.json")
87
- end
88
- msg = "epoch #{epoch}/#{epochs} loss: #{loss}"
89
- if test
90
- acc = accurate(*test, tolerance, &block)
91
- puts "#{msg} accurate: #{acc}"
92
- break if border && acc >= border
93
- else
94
- puts msg
95
- end
96
- @learning_rate -= learning_rate_decay
97
- @learning_rate = 1e-7 if @learning_rate < 1e-7
78
+ puts "epoch #{epoch}/#{epochs} loss: #{loss}"
79
+ block.call(epoch) if block
98
80
  end
99
81
  end
100
82
 
@@ -160,9 +142,11 @@ class NN
160
142
  end
161
143
 
162
144
  def run(x)
163
- x = SFloat.cast(x) if x.is_a?(Array)
164
- out = forward(x, false)
165
- out.to_a
145
+ if x.is_a?(Array)
146
+ forward(SFloat.cast(x), false).to_a
147
+ else
148
+ forward(x, false)
149
+ end
166
150
  end
167
151
 
168
152
  def save(file_name)
@@ -257,22 +241,30 @@ class NN
257
241
  def update_weight_and_bias
258
242
  @layers.select{|layer| layer.is_a?(Affine)}.each.with_index do |layer, i|
259
243
  weight_amount = layer.d_weight.mean(0) * @learning_rate
260
- @weight_amounts[i] = weight_amount + @momentum * @weight_amounts[i]
261
- @weights[i] -= @weight_amounts[i]
262
244
  bias_amount = layer.d_bias.mean * @learning_rate
263
- @bias_amounts[i] = bias_amount + @momentum * @bias_amounts[i]
264
- @biases[i] -= @bias_amounts[i]
245
+ if @momentum > 0
246
+ weight_amount += @momentum * @weight_amounts[i]
247
+ @weight_amounts[i] = weight_amount
248
+ bias_amount += @momentum * @bias_amounts[i]
249
+ @bias_amounts[i] = bias_amount
250
+ end
251
+ @weights[i] -= weight_amount
252
+ @biases[i] -= bias_amount
265
253
  end
266
254
  end
267
255
 
268
256
  def update_gamma_and_beta
269
257
  @layers.select{|layer| layer.is_a?(BatchNorm)}.each.with_index do |layer, i|
270
258
  gamma_amount = layer.d_gamma.mean * @learning_rate
271
- @gamma_amounts[i] = gamma_amount + @momentum * @gamma_amounts[i]
272
- @gammas[i] -= @gamma_amounts[i]
273
259
  beta_amount = layer.d_beta.mean * @learning_rate
274
- @beta_amounts[i] = beta_amount + @momentum * @beta_amounts[i]
275
- @betas[i] -= @beta_amounts[i]
260
+ if @momentum > 0
261
+ gamma_amount += @momentum * @gamma_amounts[i]
262
+ @gamma_amounts[i] = gamma_amount
263
+ beta_amount += @momentum * @beta_amounts[i]
264
+ @beta_amounts[i] = beta_amount
265
+ end
266
+ @gammas[i] -= gamma_amount
267
+ @betas[i] -= gamma_amount
276
268
  end
277
269
  end
278
270
  end
@@ -298,8 +290,11 @@ class NN::Affine
298
290
 
299
291
  def backward(dout)
300
292
  x = @x.reshape(*@x.shape, 1)
301
- d_ridge = @nn.weight_decay * @nn.weights[@index]
302
- @d_weight = x.dot(dout.reshape(dout.shape[0], 1, dout.shape[1])) + d_ridge
293
+ @d_weight = x.dot(dout.reshape(dout.shape[0], 1, dout.shape[1]))
294
+ if @nn.weight_decay > 0
295
+ dridge = @nn.weight_decay * @nn.weights[@index]
296
+ @d_weight += dridge
297
+ end
303
298
  @d_bias = dout
304
299
  dout.dot(@nn.weights[@index].transpose)
305
300
  end
data/nn.gemspec CHANGED
@@ -5,7 +5,7 @@ require "nn"
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "nn"
8
- spec.version = NN::VERSION
8
+ spec.version = NN::VERSION + ".0"
9
9
  spec.authors = ["unagiootoro"]
10
10
  spec.email = ["ootoro838861@outlook.jp"]
11
11
 
data/nn.rb ADDED
@@ -0,0 +1,441 @@
1
+ require "numo/narray"
2
+ require "json"
3
+
4
+ class NN
5
+ VERSION = "2.1"
6
+
7
+ include Numo
8
+
9
+ attr_accessor :weights
10
+ attr_accessor :biases
11
+ attr_accessor :gammas
12
+ attr_accessor :betas
13
+ attr_accessor :learning_rate
14
+ attr_accessor :batch_size
15
+ attr_accessor :activation
16
+ attr_accessor :momentum
17
+ attr_accessor :weight_decay
18
+ attr_accessor :dropout_ratio
19
+ attr_reader :training
20
+
21
+ def initialize(num_nodes,
22
+ learning_rate: 0.01,
23
+ batch_size: 1,
24
+ activation: %i(relu identity),
25
+ momentum: 0,
26
+ weight_decay: 0,
27
+ use_dropout: false,
28
+ dropout_ratio: 0.5,
29
+ use_batch_norm: false)
30
+ SFloat.srand(rand(2 ** 64))
31
+ @num_nodes = num_nodes
32
+ @learning_rate = learning_rate
33
+ @batch_size = batch_size
34
+ @activation = activation
35
+ @momentum = momentum
36
+ @weight_decay = weight_decay
37
+ @use_dropout = use_dropout
38
+ @dropout_ratio = dropout_ratio
39
+ @use_batch_norm = use_batch_norm
40
+ init_weight_and_bias
41
+ init_gamma_and_beta if @use_batch_norm
42
+ @training = true
43
+ init_layers
44
+ end
45
+
46
+ def self.load(file_name)
47
+ Marshal.load(File.binread(file_name))
48
+ end
49
+
50
+ def self.load_json(file_name)
51
+ json = JSON.parse(File.read(file_name))
52
+ nn = self.new(json["num_nodes"],
53
+ learning_rate: json["learning_rate"],
54
+ batch_size: json["batch_size"],
55
+ activation: json["activation"].map(&:to_sym),
56
+ momentum: json["momentum"],
57
+ weight_decay: json["weight_decay"],
58
+ use_dropout: json["use_dropout"],
59
+ dropout_ratio: json["dropout_ratio"],
60
+ use_batch_norm: json["use_batch_norm"],
61
+ )
62
+ nn.weights = json["weights"].map{|weight| SFloat.cast(weight)}
63
+ nn.biases = json["biases"].map{|bias| SFloat.cast(bias)}
64
+ if json["use_batch_norm"]
65
+ nn.gammas = json["gammas"].map{|gamma| SFloat.cast(gamma)}
66
+ nn.betas = json["betas"].map{|beta| SFloat.cast(beta)}
67
+ end
68
+ nn
69
+ end
70
+
71
+ def train(x_train, y_train, epochs, func = nil, &block)
72
+ num_train_data = x_train.is_a?(SFloat) ? x_train.shape[0] : x_train.length
73
+ (1..epochs).each do |epoch|
74
+ loss = nil
75
+ (num_train_data.to_f / @batch_size).ceil.times do
76
+ loss = learn(x_train, y_train, &func)
77
+ if loss.nan?
78
+ puts "loss is nan"
79
+ return
80
+ end
81
+ end
82
+ puts "epoch #{epoch}/#{epochs} loss: #{loss}"
83
+ block.call(epoch) if block
84
+ end
85
+ end
86
+
87
+ def test(x_test, y_test, tolerance = 0.5, &block)
88
+ acc = accurate(x_test, y_test, tolerance, &block)
89
+ puts "accurate: #{acc}"
90
+ acc
91
+ end
92
+
93
+ def accurate(x_test, y_test, tolerance = 0.5, &block)
94
+ correct = 0
95
+ num_test_data = x_test.is_a?(SFloat) ? x_test.shape[0] : x_test.length
96
+ (num_test_data.to_f / @batch_size).ceil.times do |i|
97
+ x = SFloat.zeros(@batch_size, @num_nodes.first)
98
+ y = SFloat.zeros(@batch_size, @num_nodes.last)
99
+ @batch_size.times do |j|
100
+ k = i * @batch_size + j
101
+ break if k >= num_test_data
102
+ if x_test.is_a?(SFloat)
103
+ x[j, true] = x_test[k, true]
104
+ y[j, true] = y_test[k, true]
105
+ else
106
+ x[j, true] = SFloat.cast(x_test[k])
107
+ y[j, true] = SFloat.cast(y_test[k])
108
+ end
109
+ end
110
+ x, y = block.call(x, y) if block
111
+ out = forward(x, false)
112
+ @batch_size.times do |j|
113
+ vout = out[j, true]
114
+ vy = y[j, true]
115
+ case @activation[1]
116
+ when :identity
117
+ correct += 1 unless (NMath.sqrt((vout - vy) ** 2) < tolerance).to_a.include?(0)
118
+ when :softmax
119
+ correct += 1 if vout.max_index == vy.max_index
120
+ end
121
+ end
122
+ end
123
+ correct.to_f / num_test_data
124
+ end
125
+
126
+ def learn(x_train, y_train, &block)
127
+ x = SFloat.zeros(@batch_size, @num_nodes.first)
128
+ y = SFloat.zeros(@batch_size, @num_nodes.last)
129
+ @batch_size.times do |i|
130
+ if x_train.is_a?(SFloat)
131
+ r = rand(x_train.shape[0])
132
+ x[i, true] = x_train[r, true]
133
+ y[i, true] = y_train[r, true]
134
+ else
135
+ r = rand(x_train.length)
136
+ x[i, true] = SFloat.cast(x_train[r])
137
+ y[i, true] = SFloat.cast(y_train[r])
138
+ end
139
+ end
140
+ x, y = block.call(x, y) if block
141
+ forward(x)
142
+ backward(y)
143
+ update_weight_and_bias
144
+ update_gamma_and_beta if @use_batch_norm
145
+ @layers[-1].loss(y)
146
+ end
147
+
148
+ def run(x)
149
+ if x.is_a?(Array)
150
+ forward(SFloat.cast(x), false).to_a
151
+ else
152
+ forward(x, false)
153
+ end
154
+ end
155
+
156
+ def save(file_name)
157
+ File.binwrite(file_name, Marshal.dump(self))
158
+ end
159
+
160
+ def save_json(file_name)
161
+ json = {
162
+ "version" => VERSION,
163
+ "num_nodes" => @num_nodes,
164
+ "learning_rate" => @learning_rate,
165
+ "batch_size" => @batch_size,
166
+ "activation" => @activation,
167
+ "momentum" => @momentum,
168
+ "weight_decay" => @weight_decay,
169
+ "use_dropout" => @use_dropout,
170
+ "dropout_ratio" => @dropout_ratio,
171
+ "use_batch_norm" => @use_batch_norm,
172
+ "weights" => @weights.map(&:to_a),
173
+ "biases" => @biases.map(&:to_a),
174
+ }
175
+ if @use_batch_norm
176
+ json_batch_norm = {
177
+ "gammas" => @gammas,
178
+ "betas" => @betas
179
+ }
180
+ json.merge!(json_batch_norm)
181
+ end
182
+ File.write(file_name, JSON.dump(json))
183
+ end
184
+
185
+ private
186
+
187
+ def init_weight_and_bias
188
+ @weights = Array.new(@num_nodes.length - 1)
189
+ @biases = Array.new(@num_nodes.length - 1)
190
+ @weight_amounts = Array.new(@num_nodes.length - 1, 0)
191
+ @bias_amounts = Array.new(@num_nodes.length - 1, 0)
192
+ @num_nodes[0...-1].each_index do |i|
193
+ weight = SFloat.new(@num_nodes[i], @num_nodes[i + 1]).rand_norm
194
+ bias = SFloat.new(@num_nodes[i + 1]).rand_norm
195
+ if @activation[0] == :relu
196
+ @weights[i] = weight / Math.sqrt(@num_nodes[i]) * Math.sqrt(2)
197
+ @biases[i] = bias / Math.sqrt(@num_nodes[i]) * Math.sqrt(2)
198
+ else
199
+ @weights[i] = weight / Math.sqrt(@num_nodes[i])
200
+ @biases[i] = bias / Math.sqrt(@num_nodes[i])
201
+ end
202
+ end
203
+ end
204
+
205
+ def init_gamma_and_beta
206
+ @gammas = Array.new(@num_nodes.length - 2, 1)
207
+ @betas = Array.new(@num_nodes.length - 2, 0)
208
+ @gamma_amounts = Array.new(@num_nodes.length - 2, 0)
209
+ @beta_amounts = Array.new(@num_nodes.length - 2, 0)
210
+ end
211
+
212
+ def init_layers
213
+ @layers = []
214
+ @num_nodes[0...-2].each_index do |i|
215
+ @layers << Affine.new(self, i)
216
+ @layers << BatchNorm.new(self, i) if @use_batch_norm
217
+ @layers << case @activation[0]
218
+ when :sigmoid
219
+ Sigmoid.new
220
+ when :relu
221
+ ReLU.new
222
+ end
223
+ @layers << Dropout.new(self) if @use_dropout
224
+ end
225
+ @layers << Affine.new(self, -1)
226
+ @layers << case @activation[1]
227
+ when :identity
228
+ Identity.new(self)
229
+ when :softmax
230
+ Softmax.new(self)
231
+ end
232
+ end
233
+
234
+ def forward(x, training = true)
235
+ @training = training
236
+ @layers.each do |layer|
237
+ x = layer.forward(x)
238
+ end
239
+ x
240
+ end
241
+
242
+ def backward(y)
243
+ dout = @layers[-1].backward(y)
244
+ @layers[0...-1].reverse.each do |layer|
245
+ dout = layer.backward(dout)
246
+ end
247
+ end
248
+
249
+ def update_weight_and_bias
250
+ @layers.select{|layer| layer.is_a?(Affine)}.each.with_index do |layer, i|
251
+ weight_amount = layer.d_weight * @learning_rate
252
+ bias_amount = layer.d_bias * @learning_rate
253
+ if @momentum > 0
254
+ weight_amount += @momentum * @weight_amounts[i]
255
+ @weight_amounts[i] = weight_amount
256
+ bias_amount += @momentum * @bias_amounts[i]
257
+ @bias_amounts[i] = bias_amount
258
+ end
259
+ @weights[i] -= weight_amount
260
+ @biases[i] -= bias_amount
261
+ end
262
+ end
263
+
264
+ def update_gamma_and_beta
265
+ @layers.select{|layer| layer.is_a?(BatchNorm)}.each.with_index do |layer, i|
266
+ gamma_amount = layer.d_gamma * @learning_rate
267
+ beta_amount = layer.d_beta * @learning_rate
268
+ if @momentum > 0
269
+ gamma_amount += @momentum * @gamma_amounts[i]
270
+ @gamma_amounts[i] = gamma_amount
271
+ beta_amount += @momentum * @beta_amounts[i]
272
+ @beta_amounts[i] = beta_amount
273
+ end
274
+ @gammas[i] -= gamma_amount
275
+ @betas[i] -= gamma_amount
276
+ end
277
+ end
278
+ end
279
+
280
+
281
+ class NN::Affine
282
+ include Numo
283
+
284
+ attr_reader :d_weight
285
+ attr_reader :d_bias
286
+
287
+ def initialize(nn, index)
288
+ @nn = nn
289
+ @index = index
290
+ @d_weight = nil
291
+ @d_bias = nil
292
+ end
293
+
294
+ def forward(x)
295
+ @x = x
296
+ @x.dot(@nn.weights[@index]) + @nn.biases[@index]
297
+ end
298
+
299
+ def backward(dout)
300
+ x = @x.reshape(*@x.shape, 1)
301
+ @d_weight = x.dot(dout.reshape(dout.shape[0], 1, dout.shape[1])).mean(0)
302
+ if @nn.weight_decay > 0
303
+ dridge = @nn.weight_decay * @nn.weights[@index]
304
+ @d_weight += dridge
305
+ end
306
+ @d_bias = dout.mean
307
+ dout.dot(@nn.weights[@index].transpose)
308
+ end
309
+ end
310
+
311
+
312
+ class NN::Sigmoid
313
+ include Numo
314
+
315
+ def forward(x)
316
+ @out = 1.0 / (1 + NMath.exp(-x))
317
+ end
318
+
319
+ def backward(dout)
320
+ dout * (1.0 - @out) * @out
321
+ end
322
+ end
323
+
324
+
325
+ class NN::ReLU
326
+ def forward(x)
327
+ @x = x.clone
328
+ x[x < 0] = 0
329
+ x
330
+ end
331
+
332
+ def backward(dout)
333
+ @x[@x > 0] = 1.0
334
+ @x[@x <= 0] = 0.0
335
+ dout * @x
336
+ end
337
+ end
338
+
339
+
340
+ class NN::Identity
341
+ def initialize(nn)
342
+ @nn = nn
343
+ end
344
+
345
+ def forward(x)
346
+ @out = x
347
+ end
348
+
349
+ def backward(y)
350
+ @out - y
351
+ end
352
+
353
+ def loss(y)
354
+ ridge = 0.5 * @nn.weight_decay * @nn.weights.reduce(0){|sum, weight| sum + (weight ** 2).sum}
355
+ 0.5 * ((@out - y) ** 2).sum / @nn.batch_size + ridge
356
+ end
357
+ end
358
+
359
+
360
+ class NN::Softmax
361
+ include Numo
362
+
363
+ def initialize(nn)
364
+ @nn = nn
365
+ end
366
+
367
+ def forward(x)
368
+ @out = NMath.exp(x) / NMath.exp(x).sum(1).reshape(x.shape[0], 1)
369
+ end
370
+
371
+ def backward(y)
372
+ @out - y
373
+ end
374
+
375
+ def loss(y)
376
+ ridge = 0.5 * @nn.weight_decay * @nn.weights.reduce(0){|sum, weight| sum + (weight ** 2).sum}
377
+ -(y * NMath.log(@out + 1e-7)).sum / @nn.batch_size + ridge
378
+ end
379
+ end
380
+
381
+
382
+ class NN::Dropout
383
+ include Numo
384
+
385
+ def initialize(nn)
386
+ @nn = nn
387
+ @mask = nil
388
+ end
389
+
390
+ def forward(x)
391
+ if @nn.training
392
+ @mask = SFloat.ones(*x.shape).rand < @nn.dropout_ratio
393
+ x[@mask] = 0
394
+ else
395
+ x *= (1 - @nn.dropout_ratio)
396
+ end
397
+ x
398
+ end
399
+
400
+ def backward(dout)
401
+ dout[@mask] = 0 if @nn.training
402
+ dout
403
+ end
404
+ end
405
+
406
+
407
+ class NN::BatchNorm
408
+ include Numo
409
+
410
+ attr_reader :d_gamma
411
+ attr_reader :d_beta
412
+
413
+ def initialize(nn, index)
414
+ @nn = nn
415
+ @index = index
416
+ end
417
+
418
+ def forward(x)
419
+ @x = x
420
+ @mean = x.mean(0)
421
+ @xc = x - @mean
422
+ @var = (@xc ** 2).mean(0)
423
+ @std = NMath.sqrt(@var + 1e-7)
424
+ @xn = @xc / @std
425
+ out = @nn.gammas[@index] * @xn + @nn.betas[@index]
426
+ out.reshape(*@x.shape)
427
+ end
428
+
429
+ def backward(dout)
430
+ @d_beta = dout.sum(0).mean
431
+ @d_gamma = (@xn * dout).sum(0).mean
432
+ dxn = @nn.gammas[@index] * dout
433
+ dxc = dxn / @std
434
+ dstd = -((dxn * @xc) / (@std ** 2)).sum(0)
435
+ dvar = 0.5 * dstd / @std
436
+ dxc += (2.0 / @nn.batch_size) * @xc * dvar
437
+ dmean = dxc.sum(0)
438
+ dx = dxc - dmean / @nn.batch_size
439
+ dx.reshape(*@x.shape)
440
+ end
441
+ end
@@ -0,0 +1,38 @@
1
+ require "nn"
2
+ require "nn/cifar10"
3
+
4
+ x_train = []
5
+ y_train = []
6
+
7
+ (1..5).each do |i|
8
+ x_train2, y_train2 = CIFAR10.load_train(i)
9
+ x_train.concat(x_train2)
10
+ y_train.concat(CIFAR10.categorical(y_train2))
11
+ end
12
+ GC.start
13
+
14
+ x_test, y_test = CIFAR10.load_test
15
+ y_test = CIFAR10.categorical(y_test)
16
+ GC.start
17
+
18
+ puts "load cifar10"
19
+
20
+ nn = NN.new([3072, 100, 100, 10],
21
+ learning_rate: 0.1,
22
+ batch_size: 32,
23
+ activation: [:relu, :softmax],
24
+ momentum: 0.9,
25
+ use_dropout: true,
26
+ dropout_ratio: 0.2,
27
+ use_batch_norm: true,
28
+ )
29
+
30
+ func = -> x, y do
31
+ x /= 255
32
+ [x, y]
33
+ end
34
+
35
+ nn.train(x_train, y_train, 20, func) do |epoch|
36
+ nn.test(x_test, y_test, &func)
37
+ nn.learning_rate *= 0.99
38
+ end
@@ -0,0 +1,38 @@
1
+ #ライブラリの読み込み
2
+ require "nn"
3
+ require "nn/mnist"
4
+
5
+ #MNISTのトレーニング用データを読み込む
6
+ x_train, y_train = MNIST.load_train
7
+
8
+ #y_trainを10クラスに配列でカテゴライズする
9
+ y_train = MNIST.categorical(y_train)
10
+
11
+ #MNISTのテスト用データを読み込む
12
+ x_test, y_test = MNIST.load_test
13
+
14
+ #y_testを10クラスにカテゴライズする
15
+ y_test = MNIST.categorical(y_test)
16
+
17
+ puts "load mnist"
18
+
19
+ #ニューラルネットワークの初期化
20
+ nn = NN.new([784, 100, 100, 10], #ノード数
21
+ learning_rate: 0.1, #学習率
22
+ batch_size: 100, #ミニバッチの数
23
+ activation: [:relu, :softmax], #活性化関数
24
+ momentum: 0.9, #モーメンタム係数
25
+ use_batch_norm: true, #バッチノーマライゼーションを使用する
26
+ )
27
+
28
+ #ミニバッチを0~1の範囲で正規化
29
+ func = -> x_batch, y_batch do
30
+ x_batch /= 255
31
+ [x_batch, y_batch]
32
+ end
33
+
34
+ #学習を行う
35
+ nn.train(x_train, y_train, 10, func) do
36
+ #学習結果のテストを行う
37
+ nn.test(x_test, y_test, &func)
38
+ end
data/sample/xor.rb ADDED
@@ -0,0 +1,24 @@
1
+ #ライブラリの読み込み
2
+ require "nn"
3
+
4
+ x = [
5
+ [0, 0],
6
+ [1, 0],
7
+ [0, 1],
8
+ [1, 1],
9
+ ]
10
+
11
+ y = [[0], [1], [1], [0]]
12
+
13
+ #ニューラルネットワークの初期化
14
+ nn = NN.new([2, 4, 1], #ノード数
15
+ learning_rate: 0.1, #学習率
16
+ batch_size: 4, #ミニバッチの数
17
+ activation: [:sigmoid, :identity] #活性化関数
18
+ )
19
+
20
+ #学習を行う
21
+ nn.train(x, y, 20000)
22
+
23
+ #学習結果の確認
24
+ p nn.run(x)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nn
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.8'
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - unagiootoro
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-05-03 00:00:00.000000000 Z
11
+ date: 2018-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: numo-narray
@@ -70,6 +70,10 @@ files:
70
70
  - lib/nn/cifar10.rb
71
71
  - lib/nn/mnist.rb
72
72
  - nn.gemspec
73
+ - nn.rb
74
+ - sample/cifar10_program.rb
75
+ - sample/mnist_program.rb
76
+ - sample/xor.rb
73
77
  homepage: https://github.com/unagiootoro/nn.git
74
78
  licenses:
75
79
  - MIT