Tensorflowによるカテゴリー分類の、CSVデータのデータセットの作り方

# はじめに
　こんにちは、新しい技術が大好きなsazanami5です。qiita初投稿ですのでお手柔らかにお願いします。ご意見、ご指摘等ありましたらぜひコメントいただけると嬉しいです。
　流行りの深層学習に興味を持ち、「深層学習の教科書」という本を読んでからtensorflowで時々遊んでいます。最近、TensorFlowを使ってカテゴリー分類の機械学習を自前のCSVデータで動かしたのですが、データセットがうまく作れず、少し苦戦してしまいました。データセットを作る方法をまとめた記事があまりなく、[TensorFlowチュートリアル](https://www.tensorflow.org/tutorials?hl=ja)も少し読みにくかったため、今回データセットの作り方を中心に投稿させていただくことにしました。

よくあるアヤメの分類のような**カテゴリー分類**のためのデータセットを作っています。

# 環境
私はローカルの仮想環境で実行していますが、[Google Colaboratory](https://colab.research.google.com/)を利用すれば環境構築の必要はありません。
python 3.8.6
tensorflow 2.4.1
numpy 1.19.5
pandas1.2.1
matplotlib 3.3.3


# 使用するCSVデータ
このデータは第二外国語を学習したことのある人を対象に行ったものです。
学習したことのある第二外国語（その他を含む8言語の中から選択）を最初に聞き、Q1~Q30では日常や生活習慣などに関する二択の質問を行っています。
<details><summary>質問の内容とラベルの内容</summary>
みづらくてゴメンなさい:confounded:

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1298343/4f6d448d-8bc4-fc15-7866-3a7a2f662c74.png)																														

</details>

それぞれの回答を数字に変換したデータは以下のようになっています。

																														

| language | Q1 | Q2 | Q3 |...|Q30|
|:-------:|:---:|:--:|:--:|:-:|:-:|
|0        | 0   |  1 | 0  |...| 1 |
|1        |  0  |  0 | 0  |...| 0 |
|2        |   1 |  0 | 0  |...| 1 |
|6        |    0|  0 | 0  |...| 1 |
|4        |    1|  0 | 0  |...| 1 |
|2        |    1|  0 | 0  |...| 0 |
|...      |  ...| ...|... |...|...|
|2        |    1|  0 | 0  |...| 1 |

#データセットを作る
ついに本題のデータセットの作り方について説明します。
上で用意したcsvデータを、分類するラベル（languageのカラム）と学習させるデータ（languageカラム以外の列）に分ける必要があります。
pandas等を使ってコードでも出来るのですが、今回はExcelを使い、以下の4つのファイルを作成しました。

訓練用データ（データの7割）
x_train.csv（languageカラム以外の列）
y_train.csv（languageカラムの列）

評価用データ(データの3割)
x_eval.csv（languageカラム以外の列）
y_eval.csv（languageカラムの列）

###ライブラリのインポート
必要なライブラリをインポートします。
データ処理は私が慣れているPandasを利用しました。

```python:secondLanguageRecommend.ipynb

import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Activation
from tensorflow.keras.optimizers import Adam
import numpy as np
import pandas as pd
```
### 変数のセット

```python:secondLanguageRecommend.ipynb

tf.random.set_seed(0) #再現性を持たせるためにランダムの値を固定する
num_classes = 8 #ラベルの種類が8つ
batch_size = 5
epochs = 20
```


### CSVデータの読み込み
pd.read_csvを使って読み込みます。windowsやmacのどちらのデータでも使えるようにするため、念の為エンコードをutf-8に指定しています。

データ構造

```
secondLanguageRecommend
 ├ secondLanguageRecommend.ipynb
 └ data/
    ├ x_train.csv
    ├ x_eval.csv
    ├ y_train.csv
    └ y_eval.csv
```


```python:secondLanguageRecommend.ipynb

# データの読み込み
x_train = pd.read_csv("data/x_train.csv", encoding= "utf-8")
x_test = pd.read_csv("data/x_eval.csv", encoding= "utf-8")
y_train = pd.read_csv("data/y_train.csv", encoding= "utf-8")
y_test = pd.read_csv("data/y_eval.csv", encoding= "utf-8")

# 一行目のカラムの行を取り除き、数値部分のみにする
x_train = x_train.values
x_test = x_test.values
y_train = y_train.loc[:,"language"]
y_test = y_test.loc[:,"language"]
print(x_train[:10])
print(type(x_train))
```

出力

```:out

[[0 1 0 1 0 0 1 0 1 0 0 0 0 0 1 0 0 1 0 0 0 1 0 1 1 0 1 0 1 1]
 [0 0 0 0 0 0 1 1 0 0 0 0 1 1 1 1 1 1 0 0 1 0 0 0 1 1 0 1 1 0]
 [1 0 0 1 0 1 0 0 0 0 0 0 0 1 1 1 1 1 0 1 1 0 1 1 1 0 1 0 0 1]
 [0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 1 0 1 1 0 0 0 1 0 1 0 1 0 1 1]
 [1 0 0 1 0 0 1 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 1 0 0 1 0 0 1 1]
 [1 0 0 1 0 1 0 0 1 0 1 0 0 0 0 1 1 0 1 0 1 0 0 0 0 0 0 0 0 1]
 [0 0 0 0 0 0 1 0 1 1 0 0 0 0 0 1 1 1 1 0 1 1 0 0 0 0 1 1 1 0]
 [0 0 0 0 0 1 1 0 1 1 0 1 0 1 0 1 1 0 0 0 1 0 0 1 1 0 0 0 0 1]
 [0 0 0 0 0 1 1 1 0 0 0 0 1 0 0 1 1 1 0 1 1 0 1 0 0 1 0 0 1 0]
 [0 1 0 1 0 0 1 1 1 1 1 0 0 1 1 0 1 1 1 0 0 1 1 1 0 1 0 0 1 1]]
<class 'numpy.ndarray'>
```

ここで型が**numpy.ndarray**になっていることが大事です。
自分はここでDataFrameやlistではできないことを知らなくてつまずきました。ぴえん。
全ての列のデータを取りたい時は`.values`、特定の列のデータを取りたい時は`.loc[:,"カラム名１","カラム名２"]`を使ってください。


### ラベルをone-hotベクトルにする
#### one-hotベクトルとは
>One-hot ベクトルとは、(0,1,0,0,0,0) のように、1つの成分が1で残りの成分が全て0であるようなベクトルのことです。
[引用元:算数から高度な数学まで、網羅的に解説したサイト](https://mathwords.net/onehot)

今回は0〜７の8個のカテゴリーがあるので、[[0][4][3]]のようなデータを[[1,0,0,0,0,0,0,0][0,0,0,0,1,0,0,0][0,0,0,1,0,0,0,0]]にするということです。

kerasにある**to_categorical**関数を使って変換します。
[keras公式ドキュメント](https://keras.io/ja/utils/)

```python:secondLanguageRecommend.ipynb

# convert class vectors to binary class matrices
y_train = tf.keras.utils.to_categorical(y_train, num_classes)
y_test = tf.keras.utils.to_categorical(y_test, num_classes)
print(y_train[:10])
print(type(y_train))
```
出力

```:out
[[0. 1. 0. 0. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0. 0.]]
<class 'numpy.ndarray'>
```

変換できました。
これでデータセットは完成です。
これをそのままfitの引数に渡すことで学習できます。

# まとめ
- 型はnumpy.ndarray
- カテゴリー分類はone-hotベクトルに変換

# 最後に
　データセットの作り方は今回の方法の他にも様々なものがあり、どれが正解というものでもないと思います。
一つの選択肢として誰かの役に立てば幸いです。
また、今回のような０と１の回答だけではない場合は正規化という作業も必要ですので、時間があれば追記いたします。
ここまで読んでいただきありがとうございました。

最後に今回のコードを貼っておきます。

```python:secondLanguageRecommend.ipynb

import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Activation
from tensorflow.keras.optimizers import Adam
import numpy as np
import pandas as pd

tf.random.set_seed(0)
num_classes=  8
batch_size= 5
epochs= 20

#load data
x_train= pd.read_csv("data/x_train.csv", encoding= "utf-8")
x_test= pd.read_csv("data/x_eval.csv", encoding= "utf-8")
y_train= pd.read_csv("data/y_train.csv", encoding= "utf-8")
y_test= pd.read_csv("data/y_eval.csv", encoding= "utf-8")

y_train = y_train.loc[:,"language"]
y_test = y_test.loc[:,"language"]
x_train = x_train.values
print(x_train[:10])
print(type(x_train))

# convert class vectors to binary class matrices
y_train = tf.keras.utils.to_categorical(y_train, num_classes)
y_test = tf.keras.utils.to_categorical(y_test, num_classes)
print(y_train[:10])
print(type(y_train))

# モデル作成
model = Sequential()
model.add(Dense(32, activation='relu', input_shape=(30,)))
model.add(Dense(32, activation='relu'))
model.add(Dense(num_classes, activation='softmax'))
model.summary()

model.compile(loss='categorical_crossentropy',
              optimizer="adam",
              metrics=['accuracy'])

# モデルを学習
history = model.fit(x_train, y_train,
                    batch_size=batch_size,
                    epochs=epochs,
                    verbose=1,
                    validation_data=(x_test, y_test))

# テストデータに対して誤差と精度を評価
score = model.evaluate(x_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
```