【GNN】時空グラフネットワークのTensorflow実装

この記事は、ブロガーの 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={ vvtj _(,j )εH }

    HHH は、自然に接続された人間の関節のセットです。

  • 2 つの連続するフレーム内の同じノードをエッジに接続し、これらのエッジが時間エッジを形成します

    EF = { vtiv ( t + 1 ) i } E_{F}=\左\{v_{ti} v_{(t+1) i}\右\}={ vv( 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 = 1Kw = 1K( p ( x ,h w ) )ww ( 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( vvtj _)=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 ,K1 }

隣接ノードを対応するサブセット ラベルにマップすると、重みの式は次のようになります。

w ( vti , vtj ) = w ' ( lti ( vtj ) ) \mathbf{w}\left(v_{ti}, v_{tj}\right)=\mathbf{w}^{\prime}\left(l_ {ti}\左(v_{tj}\右)\右)w( vvtj _)=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( vvtj _) )w( vvtj _)

その中でも正規化の項目は

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 kl( 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 qt Γ / 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 _)+( qt+Γ / 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}あなた_=L21( A+I ) Λ21W

その中、Λ ii = ∑ j ( A ij + I ij ) \Lambda^{ii}=\sum_{j}\left(A^{ij}+I^{ij}\right)L=( A+

注: 筆者は実装を使用しているpytorchtensor 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列のピクセルのコンボリューションが毎回完了する。ssstrides、次に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 STGCNunits、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 のより多くのバージョンを開発し、それを特定のドメインの問題に使用することを望んでいます。

おすすめ

転載: blog.csdn.net/qq_38904659/article/details/113469272