6. 反応にフィードバックしよう―概念識別

6.1. この章の実験の概要

ずっと視知覚の実験ばかりが続いたので、この章では概念識別の実験を取り上げましょう。私たちが新しい概念を学ぶときには講義を聞いたり書籍を読んだりといった言語を通じた手段を用いることが多いでしょう。その他にも、ある概念について「この事例は当てはまる」、「あの事例は当てはまらない」といった事例を経験することによって、概念を獲得することがあります。例えば、幼児が言語を獲得する時に「これは『ぶーぶー』じゃないよ」、「いま『ぶーぶー』がみえたね」といった事例を通じて「ぶーぶー」という概念を獲得するといった具合です。ある概念が適用される事例を正事例、適用されない事例を負事例と呼びます。この章で取り上げる概念識別の実験は、この事例を通じた概念の獲得過程を単純化したものです。

実験では 図6.1 に示す画像を刺激として使用します。眼鏡の有無(かけている/かけていない)、顔の形(丸い/四角い)、目の大きさ(大きい/小さい)、眉毛の形(上がっている/下がっている)、口の形(口角が上がっている/下がっている)の5種類の次元の組み合わせで合計2の5乗=32種類の顔画像を用います。これらの画像ファイルは 図6.1 下に示すように、'Face'+5桁の数値+'.png'という名前で保存され、数値の各桁の値が5種類の次元の値に対応しています。

実験の最初に無意味な単語をひとつ決定します。以後、この単語を「ターゲット語」と呼びます。例えば今、ターゲット語として「リニ」という無意味な単語を選んだとしましょう。この「リニ」の正事例として、5種類の次元のいずれを選んでその値のどちらかを割り当てます。例えば「眼鏡の有無」の「かけていない」を選んだとしましょう。そうすると、これから実施する実験では「リニ」は刺激の顔が「眼鏡をかけていない」時に「当てはまる」、それ以外の時に「当てはまらない」ということになります。

_images/stimul-concept-identification.png

図6.1 実験に用いる刺激。5次元の特徴にそれぞれ2種類の値があり、合計2の5乗=32種類の画像あります。

以上のことを決めたうえで、実験に入ります。実験は1回から複数回のセッションから成っています( 図6.2 )。各セッションの最初には、先ほど決定したターゲット語と、反応方法の教示が表示されます。反応方法は「当てはまる」ならばカーソルキーの左、「当てはまらない」ならカーソルキーの右を押すことにしましょう。実験参加者が教示画面でカーソルキーの左右いずれかを押すと最初の試行が始まります。各試行では、スクリーン中央にターゲット語と顔画像が提示され、スクリーン左下に「当てはまる」、右下に「当てはまらない」と提示されます。実験参加者はこの顔画像がターゲット語に「当てはまる」か「当てはまらない」か判断して、キーを押して反応します。反応に制限時間は設けず、刺激は反応があるまで提示し続けます。反応の検出後に、反応が正解であれば「正解です」、誤答であれば「不正解です」とスクリーンに表示して正誤を実験参加者にフィードバックします。32種類の全ての画像に対して一回ずつ判断するまで試行を繰り返し、終了したら正事例の条件がどれだけわかったか、確信度を1から7の7段階尺度(1が最も確信度が低い)で回答させます。以上の手続きを1セッションとします。セッション終了後に、実験参加者に正事例の条件を口頭で回答させます。実験者は回答をメモ用紙に記録し、正しかった場合はそこで実験を終了します。回答が誤っていた場合はもう1セッション実験を実行します。5セッション実行しても正解しなかった場合は、そこで実験を終了します。Builderでは1セッション分のフローをひとつの実験として作成し、1セッション実行する毎にBuilderから実験を実行するものとします。

_images/concept-identification-procedure.png

図6.2 実験の概要。

以上が実験の概要です。実験の作成に入る前に、実験の作成に必要となる新しいコンポーネントについて解説しましょう。画像を提示するImageコンポーネント、尺度を表示して回答を記録するRatingScaleコンポーネントの二つです( 図6.3 )。

_images/image-ratingscale-components.png

図6.3 ImageコンポーネントとRatingScaleコンポーネント

6.2. Imageコンポーネント

Imageコンポーネントは画像ファイルをスクリーン上に提示するコンポーネントです。Gratingコンポーネントと共通する部分が多く、Gratingコンポーネントと同様に [マスク][テクスチャの解像度 $][補間] というプロパティがあります。これらのプロパティについては 第4章 を参考にしてください。

他コンポーネントと共通のプロパティのうち、ひとつだけ注意が必要なプロパティが [色] です。Imageを空白にして [色] を指定すると、 [色] で指定した色で塗りつぶされた長方形が提示されます。古いバージョンのBuilderではPolygonコンポーネントが存在せず、代わりにこのImageコンポーネントを使って長方形を提示していました。Imageで画像ファイルを指定して、なおかつ [色] を指定すると、 [色] で指定した色で画像を着色します。着色の度合いが指定できないので、同様のことを行いたい場合は画像の上に半透明のPolygonコンポーネントを重ねる方がよいと思います。

Imageコンポーネントで初登場のプロパティは [水平に反転][垂直に反転][画像] です。 [水平に反転][垂直に反転] にチェックを入れておくと、それぞれ画像が左右反転、上下反転された状態で提示されます。 [画像] には画像ファイル名を指定します。条件ファイルと同様に、ただファイル名だけが与えられた場合には、実行しているpsyexpファイルと同じフォルダからファイルを探します。当然ファイルが見つからなければエラーとなり実験は停止してしまいます。psyexpと異なるフォルダにある画像ファイルを参照する場合は、絶対パスおよび相対パスによる指定が使えます。絶対パス、相対パスと言われてもピンと来ない方もおられると思いますので、節を改めて解説しておきましょう。

チェックリスト
  • 画像ファイルをスクリーン上に提示することが出来る。

  • 画像ファイルを上下、または左右に反転させて提示することが出来る。

6.3. 絶対パスと相対パス

パス(path)とは「小道」のことで、PCではプログラムを実行するときに開いたり保存したりしたいファイルへたどり着くための経路を表します。私たちの身の回りのものに例えるとすれば、「住所」によく似ています。「X県Y市Z町1丁目1-1」という住所があるとして、手紙の宛先にこの住所を書いておけば、全国どこから発送しても同じところへ配送されます。PCの場合でも同様に、PCのファイルシステム(ハードディスクやUSBメモリ等)でファイルの位置を特定できる「住所」があります。この住所を絶対パスと呼びます。

Microsoft Windowsの場合、USBメモリを差し込むと「ドライブF:」などのようにアルファベットが割り当てられるのは御存知のことと思います。このアルファベットをドライブレターと呼びます。このUSBメモリに「psychology」というフォルダがあって、さらにその中に「report」というフォルダがあって、その中の「report01.docx」というファイルがあるとします。このreport01.docxを絶対パスで表すには、以下のようにドライブレターの後にコロンとバックスラッシュを書き、以後フォルダ名をバックスラッシュで区切って記述します。

F:\psychology\report\report01.docx

日本語Windowsではバックスラッシュは半角の円記号(¥)で表示されますのでご注意ください(「 3.11.6:$を含む文字列を提示する 」を参照)。日本での住所の表記が都道府県、市町村、と大きな区分から小さな区分に向かって書かれるのと似ています。

UbuntuなどのLinux系OSでは、バックスラッシュではなく以下のようにスラッシュでフォルダを区切ります。また、ドライブレターは使用されず、絶対パスの先頭はスラッシュです。

/home/user/Documents/Report/report01.txt

言うなれば先頭のスラッシュの前に「名前がない」フォルダがあることになりますが、この名無しフォルダの事をrootと呼びます。ファイルシステムがこの名無しフォルダを根として枝が広がっていくように見えるところに由来する名称です。

絶対パスの良いところは、どの位置に目的のファイルがあるかを曖昧さなしに特定できることです。学期末のレポートの時期に「○○概論」や「△△特殊講義」といったあちこちのフォルダにreport.docという名前のファイルが散らかっていて訳が分からなくなることがありますが、絶対パスであればどのフォルダのreport.docであるかを見失うことがありません。しかし、この性質が逆に「融通の利かなさ」という欠点になる場面もあります。例えば自宅のPCでUSBメモリがF:ドライブとして接続されていて、その中にあるF:\Exp07\stim01.jpgという刺激画像をBuilderから絶対パスで参照するようにしたとします。そしてpsyexpファイルを保存して大学の実験室へ入り、実験室のPCにUSBメモリを接続したら、E:ドライブとして認識されてしまったとしましょう。そうするとpsyexpファイルで参照しているF:\Exp07\stim01.jpgの絶対パスは今やE:\Exp07\stim01.jpgに変わっていますので、それに合わせてpsyexpファイルを書き換えないといけません。これはあまりにも面倒です。このようなときに便利なのが相対パスによる指定です。

