Passed
Push — master ( 5fc565...530393 )
by Simon
01:33
created

GridSearchOptimizer.__init__()   A

Complexity

Conditions 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nop 4
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
# Author: ...
2
# Email: ...
3
# License: MIT License
4
5
import numpy as np
6
7
try:
8
    from fractions import gcd
9
except:
10
    from math import gcd
11
12
from ..base_optimizer import BaseOptimizer
13
from ...search import Search
14
15
16
class GridSearchOptimizer(BaseOptimizer, Search):
17
    name = "Grid Search"
18
    _name_ = "grid_search"
19
    __name__ = "GridSearchOptimizer"
20
21
    optimizer_type = "global"
22
    computationally_expensive = False
23
24
    def __init__(self, *args, step_size=1, **kwargs):
25
        super().__init__(*args, **kwargs)
26
        self.initial_position = np.zeros((self.conv.n_dimensions,), dtype=int)
27
        # current position mapped in [|0,search_space_size-1|] (1D)
28
        self.high_dim_pointer = 0
29
        # direction is a generator of our search space (prime with search_space_size)
30
        self.direction = None
31
        # step_size describes how many steps of size direction are jumped at each iteration
32
        self.step_size = step_size
33
34
    def get_direction(self):
35
        """
36
        Aim here is to generate a prime number of the search space size we call our direction.
37
        As direction is prime with the search space size, we know it is
38
        a generator of Z/(search_space_size*Z).
39
        """
40
        n_dims = self.conv.n_dimensions
41
        search_space_size = self.conv.search_space_size
42
43
        # Find prime number near search_space_size ** (1/n_dims)
44
        dim_root = int(np.round(np.power(search_space_size, 1 / n_dims)))
45
        is_prime = False
46
47
        while not is_prime:
48
            if gcd(int(search_space_size), int(dim_root)) == 1:
49
                is_prime = True
50
            else:
51
                dim_root += -1
52
53
        return dim_root
54
55
    def grid_move(self):
56
        """
57
        We convert our pointer of Z/(search_space_size * Z) in a position of our search space.
58
        This algorithm uses a bijection from Z/(search_space_size * Z) -> (Z/dim_1*Z)x...x(Z/dim_n*Z).
59
        """
60
        new_pos = []
61
        dim_sizes = self.conv.dim_sizes
62
        pointer = self.high_dim_pointer
63
64
        # The coordinate of our new position for each dimension is
65
        # the quotient of the pointer by the product of remaining dimensions
66
        # Describes a bijection from Z/search_space_size*Z -> (Z/dim_1*Z)x...x(Z/dim_n*Z)
67
        for dim in range(len(dim_sizes) - 1):
68
            new_pos.append(pointer // np.prod(dim_sizes[dim + 1 :]) % dim_sizes[dim])
69
            pointer = pointer % np.prod(dim_sizes[dim + 1 :])
70
        new_pos.append(pointer)
71
        return np.array(new_pos)
72
73
    @BaseOptimizer.track_new_pos
74
    def iterate(self):
75
        # If this is the first iteration:
76
        # Generate the direction and return initial_position
77
        if self.direction is None:
78
            self.direction = self.get_direction()
79
            return self.initial_position
80
81
        # If this is not the first iteration:
82
        # Update high_dim_pointer by taking a step of size step_size * direction.
83
84
        # Multiple passes are needed in order to observe the entire search space
85
        # depending on the step_size parameter.
86
        _, current_pass = self.nth_iter, self.high_dim_pointer % self.step_size
87
        current_pass_finished = (
88
            (self.nth_iter + 1) * self.step_size // self.conv.search_space_size
89
            > self.nth_iter * self.step_size // self.conv.search_space_size
90
        )
91
        # Begin the next pass if current is finished.
92
        if current_pass_finished:
93
            self.high_dim_pointer = current_pass + 1
94
        else:
95
            # Otherwise update pointer in Z/(search_space_size*Z)
96
            # using the prime step direction and step_size.
97
            self.high_dim_pointer = (
98
                self.high_dim_pointer + self.step_size * self.direction
99
            ) % self.conv.search_space_size
100
101
        # Compute corresponding position in our search space.
102
103
        pos_new = self.grid_move()
104
        pos_new = self.conv2pos(pos_new)
105
        return pos_new
106
107
    @BaseOptimizer.track_new_score
108
    def evaluate(self, score_new):
109
        BaseOptimizer.evaluate(self, score_new)
110