ANN/ORE #4 – Performance de neuralnet

On a vu dans le billet précédent que la construction du modèle avec le package neuralnet était considérablement plus rapide qu’avec nnet.

Cette différence m’a intrigué dans la mesure ou les deux packages doivent fonctionner – à priori – selon une logique similaire: gradient de descente, forward et back propagation…

En observant le profil de charge de la machine à l’aide de la commande top pendant la phase de construction du modèle, j’ai rapidement compris l’origine de la différence. En effet, il apparaît que l’implémentation de neuralnet est multithreadé contrairement à celle de nnet:

 

 

Dans un contexte ORE, cela signifie que le processus extproc instancie plusieurs threads qui opèrent en parallèle. Le champ intéressant ici est nlwp qui indique le nombre de threads du processus extproc:

 

 

 

Le script de collecte (rudimentaire!) suivant a donc été lancé pendant la construction des modèles avec nnet et neuralnet (fichiers extproc_nnet et extproc_neuralnet).

$ cat extproc_ps.sh
#!/bin/bash

>extproc.txt

while true
do
        date >>extproc.txt
        ps -C extproc -o pid,rss,vsz,time,nlwp >>extproc.txt
        sleep 30
done

$

 

Les données collectées ont été exploitées à l’aide d’une table externe:

SQL> CREATE OR REPLACE DIRECTORY d1 AS '/tmp';

Directory created.

SQL>
SQL> CREATE TABLE exttab_extproc
  2  (
  3      line VARCHAR2 (300)
  4  )
  5  ORGANIZATION EXTERNAL
  6      (TYPE oracle_loader
  7       DEFAULT DIRECTORY D1
  8       ACCESS PARAMETERS (
  9           RECORDS DELIMITED BY NEWLINE
 10       )
 11       LOCATION ('extproc_neuralnet.txt'));

Table created.

SQL>
SQL>
SQL> SELECT * FROM exttab_extproc FETCH FIRST 5 ROWS ONLY;

LINE
----------------------------------------------------------
Tue Sep 12 11:21:35 CEST 2017
  PID   RSS    VSZ     TIME NLWP
 9161 51024 362776 00:00:00    1
Tue Sep 12 11:22:05 CEST 2017
  PID   RSS    VSZ     TIME NLWP

SQL>

La mise en forme est ensuite réalisée à l’aide de d’expressions régulières et de CTE. Le résultat est présenté via une vue:

SQL> CREATE OR REPLACE VIEW evol_process_extproc
  2  AS
  3      WITH
  4          procres
  5          AS
  6              (SELECT ROWNUM                                        rn,
  7                      REGEXP_REPLACE (line, '[[:space:]]{2,}', ' ') line
  8                 FROM exttab_extproc
  9                WHERE line NOT LIKE '%PID%RSS%'),
 10          procres_evol
 11          AS
 12              (SELECT line,
 13                      TRIM (LEAD (line) OVER (PARTITION BY NULL ORDER BY rn))
 14                          next_line
 15                 FROM procres)
 16      SELECT TO_DATE (REPLACE (line, ' CEST ', ' '),
 17                      'Dy Mon DD HH24:MI:SS YYYY')
 18                 dt,
 19             REGEXP_SUBSTR (next_line,
 20                            '(.*?)([[:space:]]|$)',
 21                            1,
 22                            1,
 23                            NULL,
 24                            1)
 25                 pid,
 26             REGEXP_SUBSTR (next_line,
 27                            '(.*?)([[:space:]]|$)',
 28                            1,
 29                            2,
 30                            NULL,
 31                            1)
 32                 rss,
 33             REGEXP_SUBSTR (next_line,
 34                            '(.*?)([[:space:]]|$)',
 35                            1,
 36                            3,
 37                            NULL,
 38                            1)
 39                 vsz,
 40               (  TO_DATE (REGEXP_SUBSTR (next_line,
 41                                          '(.*?)([[:space:]]|$)',
 42                                          1,
 43                                          4,
 44                                          NULL,
 45                                          1),
 46                           'HH24:MI:SS')
 47                - TRUNC (TO_DATE ('00:00', 'HH24:MI')))
 48             * 86400
 49                 cpusec,
 50             REGEXP_SUBSTR (next_line,
 51                            '(.*?)([[:space:]]|$)',
 52                            1,
 53                            5,
 54                            NULL,
 55                            1)
 56                 nlwp
 57        FROM procres_evol
 58       WHERE next_line NOT LIKE '%2017';

View created.

SQL>
SQL> column NLWP format a5
SQL> column PID format a8
SQL> column RSS format a10
SQL> column VSZ format a10
SQL>
SQL> SELECT * FROM evol_process_extproc FETCH FIRST 10 ROWS ONLY;

