Golfe de aprendizado de máquina: multiplicação


68

Gostaria de propor um tipo diferente de desafio de golfe a esta comunidade:

As redes neurais (artificiais) são modelos de aprendizado de máquina muito populares que podem ser projetados e treinados para aproximar qualquer função (geralmente desconhecida). Eles costumam ser usados ​​para resolver problemas altamente complexos que não sabemos como resolver algoritmicamente, como reconhecimento de fala, certos tipos de classificações de imagens, várias tarefas em sistemas de direção autônomos ... Para uma cartilha em redes neurais, considere isso excelente Artigo da Wikipedia .

Como este é o primeiro do que espero ser uma série de desafios de golfe de aprendizado de máquina, gostaria de manter as coisas o mais simples possível:

Na linguagem e estrutura de sua escolha, design e treinar uma rede neural que, dado (x1,x2) calcula seu produto x1x2 para todos os inteiros x1,x2 entre (e incluindo) 10 e 10 .

Objetivo de desempenho

Para se qualificar, seu modelo não pode se desviar em mais de 0.5 do resultado correto em qualquer uma dessas entradas.

Regras

Seu modelo

  • deve ser uma rede neural 'tradicional' (o valor de um nó é calculado como uma combinação linear ponderada de alguns dos nós de uma camada anterior seguida por uma função de ativação),
  • só pode usar as seguintes funções de ativação padrão:
    1. linear(x)=x ,
    2. softmax(x)i=exijexj ,
    3. seluα,β(x)={βx, if x>0αβ(ex1), otherwise ,
    4. softplus(x)=ln(ex+1) ,
    5. leaky-reluα(x)={x, if x<0αx, otherwise ,
    6. tanh(x) ,
    7. sigmoid(x)=exex+1 ,
    8. hard-sigmoid(x)={0, if x<2.51, if x>2.50.2x+0.5, otherwise ,
    9. ex
  • deve tomar (x1,x2) como um tupel / vetor / lista / ... de números inteiros ou flutuar como sua única entrada,
  • retorne a resposta como um número inteiro, float (ou um contêiner adequado, por exemplo, um vetor ou lista, que contenha essa resposta).

Sua resposta deve incluir (ou vincular a) todo o código necessário para verificar seus resultados - incluindo os pesos treinados do seu modelo.

Pontuação

A rede neural com o menor número de pesos (incluindo pesos de viés) vence.

Desfrutar!


9
Bem vindo ao site! Eu acho que esse desafio poderia se beneficiar bastante de uma definição mais robusta de uma rede neural. Existem algumas coisas aqui 1) Seria muito bom que você o declarasse em uma linguagem que ainda não implica o conhecimento de NNs. 2) Você realmente deve listar as funções de ativação em sua postagem em vez de vincular a uma fonte externa ( links externos podem mudar ou desaparecer).
Wheat Wizard

4
Podemos reutilizar pesos / usar camadas convolucionais? (Eu recomendo remover o bônus, pois ele não adiciona nada ao desafio e apenas distrai o objetivo principal.) Os pesos devem ser reais ou podem ser complexos?
flawr 2/07

4
Sua redação implica que os nós da camada 3 não podem usar entradas da camada 1. Custa um peso ter um nó da camada 2 simplesmente enviando f(x) = xpara encaminhar sua entrada?
Grimmy 02/07

4
Na coluna da direita, deve haver um link para o Sandbox, que foi criado expressamente para corrigir esse tipo de problema antes que a pergunta seja postada no site principal. E a filosofia da rede é que é melhor fechar uma pergunta, corrigi-la e reabri-la do que obter várias respostas que não farão sentido depois que a pergunta for corrigida ou restringirão fortemente as alterações que podem ser feitas na pergunta. .
Peter Taylor

7
De modo nenhum. Esses tipos de problemas são detectados pela experiência de muitos anos de ver outras pessoas cometendo o mesmo tipo de erro. Algumas ambiguidades escapam da caixa de areia, mas muitas outras são capturadas lá. E isso definitivamente teria sido pego, porque, como indicado no meu primeiro comentário, tivemos exatamente os mesmos problemas com uma questão da rede neural há dois meses.
Peter Taylor

Respostas:


37

21 13 11 9 pesos

Isso se baseia na identidade de polarização de formas bilineares que, no caso real unidimensional, se reduz à identidade polinomial:

xy=(x+y)2(xy)24

Então, y1apenas calcula [x+y, x-y]usando uma transformação linear, e y3é apenas o valor absoluto y1como etapa de pré-processamento para a próxima: A parte "difícil" é computar os quadrados que explicarei abaixo e, depois, apenas calcular a diferença e o dimensionamento que é novamente uma operação linear.

s{0,1,2,,20}0.5

approx_square(x)=i=02wiexp(0.0001ix)

W2=(wi)i0.02

function p = net(x)
% 9 weights
one = 1; 
mone =-1;
zero = 0;
fourth = 0.25;
W1 = [1e-4, 2e-4];
W2  = [-199400468.100687;99700353.6313757];
b2 = 99700114.4299316;
leaky_relu = @(a,x)max(a*x,x); 


% Linear
y0 = [one, one; one, mone] * x;

% Linear + ReLU
y1 = mone * y0;
y2 = [leaky_relu(zero, y0), leaky_relu(zero, y1)];

% Linear
y3 = y2 * [one; one];

% Linear + exp
y4 = exp(y3 * W1); 

% Linear + Bias
y5 =  y4 * W2 + b2;

% Linear
y6 = [one, mone]*y5;
p = y6 * fourth;

end

Experimente online!


Eu acho que seu código de verificação no rodapé do link TIO perde um aplicativo de abs. Mas está tudo bem de qualquer maneira.
Christian Sievers

@ChristianSievers Obrigado, atualizei o link TIO!
flawr 3/07

Não sou especialista em NN, por curiosidade, como é feita a contagem de peso? y0precisa 4, y1precisa 2, y3precisa 2, y4precisa 1, y5precisa 1 e y6precisa 2. São 12?
Margaret Bloom

3
@MargaretBloom Sim, isso é realmente um pouco incomum, mas o OP disse nos comentários que podemos reutilizar pesos e só precisamos contá-los uma vez, mesmo se usarmos o mesmo peso várias vezes. Portanto, todos os pesos que estou usando são definidos na primeira parte da função.
flawr 3/07

31

7 pesos

eps = 1e-6
c = 1 / (2 * eps * eps)

def f(A, B):
	e_s = exp(eps * A + eps * B)  # 2 weights, exp activation
	e_d = exp(eps * A - eps * B)  # 2 weights, exp activation
	return c * e_s + (-c) * e_d + (-1 / eps) * B  # 3 weights, linear activation

Experimente online!

ϵex1+x+x22

ABeϵA+ϵBeϵAϵB2ϵ2Bϵ

ϵepsc


11
Não tenho certeza se isso conta como uma 'rede neural tradicional' (regra 1), mas é óbvio que ela pode ser reformatada em uma, então não vejo problema nisso. Ótima solução!
Stefan Mesken 03/07

11
Você pode definir C = -B(1 peso) e, em seguida, ter [e_s, e_d] = conv([A,B,C], [eps, eps])(2 pesos) para economizar um peso :) (BTW: abordagem muito inteligente!)
flawr 03/07

