本文へスキップ
ひもとくAI

画像は数の表

レッスン 1/7

この章で学ぶこと:画像は数の表

第6章のミニプロジェクトでは、ニューラルネットワークで手書きの数字を見分けました。テストデータでの正解率は約0.96でした。

実際の画像認識(写真に写っているものを当てる、手書きの文字を読むなど)では、畳み込みニューラルネットワーク(たたみこみ、英語の頭文字で CNN)という形のニューラルネットワークが広く使われています。この章では、CNNの仕組みを、中学数学と第4〜6章で学んだこと(内積、ReLU、誤差逆伝播)だけで組み立てていきます。

最初のレッスンでは、その準備として、コンピュータの中で画像がどう表されているか を確かめます。

画像は、数を並べた表

方眼紙に数字を書き、マスごとに「どれくらい濃く塗られているか」を数で書き込む場面を想像してください。白いマスは0、真っ黒なマスは16のように決めれば、1枚の絵を「数の表」として書き表せます。

第6章で使った手書き数字のデータは、まさにこの形をしています。次の図は、1枚目の画像(数字の0)です。8行×8列のマスに、0〜16の濃さが入っています。

01234567000513910010013151015502031520118030412008804058009805041101127060214510120070061310000
手書き数字のデータの1枚目。8行×8列のマスに0〜16の濃さが入っている。濃いマス(大きい値)が、数字の0の形の輪になって並んでいる。上と左の小さな数字は、行と列の番号(0から数える)
0行目005139100
1行目001315101550
2行目0315201180
3行目041200880
4行目05800980
5行目0411011270
6行目02145101200
7行目0061310000
手書き数字のデータの1枚目。8行×8列のマスに0〜16の濃さが入っている。濃いマス(大きい値)が、数字の0の形の輪になって並んでいる。上と左の小さな数字は、行と列の番号(0から数える)

濃いマス(大きい値)をたどると、0の形の輪が見えてきます。コンピュータにとっての画像は、この 数の表 そのものです。表の1つ1つのマスを 画素(がそ、英語で ピクセル)と呼びます。

現実の画像も、同じ仕組み

スマートフォンで撮った写真も、同じように数の表です。違うのは大きさと枚数です。

  • 大きさ: 例えば縦3000×横4000マスなら、1200万マス(「1200万画素のカメラ」の画素は、このマスの数のことです)
  • 枚数: カラーの画像は、赤・緑・青の3色の濃さを、それぞれ別の表で持ちます。表が3枚重なっていると考えてください

出典: Alpaydin, E. & Kaynak, C. (1998). Optical Recognition of Handwritten Digits [Dataset]. UCI Machine Learning Repository. https://doi.org/10.24432/C50P49 ライセンス: CC BY 4.0

NumPyで画像を扱う

scikit-learnの手書き数字のデータには、画像を 8行×8列の表のまま 取り出せる images があります。第3章で学んだNumPyの配列として扱えます。

from sklearn.datasets import load_digits

data = load_digits()
images = data.images

images.shape    # → (1797, 8, 8): 1797枚、1枚は8行×8列
image = images[0]    # 1枚目の画像(数字の0)
image.shape     # → (8, 8)
image[2, 3]     # → 2.0

shape は、配列が「何が、いくつずつ並んでいるか」を表します。(1797, 8, 8) は、「8行×8列の表が1797枚」という意味です。

image[2, 3] は、上から2行目・左から3列目のマス の値です。行と列の番号は、どちらも 0から数えます。一番上の行が0行目、一番左の列が0列目です。書く順番は必ず 行、列 です。値が 2 ではなく 2.0 と表示されるのは、このデータの濃さが小数の形で保存されているためで、値の大きさは同じです。

01234567000513910010013151015502031520118030412008804058009805041101127060214510120070061310000
1枚目の画像。太い枠のマスが image[2, 3](2行目・3列目)で、値は2。輪の内側に近い、薄いマス
0行目005139100
1行目001315101550
2行目0315201180
3行目041200880
4行目05800980
5行目0411011270
6行目02145101200
7行目0061310000
1枚目の画像。太い枠のマスが image[2, 3](2行目・3列目)で、値は2。輪の内側に近い、薄いマス

