「線形代数の半歩先」をPythonで写経 ~ 10章 DMD動画分析、DMD振り返り
第5部「ならべた数のさらなる発展」
書籍の著者 大久保 潤 先生
この記事は、書籍「線形代数の半歩先」の 第5部「ならべた数のさらなる発展」に掲載の「非線形系における線形性」に関する Python写経活動のドキュメンタリーです。
第5部は「時間発展方程式」を取り扱います。
前回に引き続き、ChatGPT提案の「完全マスター」を実践します。
ChatGPTによるコードと解説に全面依拠して 動的モード分解(DMD)マスターを目指してまいります。
今回はいよいよ 最終段階に突入 します!
動的モード分解で動画データを分析します!
いっけんテキストから離れるように見えますが、動的モード分解の理解を通じて、テキストを読み解く素地ができると思います!
とにかく数学素人なので、どうぞお手柔らかにお願いいたします。
では書籍とChatGPTを開いて線形代数の旅に出発です🚀

はじめに
このブログシリーズは、書籍「線形代数の半歩先 データサイエンス・機械学習に挑む前の30話」(講談社サイエンティフィク、「テキスト」と呼びます)の Python 写経の実践を通じて得た個人的な知見を書きます。
書籍の紹介と引用表記はリンク先の記事に掲載しています。

第5部 ならべた数のさらなる発展
クープマン理論&DMD完全マスター・ロードマップ
ChatGPT提案の「クープマン理論&DMD完全マスター」ロードマップです。(DMD は動的モード分解の略称です)

この記事は STEP 6 の 動画データにDMDを適用 を実践します。
(注意事項)
ChatGPTの回答をそのまま記載しています。
内容の適否はチェックしていませんので、ご了承ください。

STEP6:実データ応用(動画 → 行列 → DMD分解)
ChatGPTが提示したコードの概要は次のとおりです。
🔍 概要
$$
\begin{array}{l:l}
ステップ & 処理内容 \\
\hline
\\
🎦動画読み込み & \text{imageio} を使ってフレームごとに読み込み、\\
& グレースケールに変換 \\
\\
🧮 行列化 & 各フレームを1列のベクトルとし、\\
&列方向に連結して行列 X \in \R^{n_{\text{pixels}}\times T} を構成 \\
\\
⚡ \text{DMD}解析 & \text{pydmd.DMD} による固有モード・固有値抽出 \\
\\
🎨モード可視化 & 上位3モードを元画像サイズに戻して表示 \\
\\
🔁 予測・再構成 & \text{dmd.reconstructed\_data} で\\
(オプション)& アニメーション生成も可能 \\
\end{array}
$$
🎞️ 動画ファイルの概要
SHARP さんのサイトから「HG-02Sサンプルムービー sample2_k.mpg」(ゴンドラ)をお借りしました。
ありがとうございます!

(1)Python 実装
上記の概要どおりの実装です!
%%time
# 動画取得サイト:https://jp.sharp/galileo/guide/movie/index.html
# 動画名 :HG-02Sサンプルムービー sample2_k.mpg
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'Meiryo' # ★はマニュアル追加コード
import imageio
from pydmd import DMD
# import os
# --- 動画ファイルの読み込み(グレースケール変換) ---
# 設定
video_path = '../sample2_k.mpg' # 動画のパス
duration = 100 # フレーム数(例:最初の100フレーム)
# 動画リーダーの設定
reader = imageio.get_reader(video_path)
frames = []
# 動画ファイルの読み込み
for i, frame in enumerate(reader):
if i >= duration:
break
# RGB to grayscale変換
gray = np.dot(frame[..., :3], [0.299, 0.587, 0.114])
# 列ベクトル化して保存
frames.append(gray.flatten())
reader.close()
# --- 行列化 ---
X = np.column_stack(frames) # shape: (画素数, フレーム数)
print("X.shape:", X.shape)
# --- DMD解析 ---
dmd = DMD(svd_rank=10)
dmd.fit(X)
print("DMD modes:", dmd.modes.shape)
print("DMD eigenvalues:")
print(dmd.eigs)
# --- 可視化:代表的なモードを画像に復元 ---
frame_shape = frames[0].reshape((frame.shape[0], frame.shape[1]))
plt.figure(figsize=(12, 4))
for i in range(3):
mode_img = dmd.modes[:, i].real.reshape(frame_shape.shape)
plt.subplot(1, 3, i+1)
plt.imshow(mode_img, cmap='gray')
plt.title(f"DMD Mode {i+1}")
plt.axis('off')
plt.tight_layout()
plt.show()
# --- モードの時間発展(再構成 or 予測)も可能 ---
future = dmd.reconstructed_data.real[:, -20:] # 最後20フレームを再構成
imageio.mimsave('reconstructed.gif',
[f.reshape(frame_shape.shape) for f in future.T], fps=10)【実行結果】
■ 処理時間
CPU times: total: 8.78 s
Wall time: 10.4 s
■ 主要データの概要

