💬

MINST on Scratch DeepDive

に公開

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

お疲れ様です、波浪です。
今年の後半は1行も自分でコード書かずにAIの書いたコードをレビューする生活してました、まじでぜんぜんコード書いてないんでちょっとリハビリがてら小学生でもできるらしいScratch開発をしてみたいと思います。

今年のアドベントカレンダーにはAIに書かせた記事があらゆるTECH企業から大量に出てるはずなんで、

ここで一発AIで書くことが難しいScratchの話を書いて「僕は記事を書く事にはAI使ってませんよ」ってドヤ顔をする意味もあります。(ドヤッ)

なおScratchは一度も触ったことがなかったので、プロジェクト作ってデフォルトで設定されている猫のスプライトの消し方すらわからずかなり右往左往しました、そんなやつの作ったコードなのでスクラッチ強者から見ると非効率な部分もあると思いますがお目溢しいただければ幸いです

ターゲットユーザーは僕と同じくスクラッチを一回も触ったことない人向けに書きます。
逆にMINST程度は組んだことある人が大半だと思うのでそこはすっ飛ばします。

一応MINSTとか3層ニューラルネットワークの参考書籍を置いときます
https://www.oreilly.co.jp/books/9784873117584/

出来上がるもの

https://scratch.mit.edu/projects/1250300287/

緑の旗を押すと入力欄が出てきます(出るまでに少しかかります)

出てきた入力欄に0から9の数字をいれて「すいろん」ボタンを押してください

うまくいけば、書いた数字をあててくれます。

入力欄

正直に言いますが、ここが一番大変で一番時間かかりました。
MINSTは28x28の画像を入力にしたネットワークです

ってことは、ユーザーに28x28の画像をユーザーに書いてもらわなきゃいけません。

。。。どうやって??

ということではじめの一歩で躓いたからここで投げて2日くらい塩漬けにした事は秘密です。

初めにもかきましたが僕と同じく何も触ったことない人に向けて書くのでここ詳しく書きます

やってることは

  1. 初期値が白で押したら黒になるスプライトを用意
  2. ループで回して14x14に配置

これだけです、これだけなんですが塩漬け期間含めて正味3人日はかかりました。

スプライトの作り方


初期画面の左上にある「コスチューム」を押してから、画面右下の猫のアイコンにマウスオーバーします。

この猫のアイコンを押しちゃうとスプライトを選ぶになり、入力欄は作れないので気をつけてください、適当な白いパネルを書いた方が多分早いです。

マウスオーバーをして「描く」を選択すると、上記の画像にもあるような「スプライト1」が出てきます、名前は後で「白」にでもしたほうがいいです。

で、とにかくこのスプライト1を作る事がめっちゃ大事です。
スクラッチを触ってわかった事ですが、スクラッチはスプライトが全ての始まりで "塊" であり "魂" です。

なんとコードに当たる部分もスプライトに紐づけて書いていきます、僕ら的な感覚で言うと「クラスを作る」と「スプライトを作る」がニアリーイコールだと思った方がここから後がすんなりいくと思います。

はい、ではスプライトができたら適当に四角を書いてください。
これはそこらへんのペイントソフトと変わらないので適当にやってください。

で、大事なのは「大きさを気にしない事」です

僕はここで 「いや今書いたこれ何Pixelなの?そもそも使える画面サイズいくつなの???」

ってとことでまたも半日くらい使いましたが、正解は「気にしない」です。

まあそりゃそうですよね、小学生でもできるって謳い文句なんでそんな細かい数字は気にしちゃダメなんです。

作ったスプライトは後で拡縮で大きさを微調整することができます、なので実際のPixel数とかは気にしないでください。とにかく正方形を書きましょう。

クローンで配置

押したら黒になるとかそういう細かい話は後にしてまずは28x28を配置します。

画面左上の「コード」「コスチューム」「音」からコードを選択してください。

さて、この「コード」ですが、先ほども書いた通り右下で選択したスプライトと完全に紐づいています

いまから書くコードは選択中のスプライトの中でのみ使えるコードです。

他のスプライトから使うためにはイベントトリガーを発生させてメッセージングする必要があります、つまりオブジェクト指向のメッセージパッシングなんですねこれ。

繰り返しますがグローバルなコードは存在しなくて、全部スプライトに紐づいたコードしかありません。

っていうか、関数ブロックとかこの後で作りますが、それもグローバルとかutilとかないんでちゃんとやるなら割と管理に悩む事になりそうです。今回は推論しかしないので大半のコードは「すいろん」ボタンに書いてあります。

とりあえずまあ今はクローンの話をしましょう。

スクラッチはどうやら起動イベントが「緑の旗を押す」のようです。

なので、緑の旗が押された時 から始めていきます。

コードはこんなんですね、これで緑の旗を押すととりあえず配置してくれるんですが、当然初めに作ったスプライトは大きさの調整がされてないのでめちゃくちゃな状態だったと思います。

なので、この開始座標とか、変化する値を調整してください。

緑の旗をクリックすれば画面を初期化してから動くんでいい感じになるまで調整します。

また、大きさについても、この時点で画面右下スプライトの「大きさ」を修正していきます。

デフォルトは100ですが、どうやらこれ固定小数点ズーム倍率(100=1.00)らしく下げるとスプライトが小さくなります。

というわけでこの時点でサイズを変更したり位置座標をかえることでスプライトの大きさを決定できるから先ほどは雑にとにかく正方形かいてくれって言ってたんですね。

あ、ここで一つ注意点がありまして、右下から左上に向かってクローンしてください。

これ後でINPUT層にいれるため直列にしますが、その時に左上から右下に向かって入力を配置したいですよね?そのためにはスプライトは右下から左上に向かってクローンを配置する方がいいです。

