diff --git a/nx/guides/cheatsheets/numpy_nx.cheatmd b/nx/guides/cheatsheets/numpy_nx.cheatmd new file mode 100644 index 0000000000..dd8bddf97c --- /dev/null +++ b/nx/guides/cheatsheets/numpy_nx.cheatmd @@ -0,0 +1,405 @@ +# NumPy -> Nx + +This cheatsheet is designed to assist Python developers in transitioning to Elixir, +specifically by providing equivalent commands and code examples between NumPy and Nx. + +## Tensor Creation +{: .col-2} + +### From list or nested list +#### NumPy +```python +>>> np.array([1, 2, 3]) +array([1, 2, 3]) +``` + +#### Nx +```elixir +iex> Nx.tensor([1, 2, 3]) +#Nx.Tensor< + s32[3] + [1, 2, 3] +> +``` + +### 2D Arrays/Tensors +#### NumPy +```python +>>> np.array([[1, 2], [3, 4]]) +array([[1, 2], + [3, 4]]) +``` + +#### Nx +```elixir +iex> Nx.tensor([[1, 2], [3, 4]]) +#Nx.Tensor< + s32[2][2] + [ + [1, 2], + [3, 4] + ] +> +``` + +### Zeros and Ones + +#### NumPy +```python +>>> np.zeros((2, 3)) +array([[0., 0., 0.], + [0., 0., 0.]]) + +>>> np.ones((2, 3)) +array([[1., 1., 1.], + [1., 1., 1.]]) +``` + +#### NumPy +```elixir +iex> Nx.broadcast(0, {2, 3}) +#Nx.Tensor< + s32[2][3] + [ + [0, 0, 0], + [0, 0, 0] + ] +> + +iex> Nx.broadcast(1, {2, 3}) +#Nx.Tensor< + s32[2][3] + [ + [1, 1, 1], + [1, 1, 1] + ] +> +``` + +### Range of Numbers + +#### NumPy +```python +>>> np.arange(0, 10, 2) +array([0, 2, 4, 6, 8]) +``` + +#### NumPy +```elixir +iex> Nx.iota({5}, axis: 0) |> Nx.multiply(2) +#Nx.Tensor< + s32[5] + [0, 2, 4, 6, 8] +> +``` + +### Linearly Spaced Values + +#### NumPy +```python +>>> np.linspace(0, 1, 5) +array([0. , 0.25, 0.5 , 0.75, 1. ]) +``` + +#### NumPy +```elixir +iex> Nx.iota({5}) |> Nx.divide(4) +#Nx.Tensor< + f32[5] + [0.0, 0.25, 0.5, 0.75, 1.0] +> +``` + +## Tensor Inspection +{: .col-2-left} + +### Shape +#### NumPy +```python +>>> a = np.array([[1, 2, 3], [4, 5, 6]]) +>>> a.shape +(2, 3) +``` + +#### Nx +```elixir +iex> a = Nx.tensor([[1, 2, 3], [4, 5, 6]]) +#Nx.Tensor< + s32[2][3] + [ + [1, 2, 3], + [4, 5, 6] + ] +> +iex> Nx.shape(a) +{2, 3} +``` + +### Number of dimensions + +#### NumPy +```python +>>> a.ndim +2 +``` + +#### Nx +```elixir +iex> Nx.rank(a) +2 +``` + +### Data Type + +#### NumPy +```python +>>> a.dtype +dtype('int64') +``` + +#### Nx +```elixir +iex> Nx.type(a) +{:s, 32} +``` + +### Total Number of Elements + +#### NumPy +```python +>>> a.size +6 +``` + +#### Nx +```elixir +iex> Nx.size(a) +6 +``` + +## Indexing and Slicing +{: .col-2} + +### Indexing a Single Element +#### NumPy +```python +>>> a = np.array([[10, 20], [30, 40]]) +>>> a[0, 1] +np.int64(20) +``` + +#### Nx +```elixir +# Indexing a Single Element +iex> tensor = Nx.tensor([[10, 20], [30, 40]]) +iex> tensor[[0, 1]] +#Nx.Tensor< + s32 + 20 +> +``` + +### Slicing a Range +#### NumPy +```python +>>> a = np.array([10, 20, 30, 40, 50]) +>>> a[1:4] +array([20, 30, 40]) +``` + +#### Nx +```elixir +# Slicing a Range +iex> a = Nx.tensor([10, 20, 30, 40, 50]) +iex> a[1..3] +#Nx.Tensor< + s32[3] + [20, 30, 40] +> +``` + +### Selecting Along a Specific Axis +#### NumPy +```python +>>> a = np.array([[1, 2], [3, 4], [5, 6]]) +>>> a[:, 1] +array([2, 4, 6]) +``` + +#### Nx +```elixir +# Selecting Along a Specific Axis +iex> a = Nx.tensor([[1, 2], [3, 4], [5, 6]]) +iex> a[[.., 1]] +#Nx.Tensor< + s32[3] + [2, 4, 6] +> +``` + +### Boolean Masking +#### NumPy +```python +>>> x = np.arange(10) +>>> x[x % 2 == 0] +array([0, 2, 4, 6, 8]) +``` + +#### Nx + +Boolean masking requires dynamic shape behavior, which is not +supported in Nx because Nx compiles all operations +ahead-of-time (like XLA or Jax), and for that, tensors must have static shapes. + +## Linear Algebra Operations +{: .col-2} + +### Matrix Multiplication +#### NumPy +```python +>>> A = np.array([[1, 2], [3, 4]]) +>>> B = np.array([[5, 6], [7, 8]]) +>>> np.matmul(A, B) +array([[19, 22], + [43, 50]]) +``` + +#### Nx +```elixir +iex> a = Nx.tensor([[1, 2], [3, 4]]) +iex> b = Nx.tensor([[5, 6], [7, 8]]) +iex> Nx.dot(a, b) +#Nx.Tensor< + s32[2][2] + [ + [19, 22], + [43, 50] + ] +> +``` + +### Transpose +#### NumPy +```python +>>> A.T +array([[1, 3], + [2, 4]]) +``` + +#### Nx +```elixir +iex> Nx.transpose(a) +#Nx.Tensor< + s32[2][2] + [ + [1, 3], + [2, 4] + ] +> +``` + +### Identity Matrix +#### NumPy +```python +>>> np.eye(3) +array([[1., 0., 0.], + [0., 1., 0.], + [0., 0., 1.]]) +``` + +#### Nx +```elixir +iex> Nx.eye({3, 3}) +#Nx.Tensor< + s32[3][3] + [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1] + ] +> +``` + +### Determinant +#### NumPy +```python +>>> np.linalg.det(A) +np.float64(-2.0000000000000004) +``` + +#### Nx +```elixir +iex> Nx.LinAlg.determinant(a) +#Nx.Tensor< + f32 + -2.0 +> +``` + +### Inverse +#### NumPy +```python +>>> np.linalg.inv(A) +array([[-2. , 1. ], + [ 1.5, -0.5]]) +``` + +#### Nx +```elixir +iex> Nx.LinAlg.invert(a) +#Nx.Tensor< + f32[2][2] + [ + [-2.000000476837158, 1.0000003576278687], + [1.5000004768371582, -0.5000002384185791] + ] +> +``` + +### Solve a System of Linear Equations +#### NumPy +```python +>>> A = np.array([[3, 1], [1, 2]]) +>>> b = np.array([9, 8]) +>>> np.linalg.solve(A, b) +array([2., 3.]) +``` + +#### Nx +```elixir +iex> a = Nx.tensor([[3.0, 1.0], [1.0, 2.0]]) +iex> b = Nx.tensor([9.0, 8.0]) +iex> Nx.LinAlg.solve(a, b) +#Nx.Tensor< + f32[2] + [2.0, 3.0] +> +``` + +### Eigenvalues and Eigenvectors +#### NumPy +```python +>>> np.linalg.eigh(A) +EighResult( + eigenvalues=array([1.38196601, 3.61803399]), + eigenvectors=array([ + [ 0.52573111, -0.85065081], + [-0.85065081, -0.52573111] + ])) +``` + +#### Nx +```elixir +iex> Nx.LinAlg.eigh(a) +{#Nx.Tensor< + f32[2] + [3.618025779724121, 1.381974220275879] + >, + #Nx.Tensor< + f32[2][2] + [ + [0.8516583442687988, -0.5240974426269531], + [0.5240974426269531, 0.8516583442687988] + ] + >} +``` \ No newline at end of file diff --git a/nx/guides/getting_started/broadcast.livemd b/nx/guides/getting_started/broadcast.livemd new file mode 100644 index 0000000000..ecd46f1b7a --- /dev/null +++ b/nx/guides/getting_started/broadcast.livemd @@ -0,0 +1,153 @@ +# Broadcasts + +Often, the dimensions of tensors in an operator don't match. +For example, you might want to subtract a `1` from every +element of a `{2, 2}` tensor, like this: + +$$ +\begin{bmatrix} + 1 & 2 \\\\ + 3 & 4 +\end{bmatrix} - 1 = +\begin{bmatrix} + 0 & 1 \\\\ + 2 & 3 +\end{bmatrix} +$$ + +Mathematically, it's the same as this: + +$$ +\begin{bmatrix} + 1 & 2 \\\\ + 3 & 4 +\end{bmatrix} - +\begin{bmatrix} + 1 & 1 \\\\ + 1 & 1 +\end{bmatrix} = +\begin{bmatrix} + 0 & 1 \\\\ + 2 & 3 +\end{bmatrix} +$$ + +That means we need a way to convert `1` to a `{2, 2}` tensor. +`Nx.broadcast/2` solves that problem. This function takes +a tensor or a scalar and a shape. + +```elixir +Mix.install([ + {:nx, "~> 0.5"} +]) + + +Nx.broadcast(1, {2, 2}) +``` + +This broadcast takes the scalar `1` and translates it +to a compatible shape by copying it. Sometimes, it's easier +to provide a tensor as the second argument, and let `broadcast/2` +extract its shape: + +```elixir +tensor = Nx.tensor([[1, 2], [3, 4]]) +Nx.broadcast(1, tensor) +``` + +The code broadcasts `1` to the shape of `tensor`. In many operators +and functions, the broadcast happens automatically: + +```elixir +Nx.subtract(tensor, 1) +``` + +This result is possible because Nx broadcasts _both tensors_ +in `subtract/2` to compatible shapes. That means you can provide +scalar values as either argument: + +```elixir +Nx.subtract(10, tensor) +``` + +Or subtract a row or column. Mathematically, it would look like this: + +$$ +\begin{bmatrix} + 1 & 2 \\\\ + 3 & 4 +\end{bmatrix} - +\begin{bmatrix} + 1 & 2 +\end{bmatrix} = +\begin{bmatrix} + 0 & 0 \\\\ + 2 & 2 +\end{bmatrix} +$$ + +which is the same as this: + +$$ +\begin{bmatrix} + 1 & 2 \\\\ + 3 & 4 +\end{bmatrix} - +\begin{bmatrix} + 1 & 2 \\\\ + 1 & 2 +\end{bmatrix} = +\begin{bmatrix} + 0 & 0 \\\\ + 2 & 2 +\end{bmatrix} +$$ + +This rewrite happens in Nx too, also through a broadcast. We want to +broadcast the tensor `[1, 2]` to match the `{2, 2}` shape, like this: + +```elixir +Nx.broadcast(Nx.tensor([1, 2]), {2, 2}) +``` + +The `subtract` function in `Nx` takes care of that broadcast +implicitly, as before: + +```elixir +Nx.subtract(tensor, Nx.tensor([1, 2])) +``` + +The broadcast worked as advertised, copying the `[1, 2]` row +enough times to fill a `{2, 2}` tensor. A tensor with a +dimension of `1` will broadcast to fill the tensor: + +```elixir +[[1], [2]] |> Nx.tensor() |> Nx.broadcast({1, 2, 2}) +``` + +```elixir +[[[1, 2, 3]]] +|> Nx.tensor() +|> Nx.broadcast({4, 2, 3}) +``` + +Both of these examples copy parts of the tensor enough +times to fill out the broadcast shape. You can check out the +Nx broadcasting documentation for more details: + + + +```elixir +h Nx.broadcast +``` + +Much of the time, you won't have to broadcast yourself. Many of +the functions and operators Nx supports will do so automatically. + +We can use tensor-aware operators via various `Nx` functions and +many of them implicitly broadcast tensors. + +Throughout this section, we have been invoking `Nx.subtract/2` and +our code would be more expressive if we could use its equivalent +mathematical operator. Fortunately, Nx provides a way. Next, we'll +dive into numerical definitions using `defn`. diff --git a/nx/guides/getting_started/numerical_definitions.livemd b/nx/guides/getting_started/numerical_definitions.livemd index 5fefb4af22..04ba8dadda 100644 --- a/nx/guides/getting_started/numerical_definitions.livemd +++ b/nx/guides/getting_started/numerical_definitions.livemd @@ -1,8 +1,6 @@ -# Numerical Definitions (defn) +# Numerical definitions (defn) -## Section - -The `defn` macro and its siblings simplify the expression of mathematical formulas +The `defn` macro simplifies the expression of mathematical formulas containing tensors. Numerical definitions have two primary benefits over classic Elixir functions. diff --git a/nx/mix.exs b/nx/mix.exs index 0b05a2df72..e76161f5d1 100644 --- a/nx/mix.exs +++ b/nx/mix.exs @@ -37,7 +37,9 @@ defmodule Nx.MixProject do [ {:complex, "~> 0.6"}, {:telemetry, "~> 0.4.0 or ~> 1.0"}, - {:ex_doc, "~> 0.29", only: :docs} + {:ex_doc, "~> 0.29", only: :docs}, + {:makeup, "~> 1.2.1", only: :docs}, + {:makeup_syntect, "~> 0.1", only: :docs} ] end @@ -62,6 +64,7 @@ defmodule Nx.MixProject do "guides/getting_started/quickstart.livemd", "guides/getting_started/broadcasting.livemd", "guides/getting_started/numerical_definitions.livemd", + "guides/cheatsheets/numpy_nx.cheatmd", "guides/advanced/vectorization.livemd", "guides/advanced/aggregation.livemd", "guides/advanced/automatic_differentiation.livemd", @@ -118,6 +121,7 @@ defmodule Nx.MixProject do ], groups_for_extras: [ "Getting Started": ~r"^guides/getting_started/", + Cheatsheets: ~r"^guides/cheatsheets/", Exercises: ~r"^guides/exercises/", Advanced: ~r"^guides/advanced/" ] diff --git a/nx/mix.lock b/nx/mix.lock index 2d7fd485c4..6af877993c 100644 --- a/nx/mix.lock +++ b/nx/mix.lock @@ -1,10 +1,13 @@ %{ + "castore": {:hex, :castore, "1.0.12", "053f0e32700cbec356280c0e835df425a3be4bc1e0627b714330ad9d0f05497f", [:mix], [], "hexpm", "3dca286b2186055ba0c9449b4e95b97bf1b57b47c1f2644555879e659960c224"}, "complex": {:hex, :complex, "0.6.0", "b0130086a7a8c33574d293b2e0e250f4685580418eac52a5658a4bd148f3ccf1", [:mix], [], "hexpm", "0a5fa95580dcaf30fcd60fe1aaf24327c0fe401e98c24d892e172e79498269f9"}, "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"}, - "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, + "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "makeup_syntect": {:hex, :makeup_syntect, "0.1.3", "ae2c3437f479ea50d08d794acaf02a2f3a8c338dd1f757f6b237c42eb27fcde1", [:mix], [{:makeup, "~> 1.2", [hex: :makeup, repo: "hexpm", optional: false]}, {:rustler, "~> 0.36.1", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.8.2", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "a27bd3bd8f7b87465d110295a33ed1022202bea78701bd2bbeadfb45d690cdbf"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "rustler_precompiled": {:hex, :rustler_precompiled, "0.8.2", "5f25cbe220a8fac3e7ad62e6f950fcdca5a5a5f8501835d2823e8c74bf4268d5", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "63d1bd5f8e23096d1ff851839923162096364bac8656a4a3c00d1fff8e83ee0a"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, }