最初の 100 フレームからなる データ行列 $${X}$$ の形状は $${(345600, 100)}$$ です。
ランク 10 の SVD で適用した DMD のモードは 10 になっています。
10 個の固有値のうち 8 個の虚部が非ゼロです。
■ 上位 3 モードから復元した画像
グレースケールになり、DMDで圧縮されたことで、ぼんやりした画像になっています。

■ DMD再構成で生成したアニメーション(GIFファイル)
実行フォルダに出力された「reconstructed.gif」がアニメーションファイルに該当します。
100 フレームのうちの最後の 20 フレームのデータを用いているようです。
動画もぼんやりした像になっています。
■ DMD サマリーの可視化
# DMD サマリーの可視化
from pydmd.plotter import plot_summary
plot_summary(dmd, index_modes=(0, 2, 4))【実行結果】
特異値は最初の 2 つのランクで 4割くらいに達しているようです。
固有値はほぼ同じような傾向でしょう。


(2)コードの注目ポイント・要点整理
🧩 全体の流れ
🎞️ 動画読み込み → 🧮 行列化 → ⚡ DMD分解 → 🖼️ モード可視化 → 🔁(再構成や予測も可能)
📌 コード詳細解説
① 必要ライブラリのインポート
import numpy as np
import matplotlib.pyplot as plt
import imageio
from pydmd import DMDnumpy, matplotlib: 基本の数値計算&可視化
imageio: 動画ファイル(mp4, gif等)からフレーム画像を読み込む
pydmd: DMDの実装ライブラリ
② 動画からフレームを抽出し、行列に変換
reader = imageio.get_reader(video_path)
frames = []
for i, frame in enumerate(reader):
if i >= duration:
break
gray = np.dot(frame[..., :3], [0.299, 0.587, 0.114])
frames.append(gray.flatten())imageio.get_reader():動画をフレームごとに読み込む
gray = np.dot(..., [...]):RGB画像をグレースケールに変換(輝度加重平均)
.flatten():2次元画像を1次元ベクトルに変換(DMDでは列ベクトルが基本)
frames:各フレームを列とするリスト(後で行列にする)
③ 行列化(スナップショット行列の構築)
X = np.column_stack(frames) # shape: (画素数, フレーム数)各列が「時刻 t における画素の値」になる行列 $${X \in \R^{n_{\text{pixels}}\times T}}$$
これが DMD の入力(時間発展のあるデータ行列)
④ DMD 分解を実行
dmd = DMD(svd_rank=10)
dmd.fit(X)svd_rank=10: モードを最大10個抽出(必要に応じて変更可)
.fit(X): スナップショット行列から
クープマン固有値(時間スケールや周波数)
DMDモード(画素構造の時間変化)
DMD振幅(各モードの重要度) を抽出
⑤ モードを可視化
for i in range(3):
mode_img = dmd.modes[:, i].real.reshape(frame_shape.shape)
plt.imshow(mode_img, cmap='gray')dmd.modes:各モードは「flattenされた画像形式」
reshape():元画像の2次元サイズに戻す
.real:DMDモードは一般に複素数なので、表示用に実部だけ使用
⑥ オプション:再構成・予測
future = dmd.reconstructed_data.real[:, -20:]
imageio.mimsave('reconstructed.gif', [...], fps=10)dmd.reconstructed_data:DMDで再構成された時系列行列(X の近似)
imageio.mimsave():再構成結果を動画/GIFとして保存可能
応用例:未来予測・ノイズ除去・背景と動きの分離 など
📈 実データDMDの意義と活用例
$$
\begin{array}{l:l}
活用シーン & 説明 \\
\hline
\\
🎥 動画の圧縮 & 最重要モードだけ残して近似再構成(軽量化) \\
🧎動作認識 & \text{DMD}モードの空間を使って時系列分類| \\
🚰流体解析 & 流れ場の時間変化を支配モードで分解 \\
🧽ノイズ除去 & 高周波モードを除外してスムージング \\
\end{array}
$$
🧠 学びのポイントまとめ
動画も「画素の時間変化」としてDMD可能
画素を1列ベクトル化 → DMD入力へ
固有モードは時間にわたる動きのパターン
固有値の虚部 → 周波数、実部 → 増減(成長・減衰)
再構成で「意味のある動き」だけを抜き出せる
▶️ 次にやってみたいこと
再構成GIFの出力
背景分離(静的モード vs 動的モード)
動画 → 時系列クラスタリング
RBFカーネルによる非線形DMD拡張

