[PYTHON] Théorie et implémentation de PointNet (données de groupe de points)

introduction

C'est l'explication de ** PointNet **, qui est le plus élémentaire des modèles d'apprentissage en profondeur pour les ** données de groupe de points **. Afin de comprendre PointNet, il est nécessaire de comprendre les données du groupe de points, nous allons donc d'abord expliquer les données du groupe de points, puis expliquer la théorie de PointNet et l'implémenter avec ** PyTorch **.

De plus, en tant qu'expérience simple utilisant PointNet, nous effectuerons une ** tâche de classification binaire ** qui échantillonne des données tridimensionnelles à partir d'une distribution uniforme et d'une distribution normale et devine à partir de quelle distribution les données ont été échantillonnées.

Le document PointNet est ici. Le code implémenté est publié sur GitHub.

Qu'est-ce que les données de groupe de points (nuage de points)?

Les données de groupe de points sont des données qui ont attiré l'attention dans le fonctionnement automatique ces dernières années. En effet, les données obtenues à partir d'un capteur appelé LiDAR utilisé en fonctionnement automatique sont des données de groupe de points. De plus, il est utilisé dans la mesure tridimensionnelle dans l'industrie de la construction et dans le calcul chimique des molécules, et son domaine d'application est large.

Bien que les données de groupe de points aient un si large éventail d'applications, elles ont trois propriétés principales **, et il est nécessaire de prendre en compte les trois propriétés suivantes lors de la gestion des données de groupe de points dans l'apprentissage automatique.

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302831/9e731cee-f997-6f85-b7c1-ac37a025ba5c.png ", width="35%">

Indéfini

L'invariance d'ordre est la propriété selon laquelle la sortie est invariante même si l'ordre des données du groupe de points est modifié et entré dans le modèle d'apprentissage automatique.

Par exemple, dans le cas d'une image, il est possible d'ordonner chaque pixel du haut à gauche vers le bas à droite. Cependant, les données de groupe de points ne permettent pas de classer chaque point, donc chaque fois que vous entrez dans un modèle d'apprentissage automatique, les points sont saisis dans un ordre différent. À ce stade, le modèle d'apprentissage automatique doit générer la même valeur (elle doit être invariante) à chaque fois pour les entrées de groupe de points dans différentes séquences. En d'autres termes, la satisfaction de l'équation suivante est une condition d'invariance. $ f(\boldsymbol x_1, \boldsymbol x_2, ..., \boldsymbol x_M)= f(\boldsymbol x_{\pi(1)}, \boldsymbol x_{\pi(2)}, ..., \boldsymbol x_{\pi(M)}) $ Où $ \ boldsymbol x_m $ est la coordonnée tridimensionnelle du $ m $ ème point, et $ \ pi $ représente n'importe quel tri. Puisqu'il est difficile à comprendre à partir de la formule seule, c'est comme suit dans la figure. <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302831/992734b8-caa4-a89b-d74b-bd26f6e55751.png ", width="65%"> Comme je l'expliquerai en détail plus tard, PointNet satisfait l'invariance d'ordre en utilisant une fonction appelée Max Pooling.

Mouvement immuable

L'invariance de mouvement est la propriété que la sortie est invariante même si des données de groupe de points avec mouvement parallèle ou mouvement de rotation sont entrées dans le modèle d'apprentissage automatique **. En fait, cette propriété n'est pas unique pour les données de groupe de points et les images ont la même propriété. L'invariance du mouvement parallèle est exprimée par l'équation suivante. $ f(\boldsymbol x_1+\boldsymbol r, \boldsymbol x_2+\boldsymbol r, ..., \boldsymbol x_M+\boldsymbol r)= f(\boldsymbol x_1, \boldsymbol x_2, ..., \boldsymbol x_M) $ L'entrée $ \ boldsymbol x_m $ est mise en parallèle avec $ \ boldsymbol r , mais la sortie n'a pas changé. Ensuite, l'équation de l'invariance du mouvement de rotation est la suivante. $ f(R\boldsymbol x_1, R\boldsymbol x_2, ..., R\boldsymbol x_M)= f(\boldsymbol x_1, \boldsymbol x_2, ..., \boldsymbol x_M) $$

Vous pouvez voir que la sortie est invariante par rapport au mouvement de rotation des données d'entrée.

Comme cela sera expliqué en détail plus loin, PointNet acquiert approximativement l'invariance de mouvement en effectuant une transformation affine (mouvement parallèle / rotationnel) sur le groupe de points d'entrée. Cependant, ** je ne suis pas strictement satisfait de l'immuabilité du mouvement **. Afin de satisfaire strictement l'invariance de mouvement, par exemple, une méthode d'utilisation de la distance entre deux points comme quantité de caractéristiques peut être envisagée. (Même si deux points sont déplacés en parallèle ou tournés, la distance est constante. Par conséquent, l'invariance de mouvement peut être strictement satisfaite en utilisant la distance entre les deux points comme quantité d'entités. SchNet et [HIP-NN](https: // aip. J'utilise cette méthode dans un article sur la chimie appelé scitation.org/doi/abs/10.1063/1.5011181).)

