[PYTHON] PointNet-Theorie und Implementierung (Punktgruppendaten)

Einführung

Dies ist die Erklärung von ** PointNet **, dem grundlegendsten Deep-Learning-Modell für ** Punktgruppendaten **. Um PointNet zu verstehen, müssen die Punktgruppendaten verstanden werden. Daher werden zuerst die Punktgruppendaten erläutert, dann die Theorie von PointNet erläutert und mit ** PyTorch ** implementiert.

Darüber hinaus führen wir als einfaches Experiment mit PointNet eine ** binäre Klassifizierungsaufgabe ** durch, bei der dreidimensionale Daten aus einer gleichmäßigen Verteilung und einer Normalverteilung abgetastet werden und erraten wird, aus welcher Verteilung die Daten abgetastet wurden.

Das PointNet-Papier ist hier. Der implementierte Code wird auf GitHub veröffentlicht.

Was sind Punktgruppendaten (Punktwolke)?

Punktgruppendaten sind Daten, die in den letzten Jahren im automatischen Betrieb Aufmerksamkeit erregt haben. Dies liegt daran, dass die Daten, die von einem im automatischen Betrieb verwendeten Sensor namens LiDAR erhalten werden, Punktgruppendaten sind. Darüber hinaus wird es in der Bauindustrie zur dreidimensionalen Messung und zur chemischen Berechnung von Molekülen eingesetzt, und sein Anwendungsbereich ist breit gefächert.

Obwohl Punktgruppendaten einen so breiten Anwendungsbereich haben, haben sie drei Haupteigenschaften **, und es ist erforderlich, die folgenden drei Eigenschaften beim Umgang mit Punktgruppendaten beim maschinellen Lernen zu berücksichtigen.

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

Unbestimmt

Die Ordnungsinvarianz ist die Eigenschaft, dass die Ausgabe auch dann unveränderlich ist, wenn die Reihenfolge der Punktgruppendaten geändert und in das maschinelle Lernmodell eingegeben wird.

Im Fall eines Bildes ist es beispielsweise möglich, jedes Pixel von links oben nach rechts unten zu ordnen. Bei Punktgruppendaten kann jedoch nicht jeder Punkt geordnet werden. Daher werden die Punkte jedes Mal, wenn Sie ein Modell für maschinelles Lernen eingeben, in einer anderen Reihenfolge eingegeben. Zu diesem Zeitpunkt muss das maschinelle Lernmodell jedes Mal den gleichen Wert (er muss unveränderlich sein) für Punktgruppeneingaben in verschiedenen Sequenzen ausgeben. Mit anderen Worten ist das Erfüllen der folgenden Gleichung eine Bedingung der Invarianz. $ f(\boldsymbol x_1, \boldsymbol x_2, ..., \boldsymbol x_M)= f(\boldsymbol x_{\pi(1)}, \boldsymbol x_{\pi(2)}, ..., \boldsymbol x_{\pi(M)}) $ Wobei $ \ boldsymbol x_m $ die dreidimensionale Koordinate des $ m $ -ten Punkts ist und $ \ pi $ eine beliebige Art darstellt. Da es allein aus der Formel schwer zu verstehen ist, ist es in der Abbildung wie folgt. <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302831/992734b8-caa4-a89b-d74b-bd26f6e55751.png ", width="65%"> Wie ich später ausführlich erläutern werde, erfüllt PointNet die Auftragsinvarianz mithilfe einer Funktion namens Max Pooling.

Bewegung unveränderlich

Die Bewegungsinvarianz ist die Eigenschaft, dass die Ausgabe auch dann unveränderlich ist, wenn Punktgruppendaten mit paralleler Bewegung oder Rotationsbewegung in das maschinelle Lernmodell eingegeben werden. Tatsächlich ist diese Eigenschaft nicht nur für Punktgruppendaten gültig, und Bilder haben dieselbe Eigenschaft. Die Parallelbewegungsinvarianz wird durch die folgende Gleichung ausgedrückt. $ 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) $ Die Eingabe $ \ boldsymbol x_m $ wird von $ \ boldsymbol r $ parallelisiert, aber die Ausgabe hat sich nicht geändert. Als nächstes lautet die Gleichung für die Rotationsbewegungsinvarianz wie folgt. $ f(R\boldsymbol x_1, R\boldsymbol x_2, ..., R\boldsymbol x_M)= f(\boldsymbol x_1, \boldsymbol x_2, ..., \boldsymbol x_M) $

