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

教師あり学習(ORの予測)

logical_or.livemd

教師あり学習(ORの予測)

Mix.install([
  {:nx, "~> 0.4"},
  {:axon, "~> 0.3"},
  {:exla, "~> 0.4"},
  {:table_rex, "~> 3.1"},
  {:kino_vega_lite, "~> 0.1.7"}
])

概要

  • 教師あり学習(ORの予測)
  • 二値分類
  • 非線形分類によって二値分類が実現していることを理解する
  • 学習データの可視化
  • 学習過程のアニメーション化
  • 予測の可視化
  • 学習回数や学習率による精度の変化
  • 学習グラフの読み方

資料

Nxの練習

行列をNx.tensorで作り、各値を3で除算するというNxのコード

Nx.tensor([[1, 2], [3, 4]])
|> Nx.divide(3)
Nx.tensor(for _ <- 1..32, do: [Enum.random(0..1)])

教師あり学習を実践:ORの予測

  • ビット演算などで使う「OR」を教師あり学習させて予測させる
  • ラベルは、入力1と入力2のOR演算を行えば計算できるため、教師あり学習のためのラベルの準備を手作業で行わなくて済む

ORのビット演算

input1 input2 output
0 0 0
0 1 1
1 0 1
1 1 1

ⅰ)学習/テストデータとラベルの準備

準備するもの

  • 学習に使うデータ
  • テストに使うデータ
  • データが入力されたときに期待する正解である「ラベル」

ラベル生成

  • 通常は、機械的なラベル生成はできないため、人が学習/テストデータを見て、ラベルを付与
## 学習データとラベルのセットを1000件生成

train_datas =
  Stream.repeatedly(fn ->
    input1 = Nx.tensor(for _ <- 1..32, do: [Enum.random(0..1)])
    input2 = Nx.tensor(for _ <- 1..32, do: [Enum.random(0..1)])

    data = %{"input1" => input1, "input2" => input2}
    label = Nx.logical_or(input1, input2)

    # 学習データとラベルをセットにする
    {data, label}
  end)
  |> Enum.take(1000)

学習データ1セットの可視化

  • 「Smart」Cellで気軽にグラフを描画できる
  • 「Smart」Cellはコード化することができる
## グラフ描画可能なデータを準備

# 4点が全て重なると、どれだけ生成されたかが分かりにくいので、各値を適度に揺らす
gen_random = fn -> Enum.random(0..1) + Enum.random(0..5) / 100 end

input1 = Nx.tensor(for _ <- 1..32, do: [gen_random.()])
input2 = Nx.tensor(for _ <- 1..32, do: [gen_random.()])

datas =
  Enum.zip([Nx.to_flat_list(input1), Nx.to_flat_list(input2)])
  |> Enum.map(fn {input1, input2} ->
    %{input1: input1, input2: input2}
  end)
## グラフを描画

VegaLite.new(width: 600, height: 400)
|> VegaLite.data_from_values(datas, only: ["input1", "input2"])
|> VegaLite.mark(:point)
|> VegaLite.encode_field(:x, "input1", type: :quantitative)
|> VegaLite.encode_field(:y, "input2", type: :quantitative)

ⅱ)モデルの学習

  • ⅰ)で作った学習データを入力したとき、そのラベルが出力となるよう、モデルに「学習」をさせる
  • 「学習」とは、「活性化関数」による通過/非活性の度合い(「重み」)を調整することを指す
  • Axon.Loopモジュールにある学習機能を使って、モデルの学習を行う

シグモイド関数

  • 二値分類、つまり0/1を分類するための関数

活性化関数(Activation function)

ReLU(Rectified Linear Unit)

## ニューラルネットワーク(モデル)の構築

require Axon

# 入力層:学習(もしくは予測)で入力されるデータの形を定義
input1 = Axon.input("input1", shape: {nil, 1})
input2 = Axon.input("input2", shape: {nil, 1})

# 構築したモデル
model =
  Axon.concatenate(input1, input2)
  # 中間層:活性化関数で通過させるか決定
  |> Axon.dense(8, activation: :relu)
  # 出力層:シグモイド関数で二値分類
  |> Axon.dense(1, activation: :sigmoid)

# モデルの入出力を表示
Axon.Display.as_table(model, Nx.template({1, 1}, :s64))
|> IO.puts()

正解率(Accuracy)

損失率(loss)

バイナリクロスエントロピー

最適化アルゴリズム

確率的勾配降下法(stochastic gradient descent)

エポック数

epochs_input = Kino.Input.number("Epochs", default: 3)
learning_rate_input = Kino.Input.number("Learning rate", default: 0.01)
## モデルの学習

# 学習結果状態
trained_state =
  model
  |> Axon.Loop.trainer(
    :binary_cross_entropy,
    Axon.Optimizers.sgd(Kino.Input.read(learning_rate_input))
  )
  |> Axon.Loop.metric(:accuracy, "Accuracy")
  |> Axon.Loop.run(train_datas, %{},
    epochs: Kino.Input.read(epochs_input),
    iterations: 1000,
    compiler: EXLA
  )

学習過程のアニメーション化

「学習」を可視化向けに関数化

fit = fn model, datas ->
  model
  |> Axon.Loop.trainer(:binary_cross_entropy, Axon.Optimizers.sgd(0.05))
  |> Axon.Loop.metric(:accuracy, "Accuracy")
  |> Axon.Loop.run(datas, %{}, compiler: EXLA)
end

