Trouver la plus grande racine d'un polynôme avec un réseau neuronal

11

Le défi

Trouver le plus petit réseau neuronal à action directe de telle sorte que, étant donné tout vecteur d'entrée en trois dimensions (une,b,c) avec des entrées entières dans [-dix,dix] , le réseau génère la racine la plus grande (c'est-à-dire "la plus positive") de polynôme X3+uneX2+bX+c avec une erreur strictement inférieure à 0,1 .

Admissibilité

La notion d'admissibilité dans mon précédent défi de golf de réseau neuronal semblait un peu restrictive, donc pour ce défi, nous utilisons une définition plus libérale du réseau neuronal à action directe:

Un neurone est une fonction ν:RnR qui est spécifiée par un vecteur wRn de poids , un biais bR et une fonction d'activation F:RR de la manière suivante:

ν(X): =F(wX+b),XRn.

Un réseau neuronal à action directe avec des nœuds d'entrée {1,,n} est fonction de (X1,,Xn)Rn qui peut être construit à partir d'une séquence (νk)k=n+1N de neurones, où chaque νk:Rk1R prend les entrées de (x1,,xk1)et produit un scalairexk . Compte tenucertains ensemble spécifiéS{1,,N}denoeuds de sortie, la sortie du réseauneurones est le vecteur(xk)kS .

Étant donné que les fonctions d'activation peuvent être réglées pour une tâche donnée, nous devons restreindre la classe des fonctions d'activation pour garder ce défi intéressant. Les fonctions d'activation suivantes sont autorisées:

  • Identité. f(t)=t

  • ReLU. f(t)=max(t,0)

  • SoftPlus. F(t)=ln(et+1)

  • Sigmoïde. F(t)=etet+1

  • Sinusoïde. F(t)=péchét

Dans l'ensemble, un réseau neuronal admissible est spécifié par des nœuds d'entrée, une séquence de neurones et des nœuds de sortie, tandis que chaque neurone est spécifié par un vecteur de poids, un biais et une fonction d'activation de la liste ci-dessus. Par exemple, le réseau neuronal suivant est admissible, bien qu'il n'atteigne pas l'objectif de performance de ce défi:

  • Noeuds d'entrée: {1,2}

  • Neurones: νk(X1,,Xk-1): =Xk-2+Xk-1 pour k{3,,dix}

  • Noeuds de sortie: {5,9,dix}

Ce réseau se compose de 8 neurones, chacun avec un biais nul et une activation d'identité. En termes, ce réseau calcule la séquence de Fibonacci généralisée générée par X1 et X2 , puis émet les 5e, 9e et 10e nombres de cette séquence, dans cet ordre.

Notation

Étant donné un nombre réel X avec une expansion décimale terminale, soit p(X) le plus petit entier non négatif p pour lequel dix-p|X|<1 , et que q(X) soit le plus petit entier non négatif q pour lequel dixqX est entier. Ensuite, nous disons que p(X)+q(X) est la précision de X .

Par exemple, X=1,001 a une précision de 4 , tandis que X=0 a une précision de 0 .

Votre score est la somme des précisions des poids et des biais dans votre réseau neuronal.

