Afin de vraiment comprendre les couches convolutives, j'ai réimplémenté la méthode forward d'une seule couche keras Conv2D dans numpy de base. Les sorties des deux coutures sont presque identiques, mais il y a quelques différences mineures.
Obtention de la sortie keras:
Mine: 2.6608338356018066 Keras: 2.660834312438965 Mine: 1.7892705202102661 Keras: 1.7892701625823975 Mine: 0.007190803997218609 Keras: 0.007190565578639507 Mine: 4.970898151397705 Keras: 4.970897197723389
Ma sortie:
def relu(x): return np.maximum(0, x) def forward(inp, filter_weights, filter_biases): result = np.zeros((1, 64, 64, 32)) inp_with_padding = np.zeros((1, 66, 66, 1)) inp_with_padding[0, 1:65, 1:65, :] = inp for filter_num in range(32): single_filter_weights = filter_weights[:, :, 0, filter_num] for i in range(64): for j in range(64): prod = single_filter_weights * inp_with_padding[0, i:i+3, j:j+3, 0] filter_sum = np.sum(prod) + filter_biases[filter_num] result[0, i, j, filter_num] = relu(filter_sum) return result my_output = forward(test_x, filter_weights, biases_weights)
3 Réponses :
Compte tenu de l'importance des différences, je dirais qu'il s'agit d'erreurs d'arrondi.
Je recommande d'utiliser np.isclose (ou math.isclose ) pour vérifier si les flottants sont "égaux". < / p>
Oui tres vrai! Cependant, je suis très intéressé à comprendre exactement pourquoi cela se produit, ce qui se passe à l'intérieur de keras et à faire en sorte que les sorties de mes fonctions correspondent parfaitement aux sorties de keras.
La première chose à faire est de vérifier si vous utilisez padding = 'same'
. Vous semblez utiliser le même remplissage dans votre implémentation.
Si vous utilisez d'autres types de remplissage, y compris la valeur par défaut qui est padding = 'valid'
, il y aura une différence.
Une autre possibilité est que vous accumuliez des erreurs à cause de la triple boucle de petites sommes.
Vous pouvez le faire tout de suite et voir si cela devient différent. Comparez cette implémentation avec la vôtre, par exemple:
def forward3(inp, filter_weights, filter_biases): inShape = inp.shape #(batch, imgX, imgY, ins) wShape = filter_weights.shape #(wx, wy, ins, out) bShape = filter_biases.shape #(out,) ins = inShape[-1] out = wShape[-1] wx = wShape[0] wy = wShape[1] imgX = inShape[1] imgY = inShape[2] assert imgX >= wx assert imgY >= wy assert inShape[-1] == wShape[-2] assert bShape[-1] == wShape[-1] #you may need to invert this padding, exchange L with R loseX = wx - 1 padXL = loseX // 2 padXR = padXL + (1 if loseX % 2 > 0 else 0) loseY = wy - 1 padYL = loseY // 2 padYR = padYL + (1 if loseY % 2 > 0 else 0) padded_input = np.pad(inp, ((0,0), (padXL,padXR), (padYL,padYR), (0,0))) #(batch, paddedX, paddedY, in) stacked_input = np.stack([padded_input[:, i:imgX + i] for i in range(wx)], axis=1) #(batch, wx, imgX, imgY, in) stacked_input = np.stack([stacked_input[:,:,:,i:imgY + i] for i in range(wy)], axis=2) #(batch, wx, wy, imgX, imgY, in) stacked_input = stacked_input.reshape((-1, wx, wy, imgX, imgY, ins, 1)) w = filter_weights.reshape(( 1, wx, wy, 1, 1, ins, out)) b = filter_biases.reshape(( 1, 1, 1, out)) result = stacked_input * w result = result.sum(axis=(1,2,-2)) result += b result = relu(result) return result
Une troisième possibilité est de vérifier si vous utilisez le GPU et de tout passer au CPU pour le test. Certains algorithmes pour GPU sont même non déterministes.
Pour toute taille de noyau:
def forward2(inp, filter_weights, filter_biases): #inp: (batch, 64, 64, in) #w: (3, 3, in, out) #b: (out,) padded_input = np.pad(inp, ((0,0), (1,1), (1,1), (0,0))) #(batch, 66, 66, in) stacked_input = np.stack([ padded_input[:, :-2], padded_input[:, 1:-1], padded_input[:, 2: ]], axis=1) #(batch, 3, 64, 64, in) stacked_input = np.stack([ stacked_input[:, :, :, :-2], stacked_input[:, :, :, 1:-1], stacked_input[:, :, :, 2: ]], axis=2) #(batch, 3, 3, 64, 64, in) stacked_input = stacked_input.reshape((-1, 3, 3, 64, 64, 1, 1)) w = filter_weights.reshape(( 1, 3, 3, 1, 1, 1, 32)) b = filter_biases.reshape (( 1, 1, 1, 32)) result = stacked_input * w #(-1, 3, 3, 64, 64, 1, 32) result = result.sum(axis=(1,2,-2)) #(-1, 64, 64, 32) result += b result = relu(result) return result
Merci d'avoir répondu! Le rembourrage est en effet "même", donc tout va bien là-bas. Pour être honnête, je ne comprends pas vraiment votre code. Quelle est l'entrée empilée? Pouvons-nous exprimer une convolution comme une simple multiplication sous une forme aussi simple? En outre, cette partie du code est collée deux fois différemment.
Bon conseil sur le GPU, mais les deux codes s'exécutent sur le CPU :)
@EliteKaffee La deuxième pâte doit utiliser stacked_input
désolé. Fixé. Regardez les formes après chaque pile pour voir ce qu'elles font.
J'ai corrigé mes codes, les erreurs sont presque les mêmes que les vôtres. Il y a cependant un gros gain de vitesse avec ma mise en œuvre. Expliquant mieux, les entrées empilées répliquent les images avec glissement, créant une matrice kernel_size pour multiplier comme si le noyau glissait.
Les opérations en virgule flottante ne sont pas commutables. Voici un exemple:
In [19]: 1.2 - 1.0 - 0.2 Out[19]: -5.551115123125783e-17 In [21]: 1.2 - 0.2 - 1.0 Out[21]: 0.0
Donc, si vous voulez des résultats complètement identiques, vous n'avez pas seulement besoin de faire les mêmes calculs analytiquement. Mais vous devez également les faire exactement dans le même ordre, avec les mêmes types de données et la même implémentation d'arrondi.
Pour déboguer cela. Commencez par le code Keras et changez-le ligne par ligne en fonction de votre code, jusqu'à ce que vous voyiez une différence.
Utilisez-vous
padding = 'same'
dans Keras?Oui je le suis, exactement!