2
2

小さなGPUでの算法術。忘れ物をしたマリオをルイージが追いかける。スプラインの類似度計算をTensorFlow.jsを用いてGPUで高速にマリオの軌道予測並列計算。

Last updated at Posted at 2024-09-02

スクリーンショット 2024-09-02 191601.png

衝突の理

東京の小さなオフィスビルの一角、青白い蛍光灯の下でキーボードのクリック音が響く。夜遅くまで働くのが日常のプログラマー、山田達也は、深夜のカフェインに頼りながらコードと向き合っていた。彼は、かつて子供時代に夢中になったゲーム「スーパーマリオブラザーズ」の新しいプログラムに取り組んでいた。

プロジェクトのテーマは「忘れ物をしたマリオをルイージが追いかける」というシンプルなものだが、その背後には高度な計算と物理法則が隠されていた。山田は、マリオがジャンプし、走り、そしてピーチ姫を救うために次々と冒険を繰り広げる中で、彼の軌道を正確に予測するアルゴリズムを考案していた。

スプライン関数による軌道のデータベース化
「マリオもルイージも物理的な存在である以上、瞬間移動はできない」と、山田は考える。もし彼らが現実世界の物理法則に従うならば、その軌道は必ず連続的であるはずだ。この事実に基づき、山田はマリオの動きをスプライン関数で表現し、それをデータベース化することに決めた。

山田は、まずマリオの軌道を模擬するスプライン関数を生成した。この関数を使えば、マリオの移動するあらゆる瞬間の位置と速度を正確に計算できる。そして、このデータを使ってルイージがマリオの動きを予測できるようにするのが、山田の次なる課題だった。

ルイージの軌道予測
ルイージがマリオを追いかけるには、マリオの軌道を事前に予測する必要がある。ここで山田は、ベクトルの内積計算を用いたアルゴリズムを考案した。ルイージは、現在得られるマリオの位置や速度などの感触情報をもとに、データベース化されたマリオの軌道と内積を計算し、最も類似した軌道を見つけ出す。

この内積計算は、ルイージがマリオの次の動きを予測し、彼に追いつくための最適な経路を決定するのに役立つ。ルイージは、スプライン関数で表現された複数の軌道から最も可能性の高いものを選び、それに従って追跡を続ける。

山田はこの考えを元に、コードを書き始めた。

このコードは、マリオの軌道をスプライン関数でモデル化し、その軌道に最も近い軌道をルイージが追跡できるようにする。内積計算によって、ルイージが追跡すべき最適な追跡軌道が求められる。

衝突の瞬間
山田の指がキーボードから離れ、プログラムが走り始めた。画面には、マリオとルイージが3次元空間を駆け抜け、そしてついに二人が同じ座標に到達する瞬間が表示された。タイムラインは止まり、彼の計算が正しかったことを示す。

「ルイージが最適な軌道を追跡する時間: 4.00 秒」

コードが完了したことを確認し、山田は深いため息をついた。「これで、ルイージは無事にマリオに追いつける」と、彼は独りごちた。そして、彼は子供の頃から愛してやまないゲームの世界に、新たな一ページを加えたことに満足感を覚えた。

マリオが忘れた物を届けるために、ルイージがどこまでも追いかけていく。山田の頭の中には、コードの計算結果が、再びゲームのストーリーとして紡がれていくのだった。それは、単なるゲームではなく、現実の世界でも通用する理論とアルゴリズムの美しい融合だった。

山田は、スーパーマリオの世界の中に隠された物理学と計算の力を再確認し、満足そうにパソコンを閉じた。明日もまた、新しい冒険が待っているだろう。

スプラインの類似度計算をTensorFlow.jsを用いてGPUで高速に並列処理してます。

image.png

image.png

image.png