Sie können sehen, dass die Ausgabe in Bezug auf die Rotationsbewegung der Eingabedaten unveränderlich ist.

Wie später ausführlich erläutert wird, erfasst PointNet die Bewegungsinvarianz ungefähr, indem eine affine Transformation (Parallel- / Rotationsbewegung) an der Eingabepunktgruppe durchgeführt wird. ** Ich bin jedoch nicht unbedingt zufrieden mit der Unveränderlichkeit der Bewegung **. Um beispielsweise die Bewegungsinvarianz strikt zu erfüllen, kann ein Verfahren zum Verwenden des Abstands zwischen zwei Punkten als Merkmalsgröße in Betracht gezogen werden. (Selbst wenn zwei Punkte parallel bewegt oder gedreht werden, ist der Abstand konstant. Daher kann die Bewegungsinvarianz streng erfüllt werden, indem der Abstand zwischen den beiden Punkten als Merkmalsgröße verwendet wird. SchNet und [HIP-NN](https: // aip. Ich verwende diese Methode in einem chemischen Papier namens scitation.org/doi/abs/10.1063/1.5011181).)

Lokalität

Lokalität ist die Eigenschaft, dass ** Punkte, die räumlich nahe beieinander liegen, eine enge Beziehung haben und Punkte, die räumlich entfernt sind, weniger miteinander verwandt sind **. Diese Eigenschaft ist nicht nur für die Punktgruppe gültig, und auch Bilder usw. haben diese Eigenschaft. Wenn es sich um ein Bild handelt, kann die Lokalität durch Verwendung einer Faltungsschicht erfüllt werden. (Tatsächlich ist PointNet mit der Lokalität nicht zufrieden. Das Modell, das den größten Nachteil von PointNet überwindet, ist PointNet ++. -on-Punkt-setzt-in-einem-metrischen-Raum).)

PointNet-Theorie

Zunächst zeige ich Ihnen die Architektur von PointNet. Der blaue Teil ist das Klassifizierungsnetzwerk und der gelbe Teil ist das Segmentierungsnetzwerk. Wie der Name schon sagt, werden Klassifizierung und Segmentierung entsprechend dem Zweck verwendet. Dieses Mal werde ich nur das blaue Klassifizierungsnetzwerk erklären, aber das ist die Essenz von PointNet. <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302831/01a21a0e-8582-98d4-e902-fb348b9482cb.png ", width="100%"> Als Fluss des Klassifizierungsnetzwerks wird zunächst die Eingabepunktgruppe einer affinen Transformation durch ** Eingabetransformation ** unterzogen, und die Bewegungsinvarianz wird ungefähr erfasst. Als nächstes werden die Punkte nach der affinen Transformation vom neuronalen Netz verarbeitet, und die affine Transformation wird erneut von der Merkmalstransformation durchgeführt. Dann wird es mit einem neuronalen Netz verarbeitet und schließlich wird ** Max Pooling ** verwendet, um Vorwärtsinvarianz und Ausgabe zu erhalten.

Max Pooling Das Wichtigste an PointNet ist Max Pooling **. Max Pooling ist eine sehr einfache Funktion **, eine Funktion, die das größte Element der Eingabeelemente ausgibt **. Wenn das Eingabeelement beispielsweise {0, 1, 2, 3} ist, ist die Ausgabe von Max Pooling 3, was das maximale Element ist. $ \rm{MaxPooling}(0, 1, 2, 3)=3 $ Sie können sehen, dass sich die Ausgabe auch dann nicht ändert, wenn die Eingabeelemente neu angeordnet und durch Max Pooling geleitet werden. $ \rm{MaxPooling}(1, 0, 3, 2)=3 $ Daraus ist ersichtlich, dass Max Pooling die Auftragsinvarianz erfüllt. PointNet hat ** invariant ** mithilfe von Max Pooling am Ende des Netzwerks erfasst.

Input transform Die Eingabetransformation (Feature-Transformation) verschiebt die Eingabepunktgruppe parallel und rotierend, indem eine ** affine Matrix ** auf die Eingabepunktgruppe angewendet wird, und erhält ungefähr eine ** Bewegungsinvarianz **. Obwohl es sich um eine affine Matrix handelt, kann sie als Ausgabe von ** T-Net ** erhalten werden. T-Net hat eine ähnliche Struktur wie Mini Point-Net und besteht aus einer Kombination von neuronalen Netzen und Max Pooling. Wenn Sie eine Gruppe dreidimensionaler Punkte in dieses T-Net eingeben, erhalten Sie eine affine Matrix als Ausgabe.

Implementierung (PyTorch)

Implementierung der Eingangstransformation (T-Net)

Wie bereits erläutert, ist T-Net ein Netzwerk, das eine Gruppe dreidimensionaler Punkte als Eingabe verwendet und eine affine Matrix ausgibt.

Wie unten gezeigt, wird die nichtlineare Umwandlung durch das neuronale Netz (nichtlinear) wiederholt, Max Pooling wird in der Mitte eingeklemmt und schließlich wird ein Tensor der Größe (9 × 1) ausgegeben. Ändern Sie die Größe dieser Ausgabe auf (3x3), um die affine Matrix zu erhalten. Dann wird das Produkt der erhaltenen affinen Matrix und der Eingabedaten berechnet und an die nächste Schicht weitergeleitet.

Beachten Sie, dass die Feature-Transformation, die eine affine Transformation für den Feature-Betrag in der Mitte von PointNet durchführt, nahezu identisch ist, sodass die Erklärung weggelassen wird.

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

NonLinear ist übrigens eine selbst erstellte Funktion, die Dense, ReLU und Batch Normalization zusammenfasst.

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)

