Beim maschinellen Lernen wird häufig eine Datenerweiterung verwendet, die ein Überlernen durch Verarbeitung von Eingabedaten verhindert. In letzter Zeit wurde jedoch ein neues Datenerweiterungsverfahren auf dem Gebiet der Bilderkennung vorgeschlagen.
Beides sind Methoden zum Maskieren eines zufälligen rechteckigen Teilbereichs des Bildes, bei dem es sich um die Lehrerdaten handelt. Der Unterschied besteht darin, dass durch zufälliges Löschen die Größe und das Seitenverhältnis des Rechtecks zufällig ausgewählt werden, während der Ausschnitt eine feste Größe hat. (Cutout experimentiert jedoch auch mit einer Methode zum selektiven Maskieren eines Teils des Zielobjekts. Eine Maske mit fester Größe ist genauso effektiv. Wenn Sie also der Einfachheit halber eine Maske mit fester Größe verwenden Anspruch) Neben der Bildklassifizierung hat Random Erasing auch die Wirksamkeit bei der Objekterkennung und beim Personenabgleich bestätigt.
(Das hier verwendete Bild unterscheidet sich von den diesmal verwendeten Daten.)
Vor der Bildverarbeitung | Nach der Bildverarbeitung |
---|---|
Ich beschloss, Random Erasing auszuprobieren. Ich habe dies anstelle von Ausschnitt gewählt, weil es effektiver zu sein scheint, die Rechteckgröße zufällig zu machen.
Die durchgeführte Aufgabe ist die Klassifizierung des CIFAR-10-Datensatzes. Implementiert mit Chainer. Der Quellcode ist unten.
Nach dem Klonen des Quellcodes können Sie mit dem folgenden Befehl trainieren (wenn Sie die letzte Option "-p" beibehalten, werden die gespeicherten Daten überschrieben, daher wird empfohlen, sie bei jedem Training zu ändern).
$ python src/download.py
$ python src/dataset.py
$ python src/train.py -g 0 -m vgg_no_fc -p remove_aug --iter 300 -b 128 --lr 0.1 --lr_decay_iter 150,225
Die Hyperparameter im Zusammenhang mit dem zufälligen Löschen sind wie folgt.
Dieses Mal habe ich einen Wert in der Nähe des Papiers ausgewählt und wie folgt eingestellt.
Hyperparameter | Wert |
---|---|
p | 0.5 |
0.02 | |
0.4 | |
1/3 | |
3 |
Der tatsächlich verwendete Code lautet wie folgt.
Es wird als Methode der geerbten Klasse "chainer.datasets.TupleDataset" implementiert.
Die Teile von "# Löschstart entfernen" bis "# Löschende entfernen" sind die Prozesse, die sich auf Löschen entfernen beziehen, und der zufällige rechteckige Bereich wird mit zufälligen Werten gefüllt. (Ich denke, es ist besser, den zu füllenden Wertebereich an den zu verwendenden Datenbereich anzupassen.)
x
von _transform
ist ein Array von Eingabedaten und hat die Größe [Stapelgröße, Anzahl der Kanäle, Höhe, Breite].
def _transform(self, x):
image = np.zeros_like(x)
size = x.shape[2]
offset = np.random.randint(-4, 5, size=(2,))
mirror = np.random.randint(2)
remove = np.random.randint(2)
top, left = offset
left = max(0, left)
top = max(0, top)
right = min(size, left + size)
bottom = min(size, top + size)
if mirror > 0:
x = x[:,:,::-1]
image[:,size-bottom:size-top,size-right:size-left] = x[:,top:bottom,left:right]
# Remove erasing start
if remove > 0:
while True:
s = np.random.uniform(0.02, 0.4) * size * size
r = np.random.uniform(-np.log(3.0), np.log(3.0))
r = np.exp(r)
w = int(np.sqrt(s / r))
h = int(np.sqrt(s * r))
left = np.random.randint(0, size)
top = np.random.randint(0, size)
if left + w < size and top + h < size:
break
c = np.random.randint(-128, 128)
image[:, top:top + h, left:left + w] = c
# Remove erasing end
return image
Der Netzwerkcode wird unten angezeigt. Es kombiniert Convolutional und Max Pooling wie VGG. Eine vollständig verbundene Ebene wird jedoch nicht bereitgestellt, und die Anzahl der Parameter wird reduziert, indem stattdessen ein globales Pooling durchgeführt wird.
class BatchConv2D(chainer.Chain):
def __init__(self, ch_in, ch_out, ksize, stride=1, pad=0, activation=F.relu):
super(BatchConv2D, self).__init__(
conv=L.Convolution2D(ch_in, ch_out, ksize, stride, pad),
bn=L.BatchNormalization(ch_out),
)
self.activation=activation
def __call__(self, x):
h = self.bn(self.conv(x))
if self.activation is None:
return h
return self.activation(h)
class VGGNoFC(chainer.Chain):
def __init__(self):
super(VGGNoFC, self).__init__(
bconv1_1=BatchConv2D(3, 64, 3, stride=1, pad=1),
bconv1_2=BatchConv2D(64, 64, 3, stride=1, pad=1),
bconv2_1=BatchConv2D(64, 128, 3, stride=1, pad=1),
bconv2_2=BatchConv2D(128, 128, 3, stride=1, pad=1),
bconv3_1=BatchConv2D(128, 256, 3, stride=1, pad=1),
bconv3_2=BatchConv2D(256, 256, 3, stride=1, pad=1),
bconv3_3=BatchConv2D(256, 256, 3, stride=1, pad=1),
bconv3_4=BatchConv2D(256, 256, 3, stride=1, pad=1),
fc=L.Linear(256, 10),
)
def __call__(self, x):
h = self.bconv1_1(x)
h = self.bconv1_2(h)
h = F.dropout(F.max_pooling_2d(h, 2), 0.25)
h = self.bconv2_1(h)
h = self.bconv2_2(h)
h = F.dropout(F.max_pooling_2d(h, 2), 0.25)
h = self.bconv3_1(h)
h = self.bconv3_2(h)
h = self.bconv3_3(h)
h = self.bconv3_4(h)
h = F.dropout(F.max_pooling_2d(h, 2), 0.25)
h = F.average_pooling_2d(h, 4, 1, 0)
h = self.fc(F.dropout(h))
return h
Die Lernbedingungen sind wie folgt.
Die Genauigkeit wurde durch Verwendung der zufälligen Löschung wie folgt verbessert.
Methode | Test Error |
---|---|
Zufälliges Löschen wird nicht verwendet | 6.68 |
Verwenden Sie das zufällige Löschen | 5.67 |
Der Übergang von Trainingsfehler und Testfehler ist wie folgt. Bei Verwendung der zufälligen Löschung ist der Unterschied zwischen Trainingsfehler und Testfehler geringer, und es scheint, dass Überlernen unterdrückt wird.
Zufälliges Löschen nicht verwendet:
Zufälliges Löschen verwendet:
Es war eine einfache Methode, das Eingabebild zu maskieren, sodass ich es sofort ausprobieren konnte. Diesmal war es effektiv, aber ich denke, es ist notwendig zu überprüfen, ob es unter verschiedenen Bedingungen eine wirksame Methode ist. Wenn es effektiv ist, kann es in Zukunft zum Standard werden.
Es ist eine so einfache Methode, dass ich mich persönlich frage, ob sie in der Vergangenheit vorgeschlagen wurde.
Recommended Posts