DT                PID      RSS        VSZ            CPUSEC NLWP
----------------- -------- ---------- ---------- ---------- -----
12/09/17 11:21:35 9161     51024      362776              0 1
12/09/17 11:22:05 9161     51024      362776              0 1
12/09/17 11:22:35 9161     51024      362776              0 1
12/09/17 11:23:05 9161     51024      362776              0 1
12/09/17 11:23:35 9161     51024      362776              0 1
12/09/17 11:24:05 9161     200128     529856              7 1
12/09/17 11:24:35 9161     1803592    2132512            35 1
12/09/17 11:25:05 9161     2869720    4926496           164 25
12/09/17 11:25:35 9161     3903716    5960336           499 25
12/09/17 11:26:05 9161     3676520    5733096           785 25

10 rows selected.

SQL>

Finalement, la requête suivante (basée sur des fonctions analytiques) est utilisée pour calculer le nombre de threads actifs (champ AAT) par période d’échantillonnage:

SQL>   SELECT dt,
  2           nlwp,
  3           vsz,
  4           rss,
  5           ROUND (cpusec / elaps, 1) aat
  6      FROM (SELECT dt,
  7                   86400 * (dt - LAG (dt) OVER (PARTITION BY NULL ORDER BY dt))
  8                       elaps,
  9                   cpusec - LAG (cpusec) OVER (PARTITION BY NULL ORDER BY dt)
 10                       cpusec,
 11                   nlwp,
 12                   vsz,
 13                   rss
 14              FROM evol_process_extproc)
 15  ORDER BY dt
 16  FETCH FIRST 10 ROWS ONLY;

DT                NLWP  VSZ        RSS               AAT
----------------- ----- ---------- ---------- ----------
12/09/17 11:21:35 1     362776     51024
12/09/17 11:22:05 1     362776     51024               0
12/09/17 11:22:35 1     362776     51024               0
12/09/17 11:23:05 1     362776     51024               0
12/09/17 11:23:35 1     362776     51024               0
12/09/17 11:24:05 1     529856     200128             .2
12/09/17 11:24:35 1     2132512    1803592            .9
12/09/17 11:25:05 25    4926496    2869720           4.3
12/09/17 11:25:35 25    5960336    3903716          11.2
12/09/17 11:26:05 25    5733096    3676520           9.5

10 rows selected.

SQL>

On peut alors représenter à l’aide de ggplot2, le profil d’activité des threads lors de la construction du modèle:

> library(ROracle)
Loading required package: DBI
> ora = Oracle()
> cnx = dbConnect(ora, username="c##rafa", password="Password1#", dbname="//clorai2-scan:1521/pdb_hodba08")
> evol_cpu_mem <- dbGetQuery(cnx, "  SELECT dt,
+          nlwp,
+          vsz,
+          rss,
+          ROUND (cpusec / elaps, 1) aat
+     FROM (SELECT dt,
+                  86400 * (dt - LAG (dt) OVER (PARTITION BY NULL ORDER BY dt))
+                      elaps,
+                  cpusec - LAG (cpusec) OVER (PARTITION BY NULL ORDER BY dt)
+                      cpusec,
+                  nlwp,
+                  vsz,
+                  rss
+             FROM evol_process_extproc)
+ ORDER BY dt")
> 
> library(ggplot2)
> 
> evol_cpu_mem$couleur <- ifelse(evol_cpu_mem$AAT==0, "blue", 
+                                ifelse(evol_cpu_mem$AAT<0.7, "forestgreen",
+                                       ifelse(evol_cpu_mem$AAT == 1, "darkorange", "red")
+                                       )
+                                )
> 
> ggplot(evol_cpu_mem[-1,], aes(DT, AAT)) +
+   geom_point(color=evol_cpu_mem$couleur[-1]) +
+   geom_line(color=evol_cpu_mem$couleur[-1])  +   
+   xlab("Heure") + 
+   ylab("Nombre moyen de threads actifs") +   
+   theme_classic() +   
+   ggtitle("Activité des threads lors de la construction du modèle") +
+   theme(plot.title = element_text(hjust = 0.5)) +
+   theme(panel.grid.major = element_line(linetype = "dotted")) + 
+   scale_y_continuous(breaks = seq(0, 12),limits = c(0,11.5))
> 

Le graphique obtenu est colorisé en fonction du niveau d’activité des threads. On constate que le profil n’est pas uniforme, en particulier ce n’est pas la totalité de la construction du modèle qui donne lieu à une activité multithreadée :

  • en rouge, plusieurs threads du processus extproc sont actifs simultanément (une dizaine en moyenne pendant 25 minutes).
  • en orange, le processus extproc opère en mode monothread (~20 minutes).
  • en vert, le processus est peu actif (~7 minutes) mais une activité résiduelle subsiste. Il doit s’agir de la phase finale de communication entre extproc et l’instance Oracle.
  • en bleu, le processus est inactif.

A titre de comparaison, si j’avais tracé un graphique similaire pour la construction du modèle avec nnet, le profil aurait été très différent. L’implémentation n’étant pas multithreadée, on aurait eu une ligne de charge continue correspondant à l’activité du processus extproc monothreadé (équivalent de ligne orange ci-dessus).

Cela explique le gain de performance dans la construction de l’ANN avec le package neuralnet. Néanmoins, il subsiste le problème de réutilisation de ce modèle un fois qu’il a été sauvegardé dans un datastore ORE. Ce sera l’objet du prochain billet!

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

30 + = thirty two