相対パスとは、住所の例えで言うならば、何県の話をしているのか文脈から明らかなときに県を省略して「Y市Z町1丁目1-1」のように書くことに似ています。しかし、住所の例えではPCの相対パスは上手く説明できません。PCの相対パスでは、まず基準となる位置を決めて、そこから住所をどのように辿っていくかを記述します。基準となる位置のことをカレントフォルダと呼びます。今、F:\experiment\exp01というフォルダにexp01.psyexpというBuilderの実験ファイルがあるとしましょう( 図6.4 )。このexp01.psyexpを実行する時には、F:\experiment\exp01がカレントフォルダとなります。exp01.psyexpから他のファイルを探す時、絶対パスではないパス(ドライブレターやrootから始まっていない書き方)が与えられると、カレントフォルダからファイルを探します。第3章 以降で条件ファイルを指定する時にexp01cnd.xlsxという具合にファイル名だけを書いたことを思い出してください。これは絶対パスではないので、カレントフォルダであるF:\experiment\exp01のexp01cnd.xlsxを指すことになります( 図6.4 (1))。image\stim01.pngと指定されたら、カレントフォルダの中に含まれるimageというフォルダの中にあるstim01.pngを指していると解釈されます( 図6.4 (2))。このようなカレントフォルダを基準にした指定方法を相対パスと呼びます。

_images/path-example.png

図6.4 相対パスによるファイルの指定。

普通の住所の表記と相対パスが大きく異なるのは、相対パスには階層をさかのぼる記法が用意されている点です。相対パスの中にピリオドが2文字だけのフォルダ( ..\ )が含まれていると、それは現在のフォルダを含んでいる上位のフォルダを指します。 図6.4 の例では、..\がF:\experimentを指します。この記法は重ねて使用することが出来るので、..\..\と書くとF:\experimentのさらに上位のフォルダであるF:\を指します。..\で指し示される上位フォルダのことを親フォルダと呼びます。この記法を用いることによって、カレントフォルダより上位のフォルダに含まれるファイルでも自在に指定することが出来ます。 図6.4 の(3)のexp02cnd.xlsxへ到達するためにはまず..\で親フォルダに移動し、そこからexp02フォルダへ下ればたどり着けますから..\exp02\exp02cnd.xlsxと書きます。同様に(4)のface001.jpgにたどり着くためには、..\..\と書いて親フォルダを二つ遡ってF:\まで移動し、そこからphotoフォルダ、faceフォルダと下ればたどり着きますから..\..\photo\face\face001.jpgと書けばよいということです。Builderで大量の画像ファイルを刺激として使う場合や、複数の実験で同じ画像を使いまわす場合などには、この相対パスの表記法を覚えておくと必ず役に立ちます。しっかり理解しておきましょう。

相対パスのもう一つの利点は、「相対パスを使えばPythonがOSによるパスの区切り文字の違いを吸収してくれる」という点です。先ほどから繰り返し例を示しているように、Microsoft Windowsではフォルダ名を区切る文字としてバックスラッシュを用います。ですから、Windows上で動作するプログラムで相対パスを記述する時にはimage\stim01.pngという具合に書かなければいけません。しかし、先述のようにLinuxではスラッシュで区切りますので、Windows上で動いたプログラムをそのまま持ってきても動作しません。この問題は「個人用PCはWindowsだけど大学の実験室ではLinux」といった状況では困りものなのですが、ありがたいことにWindows版のPythonにはパスをLinux流にimage/stim01.pngと書いてもパスとして解釈してくれる機能があります。ですから、Linux流の相対パスを書いておけばWindowsでもLinuxでも動作するBuilderの実験を作ることが出来るのです。「Windows上でもスラッシュをパスの区切り文字と見なしてくれる機能は絶対パスの時にも有効なんじゃないの?」と思われる方もいるかも知れませんが、絶対パスの場合はドライブレターの問題が立ちはだかります。さすがのPythonでもドライブレターを自動的に補ったり削除したりは出来ませんので、絶対パスで書くとOS依存になってしまいます。

最後に、親フォルダの記法を紹介したついでに、カレントフォルダの記法も一応紹介しておきます。ピリオド1文字だけのフォルダ名はカレントフォルダとして解釈されます。ですから、 図6.4 の(1)は.\exp01cnd.xlsxとも書くことが出来ます。 (2)も同様に.\image\stim01.pngと書くことが出来ます。Builderを使うだけでしたら覚える必要はないのですが、今後本格的なプログラミングを学習したりする時には知っていると役に立つかもしれません。

チェックリスト
  • 画像ファイルや条件ファイル等の位置を絶対パスで指定することが出来る。

  • 画像ファイルや条件ファイル等の位置を相対パスで指定することが出来る。

  • OSによるパスの書き方の違いを説明できる。

  • 複数のOSで実行できるBuilderの実験を作成するためにはどの記法でパスを記述したらよいか答えられる。

6.4. RatingScaleコンポーネント

RatingScaleコンポーネントは定規のような「尺度」をスクリーン上に提示して、尺度上の位置を参加者に選択させることによって反応を記録するためのコンポーネントです。ここまでに紹介した他のコンポーネントと違って、スクリーン描画機能と反応取得機能の両方を持っている複雑なコンポーネントです。かなり荒削りというか、開発途上な感が強いコンポーネントなので、入門テキストで取り上げるべきか迷いましたが、この種の反応記録方法を必要とする実験も多いでしょうからここで取り上げます。

_images/ratingscale-properties.png

図6.5 RatingScaleコンポーネントのプロパティ設定ダイアログの内容

図6.5 にRatingScaleコンポーネントのプロパティ設定ダイアログの内容を示します。これまでに紹介してきたコンポーネントと同一のプロパティは [名前][開始][終了] のみです。これらのプロパティの意味は他のコンポーネントと同一です。続いて [アナログ尺度][カテゴリカル尺度] ですが、これらの項目の設定で提示される尺度が変わります。 [アナログ尺度] のチェックがオフで、 [カテゴリカル尺度] が空白であれば、N件法の尺度が提示されます( 図6.6 一番上)。 [ラベル] に「あてはまらない, あてはまる」のようにカンマ区切りで入力すると、両端のラベルとして表示されます。 [ラベル] を空白のままにしておいた場合は [最小値 $][最大値 $] が表示されます。

N件法の尺度では、参加者はマウスで尺度上の位置をクリックして回答を選択することが出来ます。選択すると尺度の下のkey, clickと書かれたボタンに選択した値が表示されますので、このボタンをクリックしたら反応が確定されます。反応を確定するまでは何度でも選択できます。キーボードで反応する場合は、キーボードの数値のキー(テンキーではなくキーボード上段左端から右へ1、2、3、…と並んでいるキー)で選択します。 図6.6 一番上のように両端にラベルを付けている場合は、キーボードの数値キーを押して反応するように参加者に求めるのは厳しいと思いますが、数値キー自体は使用できます。一度いずれかの値を選択した後であれば、カーソルキーで左右に選択項目を動かして、Enterキーで確定することもできます。

図6.6 上から二番目の例では、 [最小値 $] に負の値を指定しています。このような場合、数値キーで直接-3などを入力することが出来ないので、数値キーによる選択は出来ません。これに対応して、尺度の下のボタンがclick lineに変更されています。

図6.6 上から三番目は、カテゴリカル尺度の例です。 [カテゴリカル尺度] に選択肢をカンマ区切りで入力して作成します。N件法の尺度と同様にキーボードからも操作が可能なのですが、一番左端が 0キー 、左から二番目が 1キー となってしまいますので、やはりあまり実用的ではないでしょう。

図6.6 上から四番目は、アナログ尺度の例です。 [アナログ尺度] がチェックされていれば、アナログ(無段階)尺度が提示されます。マウスカーソルを使って尺度上の位置をクリックして選択し、下のaccept?と表示されたボタンを押して確定します。キーボードから反応する場合は、まず数値キーの0か1を押してマーカーを表示した後、カーソルキーの左右でマーカーを動かすことが出来ます。Enterキーで確定です。

