柴田淳『みんなのPython』
- 作者: 柴田淳
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2016/12/22
- メディア: 単行本
- この商品を含むブログ (1件) を見る
『心理学実験プログラミング』(朝倉書店)からPythonに入り、一応簡単な実験プログラムなら書けるようになったものの、仕組みがよく分からぬまま動かしているところもあったので、Pythonという言語そのものについて知ろうと思い読んだ。
全部で13章からなり、前半はPythonをインストールするところから始まり、基本的な関数、組み込み型の使い方へと続く。中盤は、オブジェクト志向の概念を中心に、クラスやモジュール、標準ライブラリの扱い方を説明している。最後には、データサイエンスでの活用例としてNumPyやmatplotlibなどのライブラリでの実践例を紹介している。
自分は、Rしか使ったことがなかったので、メソッドやらクラスやらアトリビュートやら、オブジェクト志向の言語の仕様が分かっていなかったのだが、この本の真ん中のあたりの章(5~9ぐらいまで)を読んでだいぶ見通しがよくなった。
よくよく読むと『心理学実験プログラミング』にも同様のことが書いてある(った)のだが、『みんなのPython』で1章割かれているような内容が、1段落で済まされていたりするため、Python初心者の私のような人間はさらっと未理解のまま流してしまっていたりする。
Pythonそのものへの入門としてはとてもよく出来ているように思ったので、これからPythonをやってみようという人にはオススメできると思う。ただ、これ1冊読んでも、作りたいものまでの距離感というのは分からないような気もするので、作りたいものが明確な人は、それに特化した入門書なりに手をつけて(写経し)、仕組みはそこまで分からないけど動かせるようになってから、その仕組みを詳しく学ぶために本書のような入門書に手をつけても良いのかもしれない。モチベーション的に。
次は、画像処理系あたりに手をつけていこうと思う。
pep8とflake8の導入について
pep8というものがある。自閉症療育に関心がある方なら、TEACCHプログラムの開発したPEP-3(自閉症・発達障害児教育診断検査第3版)というのが思い浮かぶかもしれないが全くもって関係ない。
pep8とは、Pythonでコードを書くときのスタイルガイドである。これに準拠しなかったらプログラムが動かなくなる訳ではないが、一貫したスタイルのコードを書くことで可読性をあげようとかそんな話だと思う。
pep8の日本語訳された文章は以下から読める。
自分が書いたコードがこのスタイルに準拠しているか調べるための文法チェッカーというものもある。便利な世の中である。
いくつかあるうちでflake8というものが有名らしいので試しに使ってみる。
導入はpipが入っていれば簡単で、ターミナル上で以下のコマンドでインストールされる。
$ pip install flake8
使い方もまた簡単で、以下のように引数にファイル名を指定してやれば良い。
$ flake8 test.py
例えば、次のようなコードをチェックするとする。
a = [0,1,3 ] print("Hello World!") #ハロワ
するとこんな感じで、スタイルに沿っていないところを指摘してくれる。
test.py:1:7: E231 missing whitespace after ',' test.py:1:9: E231 missing whitespace after ',' test.py:1:12: E202 whitespace before ']' test.py:3:22: E261 at least two spaces before inline comment test.py:3:23: E262 inline comment should start with '# '
何行目・何文字目に、どんな違反なのかが書いてある。エラーコードの頭にあるアルファベットは何についての違反かを示しており、Eはpep8のスタイルに準拠していないことを示している。
例えば、1行目の7文字目のところでは、「カンマの後に空白が入ってないよ」と教えてくれている訳だ。
Error / Violation Codes — flake8 3.4.1 documentation
Introduction — pycodestyle 2.3.1 documentation
このエラーメッセージを元にさっきのコードを修正すると次のようになる。
a = [0, 1, 3] print("Hello World!") # ハロワ
これで、pep8に準拠したリーダブルなコードになった訳だ。
このflake8はAtom上で動くパッケージも出ており、linter-flake8という名前である。
GitHub - AtomLinter/linter-flake8: Linting Python files on the fly using flake8 with Atom
特に難しいこともなく、上記のREADMEに従ってinstallするとAtomでコードを書きながらリアルタイムで指摘してくれるようになった。
どうせ書くなら読みやすく美しいコードを、ということでflake8の導入の話でした。
信号検出理論事始め
信号検出理論というものがある。もともとは、レーダーの性能など通信工学的な分野で発展した理論でそれが感覚や知覚の心理学研究に応用されるようになったらしい。
以下の本を参考に、自分の学習のメモを残す。(記事の内容の正しさは保証できないため、しっかりと学びたい人は原典をあたることをおすすめする。)
Detection Theory: A User's Guide
- 作者: Neil A. Macmillan,C. Douglas Creelman
- 出版社/メーカー: Lawrence Erlbaum
- 発売日: 2004/10/04
- メディア: ペーパーバック
- この商品を含むブログを見る
はじめに ~考え方編~
まず信号検出理論の特色を一言で言ってしまえば、それは、観察者の反応を(1)提示される刺激による要因と(2)反応のバイアスによる観察者側の要因の2つに分離して記述できることだと思う。古典的な精神物理学の研究では、刺激側の要因と心理反応の関数関係の記述を主としていたが、反応における認知的なプロセスの部分も考慮に入れてモデル化しているというのは、この理論の強みであるように思う。
これだけだと「何のことだ?」と思う方もいるかもしれないので例を出して考えよう。
例えば、次のような事態を考えてみる。ここに嘘発見器があるとする。発見器をつけた状態で話をすると、発見器は「嘘」か「正直」のどちらかの回答を返すものとする。さて、この発見器の性能を検証しようと考え、次のような実験を行う。発見器をつけた状態で、それぞれ25回の「嘘の話」と「本当の話」をする(こういうのを刺激クラスと呼ぶ)。「嘘の話」に正しく「嘘」と言うことができ、本当の話に対して「正直」ということができる割合が高ければ、この発見器は優秀だと言うことができる。この架空の実験の結果を表で示す。
刺激クラス | 「嘘」と反応 | 「正直」と反応 | 合計 |
---|---|---|---|
嘘の話 | 20 | 5 | 25 |
本当の話 | 15 | 10 | 25 |
さて、この嘘発見器は優秀なのであろうか。嘘の話に注目すると、25回中20回も嘘を見破っている。確率で言えば80%の確率で嘘を発見しているので優秀だと言えるかもしれない。しかし、本当の話に注目すると、25回中15回も間違って嘘だという反応を返している。確率でいえば60%の確率で間違った判断を下している。これでは、この発見器が優秀だと言いきることはできなかろう。
このように、片方の刺激クラスにのみに注目していたのでは間違った推論をしてしまう恐れがある。したがって、嘘発見器の正確性を評価しようと思ったのであれば、どちらの刺激クラスも考えなければならない。
ところで、嘘発見器の性能を正確性という一つのパラメータで記述してよいものであろうか。ここに嘘発見器Bがあるとして(さっきの嘘発見器をAとする)、先ほどと同様の実験を行った結果が下記の通りだとする。
刺激クラス | 「嘘」と反応 | 「正直」と反応 | 合計 |
---|---|---|---|
嘘の話 | 16 | 9 | 25 |
本当の話 | 11 | 14 | 25 |
嘘の話に注目すると、64%の正確性で嘘を見破っており、嘘発見器Aよりも嘘を見破る力は弱い。本当の話に注目すると、44%の確立で間違った判断をしてしまっていて、嘘発見器Aより間違って「嘘」だと反応してしまう率は低い。Aの発見器よりも「保守的な」発見器だと言えるかもしれない。
どちらの発見器も、正しく判断できた数(左上と右下のセルの合計)は30であり、同程度の正確性で嘘を検出している。しかし、この2つは嘘検出の正確性では同程度だとしても、その反応のパターンは結構ちがっている。こうした、反応パターンの違いを考慮してモデルを構築するのが信号検出理論の大きな強みだろう。
信号検出理論の用語たち
さて、ここで信号検出理論の用語を導入する。一番最初の嘘発見を例にして話を進める。2つの刺激クラスについて2つの反応パターンがあった訳なので、この組み合わせは、2×2で4つのパターンがあることになる。その用語を以下の表に示す。いかにも、もともとレーダーの研究で使われていたっぽい用語である。
刺激クラス | 嘘 | 正直 | 合計 |
---|---|---|---|
嘘の話 | ヒット(Hits) (20) |
ミス(Misses) (5) |
25 |
本当の話 | 誤警報(False Alarms) (15) |
正棄却(Correct Rejection) (10) |
25 |
この表には、4つの単語があるが実質独立しているのは2つだけなので(例えば、ヒットの数が決まればミスの数は必然的に(合計 ー ヒット)になる)、この表の特徴は2つの値で表すことができる。すなわち、ヒット率をH、誤警報率をFと表すと次のように書くことができる。(F , H)=(.60, .80)
まず、求めるのは検出力を表す指標である。検出力はHが高い時に高くなり、Fが高い時に低くなる(なにせ間違った判断を下している訳だから)ものが望ましい。この条件を満たすシンプルなものは、H - Fである。今の発見器の例だと、 .80 - .60 = .20 になる。
あるいは正しい判断を行なった確率というのでも良い指標であろう。ヒット(H)と正棄却(1 - F)を2で割ると、試行数における正しい判断の割合が算出できる。
今の例だと、( .80 + .40) / 2 = .60 になる。
ここまでで2つの指標を導入した訳だが、信号検出理論でもっとも広く使われる指標はもう少し複雑である。 HとFの確率を、正規分布における確率点に変換した値を計算して得られるd'(読みはディープライム)というものが使われる。
先の例で言うならば、z(H) = 0.842 、 z(F) = 0.253であるから、d' = 0.589 となる。Rで確率点を求めたいのであれば,qnorm()という関数を使うと良い。
> H <- qnorm(0.8) > F <- qnorm(0.6) > H - F [1] 0.5882741
とりあえず、これで検出における正確性を表す指標が手に入ったこととなる。今日はここまでで、次回以降で正確性を表す指標とは違う、反応のバイアスに関わる指標について書いていこうと思う。
Pythonでお絵かき(続・カフェウォール錯視編)
前回の記事でPILというライブラリを使ってカフェウォール錯視を描く方法について書き、結果次のような画像が出来上がった。
今回はより実験刺激っぽく、明度を系統的に変化させながら刺激を作る場合について書こう。
例えば、真ん中の線の明度を系統的に変化させたいとする。
ここでは完全な黒から白まで5段階くらいで試してみよう。まず、明度情報についてのリストを用意する。
#線の明るさのリスト value = [0,64,128,192,255]
そして、このリストを用いて繰り返しの処理を行うのだが、繰り返しのたびに保存するファイルの名前を変えたい。そこで、filenameという変数を作り、繰り返し処理の最初に明度情報を含んだ「cafe64.png」のようなを文字列をその中にいれるようにする。注意するべき点として、明度情報は整数で与えてあるのでそれを文字列型に変換しないとうまくいかない(str関数を使う)。
画像ファイルの出力は、前回と違って繰り返し処理の内部に埋め込む。
for k in value: filename = "cafe" + str(k) +".png" draw.line([(0,61),(620,61)], width = 2, fill = (k,k,k)) #真ん中の灰色の線 image.save(filename)
あとは、前回のコードと同様である。
完成したカフェウォール群がこちら。明度が順番に0,64,128,192,255と増えている。
灰色の場合は明るくなるにつれて錯視量が大きくなっている気がするが、完全な黒と白の場合においては錯視は起きていないように思える。みなさまの目にはどう映りますでしょうか。
Pythonでお絵かき(カフェウォール錯視編)
PIL(Python Imaging Library)というものがある。これは、Pythonで画像関係のデータを扱う際に必要なものをまとめたライブラリなのだそうだ。何かしらの実験刺激を作る際に、決まったパターンで単純な図形を繰り返したいときというのがあり、そういう場合にこれは役に立つのではないかと思ったので、試しにカフェウォール錯視でも作ってみる。
カフェウォール錯視というのは、傾きの錯視の一つでリンク先を見てもらうか、googleで検索してもらえればどんなものかは分かるでしょう。
※北岡先生のサイトにはたくさんの錯視が紹介されていて見ているだけで楽しい。
手順としては、四角形の開始位置のリストを最初に作り、その位置情報に従い繰り返し処理で順々に描画していくという流れ。今回は2段しか四角形を描いていないのでそれぞれで繰り返し処理を行なっているが、y座標の開始位置のリストを作れば複数段重ねることも容易でしょう。位置情報のリストを作る際には、前の記事で紹介したnumpyのarray関数を使った。
#coding:utf-8 from __future__ import division from __future__ import unicode_literals from numpy import array from PIL import Image, ImageDraw #黒の四角の位置情報 black1 = array(range(5)) * 120 + 30 #描画オブジェクトとか image = Image.new("RGB", (620,123), color=(255,255,255)) draw = ImageDraw.Draw(image) for i in range(5): #上段の四角形を描く draw.rectangle(((black1[i],0),(black1[i]+60,60)), outline = None, fill = (0,0,0)) for j in range(5): #下段の四角形を描く draw.rectangle(((black1[j]+30,63),(black1[j]+90,123)), outline = None, fill = (0,0,0)) draw.line([(0,61),(620,61)], width = 2, fill = (128,128,128)) #真ん中の灰色の線 image.save("cafe.png")
完成品はこちら。
ちなみに水平線の灰色を明るい色にするとやや錯視量が大きくなる気がしますがどうでしょうかね。(最初のはRGB=(128,128,128)で2番目がRGB=(160,160,160))
Pythonでのベクトル処理
Python初学者が戸惑ったとことかを備忘録がてらメモしておく。
Rとの違いでまず迷ったのがベクトルとスカラーの演算である。
例えば、Rだとあまり何も考えずにオブジェクトにベクトルを放り込んで演算をすれば勝手にやってくれる。
> a <- c(1:20) > a [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 > a + 1 [1] 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 > a * 2 [1] 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 > b <- c(21:40) > a + b [1] 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 > a * b [1] 21 44 69 96 125 156 189 224 261 300 341 384 429 476 525 576 629 684 741 800
ところがpythonでこのノリは通用せずエラーを吐き出す。掛け算ではリストの要素を2回繰り返される。
>>> a = list(range(20)) >>> a [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] >>> a + 1 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can only concatenate list (not "int") to list >>> a * 2 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
pythonでベクトルの演算をするにはどうすれば良いかといえば、numpyというライブラリを使うと良いらしい。使うのはarray()という配列を作る関数で、リストを読み込めば、1次元の配列(つまりはベクトル)が生成され、ネストされたリストを読み込めばN次元の配列が作られるようだ。
>>> import numpy as np >>> aa = np.array(a) >>> aa array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) >>> aa + 1 array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) >>> aa * 2 array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38])
ちなみに、片方が配列であれば、もう片方が普通のリストであっても配列同士の演算がされるようだ。リスト同士の演算と比べて見ると差がわかる。
>>> aa + b array([20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58]) >>> a + b [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
やはり、統計解析を主たる目的で作られている言語と汎用的な言語ということで勝手は色々違うみたいですね。