ぱたへね

はてなダイアリーはrustの色分けができないのでこっちに来た

GenesisでURロボットを表示する

Genesisのインストール

pytorchをインストールした後、ドキュメント通りにインストールするとtaichiでエラーがでました。 taichiだけ個別にインストールしても駄目。

pip install taichi-nightly
ERROR: Could not find a version that satisfies the requirement taichi-nightly (from versions: none)
ERROR: No matching distribution found for taichi-nightly

原因は使っていたPythonのバージョンでした。3.13はtaichiが対応していないので駄目、3.12に下げれば無事インストール出来ました。

URロボットのURDFをダウンロードする。

officialなURDFは見つからなかったので、ここからgit cloneしました。

github.com

Genesisで動かす。

Genesisが正しくインストールされていれば、サンプルの一行を変えるだけで表示されます。

import genesis as gs  
gs.init(backend=gs.gpu)  

scene = gs.Scene(show_viewer=True)  
plane = scene.add_entity(gs.morphs.Plane())  
franka = scene.add_entity(  
    # ここにURDFファイルのパスを指定
    gs.morphs.URDF(file='D:/home/myproj/genesis/pybullet_ur5_gripper/robots/urdf/ur5e.urdf'),  
)  
  
scene.build()  
  
for i in range(1000):  
    input()  
    scene.step()

最後にinput() を入れています。 すぐシミュレーションが終わってしまうので、キー入力待ちにして、動きを確認出来るようにしました。

実行すると無事URロボットが表示されました。

WSL2+MobaXtermでGENESISが動かなかった話

結論を先に書くとMobaXtermのX Serverが問題なので他のターミナルを使いましょう。

GENESIS自体のインストールはここの通りやればOK。

github.com

蛇のチュートリアルを実行すると、こんなエラーがでたり、バージョンによっては別のエラーが出ます。