図6.6 の一番下は [マーカーの種類] の使用例です。この例では Hover という値を指定しています。Hoverはカテゴリカル尺度でのみ有効で、図のように横線が表示されずに選択肢(この例では A, B, C)のみが表示されています。選択肢にマウスカーソルを重ねると、文字の色が変化します。初期値はtriangleで、上から三番目の例に表示されているような青い三角形が選択した位置に表示されます。

_images/ratingscale-samples.png

図6.6 RatingScaleコンポーネントで作成できる尺度の例

N件法の尺度の場合、 [尺度の説明] に文字列を記入すると 図6.6 一番上の例のように尺度の上にメッセージを表示できます。しかし、文字の大きさや色を自由に指定できませんので、細かい調整をしたい場合はTextコンポーネントを使った方が早いと思います。 [マーカーの初期位置] に値を指定すると、最初からいずれかの値を選択した状態にすることが出来ます。キーボードを使って選択させたい場合は便利な機能です。 図6.6 上から三番目では [マーカーの初期位置] に「その他」を指定しています。カテゴリカル尺度の場合は、左端を0とした数値を入力しても構いませんし、選択肢の文字列を直接入力しても構いません。

以上で「基本」タブの解説は終わりです。続いて「高度」タブの内容を見ていきましょう。

[サイズ]

相対的な大きさを指定します。他のコンポーネントの [サイズ [w, h] $] のように [単位] の影響をうけませんので注意が必要です。

[位置 [x, y]]

位置を指定します。こちらは実験設定ダイアログの [単位] に従います。

[目盛の高さ]

目盛の高さを指定します。正の値は上向き、負の値は下向きを表します。0ならば目盛は描画されません。 [サイズ] 同様、 [単位] の影響を受けません。

[消去]

反応が確定した尺度が画面から消去されます。

[Routineを終了]

Keyboardコンポーネントの [Routineを終了] と同様に、反応が確定したら直ちにルーチンを終了します。ただし、複数のRatingScaleがスクリーン上に配置されている場合などには、すべてのRatignScaleが確定しないとルーチンが終わりません。

[acceptボタンを表示]

尺度の下の確定ボタンを表示しません。

[直ちに確定]

尺度を一度クリックしたら直ちに反応が確定されます。選びなおしは出来ません。

[履歴を記録]

反応確定までにクリックされた選択肢を反応時間付きですべて記録ファイルに保存します。

[評定を記録]

確定時に選択されていた値を記録ファイルに保存します。あまりこのチェックを外して使用する機会はないと思います。

[反応時間を記録]

最終的に反応を確定するまでに要した時間を記録ファイルに保存します。

最後に「カスタム」のタブですが、このページには [すべてをカスタマイズ] という項目しかありません。これはPsychoPyのコードを直接記述するためのもので、本書で想定しているレベルを超えていますので解説を省略します。興味がある方は「 6.10.1:RatingScaleコンポーネントの [すべてをカスタマイズ] について(上級) 」を参考にしてください。

チェックリスト
  • N件法の尺度をスクリーンに提示して反応を記録することが出来る。

  • アナログ尺度をスクリーンに提示して反応を記録することが出来る。

  • カテゴリカルな尺度をスクリーンに提示して反応を記録することが出来る。

6.5. 実験の作成

コンポーネントの解説が終わったので、実験の作成に入りましょう。この章の解説では、Builderで実験を新規作成し、以下の作業を行ってexp06.psyexpの名前で保存したものとします。「適当な値(○○など)に設定する」と書かれている項目は、使用しているモニターの寸法などによっては括弧内の値ではスクリーンからはみ出たり文字が小さすぎたりするかもしれませんので、必要に応じて変更してください。

  • 実験設定ダイアログ

    • 「xlsx形式のデータを保存」をチェックする。

    • [単位] をpixにする。

    • [実験情報ダイアログ] にwordという項目と、conditionという項目を追加する。

  • trialルーチン

    • Imageコンポーネントをひとつ配置して、 [名前] をfaceImageにする。 [終了] を空白にする。 [サイズ [w, h] $] を[400,400]にする。

    • Textコンポーネントを3つ配置して、 [名前] をtextYes、textNo、textQuestionにする

      • すべて [終了] を空白にし、 [文字の高さ $] を適当な値(36など)にする。

      • textYesがスクリーン左下に提示されるように [位置 [x, y] $] を適当な値に設定する([-250,-280]など)。 [文字列] に「はい」と入力する。

      • textNoがスクリーン右下に提示されるように [位置 [x, y] $] を適当な値に設定する([250,-280]など)。 [文字列] に「いいえ」と入力する。

      • textQuestionがfaceImageの下に提示されるように [位置 [x, y] $] を適当な値に設定する([0,-220]など)。

    • Keyboardコンポーネントをひとつ配置し、以下のように設定する。

      • [名前] をkey_choiceにする。

      • [終了] を空白にする。

      • [検出するキー $] を'left', 'right'にする。

      • [正答を記録] をチェックして、 [正答] に$correctAnsと入力する。

  • instructionルーチン(作成する)

    • フローの先頭に挿入する。

    • Textコンポーネントをひとつ配置し、 [名前] をtextInstにする。 [終了] を空欄にし、 [文字の高さ $] に適当な値(24など)を入力する。

    • Keyboardコンポーネントをひとつ配置し、 [終了] を空白にする。 [検出するキー $] を'left','right'にし、 [記録] を「なし」にする。

  • feedbackルーチン(作成する)

    • フローのtrialルーチンの直後に挿入する。

    • Imageコンポーネントをひとつ配置して、 [名前] をfaceImage_2にする。 [終了] を空白にする。 [サイズ [w, h] $] を[400,400]にする。

    • Textコンポーネントをひとつ配置し、 [名前] をtextFeedbackにする。 [終了] を空白にし、 [文字の高さ $] を適当な値(36など)にする。faceImage_2の下に提示されるように [位置 [x, y] $] を適当な値に設定する([0,-220]など)。

    • Keyboardコンポーネントをひとつ配置し、 [終了] を空白にする。 [検出するキー $] を'left','right'にし、 [記録] を「なし」にする。

  • ratingルーチン(作成する)

    • フローの末尾に挿入する。

    • RatingScaleコンポーネントを配置し、 [名前] をcertaintyRatingにする。[サイズ $] を適当な値(0.7など)にする。

  • trialsループ(作成する)

    • trialルーチンとfeedbackルーチンを繰り返すように挿入する。

    • [繰り返し回数 $] を1にする。

  • 刺激画像

  • exp06cnd01.xlsx(条件ファイル)

    • imageFile、correctAnsの2パラメータを設定する。

    • imageFileに32種類の刺激画像のファイル名を設定する。ファイル名(FileXXXXX.png)のみでパスは入力しないこと。

    • imageFileの列でファイル名の5桁の数字の左から1桁目が1である行のcorrectAnsをleftに、それ以外の行のcorrectAnsをrightにする。これで眼鏡をかけている画像ファイルに対応するcorrectAnsがleftとなる。

  • exp06cnd02.xlsx(条件ファイル)

    • exp06cnd01.xlsxをコピーし、correctAnsの列のrightをleftに、leftをrightに書き換える。これで眼鏡をかけていない画像ファイルに対応するcorrectAnsがleftとなる。

以上で準備完了です。Imageコンポーネントの [画像] をはじめ、いくつかのTextコンポーネントの [文字列] やループの [繰り返し条件] などが未設定のまま残っています。これから、以下の作業に取り組みたいと思います。

  • Imageコンポーネントの [画像] に、条件ファイルから読み込んだ刺激画像ファイル名にフォルダ名を付け足して相対パスを完成させる式を入力する。

  • 実験実行時に実験情報ダイアログのwordからターゲット語を取得し、instructionやtrialルーチンのTextコンポーネントの [文字列] 、およびratingルーチンのRatingScaleコンポーネントのScale Descriptionで提示する教示文に組み込む。

  • 実験実行時に実験情報ダイアログのconditionから条件ファイル名の番号(01、02)を取得し、前にexp06cnd、後ろに.xlsxを結合して条件ファイル名を得る。条件ファイル名を全て入力する手間を省ける。

6.6. 文字列を結合して画像ファイルのパスや教示文を作成しよう

