HiSilicon-Entwicklung: yolo v5s: pytorch->onnx->caffe->nnie

I. Einleitung

Der Hauptgrund ist, dass Sie, wenn Sie auf ein paar Probleme stoßen, diese schnell aufzeichnen sollten, um später nicht den gleichen Verlust durch die Brüder zu erleiden, und um sich selbst zu helfen, sich zu erinnern. Hängen Sie meinen yolo v5-Nachbearbeitungsteil C-Sprachversionscode an: C-Version yolo v5s-Nachbearbeitungsteil

Zweitens, Konvertierungsfehler

1. 报错:
Reshape dimention number shall be 2 or 4

Bei näherer Betrachtung hängt es mit der von meinem Umformen verarbeiteten Datendimension zusammen, und die maximale Anzahl von Dimensionen, die von Umformen im Konvertierungscode unterstützt werden, beträgt 4. Und meine Datenform ist (1, 3, H, W, class_num + 5), was ein fünfdimensionales Array ist.
Bildbeschreibung hier einfügen
Ursprünglich wollte ich eine Bedingung hinzufügen: len(shape) == 5 und hatte Angst vor neuen Fehlern.Ich habe im Internet andere Konvertierungscodes gefunden, hineingeklickt und geschaut und festgestellt, dass der Inhalt im Wesentlichen derselbe ist, außer dass len fehlte. (Form) == 3. Nachdem ich es gelesen habe, verstehe ich, dass der Autor möglicherweise einige Änderungen an der Demo vorgenommen hat, die ich jetzt verwende, und das Problem, auf das er gestoßen ist, das gleiche sein könnte wie meins. Sie können also gerne len(shape) == 5 hinzufügen
und die Demo erneut ausführen.

2. 报错:
Permute dimention number shall be 2 or 4

Der zweite Fehler ist derselbe wie oben, Sie kennen die Straße und fügen hinten eine Bedingung hinzu.
Bildbeschreibung hier einfügen

3. 报错:
Check failed: num_output_ % group_ == 0 (1 vs. 0) Number of output should be multiples of group.

Hier Bild einfügen ist die Slice-Beschreibung
Der spezifische Fehler ist wie oben, das heißt, die Gruppenfaltung/Gruppenentfaltung hat eine strenge Anforderung, dass input_channel und output_channel ganzzahlige Vielfache der Gruppe sind, aber der Umwandlungsgrund macht output_channel hier gleich 1. Schauen Sie sich direkt den konvertierten Quellcode an.
Bildbeschreibung hier einfügen
Der Code in der Box war ursprünglich num_output=W.shape[1], aber ich habe die Gruppenfaltung für die Dekonvolution verwendet, w.shape = (512, 1, 2, 2), das ist falsch, die Ausgabenummer sollte 512 sein, ist richtig , nehmen Sie die Korrektur im Screenshot vor. Führen Sie es erneut aus und die Konvertierung ist abgeschlossen! Lassen Sie uns ein paar Bilder laufen lassen, um zu sehen, ob die Ausgabewerte konsistent sind.

4. caffe 转 wk 模型报错
Permute can only support (0,1,2,3) -> (0,2,3,1) order!
reshape only support C/H/W, not N!

Diese beiden Fehler treten nicht zusammen auf, und die Gründe für die Fehler sind ähnlich, das heißt, das Format der prototxt-Datei des Modells erfüllt nicht die Anforderungen von nnie. Lassen Sie mich zuerst über die folgende Ebene sprechen, nämlich die Permute-Ebene.Wenn Sie vorübergehend das HiSilicon-Dokument durchblättern, lautet die Beschreibung wie folgt: Sie können sehen, dass
Bildbeschreibung hier einfügen
die Permute-Ebene eine ziemliche Einschränkung hat.Nach dem Feedback der Gruppe, dies op ist ziemlich langsam. Erwägen Sie, es basierend auf der tatsächlichen Situation zu entfernen. Das heißt, löschen Sie es in der prototxt-Datei.
Bildbeschreibung hier einfügen
Was die Umformungsebene betrifft, konnte ich sie nicht in der Dokumentation finden, also musste ich verschiedene Methoden ausprobieren und fand schließlich heraus, dass das richtige Format dieses ist.
Bildbeschreibung hier einfügen
Ändern Sie es einfach in das folgende Format:
Bildbeschreibung hier einfügen
Konvertieren Sie es später, es wird kein Problem geben, warten Sie einfach, bis die Modellkonvertierung abgeschlossen ist.

