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

Demo 4: AI/ML com Elixir

sorry-dave/ai_elixir_tools.livemd

Demo 4: AI/ML com Elixir

Mix.install(
  [
    {:kino, "~> 0.14.1"},
    {:kino_flame, github: "hugobarauna/kino_flame"},
    {:kino_bumblebee, "~> 0.5.0"},
    {:axon, "~> 0.5"},
    {:bumblebee, "~> 0.5"},
    {:nx, "~> 0.8.0", override: true},
    {:exla, "~> 0.8.0", override: true},
    {:explorer, "~> 0.9.2"},
    {:kino_explorer, "~> 0.1.23"},
    {:scidata, "~> 0.1.11"},
    {:castore, "~> 1.0", override: true},
    {:flame, "~> 0.5.1"}
  ],
  system_env: [
    XLA_TARGET: "cuda12"
  ],
  config: [
    nx: [
      default_backend: EXLA.Backend,
      default_defn_options: [compiler: EXLA, client: :cuda]
    ]
  ]
)

defmodule KinoSlides do
  use GenServer

  def new(slides) do
    frame = Kino.Frame.new()
    Kino.start_child({KinoSlides, %{slides: slides, layout_frame: frame}})

    frame
  end

  def start_link(init_arg) do
    GenServer.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  @impl true
  def init(init_arg) do
    prev_button = Kino.Control.button("<")
    next_button = Kino.Control.button(">")

    Kino.Control.subscribe(prev_button, :prev_button_clicked)
    Kino.Control.subscribe(next_button, :next_button_clicked)

    state = Map.merge(init_arg, %{current_slide: 0, buttons: [prev_button, next_button]})

    {:ok, state |> render()}
  end

  defp render(state) do
    slide = Enum.at(state.slides, state.current_slide)

    layout =
      Kino.Layout.grid([
        Kino.Markdown.new(slide),
        Kino.Layout.grid(state.buttons, columns: 8)
      ])

    Kino.Frame.render(state.layout_frame, layout)
    state
  end

  @impl true
  def handle_info({:prev_button_clicked, _}, state) do
    current_slide =
      if(state.current_slide == 0) do
        0
      else
        state.current_slide - 1
      end

    state = %{state | current_slide: current_slide}
    {:noreply, state |> render()}
  end

  @impl true
  def handle_info({:next_button_clicked, _}, state) do
    max_slide = Enum.count(state.slides) - 1

    current_slide =
      if(state.current_slide == max_slide) do
        max_slide
      else
        state.current_slide + 1
      end

    state = %{state | current_slide: current_slide}
    {:noreply, state |> render()}
  end
end

slides = [
  """
  

  
  

The Nx Stack

Nx: Numerical Elixir

![](https://dashbit.co/images/posts/2021/nx.png) """
, """

Ferramentas para cada etapa

![](https://i.imgur.com/b61BFWK.png) """
]

Stack de ferramentas

KinoSlides.new(slides)

Ferrametas para cada etapa

  • Nx: tensores e compilação para CPU/GPU
  • Explorer: dataframes
  • Scholar: machine learning tradicional
  • Axon: redeus neurais
  • Bumblebee: modelos pré-treinados integrado com Hugging Face 🤗
  • Livebook: notebook computacional
  • VegaLite: bindings para o Vega-Lite

Modelos pré-treinados com Bumblebee

{:ok, model_info} = Bumblebee.load_model({:hf, "microsoft/resnet-50"})
{:ok, featurizer} = Bumblebee.load_featurizer({:hf, "microsoft/resnet-50"})

serving =
  Bumblebee.Vision.image_classification(model_info, featurizer,
    compile: [batch_size: 1],
    defn_options: [compiler: EXLA]
  )

image_input = Kino.Input.image("Image", size: {224, 224})
form = Kino.Control.form([image: image_input], submit: "Run")
frame = Kino.Frame.new()

Kino.listen(form, fn %{data: %{image: image}} ->
  if image do
    Kino.Frame.render(frame, Kino.Text.new("Running..."))

    image =
      image.file_ref
      |> Kino.Input.file_path()
      |> File.read!()
      |> Nx.from_binary(:u8)
      |> Nx.reshape({image.height, image.width, 3})

    output = Nx.Serving.run(serving, image)

    output.predictions
    |> Enum.map(&amp;{&amp;1.label, &amp;1.score})
    |> Kino.Bumblebee.ScoredList.new()
    |> then(&amp;Kino.Frame.render(frame, &amp;1))
  end
end)

Kino.Layout.grid([form, frame], boxed: true, gap: 16)

Usar Smart Cell para exemplo de classificação de imagem usando ResNet

Treinando modelos com Axon

Preparando o dataset

{images, labels} = Scidata.MNIST.download()

{image_data, image_type, image_shape} = images
{label_data, label_type, label_shape} = labels

images =
  image_data
  |> Nx.from_binary(image_type)
  |> Nx.divide(255)
  |> Nx.reshape({60000, :auto})

labels =
  label_data
  |> Nx.from_binary(label_type)
  |> Nx.reshape(label_shape)
  |> Nx.new_axis(-1)
  |> Nx.equal(Nx.iota({1, 10}))

train_range = 0..49_999//1
test_range = 50_000..-1//1

train_images = images[train_range]
train_labels = labels[train_range]
test_images = images[test_range]
test_labels = labels[test_range]

Construindo o modelo

model =
  Axon.input("images", shape: {nil, 784})
  |> Axon.dense(128, activation: :relu)
  |> Axon.dense(10, activation: :softmax)
template = Nx.template({1, 784}, :f32)
Axon.Display.as_graph(model, template)
Axon.Display.as_table(model, template)
|> IO.puts()

Treinando o modelo

batch_size = 64

train_data =
  train_images
  |> Nx.to_batched(batch_size)
  |> Stream.zip(Nx.to_batched(train_labels, batch_size))

trained_model_state =
  model
  |> Axon.Loop.trainer(:categorical_cross_entropy, :sgd)
  |> Axon.Loop.metric(:accuracy)
  |> Axon.Loop.run(train_data, %{}, epochs: 10, compiler: EXLA)

Usando o modelo

test_data =
  test_images
  |> Nx.to_batched(batch_size)
  |> Stream.zip(Nx.to_batched(test_labels, batch_size))

{test_batch, _} = Enum.at(test_data, 7)
test_image = test_batch[0]

test_image
|> Nx.reshape({28, 28})
|> Nx.to_heatmap()
{_, predict_fn} = Axon.build(model, compiler: EXLA)

probabilities =
  test_image
  |> Nx.new_axis(0)
  |> then(&amp;predict_fn.(trained_model_state, &amp;1))

predicted_number =
  probabilities
  |> Nx.argmax()
  |> Nx.to_number()