コードの説明
generateRandomSpline: 5点のランダムなスプライン関数を生成します。
generateSplines: 指定された数のスプライン関数を生成します(この場合100本)。
calculateSimilarity: TensorFlow.jsを使ってスプライン関数間のコサイン類似度を計算します。
plotSplines: Chart.jsを使用して、入力用スプライン関数と最も類似しているスプライン関数をプロットします。入力用スプラインは青色、類似したスプラインは赤色で描画されます。
findMostSimilarSpline: 最も類似しているスプライン関数を探し、その類似度を表示します。また、結果をグラフで表示します。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>スプライン類似度計算とプロット</title>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
            display: flex;
            flex-direction: column;
            align-items: center;
        }
        canvas {
            max-width: 100%;
            height: auto;
            max-height: 500px;
        }
    </style>
</head>
<body>
    <h1>スプライン類似度計算とプロット</h1>
    <label for="numPoints">データポイント数:</label>
    <input type="number" id="numPoints" value="5" min="2">
    <button onclick="findMostSimilarSpline()">最も似ているスプラインを見つける</button>
    <p id="similarity"></p>
    <canvas id="splineChart"></canvas>

    <script>
        let chartInstance = null;

        function generateRandomSpline(numPoints) {
            let points = [];
            for (let i = 0; i < numPoints; i++) {
                points.push([Math.random(), Math.random()]);
            }
            return points;
        }

        function generateSplines(num, numPoints) {
            let splines = [];
            for (let i = 0; i < num; i++) {
                splines.push(generateRandomSpline(numPoints));
            }
            return splines;
        }

        async function calculateSimilarity(spline1, spline2) {
            const tfSpline1 = tf.tensor(spline1.flat());
            const tfSpline2 = tf.tensor(spline2.flat());
            const dotProduct = tf.dot(tfSpline1, tfSpline2).dataSync()[0];
            const magnitude1 = tf.norm(tfSpline1).dataSync()[0];
            const magnitude2 = tf.norm(tfSpline2).dataSync()[0];
            const similarity = dotProduct / (magnitude1 * magnitude2);
            return similarity;
        }

        async function plotSplines(inputSpline, mostSimilarSpline) {
            const ctx = document.getElementById('splineChart').getContext('2d');
            const data = {
                datasets: [
                    {
                        label: '入力用スプライン',
                        data: inputSpline.map(point => ({x: point[0], y: point[1]})),
                        borderColor: 'blue',
                        fill: false,
                        tension: 0.4
                    },
                    {
                        label: '最も似ているスプライン',
                        data: mostSimilarSpline.map(point => ({x: point[0], y: point[1]})),
                        borderColor: 'red',
                        fill: false,
                        tension: 0.4
                    }
                ]
            };

            const config = {
                type: 'line',
                data: data,
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    scales: {
                        x: {
                            type: 'linear',
                            position: 'bottom',
                            ticks: {
                                callback: function(value) {
                                    return value.toFixed(2);
                                }
                            }
                        },
                        y: {
                            beginAtZero: true,
                            ticks: {
                                callback: function(value) {
                                    return value.toFixed(2);
                                }
                            }
                        }
                    }
                }
            };

            if (chartInstance) {
                chartInstance.destroy(); // 古いチャートを破棄
            }
            chartInstance = new Chart(ctx, config);
        }

        async function findMostSimilarSpline() {
            const numPoints = parseInt(document.getElementById('numPoints').value, 10);
            const numSplines = 100;
            const splines = generateSplines(numSplines, numPoints);
            const inputSpline = generateRandomSpline(numPoints);

            let maxSimilarity = -1;
            let mostSimilarSpline = null;

            for (const spline of splines) {
                const similarity = await calculateSimilarity(spline, inputSpline);
                if (similarity > maxSimilarity) {
                    maxSimilarity = similarity;
                    mostSimilarSpline = spline;
                }
            }

            document.getElementById('similarity').innerText = 
                `最も似ているスプラインの類似度は: ${maxSimilarity.toFixed(4)}`;

            await plotSplines(inputSpline, mostSimilarSpline);
        }
    </script>
</body>
</html>

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2