做了一个尝试,用深度强化学习policy gradient解决装箱问题。 暂时没有用Q learning。可以考虑试试AC、PPG。也可以试试VRP问题。
当然也不深,特征才用了四个,最简单的dnn,把架子先搭起来。时间的关系,还有很多待优化的,只是一个思想参考。搜了一下 强化学习解决VRP或装箱问题的代码文章不太多,嘿嘿。而且搜到的一些 运用强化学习的思想 是用来优化各种算子的,还是要配合启发式算法用。 我这个是整个流程直接使用强化学习的。好吧,悄悄给自己点个小赞。
action只有两个,拿或者不拿。 状态当前用了四个 装箱前后分别的体积重量占比。是的,状态与货物有关。训练时每次step的时候,会产生(随机选取)新的货物,也会新产生 对应货物的state。
step的时候, 不拿: 计算不拿的奖励,旧货物还原 生成新货物和状态; 拿: 那就计算拿的奖励 episode还未done则 继续生成新货物和状态。
状态与货物有关, 状态还与动作有关。首先随机选货物(后续可以优化成概率依据体积)。动作 原本应该是根据神经网络前向传播的结果来的。 在最初训练的时候,第一个货物 如果不拿,则状态的4个属性值全为0,扔到网络里会玩不起来,所以训练时第一个货物的动作固定为拿。 而在最后的前向流程时,第一个则不合适固定拿,因为此时的货物 体积可能很小。直接从网络里前向计算得到action?计算action需要状态.. 而状态会根据action不同而不同...这就变成了先有鸡还是先有蛋的问题了....(顺便说,在古生物学家眼中 先有蛋。蛋是一种羊膜卵,最早的羊膜卵是3亿年前的林晰产下的。羊膜动物代表林晰是两栖动物中的进化者,其他古老的两栖动物繁殖仍然离不开水,林晰产下了不需要在水中孵化的羊膜卵。而最早的鸡形动物1亿年后才在地球上出现。) 怎么办? 再单独搞个神经网络 训练下初始状态时 拿什么样的货物最好? 暂时不用吧,我使用了当前货物体积 与此批货物体积均值(为避免异常值影响实际用的中位数)的比值, 如果大于均值,则使用3:1的概率选中它。 3:1数字可配。
现有的代码没有考虑物体长宽高的影响(可能容积满足带长度并不能装下),如果要做,可以考虑把trailer中所有物品的长宽高属性、加和的结果 与拖车长宽高的比值 之类的数据扔到网络中,也就是特征多加几个, 然后 判断计算体积超标或重量超标的负反馈时, 增加对长宽高超限的负反馈。
为什么要有最后的前向流程? 训练时,episode快结束时 拿货物可能已经拿了 但已经超体积超重了。 所以前面只能是训练。最后还需要来个训练集上的测试流程。 如果用同一个训练好的网络 处理其他批次的货物,相当于是validation流程了。
一个episode done是因为一个拖车装满了(体积占比0.95以 上)。但是本批次 还有剩余货物的话,env_done(自创的) 不会为True。一批次的货物来了,当成是一个env。 当然,如果按仿真流水线的方法,会有不同的批次陆续到达。 也可以再做另外的批次合并处理。
前向时,经常有很多次action都是不拿,不拿次数太多导致流程很长,总是得不到装满。 有两部分原因,1 是网络没训练好, 2是标签没给好,也就是训练时的奖励设置不够好。
奖励的设置 很重要,作用相当于监督学习的标签了。如果超重或超体积,当然负反馈最大。如果体积占比已经很大了但仍然取货物成功,奖励较大。如果刚开始货物不多 却拿了小体积的东西,奖励较小。 增量阈值0.1 0.3 0.5这些 也要很小心的设置。这里未来都应该做成配置项。
def action_1_reward(self, cur_vol_ratio, cur_weight_ratio, max_vol, before_vol): if cur_vol_ratio > 1 or cur_weight_ratio > 1: return -10 before_vol_ratio = before_vol * 1.0 / max_vol if before_vol_ratio < 0.5 and (cur_vol_ratio - before_vol_ratio) < 0.1: return 1 if before_vol_ratio < 0.5 and (cur_vol_ratio - before_vol_ratio) < 0.3: return 2 if before_vol_ratio < 0.5 and (cur_vol_ratio - before_vol_ratio) < 0.5: return 3 if before_vol_ratio > 0.9: return 5 if before_vol_ratio > 0.8: return 3 return 1 def action_0_reward(self): return 0
tensorflow版本 用的1.14 如果用tensorflow2.x版本跑代码要少量修改一下
分文件写的,这里是一些主要逻辑参考:
关键文件: 网络相关的 pg.py binning环境相关的 binning.py
生成货物:gen_trailer_cargo.py
from collections import namedtuple
import random
import pandas as pd
cargo_weight_range = tuple(range(20, 501, 10))
depth_range = tuple(range(10, 151, 10))
width_range = tuple(range(10, 151, 10))
height_range = tuple(range(20, 81, 10))
# vol_unit = "cm"
trailer_load_bearing = (20000, 40000, 60000)
trailer_vol = (0.001, 0.003, 0.006)
# trailer_vol = (0.05, 0.08, 0.12)
Cargo = namedtuple("Cargo", ("volume", "weight"))
def gen_cargo_df(quantity=50):
weights = random.choices(cargo_weight_range, k=quantity)
depths = random.choices(depth_range, k=quantity)
widths = random.choices(width_range, k=quantity)
heights = random.choices(height_range, k=quantity)
# cm -> cube cm * 1e-9
vols = [d * w * h * 1e-9 for d, w, h in zip(depths, widths, heights)]
df = pd.DataFrame({"id": list(range(1, quantity + 1)),
"weight": weights,
"volume": vols,
"depths": depths,
"widths": widths,
"heights": heights},
index=list(range(1, quantity + 1)))
return df.to_dict(orient="index")
def gen_trailer_df():
# trailer 没有数量限制
df = pd.DataFrame({"id": list(range(1, len(trailer_vol) + 1)),
"load_bearing": trailer_load_bearing,
"trailer_vol": trailer_vol},
index=list(range(1, len(trailer_vol) + 1)))
return df.to_dict(orient="index")
网络相关的 pg.py
import random
import tensorflow as tf
# from keras.models import Sequential
# from keras.layers import Dense
from keras.losses import sparse_categorical_crossentropy
import numpy as np
from service.sol.reinforce.env.binnging import BinningEnv
max_epoch = 60
class PolicyGradient:
def __init__(self, n_features, n_actions, learning_rate=0.01, reward_decay=0.95, action_0_max_times=2000):
self.n_actions = n_actions
self.n_features = n_features
self.lr = learning_rate
self.gamma = reward_decay
self.action_0_max_times = action_0_max_times
self.ep_obs, self.ep_as, self.ep_rs = [], [], []
self.clear_action_0_times()
self.make_network()
def make_network(self):
with tf.name_scope("inputs"):
self.input_ph = tf.placeholder(tf.float32, [None, self.n_features], name="input_")
self.actions_ph = tf.placeholder(tf.int32, [None, self.n_actions], name="actions")