python - neuronales - Encontrar el gradiente de un filtro de convfe de Caffe con respecto a la entrada
gradientes en inteligencia artificial (2)
Necesito encontrar el gradiente con respecto a la capa de entrada para un solo filtro convolucional en una red neuronal convolucional (CNN) como una forma de visualizar los filtros .
Dada una red entrenada en la interfaz Python de Caffe , como la de este ejemplo , ¿cómo puedo encontrar el gradiente de un conv-filter con respecto a los datos en la capa de entrada?
Editar:
En función de la respuesta de cesans , agregué el código a continuación. Las dimensiones de mi capa de entrada son [8, 8, 7, 96]
. Mi primera conv-capa, conv1
, tiene 11 filtros con un tamaño de 1x5
, lo que da como resultado las dimensiones [8, 11, 7, 92]
.
net = solver.net
diffs = net.backward(diffs=[''data'', ''conv1''])
print diffs.keys() # >> [''conv1'', ''data'']
print diffs[''data''].shape # >> (8, 8, 7, 96)
print diffs[''conv1''].shape # >> (8, 11, 7, 92)
Como puede ver en la salida, las dimensiones de las matrices devueltas por net.backward()
son iguales a las dimensiones de mis capas en Caffe. Después de algunas pruebas, he encontrado que esta salida es los gradientes de la pérdida con respecto a, respectivamente, la capa de data
y la capa conv1
.
Sin embargo, mi pregunta fue cómo encontrar el gradiente de un solo filtro de conv con respecto a los datos en la capa de entrada, que es otra cosa. ¿Cómo puedo conseguir esto?
Puedes obtener los degradados en términos de cualquier capa cuando ejecutas el pase backward()
. Solo especifique la lista de capas cuando llame a la función. Para mostrar los degradados en términos de la capa de datos:
net.forward()
diffs = net.backward(diffs=[''data'', ''conv1''])`
data_point = 16
plt.imshow(diffs[''data''][data_point].squeeze())
En algunos casos, es posible que desee obligar a todas las capas a retroceder, mire el parámetro force_backward
del modelo.
https://github.com/BVLC/caffe/blob/master/src/caffe/proto/caffe.proto
Caffe net hace malabares con dos "corrientes" de números.
La primera es la "corriente" de datos: imágenes y etiquetas que se envían a través de la red. A medida que estas entradas progresan a través de la red, se convierten en vectores de alta representación y finalmente en vectores de probabilidades de clase (en tareas de clasificación).
La segunda "corriente" contiene los parámetros de las diferentes capas, los pesos de las convoluciones, los sesgos, etc. Estos números / pesos se cambian y se aprenden durante la fase de tren de la red.
A pesar del papel fundamentalmente diferente que desempeñan estas dos "secuencias", caffe, sin embargo, utiliza la misma estructura de datos, blob
, para almacenarlas y gestionarlas.
Sin embargo, para cada capa hay dos vectores de blobs diferentes uno para cada flujo.
Aquí hay un ejemplo que espero aclare:
import caffe
solver = caffe.SGDSolver( PATH_TO_SOLVER_PROTOTXT )
net = solver.net
Si ahora miras
net.blobs
Verá un diccionario que almacena un objeto "bloc de cafe" para cada capa en la red. Cada blob tiene espacio de almacenamiento para datos y gradiente
net.blobs[''data''].data.shape # >> (32, 3, 224, 224)
net.blobs[''data''].diff.shape # >> (32, 3, 224, 224)
Y para una capa convolucional:
net.blobs[''conv1/7x7_s2''].data.shape # >> (32, 64, 112, 112)
net.blobs[''conv1/7x7_s2''].diff.shape # >> (32, 64, 112, 112)
net.blobs
contiene el primer flujo de datos, su forma coincide con la de las imágenes de entrada hasta el vector de probabilidad de clase resultante.
Por otro lado, puedes ver a otro miembro de la net
net.layers
Este es un vector de caffe almacenando los parámetros de las diferentes capas.
Mirando la primera capa (capa de ''data''
):
len(net.layers[0].blobs) # >> 0
No hay parámetros para almacenar para una capa de entrada.
Por otro lado, para la primera capa convolucional
len(net.layers[1].blobs) # >> 2
La red almacena una burbuja para los pesos del filtro y otra para el sesgo constante. Aquí están
net.layers[1].blobs[0].data.shape # >> (64, 3, 7, 7)
net.layers[1].blobs[1].data.shape # >> (64,)
Como puede ver, esta capa realiza convoluciones de 7x7 en la imagen de entrada de 3 canales y tiene 64 de tales filtros.
Ahora, ¿cómo obtener los degradados? bueno, como notaste
diffs = net.backward(diffs=[''data'',''conv1/7x7_s2''])
Devuelve los gradientes de la secuencia de datos . Podemos verificar esto por
np.all( diffs[''data''] == net.blobs[''data''].diff ) # >> True
np.all( diffs[''conv1/7x7_s2''] == net.blobs[''conv1/7x7_s2''].diff ) # >> True
( TL; DR ) Desea los gradientes de los parámetros, estos se almacenan en net.layers
con los parámetros:
net.layers[1].blobs[0].diff.shape # >> (64, 3, 7, 7)
net.layers[1].blobs[1].diff.shape # >> (64,)
Para ayudarlo a mapear entre los nombres de las capas y sus índices en el vector net.layers
, puede usar net._layer_names
.
Actualización sobre el uso de degradados para visualizar respuestas de filtro:
Un gradiente normalmente se define para una función escalar . La pérdida es escalar y, por lo tanto, puede hablarse de un gradiente de peso de píxel / filtro con respecto a la pérdida escalar. Este gradiente es un número único por píxel / peso de filtro.
Si desea obtener la entrada que resulta con la activación máxima de un nodo oculto interno específico , necesita una red "auxiliar" cuya pérdida es exactamente una medida de la activación del nodo oculto específico que desea visualizar. Una vez que tenga esta red auxiliar, puede comenzar desde una entrada arbitraria y cambiar esta entrada en función de los gradientes de la pérdida auxiliar a la capa de entrada:
update = prev_in + lr * net.blobs[''data''].diff