まず、Imageコンポーネントの [画像] プロパティを設定しましょう。 [画像] には読み込む画像ファイル名を指定します。ファイル名は条件ファイルのimageFileというパラメータから読み込まれますが、単に$imageFileと書くと画像ファイルがexp06.psyexpと同じディレクトリにあると見なされてしまいます。そこでフォルダ名のimage/をファイル名の前に補いたいのですが、$image/imageFileとか$"image/"imageFileとか書いてもエラーになります。ではどう書けばいいかと言いますと、+演算子を使います。数値と数値の間に+演算子を書けば足し算になりますが、文字列と文字列の間に+演算子を書くと両者を連結した文字列が得られます。今回の場合は、以下のように [画像] に記入すれば目的を達成できます。image/を文字列として認識させるためにシングルクォーテーションまたはダブルクォーテーションで囲むことを忘れないでください。

$'image/' + imageFile

trialsルーチンのfaceImageとfeedbackルーチンのfaceImage_2の両方の [画像] にこの式を入力してください。そして、忘れずに「繰り返し毎に更新」を設定しておきましょう。

教示文へのターゲット語の組み込みも同様の方法で実現できます。expInfo['word']という式で実験情報ダイアログから文字列を取得できるのですから、以下のような式を用いれば「これは「○○」ですか?」という文字列が得られます。

$'これは「' + expInfo['word'] + '」ですか?'

trialルーチンを開いてtextQuestionの [文字列] にこの式を入力してください。実験の実行中にはexpInfo['word']の値は変化しませんので、「繰り返し毎に更新」に設定する必要はありません。この式は少々複雑なので、+演算子の前後にスペースを入れて見易くしてみましょう。

$ 'これは「'     +      expInfo['word']      +      '」ですか?'

三つの文字列を2つの+演算して結合していることがわかります。数値演算における+演算子が5+3+8という具合に3つ以上の値に次々と適用できるのと同じことです。ただし、数値演算は5+3+8でも8+3+5でも同じですが(可換性)、文字列の+演算では常に演算子の左側の文字列の最後尾に右側の文字列の先頭が結合されます。

RatingScaleルーチンも同様に設定してしまいましょう。ratingルーチンを開いて、certaintyRatingのScale Descriptionに以下の式を入力してください。 紙面の都合上2行になっていますが、入力する時は途中で改行せずに1行で入力してください。

$'「' + expInfo['word'] + '」がわかりましたか?
 (1:全くわからない 7:とてもよくわかった)'

続いてinstructionルーチンのtextInstの [文字列] ですが、ここは少々解説が必要です。目標として、以下のような複数行にわたる教示文をひとつのTextコンポーネントで表示するものとします。○○には実験情報ダイアログのwordから取得した文字列が入るものとします。

「○○」は人の顔を形容する言葉です。
提示された顏の絵が当てはまるならカーソルキーの左、
当てはまらないなら右を押してください。

○○にあたる部分をexpInfo['word']にして前後を + でつなげばよいだけのように思われるかもしれません。しかし、 図6.7 のように [文字列] に記入して実験を実行してみても、エラーメッセージすら表示されずに終了してしまいます。エラーメッセージが表示されないので何が起こっているのか非常にわかりにくい厄介なエラーです。 図6.7 の式がエラーになるのは、$が先頭に入力されていることと、文字列が途中で改行されていることが原因です。

_images/string-error.png

図6.7 改行がある文と $の指定は両立しない

第2章 で述べたとおり、Textコンポーネントの [文字列] には改行を含むことが出来ます。それなのになぜ 図6.7 はエラーになってしまうのでしょうか。この問題の鍵は、Builderが [文字列] に入力されている値をどう解釈するかにあります。 [色] の設定を思い出してください(第3章)。 [色] には$が付いていないから、redと書けばredという文字列が入力されているとBuilderは判断するのでした。一方、$redと書けば、変数redに格納された値が指定されているとBuilderは判断すると述べました。実は後者は不正確な記述で、正確には$が書かれていると、Builderは$を除いた部分がPythonの式である判断するのです。$redから$を除くとredが残り、redという式を「評価」すると、変数redに格納された値が得られるのです。ここで言う「評価」とは、式の中に+などの演算子が含まれていたらその計算をしたり、関数が含まれていたらその戻り値を計算したりといった作業を行って、最終的な式の計算結果を得ることです。この「評価」という考え方はBuilderを理解する上でとても大切です。例えば 第5章 で出てきた時刻に応じて色を変化させる式 $[t/6.0, t/6.0, t/6.0] では、t/6.0が評価されてtを6.0で割った値に置き換えられることによって、RGB値として解釈できるリストとなるのです。

これを踏まえて 図6.7 に戻ります。ここには$が入力されているのでBuilderはこれをPythonの式と見なします。Pythonの式としてこれを解釈すると、1行目の最後が文字列の途中で終わってしまっています。このような書き方はPythonの文法で許されていません。同様に2行目、3行目も文字列が適切にクォーテーションで囲まれていませんのでPythonの文法を満たしていません。従って、この実験は実行できないのです。

では目指している出力を得るにはどうすればよいでしょうか。ふたつ方法がありますが、簡単な方法を紹介しましょう。Pythonでは、シングルクォーテーションまたはダブルクォーテーションを3個連ねることにより、複数行にわたる文字列を表記することが出来ます。例えば以下のように書くと2行に表示されます。

'''必要に応じて休憩を取ってください。
準備が出来たらスペースキーを押して実験を再開してください。'''

このテクニックを使うと、textInstの [文字列] は以下のように書くことが出来ます。

'「'+expInfo['word']+'''」は人の顔を形容する言葉です。
提示された顏の絵が当てはまるならカーソルキーの左、
当てはまらないなら右を押してください。'''

以上で [文字列] に$記号を使ってPythonの式を記述した場合でも複数行にわたる文字列を表示できました。この方法を知っていればBuilderを使う分には困ることはないと思いますが、今後さらにステップアップすることを考えるのならばもうひとつの方法も知っておいた方がよいです。「6.10.2:改行文字を使った複数行の文字列の表現(上級) 」に解説しておきますので、興味がある人は読んでください。

最後に実験情報ダイアログのconditionへ入力された値から条件ファイル名を得る問題が残っていますが、ここまでの解説を理解していればもうこれ以上の解説は不要でしょう。trialsループの設定ダイアログを開き、 [繰り返し条件] に以下の式を入力してください。これで01とだけ入力すればexp06cnd01.xlsxを指定することが出来ます。

$'exp06cnd' + expInfo['conditionFile'] + '.xlsx'
チェックリスト
  • 複数の文字列を結合した文字列を得る式を書くことが出来る。

  • 条件ファイルや実験情報ダイアログから読み込んだ文字列が組み込まれた文を提示することが出来る。

  • Textコンポーネントの [文字列] にPythonの式を書いた時に、表示する文字列を改行させることが出来る。

6.7. Pythonにおける変数への代入、比較演算子、論理演算子、条件分岐を学ぼう

ここまでの作業でとりあえず実験を実行できるところまでたどり着きましたが、最後の難題である「判断の正誤をフィードバックする」が残っています。フィードバックの提示はfeedbackルーチンのtextFeedbackを用いて行います。trialルーチンでの判断が正しければtextFeedbackの [文字列] に「正解」、誤っていれば「不正解」と提示したいのですが、当然実験参加者が正解するか否かは実験を実行する前にはわからないので、条件ファイルでは実現できません。実はこの種の参加者の反応に応じた刺激や課題の変化はBuilderが苦手とするところで、現状のBuilderではどうしてもPythonの文法知識、Pythonコードの記述が必要になります。

さて、これから「反応が正しければ『正解』、誤っていれば『不正解』と提示する」という作業をPythonのコードへ変換するわけですが、このように条件に応じて行う処理を変更することを条件分岐と言います。条件分岐は

もしAが成り立つならばBを行う。さもなければCをおこなう。

という文で表現できます。一般にプログラミング言語では「成り立つ」ことを「真(True)である」と言い、成り立たないことを「偽(False)である」と言います。この用語を用いると、先の文は

Aが真であればBを行う。偽であればCをおこなう。

と書き直すことができます。Pythonでは、この文をif, elseという語を使って 図6.8 のように書きます。ifとelseの後ろにコロン( : )がある点と、B、Cがifやelseより「字下げ」されている(行頭に空白文字がある)点に注意してください。空白文字を何文字入れるかについてのPythonでの文法上の取り決めは少々複雑なのですが、Python Enhancement Proposals (PEP)と呼ばれるPythonの公式文書において、字下げには半角スペース4文字を用いることが推奨されていますので、この文書では半角スペース4文字で統一します。第7章 で詳しく触れますが、Pythonの文法では字下げが重要な意味を持っています。

