この記事は、ブロガーの shyern のブログ投稿に基づいて、 STGCN の原理と実装を紹介し、 を使用して STGCN の基本的な操作tensorflow 2
を実現すること。
グラフ畳み込みネットワークを時空間グラフ モデルに拡張することにより、著者は、時空間グラフ畳み込みネットワーク (Spatio-Temporal Graph Convolutional Networks、STGCN) と呼ばれる一般的な表現 (アクション認識のためのスケルトン シーケンス) を設計しました。
グラフの構築
NNで 1 つ覚えておいてくださいNノードとTTTフレームのボーン シーケンスの時空間図は G = ( V , E ) G=(V,E) です。G=( V 、E )、そのノード集合はV = { vti ∣ t = 1 , … , T , i = 1 , . . . , N } V=\left\{v_{ti} | t=1, \ldots, T, i=1,...,N\right\}Ⅴ={ v私は∣t _=1 、…、T 、私=1 、. . . 、N }、ttフレームtのiiiノードの特徴ベクトルF ( vti ) F\left(v_{ti}\right)ふ( v私は)は、ノードの座標ベクトルと推定された信頼度で構成されます。
グラフ構造は、次の 2 つの部分で構成されます。
-
人体の構造に従って、各フレームのノードをエッジに接続し、これらのエッジが空間エッジを形成します
ES = { vtivtj ∣ ( i , j ) ∈ H } E_{S}=\left\{v_{ti} v_{tj} |(i, j) \in H\right\}えS={ v私はvtj _∣ (私,j )εH }
HHH は、自然に接続された人間の関節のセットです。
-
2 つの連続するフレーム内の同じノードをエッジに接続し、これらのエッジが時間エッジを形成します
EF = { vtiv ( t + 1 ) i } E_{F}=\左\{v_{ti} v_{(t+1) i}\右\}えふ={ v私はv( t + 1 ) i}
空間グラフ畳み込みニューラル ネットワーク
空間グラフ畳み込みの場合、グラフ畳み込みモデルの操作は、ここでは特定のフレームについてのみ説明されています。例として、特定の位置x \mathbf{x}に対して、一般的な画像の 2 次元畳み込みを取り上げます。xの畳み込み出力は、次のように記述できます。
fout ( x ) = ∑ h = 1 K ∑ w = 1 K fin ( p ( x , h , w ) ⋅ w ⋅ w ( h , w ) f_{out}(\mathbf{x})=\sum_{ h=1}^{K} \sum_{w=1}^{K} f_{in}(\mathbf{p}(\mathbf{x}, h, w)) \cdot \mathbf{w} \cdot \mathbf{w}(h, w)へあなたの_( × )=h = 1∑Kw = 1∑Kへ私は( p ( x ,h 、w ) )⋅w⋅w ( h ,w )
入力チャンネル数はcccの特徴マップfin f_{in}へ私は、畳み込みカーネル サイズK × KK\times KK×K、サンプリング
関数方程式p ( x , h , w ) = x + p ′ ( h , w ) \mathbf{p}(\mathbf{x}, h, w)=\mathbf{x}+\mathbf{ p}^{\prime}(h, w)p ( x ,h 、w )=バツ+p」 (h,w )、重み関数チャネル番号はcccの重み関数
- サンプリング機能
画像では、サンプリング関数p ( h , w ) \mathbf{p}(h,w)p ( h ,w )はxxを指しますxピクセルを中心とする隣接ピクセル。図では、隣接ピクセルのセットは次のように定義されます。
B ( vti ) = { vtj ∣ d ( vtj , vti ) ≤ D } B\left(v_{ti}\right)=\left\{v_{tj} | d\left(v_{tj}, v_{ti}\right) \leq D\right\}B( v私は)={ vtj _∣ d( vtj _、v私は)≤D }
其中, d ( v t j , v t i ) d(v_{tj},v_{ti}) d (vtj _、v私は)はvtj から v_{tj}を参照しますvtj _vti v_{ti}v私はの最短距離なので、サンプリング関数は次のように記述できます。
p ( vti , vtj ) = vtj \mathbf{p}\left(v_{ti}, v_{tj}\right)=v_{tj}p( v私は、vtj _)=vtj _
- 重み関数
2D畳み込みでは、隣接するピクセルが中心ピクセルの周りに規則的に配置されるため、空間順序に従って規則的な畳み込みカーネルで畳み込むことができます。2D 畳み込みと同様に、図では、サンプリング関数によって取得された隣接ピクセルが異なるサブセットに分割され、各サブセットにはデジタル ラベルがあるため、
lti : B ( vti ) → { 0 , … , K − 1 } l_{ti} : B\left(v_{ti}\right) \rightarrow\{0, \ldots, K-1\}l私は:B( v私は)→{ 0 ,…、K−1 }
隣接ノードを対応するサブセット ラベルにマップすると、重みの式は次のようになります。
w ( vti , vtj ) = w ' ( lti ( vtj ) ) \mathbf{w}\left(v_{ti}, v_{tj}\right)=\mathbf{w}^{\prime}\left(l_ {ti}\左(v_{tj}\右)\右)w( v私は、vtj _)=w」( l私は( vtj _) )
- 空間グラフ畳み込み
fout ( vti ) = ∑ vtj ∈ B ( vti ) 1 Z ti ( vtj ) fin ( p ( vti , vtj ) ) ⋅ w ( vti , vtj ) f_{out}\left(v_{ti}\right)=\ sum_{v_{tj} \in B\left(v_{ti}\right)} \frac{1}{Z_{ti}\left(v_{tj}\right)} f_{in}\left(\mathbf {p}\left(v_{ti}, v_{tj}\right)\right) \cdot \mathbf{w}\left(v_{ti}, v_{tj}\right)へあなたの_( v私は)=vtj _∈ B ( v私は)∑Z私は( vtj _)1へ私は( p( v私は、vtj _) )⋅w( v私は、vtj _)
その中でも正規化の項目は
Z ti ( vtj ) = ∣ { vtk ∣ lti ( vtk ) = lti ( vtj ) } ∣ Z_{ti}\left(v_{tj}\right)=\left|\left\{v_{tk} | l_{ti}\left(v_{tk}\right)=l_{ti}\left(v_{tj}\right)\right\}\right|Z私は( vtj _)=∣ { vt k∣ l私は( vt k)=l私は( vtj _) } ∣
対応するサブセットの基底に相当します。上記の式を上記の式に代入すると、次のようになります。
fout ( vti ) = ∑ vtj ∈ B ( vti ) 1 Z ti ( vtj ) fin ( vtj ) ⋅ w ( lti ( vtj ) ) f_{out}\left(v_{ti}\right)=\sum_{v_{ tj} \in B\left(v_{ti}\right)} \frac{1}{Z_{ti}\left(v_{tj}\right)} f_{in}\left(v_{tj}\right) ) \cdot \mathbf{w}\left(l_{ti}\left(v_{tj}\right)\right)へあなたの_( v私は)=vtj _∈ B ( v私は)∑Z私は( vtj _)1へ私は( vtj _)⋅w( l私は( vtj _) )
- 時空間モデリング
空間ドメインのモデルを時間ドメインに拡張すると、sampling function
得
B ( vti ) = { vqj ∣ d ( vtj , vti ) ≤ K , ∣ q − t ∣ ≤ ⌊ Γ / 2 ⌋ } B\left(v_{ti}\right)=\left\{v_{qj}\左|d\左(v_{tj}, v_{ti}\右)\leq K,\右| qt | \leq\lfloor\Gamma / 2\rfloor\right\}B( v私は)={ vqj _∣ d( vtj _、v私は)≤K 、∣q−t ∣≤⌊ Γ / 2 ⌋ }
ここで、Γ\ガンマΓコントロールは、時間領域で畳み込みカーネルのサイズを制御します。
weight function
ために
l ST ( vqj ) = lti ( vtj ) + ( q − t + ⌊ Γ / 2 ⌋ ) × K l_{ST}\left(v_{qj}\right)=l_{ti}\left(v_{tj} \right)+(q-t+\lfloor\Gamma / 2\rfloor) \times KlS T( vqj _)=l私は( vtj _)+( q−t+⌊ Γ / 2 ⌋ )×K
其中, l i i l_{ii} l私は単一フレームの場合のラベル マッピング。
この時点で、構築された時空間グラフに対して明確に定義された畳み込み演算が行われます。
パーティション戦略
- Uni-labelling を一意に分割: ノードの 1 近傍をサブセットに分割します。
- 距離ベースの分割 距離パーティショニング: ノードの 1 つの近傍を、ノード自体のサブセットと隣接ノードのサブセットの 2 つのサブセットに分割します。
- 空間構成パーティショニング: ノードの 1 つの近傍を 3 つのサブセットに分割します. 最初のサブセットは、空間内でルート ノードよりもスケルトン全体から離れた隣接ノードを接続し、2 つ目のサブセットは中心に近いノードを接続します.ノード、3 番目のサブセットはルート ノード自体であり、それぞれ遠心運動、求心運動、静止運動の運動特性を表します。
学習可能なエッジの重要度の重み付け
移動中、異なるトランクの重要性は異なります。例えば、首よりも脚の動きの方が重要で、走る、歩く、跳ぶなどは脚で判断できますが、首の動きにはあまり有効な情報が含まれていない場合があります。
したがって、STGCN は異なる胴体に重みを付けます (各 STGCN ユニットには、トレーニング用の独自の重みパラメーターがあります)。注意メカニズムを追加した後
A j = A j ⊗ M \mathbf{A}_{j}=\mathbf{A}_{j} \otimes \mathbf{M}あじ=あじ⊗M
ここで⊗\回⊗ は内積を意味し、マスクM \bf MM はとして初期化されますtf.ones
。
STGCN の実装
GCN は、空間内の隣接する関節の局所的な特徴を学習するのに役立ちます。これに基づいて、関節の時間変化の局所的な特徴を学習する必要があります。**グラフのタイミング機能をどのように重ねるかは、グラフ ネットワークが直面する問題の 1 つです。**この分野の研究には、時間畳み込み (TCN) とシーケンス モデル (LSTM) の 2 つの主要なアイデアがあります。
STGCN は TCN を使用します. 形状が固定されているため、従来の畳み込み層を使用して時間的畳み込み操作を完了することができます. 理解を容易にするために、画像の畳み込み演算を比較できます。
fout = Λ − 1 2 ( A + I ) Λ − 1 2 fin W \mathbf{f}_{out}=\mathbf{\Lambda}^{-\frac{1}{2}}(\mathbf{A }+\mathbf{I}) \mathbf{\Lambda}^{-\frac{1}{2}} \mathbf{f}_{in} \mathbf{W}へあなたの_=L−21( A+I ) Λ−21へ私はW
その中、Λ ii = ∑ j ( A ij + I ij ) \Lambda^{ii}=\sum_{j}\left(A^{ij}+I^{ij}\right)L私は=∑じ( A私は+私私は)。
注: 筆者は実装を使用している
pytorch
tensor shape
ため、( N , C , H , W ) (N, C, H, W)( N 、C 、H 、W )であり、この論文で使用さtensorflow
れるtensor shape
(N , H , W , C ) (N,H,W,C)( N 、H 、W ,C )。興味のある読者は両者の違いを比較してみてください。後者は以下の表現で使用されています。
STGCN の特徴マップの最後の 3 次元の形状は( T , V , C ) (T, V, C)です。( T 、V 、C )、画像特徴マップの形状( H , W , C ) (H, W, C)( H 、W ,C )に対応します。
- 画像チャンネル番号CCC は関節のCCハ
- 画像幅WWW はキー フレームVVの数に対応します。Ⅴ
- 画像のHHが高いH は関節TTの数に対応しますT
空間グラフ畳み込みモデル
空間グラフ畳み込みでは、畳み込みカーネルのサイズはw × 1 w \times 1です。w×図1に示すように、w行のピクセルと1列のピクセルのコンボリューションが毎回完了する。ssstride
用s、次にsssピクセル、1 つの行が完了した後、ピクセルの次の行の畳み込みが実行されます。
# The based unit of graph convolution networks.
import tensorflow as tf
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Conv2D, Reshape
class gcn(Model):
r"""The basic module for applying a graph convolution.
Args:
filters (int): Number of channels produced by the convolution
kernel_size (int): Size of the graph convolution kernel
t_kernel_size (int): Size of the temporal convolution kernel
t_stride (int, optional): Stride of the temporal convolution. Default: 1
t_padding (int, optional): Temporal zero-padding added to both sides of
the input. Default: 0
t_dilation (int, optional): Spacing between temporal kernel elements.
Default: 1
bias (bool, optional): If ``True``, adds a learnable bias to the output.
Default: ``True``
Shape:
- Input[0]: Input graph sequence in :math:`(N, T_{in}, V, in_channels)` format
- Input[1]: Input graph adjacency matrix in :math:`(K, V, V)` format
- Output[0]: Output graph sequence in :math:`(N, T_{out}, V, out_channels)` format
- Output[1]: Graph adjacency matrix for output data in :math:`(K, V, V)` format
where
:math:`N` is a batch size,
:math:`T_{in}/T_{out}` is a length of input/output sequence,
:math:`V` is the number of graph nodes,
:math:`K` is the spatial kernel size, as :math:`K == kernel_size[1]`.
"""
def __init__(self,
filters,
t_kernel_size=1,
t_stride=1,
t_padding=0,
t_dilation=1,
bias=True):
super(gcn, self).__init__(dynamic=True)
self.filters = filters
self.t_kernel_size = t_kernel_size // 2 * 2 + 1
self.t_padding = t_padding
self.t_stride = t_stride
self.t_dilation = t_dilation
self.bias = bias
self.k_size = None
self.conv = None
self.reshape = None
def build(self, input_shape):
x_shape, A_shape = input_shape
self.k_size = A_shape[0]
self.conv = Conv2D(
filters=self.filters * self.k_size,
kernel_size=(self.t_kernel_size, 1),
padding='same' if self.t_padding else 'valid',
strides=(self.t_stride, 1),
dilation_rate=(self.t_dilation, 1),
use_bias=self.bias,
input_shape=x_shape)
n, t, v, c = self.conv.compute_output_shape(x_shape)
self.reshape = Reshape([t, v, self.k_size, c // self.k_size])
def call(self, inputs, training=None, mask=None):
x, A = inputs
h = self.conv(x)
h = self.reshape(h)
y = tf.einsum('ntvkc, kvw->ntwc', h, A)
return y
時間畳み込みモデル
時間畳み込みでは、畳み込みカーネルのサイズは、temporal_kernel_size × 1temporal\_kernel\_size\times 1です。一時的_カーネル_サイズ_ _ _ _ _ _ _ _ _ _ _ _ _ _×図1に示すように、1つのノードと1つのキーフレームのコンボリューションtemporal_kernel_size
が毎回完了する。sssが 1 の場合、1 フレームずつ移動し、1 ノードが完了したら次のノードの畳み込みを実行します。
# The based unit of graph temporal convolution networks.
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Conv2D, Dropout, BatchNormalization, Lambda, Activation
class tcn(Model):
r"""The basic module for applying a temporal convolution.
Args:
input_A (float): Graph adjacency matrix for output data in :math:`(K, V, V)` format
filters (int): Number of channels produced by the convolution
kernel_size (int): Size of the graph convolution kernel
t_kernel_size (int): Size of the temporal convolution kernel
t_stride (int, optional): Stride of the temporal convolution. Default: 1
t_padding (int, optional): Temporal zero-padding added to both sides of
the input. Default: 0
t_dilation (int, optional): Spacing between temporal kernel elements.
Default: 1
bias (bool, optional): If ``True``, adds a learnable bias to the output.
Default: ``True``
Shape:
- Input[0]: Input graph sequence in :math:`(N, T_{in}, V, in_channels)` format
- Output[0]: Output graph sequence in :math:`(N, T_{out}, V, out_channels)` format
where
:math:`N` is a batch size,
:math:`T_{in}/T_{out}` is a length of input/output sequence,
:math:`V` is the number of graph nodes,
:math:`K` is the spatial kernel size, as :math:`K == kernel_size[1]`.
"""
def __init__(self,
filters,
t_kernel_size=1,
t_stride=1,
t_padding=0,
in_batchnorm=True,
out_batchnorm=True,
t_dilation=1,
bias=True,
dropout=0):
super(tcn, self).__init__()
self.filters = filters
self.t_kernel_size = t_kernel_size
self.t_padding = t_padding
self.t_stride = t_stride
self.t_dilation = t_dilation
self.dropout = dropout
self.bias = bias
if in_batchnorm:
self.batch_1 = BatchNormalization()
else:
self.batch_1 = Lambda(lambda x: x)
self.conv = None
self.a = Activation('relu')
if out_batchnorm:
self.batch_2 = BatchNormalization()
else:
self.batch_2 = Lambda(lambda x: x)
self.dropout = Dropout(dropout)
def build(self, input_shape):
self.conv = Conv2D(filters=self.filters,
kernel_size=(self.t_kernel_size, 1),
padding='same' if self.t_padding else 'valid',
strides=(self.t_stride, 1),
dilation_rate=(self.t_dilation, 1),
use_bias=self.bias,
input_shape=input_shape)
def call(self, inputs, training=None, mask=None):
x = inputs
h = self.batch_1(x)
h = self.a(h)
h = self.conv(h)
h = self.batch_2(h)
y = self.dropout(h)
return y
ネットワーク アーキテクチャ
入力データは最初に処理されbatch normalization
、次に 9 STGCN
units、global pooling
各シーケンスの 256 次元の特徴ベクトルを取得するために a が続き、最後にSoftMax
関数分類し、最終的なラベルを取得します。STGCN
を採用する構造ごとにResnet
、最初の 3 層の出力は 64 チャネル、中間の 3 層は 128 チャネル、最後の 3 層は 256 チャネルで、毎回 ST-CGN 構造を通過した後、特徴はランダムに選択されます。 0.5 の確率でdropout
、4 番目と 7 番目の時間畳み込み層のストライドは 2 に設定されます。を使用したSGD
トレーニング、学習率は 0.01 で、学習率は 10 エポックごとに 0.1 ずつ減少します。
このホワイト ペーパーでは、興味のある読者が時空間グラフ ネットワークを設計し、実際のタスクに適用するために使用できる基本STGCN
ユニット。
# The based unit of spatial temporal module.
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Activation, Lambda
from models.gcn import gcn
from models.tcn import tcn
from models.res import res
class stgcn(Model):
r"""Applies a spatial temporal graph convolution over an input graph sequence.
Args:
filters (int): Number of channels produced by the convolution
kernel_size (tuple): Size of the temporal convolution kernel
& graph convolution kernel
stride (int, optional): Stride of the temporal convolution. Default: 1
dropout (int, optional): Dropout rate of the final output. Default: 0
residual (bool, optional): If ``True``, applies a residual mechanism.
Default: ``True``
Shape:
- Input[0]: Input graph sequence in :math:`(N, in_channels, T_{in}, V)` format
- Input[1]: Input graph adjacency matrix in :math:`(K, V, V)` format
- Output[0]: Output graph sequence in :math:`(N, out_channels, T_{out}, V)` format
where
:math:`N` is a batch size,
:math:`T_{in}/T_{out}` is a length of input/output sequence,
:math:`V` is the number of graph nodes,
:math:`K` is the spatial kernel size, as :math:`K == kernel_size[1]`.
"""
def __init__(self,
filters,
kernel_size,
stride=1,
dropout=0,
residual=True):
super(stgcn, self).__init__(dynamic=True)
assert len(kernel_size) == 2
assert kernel_size[0] % 2 == 1
padding = (kernel_size[0] - 1) // 2
self.residual = residual
self.filters = filters
self.kernel_size = kernel_size
self.res = None
self.stride = stride
self.gcn = gcn(filters=filters,
t_kernel_size=kernel_size[1])
self.tcn = tcn(filters=filters,
t_kernel_size=kernel_size[0],
t_stride=stride,
t_padding=padding,
dropout=dropout)
self.a = Activation('relu')
def build(self, input_shape):
x_shape, _ = input_shape
c = x_shape[-1]
if not self.residual:
self.res = Lambda(lambda x: 0)
elif c == self.filters and self.stride == 1:
self.res = Lambda(lambda x: x)
else:
self.res = res(filters=self.filters,
kernel_size=1,
stride=self.stride)
def call(self, inputs, training=None, mask=None):
x, A = inputs
res = self.res(x)
x = self.gcn([x, A])
x = self.tcn(x) + res
y = self.a(x)
return y
要約する
時間と空間における GCN の拡張アプリケーションは著者によってpytorch
実装されており、そのアイデアは学ぶ価値があります。この記事では、基本ユニットtensorflow 2
を実装するSTGCN
。著者のアイデアに基づいて STGCN のより多くのバージョンを開発し、それを特定のドメインの問題に使用することを望んでいます。