アニーリングマシンで機械学習

chizuchizu
Open In Colab この記事はGoogle Colaboratoryで試すことができます。

目次

  1. 機械学習の例
  • ここではシンプルなLinear Regressionを例に出します
  1. アニーリングに移行してみる
  • 重みをバイナリ変数で扱う方法を紹介します

機械学習の例 | Linear Regression

Linear Regression(線型回帰)は確率を回帰する機械学習モデルです。$x$が入力、$Y$を出力とし、パラメータ$\begin{pmatrix}\beta_0 \ \vdots \ \beta_k \end{pmatrix}$を学習させます。

線型回帰のモデルは以下のような式で定義されます。

$$ Y = \beta_0 x_0 + \cdots + \beta_k x_k = \sum_{i=0}^{k} \beta_{i} x_i $$

In [1]:
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.metrics import roc_auc_score, accuracy_score, mean_squared_error

そしたらLinear Regressionを使って学習を行ってみようと思います。

calc_y関数で定義されるように、各特徴量に対して1か0の重みを掛け、それらの和をtargetとして与えました。 回帰係数が[1, 0, 1, 1]になったら完璧ですね。

In [2]:
def calc_y(x):
    output =  x[0] * 1 + x[1] * 0 + x[2] * 1 + x[3] * 1
    # output = 1 if res >= 2 
    return output


nrows = 100
nfea = 4
# nrows x nfeaの行列を作る
df = np.random.randint(2, size=(nrows, nfea)).astype(bool)
target = []

for i in range(nrows):
    t = int(calc_y(df[i, :]))
    target.append(t)


df = pd.DataFrame(df).astype(int)
df["target"] = target

df.head()
Out [2]:
0 1 2 3 target
0 1 1 0 1 2
1 0 1 0 1 1
2 0 1 0 0 0
3 1 0 1 0 2
4 0 1 0 1 1

sklearnLinearRegressionモジュールで学習を行います。特にパラメータの指定は行いません。

In [3]:
lr = LinearRegression()
lr.fit(df.iloc[:, 0:4], df.iloc[:, 4])
Out [3]:
LinearRegression()

回帰係数はcalc_y関数で定義した通りと、意図した結果が得られました。

In [4]:
# 回帰係数の確認

lr.coef_
Out [4]:
array([1.00000000e+00, 2.22044605e-16, 1.00000000e+00, 1.00000000e+00])

アニーリングに移行してみる

どうやってやるか、端的に説明すると「重みをバイナリ変数にし、二乗誤差をエネルギーとして定式化する」になります。

重みをバイナリ変数にする

バイナリ変数は0-1の二値をとる変数です。

$$ \hat{y} = \beta_0 x_0 + \cdots + \beta_k x_k $$

↑これが ↓こう ($q$は${0, 1}$を取るバイナリ変数です)

$$ \hat{y} = q_0 x_0 + \cdots + q_k x_k $$

線型回帰の重みは任意の実数がとれますが、バイナリ変数を使うことで二値しかとれないことがわかります。

二値じゃ全然役に立たないのでは?

ここで、バイナリ変数にすることで生じる課題を解決する方法を紹介します。しかし、これからの解説では上に書いた0と1の二値の重みを使用するので、興味がある人が読んでください

逆量子化 | より多くの整数値をとる

例えば下のような式で$q_0$を定義したらどうでしょうか。$q_0$は0から10の11個の整数値をとることができます。

$$ q_0 = \sum_{i=0}^9 q_{0, i} $$

実数の値を離散値に丸めることを離散値といいますが、これは離散値をたくさん用意することで実数の値に近づける"逆量子化"を行っています。ちなみに量子化という言葉は学術的に使用されているワードですが、逆量子化は私が命名した言葉なので、この分野における正しいワードかはわかりません。

スケーリング | 整数値だけでなく、小数も扱えるように

逆量子化では整数値しかとれませんでしたが、各バイナリ変数に対して小数の重み付けをしてあげることで"全体として"小数の数を扱うことができます。

ex. 1
逆量子化の例に出した0 ~ 10の重みの範囲を0 ~ 1.0にする