_images/if-else-statement.png

図6.8 条件分岐(if文)の書式。右は実際のコードの例です。

反応が正しければ『正解』、誤っていれば『不正解』と提示する」という目標をこのif文の形式に当てはめることができれば目的は達成されますが、どう当てはめたらよいでしょうか。この目標のままではPCにとってはまだ抽象的すぎますので、もう少し書き直してみましょう。

・反応が正しければ
↓
・「押されたキーの名前が変数correctAnsの値と一致している」が真であれば

「押されたキーの名前」をPythonのコードとして表現する方法はまだ解説していないので、とりあえず変数responseに押されたキーの名前が格納されているものとして書き換えを進めますと、以下の文が得られます。

・「押されたキーの名前が変数correctAnsの値と一致している」が真であれば
↓
・「変数responseの値が変数correctAnsの値と一致している」が真であれば

続いて「『正解』と提示する」という部分についても書き直してみましょう。提示にはTextコンポーネントを使うのですから、以下のように書き直すことができます。

・『正解』と提示する
↓
・Textコンポーネントの「文字列」に'正解'と設定する

第3章 以降の解説では、Builderのコンポーネントのプロパティ値を実行中に変更する時には変数を用いてきました。今回もこの方法が有効でしょう。feedbackMsgという変数を用いることにしましょう。

・Textコンポーネントの「文字列」に'正解'と設定する
↓
・変数feedbackMsgに'正解'を代入する

「『不正解』と提示する」は「『正解』と提示する」と同様ですので省略します。ここまで書き直すことができれば、Pythonのコードへ変換することができます。 図6.8 の右側が実際にPythonのコードに置き換えてみた結果です。 図6.8 左側と見比べて、 図6.8 左側のA、B、Cに対応する右側のコードを見てください。右側のコードの意味が何となく分かると思うのですが、ここで「なんとなく」で済ませると後で躓くのでしっかり理解しておきましょう。

まずifの後に続くresponse == correctAnsですが、responseとcorrectAnsはすでに何度も出てきているPythonの変数であり、その中に値が保持されています。両者の間にある ==という記号ですが、これは比較演算子と呼ばれる演算子です。==の前後に置かれた値を比較して、両者が一致していればTrue、一致していなければFalseという「値」を返します。TrueやFalseを「値」と言われると奇妙に感じるかも知れませんが、そういうものだと思ってください。10+5を評価すると15という値が得られるのと同様に、比較演算子を含む式を評価するとTrueやFalseという「値」が得られるのです(正確に知りたい方は「 6.10.3:TrueとFalseの「値」 」参照)。比較演算子には==の他にも 表6.1 に示すものがあります。表中のXとYが両方とも数値である場合は特に難しいことはないと思うのですが、どちらか一方に文字列やリストが含まれている場合は話が厄介です。詳しく知りたい方は「 6.10.4:文字列、シーケンスに対する比較演算子 」を読んでいただきたいのですが、慣れないうちは以下の点を守って使用することをお勧めします。

  • 文字列やシーケンス型の比較の場合は==(等しい)と!=(等しくない)以外使用しない

  • 異なる種類のデータ型の比較(数値と文字列の比較)はしない

表6.1 Pythonの比較演算子

X == Y

XとYは等しい

X != Y

XとYは等しくない

X < Y

XはYより小さい

X <= Y

XはY以下

X > Y

XはYより大きい

X >= Y

XはY以上

比較演算子は、二つ以上同時に使うことも出来ます。例えば以下の例ではxが1以上で5未満の時にTrue、それ以外の時はFalseになります。数値がある範囲に収まっているか否かで処理を分岐させる時に便利です。

1 <= x < 5

比較演算子について学んだついでに、もうひとつ演算子を学んでおきましょう。「刺激の位置がスクリーンの左上だった場合」といった条件で分岐させたい場合には、X座標が負の値であること、Y座標が正の値であることの二つの条件を同時に満たす必要があります。このような場合は論理演算子( 表6.2 )を用います。X座標とY座標の値がそれぞれX、Yという変数に格納されているのであれば、この条件は以下のように記述できます。

X<0 and Y>0

逆に、「刺激の位置がスクリーンの左上ではなかった場合」とうい条件を指定したい場合は、X座標が0以上、またはY座標が0以下のどちらか一方が成立すればいいのですから、以下のように記述できます。

X>=0 or Y<=0

否定演算子を使うと以下のように書くことも出来ます。演算子を適用する順序を指定するために( )を使用していることに注意してください。まず( )の中が評価されて、その後にnotが適用されます。

not ( X<0 and Y>0 )
表6.2 Pythonにおける論理演算子

X and Y

XかつY (論理積)

XとYがともにTrueの時にTrue

X or Y

XまたはY (論理和)

XとYのいずれかがTrueの時にTrue

not X

Xの否定

XがTrueならばFalse、FalseならばTrue

比較演算子と論理演算子についての解説はこのくらいにして、 図6.8 のBとCに対応するコードについて解説しましょう。

feedbackMsg = '正解'
feedbackMsg = '不正解'

これらの式では代入演算子( = )が使用されています。代入演算子はX = Yという形で使用し、右辺の式を評価した結果を左辺の変数に代入します。代入の方向は右から左で固定されていますので、「7 = xと書いて変数xに7を代入する」ことは出来ません。左辺は変数でなければいけませんので、x+7 = 3といった書き方も出来ません。あと、プログラミングに慣れていない人にはちょっと奇妙に思われるかもしれないけれどもとても便利な用法を紹介しておきましょう。

x = x+7

この式では、変数xに格納された値に7を加えて、その結果をまた変数xに代入しています。例えば上記の式を実行する前にxに4が格納されていたのであれば、この式を評価した結果xには11が格納されています。この式が奇妙に思われる方は、恐らく数学において=記号が等号(右辺と左辺の値が等しい)として使われることが頭にあるのではないかと思います。=を等号として解釈するとこの式は確かに奇妙です。しかし、すでに述べたようにPythonにおける等号は==と書きますので、この式の=を等号と解釈してはいけません。プログラミングに慣れていないうちはよく=と==を間違えますので気を付けてください。

なお、この「計算結果を元の変数に格納する」という式は非常に良く用いられますので、Pythonでは二項算術演算子(x+yのように二つの値をとる算術演算子)と代入演算子を組みあわせた演算子が用意されています( 図6.9 左)。x=x+7をx+=7に書き換えるといった具合に二項算術演算子と代入演算子を続けて書くと、右辺に変数名を書く必要がなくなります。変数名がxなどのように短い時にはあまりありがたくないのですが、 図6.9 右のように変数名が長くなった場合に「target_leftside_lengthから3を引く」ということがとても読み取りやすくなります。

_images/compound-assignment-operators.png

図6.9 二項算術演算子と代入演算子の組み合わせ。変数名が非常に長い時に有効です。

さて、ずいぶん解説が長くなってしまいましたが、これで参加者の反応に応じて表示するメッセージを変更するためのPythonのコードの書き方がわかりました。残された問題は、この節の解説では「変数responseに格納されているものとする」と仮定した反応キー名をどうやって取得するかと、この節で解説したif文をBuilderでどのように入力するかの二点です。次節でこれらの問題を取り上げます。

チェックリスト
  • 条件に応じて処理を分岐させるPythonコードを書くことが出来る。

  • 数値の大小や一致・不一致に応じて処理を分岐させることが出来る。

  • 文字列の一致・不一致に応じて処理を分岐させることが出来る。

  • Pythonの比較演算子を6つ挙げてそれらの働きを説明することが出来る。

  • Pythonの論理演算子を3つ挙げてそれらの働きを説明することが出来る。

  • 変数に値を代入する式を書くことが出来る。

  • x+=7、x**=2といった二項算術演算子と代入演算子を組み合わせた式の働きを説明することが出来る。

6.8. オブジェクトについて学んでCodeコンポーネントを使って反応にフィードバックしよう

いよいよこの章の実験の完成が近づいてきました。前節で解説したPythonのif文をBuilderの中で使用するためには、Codeコンポーネント( 図6.10 )を使用します。このコンポーネントのプロパティ設定ダイアログは今まで紹介してきたコンポーネントのそれと大きく異なっており、他のコンポーネントと共通のプロパティが [名前] しかありません。 [名前] の下には [実験開始時][Routine開始時][フレーム毎][Routine終了時][実験終了時] というプロパティがあります。入力欄が非常に大きいので、それぞれにタブが用意されていてページを切り替えて入力します。 [名前] の横に [コードタイプ] という項目ががありますが、これはオンライン実験を作成するときに使用します。Py のままにしておいてください。

