コンピュータに対して一段上を目指すには離散数学や線形代数が必要だけど、

生物学出身だとこれらは触れる機会がほぼない。

※私が学生の時に受講した数学は統計学のみ


より深い理解が欲しいので、

去年から数学を勉強しているのだが、


先週、

ちょうど良いタイミングでオライリーから行列プログラマーという書籍が発売され、

その問題を解いている。


その中で苦戦して、

しかもネット内を探しても解答が見つからないものがあったので、

自身のメモとして投稿しておく。




p58の課題1.4.10で



Pythonで人の顔の画像(左)を読み込み、画像データを複素数の集合に変換して、

ガウス平面にドットで表示する。

変換の際、内包表記で対応することが条件となっていた。

※画像の読み込みとガウス平面への描写のための関数は用意されている

http://resources.codingthematrix.com/のThe Fieldにある各ファイルを利用します。




まず、この課題を解くにあたって、

Pythonにおける諸々の書き方をおさらいしておく必要がある。


Pythonで集合を扱うためには{}を用いる。


>>> {1,2,3,4,5}
{1, 2, 3, 4, 5}
>>> {1,1,1,3,4}
{1, 3, 4}
>>> {1,3,2,6,5}
{1, 2, 3, 5, 6}

集合論における集合では、数の重複や順番はないため、

重複している場合は重複した数字が省かれ、

順番は正しく整列される。


Pythonにはタプルというデータ構造があり、

()で扱い、集合論でいうところの関係を扱うことができる。


>>> (1,3)
(1, 3)
>>> (3,1) (3, 1) >>> (1,1) (1, 1) >>> (1,1,1) (1, 1, 1) >>> (3,1,1) (3, 1, 1)

関係は重複や順番も重要になるので、集合の時のデータの整形はない。


他言語でいうところの配列に似た構造として、Pythonにはリストがある。

リストは[]で扱う。


>>> [1,2,3,4,5]
[1, 2, 3, 4, 5]
>>> [2,1,3,5,4]
[2, 1, 3, 5, 4]

リストは配列みたいなものなので、重複や順は考慮されない上、


>>> list = [1,2,3,4,5]
>>> list
[1, 2, 3, 4, 5]
>>> list[3] = 3
>>> list
[1, 2, 3, 3, 5]

データの書き換えも行える。


>>> list = [[1,2,3], [2,3,4]]
>>> list
[[1, 2, 3], [2, 3, 4]]

リストの中にリストの多重構造もありで、

listの中から1の値を取得したい場合は、


>>> list[0][0]
1

このようにする。

上書きや値の取得の際の数字の指定は割愛します。




続いて内包表記だけど、

今まで触れてきたプログラミング言語では使わなかったので、

この表記で苦戦した。


たとえば、集合論で自然数の有限集合を書く時、

{x|x∈N, n < 6}

こう書くことで、6より小さな自然数の集合、

{1,2,3,4,5}

となる。


これをPythonで似たように書こうとした時に内包表記を活用する。

書き方は、


>>> {x for x in range(1,6)}
{1, 2, 3, 4, 5}

このように{}内でfor文を使って組み立てることが出来る。

この書き方を内包表記と呼ぶ。


>>> [x for x in range(1,6)]
[1, 2, 3, 4, 5]

内包表記はリストでも使える。




複素数の書き方は、

数学的には1 + 3iで左に実数で右に虚数で書き、虚数はi(アイ)で表す。


しかし、Pythonでは、iは何かと被るらしく、jを使うことになっている。


>> 1 + 3j
(1+3j)
>>> type(1+3j)
<class 'complex'>



それでは、



この課題をやってみる。


>>> from image import file2image
>>> data = file2image("img01.png")

左側の画像を読み込み、data変数に結果を挿入する。

dataに入っている値は大きいのでデータ構造だけ書くと、


data[y][x]

このような多重構造で、(x,y)が(0,0)で画像の左上、(165,188)が右下の166px × 188pxの画像であり、

各ドットが


>>> data[0][0]
(183, 183, 183)

明度(0〜255)のタプルで返ってきている。

明度は0が黒で、255に向かうに従って薄くなり、255で白となる。

今回の課題では明度が120以下をドットで描写という制限があるので明度が120以下であれば、

ガウス平面に描写する複素数を指定すれば良い。


ガウス平面に描写するにあたって、

複素数は集合ptsに格納しておく必要があり、それを内包表記で書くことが課題で、


pts = {(x+y*1j) for (y, d) in enumerate(data) for (x, v) in enumerate(d) if v[0] < 121}

とりあえず、このように書いてみた。


今回はdata配列からx,y,明度を取得しなければならず、

Pythonのforでリストのインデックスも同時に取得するためには、


for (y, d) in enumerate(data)

リストをenumerate関数でかましておく必要がある。


今回は更にリストの多重構造のため、一度目のforで取得できたyを元に再度for文を書かなければならない。


for (y, d) in enumerate(data) for (x, v) in enumerate(d)

それがこれ。

最後に明度のタプルから値を取得して、その値が120以下であるか調べるために、

内包表記の末尾にif文を追加した。


今回取得した結果でガウス平面に描写できるか試してみると、


>>> plot(pts, 180)


あら、

逆さまに表示された。


dataに格納されている画像の多重リストのyは画像の上から下に向かって数字が大きくなるけど、

ガウス平面では下から上に向かって数字が大きくなる。


ということで、画像の高さからyを引いてあげる処理が必要なんだ。


というわけで、


>>> pts = {(x+(189 - y)*1j) for (y, d) in enumerate(data) for (x, v) in enumerate(d) if v[0] < 121}
>>> plot(pts, 180)

この様に変更してみたら、



向きが正しくなった。