(Par exemple, l'exemple ci-dessus a un score de 16.)

Vérification

Alors que les racines peuvent être exprimées en termes de formule cubique , la plus grande racine est peut-être la plus facilement accessible par des moyens numériques. Suivant la suggestion de @ xnor, j'ai calculé la racine la plus grande pour chaque choix d'entiers une,b,c[-dix,dix] , et les résultats peuvent être trouvés ici . Chaque ligne de ce fichier texte est de la forme a,b,c,root. Par exemple, la première ligne indique que la racine la plus grande de X3-dixX2-dixX-dix est environ 10.99247140445449 .

Edit: le fichier d'origine que j'ai publié comportait des erreurs dans les cas où le polynôme présentait une racine multiple. La version actuelle devrait être exempte de telles erreurs.

Dustin G. Mixon
la source
3
Que se passe-t-il dans le polynôme d'entrée n'a pas de racines réelles, comme quand a=0et le quadratique a deux racines complexes?
xnor
Je pense que la solution la plus propre serait de dire que l'entrée sera anon nulle, ou même juste 1. De plus, je recommanderais de mettre dans certains cas de test, en donnant les racines à une haute précision afin que nous puissions vérifier que les nôtres sont à 0,1. Il serait également bon d'avoir des sorties pour toutes les entrées possibles, probablement dans un lien, car c'est beaucoup pour la publication.
xnor
1
J'aime les nouvelles règles de recevabilité. Il semble que la nouvelle fonction sinusoïdale soit extrêmement exploitable. J'ai une preuve sommaire qu'une fonction de la forme x -> a * sin(b * softplus(x) + c)peut surcharger n'importe quel nombre fini de points de données avec un entier xà une précision arbitraire en utilisant une fréquence extrêmement grande et précise.
xnor
1
Je ne sais pas à quel point cela serait utile (pour les défis futurs): dans la théorie des nombres, nous utilisons des fonctions de hauteur pour mesurer la complexité d'un nombre. Par exemple, la hauteur naïve d'une fraction (réduite) est donnée par h = log max { | p | , | q | } (et il y a beaucoup de généralisations). Peut-être que cela pourrait être utilisé comme mesure alternative. p/qh=logmax{|p|,|q|}
flawr
1
@ DustinG.Mixon Je ne sais pas si vous êtes au courant, mais nous avons un bac à sable pour publier des brouillons et discuter des détails d'un défi ainsi que d'un chat .
flawr

Réponses:

6

14674000667 5436050 5403448 10385 5994 4447
3806 précision totale

Pour une ligne de base, j'ai étudié l'approche suivante: Sélectionnez M,δ,ϵ>0 telle sorte que si nous échantillonnons le polynôme p(x)=x3+ax2+bx+c à

S:={M,M+δ,M+2δ,,M},

alors le plus grand point d'échantillon sS satisfaisant p(s)<ϵ existe nécessairement et réside nécessairement à 0.1 de la plus grande racine de p . On peut montrer que pour notre collection de polynômes, on peut prendre M=11 , δ=0.1 et ϵ=104 .

Pour concevoir un réseau de neurones qui implémente cette logique, nous commençons par une couche de neurones que l' échantillon polynôme sur S . Pour chaque sS , on prend

x1,s=s2a+sb+1c+s3.

Ensuite, nous identifions ceux qui sont inférieurs à ϵ=104 . Il s'avère que pour sS , il considère que p(s)<104 uniquement si p(s)0 . À ce titre, nous pouvons utiliser les activations relu pour identifier exactement nos échantillons:

relu(104t)relu(t)104={1if t00if t104.

Nous l'implémentons avec quelques couches de neurones:

x2,s=relu(1x1,s+104),x3,s=relu(1x1,s),x4,s=104x2,s104x3,s.

À ce stade, nous avons x4,s=1 lorsque p(s)<104 , et sinon x4,s=0 . Rappelons que l'on cherche le plus grand s pour lequel x4,s=1 . À cette fin, nous étiquetons x4,M comme x5,M (pour la commodité de la notation), et pour chaque k1, nous définissons itérativement

x5,Mkδ=1x4,Mkδ+2x5,M(k1)δ=j=0k2kjx4,Mjδ.

Grâce à cette transformation, chaque x5,s est un entier non négatif, et s est l'unique s pour lequel x5,s=1 . On peut maintenant identifier s par une autre application d'activations relu:

relu(t2)2relu(t1)+t={1if t=10if tZ0{1}.

De manière explicite, nous définissons les neurones par

x6,s=relu(1x5,s2),x7,s=relu(1x5,s1),x8,s=1x6,s2x7,s+1x5s.

Alors x8,s=1 si s=s et sinon x8,s=0 . Nous les combinons linéairement pour produire notre nœud de sortie:

x9=sSsx8,s=s.

Pour le score, chaque couche a des neurones avec différents niveaux de précision: (1) 6+3+1+9=19 , (2) 1+4=5 , (3) 1 , (4) 5+5=dix , (5) 1+1=2 , (6) 1+1=2 , (7) 1+1=2 , (8) 1+1+1=3 , (9) 3|S|. De plus, toutes les couches sauf deux ont|S|=221 neurones; la couche 5 a|S|-1 neurone et la couche 9 a1 neurone.

Edit: Améliorations: (1) Nous pouvons échantillonner le polynôme beaucoup plus efficacement en utilisant des différences finies. (2) Nous pouvons contourner les couches 2 à 4 en utilisant plutôt une activation sigmoïde. (3) Les problèmes de débordement dans la couche 5 peuvent être évités (et les couches suivantes peuvent être combinées) en appliquant plus soigneusement les activations de relu. (4) La somme finale est moins chère avec addition par parties .

Ce qui suit est le code MATLAB. Pour être clair, precest une fonction (trouvée ici ) qui calcule la précision d'un vecteur de poids ou de biais.

function sstar = findsstar2(a,b,c)

relu = @(x) x .* (x>0);

totprec = 0;

% x1 samples the polynomial on -11:0.1:11
x1=[];
for s = -11:0.1:11
    if length(x1) < 5
        w1 = [s^2 s 1];
        b1 = s^3;
        x1(end+1,:) = w1 * [a; b; c] + b1;
        totprec = totprec + prec(w1) + prec(b1);
    else
        w1 = [-1 4 -6 4];
        x1(end+1,:) = w1 * x1(end-3:end,:);
        totprec = totprec + prec(w1);
    end
end

% x4 indicates whether the polynomial is nonpositive
w4 = -6e5;
b4 = 60;
x4=[];
for ii=1:length(x1)
    x4(end+1) = sigmf(w4 * x1(ii) + b4, [1,0]);
    totprec = totprec + prec(w4) + prec(b4);
end

% x6 indicates which entries are less than or equal to sstar
x5 = zeros(size(x1));
x6 = zeros(size(x1));
x5(end) = 0;
x6(end) = 0;
for ii = 1:length(x5)-1
    w5 = [-1 -1];
    b5 = 1;
    x5(end-ii) = relu(w5 * [x4(end-ii); x6(end-ii+1)] + b5);
    totprec = totprec + prec(w5) + prec(b5);
    w6 = -1;
    b6 = 1;
    x6(end-ii) = w6 * x5(end-ii) + b6;
    totprec = totprec + prec(w6) + prec(b6);
end

% a linear combination produces sstar
w7 = 0.1*ones(1,length(x1));
w7(1) = -11;
sstar = w7 * x6;

%disp(totprec) % uncomment to display score

end
Dustin G. Mixon
la source
2

53268 29596 29306 précision totale

Une communication privée avec @ A.Rex a conduit à cette solution, dans laquelle nous construisons un réseau neuronal qui mémorise les réponses. L'idée centrale est que chaque fonction F:SR sur un ensemble fini S bénéficie de la décomposition

F(X)=sSF(s){1si X=s0autre}.

F

relu(t-1)-2relu(t)+relu(t+1)={1si t=00si tZ{0}.

Ce qui suit est une implémentation MATLAB de cette approche. Pour être clair, roots.txtest le fichier racine affiché ci-dessus (trouvé ici ), et precest une fonction (trouvée ici ) qui calcule la précision totale d'un vecteur de poids ou de biais.

Edit 1: Deux améliorations par rapport à l'original: (1) J'ai factorisé certains neurones des boucles for. (2) J'ai implémenté «l' intégration de Lebesgue » dans la somme finale en combinant d'abord les termes du même ensemble de niveaux. De cette façon, je ne paie pour la valeur de précision supérieure d'une sortie qu'une seule fois à chaque niveau défini. En outre, il est sûr d'arrondir les sorties au cinquième près par le théorème de la racine rationnelle .

Edit 2: améliorations mineures supplémentaires: (1) J'ai factorisé plus de neurones d'une boucle for. (2) Je ne me donne pas la peine de calculer le terme dans la somme finale pour laquelle la sortie est déjà nulle.

function r = approxroot(a,b,c)

relu = @(x)x .* (x>0);

totalprec=0;

% x4 indicates which entry of (-10:10) is a
w1 = ones(21,1);   b1 = -(-10:10)'-1;    x1 = relu(w1 * a + b1);
w2 = ones(21,1);   b2 = -(-10:10)';      x2 = relu(w2 * a + b2);
w3 = ones(21,1);   b3 = -(-10:10)'+1;    x3 = relu(w3 * a + b3);
w4p1 = ones(21,1); w4p2 = -2*ones(21,1); w4p3 = ones(21,1);
x4 = w4p1 .* x1 + w4p2 .* x2 + w4p3 .* x3;
totalprec = totalprec + prec(w1) + prec(w2) + prec(w3) + prec(b1) + prec(b2) + prec(b3) + prec(w4p1) + prec(w4p2) + prec(w4p3);

% x8 indicates which entry of (-10:10) is b
w5 = ones(21,1);   b5 = -(-10:10)'-1;    x5 = relu(w5 * b + b5);
w6 = ones(21,1);   b6 = -(-10:10)';      x6 = relu(w6 * b + b6);
w7 = ones(21,1);   b7 = -(-10:10)'+1;    x7 = relu(w7 * b + b7);
w8p1 = ones(21,1); w8p2 = -2*ones(21,1); w8p3 = ones(21,1);
x8 = w8p1 .* x5 + w8p2 .* x6 + w8p3 .* x7;
totalprec = totalprec + prec(w5) + prec(w6) + prec(w7) + prec(b5) + prec(b6) + prec(b7) + prec(w8p1) + prec(w8p2) + prec(w8p3);

% x12 indicates which entry of (-10:10) is c
w9 = ones(21,1);    b9 = -(-10:10)'-1;     x9 = relu(w9 * c + b9);
w10 = ones(21,1);   b10 = -(-10:10)';      x10 = relu(w10 * c + b10);
w11 = ones(21,1);   b11 = -(-10:10)'+1;    x11 = relu(w11 * c + b11);
w12p1 = ones(21,1); w12p2 = -2*ones(21,1); w12p3 = ones(21,1);
x12 = w12p1 .* x9 + w12p2 .* x10 + w12p3 .* x11;
totalprec = totalprec + prec(w9) + prec(w10) + prec(w11) + prec(b9) + prec(b10) + prec(b11) + prec(w12p1) + prec(w12p2) + prec(w12p3);

% x15 indicates which row of the roots file is relevant
x15=[];
for aa=-10:10
    w13 = 1;
    b13 = -2;
    x13 = w13 * x4(aa+11) + b13;
    totalprec = totalprec + prec(w13) + prec(b13);
    for bb=-10:10
        w14p1 = 1;
        w14p2 = 1;
        x14 = w14p1 * x13 + w14p2 * x8(bb+11);
        totalprec = totalprec + prec(w14p1) + prec(w14p2);
        for cc=-10:10
            w15p1 = 1;
            w15p2 = 1;
            x15(end+1,1) = relu(w15p1 * x14 + w15p2 * x12(cc+11));
            totalprec = totalprec + prec(w15p1) + prec(w15p2);
        end
    end
end

% r is the desired root, rounded to the nearest fifth
A = importdata('roots.txt');
outputs = 0.2 * round(5 * A(:,4)');
uniqueoutputs = unique(outputs);
x16 = [];
for rr = uniqueoutputs
    if rr == 0
        x16(end+1,:) = 0;
    else
        lvlset = find(outputs == rr);
        w16 = ones(1,length(lvlset));
        x16(end+1,:) = w16 * x15(lvlset);
        totalprec = totalprec + prec(w16);
    end
end
w17 = uniqueoutputs;
r = w17 * x16;
totalprec = totalprec + prec(w17);

%disp(totalprec) % uncomment to display score

end
Dustin G. Mixon
la source