Skip to content

Commit

Permalink
init commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Xiong5Heng committed Oct 8, 2024
1 parent 422189e commit b7ab5f9
Show file tree
Hide file tree
Showing 107 changed files with 16,119 additions and 2 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/logs/
/.idea/
*.pyc
*.csv
logs
log
.vscode
83 changes: 81 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,81 @@
# GOPT
The code is under preparation and is coming soon.
<h2 align="center">
<b>GOPT: Generalizable Online 3D Bin Packing via Transformer-based Deep Reinforcement Learning</b>

<b><i>RA-L 2024 (Accepted)</i></b>

<div align="center">
<a href="https://ieeexplore.ieee.org/abstract/document/10694688" target="_blank">
<img src="https://img.shields.io/badge/ieee-%2300629B.svg?&style=for-the-badge&logo=ieee&logoColor=white"></a>
<a href="https://arxiv.org/abs/2409.05344" target="_blank">
<img src="https://img.shields.io/badge/arxiv-%23B31B1B.svg?&style=for-the-badge&logo=arxiv&logoColor=white" alt="Paper arXiv"></a>
</div>

</h2>

If you have any questions, feel free to contact me by [email protected].

## Introduction
Robotic object packing has broad practical applications in the logistics and automation industry, often formulated by researchers as the online 3D Bin Packing Problem (3D-BPP). However, existing DRL-based methods primarily focus on enhancing performance in limited packing environments while neglecting the ability to generalize across multiple environments characterized by different bin dimensions. To this end, we propose GOPT, a generalizable online 3D Bin Packing approach via Transformer-based deep reinforcement learning (DRL). First, we design a Placement Generator module to yield finite subspaces as placement candidates and the representation of the bin. Second, we propose a Packing Transformer, which fuses the features of the items and bin, to identify the spatial correlation between the item to be packed and available sub-spaces within the bin. Coupling these two components enables GOPT’s ability to perform inference on bins of varying dimensions.

![overview](./images/overview.png)


## Installation
This code has been tested on Ubuntu 20.04 with Cuda 12.1, Python3.9 and Pytorch 2.1.0.

```
git clone https://github.com/Xiong5Heng/GOPT.git
cd GOPT
conda create -n GOPT python=3.9
conda activate GOPT
# install pytorch
conda install pytorch==2.1.0 torchvision==0.16.0 torchaudio==2.1.0 pytorch-cuda=12.1 -c pytorch -c nvidia
# install other dependencies
pip install -r requirements.txt
```

## Training
The dataset is generated on the fly, so you can directly train the model by running the following command.

```bash
python train.py --config cfg/config.yaml --device 0
```

If you do not use the default dataset (the bin is 10x10x10), you can modify the tag `env` in `cfg/config.yaml` file to specify the bin size and the number of items.
Note that most hyperparameters are in the `cfg/config.yaml` file, you can modify them to fit your needs.


## Evaluation

```bash
python eval.py --config cfg/config.yaml --device 0 --ckp /path/to/checkpoint.pth
```

If you want to visualize the packing process, you can add the `--render` flag.
```bash
python eval.py --config cfg/config.yaml --device 0 --ckp /path/to/checkpoint.pth --render
```

## Demo
<!-- ![demo](./images/demo.gif) -->
<div align="center">
<img src="./images/demo.gif" alt="A simple demo" width="400">
</div>

## Citation
If you find this work useful, please consider citing:
```
@article{xiong2024gopt,
title={GOPT: Generalizable Online 3D Bin Packing via Transformer-based Deep Reinforcement Learning},
author={Xiong, Heng and Guo, Changrong and Peng, Jian and Ding, Kai and Chen, Wenjie and Qiu, Xuchong and Bai, Long and Xu, Jianfeng},
journal={IEEE Robotics and Automation Letters},
year={2024},
publisher={IEEE}
}
```

