{"id":1133,"date":"2017-12-03T21:12:18","date_gmt":"2017-12-03T21:12:18","guid":{"rendered":"http:\/\/blog.tiran.info\/?p=1133"},"modified":"2017-12-03T21:12:18","modified_gmt":"2017-12-03T21:12:18","slug":"annore-6-algebre-lineaire-en-plsql","status":"publish","type":"post","link":"https:\/\/blog.tiran.stream\/?p=1133","title":{"rendered":"ANN\/ORE #6 &#8211; Alg\u00e8bre lin\u00e9aire en PL\/SQL"},"content":{"rendered":"<p style=\"text-align: justify;\">Dans l&rsquo;<a href=\"http:\/\/blog.tiran.info\/annore-5\" target=\"_blank\" rel=\"noopener\">article pr\u00e9c\u00e9dent<\/a>, on a mis en \u0153uvre manuellement avec R une forward-propagation \u00e0 partir de poids synaptiques pr\u00e9-calcul\u00e9s par le package neuralnet. Cela m&rsquo;a donn\u00e9 l&rsquo;id\u00e9e de tenter la m\u00eame chose avec une proc\u00e9dure PLSQL.<\/p>\n<p style=\"text-align: justify;\">Pour cela, j&rsquo;ai utilis\u00e9 le package standard <a href=\"https:\/\/docs.oracle.com\/database\/122\/ARPLS\/UTL_NLA.htm#ARPLS224\" target=\"_blank\" rel=\"noopener\">UTL_NLA<\/a> qui permet la r\u00e9alisation d&rsquo;op\u00e9rations d&rsquo;alg\u00e8bre lin\u00e9aire directement en base. Le package impl\u00e9mente une s\u00e9rie d&rsquo;op\u00e9rations des librairies sp\u00e9cialis\u00e9es <a href=\"https:\/\/fr.wikipedia.org\/wiki\/Basic_Linear_Algebra_Subprograms\" target=\"_blank\" rel=\"noopener\">BLAS<\/a> et <a href=\"https:\/\/fr.wikipedia.org\/wiki\/LAPACK\" target=\"_blank\" rel=\"noopener\">LAPACK<\/a>.<\/p>\n<p style=\"text-align: justify;\">En particulier, j&rsquo;ai utilis\u00e9 la proc\u00e9dure <a href=\"https:\/\/petewarden.com\/2015\/04\/20\/why-gemm-is-at-the-heart-of-deep-learning\/\" target=\"_blank\" rel=\"noopener\">blas_gemm<\/a> qui permet la r\u00e9alisation de <a href=\"https:\/\/fr.wikipedia.org\/wiki\/Produit_matriciel\" target=\"_blank\" rel=\"noopener\">produits matriciels<\/a>.<\/p>\n<p style=\"text-align: justify;\">En l&rsquo;occurrence, cela permet de multiplier la matrice obtenue en sortie d&rsquo;une couche par les poids synaptiques suivants. Dans l&rsquo;exemple MNIST, on d\u00e9marre avec une matrice de 10000*785 (10000 images du dataset de test contenant 784 pixel chacune + une valeur de biais). Les poids synaptiques associ\u00e9s \u00e0 la couche d&rsquo;entr\u00e9e (input layer) forment une matrice de 785*300.<\/p>\n<p style=\"text-align: justify;\">Leur multiplication permet d&rsquo;obtenir une matrice de 10000*300 aux valeurs de laquelle on applique la fonction d&rsquo;activation (sigmo\u00efde).<\/p>\n<p style=\"text-align: justify;\">Apr\u00e8s ajout d&rsquo;une valeur de biais, la matrice r\u00e9sultante 10000*301 est \u00e0 son tour multipli\u00e9e par les poids synaptiques associ\u00e9s \u00e0 la premi\u00e8re couche cach\u00e9e &#8211; soit 301*100. On parvient \u00e0 une matrice de 10000*100, \u00e0 laquelle on applique la fonction d&rsquo;activation et on ajoute une valeur de biais.<\/p>\n<p style=\"text-align: justify;\">Le r\u00e9sultat est ensuite multipli\u00e9 par les poids synaptiques associ\u00e9s \u00e0 la seconde couche cach\u00e9e &#8211; soit 101*10. On arrive alors \u00e0 une matrice de 10000*10. On y applique la fonction d&rsquo;activation pour obtenir le r\u00e9sultat final &#8211; \u00e0 savoir la couche de sortie (output layer).<\/p>\n<p style=\"text-align: justify;\">On voit donc bien l&rsquo;int\u00e9r\u00eat d&rsquo;une fonction de produit matriciel optimis\u00e9e!<\/p>\n<p style=\"text-align: justify;\">Le seul d\u00e9faut de l&rsquo;impl\u00e9mentation est, \u00e0 mon sens, que les proc\u00e9dures utilisent des variable de type UTL_NLA_ARRAY_DBL:<\/p>\n<pre class=\"brush: sql; ruler: true;\">SQL&gt; SELECT type_name, coll_type, upper_bound, elem_type_name\n  2    FROM DBA_COLL_TYPES\n  3   WHERE type_name = &#039;UTL_NLA_ARRAY_DBL&#039;;\n\nTYPE_NAME            COLL_TYPE            UPPER_BOUND ELEM_TYPE_NAME\n-------------------- -------------------- ----------- --------------------\nUTL_NLA_ARRAY_DBL    VARYING ARRAY            1000000 BINARY_DOUBLE\n\nSQL&gt;\n<\/pre>\n<p style=\"text-align: justify;\">Cela n\u00e9cessite donc \u00ab\u00a0l&rsquo;aplatissement\u00a0\u00bb (sous forme de varrays) des matrices multipli\u00e9es et ces derni\u00e8res <a href=\"https:\/\/docs.oracle.com\/database\/122\/ARPLS\/UTL_NLA.htm#ARPLS73299\" target=\"_blank\" rel=\"noopener\">sont limit\u00e9es \u00e0 un million d&rsquo;\u00e9l\u00e9ments<\/a> (cf. UPPER_BOUND ci-dessus).<\/p>\n<p style=\"text-align: justify;\">C&rsquo;est une limite \u00e9lev\u00e9e mais dans le cas pr\u00e9sent, la matrice d&rsquo;entr\u00e9e repr\u00e9sente 7.8 millions d&rsquo;entr\u00e9es (10000 images de 784 pixels). On devra donc r\u00e9aliser un lotissement pour ne pas d\u00e9passer la limite de 1e6.<\/p>\n<p style=\"text-align: justify;\">Le package PL\/SQL est disponible ici:\u00a0<a href=\"https:\/\/blog.tiran.stream\/wp-content\/uploads\/2017\/10\/FORWARD_PROPAGATION.txt\">FORWARD_PROPAGATION<\/a><\/p>\n<p style=\"text-align: justify;\">On peut alors tester l&rsquo;impl\u00e9mentation en PLSQL \u00e0 l&rsquo;aide des donn\u00e9es utilis\u00e9es dans le billet pr\u00e9c\u00e9dent (tables L1, L2 et L3 contenant les poids calcul\u00e9s par NeuralNet):<\/p>\n<pre class=\"brush: sql; ruler: true;\">SQL&gt; set timing on;\nSQL&gt; BEGIN\n  2      forward_propagation.score (&#039;MNIST_TEST_SET&#039;,\n  3                                 &#039;L1,L2,L3&#039;,\n  4                                 &#039;IMG_LBL, IMG_ID&#039;,\n  5                                 &#039;IMG_ID&#039;);\n  6  END;\n  7  \/\n\nPL\/SQL procedure successfully completed.\n\nElapsed: 00:00:45.98\nSQL&gt;\n<\/pre>\n<p style=\"text-align: justify;\">Les donn\u00e9es 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\u00e9diction correspond \u00e0 celui ayant la valeur la plus importante. Ci-dessous, le neurone O7 dispose de la valeur la plus importante pour la premi\u00e8re image (on pr\u00e9dit donc un 7), O2 dispose de la valeur la plus importante pour la seconde image (on pr\u00e9dit donc un 2):<\/p>\n<pre class=\"brush: sql; ruler: true;\">SQL&gt; set timing off;\nSQL&gt;\nSQL&gt; SELECT * FROM v_ann_out$ WHERE recid &lt; 3;\n\n     RECID         O0         O1         O2         O3         O4         O5         O6         O7      O8            O9\n---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------\n         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\n         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\n\nSQL&gt;\n<\/pre>\n<p style=\"text-align: justify;\">On peut alors comparer les valeurs pr\u00e9dites avec les labels du dataset de test:<\/p>\n<pre class=\"brush: sql; ruler: true;\">SQL&gt; WITH\n  2      pred\n  3      AS\n  4          (SELECT recid,\n  5                  CASE GREATEST (O0,O1,O2,O3,O4,O5,O6,O7,O8,O9)\n  6                      WHEN O0 THEN &#039;0&#039;\n  7                      WHEN O1 THEN &#039;1&#039;\n  8                      WHEN O2 THEN &#039;2&#039;\n  9                      WHEN O3 THEN &#039;3&#039;\n 10                      WHEN O4 THEN &#039;4&#039;\n 11                      WHEN O5 THEN &#039;5&#039;\n 12                      WHEN O6 THEN &#039;6&#039;\n 13                      WHEN O7 THEN &#039;7&#039;\n 14                      WHEN O8 THEN &#039;8&#039;\n 15                      WHEN O9 THEN &#039;9&#039;\n 16                  END\n 17                      pred_lbl\n 18             FROM v_ann_out$),\n 19      comparaison_pred_lbl\n 20      AS\n 21          (SELECT img_lbl,\n 22                  CASE WHEN pred_lbl = img_lbl THEN 1 ELSE 0 END correct_pred\n 23             FROM pred, mnist_test_set\n 24            WHERE recid = img_id)\n 25    SELECT img_lbl,\n 26           COUNT (*)                                     nb,\n 27           SUM (correct_pred)                            nb_correct_pred,\n 28           ROUND (100 * SUM (correct_pred) \/ COUNT (*), 1) correct_pred_pct\n 29      FROM comparaison_pred_lbl\n 30  GROUP BY img_lbl\n 31  ORDER BY 1;\n\nIMG_LBL                                          NB NB_CORRECT_PRED CORRECT_PRED_PCT\n---------------------------------------- ---------- --------------- ----------------\n0                                               980             966             98.6\n1                                              1135            1121             98.8\n2                                              1032             977             94.7\n3                                              1010             952             94.3\n4                                               982             934             95.1\n5                                               892             847               95\n6                                               958             924             96.5\n7                                              1028             987               96\n8                                               974             909             93.3\n9                                              1009             949             94.1\n\n10 rows selected.\n\nSQL&gt;\n<\/pre>\n<p style=\"text-align: justify;\">Cette approche peut s&rsquo;av\u00e9rer int\u00e9ressante lorsqu&rsquo;on ne dispose pas de l&rsquo;option Oracle Advanced Analytics.<\/p>\n<p style=\"text-align: justify;\">En effet, si on cr\u00e9\u00e9 le mod\u00e8le sans passer par ORE (Tensorflow, Theano etc&#8230;), une fois les poids synaptiques r\u00e9cup\u00e9r\u00e9s et charg\u00e9s en base, on peut r\u00e9aliser le scoring en base sans option additionnelle.<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Dans l&rsquo;article pr\u00e9c\u00e9dent, on a mis en \u0153uvre manuellement avec R une forward-propagation \u00e0 partir de poids synaptiques pr\u00e9-calcul\u00e9s par<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"colormag_page_container_layout":"default_layout","colormag_page_sidebar_layout":"default_layout","footnotes":""},"categories":[2,3,6],"tags":[],"class_list":["post-1133","post","type-post","status-publish","format-standard","hentry","category-ann","category-classification","category-oracle"],"_links":{"self":[{"href":"https:\/\/blog.tiran.stream\/index.php?rest_route=\/wp\/v2\/posts\/1133","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.tiran.stream\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.tiran.stream\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.tiran.stream\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.tiran.stream\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1133"}],"version-history":[{"count":0,"href":"https:\/\/blog.tiran.stream\/index.php?rest_route=\/wp\/v2\/posts\/1133\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.tiran.stream\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1133"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.tiran.stream\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1133"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.tiran.stream\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1133"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}