Convolución del gráfico de espacio-tiempo Explicación detallada de la teoría y el código ST-GCN

1. Introducción

Reconocimiento de acción basado en esqueleto (Skeleton-Based Action Recognition) La tarea principal es reconocer la acción de ejecución a partir de una serie de puntos clave de esqueleto temporalmente continuos (2D/3D) . Debido a que implica la entrada de la estructura gráfica del marco del esqueleto, el método de uso de GCN se ha convertido gradualmente en la corriente principal y ha logrado buenos resultados.

Antes de aprender ST-GCN, encontré algunos tutoriales y artículos relacionados con GCN en Internet para estudiar. Ahora, la serie recomendada de artículos está organizada de la siguiente manera y puede leerlos usted mismo:

  • Análisis GCN relativamente fácil de entender

    (https://www.zhihu.com/question/54504471/answer/611222866)

  • Un análisis GCN más completo

    (https://zhuanlan.zhihu.com/p/90470499)

Aquí resumo brevemente los pasos básicos de GCN (suponiendo que la entrada del gráfico lo sea), que pueden considerarse como

  • Realice la extracción de características en la entrada del gráfico (suponiendo que los parámetros sean ) y la salida. Desde un punto de vista microscópico, esta extracción de características puede entenderse como la extracción de las características de cada nodo en el gráfico por separado, y sus dimensiones de características cambian de a;

  • Establecer una matriz de adyacencia de acuerdo a la estructura del grafo, y normalizarla o normalizarla simétricamente para obtener;

  • Las entidades extraídas se agregan mediante la matriz de adyacencia normalizada y el resultado agregado es .

De esta forma, se implementa la operación básica de convolución de gráficos. El código de implementación específico es el siguiente:

class GraphConvolution(nn.Module):
    def __init__(self, input_dim, output_dim, use_bias=True):
        """图卷积:L*X*\theta
        Args:
        ----------
            input_dim: int
                节点输入特征的维度
            output_dim: int
                输出特征维度
            use_bias : bool, optional
                是否使用偏置
        """
        super(GraphConvolution, self).__init__()
        self.input_dim = input_dim
        self.output_dim = output_dim
        self.use_bias = use_bias
        self.weight = nn.Parameter(torch.Tensor(input_dim, output_dim))
        if self.use_bias:
            self.bias = nn.Parameter(torch.Tensor(output_dim))
        else:
            self.register_parameter('bias', None)
        self.reset_parameters()

    def reset_parameters(self):
        init.kaiming_uniform_(self.weight)
        if self.use_bias:
            init.zeros_(self.bias)

    def forward(self, adjacency, input_feature):
        """邻接矩阵是稀疏矩阵,因此在计算时使用稀疏矩阵乘法
    
        Args: 
        -------
            adjacency: torch.sparse.FloatTensor
                邻接矩阵
            input_feature: torch.Tensor
                输入特征
        """
        device = "cuda" if torch.cuda.is_available() else "cpu"
        support = torch.mm(input_feature, self.weight.to(device))
        output = torch.sparse.mm(adjacency, support)
        if self.use_bias:
            output += self.bias.to(device)
        return output

Más cerca de casa, comencemos con ST-GCN, cuyo nombre en papel y enlace de código son los siguientes:

  • 论文名:Redes convolucionales de gráficos temporales espaciales para el reconocimiento de acciones basadas en esqueletos

  • Dirección del código: https://github.com/yysijie/st-gcn

También hay algunos socios pequeños en Internet que han hecho un análisis relevante y piensan que es bastante bueno. Los enlaces son los siguientes:

https://www.zhihu.com/question/276101856/answer/638672980

A continuación, analizaremos ST-GCN a partir de (1) entrada de datos (2) estructura de red mediante la combinación de documentos y códigos.

2. Entrada de datos

2.1 Estructura de datos

La entrada general para los métodos de reconocimiento de acciones basados ​​en el esqueleto son puntos clave del esqueleto humano temporalmente continuos, como se muestra en la Figura 1 a continuación.

36c591a503566b635bd80943082e715e.png
Figura 1

Estos puntos clave se pueden obtener mediante la estimación de pose a través de openpose, o se pueden anotar manualmente. Sus dimensiones de datos son generalmente (N, C, T, V, M), de las cuales (consulte el artículo citado anteriormente):

  • N representa la cantidad de videos, generalmente un lote tiene 256 videos (de hecho, se establece de manera arbitraria, preferiblemente un índice de 2);

  • C representa la característica de la articulación, generalmente una articulación contiene 3 características como x, y, acc (4 si es un hueso tridimensional), x, y son las coordenadas de posición de la articulación del nodo y acc es la confianza nivel.

  • T representa el número de fotogramas clave, generalmente un video tiene 150 fotogramas.

  • V representa el número de articulaciones, generalmente una persona etiqueta 18 articulaciones.

  • M representa el número de personas en un cuadro y generalmente selecciona a las 2 personas con el promedio de confianza más alto.

Es necesario prestar atención a C (característica), T (tiempo), V (espacio).

2.2 Preprocesamiento de datos

De hecho, los datos de entrada anteriores (N, C, T, V, M) deben normalizarse antes de ingresarlos a la red ST-GCN.

La normalización se realiza en la dimensión temporal, en concreto, normalizando los valores de las características de un nodo en todos los fotogramas clave T. El código de implementación específico es el siguiente:

# data normalization
N, C, T, V, M = x.size()
x = x.permute(0, 4, 3, 1, 2).contiguous()
x = x.view(N * M, V * C, T)
x = self.data_bn(x)
x = x.view(N, M, V, C, T)
x = x.permute(0, 1, 3, 4, 2).contiguous()
x = x.view(N * M, C, T, V)

La función data_bn se define de la siguiente manera:

self.data_bn = nn.BatchNorm1d(in_channels * A.size(1))

2.3 Estrategia de partición de grafos

En el artículo de ST-GCN, otra gran innovación del autor es la introducción de una estrategia de partición de gráficos a través del análisis de movimiento, es decir, el establecimiento de múltiples matrices de adyacencia que reflejan diferentes estados de movimiento (como reposo, movimiento excéntrico y movimiento). movimiento centrípeto). El autor menciona en el texto original que adoptó tres estrategias diferentes, a saber:

  • Etiquetado único, es decir, todos los nodos adyacentes al nodo raíz tienen la misma etiqueta, como se muestra en la Figura b a continuación.

  • Partición de distancia, es decir, la etiqueta del propio nodo raíz se establece en 0 y sus puntos adyacentes se establecen en 1, como se muestra en la Figura c a continuación.

  • La partición de configuración espacial es la estrategia de partición de grafos propuesta en este artículo. Es decir, en base a la distancia entre el nodo raíz y el centro de gravedad (etiqueta=0), entre las distancias de todos los nodos adyacentes al centro de gravedad, se consideran como punto central aquellas menores que el valor de referencia (etiqueta= 1) , y aquellos mayores que el valor de referencia se consideran nodos centrífugos (label=2) .

31f2613cd21f8b86253e18874c66780b.png
Figura 2

La implementación del código específico es la siguiente:

A = []
for hop in valid_hop:
    a_root = np.zeros((self.num_node, self.num_node))
    a_close = np.zeros((self.num_node, self.num_node))
    a_further = np.zeros((self.num_node, self.num_node))
    for i in range(self.num_node):
        for j in range(self.num_node):
            if self.hop_dis[j, i] == hop:
                if self.hop_dis[j, self.center] == self.hop_dis[
                        i, self.center]:
                    a_root[j, i] = normalize_adjacency[j, i]
                elif self.hop_dis[j, self.
                                  center] > self.hop_dis[i, self.
                                                         center]:
                    a_close[j, i] = normalize_adjacency[j, i]
                else:
                    a_further[j, i] = normalize_adjacency[j, i]
    if hop == 0:
        A.append(a_root)
    else:
        A.append(a_root + a_close)
        A.append(a_further)
A = np.stack(A)

Vale la pena señalar que el salto es similar al tamaño del kernel en CNN. hop=0 es el nodo raíz en sí, y hop=1 representa los puntos adyacentes entre el nodo raíz y su distancia igual a 1, que es el cuadro de puntos rojos en la figura anterior (a).

Para comprender mejor el código, utilizamos de forma predeterminada el nodo raíz en los dos bucles anteriores. Debido a la condición ***if self.hop_dis[j, i] == hop*** restricción, puede considerarse como el propio nodo raíz (salto=0) o sus nodos adyacentes (salto=1).

3. Estructura de la red

Los datos de entrada del esqueleto tienen propiedades temporales y espaciales que son críticas para la detección de movimiento. Por lo tanto, se propone que ST-GCN debería tener la capacidad de extraer características de la dimensión espaciotemporal, y su desempeño en GCN es que puede agregar información de la dimensión espaciotemporal al mismo tiempo , como se muestra en la siguiente figura.

32876e6acfd2230309437ebef99ad340.png
imagen 3

Más específicamente, damos el diagrama de estructura específico de ST-GCN, como se muestra en la siguiente figura.

7b2e57439e2e663dcf898a87e708e333.png
Figura 4

Se puede dividir en los siguientes pasos:

  • Paso 1: Introducir una matriz de peso aprendible (del mismo tamaño que la matriz de adyacencia) que se multiplica bit a bit por la matriz de adyacencia. Esta matriz de peso se llama "Peso de importancia de borde aprendible" y se utiliza para otorgar mayor peso a los bordes importantes (nodos) en la matriz de adyacencia y suprimir el peso de los bordes (nodos) no importantes .

  • Paso 2: envíe la matriz de adyacencia ponderada y la entrada a GCN para la operación. Al mismo tiempo, el autor también introdujo una estructura residual (una CNN+BN) para calcular la Res, que se agrega a la salida de GCN poco a poco para realizar la agregación de información de dimensión espacial.

  • Paso 3: Utilice la red TCN (en realidad, una CNN común, con un tamaño de núcleo > 1 en la dimensión temporal) para agregar información en la dimensión temporal.

La implementación del código del módulo ST-GCN anterior es la siguiente:

def forward(self, x, A):

    res = self.residual(x)
    x, A = self.gcn(x, A)
    x = self.tcn(x) + res

    return self.relu(x), A
  • La estructura residual self.residual se define de la siguiente manera:

self.residual = nn.Sequential(
    nn.Conv2d(
        in_channels,
        out_channels,
        kernel_size=1,
        stride=(stride, 1)),
    nn.BatchNorm2d(out_channels),
)
  • GCN se define de la siguiente manera:

self.conv = nn.Conv2d(
        in_channels,
        out_channels * kernel_size,
        kernel_size=(t_kernel_size, 1),
        padding=(t_padding, 0),
        stride=(t_stride, 1),
        dilation=(t_dilation, 1),
        bias=bias)

def forward(self, x, A):
    assert A.size(0) == self.kernel_size

    x = self.conv(x)

    n, kc, t, v = x.size()
    x = x.view(n, self.kernel_size, kc//self.kernel_size, t, v)
    x = torch.einsum('nkctv,kvw->nctw', (x, A))

    return x.contiguous(), A
  • TCN se define de la siguiente manera

    self.tcn = nn.Sequential(
        nn.BatchNorm2d(out_channels),
        nn.ReLU(inplace=True),
        nn.Conv2d(
            out_channels,
            out_channels,
            (kernel_size[0], 1),
            (stride, 1),
            padding,
        ),
        nn.BatchNorm2d(out_channels),
        nn.Dropout(dropout, inplace=True),
    )

De hecho, este documento propone extraer continuamente características semánticas de alto nivel de la entrada de la estructura del gráfico mediante el apilamiento continuo de ST-GCN, de la siguiente manera:

self.st_gcn_networks = nn.ModuleList((
    st_gcn(in_channels, 64, kernel_size, 1, residual=False, **kwargs0),
    st_gcn(64, 64, kernel_size, 1, **kwargs),
    st_gcn(64, 64, kernel_size, 1, **kwargs),
    st_gcn(64, 64, kernel_size, 1, **kwargs),
    st_gcn(64, 128, kernel_size, 2, **kwargs),
    st_gcn(128, 128, kernel_size, 1, **kwargs),
    st_gcn(128, 128, kernel_size, 1, **kwargs),
    st_gcn(128, 256, kernel_size, 2, **kwargs),
    st_gcn(256, 256, kernel_size, 1, **kwargs),
    st_gcn(256, 256, kernel_size, 1, **kwargs),
))

# initialize parameters for edge importance weighting
if edge_importance_weighting:
    self.edge_importance = nn.ParameterList([
        nn.Parameter(torch.ones(self.A.size()))
        for i in self.st_gcn_networks
    ])
else:
    self.edge_importance = [1] * len(self.st_gcn_networks)

# ST-GCN与可学习的权重矩阵不断重复与堆叠
for gcn, importance in zip(self.st_gcn_networks, self.edge_importance):
 x, _ = gcn(x, self.A * importance)

Después de eso, de manera similar a la tarea de clasificación general, el autor presenta la agrupación promedio global y la rama de predicción de salida de la capa totalmente convolucional, de la siguiente manera:

# global pooling
x = F.avg_pool2d(x, x.size()[2:])
x = x.view(N, M, -1, 1, 1).mean(dim=1)

# prediction
x = self.fcn(x)
x = x.view(x.size(0), -1)

Hasta ahora, podemos comprender fácilmente la estructura de red específica de ST-GCN a través del código.

Resumir

Hasta ahora, el análisis de ST-GCN ha terminado, ¡espero que pueda ayudar a todos! ¡También puede prestar atención a mi cuenta pública de WeChat!

Lectura recomendada:

Mi intercambio de reclutamiento escolar por Internet de 2022

Mi Resumen 2021

Hablando de la diferencia entre la publicación de algoritmos y la publicación de desarrollo

Resumen de salarios de investigación y desarrollo de reclutamiento de escuelas de Internet

Para series de tiempo, todo lo que puedes hacer.

¿Qué es el problema de la secuencia espacio-temporal? ¿Qué modelos se utilizan principalmente para tales problemas? ¿Cuáles son las principales aplicaciones?

Número público: coche caracol AI

Mantente humilde, mantente disciplinado, mantente progresista

cc96ddcdb9faa8f85a57dacad7cbc104.png

Envíe [Snail] para obtener una copia del "Proyecto práctico de IA" (AI Snail Car)

Envíe [1222] para obtener una buena nota de cepillado de leetcode

Envíe [AI Four Classics] para obtener cuatro libros electrónicos clásicos de AI

Supongo que te gusta

Origin blog.csdn.net/qq_33431368/article/details/123492100
Recomendado
Clasificación