_images/code-component-properties.png

図6.10 Codeコンポーネントのアイコンとプロパティ設定ダイアログ。 [コードタイプ] は Py のままにしておいてください。

[実験開始時] にPythonのコードを記入すると、実験開始時にそのコードが実行されます。同様に、 [Routine開始時] に入力したコードは、そのCodeコンポーネントが配置されたルーチンが開始される時に実行されます。 [Routine終了時][実験終了時] も同様です。 複数のルーチンにCodeコンポーネントが置かれていた場合、 [実験開始時] に入力されたコードはpsyexpファイルに登録されている順番(Flowペインで「Routineを挿入」した時にメニューに並ぶ順)に実行されますが、そのことに依存したコードを書くべきではありません。同一ルーチンに複数のCodeコンポーネントがある場合、 [Routine開始時][フレーム毎][Routine終了時] はRoutineペインに並んでいる順番(上から下)に実行されます。何か意図があって複数のCodeコンポーネントを同一ルーチンに並べるのであれば構わないのですが、原則としてひとつのルーチンにはCodeコンポーネントは1個にしておくのがよいでしょう。

では作業に入りましょう。Builderでexp06.psyexpを開いて、trialルーチンを開いてCodeコンポーネントを配置してください。ルーチンの終了時に、押されたキーに応じて変数feedbackMsgに「正解」または「不正解」という文字列を代入しましょう。 [Routine終了時] に以下のコードを入力してください。

if key_choice.keys == correctAns:
    feedbackMsg = '正解'
else:
    feedbackMsg = '不正解'

ほぼ前節で解説した通りですが、1行目の押されたキーに対応する部分が異なっています。この部分を理解していないと次章以降の内容を理解できませんので、詳しく解説しましょう。プログラミングの経験がない方には難しい話が続きますが頑張って付いてきてください。

1行目でcorrectAnsと比較されているkey_choice.keysに注目します。変数名にピリオドを含むことは出来ませんので、これはkey_choice.keysという名前の変数ではなく、key_choiceという変数に.keysというものが添えられた式のはずです。では、key_choiceという変数には何が格納されているのでしょうか。Builderのキーボード反応計測用オブジェクト(psychopy.event.BuilderKeyResponse:以下BuilderKeyResponse)というのがその答えです。オブジェクトとは、コンピュータ上で扱うさまざまな対象、キーボードやマウスといった物理的なものから各種ソフトウェアの動作のために用いられるデータなどをプログラミング言語上から扱いやすくするための仕組みです。

とても抽象的な概念でイメージしにくいと思いますので、具体的な例を挙げましょう。 図6.11 はwebブラウザと文書作成ソフト、フォルダ内容表示の3つのウィンドウを開いている様子を示しています。これらのウィンドウは右上のボタンをクリックしてウィンドウを閉じたり最大化、最小化したりといった操作や、ウィンドウの枠をドラッグして位置や大きさを変更する動作は共通しています。しかし、ウィンドウを動かしてしまうと今まで別ウィンドウに隠されていた部分が見えるようになるのでOSはウィンドウの描き直しを行うのですが、描き直しの方法はwebブラウザや文書作成ソフトなど、個々のウィンドウで異なるでしょう。このように、コンピュータ上で扱う対象には共通化できる部分と、固有の部分があります。この共通化できる部分を共通化するための仕組みがオブジェクトです。実は、オブジェクトという考え方はここまで解説してきたBuilderのコンポーネントにも利用されています。例えば刺激に対応するコンポーネントにはいずれも [位置n [x, y] $][サイズ [w, h] $] というプロパティがあり、これらを使ってスクリーン上のどの位置にどのくらいの大きさで描画されるかを指定することが出来ました。 [色] プロパティで色を指定することも出来ましたが、ImageコンポーネントとGratingコンポーネントでは同じ値を [色] に指定してもスクリーンに描かれる刺激の色は全く異なりました ( 図6.11 )。こういった刺激間の共通点や相違点を効率よくするために、Builderの内部ではオブジェクトが使用されています。

_images/what-is-object.png

図6.11 オブジェクトの例。個々のウィンドウはそれぞれ固有の位置や大きさを持ちますが、閉じたり移動させたりといった操作方法は共通しています。これまでに扱ったBuilderの刺激にも、それぞれに共通する点や異なる点があります。

Pythonにおけるオブジェクトでは、こういったさまざまな対象を表現するために、データ属性とメソッドと呼ばれるものを用います。Builderの刺激オブジェクトの例を用いると、データ属性とは [位置 [x, y] $][サイズ [w, h] $] のように、それぞれの刺激で固有の値をとるデータのことです。メソッドについては詳しくは次章で触れますが、それぞれの刺激をスクリーンに刺激を描画する手続きのように、そのオブジェクトに対する操作をおこなうものです。それぞれのオブジェクトのデータ属性やメソッドの定義をクラスと呼び、クラスに従って生成されたオブジェクトを該当するクラスのインスタンスと呼びます。 図6.12 はデータ属性、メソッド、クラス、インスタンスの関係を示しています。Gratingクラスはグレーティング刺激を描画するためのクラスで、[位置 [x, y] $][回転角度 $][テクスチャ] に対応するPosition、Orientation、Textureといったデータ属性を持っています。また、スクリーンに描画を行うためのdrawというメソッドを持っています。グレーティング刺激をスクリーンに2個描画するために、Gratingクラスのインスタンスを2個生成して、それぞれgratingPatchとstripePatchという変数に格納しました。各インスタンスのデータ属性に値を代入する時には、gratingPatch.Position = (0,0)という具合に、変数名とデータ属性名をドット演算子( . )で結合して記述します。ドット演算子を用いることによって、どちらのインスタンスに代入するのか混乱することがありません。drawメソッドを用いて刺激を描画する時には、gratingPatch.draw( )という具合にやはりドット演算子を使って変数名とメソッド名を結合して記述します。メソッドは関数と同様に引数をとることが出来ますので、( )がdrawの後に付きます。drawメソッドでは各インスタンスのデータ属性の値を用いて刺激の位置や波形が決定されて刺激が描画されます。

_images/class-attributes-methods.png

図6.12 データ属性とメソッド、クラスとインスタンス。データ属性の値は個々のインスタンスで異なります。メソッドを呼ぶと自分のクラスで定義されているメソッドが呼び出されるので、異なるクラス間で同名のメソッドがあっても混乱しません。

グレーティング刺激に追加して、多角形の刺激を描画するPolygonクラスを用いて三角形と五角形を1個ずつ描画するとします。PolygonクラスはGratingクラスと似ていますがGratingクラスには無いN_Verticesというデータ属性を持っています( [頂点数] に対応)。また、Gratingクラスと同様にdrawというメソッドを持っていますが、その処理内容は異なります。多角形を2個描画するのですから、Polygonクラスのインスタンスを2個生成してtriangleとpentagonという変数に格納します。triangleとpentagonのデータ属性に適切な値を設定して、drawメソッドを呼びます。triangleに格納されたPolygonを描画する時にはtrialgle.draw()と記述しますが、このように変数名とメソッド名をドット演算子で結合して記述することによって、このdraw( )はPolygonクラスのオブジェクトのdrawメソッドであることがPythonにはわかります。ですから、Gratingクラスに同名のメソッドがあってもPythonが両者を混同することはありません。

なお、ここで想定したGratingクラスやPolygonクラスおよびそのデータ属性は、実際のBuilderで使用されているものと異なります。クラスおよびデータ属性の正確な名称およびBuilderとの対応関係を「 6.10.5:BuilderのコンポーネントとPsychoPyのクラスの対応 」に記しておきますので、詳しく学びたい方はご覧ください。Builderの通常の用途ではそこまで知っておく必要はありません。

ここまで説明して、ようやくCodeコンポーネントに入力したコードの1行目にあったkey_choice.keysという式の意味を解説できます。key_choiceにはBuilderKeyResponseというクラスのインスタンスが格納されています。BuilderKeyResponseはキーボードの状態を記録するためのクラスで、 表6.3 に示すデータ属性を持っています。この表によると、keysには押されたキー名が保持されています。key_choiceという変数名はBuilderのtrialルーチンに配置したKeyboardコンポーネントの [名前] に対応していますから、key_choice.keysという式はtrialルーチンに配置したKeyboardコンポーネントで記録したキー名です。具体的に言えば'left'か'right'のいずれかの文字列が格納されています。ですから、Codeコンポーネントに入力したkey_choice.keys == correctAnsは実験参加者が押したキーのキー名がcorrectAnsと一致すればTrue、一致しなければFalseが得られます。