📘 DMD(動的モード分解)まとめノート
マスターコースの締めくくりは「DMDのまとめ」です。
まるっと復習しましょう!
✅ 概要:DMDとは?
DMD(Dynamic Mode Decomposition)は、時系列データを“モード(空間構造) × 時間発展”に分解するアルゴリズム。
本来は流体力学から発展 → 現在は時系列解析、信号処理、動画処理など幅広く応用
モード:空間的な構造(パターン)
固有値:時間発展(周波数・成長/減衰)
🔢 数式の構造(ざっくり)
$$
x(t) \approx \sum_{k=1}^{r} a_k \phi_k e^{\omega_k t}
$$
$${\phi_k}$$:DMDモード(構造)
$${\omega_k}$$:固有値(時間発展)
$${a_k}$$:初期値における振幅(寄与度)
🧭 ステップごとの流れ
時系列データをスナップショット行列 $${X}$$ に変換:
各列:時刻ごとの観測(ベクトル)
SVD分解で特徴量圧縮(rank-$${r}$$)
クープマン作用素の近似 $${\widetilde{A}}$$ を求める
固有値分解して:
固有値 → 時間発展($${\omega_k = \log \lambda_k / \Delta t}$$)
固有ベクトル → モード $${\phi_k}$$
🧪 実装例:動画への適用
🎞️ ステップ
動画(MP4/GIF) → フレームをグレースケールに変換
各フレームを flatten (平坦化)して列ベクトルに
全フレームを並べて行列 $${X}$$
DMD を適用し、モード・固有値・振幅を取得
上位モードを画像に復元
再構成や予測も可能
💡 応用例
背景と動きの分離(低振幅 vs 高振幅モード)
再構成によるノイズ除去
未来フレームの生成(短時間予測)
🔍 モードの意味と解釈
$$
\begin{array}{l:l}
要素 & 意味 \\
\hline
\\
\phi_k & 空間モード(構造パターン) \\
\lambda_k & 固有値(時間変化の傾向) \\
|a_k| & 寄与度(重要度) \\
\omega_k & \log(\lambda_k)/\Delta t により成長/振動性がわかる \\
\end{array}
$$
📦 発展:EDMD / カーネルDMD
EDMD(拡張DMD):非線形変換(辞書関数)を使い、より広い動的構造を捉える
カーネルDMD:明示的に高次元写像せず、内積カーネルで非線形構造を捉える
🧠 よくある疑問とポイント
Q1. なぜ複素数が出てくるの?
→ 時系列の振動(周期性)を表すため(虚部が周波数)Q2. 固有値の大きさの意味は?
→ 実部がプラス → 発散、マイナス → 減衰Q3. どのモードが重要?
→ 振幅($${|a|}$$)が大きいもの、固有値が安定なもの
🚀 応用分野の広がり
$$
\begin{array}{l:l}
分野 & 応用例 \\
\hline
\\
流体解析 & 渦構造の抽出、遷移の予測 \\
動画処理 & 動きの分離、圧縮、再構成 \\
センサーデータ & モードごとの異常検知 \\
データ駆動制御 & モデル同定、未来予測 \\
\end{array}
$$

