ANN/ORE #6 – Algèbre linéaire en PL/SQL
Dans l’article précédent, on a mis en œuvre manuellement avec R une forward-propagation à partir de poids synaptiques pré-calculés par le package neuralnet. Cela m’a donné l’idée de tenter la même chose avec une procédure PLSQL.
Pour cela, j’ai utilisé le package standard UTL_NLA qui permet la réalisation d’opérations d’algèbre linéaire directement en base. Le package implémente une série d’opérations des librairies spécialisées BLAS et LAPACK.
En particulier, j’ai utilisé la procédure blas_gemm qui permet la réalisation de produits matriciels.
En l’occurrence, cela permet de multiplier la matrice obtenue en sortie d’une couche par les poids synaptiques suivants. Dans l’exemple MNIST, on démarre avec une matrice de 10000*785 (10000 images du dataset de test contenant 784 pixel chacune + une valeur de biais). Les poids synaptiques associés à la couche d’entrée (input layer) forment une matrice de 785*300.
Leur multiplication permet d’obtenir une matrice de 10000*300 aux valeurs de laquelle on applique la fonction d’activation (sigmoïde).
Après ajout d’une valeur de biais, la matrice résultante 10000*301 est à son tour multipliée par les poids synaptiques associés à la première couche cachée – soit 301*100. On parvient à une matrice de 10000*100, à laquelle on applique la fonction d’activation et on ajoute une valeur de biais.
Le résultat est ensuite multiplié par les poids synaptiques associés à la seconde couche cachée – soit 101*10. On arrive alors à une matrice de 10000*10. On y applique la fonction d’activation pour obtenir le résultat final – à savoir la couche de sortie (output layer).
On voit donc bien l’intérêt d’une fonction de produit matriciel optimisée!
Le seul défaut de l’implémentation est, à mon sens, que les procédures utilisent des variable de type UTL_NLA_ARRAY_DBL:
SQL> SELECT type_name, coll_type, upper_bound, elem_type_name 2 FROM DBA_COLL_TYPES 3 WHERE type_name = 'UTL_NLA_ARRAY_DBL'; TYPE_NAME COLL_TYPE UPPER_BOUND ELEM_TYPE_NAME -------------------- -------------------- ----------- -------------------- UTL_NLA_ARRAY_DBL VARYING ARRAY 1000000 BINARY_DOUBLE SQL>
Cela nécessite donc « l’aplatissement » (sous forme de varrays) des matrices multipliées et ces dernières sont limitées à un million d’éléments (cf. UPPER_BOUND ci-dessus).
C’est une limite élevée mais dans le cas présent, la matrice d’entrée représente 7.8 millions d’entrées (10000 images de 784 pixels). On devra donc réaliser un lotissement pour ne pas dépasser la limite de 1e6.
Le package PL/SQL est disponible ici: FORWARD_PROPAGATION
On peut alors tester l’implémentation en PLSQL à l’aide des données utilisées dans le billet précédent (tables L1, L2 et L3 contenant les poids calculés par NeuralNet):
SQL> set timing on;
SQL> BEGIN
2 forward_propagation.score ('MNIST_TEST_SET',
3 'L1,L2,L3',
4 'IMG_LBL, IMG_ID',
5 'IMG_ID');
6 END;
7 /
PL/SQL procedure successfully completed.
Elapsed: 00:00:45.98
SQL>
Les données de la couche de sortie sont accessibles via la vie V_ANN_OUT$. Pour chaque image, on dispose de 10 neurones de sortie et la prédiction correspond à celui ayant la valeur la plus importante. Ci-dessous, le neurone O7 dispose de la valeur la plus importante pour la première image (on prédit donc un 7), O2 dispose de la valeur la plus importante pour la seconde image (on prédit donc un 2):
SQL> set timing off;
SQL>
SQL> SELECT * FROM v_ann_out$ WHERE recid < 3;
RECID O0 O1 O2 O3 O4 O5 O6 O7 O8 O9
---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
1 5.0343E-21 2.5670E-20 5.5412E-21 1.9100E-23 4.0493E-09 2.8954E-22 4.9004E-17 1 1.0950E-27 8.4151E-12
2 2.3049E-16 1.3433E-14 1 1.1340E-17 5.3481E-18 1.8879E-14 7.4718E-11 1.2508E-17 3.1266E-17 5.0967E-24
SQL>
On peut alors comparer les valeurs prédites avec les labels du dataset de test:
SQL> WITH 2 pred 3 AS 4 (SELECT recid, 5 CASE GREATEST (O0,O1,O2,O3,O4,O5,O6,O7,O8,O9) 6 WHEN O0 THEN '0' 7 WHEN O1 THEN '1' 8 WHEN O2 THEN '2' 9 WHEN O3 THEN '3' 10 WHEN O4 THEN '4' 11 WHEN O5 THEN '5' 12 WHEN O6 THEN '6' 13 WHEN O7 THEN '7' 14 WHEN O8 THEN '8' 15 WHEN O9 THEN '9' 16 END 17 pred_lbl 18 FROM v_ann_out$), 19 comparaison_pred_lbl 20 AS 21 (SELECT img_lbl, 22 CASE WHEN pred_lbl = img_lbl THEN 1 ELSE 0 END correct_pred 23 FROM pred, mnist_test_set 24 WHERE recid = img_id) 25 SELECT img_lbl, 26 COUNT (*) nb, 27 SUM (correct_pred) nb_correct_pred, 28 ROUND (100 * SUM (correct_pred) / COUNT (*), 1) correct_pred_pct 29 FROM comparaison_pred_lbl 30 GROUP BY img_lbl 31 ORDER BY 1; IMG_LBL NB NB_CORRECT_PRED CORRECT_PRED_PCT ---------------------------------------- ---------- --------------- ---------------- 0 980 966 98.6 1 1135 1121 98.8 2 1032 977 94.7 3 1010 952 94.3 4 982 934 95.1 5 892 847 95 6 958 924 96.5 7 1028 987 96 8 974 909 93.3 9 1009 949 94.1 10 rows selected. SQL>
Cette approche peut s’avérer intéressante lorsqu’on ne dispose pas de l’option Oracle Advanced Analytics.
En effet, si on créé le modèle sans passer par ORE (Tensorflow, Theano etc…), une fois les poids synaptiques récupérés et chargés en base, on peut réaliser le scoring en base sans option additionnelle.