Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Visualizing Your Data With Kino

notebooks/kino.livemd

Visualizing Your Data With Kino

Mix.install([
  {:nx, "~> 0.5"},
  {:scidata, "~> 0.1"},
  {:explorer, "~> 0.5.0"},
  {:kino, "~> 0.9.3"}
])
:ok

Load your data

We will be grabbing the MNIST dataset from scidata using Scidata.MNIST.download(). This is equivalent (I think) to tfds. Note that MNIST splits the images from the labels.

{images, labels} = Scidata.MNIST.download()
{{<<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...>>, {:u, 8}, {60000, 1, 28, 28}},
 {<<5, 0, 4, 1, 9, 2, 1, 3, 1, 4, 3, 5, 3, 6, 1, 7, 2, 8, 6, 9, 4, 0, 9, 1, 1, 2, 4, 3, 2, 7, 3, 8,
    6, 9, 0, 5, 6, 0, 7, 6, 1, 8, 7, 9, 3, 9, 8, ...>>, {:u, 8}, {60000}}}

In the dataset, we know the images are 28x28 (so 784 pixels) and 60k images. We can also extract this information from the images data itself.

{_, _, shape} = images
{dataset_size, _channels, h, w} = shape
num_features = h * w

{dataset_size, num_features}
{60000, 784}

Note that I called the pixels num_features as we use that matrix of 1s (dark) and 0s (light) to help us figure out the letter. So we need to take our images bin and load them based on the type. We then normlize them to 0s and 1s, as the docs state that pixel 0 means background (white), 255 means foreground (black). Finally, we describe the shape of the data as containing {image, channel, height, width}.

{bin, type, _} = images

train_images =
  bin
  |> Nx.from_binary(type)
  |> Nx.divide(255.0)
  |> Nx.reshape(shape, names: [:images, :channels, :height, :width])
#Nx.Tensor<
  f32[images: 60000][channels: 1][height: 28][width: 28]
  [
    [
      [
        [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
        [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ...],
        ...
      ]
    ],
    ...
  ]
>

We can visualize the number using

train_images[[images: 1]] |> Nx.to_heatmap()
#Nx.Heatmap<
  f32[channels: 1][height: 28][width: 28]
  [
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
                                
  ]
>

Let’s visualize with a Kino using Nx.to_heatmap()

form =
  Kino.Control.form(
    [
      index: Kino.Input.number("Image Index", default: 0)
    ],
    submit: "Render"
  )

Kino.render(form)

form
|> Kino.Control.stream()
|> Kino.animate(fn %{data: %{index: index}} ->
  train_images[[images: index]] |> Nx.to_heatmap()
end)