表6.3 BuilderKeyResponseクラスのデータ属性

データ属性

概要

keys

ルーチンで記録されたキー名が格納されています。キーが押されていなければ空のリスト、「最後のキー」または「最初のキー」を記録している場合は該当するキー名、「全てのキー」を記録している場合は押されたキーのキー名が順番に並んだリストが格納されています。

corr

反応が正答であれば1、誤答であれば0が格納されています。キーが押される前は0です。

rt

キーが押された時刻が格納されています。キーが押されていなければ空のリスト、「最後のキー」または「最初のキー」を記録している場合は該当するキーが押された時刻、「全てのキー」を記録している場合は各キーが押された時刻が順番に並んだリストが格納されています。

clock

Builderが時刻を計測するための時計オブジェクトのインスタンスが格納されています。直接操作すべきではありません。

これでCodeコンポーネントに入力した式の解説はおしまいです。そして、ついにexp06.psyexpの完成です。さっそくexp06.psyexpを実行してください。実験情報ダイアログのconditionには01とだけ入力し、wordに「リニ」などの無意味語をターゲット語として入力してOKをクリックしてください。条件ファイルとしてexp06cnd01.xlsxが読み込まれて、最初の教示文にはターゲット語が埋め込まれているはずです。そして、刺激が出てきたら適当に反応して、眼鏡をかけている顔画像に対して「はい」を選択する(カーソルキーの左を押す)と正解、それ以外の顔画像に対して「はい」を選択すると不正解になることを確認しましょう。実験が終了したらもう一度実験を実行して、今度は実験情報ダイアログのconditionに02と入力してOKをクリックしてみましょう。眼鏡をかけている顔画像に「はい」を選択すると不正解、それ以外の画像に対して「はい」を選択すると正解になるはずです。

実験が終わったら、xlsx記録ファイルを開いてみましょう( 図6.13 )。xlsxファイルにはtrialsとratingsという2枚のシートがあるはずです。それぞれ、trialsループに含まれるルーチンからの出力と、ratingsループに含まれるルーチンからの出力に対応しています。trialsのシートは前章までの内容から特に新しいことはありませんので、ratingsのシートを確認しましょう。ここにはRatingScaleコンポーネントの出力が記録されいますが、RatingScaleコンポーネントもKeyboardコンポーネントと同様に、コンポーネントの [名前] に指定した名前に.response_meanとついている列に反応の平均値、.response_rawとついている列から右方向へ各試行の反応が出力されています。さらにその右には反応時間の平均値と各試行の反応時間が出力されています。なお、RatingScaleコンポーネントのプロパティ設定ダイアログでカテゴリカルな尺度を使用するように設定していた場合は、反応の平均値が計算できませんので平均値の列は省略されます。

_images/ratingscale-xlsx-output.png

図6.13 xlsx記録ファイルの例。結果がループ毎にシートに分かれて出力されています。ratingsシートを選択すると、ratingsループに含まれているルーチンからの出力を確認できます。

以上でこの章の解説はおしまいです。if文についてはまだまだ説明したいことがたくさんありますが、この章もずいぶん長くなってしまったので、ここで一区切りにして次の章で取り扱うことにしましょう。

チェックリスト
  • Codeコンポーネントを用いてPythonのコードをルーチンに配置することができる。

  • Codeコンポーネントのプロパティである [実験開始時][Routine開始時][フレーム毎][Routine終了時][実験終了時] の違いを説明できる。

  • クラスとインスタンスの違いを説明することができる。

  • データ属性とは何かを説明することができる。

  • 変数xに格納されたインスタンスのデータ属性fooに値を代入したり値を参照したりするときのPythonの式を書くことができる。

  • BuilderKeyResponseのデータ属性を参照して押されたキー名を知ることが出来る。

6.9. 練習問題:データ属性corrで正誤を判定しよう

この章の解説では、条件ファイルとして眼鏡をかけている顔画像が正事例となるexp06cnd01.xlsxと、眼鏡をかけていない顔画像が正事例となるexp06cnd02.xlsxの二種類しか作成しませんでした。しかし、実際にexp06.psyexpを使って概念識別の実験をするためには、他の特徴が正事例となる条件ファイルを準備する必要があります。練習問題として、「眼鏡をかけていて眉が上がっている(論理積)」顔画像が正事例となる条件ファイルと、「顔が丸い、または眉毛が下がっている(論理和)」顔画像が正事例となる条件ファイルを作成してください。

もうひとつ、if文とBuilderKeyResponseの復習問題として、trialルーチンの反応が正答であったか誤答であったかに応じてfeedbackMsgに代入する文字列を変更する部分で、データ属性keysを使わずにデータ属性corrを使うようにCodeコンポーネントの内容を書き換えてください。 表6.3 に示すように、データ属性corrには反応が正答であれば1、誤答であれば0が格納されています。これはほぼ答えを言っているようなものなので、これ以上はノーヒントで挑戦してください。

6.10. この章のトピックス

6.10.1. RatingScaleコンポーネントの [すべてをカスタマイズ] について(上級)

このトピックはBuilderを使わずに直接Pythonのスクリプトを書けるレベルの方を想定して書きますのでそのつもりでお読みください。

RatingScaleコンポーネントの [すべてをカスタマイズ] は、RatingScaleオブジェクトのコンストラクタに渡す追加の引数を記述するためのものです。このプロパティを指定せずにRatingScaleオブジェクトを作成すると、Pythonのコードに変換されたときに以下のようにコンストラクタが呼び出されます。

rating = visual.RatingScale(win=win, name='rating',
    marker='triangle', size=1.0, pos=[0.0, -0.4],
    low=1, high=7, labels=[''], scale='')

ここで、他のプロパティ値はすべてそのままでcustomize_everythingに以下のように書きこんでみます。

foo=bar, baz='qux'

コードをコンパイルしてみると、コンストラクタの呼び出しが以下のように変わります。

rating = visual.RatingScale(win=win, name='rating', foo=bar, baz='qux')

つまり、 [名前] 以外のプロパティ設定ダイアログの項目はすべて無視されて、 [すべてをカスタマイズ] に入力した文字列がそのままwinとname以外のコンストラクタの引数になるわけです。もちろんpsychopy.visual.RatingScaleにはfooやbazといった引数は無いので、このコードはエラーとなります。コンストラクタの引数を理解して自分で正しい呼び出しコードが書ける人向けの機能だと言えます。

6.10.2. 改行文字を使った複数行の文字列の表現(上級)

Microsoft Wordなどの文書作成アプリケーションを使ったことがある方は、この種のアプリケーションでは 図6.14 文の末尾に矢印のような記号が(しばしば本文とは異なる色で)描かれていることに気付いておられると思います。実は、アプリケーションの内部では、この「矢印」もひらがなや漢字、英数字と同じ「文字」の一種で、ここで「行が変わる」ことを表しています。文字として扱われている証拠(?)に、普通の文字と同様にBackSpaceキーやDelキーで削除できますし、削除したら前後の文が1行につながりますよね。この「行が変わる」ことを表す文字を 改行文字 と呼びます。「 3.11.6:$を含む文字列を提示する 」で「コンピュータにおいて文字は『文字コード』と呼ばれる数値で表される」と書きましたが、改行文字にも数値が割り当てられています。例えばWindowsでは0x0DA、Linuxでは0x0Aです。

_images/newline-characters.png

図6.14 改行文字の表示例。行末の矢印のような記号が改行文字です。

Pythonを含む多くのプログラミング言語では、この改行文字をエスケープシーケンス(「3.11.6:$を含む文字列を提示する 」参照)を使って\nと表すことが出来ます。つまり

'当てはまるならカーソルキーの左、\n当てはまらないなら右を押してください。'

と書くと、

当てはまるならカーソルキーの左、 当てはまらないなら右を押してください

のように\nの位置で改行されて出力されます。\nを使う方法を知っていると、インターネット上で誰かが書いたプログラムを参考にして勉強するときなどにきっと役に立ちますのでぜひとも覚えておきたいところです。

