Multi-Table Tutorial with the core API
In this notebook, we will learn how to train a classifier for a simple multi-table dataset. It is recommended to see the single table tutorial first and understand the basics of Khiops dictionary files.
Downloading samples from https://github.com/KhiopsML/khiops-samples/releases/download/11.0.0/khiops-samples-11.0.0.zip
Samples dataset successfully downloaded to /khiops_data/samples
The Accidents Dataset
We'll train a multi-table classifier on a the dataset Accidents. This dataset describes the characteristics of traffic accidents that happened in France in 2018. It has two tables with the following schema:
- The main table
Accident - The table
Vehiclein a0:nrelationship withAccident - The table
Userin a0:nrelationship withVehicle - The table
Placein a0:1relationship withAccident
Let's first check the content of the tables:
# Store the locations of the `AccidentsSummary` dataset
accidents_table_path = f"{kh.get_samples_dir()}/Accidents/Accidents.txt"
vehicles_table_path = f"{kh.get_samples_dir()}/Accidents/Vehicles.txt"
users_table_path = f"{kh.get_samples_dir()}/Accidents/Users.txt"
places_table_path = f"{kh.get_samples_dir()}/Accidents/Places.txt"
# Print the first lines of the data files
print("Accidents table:")
display(pd.read_csv(accidents_table_path, sep="\t").head(5))
print("Vehicles table:")
display(pd.read_csv(vehicles_table_path, sep="\t").head(5))
print("Users table:")
display(pd.read_csv(users_table_path, sep="\t").head(5))
print("Places table:")
display(pd.read_csv(places_table_path, sep="\t", low_memory=False).head(5))
| AccidentId | Gravity | Date | Hour | Light | Department | Commune | InAgglomeration | IntersectionType | Weather | CollisionType | PostalAddress | GPSCode | Latitude | Longitude | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 201800000001 | NonLethal | 2018-01-24 | 15:05:00 | Daylight | 590 | 5 | No | Y-type | Normal | 2Vehicles-BehindVehicles-Frontal | route des Ansereuilles | M | 50.55737 | 2.55737 |
| 1 | 201800000002 | NonLethal | 2018-02-12 | 10:15:00 | Daylight | 590 | 11 | Yes | Square | VeryGood | NoCollision | Place du général de Gaul | M | 50.52936 | 2.52936 |
| 2 | 201800000003 | NonLethal | 2018-03-04 | 11:35:00 | Daylight | 590 | 477 | Yes | T-type | Normal | NoCollision | Rue nationale | M | 50.51243 | 2.51243 |
| 3 | 201800000004 | NonLethal | 2018-05-05 | 17:35:00 | Daylight | 590 | 52 | Yes | NoIntersection | VeryGood | 2Vehicles-Side | 30 rue Jules Guesde | M | 50.51974 | 2.51974 |
| 4 | 201800000005 | NonLethal | 2018-06-26 | 16:05:00 | Daylight | 590 | 477 | Yes | NoIntersection | Normal | 2Vehicles-Side | 72 rue Victor Hugo | M | 50.51607 | 2.51607 |
| AccidentId | VehicleId | Direction | Category | PassengerNumber | FixedObstacle | MobileObstacle | ImpactPoint | Maneuver | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 201800000001 | A01 | Unknown | Car<=3.5T | 0 | NaN | Vehicle | RightFront | TurnToLeft |
| 1 | 201800000001 | B01 | Unknown | Car<=3.5T | 0 | NaN | Vehicle | LeftFront | NoDirectionChange |
| 2 | 201800000002 | A01 | Unknown | Car<=3.5T | 0 | NaN | Pedestrian | NaN | NoDirectionChange |
| 3 | 201800000003 | A01 | Unknown | Motorbike>125cm3 | 0 | StationaryVehicle | Vehicle | Front | NoDirectionChange |
| 4 | 201800000003 | B01 | Unknown | Car<=3.5T | 0 | NaN | Vehicle | LeftSide | TurnToLeft |
| AccidentId | VehicleId | Seat | Category | Gender | TripReason | SafetyDevice | SafetyDeviceUsed | PedestrianLocation | PedestrianAction | PedestrianCompany | BirthYear | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 201800000001 | A01 | 1.0 | Driver | Male | Leisure | SeatBelt | Yes | NaN | NaN | Unknown | 1960.0 |
| 1 | 201800000001 | B01 | 1.0 | Driver | Male | NaN | SeatBelt | Yes | NaN | NaN | Unknown | 1928.0 |
| 2 | 201800000002 | A01 | 1.0 | Driver | Male | NaN | SeatBelt | Yes | NaN | NaN | Unknown | 1947.0 |
| 3 | 201800000002 | A01 | NaN | Pedestrian | Male | NaN | Helmet | NaN | OnLane<=OnSidewalk0mCrossing | Crossing | Alone | 1959.0 |
| 4 | 201800000003 | A01 | 1.0 | Driver | Male | Leisure | Helmet | Yes | NaN | NaN | Unknown | 1987.0 |
| AccidentId | RoadType | RoadNumber | RoadSecNumber | RoadLetter | Circulation | LaneNumber | SpecialLane | Slope | RoadMarkerId | RoadMarkerDistance | Layout | StripWidth | LaneWidth | SurfaceCondition | Infrastructure | Localization | SchoolNear | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 201800000001 | Departamental | 41 | NaN | C | TwoWay | 2.0 | 0 | Flat | NaN | NaN | RightCurve | NaN | NaN | Normal | Unknown | Lane | 0.0 |
| 1 | 201800000002 | Communal | 41 | NaN | D | TwoWay | 2.0 | 0 | Flat | NaN | NaN | LeftCurve | NaN | NaN | Normal | Unknown | Lane | 0.0 |
| 2 | 201800000003 | Departamental | 39 | NaN | D | TwoWay | 2.0 | 0 | Flat | NaN | NaN | Straight | NaN | NaN | Normal | Unknown | Lane | 0.0 |
| 3 | 201800000004 | Departamental | 39 | NaN | NaN | TwoWay | 2.0 | 0 | Flat | NaN | NaN | Straight | NaN | NaN | Normal | Unknown | Lane | 0.0 |
| 4 | 201800000005 | Communal | NaN | NaN | NaN | OneWay | 1.0 | 0 | Flat | NaN | NaN | Straight | NaN | NaN | Normal | Unknown | Lane | 0.0 |
To train a classifier with the Khiops core API, we must specify a multi-table dataset.
The schema is specified via the Khiops dictionary file, let's see the contents its for the Accidents dataset:
Root Dictionary Accident(AccidentId)
{
Categorical AccidentId;
Categorical Gravity;
Date Date;
Time Hour;
Categorical Light;
Categorical Department;
Categorical Commune;
Categorical InAgglomeration;
Categorical IntersectionType;
Categorical Weather;
Categorical CollisionType;
Categorical PostalAddress;
Categorical GPSCode;
Numerical Latitude;
Numerical Longitude;
Entity(Place) Place;
Table(Vehicle) Vehicles;
};
Dictionary Place(AccidentId)
{
Categorical AccidentId;
Categorical RoadType;
Categorical RoadNumber;
Categorical RoadSecNumber;
Categorical RoadLetter;
Categorical Circulation;
Numerical LaneNumber;
Categorical SpecialLane;
Categorical Slope;
Categorical RoadMarkerId;
Numerical RoadMarkerDistance;
Categorical Layout;
Numerical StripWidth;
Numerical LaneWidth;
Categorical SurfaceCondition;
Categorical Infrastructure;
Categorical Localization;
Categorical SchoolNear;
};
Dictionary Vehicle(AccidentId, VehicleId)
{
Categorical AccidentId;
Categorical VehicleId;
Categorical Direction;
Categorical Category;
Numerical PassengerNumber;
Categorical FixedObstacle;
Categorical MobileObstacle;
Categorical ImpactPoint;
Categorical Maneuver;
Table(User) Users;
};
Dictionary User(AccidentId, VehicleId) {
Categorical AccidentId;
Categorical VehicleId;
Categorical Seat;
Categorical Category;
Categorical Gender;
Categorical TripReason;
Categorical SafetyDevice;
Categorical SafetyDeviceUsed;
Categorical PedestrianLocation;
Categorical PedestrianAction;
Categorical PedestrianCompany;
Numerical BirthYear;
};
We note that the Accident table contains a special Table variable. This special variable allows to create a 1:n relation. The target table is in its argument between parentheses (Vehicle).
Training the Classifier
While the dictionary file specifies the table schemas and their relations, it does not contain any information about the data files. On a single table task the third mandatory parameter of train_predictor specifies the data table file. For multi-table tasks this parameter is still used to specify the main table; to specify the rest of the tables we use the optional parameter additional_data_tables.
The additional_data_tables parameter is a Python dict whose keys are the data paths of each table and the values are their file paths (in our case just a single pair). For more information about data-paths see basics of Khiops dictionary files.
By default, the Khiops creates at most 100 multi-table variables (max_variables) and 10 random decision trees (max_trees). We change these values for this example:
model_report_path, model_kdic_path = kh.train_predictor(
accidents_kdic_path,
"Accident",
accidents_table_path,
"Gravity",
"./mt_results/AccidentsReport.khj",
additional_data_tables={
"Vehicles": vehicles_table_path,
"Vehicles/Users": users_table_path,
"Place": places_table_path,
},
max_constructed_variables=1000,
max_trees=0,
)
Displaying the Classifier’s Accuracy and AUC
Khiops calculates evaluation metrics for the train/test split datasets. We access them by loading the report file into an AnalysisResults object. Let's check this out:
model_report = kh.read_analysis_results_file(model_report_path)
train_performance = model_report.train_evaluation_report.get_snb_performance()
test_performance = model_report.test_evaluation_report.get_snb_performance()
print(f"Accidents train accuracy: {train_performance.accuracy}")
print(f"Accidents train auc : {train_performance.auc}")
print(f"Accidents test accuracy : {test_performance.accuracy}")
print(f"Accidents test auc : {test_performance.auc}")
Accidents train accuracy: 0.944799
Accidents train auc : 0.844608
Accidents test accuracy : 0.945186
Accidents test auc : 0.837824
Deploying the Classifier
We are now going to deploy the Accidents classifier that we have just trained.
To this end we use the model dictionary file that the train_predictor function created in conjunction the the deploy_model core API function. Note that the name of the dictionary for the model is SNB_Accident.
Similarly to the model training we must set the additional_data_tables parameter to take into account the secondary table.
For simplicity, we'll just deploy on the whole data table file (one usually would do this on new data):
accidents_deployed_path = "./mt_results/accidents_deployed.txt"
kh.deploy_model(
model_kdic_path, # Path of the model dictionary file
"SNB_Accident", # Name of the model dictionary
accidents_table_path, # Path of the table to deploy the model
accidents_deployed_path, # Path of the output (deployed) file
additional_data_tables = { # Pairs of {"data-path": "file-path"} describing the other tables
"Vehicles": vehicles_table_path,
"Vehicles/Users": users_table_path,
"Place": places_table_path,
},
)
The deployed model is in the path in the variable accidents_deployed_path, let's have a look at it
| AccidentId | PredictedGravity | ProbGravityLethal | ProbGravityNonLethal | |
|---|---|---|---|---|
| 0 | 201800000001 | NonLethal | 0.165470 | 0.834530 |
| 1 | 201800000002 | NonLethal | 0.148160 | 0.851840 |
| 2 | 201800000003 | NonLethal | 0.050592 | 0.949408 |
| 3 | 201800000004 | NonLethal | 0.022976 | 0.977024 |
| 4 | 201800000005 | NonLethal | 0.011898 | 0.988102 |
| 5 | 201800000006 | NonLethal | 0.138954 | 0.861046 |
| 6 | 201800000007 | NonLethal | 0.101393 | 0.898607 |
| 7 | 201800000008 | NonLethal | 0.120581 | 0.879419 |
| 8 | 201800000009 | NonLethal | 0.193743 | 0.806257 |
| 9 | 201800000010 | NonLethal | 0.049909 | 0.950091 |
The deployed data table file contains three columns
- PredictedGravity: Which contains the class prediction
- ProbGravityLethal, ProbGravityNonLethal: Which contain the probability of each class of Accidents.