おわりに
後日談
DMDまとめノートの完成後、ChatGPTはDMDのプレゼン資料(パワーポイント)の作成を提案してくれました。
全体構成、スライド概要、スライドに埋め込む図案を作ってくれて「いい感じ」に進捗しました。
例えば図案は、ChatGPTが次のような画像を複数作ってくれました。

スライドのパーツ群が「いい感じ」に出揃い、いよいよChatGPTは「パワーポイント最終化」(本番PPTX)を作成してくれました。
ファイルを開くと・・・

・・・
私の依頼・指示が悪いのか、ChatGPTの実力値なのか。。。
生成AIのさらなる進化を期待しちゃいますよね!
ありがとうございました✨️
この記事がシリーズ最終回です。
ともすれば「無味乾燥」(個人の感想です)になりがちな線形代数の数式が、データサイエンスのミストを浴びてキラキラ輝く様子を、満喫できました。
著者の先生、ありがとうございます。
ChatGPT、ありがとうございます。

シリーズの記事
前の記事
目次
ブログの紹介
note で7つのシリーズ記事を書いています。
ぜひ覗いていってくださいね!
1.のんびり統計
統計検定2級の問題集を手がかりにして、確率・統計をざっくり掘り下げるブログです。
雑談感覚で大丈夫です。ぜひ覗いていってくださいね。
統計検定2級公式問題集CBT対応版に対応しています。
Python、EXCELのサンプルコードの配布もあります。
2.実験!たのしいベイズモデリング1&2をPyMC Ver.5で
書籍「たのしいベイズモデリング」・「たのしいベイズモデリング2」の心理学研究に用いられたベイズモデルを PyMC Ver.5で描いて分析します。
この書籍をはじめ、多くのベイズモデルは R言語+Stanで書かれています。
PyMCの可能性を探り出し、手軽にベイズモデリングを実践できるように努めます。
身近なテーマ、イメージしやすいテーマですので、ぜひぜひPyMCで動かして、一緒に楽しみましょう!
3.実験!岩波データサイエンス1のベイズモデリングをPyMC Ver.5で
書籍「実験!岩波データサイエンスvol.1」の4人のベイジアンによるベイズモデルを PyMC Ver.5で描いて分析します。
この書籍はベイズプログラミングのイロハをざっくりと学ぶことができる良書です。
楽しくPyMCモデルを動かして、ベイズと仲良しになれた気がします。
みなさんもぜひぜひPyMCで動かして、一緒に遊んで学びましょう!
4.楽しい写経 ベイズ・Python等
ベイズ、Python、その他の「書籍の写経活動」の成果をブログにします。
主にPythonへの翻訳に取り組んでいます。
写経に取り組むお仲間さんのサンプルコードになれば幸いです🍀
5.RとStanではじめる心理学のための時系列分析入門 を PythonとPyMC Ver.5 で
書籍「RとStanではじめる心理学のための時系列分析入門」の時系列分析をPythonとPyMC Ver.5 で実践します。
この書籍には時系列分析のテーマが盛りだくさん!
時系列分析の懐の深さを実感いたしました。
大好きなPythonで楽しく時系列分析を学びます。
6.データサイエンスっぽいことを綴る
統計、データ分析、AI、機械学習、Pythonのコラムを不定期に綴っています。
統計・データサイエンス書籍にまつわる記事が多いです。
「統計」「Python」「数学とPython」「R」のシリーズが生まれています。
7.Python機械学習プログラミング実践記
書籍「Python機械学習プログラミング PyTorch & scikit-learn編」を学んだときのさまざまな思いを記事にしました。
この書籍は、scikit-learnとPyTorchの教科書です。
よかったらぜひ、お試しくださいませ。
最後までお読みいただきまして、ありがとうございました。
いいなと思ったら応援しよう!
応援ありがとうございます。これからもがんばって記事を作成します!