さて、本章の教示も\nを使って記述することが出来ますが、一行に書くには長すぎます。このような場合、Pythonの文法には

  • 行末が \ のみで終わる場合は次の行につながっているとみなす

  • '今日は' 'いい天気' のように二つの文字列の間に空白文字しかない場合は自動的に接続して '今日はいい天気' と同じとみなす

という規則があることを利用して

'「' + expInfo['word'] + '」は人の顔を形容する言葉です。\n' \
'提示された顏の絵が当てはまるならカーソルキーの左、\n' \
'当てはまらないなら右を押してください。'

と書くことが出来ます。\は最後の行には不要(というか書いてはいけない)点にご注意ください。\nは各行の末尾以外に置いても構わないので、

'「' + expInfo['word'] + '」は人の顔を形容する言葉です。\n提示された顏の絵が' \
'当てはまるならカーソルキーの左、\n当てはまらないなら右を押してください。 '

と書くこともできます。上の例とじっくり見比べて、同じ結果になることを確認してください。

6.10.3. TrueとFalseの「値」

Pythonでは、if文にTrueやFalse以外の値を返す式を書くことが出来ます。その場合、式を評価した結果が0であればFalse、0以外であればTrueとして処理されます。ですから、 図6.15 上段に示した例ではいずれの場合もif文に続くx=7が実行されます。このことを積極的に利用したプログラムを書くことも可能ですが、わかりにくいのでお勧めしません。

お勧めしないと言えば、Python2におけるTrueやFalseは、あたかも通常の変数であるかのように値を代入することが出来ます。ですから、True=0などと書いてTrueに0を代入することも可能です。ただ、このようにしてしまうとTrueを評価すると0が得られて、0はFalseとして機能するため、 図6.15 下段の例のような非常にややこしい事態が生じてしまいます。TrueやFalseへの代入は絶対に行うべきではありません。なお、Python3ではTrueとFalseが予約語となったため、このような代入は出来なくなっています。

_images/true-false-value.png

図6.15 TrueとFalseに関する注意事項。

6.10.4. 文字列、シーケンスに対する比較演算子

本文では<や>=といった比較演算子を使って数値の大小を比較する方法について解説しましたが、Pythonでは文字列やシーケンス型のデータに対しても比較演算子を使用することが出来ます。これらのデータに対して比較演算子が適用された場合は「辞書順」に従って比較が行われます。

例えばmemoryとmindという文字列を比較するとしましょう。英和辞典の順番では、1文字目から順番に比較していって、最初に異なる文字のアルファベット順でどちらが先に掲載されるかが決まります。memoryとmindでしたら1文字目はどちらもm、2文字目はeとiですから、eの方が前です。Pythonでは、辞書順で前にくる文字列ほど「小さい」と判定されますので、'memory' < 'mind'はTrueとなります。'memory' > 'mind'はFalseです。

少し注意が必要なのは、数値や漢字が文字列に含まれる場合です。半角数字はすべてのアルファベットより「小さい」と判断されます。ですから'magical number' < '7'を評価するとFalseが得られます('7'は'm'より小さいと判断される)。かな文字や漢字は文字コードに従って解釈されますので、文字コードを理解していなければ結果を予測するのは困難です。例えばUnicodeで「心」は「記」より小さい値で表されますので(それぞれ0x8a18と0x5fc3)'記憶' < '心'を評価するとFalseが得られます。筆者の個人的な意見としては、このような比較は非常にわかりにくいので可能な限り使用するべきではないと思います。

シーケンスの場合は、要素を先頭から順番に比較していきます。[7,8,2] < [7,1,5,9]という比較でしたら、最初の要素の7は同一、2番目の要素は8と1で左辺の方が大きい値ですので、左辺の方が大きいと判定されます。従って[7,8,2] < [7,1,5,9]はFalseです。シーケンスの要素に文字列が含まれている場合も、同様に個々の要素を先頭から順番に比較します。['theory', 7, 'mind'] > ['theory', 7, 'memory']でしたら最初と2番目の要素は同一、3番目の要素はmemoryよりmindの方が「大きい」のでしたからTrueが得られます。

6.10.5. BuilderのコンポーネントとPsychoPyのクラスの対応

図6.16 にBuilderのGratingコンポーネントと、それに対応するPsychoPyのクラスを示します。Gratingコンポーネントに対応するクラスはpsychopy.visual.GratingStimという名称(以下GratingStim)です。Gratingコンポーネントの各プロパティはGratingStimクラスのデータ属性と対応しています。Builderにおいて各プロパティに式や値を設定するという作業は、対応するGratingStimクラスのデータ属性にそれらを設定することと同義です。自分で実験用のPythonコードを書く場合は、GratingStimクラスのデータ属性名やその設定方法を覚えなければいけません。Builderはプロパティ設定ダイアログという形でプロパティの一覧を見て設定が出来るので、コードを書く方法を覚えるよりも手軽に自分の実験を作成できる段階まで学習できます。これがBuilderを利用する最大の利点です。

_images/grating-attributes-correspondence.png

図6.16 BuilderのGratingコンポーネントのプロパティと、それに対応するPsychoPyのクラスのデータ属性。

ただし、の下の方に対応するプロパティが存在しないデータ属性があるように、BuilderではGratingStimクラスの全てのデータ属性やメソッドを利用することが出来ません。PsychoPyの機能を最大限に活かすためにはやはりコードを書く必要があります。

表6.4 は、PolygonコンポーネントとPsychoPyのクラスの対応関係を示しています。Polygonコンポーネントの場合は、 [形状] に応じて最も効率がよいクラスをBuilderが選択します。クラスによってデータ属性が異なりますので、Polygonコンポーネントのプロパティとデータ属性の対応も選択されたクラスに応じて変化します。

Builderは、背後にあるPsychoPyのクラスやそのデータ属性についての知識がなくても使用できるように設計されています。実際、第5章 まではPsychoPyのクラスについて触れずに解説を進めてくることが出来ました。しかし、この章の実験のように、参加者の選択に応じて刺激などが動的に変化する実験を作成するためには、残念ながら現状のBuilderではPsychoPyのクラスについて言及せざるを得ません。

表6.4 BuilderのPolygonコンポーネントに対応するPsychoPyのクラス

形状

対応するクラス

直線

psychopy.visual.Line

三角形

psychopy.visual.ShapeStim

長方形

psychopy.visual.Rect

十字

psychopy.visual.ShapeStim

psychopy.visual.ShapeStim

正多角形...

psychopy.visual.Polygon

6.10.6. Codeコンポーネントを使ってプロパティ設定ダイアログに複雑な式を直接入力しないようにする

刺激のプロパティに複雑な式を指定すると、プロパティ設定ダイアログの入力欄より長くなってしまい非常に作業しにくくなることがあります。入力ミスを犯しやすいですし、入力ミスの確認も難しいので、あまり望ましくありません。このような場合、Codeコンポーネントを使うとすっきりさせることが出来ます。

例として以下の「 5.5:実験情報ダイアログから実験のパラメータを取得しよう 」の式を取り上げましょう。実験情報ダイアログから値を取りだしてターゲット刺激の [位置 [x, y]] に設定する座標を計算する式でした。

[float(expInfo['eccentricity']) * cos(deg2rad(targetPos)),
 float(expInfo['eccentricity']) * sin(deg2rad(targetPos))]

Codeコンポーネントを配置して、 [Routine開始時] のタブに以下のように書いておけば、targetPosXYという名前でターゲット刺激の座標を表すことが出来ます。

targetPosXY = [float(expInfo['eccentricity']) * cos(deg2rad(targetPos)),
 float(expInfo['eccentricity']) * sin(deg2rad(targetPos))]

Codeコンポーネントを使う場合は1行の式である必要はありませんので、実験情報ダイアログから値を取りだす部分と座標を計算する部分を分割して以下のように書くこともできます。式の入力ミスで多いのがカッコの位置の誤りや入力忘れですが、このように式が分割されていると入力ミスを起こしにくくなります。

ecc = float(expInfo['eccentricity'])
angle = deg2rad(targetPos)
targetPosXY = [ecc * cos(angle), ecc * sin(angle)]

ひとつ注意してほしいのが、Codeコンポーネントの配置です。targetPosXYは刺激が描画される前に計算されていなければいけないので、Routine内におけるCodeコンポーネントの順番を刺激より上にしておく必要があります。

「コンポーネントのプロパティ設定ダイアログに直接Pythonの式を書かない」という手法は、PsychoPy 3.0.0で追加されたオンライン実験の作成時にも役に立ちますので、オンライン実験に興味がある人はこの手法をマスターしておくとよいでしょう。