说明
遗传算法,或者说其他的优化算法,本质上都是在无穷多的可能里找到可行解,在可行的时间内。所以,算法需要有一定的「方向」。这种方向或者是算法本身自带的,或者是通过指定范围减少的(约束),通常来说是二者的结合。我觉得就目前的情况来看(从应用的角度),最主要的就是表达约束。以下梳理一下在geatpy里表达几种约束的方式。
内容
我把约束分为四种类型:
- 1 变量范围约束。
- 2 等号约束。
- 3 不等号约束。
- 4 例外约束。
以商店配货的场景进行举例。
1 变量范围约束
一家门店通常只能在一天的某个时段收获,并且有时候不允许某些类型车辆送货
我将时间分为 0~144个slot, 根据geatpy提供的问题类定义,里面提供了lb(lower bound)
和up(upper bound)
所以这部分可以这么写。每个lb, 和ub元素是一一对应的,这就减少了不必要的搜索。
# 决策变量下界
lb = [72,0,0, # s1.sku1:slot1, qty,vehicle
122,0,0, # s1.sku1:slot2, qty,vehicle
72,0,0, # s1.sku2:slot1, qty,vehicle
122,0,0, # s1.sku2:slot2, qty,vehicle
120,0,0, # s2.sku1:slot1, qty,vehicle
120,0,0, # s2.sku2:slot1, qty,vehicle
120,0,0, # s2.sku3:slot1, qty,vehicle
]
# 决策变量上界
ub = [121,11,5, # s1.sku1:slot1, qty,vehicle
143,11,5, # s1.sku1:slot2, qty,vehicle
121,16,5, # s1.sku2:slot1, qty,vehicle
143,16,5, # s1.sku2:slot1, qty,vehicle
143,9,5, # s2.sku1:slot1, qty,vehicle
143,5,5, # s2.sku2:slot1, qty,vehicle
143,5,5, # s2.sku3:slot1, qty,vehicle
2 等号约束
方案中总的配送量要恰好等于需求量
在之前的例子中,将一个方案按照门店和收货次数平展为一个很长的行向量。每个行向量是按照某个元素模板([slot, qty, vehicle])的规律重复叠加的。下面按照规律取出了各个门店,各个时隙的配送量。
# s1有两个sku在两个slot(两次收货)
group_num = 3 # 每组元素的值
s1_sku1_slot1 = Vars[:, [1 + 0*group_num]] # 取出第一列得到所有个体的x1组成的列向量
s1_sku1_slot2 = Vars[:, [1 + 1*group_num]]
s1_sku2_slot1 = Vars[:, [1 + 2*group_num]]
s1_sku2_slot2 = Vars[:, [1 + 3*group_num]]
s2_sku1_slot1 = Vars[:, [1 + 4*group_num]]
s2_sku2_slot1 = Vars[:, [1 + 5*group_num]]
s2_sku3_slot1 = Vars[:, [1 + 6*group_num]]
算法在搜索方案时是按照随机的方法前进的,有可能会出现不符合实际要求的情况,现在我们要约束配送量=需求量。
通过np.abs(x1 + x2 + x3... - demand)
的方式进行等号约束。
cv1_s1_sku1 = np.abs(s1_sku1_slot1+s1_sku1_slot2 -11)
cv2_s1_sku2 = np.abs(s1_sku2_slot1+s1_sku2_slot2 -16)
cv3_s2_sku1 = np.abs(s2_sku1_slot1 -9)
cv4_s2_sku2 = np.abs(s2_sku2_slot1 -5)
cv5_s2_sku3 = np.abs(s2_sku3_slot1 -5)
我从算法运行的种群中抽出一个Phen来看:
population.Phen[:,[13] ]
---
array([[9.],
[9.],
[9.],
[9.],
[9.],
[9.],
[9.],
[9.],
...
可以看到种群的进化完全按照约束来的,这里要注意矩阵的形状。每个约束值是一个向量而不是标量。我们看看约束的实际表达
s2_sku1_slot1 = population.Phen[:,[13] ]
cv3_s2_sku1 = np.abs(s2_sku1_slot1 -9)
cv3_s2_sku1
array([[0.],
[0.],
[0.],
[0.],
[0.],
[0.],
...
注意里面的值是0(对应的逻辑意义是False),可以和后面在指定例外的时候对比一下。
3 不等号约束
方案中每辆车的配货要小于车辆本身的最大载荷
这段我还没写,基本上按照这个方程x1m1 + x2m2 + M1 + M2 - M <=0
的方式去写。
cv2 = x1 * m1 + x2 * m2 + M1 + M2 - M
4 例外约束
方案中,存在每个门店某个时隙必须为同一辆车配送;或者更复杂一些,一辆车带了n家的货物,要确保这些货物的确可以在限定的时间内送完。
这里用一个简单的例外约束举例:第一家店不允许用012,4,5车。这个本来是可以在前面用变量范围约束的,但有时候我们也可以临时的添加,例如某个司机态度不好,门店不想让这个司机送货。
这个变量是我直接获取的一维列向量,要注意的是,最后处理完我要给每个标量升维。(这个主要由这个包里面的一些东西限定,我没去深究)
v1 = Vars[:, 2 + 0*group_num]
test_exd = (v1 <5).reshape(-1, 1)
实际数据
test_exd = population.Phen[:,2]
test_exd
---
array([2., 2., 2., 2., 2., 2., 2., 2.,...
test_exd <5
---
array([ True, True, True, True, True, True, True, True, True,
True, True, True, True, True, True, True, True, True,
True, True, True...
# 升维
(test_exd<5).reshape(-1,1)
---
array([[ True],
[ True],
[ True],
[ True],
约束写完以后水平叠加叠加
pop.CV = np.hstack([cv1_s1_sku1,cv2_s1_sku2,cv3_s2_sku1,cv4_s2_sku2,cv5_s2_sku3,test_exd]) # 将约束汇聚
举例:将两个约束的结果横向连起来。估计算法会对每个元素(一维向量)进行加总之类的。some_mat.sum(axis=1)
np.hstack([cv3_s2_sku1, cv3_s2_sku1])
array([[0., 0.],
[0., 0.],
[0., 0.],
[0., 0.],
...
可以看到约束后,车号都是5了。