Nx で ニューラルネットワークやってみた
Mix.install([
{:nx, "~> 0.9"},
{:axon, "~> 0.6"},
{:kino, "~> 0.16.0"},
{:kino_vega_lite, "~> 0.1.9"}
])
単層パーセプトロン
単層パーセプトロンで実装できる AND,OR,NAND を以下は以下のように実装できる。
defmodule Gate do
@doc """
ANDゲート
重み: [0.5, 0.5]
バイアス: -0.7
"""
def and_gate(x1, x2) do
x = Nx.tensor([x1, x2])
w = Nx.tensor([0.5, 0.5])
b = Nx.tensor(-0.7)
temp = Nx.add(Nx.dot(x, w), b)
if Nx.to_number(temp) > 0 do
1
else
0
end
end
@doc """
ORゲート
重み: [0.5, 0.5]
バイアス: -0.3
"""
def or_gate(x1,x2) do
x = Nx.tensor([x1, x2])
w = Nx.tensor([0.5, 0.5])
b = Nx.tensor(-0.3)
temp = Nx.add(Nx.dot(x, w), b)
if Nx.to_number(temp) > 0 do
1
else
0
end
end
@doc """
ANDゲート
重み: [-0.5, -0.5]
バイアス: 0.7
"""
def nand_gate(x1,x2) do
x = Nx.tensor([x1, x2])
w = Nx.tensor([-0.5, -0.5])
b = Nx.tensor(0.7)
temp = Nx.add(Nx.dot(x, w), b)
if Nx.to_number(temp) > 0 do
1
else
0
end
end
end
IO.puts "入力: [1, 0], 出力 AND : #{Gate.and_gate(1,0)}"
IO.puts "入力: [0, 1], 出力 OR : #{Gate.or_gate(0, 1)}"
IO.puts "入力: [1, 1], 出力 NAND: #{Gate.nand_gate(1,1)}"
AND、OR、NANDは 線形分離可能 であるため、単一のパーセプトロンで実装することができる
# AND
points =
VegaLite.new()
|> VegaLite.data_from_values([
%{x1: 0, x2: 0, label: "0"},
%{x1: 0, x2: 1, label: "0"},
%{x1: 1, x2: 0, label: "0"},
%{x1: 1, x2: 1, label: "1"}
])
|> VegaLite.mark(:point)
|> VegaLite.encode_field(:x, "x1", type: :quantitative)
|> VegaLite.encode_field(:y, "x2", type: :quantitative)
|> VegaLite.encode_field(:color, "label", type: :nominal)
line_data = for x <- 0..100, do: %{x1: x / 100, x2: 1.0 - x / 100}
line =
VegaLite.new()
|> VegaLite.data_from_values(line_data)
|> VegaLite.mark(:line)
|> VegaLite.encode_field(:x, "x1", type: :quantitative)
|> VegaLite.encode_field(:y, "x2", type: :quantitative)
VegaLite.new()
|> VegaLite.layers([points, line])
# OR
points =
VegaLite.new()
|> VegaLite.data_from_values([
%{x1: 0, x2: 0, label: "0"},
%{x1: 0, x2: 1, label: "1"},
%{x1: 1, x2: 0, label: "1"},
%{x1: 1, x2: 1, label: "1"}
])
|> VegaLite.mark(:point)
|> VegaLite.encode_field(:x, "x1", type: :quantitative)
|> VegaLite.encode_field(:y, "x2", type: :quantitative)
|> VegaLite.encode_field(:color, "label", type: :nominal)
VegaLite.new()
|> VegaLite.layers([points, line])
# NAND
points =
VegaLite.new()
|> VegaLite.data_from_values([
%{x1: 0, x2: 0, label: "1"},
%{x1: 0, x2: 1, label: "0"},
%{x1: 1, x2: 0, label: "0"},
%{x1: 1, x2: 1, label: "0"}
])
|> VegaLite.mark(:point)
|> VegaLite.encode_field(:x, "x1", type: :quantitative)
|> VegaLite.encode_field(:y, "x2", type: :quantitative)
|> VegaLite.encode_field(:color, "label", type: :nominal)
VegaLite.new()
|> VegaLite.layers([points, line])
たとえばANDを図で表すなら、次のように表すことができ、単層のパーセプトロンで実装することができる。
graph LR
subgraph 入力層
x1[x1]
x2[x2]
end
subgraph 出力層
y[y: AND]
end
x1 --> y
x2 --> y
多層パーセプトロン
XORは、単層のパーセプトロンでは実装できない。 それは線形で表現できない、非線形だからである。
# XOR
points =
VegaLite.new()
|> VegaLite.data_from_values([
%{x1: 0, x2: 0, label: "0"},
%{x1: 0, x2: 1, label: "1"},
%{x1: 1, x2: 0, label: "1"},
%{x1: 1, x2: 1, label: "0"}
])
|> VegaLite.mark(:point)
|> VegaLite.encode_field(:x, "x1", type: :quantitative)
|> VegaLite.encode_field(:y, "x2", type: :quantitative)
|> VegaLite.encode_field(:color, "label", type: :nominal)
VegaLite.new()
|> VegaLite.layers([points, line])
XORの実装は、NAND、OR、ANDを組み合わせることで実装ができる
defmodule GateExtra do
def xor_gate(x1, x2) do
s1 = Gate.nand_gate(x1, x2)
s2 = Gate.or_gate(x1, x2)
Gate.and_gate(s1, s2)
end
end
y = GateExtra.xor_gate(1,1)
IO.puts y
XORの実装を図で表すと以下のようになる。
graph LR
subgraph 入力層
x1[x1]
x2[x2]
end
subgraph 中間層
s1[s1: NAND]
s2[s2: OR]
end
subgraph 出力層
y[y: OR]
end
x1 --> s1
x2 --> s1
x1 --> s2
x2 --> s2
s1 --> y
s2 --> y
XORは、中間層でNANDとORを計算し、出力層でANDすることで実装できる 2層の 多層パーセプトロンということになる。