## License
This source code is released only for academic use. Please do not use it for commercial purposes without authorization of the author.
59 changes: 59 additions & 0 deletions arguments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import os
curr_path = os.path.dirname(os.path.abspath(__file__))

import argparse

from omegaconf import OmegaConf


def get_args():
parser = argparse.ArgumentParser()
parser.add_argument('--config', type=str, default="cfg/config.yaml")
parser.add_argument('--ckp', type=str, default=None,
help="Path to the model to be tested")
parser.add_argument('--no-cuda', action='store_true',
help='Cuda will be enabled by default')
parser.add_argument('--device', type=int, default=0,
help='Which GPU will be called')
parser.add_argument('--test-episode', type=int, default=1000,
help='Number of episodes for evaluation')
parser.add_argument('--render', action='store_true',
help='Render the environment while testing')

args = parser.parse_args()

try:
args.config = os.path.join(curr_path, args.config)
cfg = OmegaConf.load(args.config)
except FileNotFoundError:
print("No configuration file found")

box_small = int(max(cfg.env.container_size) / 10)
box_big = int(max(cfg.env.container_size) / 2)
# box_range = (5, 5, 5, 25, 25, 25)
box_range = (box_small, box_small, box_small, box_big, box_big, box_big)

if cfg.get("env.step") is not None:
step = cfg.env.step
else:
step = box_small

box_size_set = []
for i in range(box_range[0], box_range[3] + 1, step):
for j in range(box_range[1], box_range[4] + 1, step):
for k in range(box_range[2], box_range[5] + 1, step):
box_size_set.append((i, j, k))

cfg.env.box_small = box_small
cfg.env.box_big = box_big
cfg.env.box_size_set = box_size_set
cfg.cuda = not args.no_cuda

cfg = OmegaConf.merge(cfg, vars(args))

return cfg


if __name__ == "__main__":
args = get_args()
print(args.train.reward_type)
49 changes: 49 additions & 0 deletions cfg/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# configuration file for training

seed: 5
cuda_deterministic: True
log_interval: 10 # How often to print training logs

env:
id: OnlinePack-v1 # env name OnlinePack-v1, PCT-v0
scheme: EMS # the scheme of generating candidate map: heightmap, EP, FC
rot: True
box_type: random # random, cut
container_size: [10, 10, 10]
step:
k_placement: 80 # number of candidate placements


train:
algo: PPO
clip_param: 0.3
num_processes: 2 # the number of subprogresses, if debug, set to 1
num_steps: 5
epoch: 1000
last_epoch: 200
batch_size: 128
step_per_epoch: 40000 # 2**15
repeat_per_collect: 1
gae_lambda: 0.96
reward_type: # optional: "terminal", None
gamma: 1 # discount factor for rewards (default: 1)

opt: # optimizer
optimizer: Adam # optimizer: Adam, RMSprop
lr: 7e-5 # learning rate (RMSprop7e-4, 1e-6, Adam7e-5)
lr_decay: True # use a linear schedule on the learning rate
eps: 1e-5 # epsilon (default: 1e-5)
alpha: 0.99 # RMSprop alpha (default: 0.99)

loss:
entropy: 0.001 # entropy term coefficient (default: 0.01)
value: 0.5 # value loss coefficient (default: 0.5)

model:
padding_mask: False # padding mask
embed_dim: 128
heads: 1
num_layers: 3
forward_expansion: 2
dropout: 0

46 changes: 46 additions & 0 deletions cfg/default.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# configuration file for training

seed: 5
cuda_deterministic: True
log_interval: 10 # How often to print training logs

env:
id: OnlinePack-v1 # env name
scheme: EMS # the scheme of generating candidate map: heightmap, EP, FC
rot: True
box_type: random
container_size: [10, 10, 10]
k_placement: 100 # number of candidate placements


