2026.03.08 22:09
プーリング処理を書いてみる
・昨日、「ただ、全部ゼロだったらまずいので、そこは避けないとだめだな。
」なんて書いたけど、そもそも「最大値の位置が1」という条件なんだから、全部0ということはありえない。ということに、寝入りばなに気がついた。なんだかボケてるな。
・ということで、プーリング処理をコーディング。
2026.03.07 23:30
プーリング層のコーディング方針を整理
・プーリング層を考えることに。こちらのフォワード側は要するに2x2なり3x3といった領域に分割して、その領域を代表する値を作る。
・最もよく使われるのは最大値をとるMaxプーリングと呼ばれる手法らしい。
・たとえば2x2の領域ごとに分割してMaxプーリングするとき、入力が
1 2 3 4
4 8 6 7
7 3 9 2
4 8 3 7
という具合になっていれば出力は
8 7
8 9
となる。これはnp.maxを使うと割と簡単に抽出できる。
・入力は畳み込み層の出力なので、構造は[バッチサイズ,フィルタ数、 Xサイズ、Yサイズ]な4次元配列なので、ここからプーリングサイズ分切り出して
xmax=np.max(x,axis=(2,3),keepdims=True)
して4次元配列まま最大値位置を取り出す。
・で、バックプロパゲーションのためには最大値がどの場所だったのかも記録しないといけない。こちらは
mask=np.where(x==xmax,1,0)
で一発でいける。めったに無いけど、運悪く全く同じ値になる場所が複数あったときには責任を分け合ったほうが良いのだろうか。もしやるなら、
mask=np.where(x==xmax,1,0)/np.sum(xmax,axis=(2,3),keepdims=True)
で良さそうだ。
・ただ、全部ゼロだったらまずいので、そこは避けないとだめだな。
・あとはバックプロパゲーションするときには戻ってきた行列(仮にerr_inとする)との乗算、つまり
err_in*mask
をしてやれば、NumPyが勝手にブロードキャストしてくれるので、該当箇所だけ値が戻る形にしてくれる。
・…というのが、どうやらプーリング層の実装になるらしい。明日暇があったらコーディングして完成させよう。
・しかし、CNNってやたらと時間かかりそうだな。やはりもっとC/C++なりで作成された高水準ライブラリを使わないと駄目かもしれないな。
・とりあえず実際にMNIST認識をやらせてみてからPyTorchを使う方向も考えよう。
2026.03.06 23:46
バックプロパゲーションもなんとか書けた
・Chromeに脆弱性があったとかでアップデート
・いつものことながら自動ではうまくアップデートしてくれないので、.debパッケージをダウンロードしてsudo apt installでインストール。最後になんかエラーっぽいメッセージは出たけど、Chrome再起動したら「バージョン 145.0.7632.159(公式ビルド) (64 ビット)」となっていたので、たぶん良いのだろう。
・ということで、CNNの続きでバックプロパゲーションを書く。
・行数にすればわずか数行の演算なのだけど、やりたいことをどう書いたら良いのかという感じ。ウェイトやバイアスはまだ良いけど、入力に戻す誤差成分を算出するのはちょっと面倒だった。
・行列の形でいうと、バックプロパゲーションのときに後段から戻ってくる誤差データは[バッチサイズ、出力チャンネル数(フィルタ数)、Yサイズ、Xサイズ]な4次元配列データ。バッチサイズというのは、一度に複数の画像データをまとめて処理するときの画像の枚数。フィルタはたとえば3x3といった小さい領域に対する重みデータで、これが元画像から切り出した同じサイズ(3x3)のデータと積和演算されているわけだ。
・元画像側がカラー画像だと1つの画像がRGBの3枚で形成されている。これが入力チャンネル。
・で、ごちゃごちゃしたけど、1つのニューロンは
・入力:9つ(3x3)の画素データがチャンネル数分
フィルタ(3x3の重みデータ)がチャンネル数分
バイアス値がチャンネル数分
・出力:画素の各位置と対応するフィルタの値を掛けて全部足す、さらにバイアス値も足す
ということをやっていて、これが平面にズラーッと並んだものがあり、更にこれがフィルタ数分あるという感じ。
・重み(フィルタ)
weight[output_ch_num, input_ch_num, fil_xsize, fil_ysize]
・誤差データ
err_in[Bach_Size, output_ch_num, xsize, ysize]
ということで、こいつらの積和ってことなので、まずは掛け算するために次元を揃える。err_in側は1点ずつ(ニューロン1個分ずつ)切り出すので、i,jをy方向、x方向のスキャンとして、
weight_reshape = weight.reshape(1,output_ch_num, input_ch_num, fil_xsize, fil_ysize)
err_reshape = err_in[:,:,i:i+1,j:j+1].reshape(batch_size,output_ch_num,1,1,1)
で、あとは出力チャンネル数方向で積和をとってやる(複数のフィルタをかけたものを通したものからそれぞれ入力に誤差分が戻って合流する感じ)
np.sum(weight_reshape*err_reshape, axis=1)
入力データは[batch_size, input_ch_num, input_xsize, input_ysize]みたいな感じで4次元。ここからフィルタサイズ分切り出したところに足せば良いんだから、
d_input[:,:,i:i+fil_xsize, j:j+fil_ysize] = np.sum(weight_reshape*err_reshape, axis=1)
何ていう感じ。わずか数行だけど、ここにたどり着くのは少々しんどかったよと。
・他の部分もまとめてなんとかバックプロパゲーションは書けた(と思う)
・一応Gemini君は「完璧」と言ってくれたけど、本当にちゃんと動くのかな?
2026.03.05 18:13
CNNのバックプロパゲーションが見えてきた&全結合の修正で認識率96%
・というところで、バックプロパゲーションを・・・と思って書き出そうとしたらいきなりVSCode君がコードを吐き出してくれた。たぶんCoPilotが生成したんだろう。
・なんとなく面白いけどねと思いながら、それを下に置いて1行ずつ自分で改めて書きながら読むということをしていたのだけど、途中でわけがわからなくなってきた。
・ということで、困った時のいつものGCC・・・この手のはGeminiも得意だよねということで質問していたら、なんか話が噛み合わない。
・で、ある程度まとまったコードブロックを提示したら、「それ、変だよ」と指摘してきた。
・いちいち指摘が的確というのか、「なんでこの値を掛けてるんだ?」と疑問に思っていた部分がやはり間違っている。
・「CoPilotが自動生成したんだけどね」といったら「あはは・・そうでしたか」ときて、CoPilotに対してちょっと「あいつのコード生成はね・・・」的な指摘をしてくるのがまた可笑しい。
・というところで、じっと考えて整理できてきた。わかってしまえばそれほど難しくは無いのだけど、バッチサイズ分まとめてたり、フィルタ数分まとめて考えていたりというのを一度に考えると頭の中で行列がごちゃごちゃしてややこしいのだな。
・そんなこんなでやっていたら、全結合の方で軽い勘違いを見つけた。まぁ、2層程度なら問題ないんだけど、バックプロパゲーションで前段に戻すときにもLR(Learning Rate)を掛け算してしまっていたという凡ミス。
・これを修正してLRを更に小さくして(でないと、途中でオーバーフローしやすい)再度全結合でMNISTしてみたら正答率96%となった。うん、標準的な値とされる範囲になったな。
2026.03.04 17:46
とりあえず順方向は良いかな
・ぼちぼちとpythonで書く。四次元配列に混乱しそうになりながらとりあえずCNNクラスの__init__()と順方向はできたかな?
・バックプロパゲーションがちょっと難しい。というのか、4次元の配列を眺めているとどうも頭が混乱しそうになる。
・もう一回ちょっと整理しよう
2026.03.03 23:41
CNNの仕組みがだいぶ見えた・・・かな
・さて、GCC(Gemini/Chatwork/Copilot)君に書かせたコードを眺めていると、フィルタの枚数やX,Yのサイズなどの他にChannelというパラメータがある。何だこれ?ということでお尋ねしたら例えばカラー画像データのRGBのようなものとのこと。なるほどね。
・たとえばRGBであればそれぞれについてフィルタをかけてやって、RGBの三枚分のデータを全部足し算するという流れらしい。
・PyTorchなどのライブラリを使えば楽できるらしいけど、今回はNumPyだけでやるので地道にループさせる。
・で、もう一つ。出力側にもやはりチャンネルがある。画素ごとの重みをつけるフィルタの構造は
filter[出力チャンネル数、入力チャンネル数、Yサイズ、Xサイズ]
という4次元配列になる。たとえば、入力がRGBの3チャンネルあって、フィルタのサイズが5x5ならば
filter[出力チャンネル数,3,5,5]
つまり、入力チャンネル数×出力チャンネル数分のフィルタがあるということ。これはつまり、入力画像の1つのチャンネルの一つずつ(たとえばRGBならばRの画像など)についてN個のチャンネル(つまりN個のフィルタ)があるという具合。
・要するに演算の考えとしては1つのニューロンが元画像の3x3なり5x5なりといった狭い領域について全結合ニューラルネットと同じような積和演算をしていて、これがズラッと並んでいる(元画像の1ドットずつシフトしながら)。
・外から見るとちょうど元画像を底面としたピラミッドがたくさん重なり合いながら並んでいる感じで、これが入力チャンネル数×出力チャンネル数個あるわけだ。
・全結合のように、このニューロンすべてが独立したウェイトとバイアスをもたせるという手もあるけど、そこを節約して同じ出力チャンネルに属するニューロンのウェイトやバイアスは共通で使うことにした。それをフィルタと称している・・・とまぁそんな感じで解釈できるのだろう。
そんなことをすると要求されるメモリ量が膨大なものになってしまう。そこで同じチャンネルのニューロン(要するに一つのピラミッド群)
のウェイトやバイアスは同じものを使うことにしていると思えば良いのだろう。
・
3x3とか5x5とかのパターンを使いまわしているという点
全結合ニューラルネットの1層目なんかと同じようなものと思えば良いのかな?と
イメージ的には全結合のときに同じ入力に対してズラッとニューロンが並ぶようなのと似ていて、同じフィルタをかけたものを
・
2026.03.02 08:33
Claudeか。生成AIの名前はCがお好き?
・米国国防省がらみで花札君がお怒りになったとかいうClaude(日本語版はこちら)をちょっとお試し。
・アカウントを作らなくても、このAIチャットのところで遊べるのでちょっと試してみたけど、なんとなくしっくりこない。Geminiのおべんちゃら(死語?)にもいささか参るけど、こちらの回答の雰囲気もなんとなく。
・ちょっとしたプログラムを作らせてみたらまぁ普通に生成してくれたし、読みやすくなってはいるけれど、もう少しちゃんとコメント入れてほしいかなという感じ。
・これもお仲間に入れるとGCCCか。生成AIの名称は’C’がお好き?
2026.03.01 07:37
CNNのためのテストパターンを作っておこう
・さて、CNNの仕組みとしてはほぼ正解だった(らしい)ので、現状の全結合ニューラルネットの前処理的に畳み込み層とプーリング層を追加することに。
・いきなりMNISTで試すとわけがわからなくなりそうなので、単純なものでフィルタがどう形成されるのかわかるように8x8の単純なパターンを作成してまずは全結合でテスト。
・最初うっかりして全く同じデータに違う答えを与えたら見事に収束せず。あわてて修正したらすぐ収束して予定通り。
・さて、ぼちぼち書いていこう。
2026.02.28 23:58
CNNってこういう感じで良いのかな?
・CNNのバックプロパゲーションを考える。
・要するにフィルタを移動させて得た画像データを縮小したものに対する補正値が戻ってくるわけだ。
・たとえばフィルタを3x3として、2x2の4ドット分を1ドットに縮小して、このドットのデータが全結合ニューラルネットに入るという構成を考える。
・一応、今の理解では順方向の操作は
1)元画像の4x4の四角い領域を3x3の除き窓で覗く。
2)左上、右上、左下、右下の4箇所の3x3の領域について、それぞれの画素データと、対応する位置のフィルタのデータを掛け算して和を取る。(イメージとしては、照度センサの前にパターンの描かれたシートを貼り付けもので明るさを測っているようもの)
たとえば
フィルタ側を
F0 F1 F2
F3 F4 F5
F6 F7 F8
というデータ、画素側が
G00 G01 G02 G03
G04 G05 G06 G07
G08 G09 G10 G11
G12 G13 G14 G15
として、フィルタが左上なら
F0xG00 F1xG01 F2xG02
F3xG04 F4xG05 F5xG06
F6xG08 F7xG09 F8xG10
となるわけで、これら9つを足したものに更にバイアスを足したものをつくる(仮にSUM00とかする)
3)窓を元画像全体に渡って1ドットずつ(1枠ずつではなく)移動させると
SUM00 SUM01 SUM02 SUM03・・・
SUM10 SUM11 SUM12 SUM13・・・
SUM20 SUM21 SUM22 SUM23・・・
・・・・
となる
4)こうして得られたものをたとえば縦横2ドットずつまとめるなら
SUM00 SUM01 SUM02 SUM03
SUM10 SUM11 SUM12 SUM13
といった4画素分ずつまとめて、たとえばこの中の最大値を採用して4ドットを1ドットにする。これがプーリング層と呼ばれるものの役割ということらしい。
5)全フィルタについて同じようなことをする。ここまでの例なら
元画像の画素数÷4×フィルタの枚数
個のデータ列になる。これを全部まとめて全結合ニューラルネットに食わせる。
という具合。
・さて、このときたとえば、左上の4つをまとめたものに対してバックプロパゲーションで戻ってきた値というのは、SUM00,SUM01,SUM10,SUM11の4つに共通して使われる。
・ただ、「誤差の責任」はプーリングした時に採用したもの・・・たとえばSUM00だけとか、SUM11だけとか・・・にしかないので、バックプロパゲーションで調整するのにつかうのは「採用したもの」だけ
・たとえばSUM00が採用されたものだとすると、これは
F0xG00 F1xG01 F2xG02
F3xG04 F4xG05 F5xG06
F6xG08 F7xG09 F8xG10
の和だったわけで、とすると「誤差の責任の割当」は全結合のバックプロパゲーションと同じ。全結合層から戻された値をVback、学習率をLRとすれば、たとえばF00の補正はF00 ー= Vback*LR*G00てな具合。
・これをプーリング層出力の(元画像画素数÷4)個分処理するとフィルタ1個分が補正される。で、全フィルタ数分やれば全部のフィルタが補正される。以下は繰り返しである。
・ただ、加算されていく数が多いので発散しかねないから、全部足した後でプーリング層出力の数で割って平均を取ると良いんだろうな。
・たぶん、こんな感じで大きく外してはいないと思うのだけど。もう一回本当にそうなのか確認しておこう。
2026.02.27 16:53
CNNを試すか。ちょっと予備学習
・まぁそんなところで、全結合ニューラルネットワークの方はほぼ作り方がわかった(気がする)。活性化関数を色々変わったものに変えたり、ウェイトやバイアスの変化のさせ方を弄ったりしても面白いことが起きるのだろうけど。
・というところで、mnistで遊んでいたためか、GCC(Gemini/Chatwork/Copilot)君からは「CNNやってみない?」というお題がきた。
・全結合だと画像が上下左右に動いたりしたときに全く違うデータとして扱われてしまうので、これをまとめてしまおうという仕掛けがCNNということらしい。
・CNNはConvolutional Neural Networkの頭文字をとったもので一般的には「畳み込みニューラルネットワーク」と訳されているけど、Convolutionalのvolutionって回転を意味してなかったっけ?と辞書を引いたらほら案の定。
・「くるくる撒いた状態、渦巻き、込み入った状態、複雑」っていう感じで、なんとなく糸巻き的なものをイメージするようなネーミングではある。
・やっていることをちょっと検索してみた今現在の理解はこんな感じ
1)に小さい四角い窓(2X2とか3X3とか)を用意して、この中で重み付けをする(フィルタと呼んでいる)。すると、例えば横線と右斜め下がりの線とかの特徴があるとみなせる部分が強く現れる。(真っ白でも反応するけど、まぁそれはそれとして)
2)この窓をスライドさせて新しい行列を作る。
3)そして、このフィルタのパターンを色々用意しておいてそれぞれごとに行列を作る
4)フィルタが画素外に出てしまわないようにすると、その分行列がちいさいなるのでその領域は0などでパディングしておく。
・ここまでが「畳み込み層」とよばれるもので、この後が「プーリング層」とよばれているようだ。
5)こうしてできたものをたとえば縦横1/2に圧縮する。まぁ1/2なら縦横2つの画素の中の最大値をとるという方法が紹介されていたけど、「ぼかしている」という感じでもある。すると、縦横1ドットずれても結果は同じになる。
6)こうして出来上がったもの(縦横1/2の画像データ×フィルタ数)分のデータを全結合ニューラルネットで学習させる。
・そうすると、たとえば、横棒に強く反応した画素の右隣に縦棒に強く反応する画素があったとすればこれはそのあたりの場所で十字とかT型などの交差に反応するようなニューロンが生まれる可能性があるということになるのだろう。
・VOUTIONの字句を活かすなら、製糸工場など糸を紡ぐときのように、「窓」エリアから伸びた細い糸(画素データ)がまとめられていくようなのをイメージすると良いのかなという感じ。
・前処理はたぶんちょっとデータ数も多いけど「難しくはないけど手間はかかる」という類だろう。
・たぶん、フィルタ部分も初期値は適当に決めて学習するに従って最適化していくことになるのだと思うのだけど、この学習のやり方がCNNのツボなんだろうな。