Skip to content

Commit 89f5da1

Browse files
committed
Added distributions and tests
1 parent 8a5f0b5 commit 89f5da1

File tree

3 files changed

+166
-10
lines changed

3 files changed

+166
-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

+33-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

@@ -12,21 +14,33 @@ def worker_process(arg):
1214

1315
class EvolutionStrategy(object):
1416
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-
17+
num_threads=1, limits=None, printer=None, distributions=None):
18+
if limits is None:
19+
limits = (np.inf, -np.inf)
1720
self.weights = weights
21+
self.limits = limits
1822
self.get_reward = get_reward_func
1923
self.POPULATION_SIZE = population_size
20-
self.SIGMA = sigma
24+
if distributions is None:
25+
distributions = st.norm(loc=0., scale=sigma)
26+
if isinstance(distributions, Iterable):
27+
distributions = list(distributions)
28+
self.SIGMA = np.array([d.std() for d in distributions])
29+
else:
30+
self.SIGMA = distributions.std()
31+
32+
self.distributions = distributions
2133
self.learning_rate = learning_rate
2234
self.decay = decay
2335
self.num_threads = mp.cpu_count() if num_threads == -1 else num_threads
36+
if printer is None:
37+
printer = print
38+
self.printer = printer
2439

2540
def _get_weights_try(self, w, p):
2641
weights_try = []
2742
for index, i in enumerate(p):
28-
jittered = self.SIGMA * i
29-
weights_try.append(w[index] + jittered)
43+
weights_try.append(w[index] + i)
3044
return weights_try
3145

3246
def get_weights(self):
@@ -36,8 +50,13 @@ def _get_population(self):
3650
population = []
3751
for i in range(self.POPULATION_SIZE):
3852
x = []
39-
for w in self.weights:
40-
x.append(np.random.randn(*w.shape))
53+
if isinstance(self.distributions, Iterable):
54+
for j, w in enumerate(self.weights):
55+
x.append(self.distributions[j].rvs(*w.shape))
56+
else:
57+
for w in self.weights:
58+
x.append(self.distributions.rvs(*w.shape))
59+
4160
population.append(x)
4261
return population
4362

@@ -59,10 +78,14 @@ def _update_weights(self, rewards, population):
5978
if std == 0:
6079
return
6180
rewards = (rewards - rewards.mean()) / std
81+
update_factor = self.learning_rate / (self.POPULATION_SIZE * self.SIGMA)
82+
6283
for index, w in enumerate(self.weights):
6384
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
85+
if not isinstance(update_factor, np.ndarray):
86+
self.weights[index] = w + update_factor * np.dot(layer_population.T, rewards).T
87+
else:
88+
self.weights[index] = w + update_factor[index] * np.dot(layer_population.T, rewards).T
6689
self.learning_rate *= self.decay
6790

6891
def run(self, iterations, print_step=10):
@@ -75,7 +98,7 @@ def run(self, iterations, print_step=10):
7598
self._update_weights(rewards, population)
7699

77100
if (iteration + 1) % print_step == 0:
78-
print('iter %d. reward: %f' % (iteration + 1, self.get_reward(self.weights)))
101+
self.printer('iter %d. reward: %f' % (iteration + 1, self.get_reward(self.weights)))
79102
if pool is not None:
80103
pool.close()
81104
pool.join()

tests/tests.py

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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([0.7, 0.7])
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=20, sigma=0.1, learning_rate=0.03, decay=1., num_threads=1)
33+
es.run(100, print_step=1)
34+
35+
def testOptimizerDistributions(self):
36+
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)])
37+
es.run(100, print_step=1)
38+
39+
40+
if __name__ == '__main__':
41+
unittest.main()

0 commit comments

Comments
 (0)