これの続き
ED法で CrossEntropyLoss を使った学習を実装している。
線形変換の学習はできたけど、非線形な学習はなかなかに難しい。
まぁそもそも一層だけの線形変換の学習は別にED法でもBPでも本質的に変わらないので「ED法で」というのは怪しいけど。
実装
以下の実装でORゲートやANDゲートは学習できるが、XORゲートは学習できない。
backward関数の中のlayer0のbackward呼び出しはあってもなくてもほぼ結果が変わらず、学習になんの寄与もしていない。
そもそも最終層の勾配をとってきて第一層に渡しているため、コレで上手くいってもED法じゃないじゃんという話もあるが、上手くいくなら最終層以外には全部コレを渡せばうまくいくはずなのでよしとしている。
モデル
pub struct Gate {
layer0: Layer<Sigmoid>,
last_layer: Layer<PassThrough>,
}
impl Gate {
pub fn new() -> Self {
let mut rng = StdRng::seed_from_u64(42);
Gate {
layer0: Layer::new(&mut rng, 4, 16),
last_layer: Layer::new(&mut rng, 16, 4),
}
}
pub fn forward(&mut self, inputs: &[f64]) -> Vec<f64> {
let x = duplicate_elements(inputs.into_iter()).collect();
let x = self.layer0.forward(x);
let x = self.last_layer.forward(x);
unduplicate_elements(x.iter()).collect()
}
pub fn backward(&mut self, delta: Vec<f64>) {
let delta: Vec<_> = duplicate_elements(delta.iter()).collect();
let delta = self.last_layer.backward_multi(&delta);
for d in delta.iter() {
self.layer0.backward(*d);
}
}
}
学習
let train = vec![
(vec![0., 0.], vec![1., 0.]),
(vec![1., 0.], vec![1., 0.]),
(vec![0., 1.], vec![1., 0.]),
(vec![1., 1.], vec![0., 1.]),
];
for _ in 0..1000 {
let mut sum_loss = 0.;
for (input, target) in train.iter() {
let output = model.forward(&input);
let loss = CrossEntropyLoss::eval((&output, &target));
let delta = CrossEntropyLoss::derivative((&output, &target));
model.backward(delta.into_iter().map(|d| d * LEARNING_RATE).collect());
sum_loss += loss;
}
println!("loss: {}", sum_loss / train.len() as f64);
}
考察
for d in delta.iter() {
self.layer0.backward(*d);
}
ここの実装は正直書いている最中から意味ない感じがしていた。だって右往左往してるだけだし。
単純な山登りにおいて二つの山を同時に登頂しようとしてもそれは無理でしょという話もある。
でも何かしら方法はあるような気がしているのでもう少し頑張ってみるつもり。
結局のところ、ED法ではある入力に対して最終出力を上げるか下げるかしか学習できない。
だから、同一の入力に対して2つ以上の出力を学習できない。
今は1次元空間の上下運動しか学習できないけど、2次元以上の空間での動きを学習できれば最終出力も2次元以上で学習できることになる。
追記
冷静に考えると、最終層はPassThroughな訳だから、 let delta = self.last_layer.backward_multi(&delta);
の実装はなんの意味もなかった。