Article
Sylvain Guilbaud · Avr 25
## TLS, qu'est-ce que c'est ?
La TLS (Transport Layer Security ou "Sécurité de la couche de transport"), qui succède à SSL (Secure Sockets Layer ou "Couche de sockets sécurisée"), fournit de la sécurité (c'est-à-dire le chiffrement et l'authentification) sur une connexion TCP/IP. Si vous avez déjà remarqué le "s" sur les URLs "https" vous avez reconnu une connexion HTTP "sécurisée" par SSL/TLS. Dans le passé, seules les pages de connexion/autorisation sur le web utilisaient TLS, mais dans l'environnement hostile d'Internet d'aujourd'hui, les meilleures pratiques indiquent que nous devrions sécuriser toutes les connexions avec TLS.
## Pourquoi utiliser TLS?
Alors, pourquoi mettre en œuvre TLS pour les connexions HL7 ? Alors que les violations de données, les rançongiciels et les vulnérabilités sont de plus en plus fréquents, chaque mesure que vous prenez pour renforcer la sécurité de ces précieuses sources de données devient plus cruciale. La TLS est une méthode éprouvée et bien comprise pour protéger les données en transit.
TLS fournit deux fonctionnalités principales qui nous sont bénéfiques : 1) le chiffrement et 2) l'authentification.
### Chiffrement
Le chiffrement transforme les données en cours de transfert de sorte que seules les deux parties engagées dans la communication peuvent lire/comprendre les informations échangées. Dans la plupart des cas, seules les applications impliquées dans la connexion TLS peuvent interpréter les données transférées. Cela signifie que les acteurs malveillants opérant sur les serveurs ou réseaux de communication ne pourront pas lire les données, même s'ils parviennent à capturer les paquets TCP bruts à l'aide d'un renifleur de paquets (wiretap, wireshark, tcpdump, etc.).

