.. _sec_ndarray:
Manipulação de Dados
====================
Para fazer qualquer coisa, precisamos de alguma forma de armazenar e
manipular dados. Geralmente, há duas coisas importantes que precisamos
fazer com os dados: (i) adquirir eles; e (ii) processá-los assim que
estiverem dentro do computador. Não há sentido em adquirir dados sem
alguma forma de armazená-los, então vamos brincar com dados sintéticos.
Para começar, apresentamos o *array* :math:`n`-dimensional, também
chamado de *tensor*.
Se você trabalhou com NumPy, o mais amplamente utilizado pacote de
computação científica em Python, então você achará esta seção familiar.
Não importa qual estrutura você usa, sua *classe de tensor* (``ndarray``
em MXNet, ``Tensor`` em PyTorch e TensorFlow) é semelhante
ao\ ``ndarray`` do NumPy com alguns recursos interessantes. Primeiro, a
GPU é bem suportada para acelerar a computação enquanto o NumPy suporta
apenas computação de CPU. Em segundo lugar, a classe tensor suporta
diferenciação automática. Essas propriedades tornam a classe tensor
adequada para aprendizado profundo. Ao longo do livro, quando dizemos
tensores, estamos nos referindo a instâncias da classe tensorial, a
menos que seja declarado de outra forma.
Iniciando
---------
Nesta seção, nosso objetivo é colocá-lo em funcionamento, equipando você
com as ferramentas básicas de matemática e computação numérica que você
desenvolverá conforme progride no livro. Não se preocupe se você lutar
para grocar alguns dos os conceitos matemáticos ou funções de
biblioteca. As seções a seguir revisitarão este material no contexto de
exemplos práticos e irá afundar. Por outro lado, se você já tem alguma
experiência e quiser se aprofundar no conteúdo matemático, basta pular
esta seção.
.. raw:: html
.. raw:: html
Para começar, importamos o ``np`` (``numpy``) e Módulos ``npx``
(``numpy_extension``) da MXNet. Aqui, o módulo ``np`` inclui funções
suportadas por NumPy, enquanto o módulo ``npx`` contém um conjunto de
extensões desenvolvido para capacitar o *Deep Learning* em um ambiente
semelhante ao NumPy. Ao usar tensores, quase sempre invocamos a função
``set_np``: isso é para compatibilidade de processamento de tensor por
outros componentes do MXNet.
.. code:: python
from mxnet import np, npx
npx.set_np()
.. raw:: html
.. raw:: html
Para começar, importamos ``torch``. Note que apesar de ser chamado
PyTorch, devemos importar ``torch`` ao invés de ``pytorch``.
.. code:: python
import torch
.. raw:: html
.. raw:: html
Importamos ``tensorflow``. Como o nome é longo, importamos abreviando
``tf``.
.. code:: python
import tensorflow as tf
.. raw:: html
.. raw:: html
Um tensor representa uma matriz (possivelmente multidimensional) de
valores numéricos. Com uma dimensão, um tensor corresponde (em
matemática) a um *vetor*. Com duas dimensões, um tensor corresponde a
uma \* matriz \*. Tensores com mais de dois eixos não possuem nomes
matemáticos.
Para começar, podemos usar ``arange`` para criar um vetor linha ``x``
contendo os primeiros 12 inteiros começando com 0, embora eles sejam
criados como *float* por padrão. Cada um dos valores em um tensor é
chamado de *elemento* do tensor. Por exemplo, existem 12 elementos no
tensor ``x``. A menos que especificado de outra forma, um novo tensor
será armazenado na memória principal e designado para computação baseada
em CPU.
.. raw:: html
.. raw:: html
.. code:: python
x = np.arange(12)
x
.. parsed-literal::
:class: output
array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11.])
.. raw:: html
.. raw:: html
.. code:: python
x = torch.arange(12)
x
.. parsed-literal::
:class: output
tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
.. raw:: html
.. raw:: html
.. code:: python
x = tf.range(12)
x
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Podemos acessar o formato do tensor (o comprimento em cada coordenada)
inspecionando sua propriedade ``shape`` .
.. raw:: html
.. raw:: html
.. code:: python
x.shape
.. parsed-literal::
:class: output
(12,)
.. raw:: html
.. raw:: html
.. code:: python
x.shape
.. parsed-literal::
:class: output
torch.Size([12])
.. raw:: html
.. raw:: html
.. code:: python
x.shape
.. parsed-literal::
:class: output
TensorShape([12])
.. raw:: html
.. raw:: html
Se quisermos apenas saber o número total de elementos em um tensor, ou
seja, o produto de todos os *shapes*, podemos inspecionar seu tamanho.
Porque estamos lidando com um vetor aqui, o único elemento de seu
``shape`` é idêntico ao seu tamanho.
.. raw:: html
.. raw:: html
.. code:: python
x.size
.. parsed-literal::
:class: output
12
.. raw:: html
.. raw:: html
.. code:: python
x.numel()
.. parsed-literal::
:class: output
12
.. raw:: html
.. raw:: html
.. code:: python
tf.size(x)
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Para mudar o *shape* de um tensor sem alterar o número de elementos ou
seus valores, podemos invocar a função ``reshape``. Por exemplo, podemos
transformar nosso tensor, ``x``, de um vetor linha com forma (12,) para
uma matriz com forma (3, 4). Este novo tensor contém exatamente os
mesmos valores, mas os vê como uma matriz organizada em 3 linhas e 4
colunas. Para reiterar, embora a forma tenha mudado, os elementos não.
Observe que o tamanho não é alterado pela remodelagem.
.. raw:: html
.. raw:: html
.. code:: python
X = x.reshape(3, 4)
X
.. parsed-literal::
:class: output
array([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.]])
.. raw:: html
.. raw:: html
.. code:: python
X = x.reshape(3, 4)
X
.. parsed-literal::
:class: output
tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
.. raw:: html
.. raw:: html
.. code:: python
X = tf.reshape(x, (3, 4))
X
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
A remodelação especificando manualmente todas as dimensões é
desnecessária. Se nossa forma de destino for uma matriz com forma
(altura, largura), então, depois de sabermos a largura, a altura é dada
implicitamente. Por que devemos realizar a divisão nós mesmos? No
exemplo acima, para obter uma matriz com 3 linhas, especificamos que
deve ter 3 linhas e 4 colunas. Felizmente, os tensores podem calcular
automaticamente uma dimensão considerando o resto. Invocamos esse
recurso colocando ``-1`` para a dimensão que gostaríamos que os tensores
inferissem automaticamente. No nosso caso, em vez de chamar
``x.reshape (3, 4)``, poderíamos ter chamado equivalentemente
``x.reshape (-1, 4)`` ou ``x.reshape (3, -1)``.
Normalmente, queremos que nossas matrizes sejam inicializadas seja com
zeros, uns, algumas outras constantes, ou números amostrados
aleatoriamente de uma distribuição específica. Podemos criar um tensor
representando um tensor com todos os elementos definido como 0 e uma
forma de (2, 3, 4) como a seguir:
.. raw:: html
.. raw:: html
.. code:: python
np.zeros((2, 3, 4))
.. parsed-literal::
:class: output
array([[[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]],
[[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]]])
.. raw:: html
.. raw:: html
.. code:: python
torch.zeros((2, 3, 4))
.. parsed-literal::
:class: output
tensor([[[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]],
[[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]]])
.. raw:: html
.. raw:: html
.. code:: python
tf.zeros((2, 3, 4))
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Da mesma forma, podemos criar tensores com cada elemento definido como 1
da seguinte maneira:
.. raw:: html
.. raw:: html
.. code:: python
np.ones((2, 3, 4))
.. parsed-literal::
:class: output
array([[[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]],
[[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]]])
.. raw:: html
.. raw:: html
.. code:: python
torch.ones((2, 3, 4))
.. parsed-literal::
:class: output
tensor([[[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]],
[[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]]])
.. raw:: html
.. raw:: html
.. code:: python
tf.ones((2, 3, 4))
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Frequentemente, queremos amostrar aleatoriamente os valores para cada
elemento em um tensor de alguma distribuição de probabilidade. Por
exemplo, quando construímos matrizes para servir como parâmetros em uma
rede neural, vamos normalmente inicializar seus valores aleatoriamente.
O fragmento a seguir cria um tensor com forma (3, 4). Cada um de seus
elementos é amostrado aleatoriamente de uma distribuição gaussiana
(normal) padrão com uma média de 0 e um desvio padrão de 1.
.. raw:: html
.. raw:: html
.. code:: python
np.random.normal(0, 1, size=(3, 4))
.. parsed-literal::
:class: output
array([[ 2.2122064 , 1.1630787 , 0.7740038 , 0.4838046 ],
[ 1.0434405 , 0.29956347, 1.1839255 , 0.15302546],
[ 1.8917114 , -1.1688148 , -1.2347414 , 1.5580711 ]])
.. raw:: html
.. raw:: html
.. code:: python
torch.randn(3, 4)
.. parsed-literal::
:class: output
tensor([[-1.0383, 2.7221, 1.6101, 0.3270],
[ 1.2290, 0.3447, -0.8467, -1.8943],
[ 0.7013, -1.5338, -0.2593, -0.6438]])
.. raw:: html
.. raw:: html
.. code:: python
tf.random.normal(shape=[3, 4])
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Podemos também especificar os valores exatos para cada elemento no
tensor desejado fornecendo uma lista Python (ou lista de listas)
contendo os valores numéricos. Aqui, a lista externa corresponde ao eixo
0 e a lista interna ao eixo 1.
.. raw:: html
.. raw:: html
.. code:: python
np.array([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
.. parsed-literal::
:class: output
array([[2., 1., 4., 3.],
[1., 2., 3., 4.],
[4., 3., 2., 1.]])
.. raw:: html
.. raw:: html
.. code:: python
torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
.. parsed-literal::
:class: output
tensor([[2, 1, 4, 3],
[1, 2, 3, 4],
[4, 3, 2, 1]])
.. raw:: html
.. raw:: html
.. code:: python
tf.constant([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
.. _operações-1:
Operações
---------
Este livro não é sobre engenharia de software. Nossos interesses não se
limitam a simplesmente leitura e gravação de dados de/para matrizes.
Queremos realizar operações matemáticas nessas matrizes. Algumas das
operações mais simples e úteis são as operações elemento a elemento.
Estes aplicam uma operação escalar padrão para cada elemento de uma
matriz. Para funções que usam dois arrays como entradas, as operações
elemento a elemento aplicam algum operador binário padrão em cada par de
elementos correspondentes das duas matrizes. Podemos criar uma função
elemento a elemento a partir de qualquer função que mapeia de um escalar
para um escalar.
Em notação matemática, denotaríamos tal um operador escalar *unário*
(tomando uma entrada) pela assinatura
:math:`f: \mathbb{R} \rightarrow \mathbb{R}`. Isso significa apenas que
a função está mapeando de qualquer número real (:math:`\mathbb{R}`) para
outro. Da mesma forma, denotamos um operador escalar *binário* (pegando
duas entradas reais e produzindo uma saída) pela assinatura
:math:`f: \mathbb{R}, \mathbb{R} \rightarrow \mathbb{R}`. Dados
quaisquer dois vetores :math:`\mathbf{u}` e :math:`\mathbf{v}` de mesmo
*shape*, e um operador binário :math:`f`, podemos produzir um vetor
:math:`\mathbf{c} = F(\mathbf{u},\mathbf{v})` definindo
:math:`c_i \gets f(u_i, v_i)` para todos :math:`i`, onde
:math:`c_i, u_i` e :math:`v_i` são os elementos :math:`i^\mathrm{th}`
dos vetores :math:`\mathbf{c}, \mathbf{u}`, e :math:`\mathbf{v}`. Aqui,
nós produzimos o valor vetorial
:math:`F: \mathbb{R}^d, \mathbb{R}^d \rightarrow \mathbb{R}^d`
*transformando* a função escalar para uma operação de vetor elemento a
elemento.
Os operadores aritméticos padrão comuns (``+``,
``-``,\ ``*``,\ ``/``\ e\ ``**``) foram todos transformados em operações
elemento a elemento para quaisquer tensores de formato idêntico de forma
arbitrária. Podemos chamar operações elemento a elemento em quaisquer
dois tensores da mesma forma. No exemplo a seguir, usamos vírgulas para
formular uma tupla de 5 elementos, onde cada elemento é o resultado de
uma operação elemento a elemento.
Operações
~~~~~~~~~
Os operadores aritméticos padrão comuns (``+``,
``-``,\ ``*``,\ ``/``\ e\ ``**``) foram todos transformados em operações
elemento a elemento.
.. raw:: html
.. raw:: html
.. code:: python
x = np.array([1, 2, 4, 8])
y = np.array([2, 2, 2, 2])
x + y, x - y, x * y, x / y, x ** y # O ** é o operador exponenciação
.. parsed-literal::
:class: output
(array([ 3., 4., 6., 10.]),
array([-1., 0., 2., 6.]),
array([ 2., 4., 8., 16.]),
array([0.5, 1. , 2. , 4. ]),
array([ 1., 4., 16., 64.]))
.. raw:: html
.. raw:: html
.. code:: python
x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2])
x + y, x - y, x * y, x / y, x ** y # O ** é o operador exponenciação
.. parsed-literal::
:class: output
(tensor([ 3., 4., 6., 10.]),
tensor([-1., 0., 2., 6.]),
tensor([ 2., 4., 8., 16.]),
tensor([0.5000, 1.0000, 2.0000, 4.0000]),
tensor([ 1., 4., 16., 64.]))
.. raw:: html
.. raw:: html
.. code:: python
x = tf.constant([1.0, 2, 4, 8])
y = tf.constant([2.0, 2, 2, 2])
x + y, x - y, x * y, x / y, x ** y # O ** é o operador exponenciação
.. parsed-literal::
:class: output
(,
,
,
,
)
.. raw:: html
.. raw:: html
Muitos mais operações podem ser aplicadas elemento a elemento, incluindo
operadores unários como exponenciação.
.. raw:: html
.. raw:: html
.. code:: python
np.exp(x)
.. parsed-literal::
:class: output
array([2.7182817e+00, 7.3890562e+00, 5.4598148e+01, 2.9809580e+03])
.. raw:: html
.. raw:: html
.. code:: python
torch.exp(x)
.. parsed-literal::
:class: output
tensor([2.7183e+00, 7.3891e+00, 5.4598e+01, 2.9810e+03])
.. raw:: html
.. raw:: html
.. code:: python
tf.exp(x)
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Além de cálculos elemento a elemento, também podemos realizar operações
de álgebra linear, incluindo produtos escalar de vetor e multiplicação
de matrizes. Explicaremos as partes cruciais da álgebra linear (sem
nenhum conhecimento prévio assumido) em :numref:`sec_linear-algebra`.
Também podemos *concatenar* vários tensores juntos, empilhando-os ponta
a ponta para formar um tensor maior. Só precisamos fornecer uma lista de
tensores e informar ao sistema ao longo de qual eixo concatenar. O
exemplo abaixo mostra o que acontece quando concatenamos duas matrizes
ao longo das linhas (eixo 0, o primeiro elemento da forma) vs. colunas
(eixo 1, o segundo elemento da forma). Podemos ver que o comprimento do
eixo 0 do primeiro tensor de saída (:math:`6`) é a soma dos comprimentos
do eixo 0 dos dois tensores de entrada (:math:`3 + 3`); enquanto o
comprimento do eixo 1 do segundo tensor de saída (:math:`8`) é a soma
dos comprimentos do eixo 1 dos dois tensores de entrada (:math:`4 + 4`).
.. raw:: html
.. raw:: html
.. code:: python
X = np.arange(12).reshape(3, 4)
Y = np.array([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
np.concatenate([X, Y], axis=0), np.concatenate([X, Y], axis=1)
.. parsed-literal::
:class: output
(array([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[ 2., 1., 4., 3.],
[ 1., 2., 3., 4.],
[ 4., 3., 2., 1.]]),
array([[ 0., 1., 2., 3., 2., 1., 4., 3.],
[ 4., 5., 6., 7., 1., 2., 3., 4.],
[ 8., 9., 10., 11., 4., 3., 2., 1.]]))
.. raw:: html
.. raw:: html
.. code:: python
X = torch.arange(12, dtype=torch.float32).reshape((3,4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
torch.cat((X, Y), dim=0), torch.cat((X, Y), dim=1)
.. parsed-literal::
:class: output
(tensor([[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[ 2., 1., 4., 3.],
[ 1., 2., 3., 4.],
[ 4., 3., 2., 1.]]),
tensor([[ 0., 1., 2., 3., 2., 1., 4., 3.],
[ 4., 5., 6., 7., 1., 2., 3., 4.],
[ 8., 9., 10., 11., 4., 3., 2., 1.]]))
.. raw:: html
.. raw:: html
.. code:: python
X = tf.reshape(tf.range(12, dtype=tf.float32), (3, 4))
Y = tf.constant([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
tf.concat([X, Y], axis=0), tf.concat([X, Y], axis=1)
.. parsed-literal::
:class: output
(,
)
.. raw:: html
.. raw:: html
Às vezes, queremos construir um tensor binário por meio de *declarações
lógicas*. Tome ``X == Y`` como exemplo. Para cada posição, se ``X``
e\ ``Y`` forem iguais nessa posição, a entrada correspondente no novo
tensor assume o valor 1, o que significa que a declaração lógica
``X == Y`` é verdadeira nessa posição; caso contrário, essa posição
assume 0.
.. raw:: html
.. raw:: html
.. code:: python
X == Y
.. parsed-literal::
:class: output
array([[False, True, False, True],
[False, False, False, False],
[False, False, False, False]])
.. raw:: html
.. raw:: html
.. code:: python
X == Y
.. parsed-literal::
:class: output
tensor([[False, True, False, True],
[False, False, False, False],
[False, False, False, False]])
.. raw:: html
.. raw:: html
.. code:: python
X == Y
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Somando todos os elementos no tensor resulta em um tensor com apenas um
elemento.
.. raw:: html
.. raw:: html
.. code:: python
X.sum()
.. parsed-literal::
:class: output
array(66.)
.. raw:: html
.. raw:: html
.. code:: python
X.sum()
.. parsed-literal::
:class: output
tensor(66.)
.. raw:: html
.. raw:: html
.. code:: python
tf.reduce_sum(X)
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
.. _subsec_broadcasting:
Mecanismo de *Broadcasting*
---------------------------
Na seção acima, vimos como realizar operações elemento a elemento em
dois tensores da mesma forma. Sob certas condições, mesmo quando as
formas são diferentes, ainda podemos realizar operações elementar
invocando o mecanismo de *Broadcasting*. Esse mecanismo funciona da
seguinte maneira: Primeiro, expanda um ou ambos os arrays copiando
elementos de forma adequada de modo que após esta transformação, os dois
tensores têm a mesma forma. Em segundo lugar, execute as operações
elemento a elemento nas matrizes resultantes.
Na maioria dos casos, nós transmitimos ao longo de um eixo onde uma
matriz inicialmente tem apenas o comprimento 1, como no exemplo a
seguir:
.. raw:: html
.. raw:: html
.. code:: python
a = np.arange(3).reshape(3, 1)
b = np.arange(2).reshape(1, 2)
a, b
.. parsed-literal::
:class: output
(array([[0.],
[1.],
[2.]]),
array([[0., 1.]]))
.. raw:: html
.. raw:: html
.. code:: python
a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
a, b
.. parsed-literal::
:class: output
(tensor([[0],
[1],
[2]]),
tensor([[0, 1]]))
.. raw:: html
.. raw:: html
.. code:: python
a = tf.reshape(tf.range(3), (3, 1))
b = tf.reshape(tf.range(2), (1, 2))
a, b
.. parsed-literal::
:class: output
(,
)
.. raw:: html
.. raw:: html
Uma vez que ``a`` e\ ``b`` são matrizes :math:`3\times1` e
:math:`1\times2` respectivamente, suas formas não correspondem se
quisermos adicioná-los. Nós transmitimos as entradas de ambas as
matrizes em uma matriz :math:`3\times2` maior da seguinte maneira: para
a matriz ``a`` ele replica as colunas e para a matriz ``b`` ele replica
as linhas antes de adicionar ambos os elementos.
.. raw:: html
.. raw:: html
.. code:: python
a + b
.. parsed-literal::
:class: output
array([[0., 1.],
[1., 2.],
[2., 3.]])
.. raw:: html
.. raw:: html
.. code:: python
a + b
.. parsed-literal::
:class: output
tensor([[0, 1],
[1, 2],
[2, 3]])
.. raw:: html
.. raw:: html
.. code:: python
a + b
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Indexação e Fatiamento
----------------------
Assim como em qualquer outro array Python, os elementos em um tensor
podem ser acessados por índice. Como em qualquer matriz Python, o
primeiro elemento tem índice 0 e os intervalos são especificados para
incluir o primeiro, mas *antes* do último elemento. Como nas listas
padrão do Python, podemos acessar os elementos de acordo com sua posição
relativa ao final da lista usando índices negativos.
Assim, ``[-1]`` seleciona o último elemento e ``[1: 3]`` seleciona o
segundo e o terceiro elementos da seguinte forma:
.. raw:: html
.. raw:: html
.. code:: python
X[-1], X[1:3]
.. parsed-literal::
:class: output
(array([ 8., 9., 10., 11.]),
array([[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.]]))
Além da leitura, também podemos escrever elementos de uma matriz
especificando índices.
.. code:: python
X[1, 2] = 9
X
.. parsed-literal::
:class: output
array([[ 0., 1., 2., 3.],
[ 4., 5., 9., 7.],
[ 8., 9., 10., 11.]])
.. raw:: html
.. raw:: html
.. code:: python
X[-1], X[1:3]
.. parsed-literal::
:class: output
(tensor([ 8., 9., 10., 11.]),
tensor([[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.]]))
Além da leitura, também podemos escrever elementos de uma matriz
especificando índices.
.. code:: python
X[1, 2] = 9
X
.. parsed-literal::
:class: output
tensor([[ 0., 1., 2., 3.],
[ 4., 5., 9., 7.],
[ 8., 9., 10., 11.]])
.. raw:: html
.. raw:: html
.. code:: python
X[-1], X[1:3]
.. parsed-literal::
:class: output
(,
)
``Tensors`` in TensorFlow are immutable, and cannot be assigned to.
``Variables`` in TensorFlow are mutable containers of state that support
assignments. Keep in mind that gradients in TensorFlow do not flow
backwards through ``Variable`` assignments. Os ``Tensors`` no TensorFlow
são imutáveis e não podem ser atribuídos a eles. ``Variables`` no
TensorFlow são contêineres mutáveis de estado que suportam atribuições.
Lembre-se de que gradientes no TensorFlow não fluem para trás por meio
de atribuições ``Variable``.
Beyond assigning a value to the entire ``Variable``, we can write
elements of a ``Variable`` by specifying indices. Além de atribuir um
valor a toda a ``Variable``, podemos escrever elementos de um
``Variable`` especificando índices.
.. code:: python
X_var = tf.Variable(X)
X_var[1, 2].assign(9)
X_var
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Se quisermos para atribuir a vários elementos o mesmo valor,
simplesmente indexamos todos eles e, em seguida, atribuímos o valor a
eles. Por exemplo, ``[0: 2,:]`` acessa a primeira e a segunda linhas,
onde ``:`` leva todos os elementos ao longo do eixo 1 (coluna). Enquanto
discutimos a indexação de matrizes, isso obviamente também funciona para
vetores e para tensores de mais de 2 dimensões.
.. raw:: html
.. raw:: html
.. code:: python
X[0:2, :] = 12
X
.. parsed-literal::
:class: output
array([[12., 12., 12., 12.],
[12., 12., 12., 12.],
[ 8., 9., 10., 11.]])
.. raw:: html
.. raw:: html
.. code:: python
X[0:2, :] = 12
X
.. parsed-literal::
:class: output
tensor([[12., 12., 12., 12.],
[12., 12., 12., 12.],
[ 8., 9., 10., 11.]])
.. raw:: html
.. raw:: html
.. code:: python
X_var = tf.Variable(X)
X_var[0:2, :].assign(tf.ones(X_var[0:2,:].shape, dtype = tf.float32) * 12)
X_var
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Economizando memória
--------------------
As operações em execução podem fazer com que uma nova memória seja
alocado aos resultados do host. Por exemplo, se escrevermos
``Y = X + Y``, vamos desreferenciar o tensor que ``Y`` costumava apontar
para e, em vez disso, aponte ``Y`` para a memória recém-alocada. No
exemplo a seguir, demonstramos isso com a função ``id ()`` do Python,
que nos dá o endereço exato do objeto referenciado na memória. Depois de
executar ``Y = Y + X``, descobriremos que\ ``id (Y)``\ aponta para um
local diferente. Isso ocorre porque o Python primeiro avalia ``Y + X``,
alocar nova memória para o resultado e, em seguida, torna ``Y`` aponte
para este novo local na memória.
.. raw:: html
.. raw:: html
.. code:: python
before = id(Y)
Y = Y + X
id(Y) == before
.. parsed-literal::
:class: output
False
.. raw:: html
.. raw:: html
.. code:: python
before = id(Y)
Y = Y + X
id(Y) == before
.. parsed-literal::
:class: output
False
.. raw:: html
.. raw:: html
.. code:: python
before = id(Y)
Y = Y + X
id(Y) == before
.. parsed-literal::
:class: output
False
.. raw:: html
.. raw:: html
Isso pode ser indesejável por dois motivos. Em primeiro lugar, não
queremos alocar memória desnecessariamente o tempo todo. No aprendizado
de máquina, podemos ter centenas de megabytes de parâmetros e atualizar
todos eles várias vezes por segundo. Normalmente, queremos realizar
essas atualizações no local. Em segundo lugar, podemos apontar os mesmos
parâmetros de várias variáveis. Se não atualizarmos no local, outras
referências ainda apontarão para a localização da memória antiga,
tornando possível para partes do nosso código para referenciar
inadvertidamente parâmetros obsoletos.
.. raw:: html
.. raw:: html
Felizmente, executar operações no local é fácil. Podemos atribuir o
resultado de uma operação para uma matriz previamente alocada com
notação de fatia, por exemplo, ``Y [:] = ``. Para ilustrar
este conceito, primeiro criamos uma nova matriz ``Z`` com a mesma forma
de outro ``Y``, usando ``zeros_like`` para alocar um bloco de :math:`0`
entradas. : end_tab:
.. code:: python
Z = np.zeros_like(Y)
print('id(Z):', id(Z))
Z[:] = X + Y
print('id(Z):', id(Z))
.. parsed-literal::
:class: output
id(Z): 140128414209472
id(Z): 140128414209472
Se o valor de ``X`` não for reutilizado em cálculos subsequentes, também
podemos usar ``X [:] = X + Y`` ou\ ``X + = Y`` para reduzir a sobrecarga
de memória da operação.
.. code:: python
before = id(X)
X += Y
id(X) == before
.. parsed-literal::
:class: output
True
.. raw:: html
.. raw:: html
Felizmente, executar operações no local é fácil. Podemos atribuir o
resultado de uma operação para uma matriz previamente alocada com
notação de fatia, por exemplo, ``Y [:] = ``. Para ilustrar
este conceito, primeiro criamos uma nova matriz ``Z`` com a mesma forma
de outro ``Y``, usando ``zeros_like`` para alocar um bloco de :math:`0`
entradas. : end_tab:
.. code:: python
Z = torch.zeros_like(Y)
print('id(Z):', id(Z))
Z[:] = X + Y
print('id(Z):', id(Z))
.. parsed-literal::
:class: output
id(Z): 140194801216832
id(Z): 140194801216832
Se o valor de ``X`` não for reutilizado em cálculos subsequentes, também
podemos usar ``X [:] = X + Y`` ou\ ``X + = Y`` para reduzir a sobrecarga
de memória da operação.
.. code:: python
before = id(X)
X += Y
id(X) == before
.. parsed-literal::
:class: output
True
.. raw:: html
.. raw:: html
``Variables`` são contêineres mutáveis de estado no TensorFlow. Eles
providenciam uma maneira de armazenar os parâmetros do seu modelo.
Podemos atribuir o resultado de uma operação para uma ``Variable``
com\ ``assign``. Para ilustrar este conceito, criamos uma
:literal:`Variable`` Z` com a mesma forma de outro tensor ``Y``, usando
``zeros_like`` para alocar um bloco de :math:`0` entradas.
.. code:: python
Z = tf.Variable(tf.zeros_like(Y))
print('id(Z):', id(Z))
Z.assign(X + Y)
print('id(Z):', id(Z))
.. parsed-literal::
:class: output
id(Z): 140017345889040
id(Z): 140017345889040
Mesmo depois de armazenar o estado persistentemente em uma ``Variável``,
você pode querer reduzir ainda mais o uso de memória, evitando o excesso
de alocações para tensores que não são os parâmetros do seu modelo.
Porque os ``Tensors``\ do TensorFlow são imutáveis e gradientes não
fluem através de atribuições de ``Variable``, o TensorFlow não fornece
uma maneira explícita de executar uma operação individual no local.
No entanto, o TensorFlow fornece o decorador ``tf.function`` para
encerrar a computação dentro de um gráfico do TensorFlow que é compilado
e otimizado antes da execução. Isso permite que o TensorFlow remova
valores não utilizados e reutilize alocações anteriores que não são mais
necessárias. Isso minimiza a sobrecarga de memória de cálculos do
TensorFlow.
.. code:: python
@tf.function
def computation(X, Y):
Z = tf.zeros_like(Y) # Este valor não utilizado será esvaziado
A = X + Y # Alocações serão reutilizadas quando não mais necessárias
B = A + Y
C = B + Y
return C + Y
computation(X, Y)
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
Conversão para outros objetos Python
------------------------------------
Converter para um tensor NumPy, ou vice-versa, é fácil. O resultado
convertido não compartilha memória. Este pequeno inconveniente é muito
importante: quando você executa operações na CPU ou GPUs, você não quer
interromper a computação, esperando para ver se o pacote NumPy do Python
deseja fazer outra coisa com o mesmo pedaço de memória.
.. raw:: html
.. raw:: html
.. code:: python
A = X.asnumpy()
B = np.array(A)
type(A), type(B)
.. parsed-literal::
:class: output
(numpy.ndarray, mxnet.numpy.ndarray)
.. raw:: html
.. raw:: html
.. code:: python
A = X.numpy()
B = torch.tensor(A)
type(A), type(B)
.. parsed-literal::
:class: output
(numpy.ndarray, torch.Tensor)
.. raw:: html
.. raw:: html
.. code:: python
A = X.numpy()
B = tf.constant(A)
type(A), type(B)
.. parsed-literal::
:class: output
(numpy.ndarray, tensorflow.python.framework.ops.EagerTensor)
.. raw:: html
.. raw:: html
Para converter um tensor de tamanho 1 em um escalar Python, podemos
invocar a função ``item`` ou as funções integradas do Python.
.. raw:: html
.. raw:: html
.. code:: python
a = np.array([3.5])
a, a.item(), float(a), int(a)
.. parsed-literal::
:class: output
(array([3.5]), 3.5, 3.5, 3)
.. raw:: html
.. raw:: html
.. code:: python
a = torch.tensor([3.5])
a, a.item(), float(a), int(a)
.. parsed-literal::
:class: output
(tensor([3.5000]), 3.5, 3.5, 3)
.. raw:: html
.. raw:: html
.. code:: python
a = tf.constant([3.5]).numpy()
a, a.item(), float(a), int(a)
.. parsed-literal::
:class: output
(array([3.5], dtype=float32), 3.5, 3.5, 3)
.. raw:: html
.. raw:: html
Sumário
-------
- A principal interface para armazenar e manipular dados para *Deep
Learning* é o tensor (array :math:`n` -dimensional). Ele fornece uma
variedade de funcionalidades, incluindo operações matemáticas
básicas, transmissão, indexação, divisão, economia de memória e
conversão para outros objetos Python.
Exercícios
----------
1. Execute o código nesta seção. Altere a declaração condicional
``X == Y`` nesta seção para\ ``X < Y`` ou ``X > Y``, e então veja que
tipo de tensor você pode obter.
2. Substitua os dois tensores que operam por elemento no mecanismo de
transmissão por outras formas, por exemplo, tensores tridimensionais.
O resultado é o mesmo que o esperado?
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
.. raw:: html