💯

Scratch3.0で誤差逆伝播法を実装 day4 動いたけどバグってた、デバッグするぜ

に公開

この記事は株式会社ガラパゴス(有志) Advent Calendar 2025の19日目です

お疲れ様です、波浪です。

さて、前回で一応コードは完成して動くところまではいきましたが重みの更新が一番上まで伝わってなさそう、といったところでday3は終わりました。
つーかなにがday3じゃい!もっともっとかかっとるわ!

今回は僕が大っ嫌いなデバッグ作業なので尊敬しているジスマロック先生を見習って天才ボタンを用意しました。

押すと

\\\\天才!!!////

とか

\\\\さすが!!!////

みたいにガヤがしゃべってくれるボタンです。

設計段階が大事って話

ということで今回はデバッグ作業です。
実は後でデバッグが楽になるようやっておいたことがあります

なんと、変数とリストに
_tmp と _tmp_list を作っておきました。

\\\\さすが!!!////

それがどうしたって?
はい、Scratchはブロックの初期値が設定されるべきデータの一番上のやつが選択された状態になっています。

day1の頃はここに「i」が設定されていて
お、ループカウンタを選ばれる初期値にすると楽じゃん?みたいに思っていたんですが、コードが複雑になるにつれバグの温床になっていったため、逆に初期値を作った方がいいや...となったんで、こうなっています。

つまり、僕がブロックだけおいて次にいっちゃったやつは_tmp か _tmp_list になっているはずです。

バグ潰し1個目

というわけでつぶさに探してみると、はいありました。

これはいけませんね、activationsに学習画像がコピーできていません。
そりゃー勾配もくそもねえわ、INPUTがずっと全部0じゃん。

というわけでここをactivationsに修正

前回更新できてなかったweightの上の方が更新されるか確認します

\\\\天才の所業!!!////

\\\\さすが!!!////

ちゃんと更新されたのが嬉しくておもわず2回おしちゃいました。

さて、ここまできたのでepoch回しましょう

train

epoch_loss_list に 学習曲線を記録します。
ちなみに現在はTRAIN画像600枚で1epochです
とりあえず10回回してlossが下がるかみてみましょう

えーまあ、epoch1の時点で 14.53086とか出ていてこれはダメですね、でかすぎる、そのあとは悪化するし。

でもまあ望みはあります、何しろday2で作ったやつを拡張した誤差逆伝播はprobabilityがみんな0.111..に収束してましたからね

\\\\勾配消失!!!////

ま、これくらいなら全然望みはあります。
とりあえず 気になる点がないか、全部のデータを表示しながら確認する作業を今から始めます。

俺たちの戦いはこれからだ!!!

7日経過

はい、というわけで途中嫌になって投げ出してましたが、やっとやっとやっとやっと原因をみつけました。
あ、7日は大仰に言ってます、さすがにそこまでかかってません。

いやもうね、てっきりoffsetの問題だと思っていたら
どちらかといえばScratch3.0の問題でした、いやまあoffsetはoffsetなんですけどね。
僕が動的とか言わなきゃもっと簡単に特定できたと思います。

ちなみに皆さんはわかりましたか?
もしわかったら僕の天才ボタンは貴方に進呈します。

\\\\⚪︎⚪︎さん天才!!!さすが!!!////

ヒントは 以下の画像にあります。

はい、softmax関連のところですね。
よくみると 9個しかないし、足し算してもexp_sumになりません。
(exp_sumが1.0をちょと超えてるのは精度のせいなので仕様です)

では softmaxを見直しましょう。

はい、この中に虫が潜んでいます、わかりますか?

まあ 9個しかactivationに入っていないんで、そこらへんだろうってことですよね。

でも僕はこの9個しか入らない、という状況がおかしいと思ってからも、なかなかこの謎が解けませんでした。

まあ9個しかないなら、このループのどっかだろうまではわかりますが、じゃあどこだかわかります?
このコードの何がいかんのだと思います?

ヒントは、これがScratch3.0だってことです。

Scratchと僕らの大きな違いはなんでしたかね?
day1の頃にもやられました

https://zenn.dev/galapagos/articles/mnist_predictor_on_scratch#出力

ここに書いてあることです

これもバグが出て結構苦労したんですが、このindexがずれるせいでループの初期値を0からはじめるとバグを生みます

でも day3でこう書きましたね

前回はループカウンタを1からはじめることでリストを処理していましたが、今回は動的に処理する関係上offset処理があるので、ループカウンタは0からはじめます。

はい、ではこの二つを見ながらコードを見直してください。

(activationsoffset+j) を (exp_valsj 番目) / exp_sum で置き換える