ま、左上から右下に向かって配置して、入力は後でリバースするでもいいんですけどね。

僕は入力が反転してる事に後で気がついて右下から左上になるよう開始座標とインクリメントの数字を変えました。

あとこの時点で気がついた人がいると思うんですが僕のコードは入力欄が14x14になっています。

本来のMINSTは28x28なんですが、どうやらスクラッチは表示できるスプライトに上限があるようで、784個のスプライトを配置できませんでした、つーわけで、僕は14x14へ縮小しました。

もし28x28の入力欄を用意する方法を知ってる人いたら教えてください。

黒いスプライトを作成

はい、入力欄の配置が終わってから反転したスプライトを用意します。
配置が終わってからじゃないと複製したスプライトと元のスプライトのサイズを合わせる作業が入るので面倒です、先に配置をしてから複製しまよう。

ちなみに僕は白背景に黒い文字でやりましたが、みなさんご承知の通りMINSTは黒背景に白文字です。

MINSTのデフォルトに合わせる人は黒背景で押されたら白になるようにするので、スプライト1を黒、2を白にしてください。

押された時のコード

さて、押されたらといいましたが実際はぽちぽちおして入力するのは体験が悪いのでマウスでなぞると黒くなるようにします。

ここに「ドラッグできないようにする」というブロックがありますね。

でもどうやってもマウスで動かせちゃうんですよねこのスプライト達

これどうやら、スクラッチの仕様らしく

「ドラッグできないようにする」 が有効になるのは画面右上の

この 拡大ボタンを押した時だけなんですね。

そ・ん・な・ん・わかるか!!!!!!

これはマジのマジで半日は悩みました、LLMに聞いてみようと最近一番性能が高いと言われるGeminiに聞いても的外れな事ばっかり俺に指示してくるのでここから先はマジAIなしで組みました。

ちなみに Geminiをクビにした理由のもう一つとして、この先からリストを扱い始めますが

Geminiに聞くと二次元配列とかlist_i で問い合わせる事ができるとか、関数定義ブロックにリストを指定できるとか、嘘ばっかり付いてきます、でも僕のやる範囲では組めませんでした。

もし標準のスクラッチで二次元配列とか三次元配列組めるやり方あったら教えてください。

直列リスト化

さて、そん感じで14x14のスプライトに文字がかけるようになったんでこれをリストに突っ込みます

この「リスト」ですが、リストと変数はグローバルにできます。っていうかしないときつすぎるんで基本的に全部グローバル変数にした方が楽です。

さて、グローバルに作ったリストにデータ入れるために、ボタンイベントを発生させます。

適当にスプライトを作って、その中に以下のコードを書きます。

この「送信を送る」がイベントリガーですね、このメッセージを受けて入力欄スプライトに以下のコードを追加して、入力欄のデータをリストに入れます。

さて、ここでエンジニアとしてはめちゃくちゃ気持ち悪い感じがしますね

「順番、どこで指定している?」

はい、指定ありません。

「非同期処理のメッセージ飛ばしておいて、そこはなに、運任せってこと?」

はい、そのとおりです。

まあ 何度か試したところ同じ順番だったんでおそらく大丈夫だとは思います。

最後にクローンしたスプライトから順に追加されていました....いやもう自分で書いてても悲Cですが、このまま進みます。

あ、それと最後にリストから一個消している部分がありますが。
これはクローン元にしているスプライトの分です、これは入力欄に含まれていないのに画面上には存在しているせいでメッセージを受け取って動いてしまうためそれを後から消しているんですね。
これを消すためにINPUTのリストの中身が揃うまで待機する処理が入っています。

関数作成

さて、ここまでいければ後は簡単です、いつも通りに必要な関数を組んでいきます

ただ関数の組み方にも一個コツがあって、Returnがありません。

なのでグローバル変数に結果を書き込む手法になります。

そういえば途中のlayer構造ですが、スクラッチでは一次元配列しか使えないので組むの面倒になりそうだなと思い

14x14(196)→ 8 → 4 → 10 にしました。

Python上での精度評価では 10epoch回して 80%超えてるしScratchでやるんだから 10%超えてりゃ別にいいんちゃうか
くらいのノリでめっちゃ縮小化してます。

hidden_1

はい、以下は見たままですね。
先ほども書いた通りReturnは無いので、グローバルに用意したリストへ出力は書き込んでいきます。(H1_outputですね)

W1_col_0 から W1_col_7 まで用意していますが
これも初めに書いた通り、 W1_col_i でアクセスできりゃもっと簡単に書けるんですがスクラッチではできません。
なので一次元配列にflat化してindex操作で処理するか
ここでやったみたいに、列ごとにリストを用意するかになります。
もし途中の隠れ層を増やしたい人はindex操作のほうをおすすめします。
DOTの関数は正直なくてもよかったなと思っていますが、先に作っちゃってたので使ってます。

hidden_2

特に言う事なし

output

特に言う事なし

出力


出力ですが、ただの推論機なんでSoftmax外して一番大きい値を採用しています。

スクラッチには四則演算しかないので、指数関数の計算がさすがに面倒だしそもそも推論機にSoftmaxいらねえわということで外しました

あとコメントにもついてますがスクラッチの配列は indexが1から始まります。

これもバグが出て結構苦労したんですが、このindexがずれるせいでループの初期値を0からはじめるとバグを生みます
後MINSTのラベルとずれているとか普段何気なく使っているせいでかなり気持ちが削れましたね。

はい、ここまでで推論機のモデルは完了しましたが長くなってきたので一旦終了

重みの移植は次回に持ち越します。

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/articles/backpropagation_on_scratch

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

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

Discussion