(Esqueci de adicionar o exp)
flawr 03/07

4
Você pode até ficar muito mais baixo reutilizando os pesos - você não precisa contar o mesmo peso várias vezes.
flawr 3/07

2
@ flawr Esse é um bom truque, mas acho que os subsídios para convolução e reutilização de pesos nos comentários fazem com que isso seja um desafio tão diferente que vou manter essa resposta como está.
xnor 4/07

22

33 31 pesos

# Activation functions
sub hard { $_[0] < -2.5 ? 0 : $_[0] > 2.5 ? 1 : 0.2 * $_[0] + 0.5 }
sub linear { $_[0] }

# Layer 0
sub inputA() { $a }
sub inputB() { $b }

# Layer 1
sub a15() { hard(5*inputA) }

# Layer 2
sub a8()  { hard(-5*inputA + 75*a15 - 37.5) }

# Layer 3
sub aa()  { linear(-5*inputA + 75*a15 - 40*a8) }

# Layer 4
sub a4()  { hard(aa - 17.5) }

# Layer 5
sub a2()  { hard(aa - 20*a4 - 7.5) }

# Layer 6
sub a1()  { linear(0.2*aa - 4*a4 - 2*a2) }

# Layer 7
sub b15() { hard(0.25*inputB - 5*a15) }
sub b8()  { hard(0.25*inputB - 5*a8) }
sub b4()  { hard(0.25*inputB - 5*a4) }
sub b2()  { hard(0.25*inputB - 5*a2) }
sub b1()  { hard(0.25*inputB - 5*a1) }

# Layer 8
sub output() { linear(-300*b15 + 160*b8 + 80*b4 + 40*b2 + 20*b1 - 10*inputA) }

# Test
for $a (-10..10) {
        for $b (-10..10) {
                die if abs($a * $b - output) >= 0.5;
        }
}

print "All OK";

Experimente online!

Isso faz multiplicação longa em (sorta) binário e, portanto, retorna o resultado exato. Deveria ser possível aproveitar a janela de erro 0,5 para jogar mais um pouco, mas não sei como.

As camadas 1 a 6 decompõem a primeira entrada em 5 "bits". Por razões de golfe, não usamos binário real. O "bit" mais significativo tem peso -15 em vez de 16, e quando a entrada é 0, todos os "bits" são 0,5 (o que ainda funciona bem, pois preserva a identidade inputA = -15*a15 + 8*a8 + 4*a4 + 2*a2 + 1*a1).


11
Eu esperava que alguém criasse um algoritmo de multiplicação embutido e codificado por RNA. Mas não achei que seria a primeira resposta. Bem feito! (Também estou ansioso para ver se você conseguirá algo assim com o conjunto de dados MNIST ou algum outro problema de ML mais relástico: D.)
Stefan Mesken 02/07

14

43 pesos

As duas soluções postadas até agora têm sido muito inteligentes, mas suas abordagens provavelmente não funcionarão para tarefas mais tradicionais no aprendizado de máquina (como OCR). Por isso, gostaria de enviar uma solução 'genérica' (sem truques inteligentes) para esta tarefa que, esperançosamente, inspira outras pessoas a melhorar e ser sugada para o mundo do aprendizado de máquina:

Meu modelo é uma rede neural muito simples com 2 camadas ocultas construídas no TensorFlow 2.0 (mas qualquer outra estrutura também funcionaria):

model = tf.keras.models.Sequential([
tf.keras.layers.Dense(6, activation='tanh', input_shape=(2,)),
tf.keras.layers.Dense(3, activation='tanh'),
tf.keras.layers.Dense(1, activation='linear')
])

Como você pode ver, todas as camadas são densas (o que certamente não é o ideal), a função de ativação é tanh (o que pode realmente ser bom para esta tarefa), exceto a camada de saída que, devido à natureza dessa tarefa, possui uma função de ativação linear.

Existem 43 pesos:

  • (2+1)6=18
  • (6+1)3=21
  • (3+1)1=4

1010

Em seguida, eu os afinei - otimizando para o desvio máximo em qualquer uma das tarefas de multiplicação de números inteiros. Infelizmente, minhas anotações não mostram muita afinação que acabei fazendo, mas foi bem menor. Nas proximidades de 100 épocas nessas 441 amostras de treinamento, com um tamanho de lote de 441.

Estes são os pesos que acabei com:

[<tf.Variable 'dense/kernel:0' shape=(2, 6) dtype=float32, numpy=
 array([[ 0.10697944,  0.05394982,  0.05479664, -0.04538541,  0.05369904,
         -0.0728976 ],
        [ 0.10571832,  0.05576797, -0.04670485, -0.04466859, -0.05855528,
         -0.07390639]], dtype=float32)>,
 <tf.Variable 'dense/bias:0' shape=(6,) dtype=float32, numpy=
 array([-3.4242163, -0.8875816, -1.7694025, -1.9409281,  1.7825342,
         1.1364107], dtype=float32)>,
 <tf.Variable 'dense_1/kernel:0' shape=(6, 3) dtype=float32, numpy=
 array([[-3.0665843 ,  0.64912266,  3.7107112 ],
        [ 0.4914808 ,  2.1569328 ,  0.65417236],
        [ 3.461693  ,  1.2072319 , -4.181983  ],
        [-2.8746269 , -4.9959164 ,  4.505049  ],
        [-2.920127  , -0.0665407 ,  4.1409926 ],
        [ 1.3777553 , -3.3750365 , -0.10507642]], dtype=float32)>,
 <tf.Variable 'dense_1/bias:0' shape=(3,) dtype=float32, numpy=array([-1.376577  ,  2.8885336 ,  0.19852689], dtype=float32)>,
 <tf.Variable 'dense_2/kernel:0' shape=(3, 1) dtype=float32, numpy=
 array([[-78.7569  ],
        [-23.602606],
        [ 84.29587 ]], dtype=float32)>,
 <tf.Variable 'dense_2/bias:0' shape=(1,) dtype=float32, numpy=array([8.521169], dtype=float32)>]

0.44350433910=90.443504

Meu modelo pode ser encontrado aqui e você também pode experimentá-lo online! em um ambiente do Google Colab.


6

2 pesos

ϵ>0

xyeϵx+ϵy+eϵxϵyeϵxϵyeϵx+ϵy4ϵ2.

ϵ=0.01

{±ϵ,±(4ϵ2)1}{±ϵ,(4ϵ3)1}±(4ϵ2)1=±ϵ(4ϵ3)1. Como mencionei em um comentário acima, toda rede neural com pesos na precisão da máquina pode ser jogada em uma rede neural (enorme!) Com apenas dois pesos distintos. Eu apliquei este procedimento para escrever o seguinte código MATLAB:

function z=approxmultgolfed(x,y)

w1 = 0.1;   % first weight
w2 = -w1;   % second weight

k  = 250000;
v1 = w1*ones(k,1);
v2 = w2*ones(k,1);

L1 = w1*eye(2);
L2 = [ w1 w1; w2 w2; w1 w2; w2 w1 ];
L3 = [ v1 v1 v2 v2 ];
L4 = v1';

z = L4 * L3 * exp( L2 * L1 * [ x; y ] );

{±0.1}

Como fugir com apenas 1 peso (!)

{±0.1}0.10.1

0.1x=wwx,

w100.110.5

{±10k}10k

(Talvez devamos modificar a forma como os pesos reutilizados são pontuados em futuros desafios do golfe na rede neural.)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.