Last active
March 27, 2024 07:43
-
-
Save SPOREIII/586d3813d4ef2e38e47f36899cf1621f to your computer and use it in GitHub Desktop.
An example of differentiable programming using Taichi-Lang
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "cells": [ | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "检验taichi与torch的协同,将taichi编写的仿真器封装在'torch.autograd.Function'中,使用torch编写控制器并管理计算图,实现可微物理仿真的反向传播\n", | |
| "\n", | |
| "- 目标:给定小球初始状态(位置与初速度),控制质量点移动至目标位置且末速度为0\n", | |
| "- 控制器\n", | |
| " - 输入:当前位置(x, y),当前速度(x, y),当前位置与目标位置的欧式距离,当前位置相对目标位置的偏移量(x, y)\n", | |
| " - 输出:当前时刻小球的加速度(x, y)\n", | |
| "- 优化目标\n", | |
| " - 极小化结束位置与目标位置的欧式距离\n", | |
| " - 极小化结束位置的速度\n", | |
| " - 极小化输出的加速度之和(外力做功最少)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "定义参数" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 1, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "[Taichi] version 1.6.0, llvm 15.0.1, commit f1c6fbbd, win, python 3.10.12\n", | |
| "[Taichi] Starting on arch=cuda\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "from tqdm import tqdm\n", | |
| "import taichi as ti\n", | |
| "import numpy as np\n", | |
| "import torch\n", | |
| "from torch import nn\n", | |
| "import time\n", | |
| "\n", | |
| "ti.init(arch=ti.cuda, debug=True)\n", | |
| "dt = 0.2\n", | |
| "cto_opt_iter=50\n", | |
| "sim_step = 50\n", | |
| "act_step = 5\n", | |
| "vloss_weight = 1.0\n", | |
| "# device = 'cpu'\n", | |
| "device = \"cuda:0\"" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "定义控制网络(MLP)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 2, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "class NeuralNetwork(nn.Module):\n", | |
| " def __init__(self):\n", | |
| " super().__init__()\n", | |
| " self.linear_relu_stack = nn.Sequential(\n", | |
| " nn.Linear(7, 16),\n", | |
| " # nn.Dropout(0.2),\n", | |
| " # nn.ReLU(),\n", | |
| " # nn.Linear(16, 16),\n", | |
| " # nn.Dropout(0.2),\n", | |
| " # nn.ReLU(),\n", | |
| " # nn.Linear(16, 32),\n", | |
| " # nn.ReLU(),\n", | |
| " # nn.Linear(32, 64),\n", | |
| " # nn.ReLU(),\n", | |
| " # nn.Linear(64, 128),\n", | |
| " # nn.ReLU(),\n", | |
| " # nn.Linear(128, 16),\n", | |
| " # nn.Dropout(0.2),\n", | |
| " # nn.ReLU(),\n", | |
| " # nn.Linear(32, 32),\n", | |
| " # nn.ReLU(),\n", | |
| " # nn.Linear(32, 8),\n", | |
| " nn.ReLU(),\n", | |
| " nn.Linear(16, 2),\n", | |
| " # nn.Dropout(0.2),\n", | |
| " # nn.Tanh()\n", | |
| " )\n", | |
| "\n", | |
| " def forward(self, x, v, d, g):\n", | |
| " inp = torch.cat((x,v,d,g))\n", | |
| " inp = self.linear_relu_stack(inp)\n", | |
| " inp = torch.clamp(nn.LeakyReLU(0.1)(inp), -1, 1)\n", | |
| " return inp" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "定义仿真器在每个时间步的迭代计算\n", | |
| "- 在前向过程中载入小球速度与位移\n", | |
| "- 在后向过程中清零各个变量的梯度,(从torch中)载入输出变量的梯度" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 3, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "@ti.data_oriented\n", | |
| "class Simulator():\n", | |
| " def __init__(self):\n", | |
| " self.x_in = ti.Vector.field(2, ti.f32, shape=(), needs_grad=True)\n", | |
| " self.v_in = ti.Vector.field(2, ti.f32, shape=(), needs_grad=True)\n", | |
| " self.x_out = ti.Vector.field(2, ti.f32, shape=(), needs_grad=True)\n", | |
| " self.v_out = ti.Vector.field(2, ti.f32, shape=(), needs_grad=True)\n", | |
| " self.goal_x = ti.Vector.field(2, ti.f32, shape=(), needs_grad=True)\n", | |
| " self.goal_v = ti.Vector.field(2, ti.f32, shape=(), needs_grad=True)\n", | |
| " self.act = ti.Vector.field(2, ti.f32, shape=(), needs_grad=True)\n", | |
| " \n", | |
| " @ti.kernel\n", | |
| " def _step(self):\n", | |
| " self.v_out[None] = self.v_in[None] + self.act[None] * dt\n", | |
| " self.x_out[None] = self.x_in[None] + self.v_out[None] * dt\n", | |
| " \n", | |
| " def update(self, x_in, v_in, act):\n", | |
| " self.x_in.from_torch(x_in)\n", | |
| " self.v_in.from_torch(v_in)\n", | |
| " self.act.from_torch(act)\n", | |
| " self._step()\n", | |
| " return self.x_out.to_torch(device=device),\\\n", | |
| " self.v_out.to_torch(device=device)\n", | |
| "\n", | |
| " def backward(self, x_in, v_in, act, x_grad_in, v_grad_in):\n", | |
| " self.x_out.grad.fill(0.0)\n", | |
| " self.v_out.grad.fill(0.0)\n", | |
| " self.goal_x.grad.fill(0.0)\n", | |
| " self.goal_v.grad.fill(0.0)\n", | |
| " self.x_in.grad.fill(0.0)\n", | |
| " self.v_in.grad.fill(0.0)\n", | |
| " self.act.grad.fill(0.0)\n", | |
| "\n", | |
| " self.x_out.grad.from_torch(x_grad_in)\n", | |
| " self.v_out.grad.from_torch(v_grad_in)\n", | |
| " self.x_in.from_torch(x_in)\n", | |
| " self.v_in.from_torch(v_in)\n", | |
| " self.act.from_torch(act)\n", | |
| " \n", | |
| " self._step.grad()\n", | |
| " return self.x_in.grad.to_torch(device=device),\\\n", | |
| " self.v_in.grad.to_torch(device=device), \\\n", | |
| " self.act.grad.to_torch(device=device)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "将仿真器封装在'torch.autograd.Function'内" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 4, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "class Sim_module(torch.autograd.Function):\n", | |
| "\n", | |
| " @staticmethod\n", | |
| " def forward(ctx, x_in, v_in, act):\n", | |
| " ctx.save_for_backward(x_in, v_in, act)\n", | |
| " return sim.update(x_in, v_in, act)\n", | |
| "\n", | |
| " @staticmethod\n", | |
| " @torch.autograd.function.once_differentiable\n", | |
| " def backward(ctx, x_grad_in, v_grad_in):\n", | |
| " x_in, v_in, act = ctx.saved_tensors\n", | |
| " return sim.backward(x_in, v_in, act, x_grad_in, v_grad_in)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "定义训练方法" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 5, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "def train_controller(init, goal):\n", | |
| " controller.train()\n", | |
| " pbar = tqdm(range(cto_opt_iter))\n", | |
| " for _ in pbar:\n", | |
| " x = torch.tensor(init['pos'], requires_grad=True).cuda()\n", | |
| " v = torch.tensor(init['vel'], requires_grad=True).cuda()\n", | |
| " goal_pos = torch.tensor(goal['pos'], requires_grad=True).cuda()\n", | |
| " goal_vel = torch.tensor(goal['vel'], requires_grad=True).cuda()\n", | |
| " a_sum = 0\n", | |
| " for _ in range(sim_step):\n", | |
| " d = torch.unsqueeze((x - goal_pos).norm(), dim=0)\n", | |
| " \n", | |
| " a = controller(x, v, d, x-goal_pos)\n", | |
| " for _ in range(act_step):\n", | |
| " x, v = Sim_module.apply(x, v, a)\n", | |
| " a_sum += a.norm()\n", | |
| " \n", | |
| " loss = (x - goal_pos).norm() + (v - goal_vel).norm()*vloss_weight + a_sum*0.2\n", | |
| " pbar.set_description(f\"Loss: {loss.item():.4f}\")\n", | |
| " loss.backward()\n", | |
| " nn.utils.clip_grad_norm_(controller.parameters(), max_norm=1)\n", | |
| " optimizer.step()\n", | |
| " optimizer.zero_grad()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "定义在新任务上运行仿真并可视化的方法" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 6, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "def visualize_trajectory(init, goal):\n", | |
| " controller.eval()\n", | |
| " gui = ti.GUI('view', res=(400, 400))\n", | |
| " x = torch.tensor(init['pos'], requires_grad=True).cuda()\n", | |
| " v = torch.tensor(init['vel'], requires_grad=True).cuda()\n", | |
| " goal_vis = np.expand_dims(goal['pos']/50, axis=0) + [0.5, 0.5]\n", | |
| " init_vis = np.expand_dims(init['pos']/50, axis=0) + [0.5, 0.5]\n", | |
| "\n", | |
| " goal_vel = torch.tensor(goal['vel'], requires_grad=True).cuda()\n", | |
| " goal_pos = torch.tensor(goal['pos'], requires_grad=True).cuda()\n", | |
| "\n", | |
| " pbar = tqdm(range(sim_step))\n", | |
| " a_sum = 0\n", | |
| " for _ in pbar:\n", | |
| " d = torch.unsqueeze((x - goal_pos).norm(), dim=0)\n", | |
| " a = controller(x, v, d, x-goal_pos)\n", | |
| " for _ in range(act_step):\n", | |
| " x, v = Sim_module.apply(x, v, a)\n", | |
| " a_sum += a.norm()\n", | |
| "\n", | |
| " loss = (x - goal_pos).norm() + (v - goal_vel).norm()*vloss_weight + a_sum*0.2\n", | |
| " pbar.set_description(f\"Loss: {loss.item():.4f}\")\n", | |
| "\n", | |
| " gui.circles(goal_vis, radius=5, color=0xff0000)\n", | |
| " gui.circles(init_vis, radius=5, color=0xFFFFFF)\n", | |
| " gui.circles(x.detach().clone().cpu().unsqueeze(dim=0).numpy()/50 + [0.5, 0.5], \n", | |
| " radius=5, color=0xFFFFFF)\n", | |
| " gui.show()\n", | |
| " time.sleep(0.02)\n", | |
| " gui.running = False" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "定义训练数据集的产生方法,包含三个难度的控制任务\n", | |
| "- level0:随机初始位置,初速度为0,固定目标位置\n", | |
| "- level1:随机初始位置,初速度为0,随机目标位置\n", | |
| "- level2:随机初始位置,随机初速度,随机目标位置" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 7, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "def generate_train_sample(level=0):\n", | |
| " if level == 0:\n", | |
| " init = {\n", | |
| " 'pos': np.random.uniform(-10, 10, size=(2)).astype(np.float32),\n", | |
| " 'vel': np.random.uniform(0, 0, size=(2)).astype(np.float32)\n", | |
| " }\n", | |
| " goal = {\n", | |
| " 'pos': np.random.uniform(0, 0, size=(2)).astype(np.float32),\n", | |
| " 'vel': np.array([0.0, 0.0], dtype=np.float32)\n", | |
| " }\n", | |
| " if level == 1:\n", | |
| " init = {\n", | |
| " 'pos': np.random.uniform(-10, 10, size=(2)).astype(np.float32),\n", | |
| " 'vel': np.random.uniform(0, 0, size=(2)).astype(np.float32)\n", | |
| " }\n", | |
| " goal = {\n", | |
| " 'pos': np.random.uniform(-10, 10, size=(2)).astype(np.float32),\n", | |
| " 'vel': np.array([0.0, 0.0], dtype=np.float32)\n", | |
| " }\n", | |
| " if level == 2:\n", | |
| " init = {\n", | |
| " 'pos': np.random.uniform(-10, 10, size=(2)).astype(np.float32),\n", | |
| " 'vel': np.random.uniform(-5, 5, size=(2)).astype(np.float32)\n", | |
| " }\n", | |
| " goal = {\n", | |
| " 'pos': np.random.uniform(-10, 10, size=(2)).astype(np.float32),\n", | |
| " 'vel': np.array([0.0, 0.0], dtype=np.float32)\n", | |
| " }\n", | |
| "\n", | |
| " return init, goal" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "实例化定义的对象" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 8, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "sim = Simulator()\n", | |
| "controller = NeuralNetwork().cuda()\n", | |
| "optimizer = torch.optim.Adam(controller.parameters(), lr=0.005)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "训练控制器" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 9, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 0%| | 0/50 [00:00<?, ?it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "Loss: 1318.6252: 100%|██████████| 50/50 [01:29<00:00, 1.79s/it]\n", | |
| "Loss: 6.5467: 100%|██████████| 50/50 [01:24<00:00, 1.69s/it] \n", | |
| "Loss: 2.7758: 100%|██████████| 50/50 [01:28<00:00, 1.78s/it]\n", | |
| "Loss: 0.4294: 100%|██████████| 50/50 [01:24<00:00, 1.68s/it] \n", | |
| "Loss: 0.2740: 100%|██████████| 50/50 [01:11<00:00, 1.43s/it]\n", | |
| "Loss: 0.2614: 100%|██████████| 50/50 [01:11<00:00, 1.42s/it]\n", | |
| "Loss: 5.9343: 100%|██████████| 50/50 [01:16<00:00, 1.53s/it]\n", | |
| "Loss: 7.3399: 10%|█ | 5/50 [00:10<01:28, 1.97s/it]" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "for _ in range(5):\n", | |
| " init, goal = generate_train_sample(0)\n", | |
| " train_controller(init, goal)\n", | |
| " # visualize_trajectory(init, goal)\n", | |
| "for _ in range(5):\n", | |
| " init, goal = generate_train_sample(1)\n", | |
| " train_controller(init, goal)\n", | |
| "for _ in range(5):\n", | |
| " init, goal = generate_train_sample(2)\n", | |
| " train_controller(init, goal)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "测试控制器" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 11, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 0%| | 0/50 [00:00<?, ?it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "Loss: 36.1536: 100%|██████████| 50/50 [00:01<00:00, 30.02it/s] \n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "init = {'pos':np.array([10.0, 7.0], dtype=np.float32), \n", | |
| " 'vel':np.array([-15.0, 14.0], dtype=np.float32)}\n", | |
| "goal = {'pos':np.array([-10.0, -10.0], dtype=np.float32), \n", | |
| " 'vel':np.array([0.0, 0.0], dtype=np.float32)}\n", | |
| "visualize_trajectory(init, goal)" | |
| ] | |
| } | |
| ], | |
| "metadata": { | |
| "kernelspec": { | |
| "display_name": "taichi", | |
| "language": "python", | |
| "name": "python3" | |
| }, | |
| "language_info": { | |
| "codemirror_mode": { | |
| "name": "ipython", | |
| "version": 3 | |
| }, | |
| "file_extension": ".py", | |
| "mimetype": "text/x-python", | |
| "name": "python", | |
| "nbconvert_exporter": "python", | |
| "pygments_lexer": "ipython3", | |
| "version": "3.10.12" | |
| }, | |
| "orig_nbformat": 4 | |
| }, | |
| "nbformat": 4, | |
| "nbformat_minor": 2 | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment