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.tensor
Los 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.ndarray
s 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.tensor
arena np.ndarray
s 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.