$$ q_0 = \sum_{i=0}^9 0.1 q_{0, i} $$

ex. 2 重み$\begin{pmatrix}w_0 \ \vdots \ w_k \end{pmatrix}$を使う。一般化させただけ。

$$ q_0 = \sum_{i=0}^{k} w_i q_{0, i} $$

二乗誤差で定式化する

誤差関数を二乗誤差としたらアニーリングにおけるエネルギーと誤差関数を同一視することができます。

ターゲット$y$に対して予測値を$\hat{y}$としたとき、エネルギー(誤差関数)$H$は以下のように与えられます。

$$ H = (y - \hat{y}) ^2 $$

$\hat{y}$は重みをバイナリ変数にするチャプターで定義したので、それを代入します。

$$ H = (y - (q_0 x_0 + \cdots + q_k x_k))^2 = (y - \sum_0^k q_i x_i)^2 $$

実装してみる

先程、様々な小数の値をとることのできる逆量子化を紹介しましたが、今回の重みは0か1をとる1つのバイナリ変数のみで実装します。

In [5]:
from amplify import IsingPoly, IsingMatrix, gen_symbols, sum_poly, BinaryPoly
from amplify import Solver, decode_solution, BinarySymbolGenerator
from amplify.client import FixstarsClient
import json

自分はローカルのファイルからトークンを読み込んでいますが、動かすときにはtoken読み込みを消して以下ようにベタ書きしてください。

余談ですが、ローカルから読み込むようにすればトークンの流出が防げたり、トークンを更新してもコードを変える必要がなくなったりします。

client.token = "token"
In [6]:
client = FixstarsClient()

"""token読み込み はじまり"""
token_path = "/home/yuma/.amplify/token.json"

with open(token_path) as f:
    client.token = json.load(f)["AMPLIFY_TOKEN"]

"""token読み込み おわり"""
    
client.parameters.timeout = 100
solver = Solver(client)

学習させる4つのバイナリ変数(0か1をとるイジング変数)を用意します。

In [7]:
gen = BinarySymbolGenerator()
q = gen.array(nfea)

q
Out [7]:
[q_0, q_1, q_2, q_3]

エネルギーの定式化を行います。各行のデータに対して二乗誤差を計算し、エネルギーに足していきます。

In [8]:
f = 0

for i in range(nrows):
    # amplifyのドキュメントでは標準のsum関数は推奨されていません。
    # イテレートする回数が多い時はamplifyのsum_poly関数を使いましょう。
    
    y_hat = sum(
        q[j] * df.iloc[i, j]
        for j in range(nfea)
    )
    
    # 二乗誤差
    f += (df.iloc[i, 4] - y_hat) ** 2
    
    if i == 0:
        print(f"{'dfの1行目':-^30}")
        print(df.iloc[[0], :])
        print(f"{'y_hat, y':-^30}")
        print(f"{y_hat}, {df.iloc[0, 4]}")
        print(f"{'エネルギー':-^30}")
        print(f"{f}")
------------dfの1行目------------
   0  1  2  3  target
0  1  1  0  1       2
-----------y_hat, y-----------
q_0 + q_1 + q_3, 2
------------エネルギー-------------
2 q_0 q_1 + 2 q_0 q_3 + 2 q_1 q_3 - 3 q_0 - 3 q_1 - 3 q_3 + 4

定式化は完了したので、Amplify AEに計算式を投げて解きます。

In [9]:
result = solver.solve(f)
binary_weight = q.decode(result[0].values)

binary_weight
Out [9]:
array([1., 0., 1., 1.])

上の解のエネルギー(誤差関数)はいくらでしょうか! 0です!! 最強のAIができました。

これはyを定義したcalc_yの定義式、Linear Regressionの解とも一致しています。

In [10]:
result[0].energy
Out [10]:
0.0

以上を通してアニーリングを用いて機械学習を行うことができます。

今回は限定的な線型回帰でしたが、この考え方を使うことで幅広い機械学習モデルを実装することが可能になります!

chizuchizu @chizuchizu

このコンテンツにコメントはありません。

empty
ユーザーのみコメントを投稿できます。