脳波データから感情を推測するAIを作ってみる

# はじめに
脳波をAIに学習させて感情を判別できるか試してみた。
手元にあるのが前頭部の電位のみを計測する簡易なヘッドセットなので、どこまで精度が上げられるのか。今回は感情のうち「面白い」を判別してみる。

# 準備
#### 用意したもの
* [Mac](https://www.apple.com/jp/mac/)
* [NeuroSky](https://store.neurosky.com/)社の[MindWave Mobile](https://store.neurosky.com/pages/mindwave)
（ [Amazonで入手出来ます](https://amzn.to/2PuxypR) ）


#### 環境
* MacOSX 10.12.6
* python 2.7

#### 使用ライブラリ
* [Keras](https://keras.io/)
* [TensorFlow](https://www.tensorflow.org/)
* [NeuroPy](https://pypi.python.org/pypi/NeuroPy)



# 訓練用データの取得

### 条件
* ポジティブデータとネガティブデータを50ずつ、計100シーケンスを用意
* 訓練データ:テストデータ=4:1
* ラベルはポジティブを1, ネガティブを0


### EEG取得サンプルコード

今まではEEGをインスタレーションやOOHに使用することが多かったので、Adobe AIRでEEGを拾って解析を行っていたが、今回はAIで使用することもありPythonで取得。
Neurosky headsetをPythonで扱うライブラリ「[NeuroPy](https://pypi.python.org/pypi/NeuroPy)」を使用し、下記のようなコードで簡単にヘッドセットが送出するデータを取得できる。

```py3
from NeuroPy import NeuroPy

PORT = '/dev/tty.MindWaveMobile-DevA'
headset = NeuroPy(PORT)

def get_rawdata(raw_value): 
    print(raw_value)

headset.setCallBack("rawValue", get_rawdata) 
headset.start() 
```

PORTは

```shell
ls /dev/tty.* 
```
で、出てきたもののうち、 /dev/tty.MindWaveMobile〜を適宜設定。

### 取得する値

下記の値がNeuroSky MindWaveから取得可能。

##### 精神状態
 
* attention　（集中度）
* meditation　（リラックス度）

##### ローデータ

* rawValue

##### 各帯域

* delta　（δ波:0.5 - 2.75Hz）
* theta　（θ派:3.5 - 6.75Hz）
* lowAlpha　（α波:7.5 - 9.25Hz）
* highAlpha　（α波:10 - 11.75Hz）
* lowBeta　（β波:13 - 16.75Hz）
* highBeta　（β波:18 - 29.75Hz）
* lowGamma　（γ波:31 - 39.75Hz）
* midGamma　（γ波:41 - 49.75Hz）

##### その他

* poorSignal　(信号強度)

今回はrawValueを学習。連続した2000電位データ（約3~4秒分）を１シーケンスとする。


### 取得時の工夫

* 感情が湧いた前後の脳波を取得するため、任意のタイミング（Enterキー押下）で直前の約3秒間の脳波を保存。
* poorSignalの値を監視して、ヘッドセットがうまく脳波にアクセスできていない場合は保存処理を行わない。（前頭部に金属のセンサーを押し当てるようなヘッドセットなので、ちょっとしたズレで脳波が拾えなくなってしまうことも多い）


# 学習

### モデル
CNN（ノイズ除去、時間軸のズレを吸収、シーケンス長の圧縮）
↓
LSTM（時系列データを学習）

```py
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.layers import Embedding
from keras.layers import LSTM

#　シーケンス長
seq_length = 2000

# 計測したデータをCSVから読み込み
x_list_0 = np.loadtxt("eeg_normal.csv",delimiter=",")
x_list_1 = np.loadtxt("eeg_fun.csv",delimiter=",")

# ラベル用配列を生成
y_list_0 = np.zeros(50)
y_list_1 = np.ones(50)

# 訓練データとテストデータをわける
x_train = np.r_[x_list_0[:40], x_list_1[:40]]
y_train = np.r_[y_list_0[:40], y_list_1[:40]]
x_test = np.r_[x_list_0[-10:], x_list_1[-10:]]
y_test = np.r_[y_list_0[-10:], y_list_1[-10:]]

# 最初の畳み込み層への入力Shapeを3階テンソルにする
x_train = np.reshape(x_train, x_train.shape + (1,))
x_test = np.reshape(x_test, x_test.shape + (1,))

#　モデル構築
model = Sequential()
model.add(Conv1D(64, 3, activation='relu', input_shape=(seq_length, 1)))
model.add(AveragePooling1D(4))
model.add(LSTM(128))
model.add(Dropout(0.5))
model.add(Dense(1, activation='sigmoid'))

model.compile(loss='binary_crossentropy',
              optimizer='rmsprop',
              metrics=['accuracy'])

# 訓練
model.fit(x_train, y_train, batch_size=5, epochs=100)
```
参考
[Kerasの1D Convolutionサンプル](https://keras.io/ja/getting-started/sequential-model-guide/#1d-convolution)
[KerasのLSTMサンプル](https://keras.io/ja/getting-started/sequential-model-guide/#lstm)

# 結果

```py
score = model.evaluate(x_test, y_test, batch_size=5)
print(score)
```

```
[0.54980045557022095, 0.85000002384185791]
```
Loss 0.55
Accuracy 0.85(85%)

# 考察
思ったよりも良い精度が出ました。

ただ今回、学習データのサンプリングの際に、「面白いと感じたかどうか」を「笑ったかどうか」で判定していたので、顔の表情筋が動き、その筋電位を拾って特徴的な電位データとなった可能性があり、脳波で判別できたとは言い切れないです。

今回はrawValueをそのまま使用しましたが、脳波をあらかじめFTで帯域ごとに分解（α波、β波など）することで、より精度を上げられそうな気もします。

今後はそのあたりを踏まえて、他の感情の判別や精度向上を試してみたいです。


*今回のAIモデル構築にあたり、[@bitchal](https://qiita.com/bitchal)さんに多大なサポートをいただきました。感謝！*