### Authentification
L'authentification garantit que chaque partie communique avec la partie prévue et non avec un imposteur. En s'appuyant sur l'échange de certificats (et la vérification de la preuve de propriété associée qui s'est produite lors d'un handshake TLS), lorsque vous utilisez TLS, vous pouvez être sûr que vous échangez des données avec une partie de confiance. Plusieurs attaques consistent à tromper un serveur pour qu'il communique avec un acteur malveillant en redirigeant le trafic vers le mauvais serveur (par exemple, l'emploi de DNS et d'ARP poisoning) Lorsque TLS est impliqué, les imposteurs doivent non seulement rediriger le trafic, mais aussi voler les certificats et les clés appartenant à la partie de confiance.
L'authentification protège non seulement contre les attaques intentionnelles de pirates informatiques ou de acteurs malveillants, mais aussi contre les erreurs de configuration accidentelles qui pourraient envoyer des données vers le ou les mauvais systèmes. Par exemple, si vous attribuez accidentellement l'adresse IP d'une connexion HL7 à un serveur qui n'utilise pas le certificat attendu, la vérification de la négociation TLS échouera avant l'envoi de données vers ce mauvais serveur.
#### Vérification d'hôte
Lors de la vérification, les clients ont la possibilité d'effectuer une vérification d'hôte. Cette vérification compare l'adresse IP ou le nom d'hôte utilisé dans la connexion avec les adresses IP et les noms d'hôte intégrés dans le certificat. Si cette vérification est activée et que l'adresse IP/l'hôte de la connexion ne correspond pas à une adresse IP/un hôte figurant dans le certificat, le handshake TLS échouera. Vous trouverez les adresses IP et les noms d'hôte dans les champs X.509 « Subject » et « Subject Alternative Name » présentés ci-dessous.
#### Preuve de la propriété d'un certificat avec une clé privée
Pour prouver la propriété des certificats échangés avec TLS, vous devez également avoir accès à la clé privée liée à la clé publique intégrée au certificat. Nous ne discuterons pas de la cryptographie employée pour la preuve de propriété avec une clé privée, mais vous devez savoir que l'accès à la clé privée de votre certificat est nécessaire pendant le handshake TLS.
#### TLS mutuel
Pour la plupart des connexions https établies par votre navigateur web, seul le certificat d'authenticité du serveur web est vérifié. Normalement, les serveurs web n'authentifient pas le client avec des certificats. Au lieu de cela, la plupart des serveurs web s'appuient sur l'authentification du client au niveau de l'application (formulaires de connexion, cookies, mots de passe, etc.).
Avec HL7, il est préférable que les deux côtés de la connexion soient authentifiés. Lorsque les deux côtés sont authentifiés, on parle de «TLS mutuel». Avec le TLS mutuel, le serveur et le client échangent leurs certificats et l'autre côté vérifie les certificats fournis avant de poursuivre la connexion et l'échange de données.
## X.509 Certificats
### X.509 Champs du certificat
Pour fournir le cryptage et l'authentification, les informations sur la clé publique et l'identité de chaque partie sont échangées dans des certificats [X.509] (https://en.wikipedia.org/wiki/X.509). Vous trouverez ci-dessous certains champs courants d'un certificat X.509 qui nous intéresseront:
- `Serial Number`: numéro unique à un CA qui identifie ce certificat spécifique
- `Subject Public Key Info`: clé publique du propriétaire
- `Subject`: nom distinctif (DN) du serveur/service représenté par ce certificat
- Ce champ peut être vide si des "Subject Alternative Names" (noms alternatifs du sujet) sont fournis.
- `Issuer`: nom distinctif (DN) du CA qui a émis/signé ce certifica
- `Validity Not Before`: date de mise en vigueur de ce certificat
- `Validity Not After`: date d'expiration de ce certificat
- `Basic Constraints`: indique s'il s'agit d'un CA ou non
- `Key Usage`: l'utilisation prévue de la clé publique fournie par ce certificat
- Valeurs d'exemple: digitalSignature, contentCommitment, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign, cRLSign, encipherOnly, decipherOnly
- `Extended Key Usage`: utilisations supplémentaires prévues de la clé publique fournie par ce certificat
- Valeurs d'exemple: serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, OCSPSigning, ipsecIKE, msCodeInd, msCodeCom, msCTLSign, msEFS
- Pour les connexions TLS mutuelles, les deux modes d'utilisation serverAuth et clientAuth sont nécessaires.
- `Subject Key Identifier`: identifie la clé publique du sujet fournie par ce certificat
- `Authority Key Identifier`: identifie la clé publique du fournisseur utilisée pour vérifier ce certificat
- `Subject Alternative Name`: contient un ou plusieurs noms alternatifs pour ce sujet
- Les noms `DNS` et les adresses `IP` sont des noms alternatifs fréquemment fournis dans ce champ.
- `Subject Alternative Name` est parfois abrégé en `SAN`.
- Le nom DNS ou l'adresse IP utilisés dans la connexion doivent figurer dans cette liste ou dans le `Common Name` du `Subject` pour que la vérification de l'hôte soit réussie.
#### Noms distingués
Les champs `Subject` (Sujet) et `Issuer` (Émetteur) d'un certificat X.509 sont définis comme des `Distinguished Names` (DN, Noms Distingués). Les noms distingués sont constitués de plusieurs attributs, chaque attribut ayant le format `=`. Voici une liste non exhaustive des attributs courants que l'on trouve dans les champs `Subject` et `Issuer`
| Abréviation | Nom | Exemple | Remarques |
|--------------|--------------------|-----------------------|-----------------------------|
|CN |Nom commun | CN=server1.domain.com | Le nom de domaine complet (FQDN) d'un serveur/service |
|C |Pays | C=US | Code pays à deux caractères
|ST |État (ou province) | ST=Massachusetts | Nom complet de l'état/province
|L |Localité | L=Cambridge | Ville, région, etc.
|O |Organisation | O=Best Corporation | Nom de l'organisation
|OU |Unité opérationnelle| OU=Finance | Départment, division, etc.
Selon les exemples du tableau ci-dessus, le DN complet pour cet exemple serait `C=US, ST=Massachusetts, L=Cambridge, O=Best Corporation, OU=Finance, CN=server1.domain.com`
Notez que le `Common Name` (nom commun) trouvé dans le `Subject` (sujet) est utilisé lors de la vérification de l'hôte et correspond normalement au nom de domaine complet (FQDN) du serveur ou du service associé au certificat. Les `Subject Alternative Names` (noms alternatifs du sujet) du certificat peuvent également être utilisés lors de la vérification de l'hôte.
#### Expiration du certificat
Les champs `Validity Not Before` (Validité avant la date) et `Validity Not After` (Validité après la date) du certificat fournissent une plage de dates entre lesquelles le certificat est valide
Normalement, les certificats feuille ont une validité d'un ou deux ans (bien que les sites Web soient encouragés à réduire leurs délais d'expiration à des périodes beaucoup plus courtes). Les autorités de certification ont généralement un délai d'expiration de plusieurs années.
L'expiration des certificats est une fonctionnalité TLS nécessaire mais peu pratique. Avant d'ajouter TLS à vos connexions HL7, assurez-vous d'avoir un plan pour remplacer les certificats avant leur expiration. Une fois qu'un certificat expire, vous ne pourrez plus établir de connexion TLS à l'aide de celui-ci.
### Formats de certificat X.509
Les champs de ces certificats X.509 (ainsi que d'autres) sont structurés suivant le format [ASN.1](https://en.wikipedia.org/wiki/ASN.1) et généralement enregistrés dans l'un des formats de fichier suivants :
- [DER] (https://en.wikipedia.org/wiki/Distinguished_Encoding_Rules) (format binaire)
- [PEM] (https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) (base64)
Exemple d'encodage PEM d'un certificat X.509:
```
-----BEGIN CERTIFICATE-----
MIIEVTCCAz2gAwIBAgIQMm4hDSrdNjwKZtu3NtAA9DANBgkqhkiG9w0BAQsFADA7
MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMQww
CgYDVQQDEwNXUjIwHhcNMjUwMTIwMDgzNzU0WhcNMjUwNDE0MDgzNzUzWjAZMRcw
FQYDVQQDEw53d3cuZ29vZ2xlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA
BDx/pIz8HwLWsWg16BG6YqeIYBGof9fn6z6QwQ2v6skSaJ9+0UaduP4J3K61Vn2v
US108M0Uo1R1PGkTvVlo+C+jggJAMIICPDAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0l
BAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU3rId2EvtObeF
NL+Beadr56BlVZYwHwYDVR0jBBgwFoAU3hse7XkV1D43JMMhu+w0OW1CsjAwWAYI
KwYBBQUHAQEETDBKMCEGCCsGAQUFBzABhhVodHRwOi8vby5wa2kuZ29vZy93cjIw
JQYIKwYBBQUHMAKGGWh0dHA6Ly9pLnBraS5nb29nL3dyMi5jcnQwGQYDVR0RBBIw
EIIOd3d3Lmdvb2dsZS5jb20wEwYDVR0gBAwwCjAIBgZngQwBAgEwNgYDVR0fBC8w
LTAroCmgJ4YlaHR0cDovL2MucGtpLmdvb2cvd3IyLzlVVmJOMHc1RTZZLmNybDCC
AQMGCisGAQQB1nkCBAIEgfQEgfEA7wB2AE51oydcmhDDOFts1N8/Uusd8OCOG41p
wLH6ZLFimjnfAAABlIMTadcAAAQDAEcwRQIgf6SEH+xVO+nGDd0wHlOyVTbmCwUH
ADj7BJaSQDR1imsCIQDjJjt0NunwXS4IVp8BP0+1sx1BH6vaxgMFOATepoVlCwB1
AObSMWNAd4zBEEEG13G5zsHSQPaWhIb7uocyHf0eN45QAAABlIMTaeUAAAQDAEYw
RAIgBNtbWviWZQGIXLj6AIEoFKYQW4pmwjEfkQfB1txFV20CIHeouBJ1pYp6HY/n
3FqtzC34hFbgdMhhzosXRC8+9qfGMA0GCSqGSIb3DQEBCwUAA4IBAQCHB09Uz2gM
A/gRNfsyUYvFJ9J2lHCaUg/FT0OncW1WYqfnYjCxTlS6agVUPV7oIsLal52ZfYZU
lNZPu3r012S9C/gIAfdmnnpJEG7QmbDQZyjF7L59nEoJ80c/D3Rdk9iH45sFIdYK
USAO1VeH6O+kAtFN5/UYxyHJB5sDJ9Cl0Y1t91O1vZ4/PFdMv0HvlTA2nyCsGHu9
9PKS0tM1+uAT6/9abtqCBgojVp6/1jpx3sx3FqMtBSiB8QhsIiMa3X0Pu4t0HZ5j
YcAkxtIVpNJ8h50L/52PySJhW4gKm77xNCnAhAYCdX0sx76eKBxB4NqMdCR945HW
tDUHX+LWiuJX
-----END CERTIFICATE-----
```
Comme vous pouvez le voir, l'encodage PEM ajoute -----BEGIN CERTIFICATE----- et -----END CERTIFICATE----- aux données ASN.1 du certificat encodées en base64.
## Établir la confiance avec les autorités de certification
Sur l'Internet ouvert, il serait impossible pour votre navigateur Web de connaître et de faire confiance au certificat de chaque site Web. Il y en a tout simplement trop!
Pour contourner ce problème, votre navigateur Web délègue la confiance à un ensemble prédéterminé d'autorités de certification (AC). Les autorités de certification sont des entités qui vérifient qu'une personne demandant un certificat pour un site Web ou un domaine est bien propriétaire et responsable du serveur, du domaine ou des activités commerciales associés à la demande de certificat. Une fois que l'autorité de certification a vérifié un propriétaire, elle est en mesure d'émettre le certificat demandé.
Chaque autorité de certification est représentée par un ou plusieurs certificats X.509. Ces certificats CA sont utilisés pour signer tous les certificats émis par la CA. Si vous regardez dans le champ `Issuer` (Émetteur) d'un certificat X.509, vous trouverez une référence au certificat CA qui a créé et signé ce certificat.
Si un certificat est créé sans autorité de certification, il est appelé certificat *auto-signé*. Vous savez qu'un certificat est auto-signé si les champs `Subject` (Sujet) et `Issuer` (Émetteur) du certificat sont identiques.
En général, la CA crée un certificat `root` (racine) auto-signé avec une longue fenêtre d'expiration. Ce certificat racine est ensuite utilisé pour générer quelques autorités de certification `intermédiaires`, qui ont une fenêtre d'expiration légèrement plus courte. La CA racine sera sécurisée et rarement utilisée après la création des CA intermédiaires. Les CA intermédiaires seront utilisées pour émettre et signer les certificats `leaf` (feuille) au quotidien.
Les CA intermédiaires sont créées au lieu d'utiliser directement la CA racine afin de minimiser l'impact en cas de violation ou de mauvaise gestion d'un certificat. Si une seule CA intermédiaire est compromise, l'entreprise aura toujours les autres CA disponibles pour continuer à fournir le service.
### Chaînes de certificats
Un certificat de connexion et tous les certificats CA impliqués dans l'émission et la signature de ce certificat peuvent être organisés en une structure appelée *chaîne de certificats*. Cette chaîne de certificats (décrite ci-dessous) sera utilisée pour vérifier et approuver le certificat de connexion.
Si vous suivez le certificat feuille d'une connexion jusqu'à la CA émettrice (en utilisant le champ `Issuer`) puis, à partir de cette CA, jusqu'à son émetteur (et ainsi de suite, jusqu'à ce que vous atteigniez un certificat racine auto-signé), vous aurez parcouru la chaîne de certificats.

### Faire confiance à un certificat
Votre navigateur Web et votre système d'exploitation conservent généralement une liste d'autorités de certification approuvées. Lors de la configuration d'une interface HL7 ou d'une autre application, vous dirigerez probablement votre interface vers un fichier CA-bundle contenant une liste de CA approuvées. Ce fichier contiendra généralement une liste d'un ou plusieurs certificats CA encodés au format PEM. Par exemple:
```
# Probablement, une CA intermédiaire
-----BEGIN CERTIFICATE-----
MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF
...
rqXRfboQnoZsG4q5WTP468SQvvG5
-----END CERTIFICATE-----
# Probablement, une CA racine
-----BEGIN CERTIFICATE-----
MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV
...
WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
-----END CERTIFICATE-----
```
Lorsque votre navigateur Web (ou l'interface HL7) tente d'établir une connexion TLS, il utilise cette liste de certificats CA de confiance pour déterminer s'il fait confiance au certificat échangé lors du handshake TLS.
Le processus commence par le certificat racine et traverse la chaîne de certificats jusqu'au certificat CA suivant. Si le certificat CA n'est pas trouvé dans le magasin de confiance ou le fichier CA-bundle, le certificat racine *n'est pas* considéré comme fiable et la connexion TLS échoue.
Si le certificat CA ou le fichier CA-bundle est trouvé dans le magasin de confiance, le processus continue en remontant la chaîne de certificats, en vérifiant que chaque CA se trouvant sur le chemin est dans le magasin de confiance. Une fois que le certificat CA racine au sommet de la chaîne est vérifié (ainsi que tous les certificats CA intermédiaires se trouvant sur le chemin), le processus peut approuver le certificat feuille du serveur.

## Le handshake TLS
Pour ajouter TLS à une connexion TCP/IP (comme un flux HL7), le client et le serveur doivent effectuer un handshake TLS après que la connexion TCP/IP a été établie. Ce handshake implique de s'accorder sur les chiffrements/méthodes de chiffrement, de s'accorder sur la version TLS, d'échanger des certificats X.509, de prouver la propriété de ces certificats et de valider que chaque partie fait confiance à l'autre.
Les étapes principales d'un handshake TLS sont les suivantes:
1. Le client établit une connexion TCP/IP avec le serveur.
2. Le client lance le handshake TLS.
3. Le serveur envoie son certificat (et la preuve de sa propriété) au client.
4. Le client vérifie le certificat du serveur.
5. En cas de TLS mutuel, le client envoie son certificat (et la preuve de sa propriété) au serveur.
6. En cas de TLS mutuel, le serveur vérifie le certificat du client.
7. Le client et le serveur s'échangent des données encryptées.

### 1. Le client établit une connexion TCP/IP avec le serveur.
À l'étape n° 1, le client et le serveur effectuent un handshake TCP à la procédure de base « ternaire » [TCP 3-way handshake] (https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Connection_establishment) pour établir une connexion TCP/IP entre eux. Dans un handshake à la procédure de base ternaire:
1. Le client envoie un paquet `SYN`.
2. Le serveur envoie un paquet `SYN-ACK`.
3. Le client envoie un paquet `ACK`.
Une fois ce handshake terminé, la connexion TCP/IP est établie. L'étape suivante consiste à lancer le handshake TLS.
### 2. Le client lance le handshake TLS.
Une fois la connexion TCP établie, l'une des parties doit agir en tant que client et lancer le handshake TLS. Généralement, le processus qui a initié la connexion TCP est également responsable du lancement du handshake TLS, mais cela peut être inversé dans de rares cas.
Pour lancer le handshake TLS, le client envoie un message ClientHello au serveur. Ce message contient diverses options utilisées pour négocier les paramètres de sécurité de la connexion avec le serveur.
### 3. Le serveur envoie son certificat (et la preuve de sa propriété) au client.
Après avoir reçu le message `ClientHello` du client, le serveur répond à son tour par un message `ServerHello`. Celui-ci inclut les paramètres de sécurité négociés.
Après le message `ServerHello`, le serveur envoie également un message `Certificate` et `CertificateVerify` au client. Cela permet de partager la chaîne de certificats X.509 avec le client et de fournir la preuve de propriété de la clé privée associée au certificat.
### 4. Le client vérifie le certificat du serveur.
Une fois que le client a reçu les messages `ServerHello`, `Certificate` et `CertificateVerify`, il vérifie que le certificat est valide et approuvé (en comparant les CAs aux fichiers CA-bundle approuvés, au magasin de certificats du système opérationnel ou au magasin de certificats du navigateur web). Le client effectue également toute vérification de l'hôte (voir ci-dessus) pour s'assurer que l'adresse de connexion correspond aux adresses/IP du certificat.
### 5. S'il s'agit d'une connexion TLS mutuelle, le client envoie son certificat (et la preuve de propriété) au serveur.
S'il s'agit d'une connexion TLS mutuelle (déterminée par l'envoi d'un message `CertificateRequest` par le serveur), le client enverra un message `Certificate` incluant sa chaîne de certificats, puis un message `CertificateVerify` pour prouver qu'il est le propriétaire de la clé privée associée.
### 6. S'il s'agit d'une connexion TLS mutuelle, le serveur vérifie le certificat client.
Là encore, s'il s'agit d'une connexion TLS mutuelle, le serveur vérifie que la chaîne de certificats envoyée par le client est valide et approuvée.
### 7. Le client et le serveur s'échangent des données encryptées.
Si la négociation TLS se déroule sans erreur, le client et le serveur s'échangent des messages `Finished` (Terminé) pour achever la négociation. Après cela, les données encryptées peuvent être échangées entre le client et le serveur.
## Configuration de TLS sur les interfaces HL7
Félicitations d'être arrivé jusqu'ici ! Maintenant que vous savez *en quoi* consiste TLS, comment procéderiez-vous pour *mettre en œuvre* le protocole TLS sur vos connexions HL7 ? De manière générale, voici les étapes à suivre pour configurer TLS sur vos connexions HL7.
1. Choisissez une autorité de certification.
2. Créez une clé et une demande de signature de certificat.
3. Obtenez votre certificat auprès de votre CA.
4. Obtenez la chaîne de certificats pour votre pair.
5. Créez une configuration SSL pour la connexion.
6. Ajoutez la configuration SSL à l'interface, faites rebondir l'interface et vérifiez le flux de messages.
### 1. Choisissez une autorité de certification.
La procédure que vous utiliserez pour obtenir un certificat et une clé pour votre serveur dépendra largement des politiques de sécurité de votre entreprise. Dans la plupart des cas, votre certificat sera signé par l'une des autorités de certification suivantes:
1. Votre certificat sera signé par une CA interne à l'entreprise.
- C'est mon option préférée, car votre entreprise dispose déjà de l'infrastructure nécessaire pour gérer les certificats et les CAs. Il vous suffit de travailler avec l'équipe qui possède cette infrastructure pour obtenir votre propre certificat pour vos interfaces HL7.
2. Votre certificat sera signé par une CA publique.
- Cette option est intéressante dans le sens où la CA publique dispose également de toute l'infrastructure nécessaire pour maintenir les certificats et les CAs. Cette option est sans doute exagérée pour la plupart des interfaces HL7, car les CA publiques fournissent généralement des certificats pour l'Internet ouvert ; les interfaces HL7 ont tendance à se connecter via un intranet privé, et non via l'Internet public.
- L'obtention de certificats auprès d'une CA publique peut également entraîner des frai.
3. Votre certificat sera signé par une CA que vous créerez et maintiendrezvous-même.
- Cette option peut vous convenir, mais malheureusement, cela signifie que vous supportez la charge de la maintenance et de la sécurisation de votre configuration CA et de votre logiciel.
- Vous l'utilisez à vos risques et périls!
- Cette option est la plus complexe. Préparez-vous à une courbe d'apprentissage abrupte.
- Vous pouvez utiliser des progiciels open source éprouvés pour gérer votre CA et vos certificats. La suite OpenSSL est une excellente option. Les autres options sont EJBCA, step-ca et cfssl.
### 2. Créez une clé et une demande de signature de certificat.
Après avoir choisi votre CA, l'étape suivante consiste à créer une clé privée et une demande de signature de certificat (`CSR`) . La manière dont vous générez la clé et la CSR dépendra de la politique de votre entreprise et de la CA choisie. Pour l'instant, nous allons simplement parler des étapes de manière générale.
Lors de la génération d'une clé privée, la clé publique associée est également générée. La clé publique sera intégrée à votre CSR et à votre certificat signé. Ces deux clés seront utilisées pour prouver la propriété de votre certificat signé lors de l'établissement d'une connexion TLS.
ATTENTION! Veillez à enregistrer votre clé privée dans un endroit sûr (de préférence dans un format protégé par un mot de passe). Si vous perdez cette clé, votre certificat ne sera plus utilisable. Si quelqu'un d'autre accède à cette clé, il pourra se faire passer pour votre serveur.
La demande de signature de certificat inclura des informations sur votre serveur, votre entreprise, votre clé publique, la manière d'utiliser le certificat, etc. Elle inclura également la preuve que vous possédez la clé privée associée. Cette CSR sera ensuite fournie à votre CA pour générer et signer votre certificat.
REMARQUE: lors de la création de la CSR, assurez-vous de demander une `Extended Key Usage` (utilisation étendue de la clé) à la fois pour `serverAuth` et `clientAuth`, si vous utilisez le TLS mutuel. La plupart des CA sont habituées à signer des certificats avec uniquement la clé `serverAuth`. Malheureusement, cela signifie que le certificat ne peut pas être utilisé comme certificat client dans une connexion TLS mutuelle.
### 3. Obtenez votre certificat auprès de votre CA.
Après avoir créé votre clé et votre CSR, soumettez la CSR à votre autorité de certification. Après avoir effectué plusieurs vérifications, votre CA devrait être en mesure de vous fournir un certificat signé et la chaîne de certificats associée. Ce certificat et cette chaîne doivent être enregistrés au format PEM. Si la CA a fourni votre certificat dans un format différent, vous devrez le convertir à l'aide d'un outil tel qu'OpenSSL.
### 4. Obtenez la chaîne de certificats pour votre homologue.
Les étapes précédentes étaient axées sur l'obtention d'un certificat pour votre serveur. Vous devriez pouvoir utiliser ce certificat (et la clé associée) avec chaque connexion HL7 vers/depuis ce serveur. Vous devrez également obtenir les chaînes de certificats pour chacun des systèmes/homologues auxquels vous vous connecterez.
Les chaînes de certificats de chaque homologue devront être enregistrées dans un fichier au format PEM. Ce CA-bundle n'aura pas besoin de contenir les certificats feuille ; il doit uniquement contenir les certificats CA intermédiaires et racine.
Veillez à fournir à votre homologue un CA-bundle contenant vos CA intermédiaires et racine. Cela lui permettra de faire confiance à votre certificat lorsque vous établirez une connexion.
### 5. Créez une configuration SSL pour la connexion.
Dans Health Connect d'InterSystems, il vous faudra créer des configurations SSL client et serveur pour chaque système auquel votre serveur se connectera. Ces configurations SSL dirigeront vers le fichier CA-bundle du système associé et vers les fichiers clé et de certificat de votre serveur.
Les configurations SSL client sont utilisées lors des opérations pour lancer le handshake TLS. Les configurations SSL serveur sont utilisées sur les services pour répondre aux handshakes TLS. Si un système dispose à la fois de services entrants et de services sortants, il faudra configurer à la fois une configuration SSL client et une configuration SSL serveur pour ce système.
Pour créer une configuration SSL client:
1. Accédez à `System Administration > Security > SSL/TLS Configurations` (Administration système > Sécurité > Configurations SSL/TLS).
2. Appuyeze sur `Create New Configuration` (Créer une nouvelle configuration).
3. Donnez un `Configuration Name` (Nom de configuration) et une `Description` (Description) à votre configuration SSL.
4. Assurez-vous que votre configuration SSL est `Enabled` (Activée).
5. Choisissez `Client` comme `Type`.
6. Choisissez `Require` (Obligatoire) pour le champ `Server certificate verification` (Vérification du certificat serveur). Cela effectue une vérification de l'hôte sur la connexion.
7. Dirigez le champ `File` (Fichier) contenant le(s) certificat(s) CA de confiance vers le fichier CA-bundle contenant les CA intermédiaires et racines (au format PEM) du système auquel vous vous connectez.
8. Dirigez le champ `File` (Fichier) contenant le certificat de ce client vers le fichier contenant le certificat X.509 de votre serveur au format PEM.
9. Dirigez le champ `File` (Fichier) contenant la clé privée associée vers le fichier contenant la clé privée de votre certificat.
10. Le `Private key type` (type de clé privée) sera très probablement `RSA` (chiffrement RSA). Cela devrait correspondre au type de votre clé privée.
11. Si votre clé privée est protégée par un mot de passe (comme cela devrait être le cas), saisissez le mot de passe dans les champs `Private key password` (mot de passe de la clé privée) et `Private key password (confirm)` (confirmer le mot de passe de la clé privée).
12. Vous pouvez probablement laisser les autres champs à leurs valeurs par défaut.
Pour créer une configuration de serveur SSL:
1. Go to `System Administration > Security > SSL/TLS Configurations`.
2. Appuyeze sur `Create New Configuration` (Créer une nouvelle configuration).
3. Donnez un `Configuration Name` (Nom de configuration) et une `Description` (Description) à votre configuration SSL.
4. Assurez-vous que votre configuration SSL est `Enabled` (Activée).
5. Choisissez `Server` comme `Type`.
6. Choisissez `Require` (Obligatoire) pour le champ `Client certificate verification` (Vérification du certificat client). Cela permettra de s'assurer que le TLS mutuel est exécuté.
7. Dirigez `File containing trusted Certificate Authority certificate(s)` (Fichier contenant le(s) certificat(s) de l'autorité de certification de confiance) vers le fichier CA-bundle contenant les CA intermédiaires et racines (au format PEM) du système auquel vous vous connectez.
8. Dirigez `File containing this server's certificate` (Fichier contenant le certificat de ce serveur) vers le fichier contenant le certificat X.509 de votre serveur au format PEM.
9. Dirigez `File containing associated private key` (Fichier contenant la clé privée associée) vers le fichier contenant la clé privée de votre certificat.
10. Le `Private key type` (type de clé privée) sera très probablement `RSA` (chiffrement RSA). Cela devrait correspondre au type de votre clé privée.
11. Si votre clé privée est protégée par un mot de passe (comme cela devrait être le cas), saisissez le mot de passe dans les champs `Private key password` (mot de passe de la clé privée) et `Private key password (confirm)` (confirmer le mot de passe de la clé privée).
12. Vous pouvez probablement laisser les autres champs à leurs valeurs par défaut.

### 6. Ajoutez la configuration SSL à l'interface, relancez l'interface et vérifiez le flux de messages.
Une fois que vous avez créé les configurations SSL client et serveur, vous êtes prêt à activer TLS sur les interfaces. Pour chaque service ou opération, choisissez la configuration SSL associée dans le menu déroulant `Connection Settings > SSL Configuration` (Paramètres de connexion > Configuration SSL) qui se trouve dans l'onglet *Settings* (Paramètres) de l'interface.
Après avoir relancé l'interface, vous verrez la connexion se rétablir. Lorsqu'un nouveau message est transféré, un statut `Completed` (Terminé) indique que TLS fonctionne. Si TLS ne fonctionne pas, la connexion sera interrompue à chaque tentative de message.
Pour vous aider à déboguer les problèmes avec TLS, il se peut que vous ayez besoin d'utiliser des outils tels que tcpdump, Wireshark ou l'utilitaire s_client d'OpenSSL.
## Conclusion
Nous avons fait une analyse très approfondie du sujet SSL/TLS. Il y a tellement d'autres informations qui n'ont pas été incluses dans cet article. J'espère que cet article vous a fourni un aperçu suffisant du fonctionnement de TLS pour que vous puissiez rechercher les détails et obtenir plus d'informations si nécessaire.
Si vous recherchez une ressource approfondie sur TLS, consultez le site Web d'Ivan Ristić, [fiestyduck.com](https://www.feistyduck.com/) et son livre, [Bulletproof TLS and PKI](https://www.feistyduck.com/books/bulletproof-tls-and-pki/). J'ai trouvé que ce livre était une excellente ressource pour en savoir plus sur l'utilisation de TLS.
Article
Sylvain Guilbaud · Mars 29, 2023
Keywords: IRIS, IntegratedML, apprentissage automatique, Covid-19, Kaggle
## Objectif
J'ai récemment remarqué un [jeu de données Kaggle](https://www.kaggle.com/S%C3%ADrio-Libanes/covid19/kernels) permettant de prédire si un patient Covid-19 sera admis en soins intensifs. Il s'agit d'un tableur de 1925 enregistrements comprenant 231 colonnes de signes vitaux et d'observations, la dernière colonne " USI " valant 1 pour Oui ou 0 pour Non.
Ce jeu de données représente un bon exemple de ce que l'on appelle une tâche "traditionnelle de ML". Les données semblent avoir une quantité suffisante et une qualité relativement bonne. Il pourrait avoir de meilleures chances d'être appliqué directement sur le kit [IntegratedML demo](https://github.com/intersystems-community/integratedml-demo-template). Quelle serait donc l'approche la plus simple pour un test rapide basé sur les pipelines ML normaux par rapport à l'approche possible avec IntegratedML ?
## Champ d'application
Nous examinerons brièvement quelques étapes normales de ML, telles que :
* Analyse des données (EDA)
* Sélection des caractéristiques
* Sélection du modèle
* Ajustement des paramètres du modèle via le quadrillage
Vs.
* Approches ML intégrées via SQL.
Il est exécuté sur un serveur AWS Ubuntu 16.04 avec Docker-compose, etc.
## Environnement
Nous allons réutiliser l'environnement Docker de [integredML-demo-template](https://openexchange.intersystems.com/package/integratedml-demo-template):

Le fichier de bloc-notes suivant est exécuté sur "tf2jupyter", et IRIS avec IntegratedML sur "irismlsrv". Docker-compose fonctionne sur un AWS Ubuntu 16.04.
## Données et tâches
Le jeu de données contient 1925 enregistrements collectés auprès de 385 patients, chacun comportant exactement 5 enregistrements de rendez-vous. Sur ses 231 colonnes, une seule, "USI", constitue notre cible d'apprentissage et de prédiction, et les 230 autres colonnes pourraient toutes être utilisées comme entrées de quelque manière que ce soit. L'unité de soins intensifs a une valeur binaire de 1 ou 0. À l'exception de deux colonnes qui semblent être des chaînes catégorielles (présentées comme "objet" dans le cadre de données), toutes les autres sont numériques.
import numpy as np
import pandas as pd
from sklearn.impute import SimpleImputer
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, roc_auc_score, roc_curve
import seaborn as sns
sns.set(style="whitegrid")
import os
for dirname, _, filenames in os.walk('./input'):
for filename in filenames:
print(os.path.join(dirname, filename))
./input/datasets_605991_1272346_Kaggle_Sirio_Libanes_ICU_Prediction.xlsx
df = pd.read_excel("./input/datasets_605991_1272346_Kaggle_Sirio_Libanes_ICU_Prediction.xlsx")
df
IDENTIFIANT_DE_VISITE_DU_PATIENT
ÂGE_AU-DESSUS65
ÂGE_POURCENTAGE
GENRE
GROUPE DE MALADIES 1
GROUPE DE MALADIES 2
GROUPE DE MALADIES 3
GROUPE DE MALADIES 4
GROUPE DE MALADIES 5
GROUPE DE MALADIES 6
...
DIFFÉRENCE_DE_TEMPÉRATURE
DIFFÉRENCE_DE SATURATION_D'OXYGÈNE
DIFFÉRENCE_DE_TENSION_DIASTOLIQUE_REL
DIFFÉRENCE_DE_TENSION_SISTOLIQUE_REL
DIFFÉRENCE_DU_RYTHME_CARDIAQUE_REL
DIFFÉRENCE_DE_TAUX_RESPIRATOIRE_REL
DIFFÉRENCE_DE_TEMPÉRATURE_REL
DIFFÉRENCE_DE_SATURATION_D'OXYGÈNE_REL
FENÊTRE
ICU
1
âge de 60-69 ans
0.0
0.0
0.0
0.0
1.0
1.0
...
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
0-2
1
1
âge de 60-69 ans
0.0
0.0
0.0
0.0
1.0
1.0
...
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
2-4
2
1
âge de 60-69 ans
0.0
0.0
0.0
0.0
1.0
1.0
...
NaN
NaN
NaN
NaN
NaN
NaN
NaN
NaN
4-6
3
1
âge de 60-69 ans
0.0
0.0
0.0
0.0
1.0
1.0
...
-1.000000
-1.000000
NaN
NaN
NaN
NaN
-1.000000
-1.000000
6-12
4
1
âge de 60-69 ans
0.0
0.0
0.0
0.0
1.0
1.0
...
-0.238095
-0.818182
-0.389967
0.407558
-0.230462
0.096774
-0.242282
-0.814433
AU-DESSUS_12
1
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
1920
384
âge de 50-59 ans
1
0.0
0.0
0.0
0.0
0.0
0.0
...
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
0-2
1921
384
âge de 50-59 ans
1
0.0
0.0
0.0
0.0
0.0
0.0
...
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
2-4
1922
384
âge de 50-59 ans
1
0.0
0.0
0.0
0.0
0.0
0.0
...
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
4-6
1923
384
âge de 50-59 ans
1
0.0
0.0
0.0
0.0
0.0
0.0
...
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
-1.000000
6-12
1924
384
âge de 50-59 ans
1
0.0
0.0
1.0
0.0
0.0
0.0
...
-0.547619
-0.838384
-0.701863
-0.585967
-0.763868
-0.612903
-0.551337
-0.835052
AU-DESSUS_12
1925 lignes × 231 colonnes
df.dtypes
IDENTIFIANT_DE_VISITE_DU_PATIENT int64
ÂGE_AU-DESSUS65 int64
ÂGE_POURCENTAGE object
GENRE int64
GROUPE DE MALADIES 1 float64
...
DIFFÉRENCE_DE_TAUX_RESPIRATOIRE_REL float64
DIFFÉRENCE_DE_TEMPÉRATURE_REL float64
DIFFÉRENCE_DE SATURATION_D'OXYGÈNE_REL float64
FENÊTRE object
USI int64
Longeur: 231, dtype: object
Il existe certainement plusieurs options pour définir ce problème et ses approches. La première option qui nous vient à l'esprit est qu'il peut s'agir d'un problème fondamental de "classification binaire". Nous pouvons traiter les 1925 enregistrements comme des enregistrements individuels "apatrides", qu'ils proviennent ou non du même patient. Bien sûr, il pourrait également s'agir d'un problème de "régression" si nous traitions les valeurs de l'unité de soins intensifs et d'autres valeurs comme étant toutes numériques.
Il existe certainement d'autres approches possibles. Par exemple, nous pouvons considérer que l'ensemble de données comporte 385 jeux distincts de courtes "séries temporelles", chacun pour un patient. Nous pourrions dissoudre le jeu entier en 385 jeux distincts pour Train/Val/Test, et pourrions-nous essayer des modèles d'apprentissage profond tels que CNN ou LSTM pour capturer la "phase ou le modèle de développement des symptômes" caché dans chaque jeu pour chaque patient individuel ? C'est possible. Ce faisant, nous pourrions également appliquer une augmentation des données pour enrichir les données de test par divers moyens. Il s'agit là d'un sujet qui dépasse le cadre de cet article.
Dans cet article, nous nous contenterons de tester rapidement l'approche ML dite "traditionnelle" par rapport à l'approche IntegratedML (une approche AutoML)..
## Approche ML "traditionnelle" ?
Il s'agit d'un jeu de données relativement normalisé par rapport à la plupart des cas réels, à l'exception de quelques valeurs manquantes, de sorte que nous pourrions sauter la partie relative à l'ingénierie des caractéristiques et utiliser directement les colonnes comme caractéristiques. Passons donc directement à la sélection des caractéristiques.
### **Imputation des données manquantes**
Il faut d'abord s'assurer que toutes les valeurs manquantes sont remplies au moyen d'une imputation simple :
df_cat = df.select_dtypes(include=['object'])
df_numeric = df.select_dtypes(exclude=['object'])
imp = SimpleImputer(missing_values=np.nan, strategy='mean')
idf = pd.DataFrame(imp.fit_transform(df_numeric))
idf.columns = df_numeric.columns
idf.index = df_numeric.index
idf.isnull().sum()
###
### **Sélection sur les caractéristiques**
Nous pouvons certainement utiliser la fonction de corrélation normale intégrée dans la base de données pour calculer la corrélation entre les valeurs de chaque colonne et les unités de soins intensifs.
#### l'ingénierie des caractéristiques - **corrélation** {#featuring-engineering---correlation}
idf.drop(["PATIENT_VISIT_IDENTIFIER"],1)
idf = pd.concat([idf,df_cat ], axis=1)
cor = idf.corr()
cor_target = abs(cor["ICU"])
relevant_features = cor_target[cor_target>0.1] # correlation above 0.1
print(cor.shape, cor_target.shape, relevant_features.shape)
#relevant_features.index
#relevant_features.index.shape
Il répertorie 88 caractéristiques présentant une corrélation >0,1 avec la valeur cible de l'unité de soins intensifs. Ces colonnes peuvent être directement utilisées comme entrée de notre modèle
J'ai également exécuté quelques autres "méthodes de sélection de caractéristiques" qui sont normalement utilisées dans les tâches traditionnelles de ML :
#### Sélection des caractéristiques - **Chi carré** {#feature-selection---Chi-squared}
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
from sklearn.preprocessing import MinMaxScaler
X_norm = MinMaxScaler().fit_transform(X)
chi_selector = SelectKBest(chi2, k=88)
chi_selector.fit(X_norm, y)
chi_support = chi_selector.get_support()
chi_feature = X.loc[:,chi_support].columns.tolist()
print(str(len(chi_feature)), 'selected features', chi_feature)
88 caractéristiques sélectionnées ['ÂGE_AU-DESSUS65', 'GENRE', 'GROUPE DE MALADIES 1', ... ... 'P02_VENEUS_MIN', 'P02_VENEUS_MAX', ... ... RATURE_MAX', 'DIFFÉRENCE_DE_TENSION_ARTÉRIELLE_DIASTOLIQUE', ... ... 'DIFFÉRENCE_DE_TEMPÉRATURE_REL', 'DIFFÉRENCE_DE SATURATION_D'OXYGÈNE_REL']
Sélection des caractéristiques - **Corrélation de Pearson**
def cor_selector(X, y,num_feats):
cor_list = []
feature_name = X.columns.tolist()
# calculate the correlation with y for each feature
for i in X.columns.tolist():
cor = np.corrcoef(X[i], y)[0, 1]
cor_list.append(cor)
# replace NaN with 0
cor_list = [0 if np.isnan(i) else i for i in cor_list]
# feature name
cor_feature = X.iloc[:,np.argsort(np.abs(cor_list))[-num_feats:]].columns.tolist()
# Sélection des caractéristiques? 0 for not select, 1 for select
cor_support = [Vrai if i in cor_feature else False for i in feature_name]
return cor_support, cor_feature
cor_support, cor_feature = cor_selector(X, y, 88)
print(str(len(cor_feature)), 'selected features: ', cor_feature)
88 caractéristiques sélectionnées: ['TEMPÉRATURE_MOYENNE', 'TENSION_DIASTOLIQUE_MAX', ... ... 'DIFFÉRENCE_DE_TAUX_ RESPIRATOIRE', 'AUX_ RESPIRATOIRE_MAX']
#### Sélection des caractéristiques - **élimination de caractéristiques récursives (RFE)** {#feature-selection---Recursive-Feature-Elimination-(RFE)}
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
rfe_selector = RFE(estimator=LogisticRegression(), n_features_to_select=88, step=100, verbose=5)
rfe_selector.fit(X_norm, y)
rfe_support = rfe_selector.get_support()
rfe_feature = X.loc[:,rfe_support].columns.tolist()
print(str(len(rfe_feature)), 'selected features: ', rfe_feature)
Estimateur d'ajustement avec 127 caractéristiques.
88 caractéristiques sélectionnées: ['ÂGE_AU-DESSUS65', 'GENRE', ... ... 'DIFFÉRENCE_DE_TAUX_ RESPIRATOIRE_REL', 'DIFFÉRENCE_DE_TEMPÉRATURE_REL']
Sélection des caractéristiques - **Lasso**
ffrom sklearn.feature_selection import SelectFromModel
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import MinMaxScaler
X_norm = MinMaxScaler().fit_transform(X)
embeded_lr_selector = SelectFromModel(LogisticRegression(penalty="l2"), max_features=88)
embeded_lr_selector.fit(X_norm, y)
embeded_lr_support = embeded_lr_selector.get_support()
embeded_lr_feature = X.loc[:,embeded_lr_support].columns.tolist()
print(str(len(embeded_lr_feature)), 'selected features', embeded_lr_feature)
65 caractéristiques sélectionnées ['ÂGE_AU-DESSUS65', 'GENRE', ... ... 'DIFFÉRENCE_DE_TAUX_ RESPIRATOIRE_REL', 'DIFFÉRENCE_DE_TEMPÉRATURE_REL']
Sélection des caractéristiques - **RF Tree-based**: SelectFromModel
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier
embeded_rf_selector = SelectFromModel(RandomForestClassifier(n_estimators=100), max_features=227)
embeded_rf_selector.fit(X, y)
embeded_rf_support = embeded_rf_selector.get_support()
embeded_rf_feature = X.loc[:,embeded_rf_support].columns.tolist()
print(str(len(embeded_rf_feature)), 'selected features', embeded_rf_feature)
48 selected features ['ÂGE_AU-DESSUS65', 'GENRE', ... ... 'DIFFÉRENCE_DE_TEMPÉRATURE_REL', 'DIFFÉRENCE_DE SATURATION_D'OXYGÈNE_REL']
#### Sélection des caractéristiques - **LightGBM** or **XGBoost** {#feature-selection---LightGBM-or-XGBoost}
from sklearn.feature_selection import SelectFromModel
from lightgbm import LGBMClassifier
lgbc=LGBMClassifier(n_estimators=500, learning_rate=0.05, num_leaves=32, colsample_bytree=0.2,
reg_alpha=3, reg_lambda=1, min_split_gain=0.01, min_child_weight=40)
embeded_lgb_selector = SelectFromModel(lgbc, max_features=128)
embeded_lgb_selector.fit(X, y)
embeded_lgb_support = embeded_lgb_selector.get_support()
embeded_lgb_feature = X.loc[:,embeded_lgb_support].columns.tolist()
print(str(len(embeded_lgb_feature)), 'selected features: ', embeded_lgb_feature)
embeded_lgb_feature.index
56 selected features: ['ÂGE_AU-DESSUS65', 'GENRE', 'HTN', ... ... 'DIFFÉRENCE_DE_TEMPÉRATURE_REL', 'DIFFÉRENCE_DE SATURATION_D'OXYGÈNE_REL']
#### Sélection des caractéristiques - **Les regrouper tous** {#feature-selection---Ensemble-them-all}
feature_name = X.columns.tolist()
# regrouper toute la sélection
feature_selection_df = pd.DataFrame({'Feature':feature_name, 'Pearson':cor_support, 'Chi-2':chi_support, 'RFE':rfe_support, 'Logistics':embeded_lr_support, 'Random Forest':embeded_rf_support, 'LightGBM':embeded_lgb_support})
# compter les temps sélectionnés pour chaque caractéristique
feature_selection_df['Total'] = np.sum(feature_selection_df, axis=1)
# afficher les 100 premières
num_feats = 227
feature_selection_df = feature_selection_df.sort_values(['Total','Feature'] , ascending=False)
feature_selection_df.index = range(1, len(feature_selection_df)+1)
feature_selection_df.head(num_feats)
df_selected_columns = feature_selection_df.loc[(feature_selection_df['Total'] > 3)]
df_selected_columns
Nous pouvons dresser la liste des caractéristiques qui ont été sélectionnées dans le cadre d'au moins quatre méthodes :
.png)
... ...
.png)
Nous pouvons certainement choisir ces 58 caractéristiques. Entre-temps, l'expérience nous a appris que la sélection des caractéristiques n'est pas nécessairement toujours un vote démocratique ; le plus souvent, elle peut être spécifique au problème du domaine, aux données spécifiques et parfois au modèle ou à l'approche ML spécifique que nous allons adopter plus tard.
Sélection des caractéristiques - **Outils tiers**
Il existe des outils industriels et des outils AutoML largement utilisés, par exemple DataRobot qui peut fournir une bonne sélection automatique des caractéristiques :

Le graphe DataRobot ci-dessus montre, sans surprise, que les valeurs de fréquence respiratoire et de tension artérielle sont les caractéristiques les plus pertinentes pour l'admission en soins intensifs.
Sélection des caractéristiques - **Sélection finale**
Dans ce cas, j'ai fait quelques expériences rapides et j'ai remarqué que la sélection des caractéristiques par LightGBM donnait un résultat un peu meilleur, c'est pourquoi nous n'utiliserons que cette méthode de sélection.
df_selected_columns = embeded_lgb_feature # mieux que la sélection ensembliste
dataS = pd.concat([idf[df_selected_columns],idf['ICU'], df_cat['FENÊTRE']],1)
dataS.ICU.value_counts()
print(dataS.shape)
(1925, 58)
Nous pouvons voir que 58 caractéristiques sont sélectionnées, c'est-à-dire ni trop peu, ni trop beaucoup, ce qui semble être la bonne quantité pour ce problème spécifique de classification binaire à cible unique.
### **Déséquilibre des données**
plt.figure(figsize=(10,5))
count = sns.countplot(x = "USI",data=data)
count.set_xticklabels(["Non admis", "Admis"])
plt.xlabel("Admission à l'USI")
plt.ylabel("Nombre de patients")
plt.show()
Cela indique que les données sont déséquilibrées, seuls 26 % des enregistrements étant admis en USI. Cela aura un impact sur les résultats et nous pouvons donc envisager des approches normales d'équilibrage des données telles que SMOTE, etc.
Nous pouvons essayer toutes sortes d'autres AED pour analyser les différentes distributions de données en conséquence.
### **Exécuter une formation de base en LR**
Le site Kaggle propose de jolis carnets d'entraînement rapide que nous pouvons exécuter rapidement en fonction de notre propre sélection de colonnes de caractéristiques. Commençons par une exécution rapide du classificateur LR pour le pipeline de formation :
data2 = pd.concat([idf[df_selected_columns],idf['USI'], df_cat['FENÊTRE']],1)
data2.AGE_ABOVE65 = data2.AGE_ABOVE65.astype(int)
data2.ICU = data2.ICU.astype(int)
X2 = data2.drop("USI",1)
y2 = data2.ICU
from sklearn.preprocessing import LabelEncoder
label_encoder = LabelEncoder()
X2.WINDOW = label_encoder.fit_transform(np.array(X2["FENÊTRE"].astype(str)).reshape((-1,)))
confusion_matrix2 = pd.crosstab(y2_test, y2_hat, rownames=['Réel'], colnames=['Prédit'])
sns.heatmap(confusion_matrix2, annot=Vrai, fmt = 'g', cmap = 'Reds') print("ORIGINAL")
print(classification_report(y_test, y_hat))
print("USI = ",roc_auc_score(y_test, y_hat),'\n\n')
print("ENCODAGE D'ÉTIQUETTE")
print(classification_report(y2_test, y2_hat))
print("ASC = ",roc_auc_score(y2_test, y2_hat))
y2hat_probs = LR.predict_proba(X2_test)
y2hat_probs = y2hat_probs[:, 1] fpr2, tpr2, _ = roc_curve(y2_test, y2hat_probs) plt.figure(figsize=(10,7))
plt.plot([0, 1], [0, 1], 'k--')
plt.plot(fpr, tpr, label="Base")
plt.plot(fpr2,tpr2,label="Étiquette encodée")
plt.xlabel('Taux de faux positifs')
plt.ylabel('Taux de vrais positifs')
plt.title('Courbe ROC')
plt.legend(loc="meilleur")
plt.show()
ORIGINAL
précision rappel score f1 support
0 0.88 0.94 0.91 171
1 0.76 0.57 0.65 54
exactitude 0.85 225
moyenne macro 0.82 0.76 0.78 225
moyenne pondérée 0.85 0.85 0.85 225
ASC= 0.7577972709551657
LABEL ENCODING
précision rappel score f1 support
0 0.88 0.93 0.90 171
1 0.73 0.59 0.65 54
accuracy 0.85 225
moyenne macro 0.80 0.76 0.78 225
moyenne pondérée 0.84 0.85 0.84 225
ASC = 0.7612085769980507
Il semble qu'il atteigne une AUC de 76 %, avec une précision de 85 %, mais le rappel pour les patients admis en réanimation n'est que de 59 % - il semble y avoir trop de faux négatifs. Ce n'est certainement pas l'idéal - nous ne voulons pas passer à côté des risques réels de l'USI pour le dossier d'un patient. Toutes les tâches suivantes seront donc axées sur l'objectif sur la manière d'augmenter le taux de rappel, en réduisant le FN, avec une précision globale quelque peu équilibrée, nous l'espérons.
Dans les sections précédentes, nous avons mentionné des données déséquilibrées, de sorte que notre premier réflexe serait de stratifier l'ensemble de test et de le MODIFIER pour obtenir un ensemble de données plus équilibré.
#stratifier les données de test, afin de s'assurer que les données de train et de test ont le même ratio de 1:0
X3_train,X3_test,y3_train,y3_test = train_test_split(X2,y2,test_size=225/1925,random_state=42, stratify = y2, shuffle = Vrai) <span> </span>
# former et prédire
LR.fit(X3_train,y3_train)
y3_hat = LR.predict(X3_test)
#MODIFIER les données pour faire de l'UCI 1:0 une distribution équilibrée
from imblearn.over_sampling import SMOTE sm = SMOTE(random_state = 42)
X_train_res, y_train_res = sm.fit_sample(X3_train,y3_train.ravel())
LR.fit(X_train_res, y_train_res)
y_res_hat = LR.predict(X3_test)
#recréer la matrice de confusion, etc.
confusion_matrix3 = pd.crosstab(y3_test, y_res_hat, rownames=['Actual'], colnames=['Predicted'])
sns.heatmap(confusion_matrix3, annot=Vrai, fmt = 'g', cmap="YlOrBr")
print("LABEL ENCODING + STRATIFY")
print(classification_report(y3_test, y3_hat))
print("ASC = ",roc_auc_score(y3_test, y3_hat),'\n\n')
print("SMOTE")
print(classification_report(y3_test, y_res_hat))
print("ASC = ",roc_auc_score(y3_test, y_res_hat))
y_res_hat_probs = LR.predict_proba(X3_test)
y_res_hat_probs = y_res_hat_probs[:, 1]
fpr_res, tpr_res, _ = roc_curve(y3_test, y_res_hat_probs) plt.figure(figsize=(10,10))
#Et tracez la courbe ROC comme précédemment.
LABEL ENCODING + STRATIFY (CODAGE D'ÉTIQUETTES + STRATIFICATION)
précision rappel f1 score support
0 0.87 0.99 0.92 165
1 0.95 0.58 0.72 60
exactitude 0.88 225
moyenne macro 0.91 0.79 0.82 225
moyenne pondérée 0.89 0.88 0.87 225
ASC = 0.7856060606060606
SMOTE
précision rappel f1 score support
0 0.91 0.88 0.89 165
1 0.69 0.75 0.72 60
exactitude 0.84 225
moyenne macro 0.80 0.81 0.81 225
moyenne pondérée 0.85 0.84 0.85 225
ASC = 0.8143939393939393
Les traitements des données par STRATIFY (stratification) et SMOT (optimisation) semblent donc améliorer le rappel, qui passe de 0,59 à 0,75, avec une précision globale de 0,84.
Maintenant que le traitement des données est largement effectué comme d'habitude pour le ML traditionnel, nous voulons savoir quel pourrait être le(s) meilleur(s) modèle(s) dans ce cas ; peuvent-ils faire mieux, et pouvons-nous alors essayer une comparaison globale relative ?
### **Comparaison de l'entraînement à la course de différents modèles**:
Poursuivons l'évaluation de quelques algorithmes de ML couramment utilisés, et générons un tableau de bord de résultats à comparer à l'aide de diagrammes en boîte à moustaches :
# comparer les algorithmes
from matplotlib import pyplot
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import StratifiedKFold
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.naive_bayes import GaussianNB
from sklearn.svm import SVC
#Importer un modèle arborescent aléatoire
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier
# Répertorier les algorithmes ensemble
models = []
models.append(('LR', <strong>LogisticRegression</strong>(solver='liblinear', multi_class='ovr')))
models.append(('LDA', LinearDiscriminantAnalysis()))
models.append(('KNN', <strong>KNeighborsClassifier</strong>()))
models.append(('CART', <strong>DecisionTreeClassifier</strong>()))
models.append(('NB', <strong>GaussianNB</strong>()))
models.append(('SVM', <strong>SVC</strong>(gamma='auto')))
models.append(('RF', <strong>RandomForestClassifier</strong>(n_estimators=100)))
models.append(('XGB', <strong>XGBClassifier</strong>())) #clf = XGBClassifier()
# évaluer chaque modèle à tour de rôle
résultats = []
noms = []
pour nom, modèler dans modèles :
kfold = StratifiedKFold(n_splits=10, random_state=1)
cv_results = cross_val_score(model, X_train_res, y_train_res, cv=kfold, scoring='f1') ## exactitude, précision, rappel
results.append(cv_results)
names.append(name)
print('%s: %f (%f)' % (name, cv_results.mean(), cv_results.std()))
# Comparer les performances de tous les modèles. Question - Souhaitez-vous voir un article intégré sur le site ?
pyplot.figure(4, figsize=(12, 8))
pyplot.boxplot(résultats, étiquettes=noms)
pyplot.title('Comparaison des algorithmes')
pyplot.show()
LR: 0.805390 (0.021905) LDA: 0.803804 (0.027671) KNN: 0.841824 (0.032945) CART: 0.845596 (0.053828)
NB: 0.622540 (0.060390) SVM: 0.793754 (0.023050) RF: 0.896222 (0.033732) XGB: 0.907529 (0.040693)

Ce qui précède semble montrer que le classificateur XGB et le classificateur de la forêt aléatoire "Random Forest" obtiendraient un meilleur score F1 que les autres modèles.
Comparons leurs résultats réels sur le même ensemble de données de test normalisées :
Temps d'importation
from pandas import read_csv
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score
from sklearn.svm import SVC
pour nom, modèler dans modèles :
print(name + ':\n\r')
start = time.clock()
model.fit(X_train_res, y_train_res)
print("Temps de formation pour ", model, " ", time.clock() - start)
predictions = model.predict(X3_test) #(X_validation)
# Evaluate predictions
print(accuracy_score(y3_test, predictions)) # Y_validation
print(confusion_matrix(y3_test, predictions))
print(classification_report(y3_test, predictions))
LR:
Temps de formation pour LogisticRegression(multi_class='ovr', solver='liblinear') 0.02814499999999498
0.8444444444444444
[[145 20]
[ 15 45]]
précision rappel f1 score support
0 0.91 0.88 0.89 165
1 0.69 0.75 0.72 60
exactitude 0.84 225
moyenne macro 0.80 0.81 0.81 225
moyenne pondérée 0.85 0.84 0.85 225
LDA:
Temps de formation pour LinearDiscriminantAnalysis() 0.2280070000000194
0.8488888888888889
[[147 18]
[ 16 44]]
précision rappel f1 score support
0 0.90 0.89 0.90 165
1 0.71 0.73 0.72 60
exactitude 0.85 225
moyenne macro 0.81 0.81 0.81 225
moyenne pondérée 0.85 0.85 0.85 225
KNN:
Temps de formation pour KNeighborsClassifier() 0.13023699999999394
0.8355555555555556
[[145 20]
[ 17 43]]
précision rappel f1 score support
0 0.90 0.88 0.89 165
1 0.68 0.72 0.70 60
exactitude 0.84 225
moyenne macro 0.79 0.80 0.79 225
moyenne pondérée 0.84 0.84 0.84 225
CART:
Temps de formation pour DecisionTreeClassifier() 0.32616000000001577
0.8266666666666667
[[147 18]
[ 21 39]]
précision rappel f1 score support
0 0.88 0.89 0.88 165
1 0.68 0.65 0.67 60
exactitude 0.83 225
moyenne macro 0.78 0.77 0.77 225
moyenne pondérée 0.82 0.83 0.83 225
NB:
Temps de formation pour GaussianNB() 0.0034229999999979555
0.8355555555555556
[[154 11]
[ 26 34]]
précision rappel f1 score support
0 0.86 0.93 0.89 165
1 0.76 0.57 0.65 60
exactitude 0.84 225
moyenne macro 0.81 0.75 0.77 225
moyenne pondérée 0.83 0.84 0.83 225
SVM:
Temps de formation pour SVC(gamma='auto') 0.3596520000000112
0.8977777777777778
[[157 8]
[ 15 45]]
précision rappel f1 score support
0 0.91 0.95 0.93 165
1 0.85 0.75 0.80 60
exactitude 0.90 225
moyenne macro 0.88 0.85 0.86 225
moyenne pondérée 0.90 0.90 0.90 225
RF:
Temps de formation pour RandomForestClassifier() 0.50123099999999
0.9066666666666666
[[158 7]
[ 14 46]]
précision rappel f1 score support
0 0.92 0.96 0.94 165
1 0.87 0.77 0.81 60
exactitude 0.91 225
moyenne macro 0.89 0.86 0.88 225
moyenne pondérée 0.91 0.91 0.90 225
XGB:
Temps de formation pour XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,
colsample_bynode=1, colsample_bytree=1, gamma=0, gpu_id=-1,
importance_type='gain', interaction_constraints='',
learning_rate=0.300000012, max_delta_step=0, max_depth=6,
min_child_weight=1, missing=nan, monotone_constraints='()',
n_estimators=100, n_jobs=0, num_parallel_tree=1, random_state=0,
reg_alpha=0, reg_lambda=1, scale_pos_weight=1, subsample=1,
tree_method='exact', validate_parameters=1, verbosity=Aucun) 1.649520999999993
0.8844444444444445
[[155 10]
[ 16 44]]
précision rappel f1 score support
0 0.91 0.94 0.92 165
1 0.81 0.73 0.77 60
exactitude 0.88 225
moyenne macro 0.86 0.84 0.85 225
moyenne pondérée 0.88 0.88 0.88 225
Le résultat semble être que RF est en fait meilleur que XGB. Cela pourrait signifier que XGB est peut-être un peu plus surajouté d'une manière ou d'une autre. Le résultat de RFC est également légèrement meilleur que celui de LR.
### **Exécuter le modèle sélectionné en poursuivant "Ajustement des paramètres via la recherche par quadrillage"**
Supposons maintenant que nous ayons choisi le modèle de classificateur de la forêt aléatoire "Random Forest Classifier". Nous pouvons effectuer une nouvelle recherche sur la grille de ce modèle pour voir s'il est possible d'obtenir des résultats un peu plus performants.
Rappelez-vous que notre objectif est toujours d'optimiser le rappel dans ce cas, en minimisant le nombre de faux négatifs concernant les risques possibles pour l'USI lors de la rencontre avec le patient, nous utiliserons donc 'recall_score' pour réajuster le quadrillage ci-dessous. Une fois de plus, la validation croisée 10 fois sera utilisée comme d'habitude, étant donné que notre ensemble de test ci-dessus a toujours été fixé à environ 12 % de ces 2915 enregistrements.
from sklearn.model_selection import GridSearchCV
# Créer la grille de paramètres sur la base des résultats de la recherche aléatoire
param_grid = {'bootstrap': [Vrai],
'ccp_alpha': [0.0],
'class_weight': [Aucun],
'criterion': ['gini', 'entropy'],
'max_depth': [Aucun],
'max_features': ['auto', 'log2'],
'max_leaf_nodes': [Aucun],
'max_samples': [Aucun],
'min_impurity_decrease': [0.0],
'min_impurity_split': [Aucun],
'min_samples_leaf': [1, 2, 4],
'min_samples_split': [2, 4],
'min_weight_fraction_leaf': [0.0],
'n_estimators': [100, 125],
#'n_jobs': [Aucun],
'oob_score': [False],
'random_state': [Aucun],
#'verbose': 0,
'warm_start': [False]
}
#Ajuster par matrice de confusion
from sklearn.metrics import roc_curve, précision_recall_curve, auc, make_scorer, recall_score, accuracy_score, précision_score, confusion_matrix
scorers = {
'recall_score': make_scorer(recall_score),
'précision_score': make_scorer(précision_score),
'accuracy_score': make_scorer(accuracy_score)
}
# Créer un modèle de base
rfc = RandomForestClassifier()
# Instancier le modèle de quadrillage
grid_search = GridSearchCV(estimator = rfc, param_grid = param_grid,
scoring=scorers, refit='recall_score',
cv = 10, n_jobs = -1, verbose = 2)
train_features = X_train_res
grid_search.fit(train_features, train_labels)
rf_best_grid = grid_search.best_estimator_
rf_best_grid.fit(train_features, train_labels)
rf_predictions = rf_best_grid.predict(X3_test)
print(accuracy_score(y3_test, rf_predictions))
print(confusion_matrix(y3_test, rf_predictions))
print(classification_report(y3_test, rf_predictions))
0.92
[[ 46 14]
[ 4 161]]
précision rappel f1 score support
0 0.92 0.77 0.84 60
1 0.92 0.98 0.95 165
exactitude 0.92 225
moyenne macro 0.92 0.87 0.89 225
moyenne pondérée 0.92 0.92 0.92 225
Le résultat a montré qu'un quadrillage a permis d'augmenter légèrement la précision globale, tout en maintenant le FN au même niveau.
Traçons également les comparaisons avec l'ASC :
confusion_matrix4 = pd.crosstab(y3_test, rf_predictions, rownames=['Actual'], colnames=['Predicted'])
sns.heatmap(confusion_matrix4, annot=Vrai, fmt = 'g', cmap="YlOrBr")
print("LABEL ENCODING + STRATIFY")
print(classification_report(y3_test, 1-y3_hat))
print("ASC = ",roc_auc_score(y3_test, 1-y3_hat),'\n\n')
print("SMOTE")
print(classification_report(y3_test, 1-y_res_hat))
print("ASC = ",roc_auc_score(y3_test, 1-y_res_hat), '\n\n')
print("SMOTE + LBG Selected Weights + RF Grid Search")
print(classification_report(y3_test, rf_predictions))
print("ASC = ",roc_auc_score(y3_test, rf_predictions), '\n\n\n')
y_res_hat_probs = LR.predict_proba(X3_test)
y_res_hat_probs = y_res_hat_probs[:, 1]
predictions_rf_probs = rf_best_grid.predict_proba(X3_test) #(X_validation)
predictions_rf_probs = predictions_rf_probs[:, 1]
fpr_res, tpr_res, _ = roc_curve(y3_test, 1-y_res_hat_probs)
fpr_rf_res, tpr_rf_res, _ = roc_curve(y3_test, predictions_rf_probs)
plt.figure(figsize=(10,10))
plt.plot([0, 1], [0, 1], 'k--')
plt.plot(fpr, tpr, label="Base")
plt.plot(fpr2,tpr2,label="Label Encoded")
plt.plot(fpr3,tpr3,label="Stratify")
plt.plot(fpr_res,tpr_res,label="SMOTE")
plt.plot(fpr_rf_res,tpr_rf_res,label="SMOTE + RF GRID")
plt.xlabel('False positive rate')
plt.ylabel('Vrai positive rate')
plt.title('ROC curve')
plt.legend(loc="best")
plt.show()
CODAGE D'ÉTIQUETTES + STRATIFICATION
précision rappel f1 score support
0 0.95 0.58 0.72 60
1 0.87 0.99 0.92 165
exactitude 0.88 225
moyenne macro 0.91 0.79 0.82 225
moyenne pondérée 0.89 0.88 0.87 225
ASC = 0.7856060606060606
MODIFICATION
précision rappel f1 score support
0 0.69 0.75 0.72 60
1 0.91 0.88 0.89 165
exactitude 0.84 225
moyenne macro 0.80 0.81 0.81 225
moyenne pondérée 0.85 0.84 0.85 225
ASC = 0.8143939393939394
MODIFICATION + LBG Pondérations sélectionnées + Quadrillage RF
précision rappel f1 score support
0 0.92 0.77 0.84 60
1 0.92 0.98 0.95 165
exactitude 0.92 225
moyenne macro 0.92 0.87 0.89 225
moyenne pondérée 0.92 0.92 0.92 225
ASC = 0.8712121212121211
Le résultat a montré qu'après des comparaisons d'algorithmes et un quadrillage suivant, nous avons réussi à faire passer l'ASC de 78 % à 87 %, avec une précision globale de 92 % et un rappel de 77 %.
### **Récapitulatif de l'approche "ML traditionnelle"**
Qu'en est-il réellement de ce résultat ? Il est correct pour un processus manuel de base avec des algorithmes ML traditionnels. Comment ce résultat apparaît-il dans les tableaux de compétition Kaggle ? Eh bien, il ne figurerait pas dans le tableau de classement. J'ai passé le jeu de données brutes par le service AutoML actuel de DataRobot, le meilleur résultat serait un ASC équivalent de ~90+% (à confirmer avec des données similaires) avec le modèle " Classificateur arborescent XGB avec fonctions d'apprentissage non supervisé " (XGB Trees Classifier with Unsupervised Learning Features), sur une comparaison des 43 meilleurs modèles. C'est peut-être le genre de modèle de base que nous devrions utiliser si nous voulons vraiment être compétitifs sur Kaggle. Je joindrai également la liste des meilleurs résultats par rapport aux modèles dans le github. Finalement, pour les cas réels spécifiques aux sites de soins, j'ai le sentiment que nous devons également intégrer un certain degré d'approches d'apprentissage profond personnalisées, comme mentionné dans la section "Données et tâches" de ce billet. Bien sûr, dans les cas réels, l'endroit où collecter des colonnes de données de qualité pourrait également être une question initiale.
## L'approche IntegratedML?
Ce qui précède est un processus de ML dit traditionnel, qui comprend normalement l'EDA des données, l'ingénierie des caractéristiques, la sélection des caractéristiques, la sélection des modèles, et l'optimisation des performances par la quadrillage, etc. C'est l'approche la plus simple à laquelle j'ai pu penser jusqu'à présent pour cette tâche, et nous n'avons même pas encore abordé le déploiement du modèle et les cycles de vie de la gestion des services - nous le ferons dans le prochain article, en examinant comment nous pourrions tirer parti de Flask/FastAPI/IRIS et déployer ce modèle de ML de base dans une pile de services de démonstration de la radiographie de Covid-19.
IRIS dispose désormais d'IntegratedML, qui est une enveloppe SQL élégante d'options puissantes d'AutoMLs. Dans la deuxième partie, nous verrons comment accomplir la tâche susmentionnée dans le cadre d'un processus simplifié, de sorte que nous n'aurons plus à nous préoccuper de la sélection des caractéristiques, de la sélection des modèles, de l'optimisation des performances, etc.
Jusqu'ici, cet article pourrait être trop long pour une note de 10 minutes visant à intégrer rapidement les mêmes données, c'est pourquoi je le déplace vers [l'article suivant, partie II](https://fr.community.intersystems.com/post/pr%C3%A9dictions-de-covid-19-icu-ml-vs-integratedml-partie-ii).