「学習データ2つ(input1、input2)」と「学習データによる分類(x、y)」の2系統のレイヤーをグラフ化

# 「学習データ2つ(input1、input2)」と「学習データによる分類(x、y)」の2系統のレイヤーをグラフ化
graph =
  VegaLite.new(width: 600, height: 400)
  |> VegaLite.layers([
    VegaLite.new()
    |> VegaLite.mark(:point, tooltip: true)
    |> VegaLite.encode_field(:x, "input1", type: :quantitative)
    |> VegaLite.encode_field(:y, "input2", type: :quantitative),
    VegaLite.new()
    |> VegaLite.mark(:line)
    |> VegaLite.encode_field(:x, "x", type: :quantitative)
    |> VegaLite.encode_field(:y, "y", type: :quantitative)
  ])
  |> Kino.VegaLite.new()
  |> Kino.render()

# 「学習データ2つ(input1、input2)」と「学習データによる分類(x、y)」をグラフに流し込む処理
plot = fn model, datas, model_state ->
  input1 = datas |> Enum.map(&amp;(elem(&amp;1, 0)["input1"] |> Nx.to_flat_list())) |> List.flatten()
  input2 = datas |> Enum.map(&amp;(elem(&amp;1, 0)["input2"] |> Nx.to_flat_list())) |> List.flatten()

  x =
    for(i <- 0..99, do: i / 100)
    |> Nx.tensor()
    |> Nx.new_axis(0)
    |> Nx.transpose()

  y = Axon.predict(model, model_state, %{"input1" => x, "input2" => x})

  points =
    Enum.zip([input1, input2, Nx.to_flat_list(x), Nx.to_flat_list(y)])
    |> Enum.map(fn {input1, input2, x, y} ->
      %{input1: input1, input2: input2, x: x, y: y}
    end)

  Kino.VegaLite.clear(graph)
  Kino.VegaLite.push_many(graph, points)
end
for _ <- 1..15 do
  model_state = fit.(model, train_datas)
  plot.(model, train_datas, model_state)
end

ⅲ)テストデータによる評価

  • ⅰ)で作ったテストデータを学習済みモデルに入力した結果が、いかにラベルと一致するかを評価
  • テストが不要なほどカンタンな例のため、今回は割愛

ⅳ)未知データによる予測

  • ⅲ)で精度が充分な状態になっていることが前提
  • 学習データには存在しないデータで、期待するデータが出力(予測、識別)されることを確認
    • 期待する精度が出ないケースを捕捉
    • 期待以下が頻出する場合
      • モデルの見直し
      • 精度が出ないケースをサポート対象外とする
  • 精度を定期的にチェック
  • このタスクは、開発した学習済みモデルを本番運用に回した後も必要
Axon.predict(model, trained_state, %{
  "input1" => Nx.tensor([[0]]),
  "input2" => Nx.tensor([[0]])
})
Axon.predict(model, trained_state, %{
  "input1" => Nx.tensor([[0]]),
  "input2" => Nx.tensor([[1]])
})
Axon.predict(model, trained_state, %{
  "input1" => Nx.tensor([[1]]),
  "input2" => Nx.tensor([[0]])
})
Axon.predict(model, trained_state, %{
  "input1" => Nx.tensor([[1]]),
  "input2" => Nx.tensor([[1]])
})

予測の可視化

epochs_input = Kino.Input.number("Epochs", default: 3)
learning_rate_input = Kino.Input.number("Learning rate", default: 0.01)
predicts =
  1..5
  |> Enum.map(fn _ ->
    trained_state =
      model
      |> Axon.Loop.trainer(
        :binary_cross_entropy,
        Axon.Optimizers.sgd(Kino.Input.read(learning_rate_input))
      )
      |> Axon.Loop.metric(:accuracy, "Accuracy")
      |> Axon.Loop.run(train_datas, %{},
        epochs: Kino.Input.read(epochs_input),
        iteration: 1000,
        compiler: EXLA
      )

    [
      %{
        x: 0 + Enum.random(0..3) / 100,
        y:
          Axon.predict(model, trained_state, %{
            "input1" => Nx.tensor([[0]]),
            "input2" => Nx.tensor([[0]])
          })
          |> Nx.to_flat_list()
          |> List.first()
      },
      %{
        x: 0.1 + Enum.random(0..3) / 100,
        y:
          Axon.predict(model, trained_state, %{
            "input1" => Nx.tensor([[0]]),
            "input2" => Nx.tensor([[1]])
          })
          |> Nx.to_flat_list()
          |> List.first()
      },
      %{
        x: 0.9 - Enum.random(0..3) / 100,
        y:
          Axon.predict(model, trained_state, %{
            "input1" => Nx.tensor([[1]]),
            "input2" => Nx.tensor([[0]])
          })
          |> Nx.to_flat_list()
          |> List.first()
      },
      %{
        x: 1 - Enum.random(0..3) / 100,
        y:
          Axon.predict(model, trained_state, %{
            "input1" => Nx.tensor([[1]]),
            "input2" => Nx.tensor([[1]])
          })
          |> Nx.to_flat_list()
          |> List.first()
      }
    ]
  end)
  |> List.flatten()
VegaLite.new(width: 600, height: 400)
|> VegaLite.data_from_values(predicts, only: ["x", "y"])
|> VegaLite.mark(:point)
|> VegaLite.encode_field(:x, "x", type: :quantitative)
|> VegaLite.encode_field(:y, "y", type: :quantitative)