3. Andere Wissenspunkte

Beim Schreiben dieses Projekts bin ich auf einige Probleme gestoßen, und ich denke, es ist notwendig, Ihnen davon zu erzählen, Ihnen zu helfen, sich zu erinnern, und Ihnen eine Erfahrung als Referenz zu geben.

1. Die Dimensionen der Umformung und die Speicherverteilung der HiSilicon-Ausgangsdaten

Sehen Sie sich zuerst den Formänderungsprozess der Ausgabedaten des Quellcodes an, siehe die Datei yolov5/model/yolo.py:

bs, _, ny, nx = x[i].shape  # x(bs, 85 * 3, 20, 20) to x(bs, 3, 20, 20, 85)
# contiguous() : https://zhuanlan.zhihu.com/p/64551412
x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()

Mein Trainingsdatensatz besteht aus 48 Kategorien, und die Ausgabeform einer der Schichten ist (1, 159, 20, 20), und ihr Formänderungsprozess ist:

#                 reshape                       permute
(1, 159, 20, 20) ---------> (1, 3, 53, 20, 20) ---------> (1, 3, 20, 20, 53)

Da die Ausgabedatendimension der Python-Version zu hoch ist, wird sie von HiSilicon nicht unterstützt, und die HiSilicon-Permutierungsoperation ist von Natur aus begrenzt und kann nicht mehr Operationen unterstützen, sodass es unvermeidlich ist, die Permutierung abzubrechen. Aber wie wir die Form bekommen, die wir brauchen, am Anfang dachte ich darüber nach, in den erforderlichen Status umzuformen, wie zum Beispiel:

#                 reshape                       reshape
(1, 159, 20, 20) ---------> (0, 3, 53, 400) ---------> (03, 400, 53)
# 以上是错误操作,不要用,海思 reshape 要求输出第一维是 0 且最多维度数为 4 .

Das scheint gut zu gehen, aber wenn Sie genau darüber nachdenken, unterscheidet sich der Mechanismus von Permute von dem von Reshape. Zu diesem Zweck haben wir ein Experiment durchgeführt:

a = torch.arange(60).view((1, 15, 2, 2)).view((1, 3, 5, 2, 2)).permute(0, 1, 3, 4, 2).contiguous()
print(a.shape)
print(a)
# output:
torch.Size([1, 3, 2, 2, 5]) # shape
# data
tensor([[[[[ 0,  4,  8, 12, 16],
           [ 1,  5,  9, 13, 17]],

          [[ 2,  6, 10, 14, 18],
           [ 3,  7, 11, 15, 19]]],


         [[[20, 24, 28, 32, 36],
           [21, 25, 29, 33, 37]],

          [[22, 26, 30, 34, 38],
           [23, 27, 31, 35, 39]]],


         [[[40, 44, 48, 52, 56],
           [41, 45, 49, 53, 57]],

          [[42, 46, 50, 54, 58],
           [43, 47, 51, 55, 59]]]]])
d = torch.arange(60).view((1, 15, 2, 2)).view((1, 3, 2, 2, 5))
print(d.shape)
print(d)
# output
torch.Size([1, 3, 2, 2, 5]) # shape
# data
tensor([[[[[ 0,  1,  2,  3,  4],
           [ 5,  6,  7,  8,  9]],

          [[10, 11, 12, 13, 14],
           [15, 16, 17, 18, 19]]],


         [[[20, 21, 22, 23, 24],
           [25, 26, 27, 28, 29]],

          [[30, 31, 32, 33, 34],
           [35, 36, 37, 38, 39]]],


         [[[40, 41, 42, 43, 44],
           [45, 46, 47, 48, 49]],

          [[50, 51, 52, 53, 54],
           [55, 56, 57, 58, 59]]]]])

