blob: fd5213da7785fba1ee535e2e55ffad7396968cf1 [file] [log] [blame] [view]
<!--- Licensed to the Apache Software Foundation (ASF) under one -->
<!--- or more contributor license agreements. See the NOTICE file -->
<!--- distributed with this work for additional information -->
<!--- regarding copyright ownership. The ASF licenses this file -->
<!--- to you under the Apache License, Version 2.0 (the -->
<!--- "License"); you may not use this file except in compliance -->
<!--- with the License. You may obtain a copy of the License at -->
<!--- http://www.apache.org/licenses/LICENSE-2.0 -->
<!--- Unless required by applicable law or agreed to in writing, -->
<!--- software distributed under the License is distributed on an -->
<!--- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -->
<!--- KIND, either express or implied. See the License for the -->
<!--- specific language governing permissions and limitations -->
<!--- under the License. -->
# NDArray Operations
## Overview
This guide will introduce you to MXNet's array operations.
This content was extracted and simplified from the gluon tutorials in
[Dive Into Deep Learning](https://d2l.ai/).
## Prerequisites
* [MXNet installed in a Python environment](https://mxnet.apache.org/get_started).
* Python 2.7.x or Python 3.x
## Operations
NDArray supports a large number of standard mathematical operations.
Such as element-wise addition:
<!-- keeping it
easy -->
```{.python .input}
import mxnet as mx
from mxnet import nd
```
```{.python .input}
x = nd.ones((3, 4))
y = nd.random_normal(0, 1, shape=(3, 4))
print('x=', x)
print('y=', y)
x = x + y
print('x = x + y, x=', x)
```
Multiplication:
```{.python .input}
x = nd.array([1, 2, 3])
y = nd.array([2, 2, 2])
x * y
```
And exponentiation:
<!-- with these next ones we'll just have to take your word
for it... -->
```{.python .input}
nd.exp(x)
```
We can also grab a matrix's transpose to compute a proper matrix-matrix product.
<!-- because we need to do that before we have coffee every day... and you know
how those dirty, improper matrixeses can be... -->
```{.python .input}
nd.dot(x, y.T)
```
## In-place operations
In the previous
example, every time we ran an operation, we allocated new memory to host its
results. For example, if we write `y = x + y`, we will dereference the matrix
that `y` used to point to and instead point it at the newly allocated memory. We
can show this using Python's `id()` function, which tells us precisely which
object a variable refers to.
<!-- dereference is something C++ people would
know but everyone else... not so much. What's the point? ;) get it? Put it in
more context as to why you care about this and why this is in front of so much
other material. Seems like an optimization topic best suited for later...
###edit### we just talked about this, so I have better context. Now I
understand, but your new reader will not. This should be covered in much more
detail, and quite possibily in its own notebook since I think it will help to
show some gotchas like you mentioned verbally. I am still leaning toward
delaying the introduction of this topic....-->
```{.python .input}
print('y=', y)
print('id(y):', id(y))
y = y + x
print('after y=y+x, y=', y)
print('id(y):', id(y))
```
We can assign the result to a previously allocated array with slice notation,
e.g., `result[:] = ...`.
```{.python .input}
print('x=', x)
z = nd.zeros_like(x)
print('z is zeros_like x, z=', z)
print('id(z):', id(z))
print('y=', y)
z[:] = x + y
print('z[:] = x + y, z=', z)
print('id(z) is the same as before:', id(z))
```
However, `x+y` here will still allocate a temporary buffer to store the result
before copying it to z. To make better use of memory, we can perform operations
in place, avoiding temporary buffers. To do this we specify the `out` keyword
argument every operator supports:
```{.python .input}
print('x=', x, 'is in id(x):', id(x))
print('y=', y, 'is in id(y):', id(y))
print('z=', z, 'is in id(z):', id(z))
nd.elemwise_add(x, y, out=z)
print('after nd.elemwise_add(x, y, out=z), x=', x, 'is in id(x):', id(x))
print('after nd.elemwise_add(x, y, out=z), y=', y, 'is in id(y):', id(y))
print('after nd.elemwise_add(x, y, out=z), z=', z, 'is in id(z):', id(z))
```
If we're not planning to re-use ``x``, then we can assign the result to ``x``
itself. There are two ways to do this in MXNet.
1. By using slice notation x[:]
= x op y
2. By using the op-equals operators like `+=`
```{.python .input}
print('x=', x, 'is in id(x):', id(x))
x += y
print('x=', x, 'is in id(x):', id(x))
```
## Slicing
MXNet NDArrays support slicing in all the ridiculous ways you might
imagine accessing your data. For a quick review:
* items start through end-1: a[start:end]
* items start through the rest of the
array: a[start:]
* items from the beginning through end-1: a[:end]
* a copy of
the whole array: a[:]
Here's an example of reading the second and third rows from `x`.
```{.python .input}
x = nd.array([1, 2, 3])
print('1D complete array, x=', x)
s = x[1:3]
print('slicing the 2nd and 3rd elements, s=', s)
x = nd.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
print('multi-D complete array, x=', x)
s = x[1:3]
print('slicing the 2nd and 3rd elements, s=', s)
```
Now let's try writing to a specific element.
```{.python .input}
print('original x, x=', x)
x[2] = 9.0
print('replaced entire row with x[2] = 9.0, x=', x)
x[0,2] = 9.0
print('replaced specific element with x[0,2] = 9.0, x=', x)
x[1:2,1:3] = 5.0
print('replaced range of elements with x[1:2,1:3] = 5.0, x=', x)
```
Multi-dimensional slicing is also supported.
```{.python .input}
x = nd.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
print('original x, x=', x)
s = x[1:2,1:3]
print('plucking specific elements with x[1:2,1:3]', s)
s = x[:,:1]
print('first column with x[:,:1]', s)
s = x[:1,:]
print('first row with x[:1,:]', s)
s = x[:,3:]
print('last column with x[:,3:]', s)
s = x[2:,:]
print('last row with x[2:,:]', s)
```
## Broadcasting
You might wonder, what happens if you add a vector `y` to a
matrix `X`? These operations, where we compose a low dimensional array `y` with
a high-dimensional array `X` invoke a functionality called broadcasting. First
we'll introduce `.arange` which is useful for filling out an array with evenly
spaced data. Then we can take the low-dimensional array and duplicate it along
any axis with dimension $1$ to match the shape of the high dimensional array.
Consider the following example.
Comment (visible to demonstrate with font):
dimension one(1)? Or L(elle) or l(lil elle) or I(eye) or... ? We don't even use
the notation later, so did it need to be introduced here?
<!--Also, if you use
a shape like (3,3) you lose some of the impact and miss some errors if people
play with the values. Better to have a distinct shape so that it is more obvious
what is happening and what can break.-->
```{.python .input}
x = nd.ones(shape=(3,6))
print('x = ', x)
y = nd.arange(6)
print('y = ', y)
print('x + y = ', x + y)
```
While `y` is initially of shape $6$,
MXNet infers its shape to be (1,6),
and then broadcasts along the rows to form a (3,6) matrix).
You might wonder, why did MXNet choose to interpret `y` as a (1,6) matrix and not (6,1).
That's because broadcasting prefers to duplicate along the left most axis.
We can alter this behavior by explicitly giving `y` a $2$D shape using `.reshape`.
You can also chain `.arange` and `.reshape` to do this in one step.
```{.python .input}
y = y.reshape((3,1))
print('y = ', y)
print('x + y = ', x+y)
y = nd.arange(6).reshape((3,1))
print('y = ', y)
```
## Converting from MXNet NDArray to NumPy
Converting MXNet NDArrays to and from
NumPy is easy. The converted arrays do not share memory.
```{.python .input}
a = x.asnumpy()
type(a)
```
```{.python .input}
y = nd.array(a)
print('id(a)=', id(a), 'id(x)=', id(x), 'id(y)=', id(y))
```
## Next Up
[NDArray Contexts](03-ndarray-contexts.md)