Skip to content

Commit f6cc425

Browse files
committed
Added distributions, adam and tests (surprisingly adam works worse than simple gradient descent)
1 parent 8a5f0b5 commit f6cc425

File tree

3 files changed

+201
-10
lines changed

3 files changed

+201
-10
lines changed

.gitlab-ci.yml

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#image: pypy:latest
2+
image: python:latest
3+
4+
stages:
5+
- dependencies
6+
- build
7+
- test
8+
- tooling
9+
10+
variables:
11+
GIT_DEPTH: "1"
12+
PYTHONUSERBASE: "${CI_PROJECT_DIR}/python_user_packages"
13+
14+
dependencies:
15+
tags:
16+
- shared
17+
stage: dependencies
18+
before_script:
19+
- export EXECUTABLE_DEPENDENCIES_DIR=${PYTHONUSERBASE}/bin
20+
- export PATH="$PATH:$EXECUTABLE_DEPENDENCIES_DIR" # don't move into `variables` any of them, it is unordered
21+
script:
22+
- pip3 install --user --upgrade --pre setuptools setuptools_scm
23+
- pip3 install --user --upgrade --pre git+https://github.com/pypa/pip.git git+https://github.com/pypa/wheel.git
24+
- pip3 install --user --upgrade --pre coverage git+https://github.com/coveralls-clients/coveralls-python.git@eba54e4d19e40e3907e5fd516f68e8b4dc9e5a31 git+https://github.com/codecov/codecov-python.git@0743daa83647f12ff31b84d07113d2c24c27b924
25+
- pip3 install --upgrade --user --pre scikit-learn numpy scipy
26+
27+
cache:
28+
key: deps
29+
paths:
30+
- $PYTHONUSERBASE
31+
32+
build:
33+
tags:
34+
- shared
35+
stage: build
36+
37+
before_script:
38+
- export EXECUTABLE_DEPENDENCIES_DIR=${PYTHONUSERBASE}/bin
39+
- export PATH="$PATH:$EXECUTABLE_DEPENDENCIES_DIR" # don't move into `variables` any of them, it is unordered
40+
41+
script:
42+
- python3 setup.py bdist_wheel
43+
- mv ./dist/*.whl ./dist/evostra-0.CI-py3-none-any.whl
44+
- pip3 install --user --upgrade --pre ./dist/evostra-0.CI-py3-none-any.whl
45+
- coverage run --source=evostra ./tests/tests.py
46+
- coverage report -m
47+
- coveralls || true
48+
- codecov || true
49+
50+
cache:
51+
key: deps
52+
paths:
53+
- $PYTHONUSERBASE
54+
55+
artifacts:
56+
paths:
57+
- dist
58+
59+
sast:
60+
stage: tooling
61+
tags:
62+
- shared
63+
image: docker:latest
64+
variables:
65+
DOCKER_DRIVER: overlay2
66+
allow_failure: true
67+
services:
68+
- docker:dind
69+
script:
70+
- docker run --env SAST_CONFIDENCE_LEVEL=5 --volume "$PWD:/code" --volume /var/run/docker.sock:/var/run/docker.sock "registry.gitlab.com/gitlab-org/security-products/sast:latest" /app/bin/run /code
71+
artifacts:
72+
paths:
73+
- gl-sast-report.json
74+
75+
pages:
76+
stage: tooling
77+
tags:
78+
- shared
79+
image: alpine:latest
80+
allow_failure: true
81+
before_script:
82+
- apk update
83+
- apk add doxygen
84+
- apk add ttf-freefont graphviz
85+
script:
86+
- doxygen ./Doxyfile
87+
- mv ./docs/html ./public
88+
artifacts:
89+
paths:
90+
- public
91+
only:
92+
- master

evostra/algorithms/evolution_strategy.py

+67-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from __future__ import print_function
22
import numpy as np
3+
import scipy.stats as st
34
import multiprocessing as mp
5+
from collections.abc import Iterable
46

57
np.random.seed(0)
68

@@ -9,24 +11,66 @@ def worker_process(arg):
911
get_reward_func, weights = arg
1012
return get_reward_func(weights)
1113

14+
class WeightUpdateStrategy:
15+
__slots__ = ("learning_rate",)
16+
def __init__(self, dim, learning_rate):
17+
self.learning_rate = learning_rate
18+
19+
20+
class strategies:
21+
class GD(WeightUpdateStrategy):
22+
def update(self, i, g):
23+
return self.learning_rate * g
24+
25+
26+
class Adam(WeightUpdateStrategy):
27+
__slots__ = ("eps", "beta1", "beta2", "m", "v")
28+
def __init__(self, dim, learning_rate, eps=1e-8, beta1=0.9, beta2=0.999):
29+
super().__init__(dim, learning_rate)
30+
self.eps = eps
31+
self.beta1 = beta1
32+
self.beta2 = beta2
33+
self.m = np.zeros(dim)
34+
self.v = np.zeros(dim)
35+
36+
def update(self, i, g):
37+
self.m[i] = self.beta1 * self.m[i] + (1-self.beta1) * g
38+
self.v[i] = self.beta2 * self.v[i] + (1-self.beta2) * (g**2)
39+
return self.learning_rate * np.sqrt(1-self.beta2) / (1-self.beta1) * self.m[i] / np.sqrt(np.sqrt(self.v[i])+self.eps)
40+
1241

1342
class EvolutionStrategy(object):
1443
def __init__(self, weights, get_reward_func, population_size=50, sigma=0.1, learning_rate=0.03, decay=0.999,
15-
num_threads=1):
16-
44+
num_threads=1, limits=None, printer=None, distributions=None, strategy=None):
45+
if limits is None:
46+
limits = (np.inf, -np.inf)
1747
self.weights = weights
48+
self.limits = limits
1849
self.get_reward = get_reward_func
1950
self.POPULATION_SIZE = population_size
20-
self.SIGMA = sigma
51+
if distributions is None:
52+
distributions = st.norm(loc=0., scale=sigma)
53+
if isinstance(distributions, Iterable):
54+
distributions = list(distributions)
55+
self.SIGMA = np.array([d.std() for d in distributions])
56+
else:
57+
self.SIGMA = distributions.std()
58+
59+
self.distributions = distributions
2160
self.learning_rate = learning_rate
2261
self.decay = decay
2362
self.num_threads = mp.cpu_count() if num_threads == -1 else num_threads
63+
if printer is None:
64+
printer = print
65+
self.printer = printer
66+
if strategy is None:
67+
strategy = strategies.GD
68+
self.strategy = strategy(len(weights), self.learning_rate)
2469

2570
def _get_weights_try(self, w, p):
2671
weights_try = []
2772
for index, i in enumerate(p):
28-
jittered = self.SIGMA * i
29-
weights_try.append(w[index] + jittered)
73+
weights_try.append(w[index] + i)
3074
return weights_try
3175

3276
def get_weights(self):
@@ -36,8 +80,13 @@ def _get_population(self):
3680
population = []
3781
for i in range(self.POPULATION_SIZE):
3882
x = []
39-
for w in self.weights:
40-
x.append(np.random.randn(*w.shape))
83+
if isinstance(self.distributions, Iterable):
84+
for j, w in enumerate(self.weights):
85+
x.append(self.distributions[j].rvs(*w.shape))
86+
else:
87+
for w in self.weights:
88+
x.append(self.distributions.rvs(*w.shape))
89+
4190
population.append(x)
4291
return population
4392

@@ -59,10 +108,17 @@ def _update_weights(self, rewards, population):
59108
if std == 0:
60109
return
61110
rewards = (rewards - rewards.mean()) / std
111+
grad_factor = 1. / (self.POPULATION_SIZE * (self.SIGMA ** 2))
112+
62113
for index, w in enumerate(self.weights):
63114
layer_population = np.array([p[index] for p in population])
64-
update_factor = self.learning_rate / (self.POPULATION_SIZE * self.SIGMA)
65-
self.weights[index] = w + update_factor * np.dot(layer_population.T, rewards).T
115+
corr = np.dot(layer_population.T, rewards).T
116+
117+
if not isinstance(grad_factor, np.ndarray):
118+
g = grad_factor * corr
119+
else:
120+
g = grad_factor[index] * corr
121+
self.weights[index] = w + self.strategy.update(index, g)
66122
self.learning_rate *= self.decay
67123

68124
def run(self, iterations, print_step=10):
@@ -75,7 +131,8 @@ def run(self, iterations, print_step=10):
75131
self._update_weights(rewards, population)
76132

77133
if (iteration + 1) % print_step == 0:
78-
print('iter %d. reward: %f' % (iteration + 1, self.get_reward(self.weights)))
134+
#self.printer('iter %d. reward: %f' % (iteration + 1, self.get_reward(self.weights)))
135+
self.printer('iter %d. reward: %f' % (iteration + 1, self.get_reward(self.weights)), self.weights)
79136
if pool is not None:
80137
pool.close()
81138
pool.join()

tests/tests.py

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env python3
2+
import sys
3+
from pathlib import Path
4+
import unittest
5+
thisDir=Path(__file__).parent.absolute()
6+
sys.path.append(str(thisDir.parent))
7+
8+
import numpy as np
9+
import scipy.stats as st
10+
from evostra import EvolutionStrategy
11+
12+
def modRosenbrockNP(X, a=1, b=100):
13+
return np.sqrt(np.power(a-X[0], 4) + b*np.power(X[1]-np.power(X[0], 2), 2))
14+
15+
def ackleyRosenbrockNp(X, a=20, b=0.2, c=2*np.pi):
16+
return np.real(a*(1-np.exp(-b*np.sqrt(modRosenbrockNP(X, a=0, b=a)/X.shape[0])))-np.exp(np.sum(np.cos(c*X), axis=0)/X.shape[0])+np.exp(1))
17+
18+
19+
bounds = np.array([[0, 10], [-10, 10]])
20+
initialPoint = np.array([10., 5.])
21+
22+
def get_reward(weights):
23+
weights=np.array(weights)
24+
#print(weights)
25+
res = -ackleyRosenbrockNp(weights)
26+
#print(res)
27+
return res
28+
29+
30+
class OptimizersTests(unittest.TestCase):
31+
def testOptimizerSimple(self):
32+
es = EvolutionStrategy(initialPoint, get_reward, population_size=50, sigma=0.5, learning_rate=0.1, decay=1., num_threads=1)
33+
es.run(270, print_step=10)
34+
35+
@unittest.skip
36+
def testOptimizerDistributions(self):
37+
es = EvolutionStrategy(initialPoint, get_reward, population_size=20, learning_rate=0.03, decay=1., num_threads=1, distributions=[st.norm(loc=0., scale=0.1), st.norm(loc=0., scale=0.2)])
38+
es.run(1000, print_step=1)
39+
40+
41+
if __name__ == '__main__':
42+
unittest.main()

0 commit comments

Comments
 (0)