suzuzusu日記

(´・ω・`)

PyTorchで最急降下法による最適化

余談

Differentiable Programming(微分可能プログラミング)という捉え方がとても素晴らしいと個人的に思います. 詳しくは以下のブログを見てください.

bonotake.hatenablog.com

最急降下法による最適化

機械学習フレームワークのPyTorchを使って単純に最急降下法で関数の最適化をする. 最適化する関数は10000000次元のsphere関数を使用する.

f:id:suzuzusu:20191008011130p:plain

f:id:suzuzusu:20191008011056p:plain
Sphere Function
出典:Sphere Function

コード

# -*- coding: utf-8 -*-

import torch
import torch.nn as nn
import torch.optim as optim

# 評価関数
def _sphere(x):
    return torch.pow(x, 2).sum()

class Model(nn.Module):
    def __init__(self, dim=100, func=None):
        super(Model, self).__init__()
        if func is None:
            func = _sphere
        self.func = func
        # 設計変数
        self.x = nn.Parameter(torch.rand(dim))

    def forward(self):
        return self.func(self.x)
    
    def vars(self):
        return self.x.detach().numpy()

    def objective(self):
        with torch.no_grad():
            return self.func(self.x).numpy()

# 次元は10000000次元
model = Model(dim=10000000)
optimizer = optim.SGD(model.parameters(), lr=0.1)

N = 100

print('初期の評価値', model.objective())

print('optimization...')
for i in range(N):
    output = model()
    optimizer.zero_grad()
    loss = output
    loss.backward()
    optimizer.step()
    print('\rloss:', loss.item(), end='')
print()

print('評価値', model.objective())

実行結果

初期の評価値 3331740.8
optimization...
loss: 2.1601908358880734e-13
評価値 1.382506e-13

gist

gist.github.com

参考

GPyTorchで多クラス分類

GPyTorchとは

PyTorch上で実装されたガウス過程(GP)のライブラリである. ハイパーパラメータをPyTorchのシステムを使用してGPUなどで最適化できるのが特徴である. GPは逆行列を計算する際に \mathcal{O}(N^ 3) の計算量がかかり,スケーラビリティに難があるが,PyTorchのシステムを使ってそれを補おうとしている.

GPでは尤度を変更することで回帰や二値分類,多クラス分類が可能となる. 今回はアヤメ(iris)データセットを用いて3クラスの多クラス分類をGPyTorchで分類してみる.

コード

# -*- coding: utf-8 -*-

import torch
import gpytorch
import numpy as np

from gpytorch.models import AbstractVariationalGP
from gpytorch.variational import CholeskyVariationalDistribution
from gpytorch.mlls.variational_elbo import VariationalELBO

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split


class GPMultiClassificationModel(AbstractVariationalGP):
    def __init__(self, num_dim=2, grid_bounds=(-10., 10.), grid_size=64):
        variational_distribution = gpytorch.variational.CholeskyVariationalDistribution(
            num_inducing_points=grid_size, batch_size=num_dim
        )
        variational_strategy = gpytorch.variational.AdditiveGridInterpolationVariationalStrategy(
            self, grid_size=grid_size, grid_bounds=[grid_bounds], num_dim=num_dim,
            variational_distribution=variational_distribution, mixing_params=False, sum_output=False
        )
        super().__init__(variational_strategy)
        self.mean_module = gpytorch.means.ConstantMean()
        self.covar_module = gpytorch.kernels.ScaleKernel(gpytorch.kernels.RBFKernel())

    def forward(self, x):
        mean_x = self.mean_module(x)
        covar_x = self.covar_module(x)
        latent_pred = gpytorch.distributions.MultivariateNormal(mean_x, covar_x)
        return latent_pred

device = 'cuda' if torch.cuda.is_available() else 'cpu'

iris = load_iris()
# 特徴量は2次元
# testデータは3割
(train_x, test_x, train_y, test_y) = train_test_split(iris.data[:, [0, 2]].astype(np.float32), iris.target, test_size=0.3, random_state=0,)
# 3クラス分類
n_labels = len(np.unique(train_y))

train_x = torch.from_numpy(train_x).to(device)
train_y = torch.from_numpy(train_y).to(device)
test_x = torch.from_numpy(test_x).to(device)
test_y = torch.from_numpy(test_y).to(device)

model = GPMultiClassificationModel(num_dim=2).to(device)

# 多クラス分類なのでSoftmaxを使用する
likelihood = gpytorch.likelihoods.SoftmaxLikelihood(num_classes=n_labels, num_features=2).to(device)

# 周辺尤度
mll = VariationalELBO(likelihood, model, train_y.numel())

optimizer = torch.optim.Adam(model.parameters(), lr=0.1)

# train
model.train()
likelihood.train()
training_iter = 10000
for i in range(training_iter):
    optimizer.zero_grad()
    output = model(train_x)
    loss = -mll(output, train_y)
    loss.backward()
    print('\rIter %d/%d - Loss: %.3f' % (i + 1, training_iter, loss.item()), end='')
    optimizer.step()
print('')

# test
model.eval()
likelihood.eval()
correct = 0.0
# sample数を64とする
with torch.no_grad(), gpytorch.settings.num_likelihood_samples(64):
    data = test_x
    target = test_y 
    output = likelihood(model(data))
    pred = output.probs.mean(0).argmax(-1)
    correct += pred.eq(target.view_as(pred)).cpu().sum()
    acc = correct.numpy() / float(len(test_x)) * 100.0
    print('Accuracy:', acc)

出力結果

Iter 10000/10000 - Loss: 0.397
Accuracy: 86.66666666666667

gist

gist.github.com

参考

カーネル密度推定とエントロピー

カーネル密度推定をしてエントロピーを計算する方法を忘備録として書いておく.

標準正規分布を例にとって計算してみる. ちなみに理論値は \ln ({\sqrt{2 \pi e}})

import numpy as np
import matplotlib.pyplot as plt

from scipy import integrate
from scipy.stats import norm
from scipy.stats import gaussian_kde

x = norm.rvs(size=10000)
plt.hist(x, bins=50)
plt.title('sampling')
plt.show()

k = gaussian_kde(x)
X = np.linspace(-5, 5, 100)
plt.plot(X, norm().pdf(X), label='norm')
plt.plot(X, k(X), label='kernel estimation')
plt.legend()
plt.title('pdf')
plt.show()

def entropy(pdf):
    def f(x):
        px = pdf(x)
        return - px * np.log(px)
    return f

entropy_k = entropy(k)

print('entropy:')
print('カーネル密度推定', integrate.quad(entropy_k, -5.0, 5.0))
print('理論値', np.log(np.sqrt(2*np.pi*np.e)))

出力結果

entropy:
カーネル密度推定 (1.4426170314750473, 1.2370907729071283e-08)
理論値 1.4189385332046727

f:id:suzuzusu:20191003215447p:plain

f:id:suzuzusu:20191003215500p:plain

gist

gist.github.com

Pykkaでping pong

Pythonでactor model使いたかったので忘備録として

Pykka

Pythonのactor model frameworkです.

インストール方法

pip install pykka

Actorの実装方法

標準のThreadによる実装やgeventなどがある. ドキュメントに gevent の方が一般的に Thread よりも早いことが記載されている.

gevent実装のActorを使用する場合,geventのインストール方法

pip install gevent

通信方法

ask は同期通信,tell は非同期通信となっている. ask の場合は返り値を受け取れるが,tell の場合は None が返り値となる.

ping pong

pingというメッセージに対してpongを返すサンプルを書いてみる.

以下は threading.Thread の実装

from pykka import ThreadingActor

class Actor(ThreadingActor):
    def __init__(self):
        super().__init__()

    def on_receive(self, message):
        print('recieve:', message)
        return 'pong'

actor = Actor.start()
r = actor.tell('ping') # None
print('tell:', r)
r = actor.ask('ping') # pong
print('ask:', r)
actor.stop()

'''
tell: None
recieve: ping
recieve: ping
ask: pong
'''

以下は gevent の実装

from pykka.gevent import GeventActor
class Gactor(GeventActor):
    def __init__(self):
        super().__init__()

    def on_receive(self, message):
        print('recieve:', message)
        return 'pong'

gactor = Gactor.start()
r = gactor.tell('ping')
print('tell:', r)
r = gactor.ask('ping')
print('ask:', r)
gactor.stop()

'''
tell: None
recieve: ping
recieve: ping
ask: pong
'''

参考

混合ガウス過程で多峰な関数を回帰する

忘備録として

混合ガウス過程と多峰な関数

以下のような多峰な関数を回帰する場合は,通常の単峰にフィッティングする回帰では困難なので混合ガウス過程を用いる.

f:id:suzuzusu:20190927182219p:plain
f1,f2関数

f:id:suzuzusu:20190927194603p:plain
多峰な関数

GPclust

GPclust という混合ガウス過程のライブラリが良さそうなので今回はこれを使用する.

以下コード

gist.github.com

結果

f:id:suzuzusu:20190927194641p:plain

結果の図から多峰な関数を回帰できていることが分かる.

参考

SSH in Elixir

ElixirでSSHをする方法を忘備録として書いておく.

iexして以下を実行する

iex(1)> ip = '1.2.3.4'
'1.2.3.4'
iex(2)> port = 22
22
iex(3)> ssh_config = [
...(3)>   user: 'user',
...(3)>   password: 'password1234',
...(3)>   silently_accept_hosts: true
...(3)> ]
[user: 'user', password: 'password1234', silently_accept_hosts: true]
iex(4)> :ssh.start
:ok
iex(5)> :ssh.shell(ip, port, ssh_config)
remote > # login

command実行の場合

iex(1)> ip = '1.2.3.4'
'1.2.3.4'
iex(2)> port = 22
22
iex(3)> ssh_config = [
...(3)>   user: 'user',
...(3)>   password: 'password1234',
...(3)>   silently_accept_hosts: true
...(3)> ]
[user: 'user', password: 'password1234', silently_accept_hosts: true]
iex(4)> :ssh.start
:ok
iex(5)> {:ok, con_ref} = :ssh.connect(ip, port, ssh_config)
{:ok, #PID<0.128.0>}
iex(6)> {ok, ch_id} =  :ssh_connection.session_channel(con_ref, :infinity)
{:ok, 0}
iex(7)> :ssh_connection.exec(con_ref, ch_id, "date", :infinity)
:success
iex(8)> flush
{:ssh_cm, #PID<0.128.0>,
 {:data, 0, 0, "2019年  9月  6日 金曜日 12:36:26 JST\n"}}
{:ssh_cm, #PID<0.128.0>, {:eof, 0}}
{:ssh_cm, #PID<0.128.0>, {:exit_status, 0, 0}}
{:ssh_cm, #PID<0.128.0>, {:closed, 0}}
:ok

参考

www1.erlang.org

Distributed Elixir

Elixirで分散ノードで処理をする方法を忘備録として書いておく.

環境

  • node01
    • 192.168.0.2
  • node02
    • 192.168.0.3

方法

node01で以下のプロセスを立ち上げる

iex --name node01@192.168.0.2 --cookie hoge

node02で以下のプロセスを立ち上げる

iex --name node02@192.168.0.3 --cookie hoge

node01のプロセスから以下を実行して接続する

iex(node02@192.168.0.2)1> Node.connect :"node02@192.168.0.3"
true
iex(node02@192.168.0.2)2> Node.list
[:"node02@192.168.0.3"]

別ノードで実行する方法

iex(node02@192.168.0.2)3> Node.spawn :"node02@192.168.0.3", fn -> IO.inspect Node.self end
:"node02@192.168.0.3"
#PID<11251.119.0>

参考