Obwohl die Formen gleich sind, sind ihre numerischen Verteilungen völlig unterschiedlich, und die Datenverteilung zwischen den beiden muss keinen Regeln folgen. Die Idee von Umformen statt Permutieren ist also falsch. Aber unsere Arbeit muss weitergehen, und wir dürfen sie nicht aufgeben.
Zurück zum Thema, es wird beobachtet, dass die von Python ausgegebene Datenform der Ausgabeform von HiSilicon am nächsten kommt:

#                reshape                   permute                  reshape
(1, 159, 20, 20) -----> (1, 3, 53, 20, 20) -----> (1, 3, 20, 20, 53) -----> (1, 3, 400, 53)
# python 版本输出

#                reshape                
(1, 159, 20, 20) -----> (0, 3, 53, 400) 
# 海思 版本输出

Es ist ersichtlich, dass sich die beiden sehr ähnlich sind. Kann dies verwendet werden? Machen Sie ein Experiment und sehen Sie:

# 模拟 python 输出
a = torch.arange(60).view((1, 15, 2, 2)).view((1, 3, 5, 2, 2)).permute(0, 1, 3, 4, 2).contiguous().view((1, 3, 4, 5))
print(a.shape)
print(a)
# output
torch.Size([1, 3, 4, 5]) # shape
# data
tensor([[[[ 0,  4,  8, 12, 16], # 一行等于下面数组的一列
          [ 1,  5,  9, 13, 17],
          [ 2,  6, 10, 14, 18],
          [ 3,  7, 11, 15, 19]],
# ---------------------------------- anchor 
         [[20, 24, 28, 32, 36],
          [21, 25, 29, 33, 37],
          [22, 26, 30, 34, 38],
          [23, 27, 31, 35, 39]],
# ---------------------------------- anchor 
         [[40, 44, 48, 52, 56],
          [41, 45, 49, 53, 57],
          [42, 46, 50, 54, 58],
          [43, 47, 51, 55, 59]]]])

# 模拟 海思输出
b = torch.arange(60).view((1, 15, 2, 2)).view((1, 3, 5, 4))
print(b.shape)
print(b)
# output
torch.Size([1, 3, 5, 4]) # shape
# data
tensor([[[[ 0,  1,  2,  3], # 一列等于上面数组的一行,而且顺序对应的上
          [ 4,  5,  6,  7],
          [ 8,  9, 10, 11],
          [12, 13, 14, 15],
          [16, 17, 18, 19]],
# ---------------------------------- anchor 
         [[20, 21, 22, 23],
          [24, 25, 26, 27],
          [28, 29, 30, 31],
          [32, 33, 34, 35],
          [36, 37, 38, 39]],
# ---------------------------------- anchor 
         [[40, 41, 42, 43],
          [44, 45, 46, 47],
          [48, 49, 50, 51],
          [52, 53, 54, 55],
          [56, 57, 58, 59]]]])

Die simulierte Datenform entspricht nacheinander der tatsächlichen Datenform, 3 entspricht 3, 4 entspricht 400 und 5 entspricht 53. Wenn man sich die obigen Daten ansieht, entspricht eine Spalte mit simulierten HiSilicon-Daten einer Zeile mit simulierten Python-Daten, obwohl die Form unterschiedlich ist, im selben Anker, was interessant ist. Es ist ersichtlich, dass dieselben Daten in Python zeilenweise gelesen werden, in HiSilicon jedoch spaltenweise gelesen werden müssen. Schauen Sie sich die Datenspeichermethode in HiSilicon noch einmal an, öffnen Sie das HiSilicon-Dokument und suchen Sie den Speichereinführungsteil: Die
Bildbeschreibung hier einfügen
analoge Zahlenform von HiSilicon ist (1, 3, 5, 4), 3 ist die Anzahl der Kanäle, Frame0_Chn0, Frame0_Chn1 in Die obige Abbildung, 5 ist Höhe, 4 ist Breite, es kann festgestellt werden, dass sie mit der gedruckten Datenverteilung übereinstimmt, sodass Sie Daten sicher abrufen können. Das Folgende ist meine handgezeichnete Ausgabespeicherverteilungskarte, ich denke, es ist immer noch notwendig, es klar zu machen, überspringen Sie es einfach, wenn Sie zu wortreich sind.
Bildbeschreibung hier einfügen
Nehmen Sie als Beispiel eine der Yolo-Layer-Ausgaben, die Ausgabeform ist (0, 3, 53, 100). 3 ist die Anzahl der Anker, und der Speicher des Ausgabeergebnisses ist in drei Teile unterteilt: oben, Mitte und unten, wie in der Abbildung oben gezeigt. 53 ist die Ausgabe von bbox + trust + class_confidence Da es keine Permutation gibt, ist ihre Anordnung im Speicher nach oben und unten verteilt, was nicht mit unserer üblichen Gewohnheit übereinstimmt, Ergebnisse zeilenweise zu lesen. 100 ist die begradigte Länge der 10x10-Feature-Karte, d. h. es gibt 100 Spalten in jedem Ankergitter, jede Spalte stellt die Daten jedes Gitters dar, und es gibt 53 Zeilen in jeder Datenspalte, und jede Zeile stellt die Daten darin dar die aktuelle Netzleistung. In diesem Fall sollten Sie in Kombination mit meinem Code viel verstehen.