一つ目のリストである activations は offsetが足されてるけど、二つ目のリストである exp_valsには offsetがない

....あゝ嗚呼....
........あゝ嗚呼....

これが他の言語なら 存在しないindexにアクセスした時点で落ちるんですがScratchはなんでそのまま進むんだよ...

と、いうわけで正しいコードは

こうなります。
どうですか、みなさんわかりました?わかった人は、天才ボタン押していってください。

\\\\⚪︎⚪︎さん天才!!!////

train

よっしゃ早速回しましょう、すぐ回してみよう!

いけた? これは.... いけたんちゃうか!?

\\\\天才!!!////

あー、いけた、これは勝ちましたね。
もうこれで終わらせたい気持ちもありますが、テスト精度くらい出しましょう。

evaluate

はい、というわけで テスト精度のコードがこれです

実行

そして!!!実際に動作させた結果がこれだ!!!

テスト精度25%!!!!

....だよねー知ってた。train600枚 test100枚だもん

あー、でも回りました、ちゃんと10%(ランダム)以上になってるしこれは成功ですね。勾配消失もしてないし

学習パラメータは以下の通り

レイヤー構造は
INPUT(196) - hidden_1(8) - hidden_2(4) - output(10)
learning_rate が 0.1、epoch 10
でした。

使い方

レイヤーの修正の仕方を上の画像で説明します
「もでるしょきか」 スプライトのコード左上の方に
パラメータを決める場所があります

「モデル定義」の LAYER_SIZE_LIST の

input_size
|
8 を LAYER_SIZE_LISTに追加する
|
4 を LAYER_SIZE_LISTに追加する
|
OUTPUT_SIZE

この 8とか4が隠れ層です、ここの数字や層の厚さを修正してから「このスプライトが押されたとき」を自分でクリックして初期化してください。

その下にある なんか一人で浮いてる「学習開始を送る」を押すと学習がはじまります。

スプライトが押された時にくっつけても別にいいです、面倒な人はくっつけて使ってください。

https://scratch.mit.edu/projects/1253455000
あ、そういえばここまで貼ってなかったけどこれが ここまで頑張って作った Scratch3.0で誤差逆伝播を実装した奴です

本当は入力欄との接続もしたかったんですが、今回はそこまでいけませんでした
まあ言うてday1でやっていることなんで、入力欄との接続しても記事までは作りません。

完走した感想

いやー、きつかった

構造的にはなんのひねりもないので普通なんですが、とにかくバグをひかないように注意しなきゃいけなくて
僕が生まれるより昔、穿孔テープに虫がいたせいでプログラムが壊れることもあった人たち、当然自分の間違いで穴を開ける位置間違うこともあったでしょうから、よく発狂しなかったなと思いました。

そしてライブラリのありがたみが身に沁みましたね、ライブラリを作ってくださっている技術者への感謝がこの記事書く前と今じゃ段違いです。ありがとうnumpy!ありがとうFacebook(Meta)!PyTorch大好きです!

それでは!!最後に天才ボタンおして終わりにしたいと思います!!!

みなさんご一緒に!!!

\\\\ここまで読んでくれたみんな天才!!!////

ありがとうございました!!!!

おまけ

層を深くして
196-120-84-10-10 epoch10 だと 45%

追加で epoch10回(execをクリックするたび追加される)まわしたら62%までいきました

でも30回回したらlossが下がらなくなったんで、この構造で600枚じゃepoch23で限界ですね

層を深くしたらもっとepoch回せるようになるんでしょうが、さてどんな構造が一番精度がでるでしょう?

僕の方でいろいろ試した結果 学習600枚でも72%までは出せました。

にしても、196-120-84-10-10 って冷静に考えるとかなり深くね?いやVGGとかに比べりゃそりゃなんてことないんですが、スクラッチでこんな深い層を作れるんだなってことにはちょっと感動です

\\\\天才!!!////

day1 ScratchでMNIST推論機を作成
https://zenn.dev/galapagos/articles/mnist_predictor_on_scratch

day2 本記事 Scratchに重みを移植
https://zenn.dev/galapagos/articles/mnist_weight

day3 Scratchの標準機能だけで誤差逆伝播を組む
https://zenn.dev/galapagos/articles/backpropagation_on_scratch

day4 Scratchの標準機能だけで誤差逆伝播を組んだらバグだらけなのでDebugする
https://zenn.dev/galapagos/articles/debug_backpropagation

Scratchの標準機能だけで実装した誤差逆伝播
https://scratch.mit.edu/projects/1253455000/

GitHubで編集を提案
株式会社ガラパゴス(有志)

Discussion