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!