train:
algo: PPO
clip_param: 0.3
num_processes: 128
num_steps: 6
epoch: 500
batch_size: 128
step_per_epoch: 32768 # 2**15
repeat_per_collect: 1
gae_lambda: 0.96
reward_type: # optional: "terminal", None
gamma: 1 # discount factor for rewards (default: 1)

opt: # optimizer
optimizer: Adam # optimizer: Adam, RMSprop
lr: 7e-5 # learning rate (RMSprop7e-4, 1e-6, Adam7e-5)
lr_decay: False # use a linear schedule on the learning rate
eps: 1e-5 # epsilon (default: 1e-5)
alpha: 0.99 # RMSprop alpha (default: 0.99)

loss:
entropy: 0.001 # entropy term coefficient (default: 0.01)
value: 0.5 # value loss coefficient (default: 0.5)

model:
embed_dim: 128
heads: 1
num_layers: 4
forward_expansion: 2
dropout: 0

20 changes: 20 additions & 0 deletions envs/Packing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
"""
-------------------------------------------------
Project Name: Packing-RL
File Name: __init__.py.py
Author: XEH1SGH
Create Date: 4/14/2022
-------------------------------------------------
"""
# from gym.envs.registration import register
# from packing_env import PackingGame
#
# register(
# id='Pack-v0',
# entry_point='problems.OnlinePacking:PackingGame',
# )
from .env import PackingEnv

__version__ = "0.0.1"

80 changes: 80 additions & 0 deletions envs/Packing/binCreator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import numpy as np
import copy
import torch


class BoxCreator(object):
def __init__(self):
self.box_list = [] # generated box list

def reset(self):
self.box_list.clear()

def generate_box_size(self, **kwargs):
pass

def preview(self, length):
"""
:param length:
:return: list
"""
while len(self.box_list) < length:
self.generate_box_size()
return copy.deepcopy(self.box_list[:length])

def drop_box(self):
assert len(self.box_list) >= 0
self.box_list.pop(0)


class RandomBoxCreator(BoxCreator):
default_box_set = []
for i in range(4):
for j in range(4):
for k in range(4):
default_box_set.append((2 + i, 2 + j, 2 + k))

def __init__(self, box_size_set=None):
super().__init__()
self.box_set = box_size_set
if self.box_set is None:
self.box_set = RandomBoxCreator.default_box_set
# print(self.box_set)

def generate_box_size(self, **kwargs):
idx = np.random.randint(0, len(self.box_set))
self.box_list.append(self.box_set[idx])


# load data
class LoadBoxCreator(BoxCreator):
def __init__(self, data_name=None): # data url
super().__init__()
self.data_name = data_name
self.index = 0
self.box_index = 0
self.traj_nums = len(torch.load(self.data_name))
print("load data set successfully, data name: ", self.data_name)

def reset(self, index=None):
self.box_list.clear()
box_trajs = torch.load(self.data_name)
self.recorder = []
if index is None:
self.index += 1
else:
self.index = index
self.boxes = box_trajs[self.index]
self.box_index = 0
self.box_set = self.boxes
self.box_set.append([10, 10, 10])

def generate_box_size(self, **kwargs):
if self.box_index < len(self.box_set):
self.box_list.append(self.box_set[self.box_index])
self.recorder.append(self.box_set[self.box_index])
self.box_index += 1
else:
self.box_list.append((10, 10, 10))
self.recorder.append((10, 10, 10))
self.box_index += 1
18 changes: 18 additions & 0 deletions envs/Packing/box.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

class Box(object):
def __init__(self, length, width, height, x, y, z):
# dimension(x, y, z) + position(lx, ly, lz)
self.size_x = length
self.size_y = width
self.size_z = height
self.pos_x = x
self.pos_y = y
self.pos_z = z

def standardize(self):
"""
Returns:
tuple(size + position)
"""
return tuple([self.size_x, self.size_y, self.size_z, self.pos_x, self.pos_y, self.pos_z])
Loading

0 comments on commit b7ab5f9

Please sign in to comment.