[Genesis] [22:13:32] [INFO] Building scene <c9866d7>...
[Genesis] [22:13:35] [INFO] Compiling simulation kernels...
[Genesis] [22:13:40] [INFO] Building visualizer...
Exception in thread Thread-2 (_init_and_start_app):
Traceback (most recent call last):
  File "/home/natu/.pyenv/versions/3.12.8/lib/python3.12/threading.py", line 1075, in _bootstrap_inner
    self.run()
  File "/home/natu/.pyenv/versions/3.12.8/lib/python3.12/threading.py", line 1012, in run
    self._target(*self._args, **self._kwargs)
  File "/home/natu/myproj/genesis/Genesis/genesis/ext/pyrender/viewer.py", line 1138, in _init_and_start_app
    super(Viewer, self).__init__(
  File "/home/natu/myproj/genesis/.venv/lib/python3.12/site-packages/pyglet/window/xlib/__init__.py", line 167, in __init__
    super().__init__(*args, **kwargs)
  File "/home/natu/myproj/genesis/.venv/lib/python3.12/site-packages/pyglet/window/__init__.py", line 533, in __init__
    context = config.create_context(gl.current_context)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/natu/myproj/genesis/.venv/lib/python3.12/site-packages/pyglet/gl/xlib.py", line 117, in create_context
    return XlibContext(self, share)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/natu/myproj/genesis/.venv/lib/python3.12/site-packages/pyglet/gl/xlib.py", line 152, in __init__
    raise gl.ContextException(msg)

検索すればいろいろ対応策が出てきますが、どれもこれも解決せず。 ダメ元でPyCharmでWSLに入り、PyCharmのターミナルから実行すると、無事動作しました。

VS CodeでWSLに入りターミナルから実行しても同じように動きます。

Rust+Burnで線形回帰

データを作る所以外はほとんどCopilotまかせなので、いまいち実感がない。

use plotly::common::Mode;  
use burn::tensor::Tensor;  
use burn::backend::{Autodiff, Wgpu};  
use burn::backend::wgpu::WgpuDevice;  
  
type Backend = Wgpu;  
type AutoDIffBackend = Autodiff<Backend>;  
use plotly::Scatter;  
  
fn toy_data() -> (Vec<f32>, Vec<f32>) {  
    // 0.0~1.0の範囲でランダムなデータを100個生成してListを作成  
    //let device = Default::default();  
    let x = (0..100).map(|_| rand::random::<f32>()).collect::<Vec<f32>>();  
  
    // ノイズeを0.0から1.0の範囲で100個生成  
    let e = (0..100).map(|_| rand::random::<f32>()).collect::<Vec<f32>>();  
  
    // y = 2 * x + 5 + e  
    let y = x.iter().zip(e.iter()).map(|(x, e)| 2.0 * x + 5.0 + e).collect::<Vec<f32>>();  
  
    (x, y)  
}  
  
fn predict(x:&Vec<f32>,  w:&Tensor<AutoDIffBackend, 1>, b:&Tensor<AutoDIffBackend, 1>, device:&WgpuDevice ) -> Tensor<AutoDIffBackend, 1> {  
    let x = Tensor::<AutoDIffBackend, 1>::from_floats(x.as_slice(), &device);  
    let y = x * w.clone() + b.clone();  
    y  
}  
  
fn mean_squared_error(y: &Vec<f32>, y_hat: &Tensor<AutoDIffBackend, 1>) -> Tensor<AutoDIffBackend, 1> {  
    let y_hat = y_hat.clone();  
    let n = y.len() as f32;  
    let y = Tensor::<AutoDIffBackend, 1>::from_floats(y.as_slice(), &y_hat.device());  
    let loss = (y - y_hat).powf_scalar(2.0).sum() / n as f32;  
    loss  
}  
  
fn main() {  
    // 0.0~1.0の範囲でランダムなデータを100個生成してListを作成  
    let device = Default::default();  
  
    let (x, y) = toy_data();  
  
    let lr = 0.1;  
    let iter = 100;  
  
    let mut new_w = 0.0;  
    let mut new_b = 2.0;  
  
    for i in 0..iter {  
        let w = Tensor::<AutoDIffBackend, 1>::from_floats([new_w], &device).require_grad();  
        let b = Tensor::<AutoDIffBackend, 1>::from_floats([new_b], &device).require_grad();  
  
        let y_hat = predict(&x, &w, &b, &device);  
        let loss = mean_squared_error(&y, &y_hat);  
  
        let mut gradients = loss.backward();  
  
        let tensor_grad_w = w.grad_remove(&mut gradients);  
        let tensor_grad_b = b.grad_remove(&mut gradients);  
  
        new_w = w.into_scalar() - tensor_grad_w.unwrap().into_scalar() * lr;  
        new_b = b.into_scalar() - tensor_grad_b.unwrap().into_scalar() * lr;  
  
        if i % 10 == 0 {  
            println!("iter: {} loss = {}", i, loss.into_scalar());  
        }  
    }  
  
    println!("w = {}, b = {}", new_w, new_b);  
  
    let trace = Scatter::new(x.clone(), y.clone()).mode(Mode::Markers);  
    let mut plot = plotly::Plot::new();  
    plot.add_trace(trace);  
  
    let y_hat = x.iter().map(|x| new_w * x + new_b).collect::<Vec<f32>>();  
  
    let trace = Scatter::new(x.clone(), y_hat.clone()).mode(Mode::Lines);  
    plot.add_trace(trace);  
    plot.show();  
  
}

実行結果

Rust+Burnで勾配法

ローゼンブロック関数に対する勾配法のRust実装です。

元ネタはゼロから作るDeep Learning⑤から。

github.com

ソースはこれ。

use burn::tensor::Tensor;  
use burn::backend::{Autodiff, Wgpu};  
  
type Backend = Wgpu;  
type AutoDIffBackend = Autodiff<Backend>;  
  
  
fn rosenbrok(x0: &Tensor<AutoDIffBackend, 1>, x1: &Tensor<AutoDIffBackend, 1>) -> Tensor<AutoDIffBackend, 1> {  
    let x0 = x0.clone();  
    let x1 = x1.clone();  
  
    let y = (x1 - x0.clone().powf_scalar(2.0)).powf_scalar(2.0) * 100.0 + (x0  - 1.0).powf_scalar(2.0);  
    y  
}  
  
fn main() {  
    let device = Default::default();  
  
    let mut new_x0 = 0.0;  
    let mut new_x1 = 2.0;  
  
    let lr = 0.001;  
    let iter = 10000;  
    for i in 0..iter {  
        if i % 1000 == 0 {  
           println!("iter: {} x0 = {}, x1 = {}", i, new_x0, new_x1);  
        }  
        let x0 = Tensor::<AutoDIffBackend, 1>::from_floats([new_x0], &device).require_grad();  
        let x1 = Tensor::<AutoDIffBackend, 1>::from_floats([new_x1], &device).require_grad();  
  
        let y = rosenbrok(&x0, &x1);  
        let mut gradients = y.backward();  
  
        let tensor_grad0 = x0.grad_remove(&mut gradients);  
        let tensor_grad1 = x1.grad_remove(&mut gradients);  
  
        let grad0 = tensor_grad0.unwrap().into_scalar();  
        let grad1 = tensor_grad1.unwrap().into_scalar();  
  
        new_x0 = x0.into_scalar() - grad0 * lr;  
        new_x1 = x1.into_scalar() - grad1 * lr;  
  
    }  
    println!("x0 = {}, x1 = {}", new_x0, new_x1);  
}

最初上手く行かなかった所は、

let mut x0 = Tensor::<AutoDIffBackend, 1>::from_floats([new_x0], &device).require_grad();  

こうやってTensorを作った後、勾配を求めて更新するときに、こうすると駄目。

x0 = x0 - grad0 * lr;

この書き方だと計算グラフになってしまう。 一度スカラーにして再度Tensorを作り直しているが、これが想定されている使いかなのかがよくわからない。 計算結果はPythonバージョンとだいたい一致します。

Rust+Burnで勾配を取得する

BurnはRust製のDLフレームワークです。

burn.dev

実行前に少しだけ次元のチェックをしてくれます。

PyTorchでいうとdimのチェックをしてくれます。 [1, 3, 64, 64]と[3, 64, 64]は違うとエラーを出してくれますが、[1, 3, 64, 64]と[1, 3, 32, 32]は実行時のチェックになります。

Burnを使って単に自動微分の勾配を取得したかったのですが、それだけをやっているサンプルが見つかりませんでした。 なんとかここまで来れたのでメモ残しです。

ソースコード

rustソース

use burn::tensor::Tensor;  
use burn::backend::{Autodiff, Wgpu};  
  
type Backend = Wgpu;  
type AutoDIffBackend = Autodiff<Backend>;  
  
fn main() {  
    let device = Default::default();  
  
    let x = Tensor::<AutoDIffBackend, 1>::from_floats([5.0], &device).require_grad();  
    let y = x.clone().powf_scalar(2.0) * 3.0;  
  
    // 勾配を求めて表示する  
    let gradients = y.backward();  
    let tensor_grad = x.grad(&gradients).unwrap();  
  
    println!("y = {}", y);  
    println!("dy/dx = {:?}",  tensor_grad.into_scalar());  
  
}

carog.toml

[package]  
name = "ch5_nn"  
version = "0.1.0"  
edition = "2021"  
  
[dependencies]  
burn = { version = "~0.15", features = ["train", "wgpu", "vision", "candle"] }

説明

BackendにはWgpuを使い、さらにAutoDIffBackendを定義する。

type Backend = Wgpu;  
type AutoDIffBackend = Autodiff<Backend>;  

勾配が欲しいTensorにrequire_grad()をつける。 Tensorの所有権が移ってしまうと後で勾配が求められないのでclone()が必要。

    let x = Tensor::<AutoDIffBackend, 1>::from_floats([5.0], &device).require_grad();  
    let y = x.clone().powf_scalar(2.0) * 3.0;  

backward()した後勾配を求める。数値として取り出すには、into_scalar()が必要。

    let gradients = y.backward();  
    let tensor_grad = x.grad(&gradients).unwrap();  
    println!("dy/dx = {:?}",  tensor_grad.into_scalar());  

これで実行するとちゃんと勾配が求められています。

y = Tensor {
  data:
[74.99999],
  shape:  [1],
  device:  BestAvailable,
  backend:  "autodiff<fusion<jit<wgpu<wgsl>>>>",
  kind:  "Float",
  dtype:  "f32",
}
dy/dx = 30.0

ちなみにBurnはWSL2で動かすと遅いですが、Windowsで直接動かすと爆速です。 Windows環境の人はWindowsで動かす事をお勧めです。

2024年読んで良かった本

パソナX-TECH Advent Calendar 2024 https://qiita.com/advent-calendar/2024/pasona_x_tech の8日目の記事です。

今年はロボットとDeep Learningがメインで勉強しつつ、派手な成果は出せなかったけど基礎力が上がった気がする一年でした。今年読んだ本の中で良かった本を紹介します。

Rustで作るプログラミング言語

gihyo.jp

Rust+処理系作成と、好きな人にはたまらない組み合わせです。感想はここ。

natsutan.hatenablog.com

筆者のZennも補講的な楽しさがあります。

zenn.dev

強化学習(第2版)

www.morikita.co.jp

実質Courseraのコースの紹介です。

natsutan.hatenablog.com

ロボットをやるからには強化学習は避けて通れないと思って勉強したが、勉強すればするほど難しいと言うよりは、こんなの使い物になるのかという疑問が出てくる。来年は実践でやってみたい。

ゼロから作るDeep Learning⑤

www.oreilly.co.jp

強化学習の分野でも拡散モデルは使われていて、勉強しておいて良かったなと後から感じた本です。一回では理解し切れてないので2週目勉強中。

natsutan.hatenablog.com

他にも・・

Human-in-the-Loop機械学習 仕事は今年一年ずっとこれだった気がする。

natsutan.hatenablog.com

統計学入門 東京大学出版会 統計の勉強に使った。有名な本らしく、練習問題の解説がWebで見れるのはうれしい。

diffusion policy用にzarrのデータを作る

時間がかかったけど、なんとかzarr化できたのでメモ。

ドキュメントはここ https://github.com/real-stanford/diffusion_policy?tab=readme-ov-file#replaybuffer

data/pusht_cchi_v7_replay.zarr
 ├── data
 │   ├── action (25650, 2) float32
 │   ├── img (25650, 96, 96, 3) float32
 │   ├── keypoint (25650, 9, 2) float32
 │   ├── n_contacts (25650, 1) float32
 │   └── state (25650, 5) float32
 └── meta
     └── episode_ends (206,) int64

この構成のzarrデータを作る必要がある。 dataの下に入力データを並べ、metaの下にepisode_endsの配列を用意する。 入力データは全部まとめて一連の配列にする。配列の区切り(エピソードの区切り)をepisode_endsに入れておく。

zarr.saveだけではこの構造で保存できず。

store = zarr.DirectoryStore(OUTPUT_FILE)  
root = zarr.group(store=store, overwrite=True)  

こうやってから、個別にroot.create_datasetで上手くデータ作れました。

import os  
import glob  
import zarr  
from PIL import Image  
import numpy as np  
  
ROOTDIR = 'dofbot_data/OK'  
OUTPUT_FILE = 'dofbot.zarr'  
  
def create_state(root_dir:str) -> zarr.core.Array:  
    # ディレクトリのリストを取得  
    pose_list = []  
  
    for d in sorted(os.listdir(root_dir)):  
        # pose.csvの読み込み  
        pose_csv = os.path.join(root_dir, d, 'pose.csv')  
        with open(pose_csv, 'r') as f:  
            # 1行ずつ読み込む  
            for line in f:  
                pose_words = line.split(',')  
                # float型のリストに変換  
                pose = [float(w) for w in pose_words]  
                pose_list.append(pose)  
  
    # zarr配列に変換  
    z = zarr.array(pose_list, chunks=(1, len(pose_list[0])), dtype='float32')  
    return z  
  
def create_images(root_dir:str) -> zarr.core.Array:  
  
    images = []  
    for d in sorted(os.listdir(root_dir)):  
        # ディレクトリの中のpngファイルのリストを取得  
        files = sorted(glob.glob(os.path.join(root_dir, d, '*.png')))  
        for f in files:  
            # 画像の読み込み  
            img = Image.open(f)  
            # 画像をリサイズ  
            img = img.resize((480, 480))  
            # 画像をuint8の配列に変換  
            img_arr = np.array(img).astype('uint8')  
            images.append(img_arr)  
  
  
    # zarr配列に変換  
    z = zarr.array(images, chunks=(1, 480, 480, 3), dtype='float32')  
    return z  
  
  
  
def calc_episode_ends(dir: str) -> list:  
    # ディレクトリのリストを取得してソート  
    dirs = sorted(os.listdir(dir))  
    episode_ends = []  
  
    ei = 0  
    for d in dirs:  
        # ディレクトリの中のpngファイルのリストを取得  
        files = glob.glob(os.path.join(dir, d, '*.png'))  
        ei += len(files)  
        episode_ends.append(ei)  
  
    return episode_ends  
  
  
def create_action(states: zarr.core.Array, episode_ends:list) -> zarr.core.Array:  
  
    actions = []  
    for idx in range(len(states)):  
        if idx + 1 in episode_ends:  
            action = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]  
        else:  
            action_now = states[idx]  
            action_next = states[idx+1]  
            action = [action_next[0] - action_now[0],  
                      action_next[1] - action_now[1],  
                      action_next[2] - action_now[2],  
                      action_next[3] - action_now[3],  
                      action_next[4] - action_now[4],  
                      action_next[5] - action_now[5]]  
  
        actions.append(action)  
  
  
    z = zarr.array(actions, chunks=(1,), dtype='float32')  
    return z  
  
def main():  
    episode_ends = calc_episode_ends(ROOTDIR)  
    states = create_state(ROOTDIR)  
    actions = create_action(states, episode_ends)  
    images = create_images(ROOTDIR)  
  
    store = zarr.DirectoryStore(OUTPUT_FILE)  
    root = zarr.group(store=store, overwrite=True)  
  
    root.create_dataset('meta/episode_ends', data=episode_ends)  
    root.create_dataset('data/state', data=states)  
    root.create_dataset('data/action', data=actions)  
    root.create_dataset('data/img', data=images)  
  
    print(root.info)  
    print('saved', OUTPUT_FILE)  
  
  
if __name__ == '__main__':  
    main()