この記事の続き
とにかく、ED法でCrossEntropyLossを損失関数に使った学習ができれば大きく可能性が広がるので、なんとか方法を模索している
結果
赤が損失値で、青が訓練データの正解率、緑がテストデータの正解率。
CrossEntropyLossを使ってとにかくまぁ学習ができた。正解率は93%程度なのでまぁ良さげ。
実装
pub struct Mnist {
layer0: MultiOutputLayer<Sigmoid>,
last_layer: MultiOutputLayer<PassThrough>,
}
impl Mnist {
fn new() -> Self {
let mut rng = StdRng::seed_from_u64(42);
Mnist {
layer0: MultiOutputLayer::new(&mut rng, 10, 784 * 2, 4),
last_layer: MultiOutputLayer::new(&mut rng, 10, 4, 1),
}
}
fn forward(&mut self, inputs: &[f64]) -> Vec<f64> {
let x = duplicate_elements(inputs.into_iter()).collect();
let x = vec![x; 10];
let x = self.layer0.forward(x);
let x = self.last_layer.forward(x);
x.into_iter().map(|x| x[0]).collect()
}
fn forward_without_train(&self, inputs: &[f64]) -> Vec<f64> {
let x = duplicate_elements(inputs.into_iter()).collect();
let x = vec![x; 10];
let x = self.layer0.forward_without_train(x);
let x = self.last_layer.forward_without_train(x);
x.into_iter().map(|x| x[0]).collect()
}
fn backward(&mut self, deltas: Vec<f64>) {
self.layer0.backward(&deltas);
self.last_layer.backward(&deltas);
}
}
impl<ActivationFunc> MultiOutputLayer<ActivationFunc>
where
ActivationFunc: DifferentiableFn<Args = f64>,
{
pub fn forward(&mut self, inputs: Vec<Vec<f64>>) -> Vec<Vec<f64>> {
let output = self
.inner_layers
.iter_mut()
.zip(inputs.iter())
.map(|(layers, inputs)| {
layers
.iter_mut()
.map(|layer| layer.forward(&inputs))
.collect()
})
.collect();
self.last_inputs = inputs;
output
}
pub fn backward(&mut self, deltas: &Vec<f64>) {
for (layers, delta) in self.inner_layers.iter_mut().zip(deltas.iter()) {
for layer in layers {
layer.backward(*delta, &self.last_inputs[0]);
}
}
}
}
解説
実装はパッと見では良さそうに見えるけど、 MultiOutputLayer
は全く関連しないネットワークを10個持っているだけなので、0から9までの数字を別々に学習しているに過ぎない。
こちらの記事でも先行で実装されており、同様に93%程度の精度が出ている。
所感
前の記事でも書いた通り、ED法では1次元空間での上下運動しか学べない。ED法のモデルでN個の出力を作るためには、N次元での山登りをしなくてはならない。
今回の実装で、兎にも角にもbackwardにdeltaをN個渡し、N次元での山登りをさせた。
引数はこれでいいはず。あとはこのN次元の勾配をモデルに反映させればいいだけなはずなんだけど…