I'm currently implementing a differential evolution algorithm in python, and all is great when working in lower dimensions, however, when I start increasing the dimensions of the search space the time taken to run the algorithm increases exponentially. After doing a little profiling I found that most of the time is spent in the mutation function, which is as follows,
def _mutate(self, candidate: int) -> np.ndarray:
# r0, r1, & r2 are np.ndarrays of shape (dimension,)
r0, r1, r2 = self._select_samples(candidate)
# mutant is an np.ndarray of shape (dimension,)
mutant = np.copy(self.population[candidate])
j_rand = int(np.random.uniform() * self.dimensions)
for j in range(self.dimensions):
if np.random.uniform() < self.cr or j == j_rand:
# bound the mutant to the search space
mutant[j] = np.clip(r0[j] + self.F * (r1[j] - r2[j]),
self.range[0], self.range[1])
Now, for a population size
of 100
and a dimension
of 20
, the total time taken to run the algorithm is about ~40 seconds, ~20 of those seconds are spent in mutate
.
Now, I have chipped away at this function optimizing it to shave off about ~3 seconds from the previous version.
def _mutate_2(self, candidate: int) -> np.ndarray:
r0, r1, r2 = self._select_samples(candidate)
mutant = np.copy(self.population[candidate])
j_rand = np.random.randint(self.dimensions)
cross_indxs = np.flatnonzero(np.random.rand(self.dimensions) < self.cr)
cross_indxs = np.append(
cross_indxs, [j_rand]) if j_rand not in cross_indxs else cross_indxs
for j in cross_indxs:
mutant[j] = np.clip(r0[j] + self.F * (r1[j] - r2[j]), self.range[0],
self.range[1])
return mutant
But obviously, that is still not sufficient. I am wondering if there may be a trick in numpy
to remove the for loop applying element-wise operations on r0, r1, r2, and mutant
. The catch is that only elements whose indices are in cross_indxs
can be used.
Try this:
mutant[cross_indxs] = (r0[cross_indxs] + self.F[cross_indxs] * (r1[cross_indxs] - r2[cross_indxs])).clip(self.range[0],self.range[1])