-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcrossover.py
365 lines (314 loc) · 13.1 KB
/
crossover.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
"""
交叉算子池,交叉算子统一不处理边界信息
1. 模拟二进制交叉
2. 差分算子
3. MOEA/D-M2M里的交叉
"""
import numpy as np
import geatpy as ea
from Nature_DQN import DQN
import random
class Random_cro:
# 随机选择交叉算子
def __init__(self, maxgen, FieldDR, Encoding):
self.name = "Random Selection"
self.FieldDR = FieldDR
self.Encoding = Encoding
# self.Loop = Loop # 是否采用循环方式处理超出边界的变异结果,用不到
self.Opers = [Recsbx(XOVR=0.7, Half=True, n=20), RecM2m(maxgen), DE_rand_1(), DE_rand_2(),
DE_current_to_rand_1(), DE_current_to_rand_2()]
self.n = len(self.Opers)
self.processBound = ProcessBound(FieldDR)
def do(self, OldChrom, r0, neighbourVector, currentGen):
# off = ea.Population(self.Encoding, self.FieldDR, 1)
idx = random.randint(0, self.n)
if idx == 0:
Chrom = self.Opers[0].do(OldChrom, r0, neighbourVector)
elif idx == 1:
Chrom = self.Opers[1].do(OldChrom, r0, neighbourVector, currentGen)
else:
Chrom = self.Opers[idx].do(OldChrom, r0, neighbourVector)
return Chrom
class Best_cro:
def __init__(self, Problem, lambda_, maxgen, Encoding, FieldDR):
self.name = "Best Selection"
self.Problem = Problem
self.lambda_ = lambda_ # 权重向量
self.Encoding = Encoding
self.FieldDR = FieldDR
# self.Opers = [Recsbx(XOVR=0.7, Half=True, n=20), RecM2m(maxgen), DE_rand_1(), DE_rand_2(),
# DE_current_to_rand_1(), DE_current_to_rand_2()]
self.Opers = [Recsbx(XOVR=0.7, Half=True, n=20), RecM2m(maxgen), DE_rand_1(), DE_rand_2()]
self.n = len(self.Opers) # 候选算子个数
self.countOpers = np.zeros(self.n) # 记录算子的选择情况
self.processBound = ProcessBound(FieldDR)
def do(self, OldChrom, r0, neighbourVector, z, currentGen):
# r0是基向量索引,可以对r0:list做变异
off = ea.Population(self.Encoding, self.FieldDR, self.n) # 实例化一个种群对象用于存储用不同算子进化后的个体
off.initChrom(self.n) # 初始化种群染色体矩阵
# 使用n个交叉算子,填充到size=n的种群
# 模拟二进制交叉和M2m里的交叉需要单独处理
off.Chrom[0] = self.Opers[0].do(OldChrom, r0, neighbourVector)
off.Chrom[1] = self.Opers[1].do(OldChrom, r0, neighbourVector, currentGen)
for i in range(2, self.n):
off.Chrom[i] = self.Opers[i].do(OldChrom, r0, neighbourVector) # 执行变异
# 既然要求目标函数值,就需要处理边界
off.Chrom = self.processBound.do(off.Chrom)
# self.processBound.do(off.Chrom) # 处理边界
off.Phen = off.decoding() # 解码
self.Problem.aimFunc(off)
weight = self.lambda_[[r0] * self.n, :]
tche = ea.tcheby(off.ObjV, weight, z)
# self.TechRange = tche.max() - tche.min()
# self.TechRange = tche.mean()
# 选择Tech距离最小的作为最优子代
Techminindex = np.argmin(tche)
# print(Techminindex)
self.countOpers[Techminindex] += 1 # 更新算子选择情况
chrom = np.array([off.Chrom[Techminindex]])
# 确实比应用单个算子效果好
# chrom = np.array([off.Chrom[3]])
return chrom
class RecRL:
"""
用强化学习选择交叉算子
"""
def __init__(self, problem, lambda_, maxgen, NIND) -> None:
self.name = "RecRL"
self.problem = problem
self.lambda_ = lambda_
# self.recOpers = [Recsbx(XOVR=0.7, Half=True, n=20), RecM2m(maxgen), DE_rand_1(), DE_rand_2(),
# DE_current_to_rand_1(), DE_current_to_rand_2()]
self.Opers = [Recsbx(XOVR=0.7, Half=True, n=20), RecM2m(maxgen), DE_rand_1(), DE_rand_2()]
self.n = len(self.Opers)
self.dqn = DQN(problem.Dim + problem.M, self.n)
self.SW = np.zeros((2, NIND * 4))
self.a = 0
self.state = None
self.state_ = None
self.countOpers = np.zeros(self.n) # 统计不同算子的选择频率
def do(self, OldChrom, r0, neighbourVector, currentGen):
"""
r0: 父代在OldChrom里的索引
OldChrom: 变异前的原始矩阵
return: 返回新种群的染色体矩阵
"""
self.gen = currentGen
self.state = np.hstack((OldChrom[r0], self.lambda_[r0]))
if self.dqn.memory_counter > 300:
self.a = self.dqn.choose_action(self.state)
# 如果有算子在滑动窗口里没有记录,那么就选它
for i in range(self.n):
if np.sum(self.SW[0] == i) == 0:
self.a = i
break
else:
self.a = np.random.randint(0, self.n)
# self.a = np.random.randint(0, self.n)
# self.a = 0
self.countOpers[self.a] += 1
if self.a == 0: # 使用模拟二进制交叉
offChrom = self.Opers[0].do(OldChrom, r0, neighbourVector)
elif self.a == 1: # 使用M2m里的交叉
offChrom = self.Opers[1].do(OldChrom, r0, neighbourVector, currentGen)
else: # 使用差分算子
offChrom = self.Opers[self.a].do(OldChrom, r0, neighbourVector)
self.state_ = np.hstack((offChrom[0], self.lambda_[r0]))
return offChrom
def learn(self, r):
"""
更新DQN
:param r: 子代相对于父代适应度的提高率
"""
# 将上次进化加入滑动窗口
self.SW = np.concatenate((self.SW[:, 1:], np.array([[self.a], [r]])), axis=1)
# r1 = np.empty(self.n)
# r2 = np.empty(self.n)
# r3 = np.empty(self.n)
# for i in range(self.n):
# r1[i] = np.mean(self.SW[1, self.SW[0, :] == i])
# r2[i] = np.median(self.SW[1, self.SW[0, :] == i])
# if len(self.SW[1, self.SW[0, :] == i]):
# r3[i] = np.max(self.SW[1, self.SW[0, :] == i])
# else:
# r3[i] = 0
# print("gen", self.gen)
# print("mean", r1)
# print("median", r2)
# print("max", r3)
# self.DQN.store_transition(state,i,r[i],state_)
# reward = np.sum(self.SW[1, self.SW[0, :] == self.a])
reward = np.max(self.SW[1, self.SW[0, :] == self.a])
# reward = 6 - self.a
# print(reward1, reward)
self.dqn.store_transition(self.state, self.a, reward, self.state_)
# 学习,更新DQN
if self.dqn.memory_counter > 200 and random.random() < 0.2:
self.dqn.learn()
"""
模拟二进制交叉 Simulated Binary Crossover geatpy有实现
NewChrom = recsbx(OldChrom, XOVR, Half, n, Parallel)
该函数把输入的OldChrom种群染色体矩阵进行模拟二进制交叉,并返回新的种群染色体矩阵。
交配的一对是有序的,种群的前一半个体和后一半个体进行配对。
若个体数是奇数,则最后一个个体不参与配对。
OldChrom: 待交叉种群染色体矩阵
XOVR: 交叉概率,默认0.7
Half: 默认False,当为True时,每对交叉结果只选择第一个保留。
n : 分布指数,默认20,越大交叉结果越接近双亲
Parallel: False
"""
class Recsbx:
def __init__(self, XOVR=0.9, Half=True, n=20, Parallel=False):
self.name = "OP1"
self.XOVR = XOVR
self.Half = Half
self.n = n
self.Parallel = Parallel
def do(self, OldChrom, r0, neighbourVector):
# pop[r0]作为父代,还需要另外一个父代才能做交叉,所以需要从neighbourVector里随机选一个
r1 = np.random.choice(neighbourVector)
p0 = OldChrom[r0:r0 + 1]
p1 = OldChrom[r1:r1 + 1]
# print(p1)
D = p1.shape[1]
mu = np.random.rand(1, D)
beta = np.zeros((1, D))
idx = mu <= 0.5
beta[idx] = (2 * mu[idx])**(1 / (self.n + 1))
beta[~idx] = (2 - 2 * mu[~idx])**(-1 / (self.n + 1))
beta = beta * np.random.choice([-1, 1], (1, D))
idx = np.random.rand(1, D) < 0.5
beta[idx] = 1
# if random.random() < 0.5:
# beta = 1
if random.random() < 0.5:
off = (p0 + p1) / 2 + beta * (p0 - p1) / 2
else:
off = (p0 + p1) / 2 - beta * (p0 - p1) / 2
# off = np.array([off])
# print(off)
# r1 = np.random.choice(neighbourVector)
# p1 = np.vstack((OldChrom[r0], OldChrom[r1]))
# off = ea.recsbx(p1, self.XOVR, self.Half, self.n, self.Parallel)
return off
class RecM2m:
"""
MOEA/D-M2M里的交叉
"""
def __init__(self, maxgen: int) -> None:
self.name = 'OP2'
self.MaxGen = maxgen
def do(self, OldChrom, r0, neighbourVector, currentGen: int):
N, D = OldChrom.shape
# r1, r2 = np.random.choice(neighbourVector, 2, replace=False) # 不放回的抽取2个
r2 = np.random.choice(neighbourVector, 1, replace=False) # 不放回的抽取
p1 = OldChrom[r0]
p2 = OldChrom[r2]
# D = len(p1) # 决策变量维度
rc = (2 * np.random.rand(1) - 1) * (1 - np.random.rand(1) ** (-(1 - currentGen / (self.MaxGen + N)) ** 0.7))
# print(currentGen, self.MaxGen)
OffDec = p1 + rc * (p1 - p2)
return OffDec
class DE_rand_1:
"""
差分进化,
# vi = xi + F × (xr1 − xr2);
"""
def __init__(self, F=0.5):
self.name = "OP3"
self.F = F # 差分变异缩放因子
def do(self, OldChrom, r0, neighbourVector): # 执行变异
"""
对OldChrom中第r0个个体进行变异
OldChrom: 种群基因型
neighbourVector: 邻域个体,r0是待变异个体,r1 r2从neighbourVector中选择
return: v.shape = 1xD
"""
r1, r2 = neighbourVector[0], neighbourVector[1]
# 变异
x = OldChrom[r0:r0 + 1, :] # 保持x是二维
v = x + self.F * (OldChrom[r1] - OldChrom[r2])
return v
class DE_rand_2:
"""
差分进化,
# vi = xi + F × (xr1 − xr2 + xr3 - xr4);
"""
def __init__(self, F=0.5):
self.name = 'OP4'
self.F = F # 差分变异缩放因子
def do(self, OldChrom, r0, neighbourVector): # 执行变异
# vi = xi + F × (xr1 − xr2 +xr3 -xr4);
# xi基向量索引由传入的r0_or_Xr0确定,差分向量索引r1 r2按照随机方式确定,并尽可能保证互不相等
x = OldChrom[r0:r0 + 1, :]
r1, r2, r3, r4 = neighbourVector[0], neighbourVector[1], neighbourVector[2], neighbourVector[3]
v = x + self.F * (OldChrom[r1] - OldChrom[r2] + OldChrom[r3] - OldChrom[r4])
return v
class DE_current_to_rand_1():
"""
vi=xi+K(xi-xr1)+F(xr2-xr3)
"""
def __init__(self, F=0.5, K=0.6):
self.F = F
self.K = K
self.name = 'DE_current_rand_1'
def do(self, OldChrom, r0, neighbourVector): # 执行变异
# vi=xi+K(xi-xr1)+F(xr2-xr3)
r1, r2, r3 = neighbourVector[0], neighbourVector[1], neighbourVector[2]
# xi基向量索引由传入的r0_or_Xr0确定,差分向量索引r1 r2按照随机方式确定,并尽可能保证互不相等
x = OldChrom[r0:r0 + 1, :]
v = x + self.K * (x - OldChrom[r1]) + self.F * (OldChrom[r2] - OldChrom[r3])
return v
class DE_current_to_rand_2():
"""
# v1=xi+K(xi-xr1)+F(xr2-xr3)+F(xr4-xr5)
"""
def __init__(self, F=0.5, K=0.6):
self.F = F
self.K = K
self.name = 'DE_current_rand_2'
def do(self, OldChrom, r0, neighbourVector): # 执行变异
r1, r2, r3, r4, r5 = neighbourVector[0], neighbourVector[1], neighbourVector[2], neighbourVector[3], neighbourVector[4]
# xi基向量索引由传入的r0_or_Xr0确定,差分向量索引r1 r2按照随机方式确定,并尽可能保证互不相等
x = OldChrom[r0:r0 + 1, :]
xr1 = OldChrom[r1]
xr2 = OldChrom[r2]
xr3 = OldChrom[r3]
xr4 = OldChrom[r4]
xr5 = OldChrom[r5]
v = x + self.K * (x - xr1) + self.F * (xr2 - xr3 + xr4 - xr5)
return v
class ProcessBound:
"""
空操作,仅处理边界信息
"""
def __init__(self, FieldDR) -> None:
self.FieldDR = FieldDR
def do(self, OldChrom):
lb = self.FieldDR[0]
ub = self.FieldDR[1]
# OffChrom = OldChrom.copy()
# 边界处理
OffChrom = np.minimum(np.maximum(OldChrom, lb), ub)
return OffChrom
class RecDirect:
"""
交叉和变异各有一个空操作,变异的空操作会处理边界,交叉的空操作直接返回原种群信息
但是我怀疑空操作会降低算法的性能
"""
def do(self, OldChrom):
return OldChrom
if __name__ == '__main__':
# 检查RecM2m
# p1 = np.array([1,2,3])
# p2 = np.array([2,1,4])
# randd = np.array([0.3])
# currentGen = 6
# MaxGen = 13
# rc = (2*randd-1) * (1-randd ** (-(1-currentGen/MaxGen)**0.7))
# OffDec = p1 + rc*(p1-p2)
# print(OffDec)
Chrom = np.array([[1, 2, 3], [4, 5, 6]])
rec = ea.Recsbx(Half=True)
off = rec.do(Chrom)
print(off)