Saltar al contenido

¿Por qué llamamos a .detach () antes de llamar a .numpy () en un tensor de Pytorch?

Nuestro equipo de redactores ha estado mucho tiempo buscando soluciones a tus interrogantes, te dejamos la resolución así que nuestro deseo es que te sea de gran ayuda.

Solución:

Creo que el punto más crucial para entender aquí es el diferencia entre un torch.tensor y np.ndarray:
Si bien ambos objetos se utilizan para almacenar matrices n-dimensionales (también conocidas como “tensores”), torch.tensors tiene una “capa” adicional, que almacena el gráfico computacional que conduce a la matriz n-dimensional asociada.

Entonces, si solo está interesado en una forma eficiente y fácil de realizar operaciones matemáticas en matrices np.ndarray o torch.tensor se puede utilizar indistintamente.

Sin embargo, torch.tensorLos s están diseñados para usarse en el contexto de la optimización del descenso de gradientes y, por lo tanto, contienen no solo un tensor con valores numéricos, sino (y lo que es más importante) el gráfico computacional que conduce a estos valores. Este gráfico computacional se usa luego (usando la regla de la cadena de derivadas) para calcular la derivada de la función de pérdida con cada una de las variables independientes utilizadas para calcular la pérdida.

Como se mencionó antes, np.ndarray El objeto no tiene esta capa adicional de “gráfico computacional” y, por lo tanto, al convertir un torch.tensor para np.ndarray usted debe explícitamente eliminar el gráfico computacional del tensor usando el detach() mando.


Gráfico computacional

Por sus comentarios, parece que este concepto es un poco vago. Intentaré ilustrarlo con un ejemplo sencillo.
Considere una función simple de dos variables (vectoriales), x y w:

x = torch.rand(4, requires_grad=True)
w = torch.rand(4, requires_grad=True)

y = x @ w  # inner-product of x and w
z = y ** 2  # square the inner product

Si solo nos interesa el valor de z, no tenemos que preocuparnos por ningún gráfico, simplemente nos movemos hacia adelante de las entradas, x y w, computar y y luego z.

Sin embargo, ¿qué pasaría si no nos preocupamos tanto por el valor de z, sino que quiero hacer la pregunta “que es w ese minimizaz para una dada x“?

Para responder a esa pregunta, necesitamos calcular el derivado de z wrt w.
¿Cómo podemos hacer eso?
Usando la regla de la cadena sabemos que dz/dw = dz/dy * dy/dw. Es decir, para calcular el gradiente de z wrt w tenemos que movernos hacia atrás de z de regreso w computando el degradado de la operación en cada paso a medida que trazamos espalda nuestros pasos desde z para w. Este “camino” que rastreamos es el grafo computacional de z y nos dice cómo calcular la derivada de z wrt las entradas que conducen a z:

z.backward()  # ask pytorch to trace back the computation of z

Ahora podemos inspeccionar el gradiente de z wrt w:

w.grad  # the resulting gradient of z w.r.t w
tensor([0.8010, 1.9746, 1.5904, 1.0408])

Tenga en cuenta que esto es exactamente igual a

2*y*x
tensor([0.8010, 1.9746, 1.5904, 1.0408], grad_fn=)

ya que dz/dy = 2*y y dy/dw = x.

Cada tensor a lo largo de la ruta almacena su “contribución” al cálculo:

z
tensor(1.4061, grad_fn=)

Y

y
tensor(1.1858, grad_fn=)

Como se puede ver, y y z almacena no solo el valor “adelantado” de o y**2 pero tambien el grafo computacional — los grad_fn que se necesita para calcular las derivadas (usando la regla de la cadena) al rastrear los gradientes de z (salida) a w (entradas).

Estas grad_fn son componentes esenciales para torch.tensors y sin ellos no se pueden calcular derivadas de funciones complicadas. Sin embargo, np.ndarrays no tienen esta capacidad en absoluto y no tienen esta información.

Consulte esta respuesta para obtener más información sobre cómo rastrear el derivado utilizando backwrd() función.


