Skip to content

Instantly share code, notes, and snippets.

@mohzeki222
Created June 5, 2021 03:33
Show Gist options
  • Save mohzeki222/a51171ffa2cac79a73bca044419f4908 to your computer and use it in GitHub Desktop.
Save mohzeki222/a51171ffa2cac79a73bca044419f4908 to your computer and use it in GitHub Desktop.
BOCS.ipynb
Display the source blob
Display the rendered blob
Raw
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"name": "BOCS.ipynb",
"provenance": [],
"authorship_tag": "ABX9TyPXIh2UvjQNXpdoR0Ec545w",
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/gist/mohzeki222/a51171ffa2cac79a73bca044419f4908/bocs.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "tapN4xg_REjs"
},
"source": [
"コスト関数が定式化しにくいような複雑な問題も世の中には存在します。\n",
"そのような場合に適用されるのがベイズ的最適化によるブラックボックス最適化です。\n",
"量子アニーリングのようなコスト関数を定義さえすれば組合せ最適化問題を速やかに解くような解法が存在する場合には、解ける問題のクラスの範囲で、複雑な問題にできるだけ近い代理関数を立てて、その代理関数の最適化を通して、目的とするコスト関数を最適化することを目指します。\n",
"\n",
"まずはいつものようにD-Wave Ocean SDKのインストールはとりあえずしておきましょう。"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "krLb_LMFArxZ",
"outputId": "7dcbdb97-9574-422d-d0f1-dbec65e5c927"
},
"source": [
"pip install dwave-ocean-sdk"
],
"execution_count": 1,
"outputs": [
{
"output_type": "stream",
"text": [
"Requirement already satisfied: dwave-ocean-sdk in /usr/local/lib/python3.7/dist-packages (3.3.0)\n",
"Requirement already satisfied: dwave-neal==0.5.7 in /usr/local/lib/python3.7/dist-packages (from dwave-ocean-sdk) (0.5.7)\n",
"Requirement already satisfied: dwave-inspector==0.2.5 in /usr/local/lib/python3.7/dist-packages (from dwave-ocean-sdk) (0.2.5)\n",
"Requirement already satisfied: dimod==0.9.13 in /usr/local/lib/python3.7/dist-packages (from dwave-ocean-sdk) (0.9.13)\n",
"Requirement already satisfied: dwave-greedy==0.1.2 in /usr/local/lib/python3.7/dist-packages (from dwave-ocean-sdk) (0.1.2)\n",
"Requirement already satisfied: dwave-system==1.4.0 in /usr/local/lib/python3.7/dist-packages (from dwave-ocean-sdk) (1.4.0)\n",
"Requirement already satisfied: pyqubo==1.0.10 in /usr/local/lib/python3.7/dist-packages (from dwave-ocean-sdk) (1.0.10)\n",
"Requirement already satisfied: penaltymodel-mip==0.2.4; platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" in /usr/local/lib/python3.7/dist-packages (from dwave-ocean-sdk) (0.2.4)\n",
"Requirement already satisfied: dwave-tabu==0.3.1 in /usr/local/lib/python3.7/dist-packages (from dwave-ocean-sdk) (0.3.1)\n",
"Requirement already satisfied: penaltymodel-lp==0.1.4 in /usr/local/lib/python3.7/dist-packages (from dwave-ocean-sdk) (0.1.4)\n",
"Requirement already satisfied: penaltymodel==0.16.4 in /usr/local/lib/python3.7/dist-packages (from dwave-ocean-sdk) (0.16.4)\n",
"Requirement already satisfied: dwave-cloud-client==0.8.4 in /usr/local/lib/python3.7/dist-packages (from dwave-ocean-sdk) (0.8.4)\n",
"Requirement already satisfied: dwave-networkx==0.8.8 in /usr/local/lib/python3.7/dist-packages (from dwave-ocean-sdk) (0.8.8)\n",
"Requirement already satisfied: minorminer==0.2.5 in /usr/local/lib/python3.7/dist-packages (from dwave-ocean-sdk) (0.2.5)\n",
"Requirement already satisfied: dwavebinarycsp==0.1.2 in /usr/local/lib/python3.7/dist-packages (from dwave-ocean-sdk) (0.1.2)\n",
"Requirement already satisfied: penaltymodel-cache==0.4.3 in /usr/local/lib/python3.7/dist-packages (from dwave-ocean-sdk) (0.4.3)\n",
"Requirement already satisfied: dwave-hybrid==0.6.1 in /usr/local/lib/python3.7/dist-packages (from dwave-ocean-sdk) (0.6.1)\n",
"Requirement already satisfied: dwave-qbsolv==0.3.2 in /usr/local/lib/python3.7/dist-packages (from dwave-ocean-sdk) (0.3.2)\n",
"Requirement already satisfied: numpy>=1.16.0 in /usr/local/lib/python3.7/dist-packages (from dwave-neal==0.5.7->dwave-ocean-sdk) (1.19.5)\n",
"Requirement already satisfied: Flask>=1.1.1 in /usr/local/lib/python3.7/dist-packages (from dwave-inspector==0.2.5->dwave-ocean-sdk) (1.1.4)\n",
"Requirement already satisfied: importlib-resources>=3.2.0; python_version < \"3.9\" in /usr/local/lib/python3.7/dist-packages (from dwave-inspector==0.2.5->dwave-ocean-sdk) (5.1.3)\n",
"Requirement already satisfied: homebase<2.0.0,>=1.0.0 in /usr/local/lib/python3.7/dist-packages (from dwave-system==1.4.0->dwave-ocean-sdk) (1.0.1)\n",
"Requirement already satisfied: networkx<3.0,>=2.0 in /usr/local/lib/python3.7/dist-packages (from dwave-system==1.4.0->dwave-ocean-sdk) (2.5.1)\n",
"Requirement already satisfied: six>=1.11.0 in /usr/local/lib/python3.7/dist-packages (from pyqubo==1.0.10->dwave-ocean-sdk) (1.15.0)\n",
"Requirement already satisfied: Deprecated>=1.2.10 in /usr/local/lib/python3.7/dist-packages (from pyqubo==1.0.10->dwave-ocean-sdk) (1.2.12)\n",
"Requirement already satisfied: ortools<9.0.0,>=6.6.4659 in /usr/local/lib/python3.7/dist-packages (from penaltymodel-mip==0.2.4; platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\"->dwave-ocean-sdk) (8.2.8710)\n",
"Requirement already satisfied: scipy<2.0.0,>=0.15.0 in /usr/local/lib/python3.7/dist-packages (from penaltymodel-lp==0.1.4->dwave-ocean-sdk) (1.4.1)\n",
"Requirement already satisfied: requests[socks]>=2.18 in /usr/local/lib/python3.7/dist-packages (from dwave-cloud-client==0.8.4->dwave-ocean-sdk) (2.23.0)\n",
"Requirement already satisfied: click>=7.0 in /usr/local/lib/python3.7/dist-packages (from dwave-cloud-client==0.8.4->dwave-ocean-sdk) (7.1.2)\n",
"Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.7/dist-packages (from dwave-cloud-client==0.8.4->dwave-ocean-sdk) (2.8.1)\n",
"Requirement already satisfied: plucky>=0.4.3 in /usr/local/lib/python3.7/dist-packages (from dwave-cloud-client==0.8.4->dwave-ocean-sdk) (0.4.3)\n",
"Requirement already satisfied: decorator<5.0.0,>=4.1.0 in /usr/local/lib/python3.7/dist-packages (from dwave-networkx==0.8.8->dwave-ocean-sdk) (4.4.2)\n",
"Requirement already satisfied: fasteners in /usr/local/lib/python3.7/dist-packages (from minorminer==0.2.5->dwave-ocean-sdk) (0.16.1)\n",
"Requirement already satisfied: itsdangerous<2.0,>=0.24 in /usr/local/lib/python3.7/dist-packages (from Flask>=1.1.1->dwave-inspector==0.2.5->dwave-ocean-sdk) (1.1.0)\n",
"Requirement already satisfied: Werkzeug<2.0,>=0.15 in /usr/local/lib/python3.7/dist-packages (from Flask>=1.1.1->dwave-inspector==0.2.5->dwave-ocean-sdk) (1.0.1)\n",
"Requirement already satisfied: Jinja2<3.0,>=2.10.1 in /usr/local/lib/python3.7/dist-packages (from Flask>=1.1.1->dwave-inspector==0.2.5->dwave-ocean-sdk) (2.11.3)\n",
"Requirement already satisfied: zipp>=0.4; python_version < \"3.8\" in /usr/local/lib/python3.7/dist-packages (from importlib-resources>=3.2.0; python_version < \"3.9\"->dwave-inspector==0.2.5->dwave-ocean-sdk) (3.4.1)\n",
"Requirement already satisfied: wrapt<2,>=1.10 in /usr/local/lib/python3.7/dist-packages (from Deprecated>=1.2.10->pyqubo==1.0.10->dwave-ocean-sdk) (1.12.1)\n",
"Requirement already satisfied: absl-py>=0.11 in /usr/local/lib/python3.7/dist-packages (from ortools<9.0.0,>=6.6.4659->penaltymodel-mip==0.2.4; platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\"->dwave-ocean-sdk) (0.12.0)\n",
"Requirement already satisfied: protobuf>=3.14.0 in /usr/local/lib/python3.7/dist-packages (from ortools<9.0.0,>=6.6.4659->penaltymodel-mip==0.2.4; platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\"->dwave-ocean-sdk) (3.17.2)\n",
"Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests[socks]>=2.18->dwave-cloud-client==0.8.4->dwave-ocean-sdk) (2.10)\n",
"Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests[socks]>=2.18->dwave-cloud-client==0.8.4->dwave-ocean-sdk) (1.24.3)\n",
"Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests[socks]>=2.18->dwave-cloud-client==0.8.4->dwave-ocean-sdk) (2020.12.5)\n",
"Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests[socks]>=2.18->dwave-cloud-client==0.8.4->dwave-ocean-sdk) (3.0.4)\n",
"Requirement already satisfied: PySocks!=1.5.7,>=1.5.6; extra == \"socks\" in /usr/local/lib/python3.7/dist-packages (from requests[socks]>=2.18->dwave-cloud-client==0.8.4->dwave-ocean-sdk) (1.7.1)\n",
"Requirement already satisfied: MarkupSafe>=0.23 in /usr/local/lib/python3.7/dist-packages (from Jinja2<3.0,>=2.10.1->Flask>=1.1.1->dwave-inspector==0.2.5->dwave-ocean-sdk) (2.0.1)\n"
],
"name": "stdout"
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "kqqUjDX1RDkE"
},
"source": [
"ただベイズ的最適化を実行する場合には、繰り返し量子アニーリングマシンによる計算を実行することとなりますので、基本的にはシミュレータで代わりに実行するのが適切です。\n",
"そこでopenJijもインストールしておきましょう。"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "WqpcP6L5GlUG",
"outputId": "7d2aef18-cd21-4db2-8226-4dec30b021cd"
},
"source": [
"pip install openjij"
],
"execution_count": 2,
"outputs": [
{
"output_type": "stream",
"text": [
"Requirement already satisfied: openjij in /usr/local/lib/python3.7/dist-packages (0.3.5)\n",
"Requirement already satisfied: requests in /usr/local/lib/python3.7/dist-packages (from openjij) (2.23.0)\n",
"Requirement already satisfied: numpy>=1.18.4 in /usr/local/lib/python3.7/dist-packages (from openjij) (1.19.5)\n",
"Requirement already satisfied: dimod>=0.9.1 in /usr/local/lib/python3.7/dist-packages (from openjij) (0.9.13)\n",
"Requirement already satisfied: jij-cimod>=1.2.3 in /usr/local/lib/python3.7/dist-packages (from openjij) (1.2.3)\n",
"Requirement already satisfied: scipy in /usr/local/lib/python3.7/dist-packages (from openjij) (1.4.1)\n",
"Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests->openjij) (1.24.3)\n",
"Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests->openjij) (2020.12.5)\n",
"Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests->openjij) (3.0.4)\n",
"Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests->openjij) (2.10)\n"
],
"name": "stdout"
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "RpS34RqPSKbF"
},
"source": [
"相手となる複雑なコスト関数として、適当な乱数によるコスト関数を用意します。\n",
"ここでは簡単な場合として相手となるコスト関数もQUBOの形で書かれているものとします。\n",
"もちろんそんな都合のいいことは実際にはなく、そのコスト関数は2次関数で書くことすらできないような場合もありますので、そのことは注意しておきましょう。\n",
"\n",
"いつものように乱数を利用できるようにnumpyを利用します。"
]
},
{
"cell_type": "code",
"metadata": {
"id": "CSsNhjxUAwL-"
},
"source": [
"import numpy as np\n",
"\n",
"N = 16\n",
"Q = np.random.randn(N**2).reshape(N,N)"
],
"execution_count": 3,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "P4KSjUIGSypw"
},
"source": [
"このQUBOは、今後私たちは知ることはないものとします。\n",
"その意味でブラックボックスであるということになります。\n",
"このQUBOによるブラックボックス関数を次のように定義しておきましょう。"
]
},
{
"cell_type": "code",
"metadata": {
"id": "k_MTM4bNTASV"
},
"source": [
"def BlackBox(x):\n",
" y = np.dot(np.dot(Q,x),x)\n",
" return y"
],
"execution_count": 4,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "QMHcZ_fpTQCF"
},
"source": [
"このブラックボックス関数の中身をもっと複雑なものにしたり、ご自身の問題設定などがあれば、この中に実際に搭載すれば、そのまま量子アニーリングマシンをブラックボックス最適化に利用することができますので遊んでみましょう。"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "nOKW5SMEToeL"
},
"source": [
"どんな手順でブラックボックス最適化をするのか、まずは概要を説明します。\n",
"全く何も知らない状態でコスト関数の最小化を実行するというのは流石に無理ですね。\n",
"断片的な情報として、いくつかのデータを利用することになります。\n",
"そのデータは、試しに$x$を入力した場合に、コスト関数は$y$であったよ、というヒントとなるデータセットです。\n",
"\n",
"1. 今あるデータセットに最も合うQUBOによる**代理関数**を作ります。\n",
"2. 代理関数の最適化を行い、得られた解$x$に対するコスト関数の値$y$を手に入れて、データセットに追加します。\n",
"3. 1-2を繰り返す。"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "37clDVZ6U89Z"
},
"source": [
"そこでデータセットを格納するリストを用意しておきおましょう。"
]
},
{
"cell_type": "code",
"metadata": {
"id": "eXQwNeM2BBgP"
},
"source": [
"Xdata = []\n",
"ydata = []"
],
"execution_count": 5,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "33JyTXorVJjn"
},
"source": [
"次に代理関数を作るプログラムを用意します。\n",
"やり方は非常に簡単な線形回帰による方法を紹介します。\n",
"線形回帰というのは、入力$x$に対して、出力$y$がパラメータと呼ばれる係数の一次関数の形で書けるものの範囲で、最も合うものを探す方法である。\n",
"コスト関数を具体的には次のような形であると考えます。\n",
"\\begin{equation}\n",
"y = a_0 + a_1 x_1 + a_2 x_2 + \\ldots a_N x_N + a_{12}x_1x_2 + a_{13}x_1x_3 + \\ldots + a_{N-1,N} x_{N-1}x_N \n",
"\\end{equation}\n",
"これは実はQUBOの形で書かれていますよね。\n",
"話の単純化のためにベクトルによる表記を利用します。\n",
"まず${\\bf x}$は次のような形を持つものとします。\n",
"\\begin{equation}\n",
"{\\bf x} = (1,x_1,x_2,\\ldots, x_N, x_1x_2,x_1x_3,\\ldots,x_{N-1}x_N)\n",
"\\end{equation}\n",
"これに対して係数ベクトル${\\bf a}$は\n",
"\\begin{equation}\n",
"{\\bf a} = (a_0,a_1,a_2,\\ldots, a_N, a_{12},a_{13},\\ldots,a_{N-1,N})\n",
"\\end{equation}\n",
"とします。\n",
"このようにすれば、\n",
"\\begin{equation}\n",
"y = {\\bf a}^{\\rm T}{\\bf x}\n",
"\\end{equation}\n",
"と簡単に表記することができます。\n",
"この形を線形モデルといい、これで回帰を行うことで線形回帰と呼ばれます。"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "aEVLVEk4XArU"
},
"source": [
"このベクトルの成分の数は、添字が$1$つのものが$N+1$個あり、添字が二つのものが相異なる添字のペア(かつ前の数字の方が小さい)からなるので、$P=N+1+N(N-1)/2$である。\n",
"\n",
"これまでの並び方とは異なるので、\n",
"$x=(x_1,x_2\\ldots,x_N)$を${\\bf x}= (1,x_1,x_2,\\ldots, x_N, x_1x_2,x_1x_3,\\ldots,x_{N-1}x_N)$に直す関数を作ろう。"
]
},
{
"cell_type": "code",
"metadata": {
"id": "kneO9n9dC33a"
},
"source": [
"P = 1+N+N*(N-1)//2\n",
"\n",
"def xmake(x):\n",
" xvec = np.zeros(P)\n",
" xvec[0] = 1\n",
" xvec[1:N+1] = x\n",
" n = 0\n",
" for k in range(N-1):\n",
" for l in range(k+1,N):\n",
" xvec[N+1:N+2+n] = x[k]*x[l]\n",
" n = n + 1\n",
" return xvec"
],
"execution_count": 6,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "wmweDeFbifkz"
},
"source": [
"$x$が入力されることを想定して、1つ1つ成分ごとに代入していくことで作っている。\n",
"0番目は定数である$1$となり、$1$番目から$N$番目までは$x_1,\\ldots,x_N$を代入するために、そのまま$x$を用意する。\n",
"次の$N+1$番目以降は$x_1x_2,\\ldots$といった相異なる添字で積を取ったものを順々に代入していく。そこでカウンター代わりに$n$というものを利用して、何番目の要素となるのかを指定している。"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "PCYouqr2jMGO"
},
"source": [
"次に係数ベクトル${\\bf a}$を手に入れた後に、QUBO行列にする関数を用意しておく。\n",
"入力として${\\bf a}$がそのまま入ったと仮定して関数を自分で作っていく。"
]
},
{
"cell_type": "code",
"metadata": {
"id": "XdldEoRNigUq"
},
"source": [
"def makeQUBO(a):\n",
" QUBO = np.diag(a[1:N+1])\n",
" n = 0\n",
" for k in range(N-1):\n",
" for l in range(k+1,N):\n",
" QUBO[k,l] = a[N+1+n]\n",
" n = n + 1\n",
" return QUBO"
],
"execution_count": 7,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "Tw1vNWMjDOYe"
},
"source": [
"まず定数項である$a_0$を外し、a[1:N+1]に対角項があるので、QUBO行列の対角項にnp.diag(ベクトルから対角行列を作る関数)を利用して埋める。\n",
"その後、a[N+1+n]で残った数字をnを動かしながら、順々にQUBO行列の非対角項に入れていく。\n",
"\n",
"さてそれではブラックボックス最適化を行っていこう。\n",
"まずはデータセットの初期セットとして、乱数によるxとyをNini個用意しておく。"
]
},
{
"cell_type": "code",
"metadata": {
"id": "aIb6CJTlBXJP"
},
"source": [
"Nini = 5\n",
"X = []\n",
"ydata = []\n",
"\n",
"for k in range(Nini):\n",
" x = np.random.randint(0,2,N)\n",
" Xdata.append(x)\n",
"\n",
" y = BlackBox(x)\n",
" ydata.append(y)\n",
" X.append(xmake(x))"
],
"execution_count": 8,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "5h-nVlzbERoU"
},
"source": [
"Xは${\\bf x}= (1,x_1,x_2,\\ldots, x_N, x_1x_2,x_1x_3,\\ldots,x_{N-1}x_N)$という形での入力履歴を残すリストである。\n",
"\n",
"Xdataは$x=(x_1,x_2\\ldots,x_N)$の形で入力履歴を残すリストである。\n",
"\n",
"ydataはその入力に対する出力を残すリストである。"
]
},
{
"cell_type": "code",
"metadata": {
"id": "vzMaysRuCxaO"
},
"source": [
"yvec = np.array(ydata)\n",
"Xmat = np.array(X)"
],
"execution_count": 9,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "PJ5jzfF1E_-i"
},
"source": [
"numpyを利用して計算するために、np.arrayをydataおよびXに対して適用しておく。\n",
"これを実行しておくと、各種の計算が実行できる。\n",
"\n",
"特にここで利用する線形回帰は極々単純なものであり、実際のコスト関数値$y$と${\\bf a}^{\\rm T}{\\bf x}$を合わせるということを考える。\n",
"その際に${\\bf a}$を変えながら調整していく。\n",
"そこで利用されるズレを示す関数(損失関数)を用意して、その最小化をしてズレをできるだけないものを選びましょう。\n",
"\\begin{equation}\n",
"\\min_{\\bf a}\\left\\{ \\sum_{i=1}^n \\left( y_i - {\\bf a}^{\\rm T}{\\bf x}_i \\right)^2 + \\lambda {\\bf a}^{\\rm T} {\\bf a} \\right\\}\n",
"\\end{equation}\n",
"ここで$n$はデータセットに含まれるデータの数です。\n",
"第二項は$L_2$正則化項で過学習を防ぐために導入されます。\n",
"この最小化は、連続的な値を取る${\\bf a}$を変えることで達成できるので微分をして$0$となるということで答えが分かります。\n",
"その答えは以下の通りです。\n",
"\\begin{equation}\n",
"{\\bf a} = \\left( \\sum_{i=1}^n {\\bf x}_i {\\bf x}_i^{\\rm T} + \\lambda I_N \\right)^{-1} \\left( \\sum_{i=1}^n {\\bf x}_i^{\\rm T} y_i \\right)\n",
"\\end{equation}\n",
"ここで$I_N$は$N$次元の単位行列です。\n",
"(ここでは大学一年生程度の線形代数の知識が必要ですので結果のみを利用しても差し支えありません)\n",
"\n",
"他にも二次関数といえば平方完成というのがありましたね。\n",
"そちらでも同じ計算結果が出てきます。\n",
"まずは展開をしましょう。\n",
"\\begin{equation}\n",
"\\sum_{i=1}^n y_i^2 - 2 {\\bf a}^{\\rm T} \\sum_{i=1}^n y_i {\\bf x}_i + {\\bf a}^{\\rm T} \\left( \\sum_{i=1}^n{\\bf x}_i{\\bf x}^{\\rm T}_i + \\lambda I_N \\right) {\\bf a}\n",
"\\end{equation}\n",
"ここで面倒なので$V = \\left( \\sum_{i=1}^n{\\bf x}_i{\\bf x}^{\\rm T}_i + \\lambda I_N \\right)$とおきますと、\n",
"\\begin{equation}\n",
"= \\left( {\\bf a} - V^{-1} \\sum_{i=1}^n y_i {\\bf x}_i \\right)^{\\rm T} \n",
"V\n",
"\\left( {\\bf a} - V^{-1} \\sum_{i=1}^n y_i {\\bf x}_i \\right)\n",
"\\end{equation}\n",
"という結果となります。\n",
"平方完成をして、頂点となるのは、先ほどと同様に\n",
"\\begin{equation}\n",
"{\\bf a} = V^{-1} \\left( \\sum_{i=1}^n {\\bf x}_i^{\\rm T} y_i \\right)\n",
"\\end{equation}\n",
"です。さらに平方完成をすると、放物線の丸みがわかります。$V$はその丸み具合を示しています。\n",
"これは放物線の鋭さとも言えます。\n",
"線形回帰などの学習によりモデルのパラメータを探る場合には、その結果がどれくらい信頼のおけるものかということも重要となります。\n",
"今回の場合には、線形回帰の結果は放物線の頂点である、\n",
"\\begin{equation}\n",
"{\\bf a} = V^{-1} \\left( \\sum_{i=1}^n {\\bf x}_i^{\\rm T} y_i \\right)\n",
"\\end{equation}\n",
"ですが、その頂点付近の丸みから、このパラメータが$V$程度の確かさを持っていると考えます。逆に言えば$V^{-1}$程度の不確かさがあります。\n",
"そこでその不確かさを利用して、その結果を乱数で少しだけ揺らします。\n",
"これにより、もしかしたらそのそのその結果からは少しズレているかもしれないということを考慮します。\n",
"\n"
]
},
{
"cell_type": "code",
"metadata": {
"id": "22nlZ571DIRV"
},
"source": [
"lam = 0.01\n",
"def predict(Xmat,yvec):\n",
" XXinv = np.linalg.inv(np.dot(Xmat.T,Xmat)+lam*np.eye(P))\n",
" ave = np.dot(XXinv,np.dot(Xmat.T,yvec))\n",
" var = 0.5*XXinv\n",
" a = np.random.multivariate_normal(ave, var)\n",
" return a"
],
"execution_count": 10,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "7O2n-1wMK9jb"
},
"source": [
"ここでXXinvで逆行列$V^{-1}$を計算して、aveには線形回帰の結果を乗せ、varには$V^{-1}$から何倍かして結果をどれだけ信頼するかを調整することにします。\n",
"multivariate_normalは(2次関数できまった)多変数ガウス分布に従う乱数を生成する関数です。\n",
"これにより取得した$a$を利用してQUBOを作成します。\n",
"データに合わせ、不安が少しあるので揺らしたQUBOです。\n",
"これまでに出てきたコスト関数の断片的な情報を参考にして推測されるQUBOです。"
]
},
{
"cell_type": "code",
"metadata": {
"id": "1UGID_tHF5qS"
},
"source": [
"a = predict(Xmat,yvec)\n",
"QUBO = makeQUBO(a)"
],
"execution_count": 11,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "cwAumohRLkyU"
},
"source": [
"このQUBOに基づき作成された最適化問題であれば、量子アニーリングを実行することができます。BlackBox()の中身も今回はQUBOにしましたが、もしもそれ以外だとしても、それに非常に近い形のQUBOにしようとするため、どんな複雑な関数を相手にしても、近似的に最適化を実行することができます。\n",
"この営みをブラックボックス最適化といいます。\n",
"\n",
"さてこの手続きを繰り返すコードを実行してみましょう。\n",
"繰り返し計算することになるのでopenJijによるシミュレータがおすすめかもしれません。\n",
"その場合にはQUBO行列をdict形式に直すため、次のような関数を用意しましょう。"
]
},
{
"cell_type": "code",
"metadata": {
"id": "LAaEx8REMI0n"
},
"source": [
"def mat2dict(Q):\n",
" Qdict = {}\n",
" for i in range(N):\n",
" for j in range(N):\n",
" if Q[i][j] != 0.0:\n",
" Qdict[(i,j)] = Q[i][j]\n",
" return Qdict"
],
"execution_count": 12,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "u40AG3_YMO4R"
},
"source": [
"それでは量子アニーリングマシンに何度か計算をしてもらい、\n",
"その結果をデータセットに追加しながら、徐々に近いQUBOを取得して間接的に最適化を実行していきましょう。"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 265
},
"id": "oVJw5hlcEXPC",
"outputId": "d1a13f5e-76ab-4541-f6f6-4b6ce284d5bd"
},
"source": [
"from dwave.system import DWaveCliqueSampler\n",
"from IPython.display import clear_output\n",
"import matplotlib.pyplot as plt\n",
"from openjij import SQASampler\n",
"import copy\n",
"\n",
"Nsample = 10\n",
"sampler = SQASampler()\n",
"\n",
"#token = '**'\n",
"#endpoint = 'https://cloud.dwavesys.com/sapi/'\n",
"#sampler = DWaveCliqueSampler(solver='Advantage_system1.1', token=token)\n",
"\n",
"Tall = 100\n",
"ymin = []\n",
"for time in range(Tall):\n",
" Qdict = mat2dict(QUBO)\n",
" sampleset = sampler.sample_qubo(Qdict,num_reads=Nsample,num_sweeps=10)\n",
" for k in range(N):\n",
" x[k] = sampleset.record[0][0][k]\n",
"\n",
" Xdata.append(copy.deepcopy(x))\n",
" y = BlackBox(x)\n",
" ymin.append(np.min([y,np.min(ydata)]))\n",
" ydata.append(y)\n",
" X.append(xmake(x))\n",
" yvec = np.array(ydata)\n",
" Xmat = np.array(X)\n",
" a = predict(Xmat,yvec)\n",
" QUBO = makeQUBO(a)\n",
" \n",
" clear_output(True)\n",
" plt.plot(ymin) \n",
" plt.show()\n"
],
"execution_count": 13,
"outputs": [
{
"output_type": "display_data",
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"tags": [],
"needs_background": "light"
}
}
]
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment