1. [Programación de Pytorch] Comprender el concepto, el método de construcción y el método de almacenamiento del tensor

1. [Programación de Pytorch] Comprender el concepto, el método de construcción y el método de almacenamiento del tensor

La columna de mi blog Serie de programación Pytorch . Para la configuración del entorno de Python, consulte "[Python Learning] Windows 10 Iniciar la instalación de Anaconda y la gestión del entorno de Python" o "[Python Learning] Comandos de terminal puros para iniciar la instalación de Anaconda y la gestión del entorno de Python" .

Autor: Chen Yirong
Entorno de código: Python3.6, Pytorch1.4.0, jupyter notebook

Referencias en esta sección

Ver la versión del entorno de código

import torch
print(torch.__file__) # 查看安装位置
print(torch.__version__) # 查看版本号
print(torch.cuda.is_available()) # 查看CUDA版本是否可用
/home/phd-chen.yirong/anaconda3/envs/py36/lib/python3.6/site-packages/torch/__init__.py
1.4.0
True

Conceptos básicos de tensores

El nombre en inglés de tensor Tensor, por ejemplo, el conocido marco de aprendizaje profundo TensorFlowes Tensor+ mermaid flowchat, que significa flujo de tensor.
Para comprender los tensores, primero debe tener una comprensión preliminar de las estructuras de datos y los algoritmos.

Estructuras de datos en Python

La estructura de datos clásica en Python es la lista. Basándose en la lista, también puede implementar estructuras de datos clásicas como colas y pilas. En pocas palabras, la estructura de datos es la forma en que almacenamos y organizamos los datos, como "primero en entrar, primero en salir" de la cola, "primero en entrar, primero en salir" de la pila, etc.
Referencia: https://docs.python.org/zh-cn/3/tutorial/datastructures.html

# 列表
list1 = [1,2,3,4,5,6,7,8]
print(list1)
print(type(list1))
## 列表末尾添加元素
list1.append(9)
print(list1)
[1, 2, 3, 4, 5, 6, 7, 8]
<class 'list'>
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Estructura de datos Ndarray en NumPy

Ndarray, su nombre completo es matriz N-dimensional, que se traduce como una matriz N-dimensional. Una matriz también es una estructura de datos que se representa como una colección de elementos del mismo tipo de datos. Por lo tanto, si estamos de acuerdo en que el tipo de datos de cada elemento del objeto de lista de Python debe ser el mismo, entonces esta lista puede considerarse como una matriz. NdarrayEs una estructura de datos tan especial. En concreto, Ndarrayel interior consta de lo siguiente:

  • Un puntero a datos (un bloque de datos en la memoria o un archivo asignado a la memoria).
  • El tipo de datos, o dtype, que describe una cuadrícula de valores de tamaño fijo en la matriz.
  • Una tupla que representa la forma de la matriz, una tupla que representa el tamaño de cada dimensión.
  • Una tupla de zancada (zancada), donde el número entero se refiere al número de bytes a "zancada" para avanzar al siguiente elemento en la dimensión actual.

Un objeto ndarray consta de una porción unidimensional contigua de la memoria de la computadora, combinada con un patrón de indexación que asigna cada elemento a una ubicación en el bloque de memoria.
Referencia: https://www.runoob.com/numpy/numpy-ndarray-object.html

Crear un Ndarrayobjeto es solo cuestión de llamar a una arrayfunción NumPy:

numpy.array(object, dtype = None, copy = True, order = None, subok = False, ndmin = 0)
import numpy as np 
list1 = [1,2,3,4,5,6,7,8]
array1 = np.array(list1)  
print(array1)
print(type(array1))
[1 2 3 4 5 6 7 8]
<class 'numpy.ndarray'>

Estructura de datos tensoriales de Ndarray a Pytorch

Aquí hay antecedentes históricos: muchos investigadores consideran a Pytorch como un sustituto de Numpy, es decir, usan PyTorch para usar el rendimiento de la GPU para los cálculos.
Aquí hay dos conceptos científicos populares: CPU y GPU

  • CPU: La unidad central de procesamiento (CPU para abreviar) es el núcleo de cómputo y control del sistema informático y la unidad de ejecución final para el procesamiento de información y la operación del programa.
  • GPU: Unidad de procesamiento de gráficos (inglés: unidad de procesamiento de gráficos, abreviatura: GPU), también conocida como núcleo de pantalla, procesador visual, chip de pantalla, es una unidad de procesamiento de gráficos especial en computadoras personales, estaciones de trabajo, consolas de juegos y algunos dispositivos móviles (como tabletas, teléfonos inteligentes, etc.) son microprocesadores que realizan operaciones relacionadas con imágenes y gráficos.

Algunas de sus diferencias: CPU: pequeña cantidad de cálculo, principio: solo 4 unidades de cálculo, puede calcular operaciones complejas, velocidad de cálculo lenta para múltiples problemas aritméticos 1+1; GPU: gran cantidad de cálculo, principio: 1000 unidades de operaciones, puede solo calcule problemas aritméticos simples 1+1, y la velocidad de cálculo para múltiples problemas aritméticos 1+1 es rápida. Por lo tanto, a menudo aquellas operaciones que requieren una gran cantidad de cálculos paralelos tienen ventajas en el uso de GPU.

Los tensores nacieron de este trasfondo de desarrollo informático.

Referencia: https://zhuanlan.zhihu.com/p/156171120?utm_source=wechat_session

Podemos entenderlo simplemente así: Tensores similar a NumPy Ndarray, y Tensorla GPU se puede usar para calcular al mismo tiempo. Por lo tanto Tensor, muchas propiedades y métodos son Ndarraysimilares a .

Métodos de construcción comunes para tensores

import torch
import numpy as np
  • torch.tensor
    torch.tensor(data, *, dtype=None, device=None, requires_grad=False, pin_memory=False) → Tensor
    puede
    dataser lista, tupla, NumPy ndarray, escalar y otros tipos de datos.
    dtypeEspecifica el tipo de datos de los elementos del tensor creado, que puede ser cualquiera de torch.dtype , comúnmente utilizados son torch.float, especifica el dispositivo asignado por el tensor, 'cpu' o 'cuda', 'cuda:0', .. especifica si el tensor registra los resultados de la operación de gradiente automática. especifica que el tensor se asigna en memoria fija. Este parámetro solo está disponible para tensores de CPU. A partir de los parámetros formales de torch.tensor, en realidad podemos encontrar algunas características de los tensores: tipo de matriz, necesidad de especificar el equipo, puede admitir el cálculo de gradiente y registrar el valor del cálculo de gradiente.torch.doubletorch.int
    device
    requires_grad
    pin_memory
tensor1 = torch.tensor([[1, 2], [3, 4]])
print(tensor1)
print(tensor1[1])
print(tensor1[0,0])
print(type(tensor1))
tensor([[1, 2],
        [3, 4]])
tensor([3, 4])
tensor(1)
<class 'torch.Tensor'>

Ver propiedades de los tensores:

print(tensor1.shape)
print(tensor1.dtype)
print(tensor1.device)
torch.Size([2, 2])
torch.int64
cpu

Especifique el tipo de datos y la información del dispositivo del tensor:

tensor2 = torch.tensor([[1, 2], [3, 4]],dtype=torch.float,device=torch.device('cuda'))
print(tensor2)
print(tensor2[1])
print(tensor2[0,0])
print(type(tensor2))
tensor([[1., 2.],
        [3., 4.]], device='cuda:0')
tensor([3., 4.], device='cuda:0')
tensor(1., device='cuda:0')
<class 'torch.Tensor'>

Tensores especiales:

# 0维张量(也叫做标量)
tensor3 = torch.tensor(2022,dtype=torch.int,device=torch.device('cuda'))
print(tensor3)
# Use torch.Tensor.item() to get a Python number from a tensor containing a single value
print(tensor3.item())
tensor(2022, device='cuda:0', dtype=torch.int32)
2022
# 空张量
tensor4 = torch.tensor([],dtype=torch.int,device=torch.device('cuda'))
print(tensor4)
tensor([], device='cuda:0', dtype=torch.int32)
  • torch.from_numpy
    crea un tensor a partir de datos de tipo NumPy.ndarray, y el tensor devuelto y el ndarray comparten la misma memoria.
    Las modificaciones al tensor se reflejarán en el ndarray y viceversa. El tensor devuelto no es redimensionable.
np_array = np.array([[1,2,3,4,5,6],[1,2,3,4,5,6]])
x_np = torch.from_numpy(np_array)
print(x_np)
tensor([[1, 2, 3, 4, 5, 6],
        [1, 2, 3, 4, 5, 6]])
  • Cree un tensor con todos los 0 o todos los 1 o inicializado aleatoriamente con referencia a las dimensiones de otro tensor
x = torch.tensor([[1,2,3],[3,2,1]],dtype=torch.float,device=torch.device("cuda"))
print(x)
# 创建全为1张量,维度等与张量x相同
x_ones = torch.ones_like(x)
print(x_ones)
# 创建全为0张量,维度等与张量x相同
x_zeros = torch.zeros_like(x)
print(x_zeros)
# 创建随机设置元素的张量,维度等与张量x相同
x_rand = torch.rand_like(x, dtype=torch.float)
print(x_rand)
tensor([[1., 2., 3.],
        [3., 2., 1.]], device='cuda:0')
tensor([[1., 1., 1.],
        [1., 1., 1.]], device='cuda:0')
tensor([[0., 0., 0.],
        [0., 0., 0.]], device='cuda:0')
tensor([[0.9553, 0.0353, 0.3714],
        [0.2786, 0.4967, 0.8638]], device='cuda:0')
  • Especifique las dimensiones del tensor, cree tensores aleatorios o tensores compuestos por constantes
shape = (2,2)
rand_tensor = torch.rand(shape)
print(rand_tensor)
ones_tensor = torch.ones(shape)
print(ones_tensor)
zeros_tensor = torch.zeros(shape)
print(zeros_tensor)
tensor([[0.7727, 0.7495],
        [0.1478, 0.7323]])
tensor([[1., 1.],
        [1., 1.]])
tensor([[0., 0.],
        [0., 0.]])

Operaciones básicas sobre tensores

1. .toOperaciones tensoriales

device = "cuda" if torch.cuda.is_available() else "cpu"
tensor5 = torch.tensor([[1, 2], [3, 4]])
tensor5 = tensor5.to(device)
print(tensor5)
tensor([[1, 2],
        [3, 4]], device='cuda:0')
tensor5 = tensor5.to("cpu")
print(tensor5)
tensor([[1, 2],
        [3, 4]])

2. Índice de elementos del tensor

tensor6 = torch.rand(3, 3)
print(tensor6)
print(f"First row: {
      
      tensor6[0]}")
print(f"First column: {
      
      tensor6[:, 0]}")
print(f"Last column: {
      
      tensor6[..., -1]}")
print(f"Last column: {
      
      tensor6[:, -1]}")
tensor6[:,1] = 1
print(tensor6)
tensor([[0.4778, 0.4452, 0.2678],
        [0.2579, 0.0155, 0.9202],
        [0.5348, 0.2705, 0.2810]])
First row: tensor([0.4778, 0.4452, 0.2678])
First column: tensor([0.4778, 0.2579, 0.5348])
Last column: tensor([0.2678, 0.9202, 0.2810])
Last column: tensor([0.2678, 0.9202, 0.2810])
tensor([[0.4778, 1.0000, 0.2678],
        [0.2579, 1.0000, 0.9202],
        [0.5348, 1.0000, 0.2810]])

3. Empalme de tensor
Esta operación se usa a menudo cuando se procesa la fusión de características, por ejemplo: empalmar las características de tres modelos y luego ingresarlos en otro modelo para lograr la fusión de características.

tensor7 = torch.tensor([[1, 2], [3, 4]])
tensor3 = torch.cat([tensor7, tensor7, tensor7], dim=1)
print(tensor3)
tensor8 = torch.cat([tensor7, tensor7, tensor7], dim=0) # 最外层进行拼接
print(tensor8)
tensor9 = torch.cat([tensor7, tensor7, tensor7], dim=-1) # 最内层进行拼接
print(tensor9)
tensor([[1, 2, 1, 2, 1, 2],
        [3, 4, 3, 4, 3, 4]])
tensor([[1, 2],
        [3, 4],
        [1, 2],
        [3, 4],
        [1, 2],
        [3, 4]])
tensor([[1, 2, 1, 2, 1, 2],
        [3, 4, 3, 4, 3, 4]])

Comprender el almacenamiento de tensores a través de la función de vista

Vale la pena señalar que cuando pytorch y numpy almacenan arreglos MxN, ambos extienden el arreglo a un almacenamiento unidimensional de acuerdo con la prioridad de fila , como un tensor bidimensional

t = torch.tensor([[1, 3, 5], [2, 4, 6]])

en la memoria es en realidad

[1, 3, 5, 2, 4, 6]

almacenado así.

  • Función view(): No cambia la forma real del tensor en la memoria.Después de usar la función view, los números del tensor suelen ser semánticamente continuos, pero no continuos en la memoria.
    La función de la función en pytorch viewes reconstruir la dimensión del tensor, que es equivalente a resizela función en numpy. Los tensores devueltos comparten los mismos datos, deben tener el mismo número de elementos, pero pueden tener diferentes tamaños. Para que viewlos tensores sean válidos, viewlas dimensiones finales del tensor deben ser compatibles con sus dimensiones originales, así como con las dimensiones.
    viewEl método solo es aplicable a los tensores que cumplen las condiciones de continuidad , y esta operación no abrirá un nuevo espacio de memoria, sino que solo generará un nuevo alias y una referencia al espacio de almacenamiento original, y el valor de retorno es una vista.
t0 = torch.randn(4, 4)
print("t0=",t0)
print(t0.size())
t1 = t0.view(16)
print("t1=",t1)
print(t1.size())
t2 = t0.view(2, 8)  # the size -1 is inferred from other dimensions
print("t2=",t2)
print(t2.size())
t0= tensor([[ 0.0713,  0.2641, -1.6546,  1.0520],
        [ 2.0892,  0.9782, -0.6922,  0.8448],
        [-1.9219, -0.0295,  0.6358, -1.1346],
        [ 0.3436,  0.2619,  0.2935, -2.4253]])
torch.Size([4, 4])
t1= tensor([ 0.0713,  0.2641, -1.6546,  1.0520,  2.0892,  0.9782, -0.6922,  0.8448,
        -1.9219, -0.0295,  0.6358, -1.1346,  0.3436,  0.2619,  0.2935, -2.4253])
torch.Size([16])
t2= tensor([[ 0.0713,  0.2641, -1.6546,  1.0520,  2.0892,  0.9782, -0.6922,  0.8448],
        [-1.9219, -0.0295,  0.6358, -1.1346,  0.3436,  0.2619,  0.2935, -2.4253]])
torch.Size([2, 8])
t3 = t.view(-1) # 等价于t.view(16)
print("t3=",t3)
print(t3.size())
t3= tensor([1, 3, 5, 2, 4, 6])
torch.Size([6])

Después de usar viewla función, los números del tensor suelen ser semánticamente continuos, pero no continuos en la memoria. En este momento, la función se puede usar para .contiguous()garantizar que el nuevo tensor sea continuo tanto en la semántica como en la memoria. .contiguous()El método primero copia la dirección de un tensor en la memoria y luego organiza la dirección de acuerdo con la semántica del tensor después del cambio de forma.

t4 = torch.randn(3, 4)
print("t4=",t4)
t5 = t4.view(4, 3).contiguous() 
print("t5=",t5)
print(t5.size())
t4= tensor([[ 1.4501,  0.0161, -0.0799,  0.8645],
        [-0.2330, -0.8001, -1.6973, -0.2469],
        [ 0.9000,  0.2703, -0.1075,  1.4058]])
t5= tensor([[ 1.4501,  0.0161, -0.0799],
        [ 0.8645, -0.2330, -0.8001],
        [-1.6973, -0.2469,  0.9000],
        [ 0.2703, -0.1075,  1.4058]])
torch.Size([4, 3])
# [[1,3,5],
#  [2,4,6]]
#
t6 = torch.tensor([[1, 3, 5], [2, 4, 6]])
t6_v = t6.view(-1)
print('t6.view(-1)=', t6_v) # [1, 3, 5, 2, 4, 6]
t6.view(-1)= tensor([1, 3, 5, 2, 4, 6])

El tensor t7 a continuación es semánticamente así

[[1, 2],
 [3, 4],
 [5, 6]]

Pero es lo mismo que t6 en memoria, es así:

[1, 3, 5, 2, 4, 6]

Si se va a satisfacer la continuidad de la memoria, debe almacenarse así:

[1, 2, 3, 4, 5, 6]
t7 = t.transpose(0, 1) # 内存上:[1, 3, 5, 2, 4, 6]
print('t7=', t7)
t7= tensor([[1, 2],
        [3, 4],
        [5, 6]])
# 不满足语义和存储上的连续一致性,无法使用view函数
print(t7.view(-1))
---------------------------------------------------------------------------

RuntimeError                              Traceback (most recent call last)

<ipython-input-61-e4b044462be1> in <module>
      1 # 不满足语义和存储上的连续一致性,无法使用view函数
----> 2 print(t7.view(-1))


RuntimeError: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.
t8 = t7.contiguous() # 内存上:[1, 2, 3, 4, 5, 6]
t8_v = t8.view(-1)
print('t8.view(-1)=', t8_v) # [1, 2, 3, 4, 5, 6]
t8.view(-1)= tensor([1, 2, 3, 4, 5, 6])

Supongo que te gusta

Origin blog.csdn.net/m0_37201243/article/details/123725982
Recomendado
Clasificación