Ya que ambos np.ndarray y torch.tensor tiene una “capa” común que almacena y array de números, pytorch usa el mismo almacenamiento para ahorrar memoria:

numpy() → numpy.ndarray

Devoluciones self tensor como un ndarray NumPy. Este tensor y el ndarray devuelto compartir el mismo almacenamiento subyacente. Los cambios en el auto tensor se reflejarán en el ndarray y viceversa.

La otra dirección también funciona de la misma manera:

torch.from_numpy(ndarray) → Tensor

Crea un tensor a partir de un numpy.ndarray.
El tensor y ndarray devueltos compartir el mismo recuerdo. Las modificaciones al tensor se reflejarán en el ndarray y viceversa.

Por lo tanto, al crear un np.array de torch.tensor o viceversa, ambos objetan referencia el mismo almacenamiento subyacente en la memoria. Ya que np.ndarray no almacena / representa el gráfico computacional asociado con el array, este gráfico debe ser explícitamente eliminado usando detach() al compartir tanto numpy como antorcha, desea hacer referencia al mismo tensor.


Tenga en cuenta que si desea, por alguna razón, usar pytorch solo para operaciones matemáticas sin retropropagación, puede usar with torch.no_grad() administrador de contexto, en cuyo caso los gráficos computacionales no se crean y torch.tensorarena np.ndarrays se pueden usar indistintamente.

with torch.no_grad():
  x_t = torch.rand(3,4)
  y_np = np.ones((4, 2), dtype=np.float32)
  x_t @ torch.from_numpy(y_np)  # dot product in torch
  np.dot(x_t.numpy(), y_np)  # the same dot product in numpy

Yo pregunté, ¿Por qué rompe el gráfico para pasar a numpy? ¿Es porque alguna operación en el numpy array no se rastreará en el gráfico de autodiff?

Sí, el nuevo tensor no se conectará al antiguo tensor a través de un grad_fn, por lo que cualquier operación en el nuevo tensor no devolverá los gradientes al antiguo tensor.

Escribiendo my_tensor.detach().numpy() simplemente está diciendo: “Voy a hacer algunos cálculos sin seguimiento basados ​​en el valor de este tensor en un numpy array. “

El libro de texto Dive into Deep Learning (d2l) tiene una buena sección que describe el método detach (), aunque no habla de por qué un detach tiene sentido antes de convertirse en un numpy array.


Gracias a jodag por ayudarnos a responder esta pregunta. Como dijo, las variables son obsoletas, por lo que podemos ignorar ese comentario.

Creo que la mejor respuesta que puedo encontrar hasta ahora está en el enlace doc de jodag:

Para evitar que un tensor rastree el historial, puede llamar a .detach () para desvincularlo del historial de cómputo y evitar que se rastree el cómputo futuro.

y en los comentarios de albanD que cité en la pregunta:

Si en realidad no necesita gradientes, puede explícitamente .detach () el tensor que requiere grad para obtener un tensor con el mismo contenido que no requiere grad. Este otro tensor se puede convertir en un numpy array.

En otras palabras, el detach significa “No quiero degradados” y es imposible realizar un seguimiento de los degradados numpy operaciones (después de todo, ¡para eso están los tensores PyTorch!)

Este es un pequeño escaparate de un tensor -> numpy array conexión:

import torch
tensor = torch.rand(2)
numpy_array = tensor.numpy()
print('Before edit:')
print(tensor)
print(numpy_array)

tensor[0] = 10

print()
print('After edit:')
print('Tensor:', tensor)
print('Numpy array:', numpy_array)

Producción:

Before edit:
Tensor: tensor([0.1286, 0.4899])
Numpy array: [0.1285522  0.48987144]

After edit:
Tensor: tensor([10.0000,  0.4899])
Numpy array: [10.        0.48987144]

El valor del primer elemento es compartido por el tensor y el numpy array. Cambiarlo a 10 en el tensor lo cambió en el numpy array así como.

Comentarios y calificaciones

Eres capaz de asistir nuestra faena dejando un comentario y dejando una valoración te damos las gracias.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *