Vérification de la version¶

In [1]:
#documentation -> https://fracpete.github.io/python-weka-wrapper/
#version du package "weka wrapper" installé
from importlib_metadata import version
ver = version("python-weka-wrapper3")
print(f"Version du package : {ver}")
Version du package : 0.3.2

Démarrage de la JVM¶

In [2]:
#démarrer la machine virtuelle
import weka.core.jvm as jvm

jvm.start(packages=True, max_heap_size="512m",
          class_path=[
              "C:/Users/ricco/anaconda3/envs/env_weka/Lib/site-packages/weka/lib/weka.jar",
              "C:/Users/ricco/anaconda3/envs/env_weka/Lib/site-packages/org.jpype.jar"
          ])
DEBUG:weka.core.jvm:Adding user-supplied classpath=C:/Users/ricco/anaconda3/envs/env_weka/Lib/site-packages/weka/lib/weka.jar
DEBUG:weka.core.jvm:Adding user-supplied classpath=C:/Users/ricco/anaconda3/envs/env_weka/Lib/site-packages/org.jpype.jar
DEBUG:weka.core.jvm:Adding bundled jars
DEBUG:weka.core.jvm:Classpath=['C:/Users/ricco/anaconda3/envs/env_weka/Lib/site-packages/weka/lib/weka.jar', 'C:/Users/ricco/anaconda3/envs/env_weka/Lib/site-packages/org.jpype.jar', 'c:\\Users\\ricco\\anaconda3\\envs\\env_weka\\Lib\\site-packages\\weka\\lib\\arpack_combined.jar', 'c:\\Users\\ricco\\anaconda3\\envs\\env_weka\\Lib\\site-packages\\weka\\lib\\core.jar', 'c:\\Users\\ricco\\anaconda3\\envs\\env_weka\\Lib\\site-packages\\weka\\lib\\mtj.jar', 'c:\\Users\\ricco\\anaconda3\\envs\\env_weka\\Lib\\site-packages\\weka\\lib\\python-weka-wrapper.jar', 'c:\\Users\\ricco\\anaconda3\\envs\\env_weka\\Lib\\site-packages\\weka\\lib\\weka.jar']
DEBUG:weka.core.jvm:MaxHeapSize=512m
DEBUG:weka.core.jvm:Package support enabled
DEBUG:weka.core.jvm:Adding user-supplied classpath=C:/Users/ricco/anaconda3/envs/env_weka/Lib/site-packages/org.jpype.jar
DEBUG:weka.core.jvm:Adding bundled jars
DEBUG:weka.core.jvm:Classpath=['C:/Users/ricco/anaconda3/envs/env_weka/Lib/site-packages/weka/lib/weka.jar', 'C:/Users/ricco/anaconda3/envs/env_weka/Lib/site-packages/org.jpype.jar', 'c:\\Users\\ricco\\anaconda3\\envs\\env_weka\\Lib\\site-packages\\weka\\lib\\arpack_combined.jar', 'c:\\Users\\ricco\\anaconda3\\envs\\env_weka\\Lib\\site-packages\\weka\\lib\\core.jar', 'c:\\Users\\ricco\\anaconda3\\envs\\env_weka\\Lib\\site-packages\\weka\\lib\\mtj.jar', 'c:\\Users\\ricco\\anaconda3\\envs\\env_weka\\Lib\\site-packages\\weka\\lib\\python-weka-wrapper.jar', 'c:\\Users\\ricco\\anaconda3\\envs\\env_weka\\Lib\\site-packages\\weka\\lib\\weka.jar']
DEBUG:weka.core.jvm:MaxHeapSize=512m
DEBUG:weka.core.jvm:Package support enabled

Chargement et préparation des données¶

Chargement, inspection¶

In [3]:
#changement du dossier par défaut
import os
os.chdir("C:/Users/ricco/Desktop/demo")
In [4]:
#chargement du fichier "arff"
from weka.core.converters import Loader
loader = Loader(classname="weka.core.converters.ArffLoader")
data = loader.load_file("working_conditions.arff")

#https://fracpete.github.io/python-weka-wrapper/weka.core.html#module-weka.core.dataset
print(type(data))
<class 'weka.core.dataset.Instances'>
In [5]:
#liste des méthodes et propriétés
#dir(data)
In [6]:
#nombre d'observations
data.num_instances
Out[6]:
864
In [7]:
#nombre de variables
data.num_attributes
Out[7]:
7
In [8]:
#nom des variables
data.attribute_names()
Out[8]:
['profdev', 'conflict', 'regulat', 'jobvar', 'workgrp', 'standrds', 'working']
In [9]:
#afficher les 5 premières lignes
print(data[:5])
@relation working_conditions

@attribute profdev numeric
@attribute conflict numeric
@attribute regulat numeric
@attribute jobvar numeric
@attribute workgrp numeric
@attribute standrds numeric
@attribute working {good,poor}

@data
4.6,3.6,3,4.6,5.2,3.8,good
4.8,5,3.6,5.6,5,5.8,poor
6.4,5.2,4,4.8,5.8,5.8,good
5.6,6.4,1,6,6.4,6.4,good
6.4,6.6,3,6.4,6.2,4,good

Spécification de la variable cible¶

In [10]:
#est-ce qu'une variable cible a été définie ?
data.class_index
Out[10]:
-1
In [11]:
#ou encore
data.has_class()
Out[11]:
False
In [12]:
#la variable cible est la dernière dorénavant
data.class_is_last()
In [13]:
#son numéro
data.class_index
Out[13]:
6
In [14]:
#ou le nom de la variable corresp.
data.class_attribute
Out[14]:
@attribute working {good,poor}

Partition train-test¶

Mélanger au hasard les lignes tout d'abord (on ne sait pas comment les données ont été triées - ou pas - initialement).

In [15]:
#générateur de valeurs aléatoires
#avec une graine de départ
from weka.core.classes import Random
alea = Random(10112025)

#et mélanger les lignes
data.randomize(alea)

#5 premières lignes maintenant
data[:5]
Out[15]:
@relation working_conditions

@attribute profdev numeric
@attribute conflict numeric
@attribute regulat numeric
@attribute jobvar numeric
@attribute workgrp numeric
@attribute standrds numeric
@attribute working {good,poor}

@data
4.4,4.2,4.2,4.2,4.8,6.4,good
4.4,4.4,4.8,3.8,4,4.6,poor
7,7,1.6,5.2,7,5.4,good
3.2,4.8,4.2,5.8,6.2,4.4,good
5,5,2.6,5,5.4,5.2,good

Echantillon d'apprentissage (70%)

In [16]:
#70% pour le train - création d'un filtre
#attention, très bizarre, on retire les derniers (100-70) = 30% en fait
#pour définir le train !!!
from weka.filters import Filter
train_filter = Filter(
    classname="weka.filters.unsupervised.instance.RemovePercentage",
    options=["-P", str(100-70)])

#puis partition
train_filter.inputformat(data)
train = train_filter.filter(data)

#nombre d'instances
print(f"Nombre d'obs : {train.num_instances}")

#5 premières lignes
train[:5]
Nombre d'obs : 605
Out[16]:
@relation working_conditions-weka.filters.unsupervised.instance.RemovePercentage-P30.0

@attribute profdev numeric
@attribute conflict numeric
@attribute regulat numeric
@attribute jobvar numeric
@attribute workgrp numeric
@attribute standrds numeric
@attribute working {good,poor}

@data
4,4.6,4.4,5.2,5.4,4.2,good
3.2,4.2,4.9,4.8,6.2,6.4,good
3.2,4.8,4.2,5.8,6.2,4.4,good
4.8,5.6,4.4,4.6,3,4.8,good
5.2,5.6,3.6,5.8,6.4,6.2,good

Echantillon test (30% -- autre que les 70% en réalité)

In [17]:
#autre "-V" c.-à-d. inverser la sélection
#on conserve les derniers 30% pour le test
test_filter = Filter(
    classname="weka.filters.unsupervised.instance.RemovePercentage",
    options=["-P", str(100-70), "-V"])
#appliquer le filtre
test_filter.inputformat(data)
test = test_filter.filter(data)

#nombre d'instances
print(f"Nombre d'obs : {test.num_instances}")

#premières lignes
test[:5]
Nombre d'obs : 259
Out[17]:
@relation working_conditions-weka.filters.unsupervised.instance.RemovePercentage-P30.0-V

@attribute profdev numeric
@attribute conflict numeric
@attribute regulat numeric
@attribute jobvar numeric
@attribute workgrp numeric
@attribute standrds numeric
@attribute working {good,poor}

@data
4.4,4.2,4.2,4.2,4.8,6.4,good
4.4,4.4,4.8,3.8,4,4.6,poor
7,7,1.6,5.2,7,5.4,good
3.2,4.8,4.2,5.8,6.2,4.4,good
5,5,2.6,5,5.4,5.2,good

Modélisation - Evaluation (SVM Linéaire)(Explorer)¶

Apprentissage¶

In [18]:
#importation et instanciation du classifieur
#voir le paramétrage - noyau polynomial de degré 1
from weka.classifiers import Classifier
svm = Classifier(
        classname="weka.classifiers.functions.SMO",
        options=["-K", "weka.classifiers.functions.supportVector.PolyKernel -E 1.0"])
In [19]:
#modélisation
svm.build_classifier(train)

#comme SVM linéaire, on peut obtenir
#un modèle linéaire explicite
print(svm)
SMO

Kernel used:
  Linear Kernel: K(x,y) = <x,y>

Classifier for classes: good, poor

BinarySMO

Machine linear: showing attribute weights, not support vectors.

        -3.3445 * (normalized) profdev
 +      -0.7607 * (normalized) conflict
 +       0.7921 * (normalized) regulat
 +       0.2263 * (normalized) jobvar
 +      -3.2851 * (normalized) workgrp
 +       1.5302 * (normalized) standrds
 +       2.7034

Number of kernel evaluations: 9137 (69.219% cached)


Test¶

In [20]:
# évaluation sur l'échantillon test
from weka.classifiers import Evaluation

#sert simplement à indiquer la structure des données (attributs, classe)
#il n'y a pas de réapprentissage ici
#on aurait pu passer data en paramètre, ou encore test
evl = Evaluation(train)

#appliquer le svm sur l'échantillon test
evl.test_model(svm, test)
Out[20]:
array([1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 1., 0., 0., 0., 0.,
       1., 0., 1., 0., 0., 1., 0., 0., 0., 0., 0., 0., 1., 0., 1., 0., 0.,
       0., 0., 1., 0., 0., 0., 1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 1.,
       0., 0., 1., 0., 1., 0., 0., 0., 0., 0., 0., 1., 0., 0., 1., 0., 0.,
       1., 0., 0., 0., 0., 1., 1., 0., 0., 0., 1., 0., 0., 1., 0., 0., 0.,
       0., 1., 0., 0., 0., 0., 0., 0., 0., 1., 1., 0., 1., 0., 0., 1., 0.,
       0., 0., 0., 0., 0., 0., 1., 1., 1., 0., 0., 0., 0., 0., 0., 0., 0.,
       1., 0., 0., 0., 0., 1., 0., 0., 1., 0., 0., 0., 0., 1., 0., 1., 0.,
       0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 1.,
       0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0.,
       1., 0., 0., 0., 0., 0., 0., 1., 0., 1., 1., 0., 1., 0., 1., 0., 0.,
       0., 1., 0., 0., 1., 1., 0., 0., 1., 0., 0., 0., 1., 0., 1., 1., 1.,
       0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0.,
       1., 1., 0., 0., 0., 0., 0., 1., 1., 1., 0., 0., 0., 1., 1., 0., 1.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 1., 0., 0., 0., 0.,
       0., 0., 0., 0.])
