├── README.md ├── __init__.py ├── dataset.py ├── debug_rollout.ipynb ├── evaluate_gn.py ├── evaluate_gn_rollout.py ├── gen_data.py ├── generate_rollout.py ├── gn_models.py ├── misc ├── actions.png └── test_0.gif ├── myswimmer.py ├── normalizer.py ├── test_normalizer.ipynb ├── train_gn.py ├── utils.py └── visualize.ipynb /README.md: -------------------------------------------------------------------------------- 1 | # Graph networks for learnable physical simulation 2 | 3 | This repository is a partial implementation of [Graph networks as learnable physics engines for inference and control](https://arxiv.org/abs/1806.01242). 4 | 5 | ## Dependencies 6 | 7 | - [DeepMind control suite](https://github.com/deepmind/dm_control) 8 | - Mujoco 9 | - networkx 10 | - pytorch 0.4.1 (other versions untested) 11 | 12 | 13 | ## Generate data 14 | 15 | Generate data with `gen_data.py` script, you should get control signals and resulting 6-link swimmers states. 16 | 17 | ![](misc/actions.png) 18 | 19 | ![](misc/test_0.gif) 20 | 21 | ## Train GN 22 | 23 | Learn data distribution first with `python test_normalizer.py`. It will generate `normalize.pth`. Then run 24 | `python train_gn.py` to train the model. The learning rate schedule corresponds to "fast training" in original paper. 25 | 26 | ## Evaluate GN 27 | 28 | `python evaluate_gn.py ` 29 | 30 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxia22/gn.pytorch/c55d75fb5d8c5c92e3d98414c3db863ad72ba965/__init__.py -------------------------------------------------------------------------------- /dataset.py: -------------------------------------------------------------------------------- 1 | import torch.utils.data as data 2 | import numpy as np 3 | import torch 4 | 5 | 6 | class SwimmerDataset(data.Dataset): 7 | def __init__(self, path): 8 | self.data = np.load(path) 9 | 10 | def __len__(self): 11 | return self.data.shape[0] * (self.data.shape[1] - 2) 12 | 13 | def __getitem__(self, idx): 14 | episode = idx // (self.data.shape[1] - 2) 15 | frame = idx % (self.data.shape[1] - 2) + 1 16 | #print(episode, frame) 17 | 18 | last_state = self.data[episode, frame - 1,5:] 19 | this_state = self.data[episode, frame,5:] 20 | action = self.data[episode, frame, :5] 21 | 22 | pos = last_state[5:5 + 18].reshape(6, 3) 23 | #pos += np.random.normal(scale = 0.001, size = pos.shape) 24 | last_state[5:5 + 18] = pos.reshape(18,) 25 | 26 | delta_state = this_state - last_state 27 | delta_state[delta_state > np.pi] -= np.pi * 2 28 | delta_state[delta_state < -np.pi] += np.pi * 2 29 | 30 | return action, delta_state, last_state 31 | 32 | 33 | def __get_episode__(self, idx): 34 | episode = idx 35 | #print(episode, frame) 36 | 37 | actions = [] 38 | delta_states = [] 39 | last_states = [] 40 | 41 | for frame in range(10,110): 42 | 43 | last_state = self.data[episode, frame - 1,5:] 44 | this_state = self.data[episode, frame,5:] 45 | action = self.data[episode, frame, :5] 46 | 47 | pos = last_state[5:5 + 18].reshape(6, 3) 48 | #pos += np.random.normal(scale = 0.001, size = pos.shape) 49 | last_state[5:5 + 18] = pos.reshape(18,) 50 | 51 | delta_state = this_state - last_state 52 | delta_state[delta_state > np.pi] -= np.pi * 2 53 | delta_state[delta_state < -np.pi] += np.pi * 2 54 | 55 | actions.append(action) 56 | delta_states.append(delta_state) 57 | last_states.append(last_state) 58 | 59 | 60 | actions = np.array(actions) 61 | delta_states = np.array(delta_states) 62 | last_states = np.array(last_states) 63 | 64 | return actions, delta_states, last_states -------------------------------------------------------------------------------- /debug_rollout.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import torch.utils.data as data\n", 12 | "from torch.utils.data import DataLoader\n", 13 | "import numpy as np\n", 14 | "import networkx as nx\n", 15 | "import torch.optim as optim\n", 16 | "import matplotlib.pyplot as plt\n", 17 | "from gn_models import init_graph_features, FFGN\n", 18 | "import torch\n", 19 | "from tensorboardX import SummaryWriter\n", 20 | "from datetime import datetime\n", 21 | "import os\n", 22 | "import sys\n", 23 | "from scipy.stats import pearsonr\n", 24 | "from train_gn import SwimmerDataset\n", 25 | "from PIL import Image\n", 26 | "import imageio\n", 27 | "from utils import *\n", 28 | "import argparse\n", 29 | "%matplotlib inline" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": 3, 35 | "metadata": { 36 | "collapsed": true 37 | }, 38 | "outputs": [], 39 | "source": [ 40 | "def evaluate_graph_loss(G, state, last_state):\n", 41 | " n_nodes = len(G)\n", 42 | "\n", 43 | " dpos = state[:, 5:5 + 18].view(-1, 6, 3)\n", 44 | " dvel = state[:, 5+18:5 + 18+18].view(-1, 6, 3)\n", 45 | "\n", 46 | " last_pos = last_state[:, 5:5 + 18].view(-1, 6, 3)\n", 47 | " vel = state[:, 5 + 18:5 + 36].view(-1, 6, 3)\n", 48 | " mean = 0\n", 49 | "\n", 50 | " true = []\n", 51 | " pred = []\n", 52 | "\n", 53 | " for node in G.nodes():\n", 54 | " #print(node)\n", 55 | " #loss += torch.mean((G.nodes[node]['feat'][:,:3] - pos[:,node]) ** 2)\n", 56 | " #loss += torch.mean((G.nodes[node]['feat'][:, 3:] - vel[:, node]) ** 2)\n", 57 | " mean += torch.mean(torch.abs((G.nodes[node]['feat'][:,:3] - dpos[:,node]) / dpos[:,node] ))\n", 58 | " pred.append(G.nodes[node]['feat'][:,:3])\n", 59 | " true.append(dpos[:,node])\n", 60 | " \n", 61 | " pred.append(G.nodes[node]['feat'][:,3:])\n", 62 | " true.append(dvel[:,node])\n", 63 | " \n", 64 | " \n", 65 | "\n", 66 | " pred = torch.stack(pred).view(-1,1)\n", 67 | " true = torch.stack(true).view(-1,1)\n", 68 | "\n", 69 | " plt.figure()\n", 70 | " for node in G.nodes():\n", 71 | " pos = last_pos[0, node, :3].cpu().data.numpy()\n", 72 | "\n", 73 | " angle = pos[2]\n", 74 | " x = pos[0]\n", 75 | " y = pos[1]\n", 76 | " r = 0.05\n", 77 | " dy = np.cos(angle) * r\n", 78 | " dx = - np.sin(angle) * r\n", 79 | " # plt.figure()\n", 80 | " plt.plot([x - dx, x + dx], [y - dy, y + dy], 'g', alpha = 0.5)\n", 81 | "\n", 82 | " pos = G.nodes[node]['feat'][0,:3].cpu().data.numpy() + last_pos[0,node,:3].cpu().data.numpy()\n", 83 | " angle = pos[2]\n", 84 | " x = pos[0]\n", 85 | " y = pos[1]\n", 86 | " r = 0.05\n", 87 | " dy = np.cos(angle) * r\n", 88 | " dx = - np.sin(angle) * r\n", 89 | " # plt.figure()\n", 90 | " plt.plot([x - dx, x + dx], [y - dy, y + dy],'r', alpha = 0.5)\n", 91 | " pos = dpos[0,node].cpu().data.numpy() + last_pos[0, node, :3].cpu().data.numpy()\n", 92 | "\n", 93 | " angle = pos[2]\n", 94 | " x = pos[0]\n", 95 | " y = pos[1]\n", 96 | " r = 0.05\n", 97 | " dy = np.cos(angle) * r\n", 98 | " dx = - np.sin(angle) * r\n", 99 | " # plt.figure()\n", 100 | " plt.plot([x - dx, x + dx], [y - dy, y + dy],'b', alpha = 0.5)\n", 101 | " plt.axis('equal')\n", 102 | " #plt.show()\n", 103 | "\n", 104 | " mean /= n_nodes\n", 105 | "\n", 106 | " return mean.data.item(), true, pred" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": 4, 112 | "metadata": { 113 | "collapsed": true 114 | }, 115 | "outputs": [], 116 | "source": [ 117 | "def get_graph_features(G, bs = 1):\n", 118 | " state = torch.zeros((bs, 41)).cuda()\n", 119 | " \n", 120 | " #joints = state[:,:5]\n", 121 | " pos = torch.zeros((bs, 6, 3)).cuda()\n", 122 | " vel = torch.zeros((bs, 6, 3)).cuda()\n", 123 | " \n", 124 | " # only get node features\n", 125 | " for node in G.nodes():\n", 126 | " #print(node)\n", 127 | " pos[:,node] = G.nodes[node]['feat'][:,:3]\n", 128 | " vel[:, node] = G.nodes[node]['feat'][:, 3:]\n", 129 | "\n", 130 | " \n", 131 | " state[:, 5:5+18] = pos.view(-1, 18)\n", 132 | " state[:, 5+18:5+36] = pos.view(-1,18)\n", 133 | " return state\n" 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": 15, 139 | "metadata": { 140 | "collapsed": true 141 | }, 142 | "outputs": [], 143 | "source": [ 144 | "model_fn = '/home/fei/Development/physics_predmodel/gn/logs/runs/October02_21:49:40/model_1130000.pth'\n", 145 | "dset = SwimmerDataset('swimmer_test.npy')\n", 146 | "use_cuda = True\n", 147 | "dl = DataLoader(dset, batch_size=200, num_workers=0, drop_last=True)\n", 148 | "node_feat_size = 6\n", 149 | "edge_feat_size = 3\n", 150 | "graph_feat_size = 10\n", 151 | "gn = FFGN(graph_feat_size, node_feat_size, edge_feat_size).cuda()\n", 152 | "gn.load_state_dict(torch.load(model_fn))\n", 153 | "\n", 154 | "normalizers = torch.load('normalize.pth')\n", 155 | "in_normalizer = normalizers['in_normalizer']\n", 156 | "out_normalizer = normalizers['out_normalizer']" 157 | ] 158 | }, 159 | { 160 | "cell_type": "code", 161 | "execution_count": 16, 162 | "metadata": { 163 | "collapsed": true 164 | }, 165 | "outputs": [], 166 | "source": [ 167 | "G1 = nx.path_graph(6).to_directed()" 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": 17, 173 | "metadata": { 174 | "collapsed": true 175 | }, 176 | "outputs": [], 177 | "source": [ 178 | "dl_e = enumerate(dl)" 179 | ] 180 | }, 181 | { 182 | "cell_type": "code", 183 | "execution_count": 27, 184 | "metadata": { 185 | "collapsed": true 186 | }, 187 | "outputs": [], 188 | "source": [ 189 | "idx = 15\n", 190 | "data = dset.__get_episode__(idx)\n", 191 | "data = [torch.from_numpy(item) for item in data]" 192 | ] 193 | }, 194 | { 195 | "cell_type": "code", 196 | "execution_count": 28, 197 | "metadata": {}, 198 | "outputs": [ 199 | { 200 | "data": { 201 | "image/png": "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\nmvko/pMAAAAASUVORK5CYII=\n", 202 | "text/plain": [ 203 | "
" 204 | ] 205 | }, 206 | "metadata": {}, 207 | "output_type": "display_data" 208 | }, 209 | { 210 | "data": { 211 | "image/png": "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\n", 212 | "text/plain": [ 213 | "
" 214 | ] 215 | }, 216 | "metadata": {}, 217 | "output_type": "display_data" 218 | } 219 | ], 220 | "source": [ 221 | "action, delta_state, last_state = data\n", 222 | "action, delta_state, last_state = action.float(), delta_state.float(), last_state.float()\n", 223 | "\n", 224 | "if use_cuda:\n", 225 | " action, delta_state, last_state = action.cuda(), delta_state.cuda(), last_state.cuda()\n", 226 | "\n", 227 | "\n", 228 | "init_graph_features(G1, graph_feat_size, node_feat_size, edge_feat_size, cuda=True, bs = 100)\n", 229 | "load_graph_features(G1, action, last_state, None,bs=100, noise = 0)\n", 230 | "G_out = gn(in_normalizer.normalize(G1))\n", 231 | "G_out = out_normalizer.inormalize(G_out)\n", 232 | "loss, true, pred = evaluate_graph_loss(G_out, delta_state, last_state)\n", 233 | "\n", 234 | "true = true.data.cpu().numpy()\n", 235 | "pred = pred.data.cpu().numpy()\n", 236 | "plt.legend(['prev','gt', 'pred'])\n", 237 | "plt.savefig('vis_{}.pdf'.format(idx))\n", 238 | "plt.figure()\n", 239 | "plt.scatter(true, pred, s = 2, alpha = 0.7)\n", 240 | "plt.xlabel('gt delta state')\n", 241 | "plt.ylabel('pred delta state')\n", 242 | "plt.savefig('corr_{}.pdf'.format(idx))\n", 243 | "\n" 244 | ] 245 | }, 246 | { 247 | "cell_type": "code", 248 | "execution_count": 30, 249 | "metadata": { 250 | "collapsed": true 251 | }, 252 | "outputs": [], 253 | "source": [ 254 | "def fig2img ( fig ):\n", 255 | " \"\"\"\n", 256 | " @brief Convert a Matplotlib figure to a PIL Image in RGBA format and return it\n", 257 | " @param fig a matplotlib figure\n", 258 | " @return a Python Imaging Library ( PIL ) image\n", 259 | " \"\"\"\n", 260 | " # put the figure pixmap into a numpy array\n", 261 | " buf = fig2data ( fig )\n", 262 | " w, h, d = buf.shape\n", 263 | " plt.close()\n", 264 | " return np.array(Image.frombytes( \"RGBA\", ( w ,h ), buf.tostring( ) ) )\n", 265 | "\n", 266 | "def fig2data(fig):\n", 267 | " \"\"\"\n", 268 | " @brief Convert a Matplotlib figure to a 4D numpy array with RGBA channels and return it\n", 269 | " @param fig a matplotlib figure\n", 270 | " @return a numpy 3D array of RGBA values\n", 271 | " \"\"\"\n", 272 | " # draw the renderer\n", 273 | " fig.canvas.draw()\n", 274 | "\n", 275 | " # Get the RGBA buffer from the figure\n", 276 | " w, h = fig.canvas.get_width_height()\n", 277 | " buf = np.frombuffer(fig.canvas.tostring_argb(), dtype=np.uint8)\n", 278 | " buf.shape = (w, h, 4)\n", 279 | "\n", 280 | " # canvas.tostring_argb give pixmap in ARGB mode. Roll the ALPHA channel to have it in RGBA mode\n", 281 | " buf = np.roll(buf, 3, axis=2)\n", 282 | " return buf\n", 283 | "\n", 284 | "def draw_state(state):\n", 285 | " state = state.cpu().data.numpy()[0]\n", 286 | " positions = state[5:5+18].reshape(6,3)\n", 287 | " \n", 288 | " fig = plt.figure()\n", 289 | "\n", 290 | " for node in range(6):\n", 291 | " pos = positions[node]\n", 292 | " angle = pos[2]\n", 293 | " x = pos[0]\n", 294 | " y = pos[1]\n", 295 | " r = 0.05\n", 296 | " dy = np.cos(angle) * r\n", 297 | " dx = - np.sin(angle) * r\n", 298 | " plt.plot([x - dx, x + dx], [y - dy, y + dy], 'g', alpha = 0.5)\n", 299 | "\n", 300 | " plt.axis('equal')\n", 301 | " \n", 302 | " \n", 303 | " img = fig2img(fig)\n", 304 | " plt.close() \n", 305 | " return img" 306 | ] 307 | }, 308 | { 309 | "cell_type": "code", 310 | "execution_count": 33, 311 | "metadata": {}, 312 | "outputs": [ 313 | { 314 | "name": "stdout", 315 | "output_type": "stream", 316 | "text": [ 317 | "1\n", 318 | "2\n", 319 | "3\n", 320 | "4\n", 321 | "5\n", 322 | "6\n", 323 | "7\n", 324 | "8\n", 325 | "9\n", 326 | "10\n", 327 | "11\n", 328 | "12\n", 329 | "13\n", 330 | "14\n", 331 | "15\n", 332 | "16\n", 333 | "17\n", 334 | "18\n", 335 | "19\n", 336 | "20\n", 337 | "21\n", 338 | "22\n", 339 | "23\n", 340 | "24\n", 341 | "25\n", 342 | "26\n", 343 | "27\n", 344 | "28\n", 345 | "29\n", 346 | "30\n", 347 | "31\n", 348 | "32\n", 349 | "33\n", 350 | "34\n", 351 | "35\n", 352 | "36\n", 353 | "37\n", 354 | "38\n", 355 | "39\n", 356 | "40\n", 357 | "41\n", 358 | "42\n", 359 | "43\n", 360 | "44\n", 361 | "45\n", 362 | "46\n", 363 | "47\n", 364 | "48\n", 365 | "49\n", 366 | "50\n", 367 | "51\n", 368 | "52\n", 369 | "53\n", 370 | "54\n", 371 | "55\n", 372 | "56\n", 373 | "57\n", 374 | "58\n", 375 | "59\n", 376 | "60\n", 377 | "61\n", 378 | "62\n", 379 | "63\n", 380 | "64\n", 381 | "65\n", 382 | "66\n", 383 | "67\n", 384 | "68\n", 385 | "69\n", 386 | "70\n", 387 | "71\n", 388 | "72\n", 389 | "73\n", 390 | "74\n", 391 | "75\n", 392 | "76\n", 393 | "77\n", 394 | "78\n", 395 | "79\n", 396 | "80\n", 397 | "81\n", 398 | "82\n", 399 | "83\n", 400 | "84\n", 401 | "85\n", 402 | "86\n", 403 | "87\n", 404 | "88\n", 405 | "89\n", 406 | "90\n", 407 | "91\n", 408 | "92\n", 409 | "93\n", 410 | "94\n", 411 | "95\n", 412 | "96\n", 413 | "97\n", 414 | "98\n", 415 | "99\n" 416 | ] 417 | } 418 | ], 419 | "source": [ 420 | "errorss2 = []\n", 421 | "\n", 422 | "for episode_idx in [310]:\n", 423 | " data = dset.__get_episode__(episode_idx)\n", 424 | " data = [torch.from_numpy(item) for item in data]\n", 425 | "\n", 426 | " writer = imageio.get_writer('test_pred2.mp4', fps=6)\n", 427 | " action, delta_state, last_state = data\n", 428 | " action, delta_state, last_state = action.float(), delta_state.float(), last_state.float()\n", 429 | "\n", 430 | " if use_cuda:\n", 431 | " action, delta_state, last_state = action.cuda(), delta_state.cuda(), last_state.cuda()\n", 432 | "\n", 433 | " state = last_state[1].unsqueeze(0)\n", 434 | " state_gt = last_state[1].unsqueeze(0).clone()\n", 435 | " errors = []\n", 436 | " for i in range(1, 100):\n", 437 | " print(i)\n", 438 | " action_i = action[i].unsqueeze(0)\n", 439 | " delta_state_i = delta_state[i].unsqueeze(0)\n", 440 | " last_state_i = last_state[i].unsqueeze(0)\n", 441 | "\n", 442 | " init_graph_features(G1, graph_feat_size, node_feat_size, edge_feat_size, cuda=True, bs = 1)\n", 443 | " load_graph_features(G1, action_i, state, None, bs=1, noise = 0)\n", 444 | " G_out = gn(in_normalizer.normalize(G1))\n", 445 | " G_out = out_normalizer.inormalize(G_out)\n", 446 | "\n", 447 | " delta_state_pred = get_graph_features(G_out)\n", 448 | "\n", 449 | " state_gt += delta_state_i\n", 450 | " state += delta_state_pred\n", 451 | " error = (state_gt - state).cpu().data.numpy()[0][5:5+18].reshape(6,3)\n", 452 | " error[:,2] = error[:,2] % (np.pi*2)\n", 453 | " error = np.sum(error ** 2, axis = (0,))\n", 454 | " errors.append(np.copy(error))\n", 455 | " img = draw_state(state_gt)\n", 456 | " img_pred = draw_state(state)\n", 457 | "\n", 458 | " writer.append_data(np.concatenate([img, img_pred], axis = 0))\n", 459 | " \n", 460 | " if i % 5 == 0:\n", 461 | " Image.fromarray(np.concatenate([img, img_pred], axis = 0)).save(\"example_pred_3{:03d}.png\".format(i))\n", 462 | " \n", 463 | " writer.close()\n", 464 | "\n", 465 | " errors = np.array(errors)\n", 466 | " errorss2.append(errors)" 467 | ] 468 | }, 469 | { 470 | "cell_type": "code", 471 | "execution_count": 65, 472 | "metadata": {}, 473 | "outputs": [ 474 | { 475 | "data": { 476 | "image/png": "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\n", 477 | "text/plain": [ 478 | "
" 479 | ] 480 | }, 481 | "metadata": {}, 482 | "output_type": "display_data" 483 | }, 484 | { 485 | "data": { 486 | "image/png": "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\n", 487 | "text/plain": [ 488 | "
" 489 | ] 490 | }, 491 | "metadata": {}, 492 | "output_type": "display_data" 493 | } 494 | ], 495 | "source": [ 496 | "plt.figure()\n", 497 | "plt.plot(errorss2.mean(0)[:,0] + errorss.mean(0)[:,1])\n", 498 | "plt.plot(errorss.mean(0)[:,0] + errorss.mean(0)[:,1])\n", 499 | "plt.ylabel('position error')\n", 500 | "plt.xlabel('frame')\n", 501 | "plt.legend(['simple MLP', 'FFGN'])\n", 502 | "plt.savefig('images/translation.png')\n", 503 | "\n", 504 | "plt.figure()\n", 505 | "plt.plot(np.log(errorss2.mean(0)[:,2]))\n", 506 | "plt.plot(np.log(errorss.mean(0)[:,2]))\n", 507 | "plt.ylabel('log rotation error')\n", 508 | "plt.xlabel('frame')\n", 509 | "plt.legend(['simple MLP', 'FFGN'])\n", 510 | "plt.savefig('images/rotation.png')\n", 511 | "\n" 512 | ] 513 | }, 514 | { 515 | "cell_type": "code", 516 | "execution_count": 45, 517 | "metadata": { 518 | "collapsed": true 519 | }, 520 | "outputs": [], 521 | "source": [ 522 | "errorss = np.array(errorss)" 523 | ] 524 | }, 525 | { 526 | "cell_type": "code", 527 | "execution_count": 48, 528 | "metadata": {}, 529 | "outputs": [ 530 | { 531 | "data": { 532 | "text/plain": [ 533 | "(99, 3)" 534 | ] 535 | }, 536 | "execution_count": 48, 537 | "metadata": {}, 538 | "output_type": "execute_result" 539 | } 540 | ], 541 | "source": [ 542 | "errorss.mean(0).shape" 543 | ] 544 | }, 545 | { 546 | "cell_type": "code", 547 | "execution_count": 62, 548 | "metadata": { 549 | "collapsed": true 550 | }, 551 | "outputs": [], 552 | "source": [ 553 | "errorss2 = np.array(errorss2)" 554 | ] 555 | }, 556 | { 557 | "cell_type": "code", 558 | "execution_count": null, 559 | "metadata": { 560 | "collapsed": true 561 | }, 562 | "outputs": [], 563 | "source": [] 564 | } 565 | ], 566 | "metadata": { 567 | "kernelspec": { 568 | "display_name": "Python (py35)", 569 | "language": "python", 570 | "name": "py35" 571 | }, 572 | "language_info": { 573 | "codemirror_mode": { 574 | "name": "ipython", 575 | "version": 3 576 | }, 577 | "file_extension": ".py", 578 | "mimetype": "text/x-python", 579 | "name": "python", 580 | "nbconvert_exporter": "python", 581 | "pygments_lexer": "ipython3", 582 | "version": "3.5.4" 583 | } 584 | }, 585 | "nbformat": 4, 586 | "nbformat_minor": 2 587 | } 588 | -------------------------------------------------------------------------------- /evaluate_gn.py: -------------------------------------------------------------------------------- 1 | import torch.utils.data as data 2 | from torch.utils.data import DataLoader 3 | import numpy as np 4 | import networkx as nx 5 | import torch.optim as optim 6 | import matplotlib.pyplot as plt 7 | from gn_models import init_graph_features, FFGN 8 | import torch 9 | from tensorboardX import SummaryWriter 10 | from datetime import datetime 11 | import os 12 | import sys 13 | from scipy.stats import pearsonr 14 | from train_gn import SwimmerDataset 15 | from utils import * 16 | import argparse 17 | 18 | 19 | def evaluate_graph_loss(G, state, last_state): 20 | n_nodes = len(G) 21 | 22 | dpos = state[:, 5:5 + 18].view(-1, 6, 3) 23 | last_pos = last_state[:, 5:5 + 18].view(-1, 6, 3) 24 | vel = state[:, 5 + 18:5 + 36].view(-1, 6, 3) 25 | mean = 0 26 | 27 | true = [] 28 | pred = [] 29 | 30 | for node in G.nodes(): 31 | #print(node) 32 | #loss += torch.mean((G.nodes[node]['feat'][:,:3] - pos[:,node]) ** 2) 33 | #loss += torch.mean((G.nodes[node]['feat'][:, 3:] - vel[:, node]) ** 2) 34 | mean += torch.mean(torch.abs((G.nodes[node]['feat'][:,:3] - dpos[:,node]) / dpos[:,node] )) 35 | pred.append(G.nodes[node]['feat'][:,:3]) 36 | true.append(dpos[:,node]) 37 | 38 | pred = torch.stack(pred).view(-1,1) 39 | true = torch.stack(true).view(-1,1) 40 | 41 | plt.figure() 42 | for node in G.nodes(): 43 | pos = last_pos[0, node, :3].cpu().data.numpy() 44 | 45 | angle = pos[2] 46 | x = pos[0] 47 | y = pos[1] 48 | r = 0.05 49 | dy = np.cos(angle) * r 50 | dx = - np.sin(angle) * r 51 | # plt.figure() 52 | plt.plot([x - dx, x + dx], [y - dy, y + dy], 'g', alpha = 0.5) 53 | 54 | pos = G.nodes[node]['feat'][0,:3].cpu().data.numpy() + last_pos[0,node,:3].cpu().data.numpy() 55 | 56 | angle = pos[2] 57 | x = pos[0] 58 | y = pos[1] 59 | r = 0.05 60 | dy = np.cos(angle) * r 61 | dx = - np.sin(angle) * r 62 | # plt.figure() 63 | plt.plot([x - dx, x + dx], [y - dy, y + dy],'r', alpha = 0.5) 64 | 65 | pos = dpos[0,node].cpu().data.numpy() + last_pos[0, node, :3].cpu().data.numpy() 66 | 67 | angle = pos[2] 68 | x = pos[0] 69 | y = pos[1] 70 | r = 0.05 71 | dy = np.cos(angle) * r 72 | dx = - np.sin(angle) * r 73 | # plt.figure() 74 | plt.plot([x - dx, x + dx], [y - dy, y + dy],'b', alpha = 0.5) 75 | plt.axis('equal') 76 | plt.show() 77 | 78 | mean /= n_nodes 79 | 80 | return mean.data.item(), true, pred 81 | 82 | if __name__ == "__main__": 83 | 84 | parser = argparse.ArgumentParser() 85 | parser.add_argument('--model', type=str, default = '', help='model path') 86 | opt = parser.parse_args() 87 | print(opt) 88 | 89 | dset = SwimmerDataset('swimmer_test.npy') 90 | use_cuda = True 91 | dl = DataLoader(dset, batch_size=200, num_workers=0, drop_last=True) 92 | G1 = nx.path_graph(6).to_directed() 93 | #nx.draw(G1) 94 | #plt.show() 95 | node_feat_size = 6 96 | edge_feat_size = 3 97 | graph_feat_size = 10 98 | gn = FFGN(graph_feat_size, node_feat_size, edge_feat_size).cuda() 99 | gn.load_state_dict(torch.load(opt.model)) 100 | 101 | normalizers = torch.load('normalize.pth') 102 | in_normalizer = normalizers['in_normalizer'] 103 | out_normalizer = normalizers['out_normalizer'] 104 | std = in_normalizer.get_std() 105 | step = 0 106 | for i,data in enumerate(dl): 107 | action, delta_state, last_state = data 108 | action, delta_state, last_state = action.float(), delta_state.float(), last_state.float() 109 | if use_cuda: 110 | action, delta_state, last_state = action.cuda(), delta_state.cuda(), last_state.cuda() 111 | 112 | init_graph_features(G1, graph_feat_size, node_feat_size, edge_feat_size, cuda=True, bs = 200) 113 | load_graph_features(G1, action, last_state, delta_state, bs=200, noise = 0.03, std = std) 114 | G_out = gn(in_normalizer.normalize(G1)) 115 | G_out = out_normalizer.inormalize(G_out) 116 | loss, true, pred = evaluate_graph_loss(G_out, delta_state, last_state) 117 | true = true.data.cpu().numpy() 118 | pred = pred.data.cpu().numpy() 119 | plt.scatter(true, pred, s = 2, alpha = 0.7) 120 | plt.show() 121 | 122 | r = pearsonr(true, pred)[0][0] 123 | print(loss, r) 124 | step += 1 125 | if i > 50: 126 | break 127 | -------------------------------------------------------------------------------- /evaluate_gn_rollout.py: -------------------------------------------------------------------------------- 1 | import torch.utils.data as data 2 | from torch.utils.data import DataLoader 3 | import numpy as np 4 | import networkx as nx 5 | import torch.optim as optim 6 | import matplotlib.pyplot as plt 7 | from gn_models import init_graph_features, FFGN 8 | import torch 9 | from tensorboardX import SummaryWriter 10 | from datetime import datetime 11 | import os 12 | import sys 13 | from scipy.stats import pearsonr 14 | from train_gn import SwimmerDataset 15 | from PIL import Image 16 | import imageio 17 | from utils import * 18 | import argparse 19 | 20 | 21 | def fig2data(fig): 22 | """ 23 | @brief Convert a Matplotlib figure to a 4D numpy array with RGBA channels and return it 24 | @param fig a matplotlib figure 25 | @return a numpy 3D array of RGBA values 26 | """ 27 | # draw the renderer 28 | fig.canvas.draw() 29 | 30 | # Get the RGBA buffer from the figure 31 | w, h = fig.canvas.get_width_height() 32 | buf = np.fromstring(fig.canvas.tostring_argb(), dtype=np.uint8) 33 | buf.shape = (w, h, 4) 34 | 35 | # canvas.tostring_argb give pixmap in ARGB mode. Roll the ALPHA channel to have it in RGBA mode 36 | buf = np.roll(buf, 3, axis=2) 37 | return buf 38 | 39 | def fig2img ( fig ): 40 | """ 41 | @brief Convert a Matplotlib figure to a PIL Image in RGBA format and return it 42 | @param fig a matplotlib figure 43 | @return a Python Imaging Library ( PIL ) image 44 | """ 45 | # put the figure pixmap into a numpy array 46 | buf = fig2data ( fig ) 47 | w, h, d = buf.shape 48 | plt.close() 49 | return np.array(Image.frombytes( "RGBA", ( w ,h ), buf.tostring( ) ) ) 50 | 51 | def draw_snake(state): 52 | 53 | fig = plt.figure() 54 | for i in range(6): 55 | pos = state[i, :3] 56 | 57 | angle = pos[2] 58 | x = pos[0] 59 | y = pos[1] 60 | r = 0.05 61 | dy = np.cos(angle) * r 62 | dx = - np.sin(angle) * r 63 | # plt.figure() 64 | plt.plot([x - dx, x + dx], [y - dy, y + dy], 'g', alpha = 0.5) 65 | plt.axis('equal') 66 | 67 | return fig 68 | 69 | 70 | 71 | if __name__ == "__main__": 72 | 73 | parser = argparse.ArgumentParser() 74 | parser.add_argument('--model', type=str, default = '', help='model path') 75 | opt = parser.parse_args() 76 | print(opt) 77 | 78 | dset = SwimmerDataset('swimmer_test.npy') 79 | use_cuda = True 80 | dl = DataLoader(dset, batch_size=200, num_workers=0, drop_last=True) 81 | 82 | #nx.draw(G1) 83 | #plt.show() 84 | node_feat_size = 6 85 | edge_feat_size = 3 86 | graph_feat_size = 10 87 | gn = FFGN(graph_feat_size, node_feat_size, edge_feat_size).cuda() 88 | gn.load_state_dict(torch.load(opt.model)) 89 | action, state = dset.get_episode(10) 90 | 91 | position = state[:, 5:5 + 18].reshape(-1, 6, 3) 92 | normalizers = torch.load('normalize.pth') 93 | in_normalizer = normalizers['in_normalizer'] 94 | out_normalizer = normalizers['out_normalizer'] 95 | 96 | 97 | """ 98 | writer = imageio.get_writer('test_plt.mp4', fps=30) 99 | for frame in range(100): 100 | fig = draw_snake(position[frame]) 101 | img = fig2img(fig) 102 | writer.append_data(img) 103 | print(frame) 104 | writer.close() 105 | """ 106 | 107 | start = 10 108 | state_tensor = torch.from_numpy(state[start, :].astype(np.float32)).unsqueeze(0).cuda() 109 | writer = imageio.get_writer('test_pred.mp4', fps=10) 110 | 111 | for frame in range(start + 1,100): 112 | action_tensor = torch.from_numpy(action[frame, :].astype(np.float32)).unsqueeze(0).cuda() 113 | #action_tensor.fill_(0) 114 | #print(state_tensor.size(), action_tensor.size()) 115 | G1 = nx.path_graph(6).to_directed() 116 | init_graph_features(G1, graph_feat_size, node_feat_size, edge_feat_size, cuda=True, bs=1) 117 | load_graph_features(G1, action_tensor, state_tensor, bs=1) 118 | G_out = gn(in_normalizer.normalize(G1)) 119 | G_out = out_normalizer.inormalize(G_out) 120 | delta_tensor = torch.zeros(state_tensor.size()).cuda() 121 | 122 | for i in range(6): 123 | delta_tensor[0, 5 + 6 * i:11 + 6 * i] = G_out.nodes[i]['feat'] 124 | 125 | state_tensor += delta_tensor 126 | true_state_tensor = torch.from_numpy(state[frame, :].astype(np.float32)).unsqueeze(0).cuda() 127 | #state_tensor[0,:5] = true_state_tensor[0,:5] 128 | 129 | if frame % 2 == 0: 130 | state_tensor = true_state_tensor 131 | 132 | s = state_tensor.cpu().data.numpy() 133 | position = s[0, 5:5 + 18].reshape(6, 3) 134 | 135 | fig = draw_snake(position) 136 | img = fig2img(fig) 137 | writer.append_data(img) 138 | 139 | writer.close() 140 | 141 | """ 142 | 143 | step = 0 144 | for i,data in enumerate(dl): 145 | action, delta_state, last_state = data 146 | action, delta_state, last_state = action.float(), delta_state.float(), last_state.float() 147 | if use_cuda: 148 | action, delta_state, last_state = action.cuda(), delta_state.cuda(), last_state.cuda() 149 | 150 | init_graph_features(G1, graph_feat_size, node_feat_size, edge_feat_size, cuda=True, bs = 200) 151 | load_graph_features(G1, action, last_state,bs=200) 152 | G_out = gn(G1) 153 | loss, true, pred = evaluate_graph_loss(G_out, delta_state, last_state) 154 | true = true.data.cpu().numpy() 155 | pred = pred.data.cpu().numpy() 156 | plt.scatter(true, pred, s = 2, alpha = 0.7) 157 | plt.show() 158 | 159 | r = pearsonr(true, pred)[0][0] 160 | print(loss, r) 161 | step += 1 162 | if i > 50: 163 | break 164 | """ -------------------------------------------------------------------------------- /gen_data.py: -------------------------------------------------------------------------------- 1 | from dm_control import suite 2 | import myswimmer as swimmer 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | import imageio 6 | from scipy import interpolate 7 | from tqdm import tqdm 8 | import sys 9 | 10 | 11 | # Load one task: 12 | env = swimmer.swimmer6(time_limit = 4) #suite.load(domain_name="swimmer", task_name="random") 13 | 14 | # Iterate over a task set: 15 | #for domain_name, task_name in suite.BENCHMARKING: 16 | # env = suite.load(domain_name, task_name) 17 | 18 | # Step through an episode and print out reward, discount and observation. 19 | 20 | max_frame = 200 21 | max_episodes = int(sys.argv[1]) 22 | 23 | width = 480 24 | height = 480 25 | 26 | action_spec = env.action_spec() 27 | time_step = env.reset() 28 | 29 | actions = np.zeros((201,5)) 30 | x = np.arange(0,201,20) 31 | 32 | dataset = np.zeros((max_episodes, max_frame+1, 5 + 5 + 18 + 18)) 33 | len_dataset = dataset.shape[0] 34 | 35 | for idx in tqdm(range(len_dataset)): 36 | time_step = env.reset() 37 | video = np.zeros((max_frame, height, width, 3), dtype=np.uint8) 38 | i = 0 39 | for j in range(5): 40 | y = np.random.uniform(-1,1,x.shape) 41 | tck = interpolate.splrep(x, y, s=0) 42 | xnew = np.arange(0,201) 43 | ynew = interpolate.splev(xnew, tck, der=0) 44 | 45 | actions[:,j] = ynew 46 | 47 | actions = np.clip(actions, -1, 1) 48 | record = False 49 | 50 | while not time_step.last(): 51 | action = actions[i] 52 | time_step = env.step(action) 53 | #from IPython import embed; embed() 54 | obs = time_step.observation 55 | #print(obs) 56 | dataset[idx,i,:5] = action 57 | dataset[idx, i, 5:10] = obs['joints'] 58 | dataset[idx,i,10:28] = obs['abs'] 59 | dataset[idx,i,28:] = obs['body_velocities'] 60 | 61 | if record: 62 | if i < max_frame: 63 | video[i] = env.physics.render(height, width, camera_id=0) 64 | i += 1 65 | 66 | if record: 67 | writer = imageio.get_writer('test_{}.gif'.format(idx), fps=60) 68 | for j in range(max_frame): 69 | writer.append_data(video[j]) 70 | writer.close() 71 | 72 | np.save(sys.argv[2], dataset) 73 | 74 | ''' 75 | plt.plot(actions) 76 | plt.savefig('actions.png') 77 | ''' 78 | -------------------------------------------------------------------------------- /generate_rollout.py: -------------------------------------------------------------------------------- 1 | import torch.utils.data as data 2 | from torch.utils.data import DataLoader 3 | import numpy as np 4 | import networkx as nx 5 | import torch.optim as optim 6 | import matplotlib.pyplot as plt 7 | from gn_models import init_graph_features, FFGN 8 | import torch 9 | from tensorboardX import SummaryWriter 10 | from datetime import datetime 11 | import os 12 | import sys 13 | from scipy.stats import pearsonr 14 | from train_gn import SwimmerDataset 15 | from PIL import Image 16 | import imageio 17 | from utils import * 18 | import argparse 19 | 20 | def get_graph_features(G, bs = 1): 21 | state = torch.zeros((bs, 41)).cuda() 22 | 23 | #joints = state[:,:5] 24 | pos = torch.zeros((bs, 6, 3)).cuda() 25 | vel = torch.zeros((bs, 6, 3)).cuda() 26 | 27 | # only get node features 28 | for node in G.nodes(): 29 | #print(node) 30 | pos[:,node] = G.nodes[node]['feat'][:,:3] 31 | vel[:, node] = G.nodes[node]['feat'][:, 3:] 32 | 33 | 34 | state[:, 5:5+18] = pos.view(-1, 18) 35 | state[:, 5+18:5+36] = pos.view(-1,18) 36 | return state 37 | 38 | def fig2img ( fig ): 39 | """ 40 | @brief Convert a Matplotlib figure to a PIL Image in RGBA format and return it 41 | @param fig a matplotlib figure 42 | @return a Python Imaging Library ( PIL ) image 43 | """ 44 | # put the figure pixmap into a numpy array 45 | buf = fig2data ( fig ) 46 | w, h, d = buf.shape 47 | plt.close() 48 | return np.array(Image.frombytes( "RGBA", ( w ,h ), buf.tostring( ) ) ) 49 | 50 | def fig2data(fig): 51 | """ 52 | @brief Convert a Matplotlib figure to a 4D numpy array with RGBA channels and return it 53 | @param fig a matplotlib figure 54 | @return a numpy 3D array of RGBA values 55 | """ 56 | # draw the renderer 57 | fig.canvas.draw() 58 | 59 | # Get the RGBA buffer from the figure 60 | w, h = fig.canvas.get_width_height() 61 | buf = np.frombuffer(fig.canvas.tostring_argb(), dtype=np.uint8) 62 | buf.shape = (w, h, 4) 63 | 64 | # canvas.tostring_argb give pixmap in ARGB mode. Roll the ALPHA channel to have it in RGBA mode 65 | buf = np.roll(buf, 3, axis=2) 66 | return buf 67 | 68 | def draw_state(state): 69 | state = state.cpu().data.numpy()[0] 70 | positions = state[5:5+18].reshape(6,3) 71 | 72 | fig = plt.figure() 73 | 74 | for node in range(6): 75 | pos = positions[node] 76 | angle = pos[2] 77 | x = pos[0] 78 | y = pos[1] 79 | r = 0.05 80 | dy = np.cos(angle) * r 81 | dx = - np.sin(angle) * r 82 | plt.plot([x - dx, x + dx], [y - dy, y + dy], 'g', alpha = 0.5) 83 | 84 | plt.axis('equal') 85 | 86 | 87 | img = fig2img(fig) 88 | plt.close() 89 | return img 90 | 91 | 92 | if __name__ == '__main__': 93 | model_fn = '/home/fei/Development/physics_predmodel/gn/logs/runs/October01_14:59:16/model_1240000.pth' 94 | dset = SwimmerDataset('swimmer_test.npy') 95 | use_cuda = True 96 | dl = DataLoader(dset, batch_size=200, num_workers=0, drop_last=True) 97 | node_feat_size = 6 98 | edge_feat_size = 3 99 | graph_feat_size = 10 100 | gn = FFGN(graph_feat_size, node_feat_size, edge_feat_size).cuda() 101 | gn.load_state_dict(torch.load(model_fn)) 102 | 103 | normalizers = torch.load('normalize.pth') 104 | in_normalizer = normalizers['in_normalizer'] 105 | out_normalizer = normalizers['out_normalizer'] 106 | 107 | G1 = nx.path_graph(6).to_directed() 108 | dl_e = enumerate(dl) 109 | data = dset.__get_episode__(353) 110 | data = [torch.from_numpy(item) for item in data] 111 | 112 | writer = imageio.get_writer('test_pred.mp4', fps=6) 113 | action, delta_state, last_state = data 114 | action, delta_state, last_state = action.float(), delta_state.float(), last_state.float() 115 | 116 | if use_cuda: 117 | action, delta_state, last_state = action.cuda(), delta_state.cuda(), last_state.cuda() 118 | 119 | state = last_state[1].unsqueeze(0) 120 | state_gt = last_state[1].unsqueeze(0).clone() 121 | 122 | for i in range(1, 50): 123 | print(i) 124 | action_i = action[i].unsqueeze(0) 125 | delta_state_i = delta_state[i].unsqueeze(0) 126 | last_state_i = last_state[i].unsqueeze(0) 127 | 128 | init_graph_features(G1, graph_feat_size, node_feat_size, edge_feat_size, cuda=True, bs = 1) 129 | load_graph_features(G1, action_i, state, None, bs=1, noise = 0) 130 | G_out = gn(in_normalizer.normalize(G1)) 131 | G_out = out_normalizer.inormalize(G_out) 132 | 133 | delta_state_pred = get_graph_features(G_out) 134 | 135 | state_gt += delta_state_i 136 | state += delta_state_pred 137 | 138 | img = draw_state(state_gt) 139 | img_pred = draw_state(state) 140 | 141 | writer.append_data(np.concatenate([img, img_pred], axis = 1)) 142 | 143 | writer.close() -------------------------------------------------------------------------------- /gn_models.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import networkx as nx 3 | import matplotlib.pyplot as plt 4 | import torch.nn as nn 5 | import torch.optim as optim 6 | from utils import * 7 | 8 | 9 | _node_feat_size = 128 10 | _edge_feat_size = 128 11 | _graph_feat_size = 128 12 | 13 | 14 | class EdgeBlock(nn.Module): 15 | def __init__(self, graph_feat_size, node_feat_size, edge_feat_size): 16 | super(EdgeBlock, self).__init__() 17 | self.f_e = nn.Sequential( 18 | nn.Linear(graph_feat_size + 2 * node_feat_size + edge_feat_size, 256), 19 | nn.ReLU(inplace = True), 20 | nn.Linear(256,256), 21 | nn.ReLU(inplace = True), 22 | nn.Linear(256, 256), 23 | nn.ReLU(inplace=True), 24 | nn.Linear(256,edge_feat_size), 25 | ) 26 | def forward(self, g, ns, nr, e): 27 | x = torch.cat([g, ns, nr, e], dim = -1) 28 | return self.f_e(x) 29 | 30 | class NodeBlock(nn.Module): 31 | def __init__(self, graph_feat_size, node_feat_size, edge_feat_size): 32 | super(NodeBlock, self).__init__() 33 | self.f_n = nn.Sequential( 34 | nn.Linear(graph_feat_size + node_feat_size + edge_feat_size, 256), 35 | nn.ReLU(inplace = True), 36 | nn.Linear(256, 256), 37 | nn.ReLU(inplace = True), 38 | nn.Linear(256, 256), 39 | nn.ReLU(inplace=True), 40 | nn.Linear(256, node_feat_size), 41 | ) 42 | def forward(self, g, n, e): 43 | x = torch.cat([g, n, e], dim = -1) 44 | return self.f_n(x) 45 | 46 | 47 | class GraphBlock(nn.Module): 48 | def __init__(self, graph_feat_size, node_feat_size, edge_feat_size): 49 | super(GraphBlock, self).__init__() 50 | self.f_g = nn.Sequential( 51 | nn.Linear(graph_feat_size + node_feat_size + edge_feat_size, 256), 52 | nn.ReLU(inplace = True), 53 | nn.Linear(256, 256), 54 | nn.ReLU(inplace = True), 55 | nn.Linear(256, 256), 56 | nn.ReLU(inplace=True), 57 | nn.Linear(256, graph_feat_size), 58 | ) 59 | def forward(self, g, n, e): 60 | x = torch.cat([g, n, e], dim = -1) 61 | return self.f_g(x) 62 | 63 | 64 | class GNBlock(nn.Module): 65 | def __init__(self, graph_feat_size, node_feat_size, edge_feat_size): 66 | super(GNBlock, self).__init__() 67 | self.edge_block = EdgeBlock(graph_feat_size, node_feat_size, edge_feat_size) 68 | self.node_block = NodeBlock(graph_feat_size, node_feat_size, edge_feat_size) 69 | self.graph_block = GraphBlock(graph_feat_size, node_feat_size, edge_feat_size) 70 | 71 | self.graph_feat_size = graph_feat_size 72 | self.node_feat_size = node_feat_size 73 | self.edge_feat_size = edge_feat_size 74 | 75 | 76 | def forward(self, x): 77 | bs = x.graph['feat'].size(0) 78 | #edge update 79 | for u,v in x.edges(): 80 | g = x.graph['feat'] 81 | ns = x.node[u]['feat'] 82 | nr = x.node[v]['feat'] 83 | e = x[u][v]['feat'] 84 | x[u][v]['temp_feat'] = self.edge_block(g, ns, nr, e) 85 | 86 | for u,v in x.edges(): 87 | x[u][v]['feat'] = x[u][v]['temp_feat'] 88 | 89 | #node update 90 | for u in x.nodes(): 91 | g = x.graph['feat'] 92 | n = x.node[u]['feat'] 93 | pred = list(x.predecessors(u)) 94 | n_e_agg = torch.zeros(bs, self.edge_feat_size) 95 | if x.graph['feat'].is_cuda: 96 | n_e_agg = n_e_agg.cuda() 97 | for v in pred: 98 | n_e_agg += x[v][u]['feat'] 99 | x.node[u]['temp_feat'] = self.node_block(g, n, n_e_agg) 100 | 101 | for u in x.nodes(): 102 | x.node[u]['feat'] = x.node[u]['temp_feat'] 103 | 104 | #graph update 105 | e_agg = torch.zeros(bs, self.edge_feat_size) 106 | n_agg = torch.zeros(bs, self.node_feat_size) 107 | if x.graph['feat'].is_cuda: 108 | e_agg = e_agg.cuda() 109 | n_agg = n_agg.cuda() 110 | 111 | for u,v in x.edges(): 112 | e_agg += x[u][v]['feat'] 113 | for u in x.nodes(): 114 | n_agg += x.node[u]['feat'] 115 | g = x.graph['feat'] 116 | x.graph['feat'] = self.graph_block(g, n_agg, e_agg) 117 | 118 | return x 119 | 120 | def subtract(G, H): 121 | G_out = G.copy() 122 | G_out.graph['feat'] -= H.graph['feat'] 123 | for node in G_out.nodes(): 124 | G_out.nodes[node]['feat'] -= H.nodes[node]['feat'] 125 | for edge in G.edges(): 126 | G_out[edge[0]][edge[1]]['feat'] -= H[edge[0]][edge[1]]['feat'] 127 | 128 | return G_out 129 | 130 | class Normalizer: 131 | def __init__(self): 132 | self.count = 0 133 | self.momentum = 0.99 134 | self.G = None 135 | def input(self, G): 136 | if self.count == 0: 137 | self.G = G.copy() 138 | 139 | del self.G.graph['feat'] 140 | for node in self.G.nodes(): 141 | del self.G.nodes[node]['feat'] 142 | for edge in self.G.edges(): 143 | del self.G[edge[0]][edge[1]]['feat'] 144 | 145 | self.G.graph['feat_mean'] = torch.mean(G.graph['feat'], dim=0, keepdim=True) 146 | self.G.graph['feat_var'] = torch.var(G.graph['feat'], dim=0, keepdim=True) 147 | 148 | for node in G.nodes(): 149 | self.G.nodes[node]['feat_mean'] = torch.mean(G.nodes[node]['feat'], dim=0, keepdim=True) 150 | self.G.nodes[node]['feat_var'] = torch.var(G.nodes[node]['feat'], dim=0, keepdim=True) 151 | 152 | for edge in G.edges(): 153 | self.G[edge[0]][edge[1]]['feat_mean'] = torch.mean(G[edge[0]][edge[1]]['feat'], dim=0, keepdim=True) 154 | self.G[edge[0]][edge[1]]['feat_var'] = torch.var(G[edge[0]][edge[1]]['feat'], dim=0, keepdim=True) 155 | else: 156 | self.G.graph['feat_mean'] = self.momentum * self.G.graph['feat_mean'] + (1-self.momentum) * torch.mean(G.graph['feat'], dim=0, keepdim=True) 157 | self.G.graph['feat_var'] = self.momentum * self.G.graph['feat_var'] + (1-self.momentum) * torch.var(G.graph['feat'], dim=0, keepdim=True) 158 | 159 | for node in G.nodes(): 160 | self.G.nodes[node]['feat_mean'] = self.momentum * self.G.nodes[node]['feat_mean'] + (1-self.momentum) * torch.mean(G.nodes[node]['feat'], dim=0, keepdim=True) 161 | self.G.nodes[node]['feat_var'] = self.momentum * self.G.nodes[node]['feat_var'] + (1-self.momentum) * torch.var(G.nodes[node]['feat'], dim=0, keepdim=True) 162 | 163 | for edge in G.edges(): 164 | self.G[edge[0]][edge[1]]['feat_mean'] = self.momentum * self.G[edge[0]][edge[1]]['feat_mean'] + (1-self.momentum) * torch.mean(G[edge[0]][edge[1]]['feat'], dim=0, keepdim=True) 165 | self.G[edge[0]][edge[1]]['feat_var'] = self.momentum * self.G[edge[0]][edge[1]]['feat_var'] + (1-self.momentum) * torch.var(G[edge[0]][edge[1]]['feat'], dim=0, keepdim=True) 166 | 167 | #print(self.G.nodes[0]['feat_var']) 168 | self.count += 1 169 | ## accumulate mean and var 170 | 171 | def get(self): 172 | return self.G 173 | 174 | def normalize(self, H): 175 | G_out = H.copy() 176 | 177 | G_out.graph['feat'] = (G_out.graph['feat'] - self.G.graph['feat_mean']) / (torch.sqrt(self.G.graph['feat_var']) + 1e-6).detach() 178 | for node in G_out.nodes(): 179 | G_out.nodes[node]['feat'] = (G_out.nodes[node]['feat'] - self.G.nodes[node]['feat_mean']) / (torch.sqrt(self.G.nodes[node]['feat_var']) + 1e-6).detach() 180 | for edge in G_out.edges(): 181 | G_out[edge[0]][edge[1]]['feat'] = (G_out[edge[0]][edge[1]]['feat'] - self.G[edge[0]][edge[1]]['feat_mean']) / (torch.sqrt(self.G[edge[0]][edge[1]]['feat_var']) + 1e-6).detach() 182 | 183 | #print(G_out.nodes[0]['feat']) 184 | return G_out 185 | 186 | def inormalize(self, H): 187 | G_out = H.copy() 188 | for node in G_out.nodes(): 189 | G_out.nodes[node]['feat'] = G_out.nodes[node]['feat'] * (torch.sqrt(self.G.nodes[node]['feat_var']) + 1e-6).detach() + self.G.nodes[node]['feat_mean'] 190 | 191 | return G_out 192 | 193 | def get_std(self): 194 | std = [] 195 | for node in self.G.nodes(): 196 | std.append(self.G.nodes[node]['feat_var']) 197 | 198 | std = torch.cat(std, 0) 199 | std = torch.sqrt(std + 1e-6) 200 | #print(std) 201 | return std 202 | 203 | 204 | class FFGN(nn.Module): 205 | def __init__(self, graph_feat_size, node_feat_size, edge_feat_size): 206 | super(FFGN, self).__init__() 207 | self.GN1 = GNBlock(graph_feat_size, node_feat_size, edge_feat_size) 208 | self.GN2 = GNBlock(graph_feat_size*2, node_feat_size*2, edge_feat_size*2) 209 | self.linear = nn.Linear(node_feat_size*2, node_feat_size) 210 | 211 | def forward(self, G_in): 212 | G = G_in.copy() 213 | G = self.GN1(G) 214 | #Graph concatenate 215 | G.graph['feat'] = torch.cat([G.graph['feat'], G_in.graph['feat']], dim=-1) 216 | for node in G.nodes(): 217 | G.nodes[node]['feat'] = torch.cat([G.nodes[node]['feat'], G_in.nodes[node]['feat']], dim = -1) 218 | for edge in G.edges(): 219 | G[edge[0]][edge[1]]['feat'] = torch.cat([G[edge[0]][edge[1]]['feat'], G_in[edge[0]][edge[1]]['feat']], 220 | dim = -1) 221 | G = self.GN2(G) 222 | 223 | for node in G.nodes(): 224 | G.nodes[node]['feat'] = self.linear(G.nodes[node]['feat']) 225 | #use a linear layer to change back to original node feature size 226 | return G 227 | 228 | if __name__ == "__main__": 229 | G1 = nx.erdos_renyi_graph(10,0.3).to_directed() 230 | #nx.draw(G1) 231 | #plt.show() 232 | init_graph_features(G1, _graph_feat_size, _node_feat_size, _edge_feat_size, cuda = True) 233 | gn = FFGN(_graph_feat_size, _node_feat_size, _edge_feat_size).cuda() 234 | G_out = gn(G1) 235 | torch.sum(G_out.graph['feat'] ** 2).backward() 236 | -------------------------------------------------------------------------------- /misc/actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxia22/gn.pytorch/c55d75fb5d8c5c92e3d98414c3db863ad72ba965/misc/actions.png -------------------------------------------------------------------------------- /misc/test_0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxia22/gn.pytorch/c55d75fb5d8c5c92e3d98414c3db863ad72ba965/misc/test_0.gif -------------------------------------------------------------------------------- /myswimmer.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 The dm_control Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================ 15 | 16 | """Procedurally generated Swimmer domain.""" 17 | from __future__ import absolute_import 18 | from __future__ import division 19 | from __future__ import print_function 20 | 21 | import collections 22 | 23 | # Internal dependencies. 24 | 25 | from dm_control import mujoco 26 | from dm_control.rl import control 27 | from dm_control.suite import base 28 | from dm_control.suite import common 29 | from dm_control.suite.utils import randomizers 30 | from dm_control.utils import containers 31 | from dm_control.utils import rewards 32 | 33 | from lxml import etree 34 | import numpy as np 35 | from six.moves import xrange # pylint: disable=redefined-builtin 36 | from dm_control.mujoco.wrapper.mjbindings import mjlib 37 | 38 | _DEFAULT_TIME_LIMIT = 30 39 | _CONTROL_TIMESTEP = .02 # (Seconds) 40 | 41 | SUITE = containers.TaggedTasks() 42 | 43 | 44 | def get_model_and_assets(n_joints): 45 | """Returns a tuple containing the model XML string and a dict of assets. 46 | 47 | Args: 48 | n_joints: An integer specifying the number of joints in the swimmer. 49 | 50 | Returns: 51 | A tuple `(model_xml_string, assets)`, where `assets` is a dict consisting of 52 | `{filename: contents_string}` pairs. 53 | """ 54 | return _make_model(n_joints), common.ASSETS 55 | 56 | 57 | @SUITE.add('benchmarking') 58 | 59 | 60 | def swimmer6(time_limit=_DEFAULT_TIME_LIMIT, random=None): 61 | """Returns a 6-link swimmer.""" 62 | return _make_swimmer(6, time_limit, random=random) 63 | 64 | 65 | @SUITE.add('benchmarking') 66 | def swimmer15(time_limit=_DEFAULT_TIME_LIMIT, random=None): 67 | """Returns a 15-link swimmer.""" 68 | return _make_swimmer(15, time_limit, random=random) 69 | 70 | 71 | def swimmer(n_links=3, time_limit=_DEFAULT_TIME_LIMIT, 72 | random=None): 73 | """Returns a swimmer with n links.""" 74 | return _make_swimmer(n_links, time_limit, random=random) 75 | 76 | 77 | def _make_swimmer(n_joints, time_limit=_DEFAULT_TIME_LIMIT, random=None): 78 | """Returns a swimmer control environment.""" 79 | model_string, assets = get_model_and_assets(n_joints) 80 | physics = Physics.from_xml_string(model_string, assets=assets) 81 | task = Swimmer(random=random) 82 | return control.Environment( 83 | physics, task, time_limit=time_limit, control_timestep=_CONTROL_TIMESTEP) 84 | 85 | 86 | def _make_model(n_bodies): 87 | """Generates an xml string defining a swimmer with `n_bodies` bodies.""" 88 | if n_bodies < 3: 89 | raise ValueError('At least 3 bodies required. Received {}'.format(n_bodies)) 90 | mjcf = etree.fromstring(common.read_model('swimmer.xml')) 91 | head_body = mjcf.find('./worldbody/body') 92 | actuator = etree.SubElement(mjcf, 'actuator') 93 | sensor = etree.SubElement(mjcf, 'sensor') 94 | 95 | parent = head_body 96 | for body_index in xrange(n_bodies - 1): 97 | site_name = 'site_{}'.format(body_index) 98 | child = _make_body(body_index=body_index) 99 | child.append(etree.Element('site', name=site_name)) 100 | joint_name = 'joint_{}'.format(body_index) 101 | joint_limit = 360.0/n_bodies 102 | joint_range = '{} {}'.format(-joint_limit, joint_limit) 103 | child.append(etree.Element('joint', {'name': joint_name, 104 | 'range': joint_range})) 105 | motor_name = 'motor_{}'.format(body_index) 106 | actuator.append(etree.Element('motor', name=motor_name, joint=joint_name)) 107 | velocimeter_name = 'velocimeter_{}'.format(body_index) 108 | sensor.append(etree.Element('velocimeter', name=velocimeter_name, 109 | site=site_name)) 110 | gyro_name = 'gyro_{}'.format(body_index) 111 | sensor.append(etree.Element('gyro', name=gyro_name, site=site_name)) 112 | parent.append(child) 113 | parent = child 114 | 115 | # Move tracking cameras further away from the swimmer according to its length. 116 | cameras = mjcf.findall('./worldbody/body/camera') 117 | scale = n_bodies / 6.0 118 | for cam in cameras: 119 | if cam.get('mode') == 'trackcom': 120 | old_pos = cam.get('pos').split(' ') 121 | new_pos = ' '.join([str(float(dim) * scale) for dim in old_pos]) 122 | cam.set('pos', new_pos) 123 | 124 | return etree.tostring(mjcf, pretty_print=True) 125 | 126 | 127 | def _make_body(body_index): 128 | """Generates an xml string defining a single physical body.""" 129 | body_name = 'segment_{}'.format(body_index) 130 | visual_name = 'visual_{}'.format(body_index) 131 | inertial_name = 'inertial_{}'.format(body_index) 132 | body = etree.Element('body', name=body_name) 133 | body.set('pos', '0 .1 0') 134 | etree.SubElement(body, 'geom', {'class': 'visual', 'name': visual_name}) 135 | etree.SubElement(body, 'geom', {'class': 'inertial', 'name': inertial_name}) 136 | return body 137 | 138 | 139 | class Physics(mujoco.Physics): 140 | """Physics simulation with additional features for the swimmer domain.""" 141 | 142 | def nose_to_target(self): 143 | """Returns a vector from nose to target in local coordinate of the head.""" 144 | nose_to_target = (self.named.data.geom_xpos['target'] - 145 | self.named.data.geom_xpos['nose']) 146 | head_orientation = self.named.data.xmat['head'].reshape(3, 3) 147 | return nose_to_target.dot(head_orientation)[:2] 148 | 149 | def nose_to_target_dist(self): 150 | """Returns the distance from the nose to the target.""" 151 | return np.linalg.norm(self.nose_to_target()) 152 | 153 | def body_velocities(self): 154 | """Returns local body velocities: x,y linear, z rotational.""" 155 | xvel_local = self.data.sensordata[12:].reshape((-1, 6)) 156 | vx_vy_wz = [0, 1, 5] # Indices for linear x,y vels and rotational z vel. 157 | return xvel_local[:, vx_vy_wz].ravel() 158 | 159 | 160 | def body_abs_velocities(self): 161 | """Returns local body velocities: x,y linear, z rotational.""" 162 | #xvel_local = self.data.sensordata[12:].reshape((-1, 6)) 163 | #from IPython import embed; embed() 164 | #vx_vy_wz = [0, 1, 5] # Indices for linear x,y vels and rotational z vel. 165 | 166 | vel = np.zeros(6) 167 | vels = [] 168 | for i in range(1,7): 169 | mjlib.mj_objectVelocity(self.model.ptr, self.data.ptr, 1, i, vel, 0) 170 | vels.append(vel[[3,4,2]].copy()) 171 | return np.array(vels).ravel() 172 | 173 | 174 | def joints(self): 175 | """Returns all internal joint angles (excluding root joints).""" 176 | return self.data.qpos[3:] 177 | 178 | def body_state(self): 179 | state = np.zeros((6,3)) 180 | i = 0 181 | for k in ['head'] + ['segment_{}'.format(i) for i in range(5)]: 182 | state[i,:2] = self.named.data.xpos[k][:2] 183 | state[i,2] = np.arctan2(-self.named.data.xmat[k][1], self.named.data.xmat[k][0]) 184 | 185 | 186 | #print(state[i,2]) 187 | #from IPython import embed; embed() 188 | i += 1 189 | return state.ravel() 190 | 191 | 192 | class Swimmer(base.Task): 193 | """A swimmer `Task` to reach the target or just swim.""" 194 | 195 | def __init__(self, random=None): 196 | """Initializes an instance of `Swimmer`. 197 | 198 | Args: 199 | random: Optional, either a `numpy.random.RandomState` instance, an 200 | integer seed for creating a new `RandomState`, or None to select a seed 201 | automatically (default). 202 | """ 203 | super(Swimmer, self).__init__(random=random) 204 | 205 | def initialize_episode(self, physics): 206 | """Sets the state of the environment at the start of each episode. 207 | 208 | Initializes the swimmer orientation to [-pi, pi) and the relative joint 209 | angle of each joint uniformly within its range. 210 | 211 | Args: 212 | physics: An instance of `Physics`. 213 | """ 214 | # Random joint angles: 215 | randomizers.randomize_limited_and_rotational_joints(physics, self.random) 216 | # Random target position. 217 | close_target = self.random.rand() < .2 # Probability of a close target. 218 | target_box = .3 if close_target else 2 219 | xpos, ypos = self.random.uniform(-target_box, target_box, size=2) 220 | physics.named.model.geom_pos['target', 'x'] = xpos 221 | physics.named.model.geom_pos['target', 'y'] = ypos 222 | physics.named.model.light_pos['target_light', 'x'] = xpos 223 | physics.named.model.light_pos['target_light', 'y'] = ypos 224 | 225 | def get_observation(self, physics): 226 | """Returns an observation of joint angles, body velocities and target.""" 227 | obs = collections.OrderedDict() 228 | obs['joints'] = physics.joints() 229 | obs['to_target'] = physics.nose_to_target() 230 | obs['body_velocities'] = physics.body_abs_velocities() 231 | obs['abs'] = physics.body_state() 232 | return obs 233 | 234 | def get_reward(self, physics): 235 | """Returns a smooth reward.""" 236 | target_size = physics.named.model.geom_size['target', 0] 237 | return rewards.tolerance(physics.nose_to_target_dist(), 238 | bounds=(0, target_size), 239 | margin=5*target_size, 240 | sigmoid='long_tail') 241 | -------------------------------------------------------------------------------- /normalizer.py: -------------------------------------------------------------------------------- 1 | from torch.utils.data import DataLoader 2 | import numpy as np 3 | import networkx as nx 4 | import torch.optim as optim 5 | import matplotlib.pyplot as plt 6 | from gn_models import init_graph_features, FFGN, Normalizer, subtract 7 | import torch 8 | from tensorboardX import SummaryWriter 9 | from datetime import datetime 10 | import os 11 | from tqdm import tqdm 12 | from dataset import SwimmerDataset 13 | from utils import * 14 | 15 | if __name__ == "__main__": 16 | dset = SwimmerDataset('swimmer.npy') 17 | use_cuda = True 18 | dl = DataLoader(dset, batch_size=200, num_workers=0, drop_last=True) 19 | G1 = nx.path_graph(6).to_directed() 20 | #nx.draw(G1) 21 | #plt.show() 22 | node_feat_size = 6 23 | edge_feat_size = 3 24 | graph_feat_size = 10 25 | gn = FFGN(graph_feat_size, node_feat_size, edge_feat_size).cuda() 26 | optimizer = optim.Adam(gn.parameters(), lr = 1e-3) 27 | savedir = os.path.join('./logs','runs', 28 | datetime.now().strftime('%B%d_%H:%M:%S')) 29 | writer = SummaryWriter(savedir) 30 | step = 0 31 | 32 | in_normalizer = Normalizer() 33 | out_normalizer = Normalizer() 34 | 35 | for epoch in range(1): 36 | for i,data in tqdm(enumerate(dl)): 37 | action, delta_state, last_state = data 38 | action, delta_state, last_state = action.float(), delta_state.float(), last_state.float() 39 | if use_cuda: 40 | action, delta_state, last_state = action.cuda(), delta_state.cuda(), last_state.cuda() 41 | 42 | init_graph_features(G1, graph_feat_size, node_feat_size, edge_feat_size, cuda=True, bs = 200) 43 | load_graph_features(G1, action, last_state, None, noise = 0, bs=200, norm = True) 44 | in_normalizer.input(G1) 45 | load_graph_features(G1, action, delta_state, None, noise = 0, bs=200, norm = False) 46 | out_normalizer.input(G1) 47 | 48 | ''' 49 | for epoch in range(1): 50 | for i,data in enumerate(dl): 51 | action, delta_state, last_state = data 52 | action, delta_state, last_state = action.float(), delta_state.float(), last_state.float() 53 | if use_cuda: 54 | action, delta_state, last_state = action.cuda(), delta_state.cuda(), last_state.cuda() 55 | 56 | init_graph_features(G1, graph_feat_size, node_feat_size, edge_feat_size, cuda=True, bs = 200) 57 | load_graph_features(G1, action, last_state,bs=200) 58 | in_normalizer.normalize(G1) 59 | load_graph_features(G1, action, delta_state, bs=200) 60 | out_normalizer.normalize(G1) 61 | ''' 62 | torch.save({"in_normalizer":in_normalizer, "out_normalizer":out_normalizer}, 'normalize.pth') 63 | -------------------------------------------------------------------------------- /test_normalizer.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import torch" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 2, 17 | "metadata": { 18 | "collapsed": true 19 | }, 20 | "outputs": [], 21 | "source": [ 22 | "normalizers = torch.load('normalize.pth')\n", 23 | "in_normalizer = normalizers['in_normalizer']\n", 24 | "out_normalizer = normalizers['out_normalizer']" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 4, 30 | "metadata": {}, 31 | "outputs": [ 32 | { 33 | "name": "stdout", 34 | "output_type": "stream", 35 | "text": [ 36 | "tensor([[0.0843, 0.0817, 1.2791, 0.0308, 0.0308, 1.1275],\n", 37 | " [0.0520, 0.0512, 1.2009, 0.0272, 0.0279, 1.0055],\n", 38 | " [0.0229, 0.0227, 1.2413, 0.0260, 0.0261, 0.9713],\n", 39 | " [0.0230, 0.0227, 1.2437, 0.0260, 0.0258, 0.9775],\n", 40 | " [0.0521, 0.0514, 1.2184, 0.0275, 0.0274, 1.0083],\n", 41 | " [0.0836, 0.0809, 1.3215, 0.0308, 0.0308, 1.1443]], device='cuda:0')\n" 42 | ] 43 | } 44 | ], 45 | "source": [ 46 | "std = in_normalizer.get_std()" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 6, 52 | "metadata": {}, 53 | "outputs": [ 54 | { 55 | "data": { 56 | "text/plain": [ 57 | "tensor([[0.0843, 0.0817, 1.2791],\n", 58 | " [0.0520, 0.0512, 1.2009],\n", 59 | " [0.0229, 0.0227, 1.2413],\n", 60 | " [0.0230, 0.0227, 1.2437],\n", 61 | " [0.0521, 0.0514, 1.2184],\n", 62 | " [0.0836, 0.0809, 1.3215]], device='cuda:0')" 63 | ] 64 | }, 65 | "execution_count": 6, 66 | "metadata": {}, 67 | "output_type": "execute_result" 68 | } 69 | ], 70 | "source": [ 71 | "std[:6,:3]" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": 7, 77 | "metadata": {}, 78 | "outputs": [ 79 | { 80 | "data": { 81 | "text/plain": [ 82 | "tensor([[0.0308, 0.0308, 1.1275],\n", 83 | " [0.0272, 0.0279, 1.0055],\n", 84 | " [0.0260, 0.0261, 0.9713],\n", 85 | " [0.0260, 0.0258, 0.9775],\n", 86 | " [0.0275, 0.0274, 1.0083],\n", 87 | " [0.0308, 0.0308, 1.1443]], device='cuda:0')" 88 | ] 89 | }, 90 | "execution_count": 7, 91 | "metadata": {}, 92 | "output_type": "execute_result" 93 | } 94 | ], 95 | "source": [ 96 | "std[:6,3:]" 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": null, 102 | "metadata": { 103 | "collapsed": true 104 | }, 105 | "outputs": [], 106 | "source": [] 107 | } 108 | ], 109 | "metadata": { 110 | "kernelspec": { 111 | "display_name": "Python (py35)", 112 | "language": "python", 113 | "name": "py35" 114 | }, 115 | "language_info": { 116 | "codemirror_mode": { 117 | "name": "ipython", 118 | "version": 3 119 | }, 120 | "file_extension": ".py", 121 | "mimetype": "text/x-python", 122 | "name": "python", 123 | "nbconvert_exporter": "python", 124 | "pygments_lexer": "ipython3", 125 | "version": "3.5.4" 126 | } 127 | }, 128 | "nbformat": 4, 129 | "nbformat_minor": 2 130 | } 131 | -------------------------------------------------------------------------------- /train_gn.py: -------------------------------------------------------------------------------- 1 | import torch.utils.data as data 2 | from torch.utils.data import DataLoader 3 | import numpy as np 4 | import networkx as nx 5 | import torch.optim as optim 6 | import matplotlib.pyplot as plt 7 | from gn_models import init_graph_features, FFGN 8 | import torch 9 | from tensorboardX import SummaryWriter 10 | from datetime import datetime 11 | import os 12 | from dataset import SwimmerDataset 13 | from utils import * 14 | from tqdm import tqdm 15 | import argparse 16 | 17 | if __name__ == "__main__": 18 | 19 | parser = argparse.ArgumentParser() 20 | parser.add_argument('--model', type=str, default = '', help='model path') 21 | opt = parser.parse_args() 22 | print(opt) 23 | 24 | dset = SwimmerDataset('swimmer.npy') 25 | dset_eval = SwimmerDataset('swimmer_test.npy') 26 | use_cuda = True 27 | 28 | dl = DataLoader(dset, batch_size=200, num_workers=0, drop_last=True) 29 | dl_eval = DataLoader(dset_eval, batch_size=200, num_workers=0, drop_last=True) 30 | 31 | G1 = nx.path_graph(6).to_directed() 32 | G_target = nx.path_graph(6).to_directed() 33 | #nx.draw(G1) 34 | #plt.show() 35 | node_feat_size = 6 36 | edge_feat_size = 3 37 | graph_feat_size = 10 38 | gn = FFGN(graph_feat_size, node_feat_size, edge_feat_size).cuda() 39 | if opt.model != '': 40 | gn.load_state_dict(torch.load(opt.model)) 41 | 42 | optimizer = optim.Adam(gn.parameters(), lr = 1e-4) 43 | schedular = optim.lr_scheduler.StepLR(optimizer, 5e4, gamma = 0.975) 44 | savedir = os.path.join('./logs','runs', 45 | datetime.now().strftime('%B%d_%H:%M:%S')) 46 | writer = SummaryWriter(savedir) 47 | step = 0 48 | 49 | normalizers = torch.load('normalize.pth') 50 | in_normalizer = normalizers['in_normalizer'] 51 | out_normalizer = normalizers['out_normalizer'] 52 | std = in_normalizer.get_std() 53 | 54 | for epoch in range(300): 55 | for i,data in tqdm(enumerate(dl), total = len(dset) / 200 + 1): 56 | optimizer.zero_grad() 57 | action, delta_state, last_state = data 58 | action, delta_state, last_state = action.float(), delta_state.float(), last_state.float() 59 | if use_cuda: 60 | action, delta_state, last_state = action.cuda(), delta_state.cuda(), last_state.cuda() 61 | 62 | init_graph_features(G1, graph_feat_size, node_feat_size, edge_feat_size, cuda=True, bs = 200) 63 | load_graph_features(G1, action, last_state, delta_state,bs=200, noise = 0.02, std = std) 64 | G_out = gn(in_normalizer.normalize(G1)) 65 | 66 | init_graph_features(G_target, graph_feat_size, node_feat_size, edge_feat_size, cuda=True, bs=200) 67 | load_graph_features(G_target, action, delta_state, None, bs=200, norm = False, noise = 0) 68 | G_target_normalized = out_normalizer.normalize(G_target) 69 | 70 | loss = build_graph_loss2(G_out, G_target_normalized) 71 | loss.backward() 72 | if step % 10 == 0: 73 | writer.add_scalar('loss', loss.data.item(), step) 74 | step += 1 75 | for param in gn.parameters(): 76 | if not param.grad is None: 77 | param.grad.clamp_(-3,3) 78 | 79 | optimizer.step() 80 | schedular.step() 81 | if step % 10000 == 0: 82 | torch.save( 83 | gn.state_dict(), 84 | savedir + 85 | '/model_{}.pth'.format(step)) 86 | iter = 0 87 | sum_loss = 0 88 | 89 | #evaluation loop, done every epoch 90 | 91 | for i,data in tqdm(enumerate(dl_eval)): 92 | action, delta_state, last_state = data 93 | action, delta_state, last_state = action.float(), delta_state.float(), last_state.float() 94 | if use_cuda: 95 | action, delta_state, last_state = action.cuda(), delta_state.cuda(), last_state.cuda() 96 | 97 | init_graph_features(G1, graph_feat_size, node_feat_size, edge_feat_size, cuda=True, bs = 200) 98 | load_graph_features(G1, action, last_state, delta_state, bs=200, noise = 0) 99 | G_out = gn(in_normalizer.normalize(G1)) 100 | 101 | init_graph_features(G_target, graph_feat_size, node_feat_size, edge_feat_size, cuda=True, bs=200) 102 | load_graph_features(G_target, action, delta_state, None, bs=200, norm = False, noise = 0) 103 | G_target_normalized = out_normalizer.normalize(G_target) 104 | 105 | loss = build_graph_loss2(G_out, G_target_normalized) 106 | sum_loss += loss.data.item() 107 | iter += 1 108 | 109 | writer.add_scalar('loss_eval', sum_loss / float(iter), step) -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import numpy as np 3 | 4 | def load_graph_features(G, action, state, delta_state, bs = 1, norm = True, noise = 0.003, std = None): 5 | 6 | pos = state[:, 5:5+18].view(-1,6,3) 7 | if noise > 0: 8 | pos_noise = torch.randn(pos.size()).cuda() * noise * std[:, :3] 9 | else: 10 | pos_noise = 0 11 | 12 | 13 | pos += pos_noise 14 | if noise > 0: 15 | delta_state[:, 5:5+18] -= pos_noise.view(-1, 18) 16 | 17 | joints = pos[:,1:,-1] - pos[:,:-1,-1] 18 | joints[joints > np.pi] -= np.pi * 2 19 | joints[joints < -np.pi] += np.pi * 2 20 | 21 | if norm: 22 | center_pos = torch.mean(pos[:,:,:2], dim = 1, keepdim = True) 23 | pos[:,:,:2] -= center_pos 24 | 25 | vel = state[:, 5+18:5+36].view(-1,6,3) 26 | 27 | if noise > 0: 28 | vel_noise = torch.randn(vel.size()).cuda() * noise * std[:, 3:] 29 | else: 30 | vel_noise = 0 31 | vel += vel_noise 32 | 33 | if noise > 0: 34 | delta_state[:, 5+18:5+36] -= vel_noise.view(-1, 18) 35 | 36 | for node in G.nodes(): 37 | #print(node) 38 | G.nodes[node]['feat'][:,:3] = pos[:,node] 39 | G.nodes[node]['feat'][:, 3:] = vel[:, node] 40 | 41 | for edge in G.edges(): 42 | if edge[0] < edge[1]: 43 | G[edge[0]][edge[1]]['feat'][:,0] = -1 44 | else: 45 | G[edge[0]][edge[1]]['feat'][:, 0] = 1 46 | 47 | m = min(edge) 48 | G[edge[0]][edge[1]]['feat'][:, 1] = joints[:,m] 49 | G[edge[0]][edge[1]]['feat'][:, 2] = action[:,m] 50 | return G 51 | 52 | 53 | def build_graph_loss(G, state): 54 | loss = 0 55 | n_nodes = len(G) 56 | 57 | pos = state[:, 5:5 + 18].view(-1, 6, 3) 58 | pos[:,:,2] -= (pos[:,:,2] > np.pi).float() * np.pi * 2 59 | pos[:, :, 2] += (pos[:, :, 2] < -np.pi).float() * np.pi * 2 60 | 61 | vel = state[:, 5 + 18:5 + 36].view(-1, 6, 3) 62 | 63 | for node in G.nodes(): 64 | loss += torch.mean((G.nodes[node]['feat'][:,:3] - pos[:,node]) ** 2) 65 | loss += torch.mean((G.nodes[node]['feat'][:, 3:] - vel[:, node]) ** 2) 66 | 67 | loss /= n_nodes 68 | return loss 69 | 70 | def build_graph_loss2(G, H): 71 | loss = 0 72 | n_nodes = len(G) 73 | for node in G.nodes(): 74 | loss += torch.mean((G.nodes[node]['feat'][:,:3] - H.nodes[node]['feat'][:,:3]) ** 2) 75 | loss += torch.mean((G.nodes[node]['feat'][:, 3:] - H.nodes[node]['feat'][:,3:]) ** 2) 76 | 77 | loss /= n_nodes 78 | return loss 79 | 80 | def init_graph_features(G, graph_feat_size, node_feat_size, edge_feat_size, bs=1, cuda=False): 81 | if cuda: 82 | G.graph['feat'] = torch.zeros(bs, graph_feat_size).cuda() 83 | for node in G.nodes(): 84 | G.nodes[node]['feat'] = torch.zeros(bs, node_feat_size).cuda() 85 | for edge in G.edges(): 86 | G[edge[0]][edge[1]]['feat'] = torch.zeros(bs, edge_feat_size).cuda() 87 | else: 88 | G.graph['feat'] = torch.zeros(bs, graph_feat_size) 89 | for node in G.nodes(): 90 | G.nodes[node]['feat'] = torch.zeros(bs, node_feat_size) 91 | for edge in G.edges(): 92 | G[edge[0]][edge[1]]['feat'] = torch.zeros(bs, edge_feat_size) 93 | 94 | 95 | def detach(G): 96 | G.graph['feat'] = G.graph['feat'].detach() 97 | for node in G.nodes(): 98 | G.nodes[node]['feat'] = G.nodes[node]['feat'].detach() 99 | for edge in G.edges(): 100 | G[edge[0]][edge[1]]['feat'] = G[edge[0]][edge[1]]['feat'].detach() 101 | return G -------------------------------------------------------------------------------- /visualize.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 63, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import numpy as np\n", 12 | "import matplotlib.pyplot as plt\n", 13 | "%matplotlib inline" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 64, 19 | "metadata": { 20 | "collapsed": true 21 | }, 22 | "outputs": [], 23 | "source": [ 24 | "data = np.load('./swimmer2.npy')" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 65, 30 | "metadata": { 31 | "collapsed": true 32 | }, 33 | "outputs": [], 34 | "source": [ 35 | "def decode(data):\n", 36 | " pos = data[10:28].reshape(6,3)\n", 37 | " vel = data[28:].reshape(6,3)\n", 38 | " return pos, vel" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": 66, 44 | "metadata": {}, 45 | "outputs": [ 46 | { 47 | "data": { 48 | "image/png": "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\n", 49 | "text/plain": [ 50 | "
" 51 | ] 52 | }, 53 | "metadata": {}, 54 | "output_type": "display_data" 55 | }, 56 | { 57 | "data": { 58 | "image/png": "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\n", 59 | "text/plain": [ 60 | "
" 61 | ] 62 | }, 63 | "metadata": {}, 64 | "output_type": "display_data" 65 | }, 66 | { 67 | "data": { 68 | "image/png": "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\n", 69 | "text/plain": [ 70 | "
" 71 | ] 72 | }, 73 | "metadata": {}, 74 | "output_type": "display_data" 75 | }, 76 | { 77 | "data": { 78 | "image/png": "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\n", 79 | "text/plain": [ 80 | "
" 81 | ] 82 | }, 83 | "metadata": {}, 84 | "output_type": "display_data" 85 | } 86 | ], 87 | "source": [ 88 | "pos = None\n", 89 | "vel = None\n", 90 | "for frame in range(0,40,10):\n", 91 | " pos, vel = decode(data[1,frame])\n", 92 | " #print(pos, vel)\n", 93 | " plt.figure()\n", 94 | " for i in range(6):\n", 95 | " angle = pos[i,2]\n", 96 | " x = pos[i,0]\n", 97 | " y = pos[i,1]\n", 98 | " r = 0.05\n", 99 | " dy = np.cos(angle) * r\n", 100 | " dx = - np.sin(angle) * r\n", 101 | " #plt.figure()\n", 102 | " plt.plot([x - dx,x + dx], [y - dy,y + dy])\n", 103 | " plt.axis('equal')" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": 67, 109 | "metadata": {}, 110 | "outputs": [], 111 | "source": [ 112 | "for frame in range(0,10):\n", 113 | " pos, vel = decode(data[1,frame])\n", 114 | " next_pos, next_vel = decode(data[1, frame + 1])\n", 115 | " " 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": 68, 121 | "metadata": {}, 122 | "outputs": [], 123 | "source": [ 124 | "dt = 0.02\n", 125 | "evel = (next_pos - pos) / dt " 126 | ] 127 | } 128 | ], 129 | "metadata": { 130 | "kernelspec": { 131 | "display_name": "Python (flex)", 132 | "language": "python", 133 | "name": "flex" 134 | }, 135 | "language_info": { 136 | "codemirror_mode": { 137 | "name": "ipython", 138 | "version": 3 139 | }, 140 | "file_extension": ".py", 141 | "mimetype": "text/x-python", 142 | "name": "python", 143 | "nbconvert_exporter": "python", 144 | "pygments_lexer": "ipython3", 145 | "version": "3.6.6" 146 | } 147 | }, 148 | "nbformat": 4, 149 | "nbformat_minor": 2 150 | } 151 | --------------------------------------------------------------------------------