Packages

A composable deep learning framework in Elixir

Current section

Files

Jump to
annex lib annex layer.ex
Raw

lib/annex/layer.ex

defmodule Annex.Layer do
@moduledoc """
The Annex.Layer is the module that defines types, callbacks, and helper for Layers.
By implementing the Layer behaviour a struct/model can be used along side other
Layers to compose the layers of a deep neural network.
"""
alias Annex.{
Data,
Layer.Backprop,
LayerConfig,
Shape
}
@type t() :: struct()
@callback feedforward(t(), Data.data()) :: {struct(), Data.data()}
@callback backprop(t(), Data.data(), Backprop.t()) :: {t(), Data.data(), Backprop.t()}
@callback init_layer(LayerConfig.t(module())) :: t()
@callback data_type(t()) :: Data.type()
@callback shapes(t()) :: {Shape.t(), Shape.t()}
@optional_callbacks [
shapes: 1,
data_type: 1
]
defmacro __using__(_) do
quote do
alias Annex.Layer
@behaviour Layer
alias Annex.AnnexError
alias Annex.LayerConfig
require Annex.Utils
import Annex.Utils, only: [validate: 3]
def __annex__(:is_layer?), do: true
end
end
@spec feedforward(struct(), any()) :: {struct(), any()}
def feedforward(%module{} = layer, inputs) do
module.feedforward(layer, inputs)
end
@spec backprop(struct(), any(), Backprop.t()) :: {struct(), any(), Backprop.t()}
def backprop(%module{} = layer, error, props) do
module.backprop(layer, error, props)
end
@spec init_layer(LayerConfig.t(module())) :: t()
def init_layer(%LayerConfig{} = cfg), do: LayerConfig.init_layer(cfg)
@spec has_data_type?(module() | struct()) :: boolean()
def has_data_type?(%module{}), do: has_data_type?(module)
def has_data_type?(module) when is_atom(module), do: function_exported?(module, :data_type, 1)
@spec data_type(atom | struct()) :: Data.type()
def data_type(%module{} = layer), do: module.data_type(layer)
@spec shapes(t()) :: {Shape.t(), Shape.t()}
def shapes(%module{} = layer), do: module.shapes(layer)
@spec has_shapes?(module() | struct()) :: boolean()
def has_shapes?(%module{}), do: has_shapes?(module)
def has_shapes?(module) when is_atom(module), do: function_exported?(module, :shapes, 1)
@spec is_layer?(any()) :: boolean()
def is_layer?(%module{}) do
is_layer?(module)
end
def is_layer?(item) do
is_atom(item) && function_exported?(item, :__annex__, 1) && item.__annex__(:is_layer?)
end
def input_shape(layer) do
if has_shapes?(layer) do
{input_shape, _} = shapes(layer)
input_shape
end
end
def output_shape(layer) do
if has_shapes?(layer) do
{_, output_shape} = shapes(layer)
output_shape
end
end
@spec convert(t(), Data.data(), Shape.t()) :: Data.data()
def convert(layer, data, shape) do
layer
|> data_type()
|> Data.convert(data, shape)
end
@spec forward_shape(t()) :: Shape.t() | nil
def forward_shape(layer) do
layer
|> input_shape()
|> case do
nil -> nil
shape -> [Shape.resolve_columns(shape), :any]
end
end
@spec backward_shape(t()) :: Shape.t() | nil
def backward_shape(layer) do
layer
|> input_shape()
|> case do
nil ->
nil
shape ->
[:any, Shape.resolve_rows(shape)]
end
end
end