In [21]:
#résumé
print(evl.summary())
Correctly Classified Instances         199               76.834  %
Incorrectly Classified Instances        60               23.166  %
Kappa statistic                          0.4439
Mean absolute error                      0.2317
Root mean squared error                  0.4813
Relative absolute error                 50.577  %
Root relative squared error            102.0835 %
Total Number of Instances              259     

In [22]:
#matrice de confusion
print(evl.confusion_matrix)
[[153.  22.]
 [ 38.  46.]]

GridSearch (paramètre de régularisation "cost") (Experiments)¶

In [23]:
#instancier la grille de recherche en validation croisée
#indiquer les options à tester
#si, plutôt qu'une plage de "cost", on veut éprouver des valeurs spécifiques
#il faut passer par une boucle
grid = Classifier(classname="weka.classifiers.meta.CVParameterSelection",
                  options=[
                    "-X", "5",  # 5-fold cross-validation
                    "-P", "C 0.1 4.1 1",  #varier C de 0.1 à 4 avec un pas de 1
                    "-W", "weka.classifiers.functions.SMO",  # nom du classifieur
                    "--", "-K", "weka.classifiers.functions.supportVector.PolyKernel -E 1.0" #choix du noyau
                  ])
In [24]:
#entraînement sur "train" (en validation croisée)
grid.build_classifier(train)
In [25]:
#paramètres optimaux indentifiés
print(grid.jobject.getBestClassifierOptions())
['-C', '0.1', '', '', '-L', '0.001', '-P', '1.0E-12', '-N', '0', '-V', '-1', '-W', '1', '-K', 'weka.classifiers.functions.supportVector.PolyKernel -E 1.0 -C 250007', '-calibrator', 'weka.classifiers.functions.Logistic -R 1.0E-8 -M -1 -num-decimal-places 4']
In [26]:
#évaluation sur le test du "meilleur modèle"
evl.test_model(grid,test)

#summary
print(evl.summary())
Correctly Classified Instances         390               75.2896 %
Incorrectly Classified Instances       128               24.7104 %
Kappa statistic                          0.372 
Mean absolute error                      0.2471
Root mean squared error                  0.4971
Relative absolute error                 53.9488 %
Root relative squared error            105.4314 %
Total Number of Instances              518     

Comparaison de modèles (Experiments)¶

Définir la liste des algos.

In [27]:
#liste des algos à tester-- tous avec leurs paramètres par défaut
#très compliqué dès lors qu'on souhaite introduire des paramètres spécifiques
algos = {}

#SMO linéaire
algos["SMO"] = {'classname':'weka.classifiers.functions.SMO'}
#J48 (ersatz de C4.5)
algos["J48"] = {'classname':'weka.classifiers.trees.J48'}
#régression logistique
algos["LR"] = {'classname':'weka.classifiers.functions.Logistic'}
#KNN
algos["KNN"] = {'classname':'weka.classifiers.lazy.IBk'}

#vérif.
for item,detail in algos.items():
    print(item,detail)
SMO {'classname': 'weka.classifiers.functions.SMO'}
J48 {'classname': 'weka.classifiers.trees.J48'}
LR {'classname': 'weka.classifiers.functions.Logistic'}
KNN {'classname': 'weka.classifiers.lazy.IBk'}
In [28]:
#évaluation avec le schéma app/test

#pour disposer de la structure des données
eval = Evaluation(train)

#stockage des résultats
result = {}

#pour chaque méthode
for name,method in algos.items():
    #instanciation
    modele = Classifier(classname=method['classname'])
    #entraînement
    modele.build_classifier(train)
    #évaluation
    eval.test_model(modele,test)
    #récupération de l'accuracy
    result[name] = eval.percent_correct

#affichage des résultats
print(result)
{'SMO': 76.83397683397683, 'J48': 88.22393822393822, 'LR': 83.26898326898326, 'KNN': 87.45173745173746}

Arrêt de la JVM¶

In [29]:
#stop
jvm.stop()