Localité

La localité est la propriété que ** les points qui sont spatialement proches les uns des autres ont une sorte de relation étroite, et les points qui sont spatialement éloignés sont moins liés les uns aux autres **. Cette propriété n'est pas unique au groupe de points, et les images, etc. ont également cette propriété. S'il s'agit d'une image, la localité peut être satisfaite en utilisant une couche de convolution. (En fait, PointNet n'est pas satisfait de la localité. Le modèle qui surmonte le plus gros inconvénient de PointNet est PointNet ++. -on-point-sets-in-a-metric-space).)

Théorie de PointNet

Tout d'abord, je vais vous montrer l'architecture de PointNet. La partie bleue est le réseau de classification et la partie jaune est le réseau de segmentation. Comme son nom l'indique, la classification et la segmentation sont utilisées en fonction de l'objectif. Cette fois, je n'expliquerai que le réseau de classification bleu, mais c'est l'essence même de PointNet. <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302831/01a21a0e-8582-98d4-e902-fb348b9482cb.png ", width="100%"> En tant que flux du réseau de classification, tout d'abord, le groupe de points d'entrée est soumis à une transformation affine par ** transformée d'entrée **, et l'invariance de mouvement est approximativement acquise. Ensuite, les points après la transformation affine sont traités par le réseau neuronal et la transformation affine est à nouveau exécutée par la transformation de caractéristiques. Ensuite, il est traité avec un réseau neuronal, et enfin ** Max Pooling ** est utilisé pour obtenir l'invariance et la sortie avant.

Max Pooling La chose la plus importante à propos de PointNet est Max Pooling **. Max Pooling est une fonction très simple, ** une fonction qui produit le plus grand élément des éléments d'entrée **. Par exemple, si l'élément d'entrée est {0, 1, 2, 3}, la sortie de Max Pooling sera 3, qui est l'élément maximum. $ \rm{MaxPooling}(0, 1, 2, 3)=3 $ Vous pouvez voir que la sortie ne change pas même si les éléments d'entrée sont réorganisés et passés par Max Pooling. $ \rm{MaxPooling}(1, 0, 3, 2)=3 $ A partir de là, on peut voir que Max Pooling satisfait l'invariance d'ordre. PointNet a acquis ** invariant ** en utilisant Max Pooling à la fin du réseau.

Input transform La transformation d'entrée (transformation d'entité) déplace le groupe de points d'entrée en parallèle et en rotation en appliquant la ** matrice affine ** au groupe de points d'entrée, et obtient ** l'invariance de mouvement ** approximativement. Bien qu'il s'agisse d'une matrice affine, elle peut être obtenue comme sortie de ** T-Net **. T-Net a une structure similaire à mini Point-Net et se compose d'une combinaison de réseau neuronal et de mise en commun maximale. Si vous entrez un groupe de points tridimensionnels dans ce T-Net, vous obtiendrez une matrice affine en sortie.

Implémentation (PyTorch)

Implémentation de la transformée d'entrée (T-Net)

Comme expliqué précédemment, T-Net est un réseau qui prend un groupe de points tridimensionnels en entrée et sort une matrice affine.

Comme indiqué ci-dessous, la conversion non linéaire par le réseau neuronal (NonLinear) est répétée, Max Pooling est pris en sandwich au milieu, et enfin (9 × 1) taille Tensor est sortie. Redimensionnez cette sortie à (3x3) pour obtenir la matrice affine. Ensuite, le produit de la matrice affine obtenue et des données d'entrée est calculé et transmis à la couche suivante.

Notez que la transformation de fonctionnalité qui effectue une transformation affine pour la quantité de fonctionnalité au milieu de PointNet est presque la même, donc l'explication est omise.

model.py


class InputTNet(nn.Module):
    def __init__(self, num_points):
        super(InputTNet, self).__init__()
        self.num_points = num_points
        
        self.main = nn.Sequential(
            NonLinear(3, 64),
            NonLinear(64, 128),
            NonLinear(128, 1024),
            MaxPool(1024, self.num_points),
            NonLinear(1024, 512),
            NonLinear(512, 256),
            nn.Linear(256, 9)
        )
        
    # shape of input_data is (batchsize x num_points, channel)
    def forward(self, input_data):
        matrix = self.main(input_data).view(-1, 3, 3)
        out = torch.matmul(input_data.view(-1, self.num_points, 3), matrix)
        out = out.view(-1, 3)
        return out

À propos, NonLinear est une fonction auto-conçue qui résume la normalisation dense, ReLU et par lots.

model.py