Implementierung des gesamten PointNet

PointNet hat eine Eingabestruktur → T-Netz → NN → T-Netz → NN → Max Pool → NN → Ausgabe. Also werde ich das einfach in den Code einfügen.

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)

Experiment

Als einfaches Experiment habe ich dreidimensionale Punkte zufällig aus einer gleichmäßigen Verteilung und einer Normalverteilung abgetastet und mit PointNet vorhergesagt, von welchem sie abgetastet wurden.

Die Funktion, die aus der Wahrscheinlichkeitsverteilung abtastet, hat die folgende Implementierung.

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)

Unter Verwendung des zuvor implementierten PointNet und dieser Abtastfunktion werden Lernen und Auswertung wie folgt durchgeführt. Die Stapelgröße beträgt 64 und die Anzahl der Punkte in einem Datensatz beträgt 16.

Obwohl es sich um new_param handelt, wird es so eingestellt, dass der Anfangswert der Vorspannung der letzten Schicht von TNet eine Einheitsmatrix ist (abgeflachte Version). Eine solche Initialisierung wird im Papier empfohlen.

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))

Das Ergebnis ist wie folgt:

Ich bin mir nicht sicher, ob die Aufgabe zu einfach oder PointNet gut ist, aber Sie können sehen, dass sie gut kategorisiert ist. Der hier angegebene Code ist Teil des Ganzen. Wenn Sie also das Ganze kennenlernen möchten, lesen Sie bitte GitHub hier.

Recommended Posts

PointNet-Theorie und Implementierung (Punktgruppendaten)
Normalisierung der Strömungstheorie und -implementierung
Punkt- und Figurendatenmodellierung
Einfache Theorie und Implementierung des neuronalen Netzes
Open3D Grundlagen und Punktgruppenboxen
Python-Datenstruktur und interne Implementierung ~ Liste ~
Perceptron Grundlagen und Implementierung
Punktwolke mit Pfeffer
Theorie und Implementierung mehrerer Regressionsmodelle - warum Regularisierung erforderlich ist -