2. Verarbeitung der Netzwerkausgabe

Der Unterschied zwischen yolo v5 und yolo v3 besteht darin, dass sich die Netzwerkausgabe von der von v3 unterscheidet.Sie ​​können den Quellcode sehen:

y = x[i].sigmoid()
y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i].to(x[i].device)) * self.stride[i]  # xy
y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i]  # wh
x[:, 5:] *= x[:, 4:5]  # score = obj_conf * cls_conf

Für die Netzwerkausgabeergebnisse wird die Sigmoidabbildung zuerst einheitlich durchgeführt und dann wird xywh separat geändert, ebenso wie das Vertrauen und das Klassenvertrauen.

3. Die Klassenausgabe von yolo v5

Da yolo v5 Multi-Label-Loss-Training für Kategorien verwendet (nicht gegenseitig ausschließend und Multi-Kategorie), wird jede Kategorie ausgegeben, nachdem sie von der Sigmoid-Funktion zugeordnet wurde, sodass die Größe nicht verglichen werden muss, und gibt schließlich a zurück entsprechenden Index des Maximalwerts. Sie können sich den Quellcode ansehen:

# Detections matrix nx6 (xyxy, conf, cls)
if multi_label:
# 注意,yolo v5 不是 softmax分类 ,所以不用遍历出概率最高的那个类别
i, j = (x[:, 5:] > conf_thres).nonzero().t()  # 只做一个阈值筛选就好
x = torch.cat((box[i], x[i, j + 5, None], j[:, None].float()), 1)

Der Autor macht nur ein Schwellwertscreening und gibt den benötigten Kategorienindex zurück.Wenn Sie sagen, dass es zwei oder mehr Kategorien geben wird, kann ich dazu nur sagen, dass Sie das Modell entweder nicht gut trainiert haben oder die Datenfehler ein gut ausgebildetes Modell nicht.

4. NMS-Verarbeitung von yolo v5

Schauen wir uns zuerst den Quellcode an, die nms-Implementierung des Autors ist einfach, aber effizient:

# 作者是先拿类别索引乘以一个较大值 max_wh(4096)
c = x[:, 5:6] * (0 if agnostic else max_wh)  # classes,5 是类别对应的索引
boxes, scores = x[:, :4] + c, x[:, 4]  # boxes (offset by class), scores
i = torchvision.ops.boxes.nms(boxes, scores, iou_thres)

Der Autor hat einen Bbox-Offset für jede Kategorie. Bevor Sie nms ausführen, multiplizieren Sie zuerst den Kategorieindex mit einem größeren Wert und addieren Sie dann den Offset-Wert zum Bbox-Koordinatenwert. Was bedeutet das? Stellen Sie sich vor, jeder Kategorie einen größeren Wert hinzuzufügen, was gleichbedeutend mit der Ausführung von nms in dem Koordinatensystem ist, das dieser Kategorie für jede Kategorie zugeordnet ist.Wenn Sie nms auf diese Weise ausführen, ist es nicht erforderlich, nms-Operationen nach Kategorie wie zuvor durchzuführen, sondern direkt einfach mal rechnen. Es vermeidet, dass jene Bboxen, deren IOU > Schwelle ist, aber nicht zu derselben Klasse gehören, entfernt werden.
Ich habe die nms-Methode des Autors in meiner Demo nicht verwendet. Wenn ein Bruder nach dem Experimentieren feststellt, dass die Methode des Autors besser ist, lassen Sie es mich bitte wissen.

