CNNで蝉の鳴き声を分類する。

#**はじめに**　
この記事は夏休みが暇な大学生の暇つぶしを記事にて言語化したものです。
内容は悪しからず。
#**作業時間**
前処理:1~2時間
計算時間:3~4時間
#**データセット**
youtubeにて作業用BGMとして蝉の鳴き声の1時間動画なるものがあったのでそれをデータとして利用しました。今回は「ヒグラシ」、「ミンミンゼミ」、「ツクツクホウシ」の鳴き声を使いました。(次は蝉の種類を増やしたい。)[一応link先載せときます。](https://www.youtube.com/watch?v=2b-m9iZSz2E)
#**手法**
音声認識の手法としてスペクトルグラムを利用するというものがあります。スペクトルグラムとは音の周波数と振幅と時間を同時に表現したもので、その音の特徴を表した画像と言えます。振幅は色で表現されています。
![0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402757/9c4cd750-390a-ecf7-1926-170b20f12887.jpeg)

楽器の分類をスペクトルグラムを用いたCNNを使うことで高い精度が出たという報告があります([論文URL](https://arxiv.org/abs/1605.06644))。そこで今回は蝉の鳴き声のスペクトルグラム画像を入力にし、出力に蝉の種類を出すCNNを構築しました。
#**前処理**
①youtubeから拾ってきた音源データは1時間もあるので、まずこれを5秒ごとのデータに区切ります。すると720個の音源データが出来ます。(5秒に区切った理由としては、5秒聞けばどの蝉かわかるだろうと思ったからです。)

```python:music_split.py
from pydub import AudioSegment

 # wavファイルの読み込み
sound = AudioSegment.from_file(r"C:\Users\????\Desktop\tukutuku\tukutuku.wav", format="wav")
i=0
j=0
# 3594秒=59分くらいまで繰り返す
while(i<=3594000):
   #5000msごとに切り取る。
    sound1 = sound[i:i+5000]
   # 抽出した部分をディレクトリに出力 ファイル名は数字になるようにしている。
    sound1.export(r"C:\Users\????\Desktop\tukutuku1\00{}.wav".format(j+1), format="wav")
    i=i+5000
    j=j+1

```

②この音源データのスペクトルグラムを作ります。glob関数で、指定したディレクトリの中の音源データのパスをリストで取得できます。ここで蝉の種類ごとに720枚の画像が出来ます。

```python:sound.py
import librosa, librosa.display,matplotlib.pyplot as plt
import glob
data=glob.glob("./tukutuku1/*")#指定したディレクトリの中の音源のパスを取得
j=0
for i in data:
 plt.figure()
 x, fs = librosa.load(i, sr=44100)
 mfccs = librosa.feature.mfcc(x, sr=fs)
 librosa.display.specshow(mfccs, sr=fs, x_axis='time',y_axis='hz')
 plt.colorbar()
 plt.savefig("./tukutuku/{}.jpg".format(j))
 j=j+1

```

③①～②を蝉の種類を変えて行う。
④ここから毎度お馴染み、画像の配列読み込みと出力配列の作成です。まずはglob関数で画像データのパスを読み込みます。

```python:semi_CNN.py

#画像データ読み込み　data1,2,3はリスト構造
data1=glob.glob('./data-set/higurasi1/*')
data2=glob.glob('./data-set/minmin1/*')
data3=glob.glob('./data-set/tukutuku/*')

```
画像をリサイズして、入力と出力の配列を作ります。

```python:semi_CNN.py
X1=[]
Y=[]
class_name=['higurasi','minminzemi','tukutukubousi']
Y1=([1,0,0])#ヒグラシ
Y2=([0,1,0])#ミンミンゼミ
Y3=([0,0,1])#ツクツクホウシ
for i in range(len(data1)):
  X= np.array((Image.open(data1[i])).resize((96,128)))#i番目の画像データをopenしてリサイズしてnumpy配列にする。
  X1.append(X)#リストに追加
  Y.append(Y1)#正解ラベルを追加
for i in range(len(data2)):
  X= np.array((Image.open(data2[i])).resize((96,128)))
  X1.append(X)
  Y.append(Y2)
for i in range(len(data3)):
  X= np.array((Image.open(data3[i])).resize((96,128)))
  X1.append(X)
  Y.append(Y3)
X=np.array(X1)
Y=np.array(Y)
X=X.astype('float32')
X=X/255.0#値が0~1になるように正規化
```
#**ネットワーク構造**
dropoutなどのパラメーターは正直まだどういった値が適切なのかわかっていないです。とりあえず適当な値にしときました。

```python:semi_CNN.py
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.33, random_state=111)
# CNNを構築
model = Sequential()

model.add(Conv2D(32, (3, 3), padding='same',
                 input_shape=X_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(3))       # クラスは3個
model.add(Activation('softmax'))

# コンパイル
model.compile(loss='categorical_crossentropy',
              optimizer='SGD',
              metrics=['accuracy'])

# 実行。出力はなしで設定(verbose=0)。
history = model.fit(X_train, y_train, batch_size=5, epochs=200,
                   validation_data = (X_test, y_test), verbose = 0)
model.save('semi-CNN.h5')

#acc
plt.plot(history.history['acc'])
plt.plot(history.history['val_acc'])
plt.title('model accuracy')
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.legend(['acc', 'val_acc'], loc='lower right')
plt.show()
#loss
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'test'], loc='upper left')
plt.show()
text='学習が終わりました'
send_mail(co_msg=text)


```
今回は学習が終わると、send_mail関数でメールが来るようにしています。一応それも載せときます。

 ```python
def send_mail(co_msg):
    SMTP_SERVER = "smtp.mail.yahoo.co.jp"
    SMTP_PORT = 587
    SMTP_USERNAME = "!!!@yahoo.co.jp"#自分のユーザーネームor登録アドレス
    SMTP_PASSWORD = "!!!"#自分のパスワード
    EMAIL_FROM ="!!!@yahoo.co.jp"#自分の送信元メールアドレス
    EMAIL_TO = "!!!yahoo.co.jp"#送信先メールアドレス
    EMAIL_SUBJECT = "python"
    msg = MIMEText(co_msg)
    msg['Subject'] = EMAIL_SUBJECT
    msg['From'] = EMAIL_FROM
    msg['To'] = EMAIL_TO
    mail=smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
    mail.login(SMTP_USERNAME, SMTP_PASSWORD)
    mail.sendmail(EMAIL_FROM, EMAIL_TO, msg.as_string())
    mail.quit()
    return
```

#**結果**
accはtrain testどちらもほぼ100％でした。
![semi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402757/1e587973-c730-1f69-6f95-3ae1e934fb7f.png)
lossはtestとtrainに少し差が出ましたが、これは大きな差ではないでしょう。
![semi2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402757/2e5544d1-df93-bff7-c8c9-113b7e340a03.png)
#**感想**
send_mail関数を書いた位置が悪くて、accとlossのグラフ表示を消さないとメールが来なかったようです。。失敗
accがやたら高かったのは人間と異なり、蝉は出す音の周波数パターンがいつも同じだからだと思います。ミンミンゼミならいつも同じ感じでミンミンと鳴きますよね。また今回用いた3種類の蝉の鳴き声は人間でも容易に聞き分けられるものだったというのも大きいでしょう。次は蝉の種類を増やしたいですね。(データがあれば。。。)