This model is a SVM model with Linear Kernel which has the objective of predict the appropriate category based on various features.
There are 5 different categories:
Basic
Standard
Premium
Exclusive
Luxury
How is the dataset created?
The dataset is being created using a function which these lists and dictionaries:
To train the model, first of all the dataset must be properly prepared, that means applying One Hot Encoding, Labeling Encoding, normalization...
Python
1 # process dataset
23 # 1. apply label encoding in target df creation
4 label_encoder = LabelEncoder()
5 target_df_encoded = label_encoder.fit_transform(df['category'])
67 # 2. drop unneeded cols to create the features_df
8 features_df = df.drop(['category', 'whiskey_name'], axis=1)
910 # 3. apply One Hot to categorical independent columns
1112 # 3.1 identify categorical and numerical columns
13 categorical_cols = features_df.select_dtypes(include=['object', 'bool']).columns
14 numeric_cols = features_df.select_dtypes(include=['int64', 'float64']).columns
1516 # 3.2 apply One Hot to categorical columns
17 OH_encoder = OneHotEncoder(sparse_output=False, handle_unknown='ignore')
18 OH_encoded = OH_encoder.fit_transform(features_df[categorical_cols])
1920 OH_feature_names = OH_encoder.get_feature_names_out(categorical_cols)
21 OH_df = pd.DataFrame(OH_encoded, columns=OH_feature_names)
2223 # 3.3 keep numerical columns
24 numeric_df = features_df[numeric_cols].reset_index(drop=True)
2526 # 4. concat both dataframes
27 features_df = pd.concat([numeric_df, OH_df], axis=1)
2829 # 5. save the feature columns (this includes both categorical ones and numerical ones)
30 feature_columns = list(numeric_cols) + list(OH_encoder.get_feature_names_out(categorical_cols))
3132 # 6. normalize data, this is because SVM needs the data being normalized
33 scaler = MinMaxScaler()
34 normalized_features = scaler.fit_transform(features_df)
35 normalized_df = pd.DataFrame(normalized_features, columns=features_df.columns)
3637 # 7. add back target
38 normalized_df['category'] = target_df_encoded
Then, in prediction phase, the model would need the same columns as the training phase, so the utils should be saved to use them in prediction phase
Python
1 # save utils for later
2 dirname = "model_files"
3 # encoder
4 with open(f'{dirname}/label_encoder.pkl', 'wb') as f:
5 pickle.dump(label_encoder, f)
6 # OH encoded columns
7 with open(f'{dirname}/feature_columns.pkl', 'wb') as f:
8 pickle.dump(feature_columns, f)
9 # OH Encoder
10 with open(f'{dirname}/one_hot_encoder.pkl', 'wb') as f:
11 pickle.dump(OH_encoder, f)
12 # Normalizer
13 with open(f'{dirname}/scaler.pkl', 'wb') as f:
14 pickle.dump(scaler, f)
Also the data should be splitted in train and test data
Python
1 # split data and save
2 df_X_normalized = normalized_df.drop(columns=['category'])
3 df_Y = df[['category']]
45 df_X_normalized_train, df_X_normalized_test, df_Y_train, df_Y_test = train_test_split(df_X_normalized, df_Y, test_size=0.2, random_state=100)
In training phase the model would be created and trained with the splitted data to be saved later.
Python
1 # algorithm method creation
2 svm_classifier = SVC(kernel='linear', C=50)
34 # train model with the classifier method
5 svm_model = svm_classifier.fit(df_X_normalized_train, df_Y_train['category'])
67 modelname = "svm_model.pkl"
8 try:
9 with open(modelname, 'wb') as file:
10 pickle.dump(svm_model, file)
1112 print(f"Model saved as {modelname}")
13 except Exception as e:
14 print(f"An error occurred while saving the model: {e}")
Also this model was tested with a new dataset, using the same function to create it and the same encoders as training, in this case we used a new dataset of 500 rows, but it also has been tested with a new dataset with 50K rows.
Python
1 from sklearn.preprocessing import LabelEncoder
2 from sklearn.metrics import accuracy_score
3 import pickle
4 import pandas as pd
5 import random
67 # load processors
8 dirname = "model_files"
9 with open(f'{dirname}/label_encoder.pkl', 'rb') as f:
10 label_encoder = pickle.load(f)
11 with open(f'{dirname}/one_hot_encoder.pkl', 'rb') as f:
12 OH_encoder = pickle.load(f)
13 with open(f'{dirname}/scaler.pkl', 'rb') as f:
14 scaler = pickle.load(f)
15 with open(f'{dirname}/feature_columns.pkl', 'rb') as f:
16 feature_columns = pickle.load(f)
1718 # load model
19 with open('whiskey_classificator_model.pkl', 'rb') as f:
20 svm_model = pickle.load(f)
2122 print("Model has been loaded")
232425 n_tests = 100
26 accuracy_results = {}
27 accuracies = []
28 for i in range(0, n_tests):
2930 # generate new dataset
31 nr = random.randint(500, 50000)
32 df = generate_whiskey(num_rows=nr)
3334 # separate target
35 target_df = df['category'].reset_index(drop=True)
36 # drop uneeded columns
37 features_df = df.drop(['category', 'whiskey_name'], axis=1)
3839 # identify categorical and numerical columns
40 categorical_cols = features_df.select_dtypes(include=['object', 'bool']).columns
41 numeric_cols = features_df.select_dtypes(include=['int64', 'float64']).columns
4243 # apply One Hot to categorical columns
44 OH_encoded = OH_encoder.transform(features_df[categorical_cols]) # Use transform, no fit
45 OH_feature_names = OH_encoder.get_feature_names_out(categorical_cols)
46 OH_df = pd.DataFrame(OH_encoded, columns=OH_feature_names)
4748 # keep numerical columns
49 numeric_df = features_df[numeric_cols].reset_index(drop=True)
5051 # concat both dataframes
52 features_df = pd.concat([numeric_df, OH_df], axis=1)
5354 # get the missing columns in new dataset using the list saved
55 missing_cols = set(feature_columns) - set(features_df.columns)
56 for col in missing_cols:
57 features_df[col] = 0 # add columns with value = 0
5859 # re order the columns
60 features_df = features_df[feature_columns]
6162 # normalize using the scaler loaded (use transform, not fit_transform)
63 normalized_features = scaler.transform(features_df)
64 normalized_df = pd.DataFrame(normalized_features, columns=feature_columns)
6566 # add back encoded target
67 normalized_df['category'] = target_df.reset_index(drop=True)
68 # apply label encoder to target column
69 normalized_df['category'] = label_encoder.transform(normalized_df['category'])
7071 # drop category before predict
72 normalized_df = normalized_df.drop(['category'], axis=1)
7374 # predict with normalized_df
75 predictions = svm_model.predict(normalized_df)
7677 # create a df with the results
78 result_df = df.copy()
7980 result_df['Predicted category'] = predictions
8182 # apply label encoder to target column
83 result_df['category'] = label_encoder.transform(result_df['category'])
848586 # calculate the accuracy (prediction rate)
87 accuracy = accuracy_score(result_df['category'], result_df['Predicted category'])
8889 # save to results dict
90 accuracy_results[f"test {i+1}"] = {
91 "Rows": nr,
92 "Accuracy": f"{accuracy * 100:.2f}%"
93 }
94 accuracies.append(accuracy)
9596 # show the results
97 results_df = pd.DataFrame.from_dict(accuracy_results, orient='index')
98 print("\nSummary Table:")
99 display(results_df)
100101 # show average accuracy
102 mean_accuracy = np.mean(accuracies)
103 print(f"\nAverage Accuracy across {n_tests} tests: {mean_accuracy * 100:.2f}%")
10 Test process have been done and these are the results
Testing results table
Dataset used in trainning
The dataset was created using a function which emulates the data that could be used in a real whiskey classification.