5. Hisilicon-Version yolo v5 Netzwerkmodifikation

Sobald yolo v5 Open-Source war, habe ich mir seine Struktur angesehen und festgestellt, dass einige Operationen HiSilicon nicht unterstützten. Daher wurden zu Beginn des Modelltrainings einige Änderungen wie folgt vorgenommen: ① Ersetzen Sie die Fokusebene durch eine
Faltung , die von HiSilicon auf dieser Op nicht verwendet wird, habe ich darüber nachgedacht (korrigiert, ich habe es selbst ausprobiert, Focus kann mit Caffe implementiert werden, HiSilicon unterstützt auch Op, die Möglichkeit des Einsatzes von Yolo v5 Focus Layer HiSilicon ), und Freunde sind willkommen zu diskutieren;
② Ersetzen Sie Leaky Relu durch Relu, HiSilicon unterstützt es Es ist Prelu, also wird es auch unterstützt, aber die Gruppe von Freunden berichtete, dass diese Operation sehr langsam ist und die Ausgabe nicht stabil ist (Ich habe damit nicht experimentiert, die Authentizität ist zweifelhaft), also habe ich es einfach ersetzt; ③ Upsampling-Schicht,
HiSilicon Die unterstützte Upsampling-Schicht ist die Unpooling-Methode, und die Upsampling-Methode in yolo v5 ist die Nearest-Neighbor-Interpolation (Nearest). Angesichts verschiedener Faktoren wird sie ersetzt durch gruppierte transponierte Faltung (Gruppierung sollte hier beachtet werden, yolo v5 Tatsächlich sind die meisten Faltungen des Netzwerks tiefe Faltung + Punkt-für-Punkt-Faltung, daher ist die transponierte Faltung auch gruppiert); ④ Der Maxpool-Ceil-Modus
des spp-Layer ist der Standardzustand „false“, und der Caffe in HiSilicon unterstützt nur den Ceil-Modus, also ändern Sie ihn in „Ceil Mode = True“. Zuerst habe ich vergessen, es einzuschalten, daher war die Ausgabe-Bbox offensichtlich zu groß (den Grund dafür kann ich nicht bestätigen.) Später habe ich das Training bewusst abgebrochen und diesen Parameter geändert und dann weiter trainiert. Später fand ich dass die bbox normal war.

3. Vorwort

In Eile geschrieben, wenn etwas falsch ist, weisen Sie bitte darauf hin, danke.

4. Entschuldigung

Ich möchte mich hier entschuldigen, aufgrund meiner Nachlässigkeit und Dummheit sind die Ausgabeergebnisse von mir und anderen Brüdern nicht normal, was einigen Brüdern viel Ärger bereitet hat.
Bildbeschreibung hier einfügen
Bildbeschreibung hier einfügen

Ähnlich wie bei obigem Problem haben mich auch einige Brüder gefragt, ob im Nachbearbeitungscode ein Fehler sei, aber ich habe ihn aufgrund meiner Unachtsamkeit nicht wiedergefunden. Mit der Korrektur eines Bruders vor kurzem fand ich diesen Fehler endlich: Der Code des gitee-Lagers wurde geändert, und es wurde von einem Internetnutzer bestätigt, dass die Ergebnisse der Modellausgabe nach der Änderung verwendet werden können!
Die Lektion für mich lautet diesmal: Wenn etwas passiert, suche die Gründe bei dir selbst. Menschen geben immer gerne anderen die Schuld für ihr Versagen und schreiben ihren Erfolg sich selbst zu .

Je suppose que tu aimes

Origine blog.csdn.net/tangshopping/article/details/110038605
conseillé
Classement