QoreSDKを使った回帰タスク

これは[深層学習以外の機械学習と応用技術 by QuantumCore Advent Calendar 2019](https://qiita.com/advent-calendar/2019/quantumcore_reservoir)の9日目の記事です。

# はじめに

こんばんは、[@saitoxu](https://twitter.com/saitoxu)です。
本稿では[QoreSDK](https://qiita.com/sk1010k/items/d8ca3332e6a1f1f939a1#qoresdk%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)を使った簡単な回帰タスクの方法を説明します。
ちなみに自分は時系列解析周りの知識はあまりないので、何か間違ってること等ありましたら(優しく)コメントで教えてください:bow:

# QoreSDKを使った回帰タスク

今回は正弦波(sine)の予測をしてみます。
次のようにsin関数から生成される時系列データの一部を学習させて、未来の時刻のsineの値を予測させるタスクです。

## データの準備

最初にsine波のデータを準備します。

```py
# sine波の長さ
t_length = 1000
# 分割するときのサイズ
t_width = 400

train_x = np.arange(t_length)
# 平均がsin(x), 分散0.1の正規分布に従う配列を生成
train_y = np.random.normal(np.sin(2 * np.pi * train_x / 100), 0.1)
x = []
y = []
for i in range(len(train_x) - t_width):
    x.append(train_y[i:i + t_width])
    y.append(train_y[i + t_width])

x = np.array(x)
y = np.array(y)

# 学習データとテストデータに分割
X_train = x[0:((t_length - t_width) // 2)]
X_test = x[((t_length - t_width) // 2):]
y_train = y[0:((t_length - t_width) // 2)]
y_test = y[((t_length - t_width) // 2):]

print(X_train.shape) # (300, 400)
print(X_test.shape) # (300, 400)
```

ここでは以下のことをやってます。

1. 長さ`t_length`のsine波を生成
2. それを長さ`t_width`の部分波に分割
3. `t_length - t_width`個の部分波ができるので、それを学習データとテストデータに分割

`plt.plot(X_train[0])`などとすると、次のような時系列データが生成されていることが分かります。
また、`y_train`には`X_train`の最後の時刻$t$の次の時刻$t+1$の値が入っています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17731/c70cccc9-80e4-fe7d-160b-af8b0ea2aaae.png)

## 前処理

前処理、と言っても特段することはないのですが、QoreSDKの学習関数の入力が`(サンプル数, 時系列長, 変量数)`なのでそのようにデータを変換します。

```py
# 2次元配列だったのを3次元配列にしてるだけ
X_train = X_train.reshape([(t_length - t_width) // 2, t_width, 1])
X_test = X_test.reshape([(t_length - t_width) // 2, t_width, 1])
```

[不整脈検出のサンプル](https://qiita.com/sk1010k/items/d8ca3332e6a1f1f939a1#%E4%B8%8D%E6%95%B4%E8%84%88%E6%A4%9C%E5%87%BA)では[qore_sdk.featurizerモジュール](https://qcore-info.github.io/advent-calendar-2019/index.html#module-qore_sdk.featurizer)と[qore_sdk.utilsモジュール](https://qcore-info.github.io/advent-calendar-2019/index.html#module-qore_sdk.utils)を使って入力データに変換していましたが、今回のタスクで使ってみたところあまり良い精度が出なかったので([後述します](https://qiita.com/saitoxu/items/93cf462b4256cb4cea4d#featurizer%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9F%E3%81%8C%E3%81%86%E3%81%BE%E3%81%8F%E3%81%84%E3%81%8B%E3%81%AA%E3%81%8B%E3%81%A3%E3%81%9F))、上記のような変換としています。

## 学習

回帰タスクなので、[qore_sdk.clientモジュール](https://qcore-info.github.io/advent-calendar-2019/index.html#module-qore_sdk.client)の`regression_train`関数を使います。

```py
client = WebQoreClient('username', 'password', endpoint='endpoint')
res = client.regression_train(X_train, y_train)
print(res) # {'res': 'ok', 'train_time': 6.9450061321258545}
```

`username`, `password`, `endpoint`はご自分のものに変更してください。
だいたい10秒もあれば完了します。

## 予測と評価

学習したモデルを使って、テスト用データの予測をしてみます。

```py
res = client.regression_predict(X_test)
```

予測値と正解データをプロットしてみると以下のようになります。

```py
plt.plot(res['Y'], figure=plt.figure(figsize=(12.8, 4.8))) # 予測値(青)
plt.plot(y_test) # 正解(オレンジ)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17731/fb381b27-a7b1-58c7-dedd-ece928c78b4e.png)

だいたい正しく予測できているのが分かりました。
ちなみに次の関数を用意して、テストデータ以降の値も予測してみます。

```py
# n個先の時刻の予測を行う
def predict(n):
    # X_test[-1]が最後の時刻のデータ
    data = X_test[-1].reshape(t_width)
    for _ in range(n):
        res = client.regression_predict([data[len(data) - t_width:]])
        data = np.append(data, res['Y'][0])
    return data[len(data) - n:]
```

実際に使ってみましょう。
100個先の時刻まで予測して結果をプロットしてみます。

```py
result = predict(100)
plt.plot(result)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17731/f52e3457-3682-6f94-b5fc-70ac476cbee6.png)

一応sineっぽい形状になりました。
ちなみにこれはQoreのAPIサーバに負荷をかけることになるので、$n$は小さな値で試してください :bow: 

## Featurizerを使ったがうまくいかなかった

次のようにQoreのFeaturizerを使ってデータを入力用の配列に変換したところ、今回のタスクではあまりうまく学習されませんでした。

```py
width = 144
stepsize = 36
n_filters = 40

# ここで(サンプル数, 時系列長, 小時系列長)の配列を作成
X_train = sliding_window(X_train, width, stepsize)
X_test = sliding_window(X_test, width, stepsize)
print('X_train.shape:', X_train.shape)
print('X_test.shape:', X_test.shape)

# (サンプル数, 時系列長, 変量数)の配列に変換(Qoreへの入力となる)
featurizer = Featurizer(n_filters)
X_train = featurizer.featurize(X_train, axis=2)
X_test = featurizer.featurize(X_test, axis=2)
print(X_train.shape)
print(X_test.shape)
```

詳細は分かりませんが、Featurizerは中では周波数分解をやってるそうなので、今回のタスクのようなsine波は分割のしようがないためうまく学習されなかったのかなと思いました。

# おわりに

QoreSDKを使って回帰タスクを行ってみました。
今後、Qoreを使って株価予測などの回帰問題をやってみたいという方の参考になれば幸いです。
今回使ったノートブックは下記のリポジトリに置いてるので、合わせてどうぞ。

[saitoxu/qcore-advent-calendar](https://github.com/saitoxu/qcore-advent-calendar)

# 参考

* [リザーバコンピューティングの世界 ~Qoreとともに~](https://qiita.com/ryoppippi/items/f607c8828238094eade0)
* [QoreSDKの紹介とQoreで不整脈検出](https://qiita.com/sk1010k/items/d8ca3332e6a1f1f939a1)
* [QoreSDK 0.1.0 documentation](https://qcore-info.github.io/advent-calendar-2019/index.html#document-index)