class NonLinear(nn.Module):
    def __init__(self, input_channels, output_channels):
        super(NonLinear, self).__init__()
        self.input_channels = input_channels
        self.output_channels = output_channels

        self.main = nn.Sequential(
            nn.Linear(self.input_channels, self.output_channels),
            nn.ReLU(inplace=True),
            nn.BatchNorm1d(self.output_channels))

    def forward(self, input_data):
        return self.main(input_data)

Implémentation de l'ensemble de PointNet

PointNet a une structure d'entrée → T-Net → NN → T-Net → NN → Max Pool → NN → sortie. Je vais donc simplement déposer ceci dans le code.

model.py


class PointNet(nn.Module):
    def __init__(self, num_points, num_labels):
        super(PointNet, self).__init__()
        self.num_points = num_points
        self.num_labels = num_labels
        
        self.main = nn.Sequential(
            InputTNet(self.num_points),
            NonLinear(3, 64),
            NonLinear(64, 64),
            FeatureTNet(self.num_points),
            NonLinear(64, 64),
            NonLinear(64, 128),
            NonLinear(128, 1024),
            MaxPool(1024, self.num_points),
            NonLinear(1024, 512),
            nn.Dropout(p = 0.3),
            NonLinear(512, 256),
            nn.Dropout(p = 0.3),
            NonLinear(256, self.num_labels),
            )
        
    def forward(self, input_data):
        return self.main(input_data)

Expérience

En guise d'expérience simple, j'ai échantillonné au hasard des points tridimensionnels à partir d'une distribution uniforme et d'une distribution normale, et j'ai utilisé PointNet pour prédire lequel était échantillonné.

La fonction qui échantillonne à partir de la distribution de probabilité a l'implémentation suivante.

sampler.py


def data_sampler(batch_size, num_points):
    half_batch_size = int(batch_size/2)
    normal_sampled = torch.randn(half_batch_size, num_points, 3)
    uniform_sampled = torch.rand(half_batch_size, num_points, 3)
    normal_labels = torch.ones(half_batch_size)
    uniform_labels = torch.zeros(half_batch_size)

    input_data = torch.cat((normal_sampled, uniform_sampled), dim=0)
    labels = torch.cat((normal_labels, uniform_labels), dim=0)

    data_shuffle = torch.randperm(batch_size)
    
    return input_data[data_shuffle].view(-1, 3), labels[data_shuffle].view(-1, 1)

En utilisant le PointNet implémenté précédemment et cette fonction d'échantillonnage, l'apprentissage et l'évaluation sont effectués comme suit. La taille du lot est de 64 et le nombre de points dans un ensemble de données est de 16.

Bien qu'il s'agisse de new_param, il est positionné pour que la valeur initiale du biais de la couche finale de TNet soit une matrice unitaire (version aplatie). Une telle initialisation est recommandée dans le document.

main.py


batch_size = 64
num_points = 16
num_labels = 1

pointnet = PointNet(num_points, num_labels)
        
new_param = pointnet.state_dict()
new_param['main.0.main.6.bias'] = torch.eye(3, 3).view(-1)
new_param['main.3.main.6.bias'] = torch.eye(64, 64).view(-1)
pointnet.load_state_dict(new_param)

criterion = nn.BCELoss()
optimizer = optim.Adam(pointnet.parameters(), lr=0.001)

loss_list = []
accuracy_list = []

for iteration in range(100+1):
    
    pointnet.zero_grad()
    
    input_data, labels = data_sampler(batch_size, num_points)
    
    output = pointnet(input_data)
    output = nn.Sigmoid()(output)

    error = criterion(output, labels)
    error.backward()
    
    optimizer.step()
    
    if iteration % 10 == 0:
        with torch.no_grad():
            output[output > 0.5] = 1
            output[output < 0.5] = 0
            accuracy = (output==labels).sum().item()/batch_size
            
            loss_list.append(error.item())
            accuracy_list.append(accuracy)
            
        print('Iteration : {}   Loss : {}'.format(iteration, error.item()))
        print('Iteration : {}   Accuracy : {}'.format(iteration, accuracy))

Le résultat est le suivant:

Je ne sais pas si la tâche est trop facile ou si PointNet est bon, mais vous pouvez voir qu'il est bien catégorisé. Le code donné ici fait partie de l'ensemble, donc si vous voulez connaître l'ensemble, veuillez consulter GitHub ici.

Recommended Posts

Théorie et implémentation de PointNet (données de groupe de points)
Normalisation de la théorie et de la mise en œuvre des flux
Modélisation de données point et figure
Théorie et implémentation simples des réseaux neuronaux
Les bases d'Open3D et la boxe de groupe
Structure de données Python et implémentation interne ~ Liste ~
Principes de base et mise en œuvre de Perceptron
Nuage de points avec du poivre
Théorie et mise en œuvre de modèles de régression multiple - pourquoi une régularisation est nécessaire -