第6章では、表を1列に並べ直していた

第6章で使った data.data は、同じ画像を 1行に並べ直したもの です。0行目の8個、1行目の8個、2行目の8個……と、左上から順に64個を並べています。

data.data.shape    # → (1797, 64): 1枚は64個の数の並び
data.data[0][:24]  # → [0, 0, 5, 13, 9, 1, 0, 0,   0, 0, 13, 15, 10, 15, 5, 0,   0, 3, 15, 2, ...]

1行は8個なので、「rr 行目・cc 列目のマス」は、並べ直すと r×8+cr \times 8 + c 番目(0から数える)になります。例えば2行目・3列目は、2×8+3=192 \times 8 + 3 = 19 番目です。

1列に並べると、「隣のマス」が分からなくなる

画像では、隣り合うマスの関係 に大切な情報があります。線は、濃いマスが縦や横につながったものです。輪は、濃いマスが丸く並んだものです。

ところが、1列に並べ直すと、この関係が見えにくくなります。2行目・3列目(19番目)のマスのすぐ下にあるマスは、3行目・3列目、つまり 3×8+3=273 \times 8 + 3 = 27 番目です。表の上では隣なのに、並べ直すと8個も離れてしまいます。

実験:マスの並び順を入れ替えても、正解率はほとんど変わらない

第6章のニューラルネットワークが、隣のマスの関係を使っているかどうかを、実験で確かめました。64個のマスの並び順を、すべての画像で同じように でたらめに入れ替えてから、同じ設定で学習し直します。

学習に使ったデータテストデータでの正解率
そのままの順番0.964
マスの並び順を入れ替えたもの0.958

並び順を入れ替えた1枚目の画像は、次のようになります。人間には、もう0には見えません。

0000101296141351182015090150130803711000008125000400813100151001200125084100500
1枚目の画像(数字の0)の64個のマスを、決まった順番で入れ替えたもの。濃いマスがばらばらに散らばり、輪の形は見えない
0行目000010129
1行目61413511820
2行目15090150130
3行目803711000
4行目008125000
5行目40081310015
6行目1001200125
7行目084100500
1枚目の画像(数字の0)の64個のマスを、決まった順番で入れ替えたもの。濃いマスがばらばらに散らばり、輪の形は見えない

それでも、ニューラルネットワークの正解率はほとんど変わりません。第6章のニューラルネットワークは、64個の入力を それぞれ別々の数 として扱っていて、どのマスが隣同士かという情報を使っていないからです。

なぜ、これが問題になるのか

8×8の小さな画像なら、これでも十分に学習できました。しかし、写真のような大きな画像では困ったことが起きます。

  • 重みが多すぎる: 1000×1000マスの画像なら、入力は100万個です。隠れ層のニューロン1つにつき、100万個の重みが必要になります
  • 同じ形を何度も覚え直す: 「縦の線」が画像の左上にあっても右下にあっても、同じ縦の線です。しかし、マスを別々に扱うと、位置ごとに別の重みで覚え直すことになります

次のレッスンからは、小さな範囲の隣り合うマスをまとめて見る 仕組み(畳み込み)を学びます。これが、2つの問題を解決する鍵になります。

よくある誤解

  • 「コンピュータの中に、画像が絵のまま入っている」: 入っているのは数の表だけです。「絵に見える」のは、画面に表示するときに、数を色の濃さに変えているからです
  • 「ニューラルネットワークは、人間と同じように形を見ている」: 上の実験のとおり、第6章のニューラルネットワークは、人間には読めない並び順でもほとんど同じように学習できます。人間とは違うやり方で数を扱っています

振り返り

  • 画像は、画素(ピクセル)の濃さを並べた数の表。カラーの画像は3枚の表
  • NumPyでは image[行, 列] でマスの値を取り出す。番号は0から数える
  • 1列に並べると、隣り合うマスの関係が見えにくくなる。第6章のニューラルネットワークは、この関係を使っていなかった

演習

演習を読み込んでいます…