Let me build on your suggestion by comparing Classify and NetTrain on this problem.
I will first import the dataset as "RawData" to avoid the automatic conversion of stuff like "2" from string to integer (just to avoid dealing with mixed data types).
data = Import["/Users/giulio/Downloads/car.data", "RawData"];
Then we split the data before the conversion to rules so we can use it for the deep learning training as well.
SeedRandom[1234];
{trainingData, validationData} = ResourceFunction["TrainTestSplit"][data];
classifierTraining = Most@# -> Last@# & /@ trainingData;
classifierTesting = Most@# -> Last@# & /@ validationData;
Now we can train the classifier as suggested by @Rohit Namjoshi
classifier = Classify[classifierTraining];
cm = ClassifierMeasurements[classifier, classifierTesting]
which gives us
cm["Accuracy"]
(* 0.849711 *)
which is not super high. Using the same one hot encoding as proposed by the OP we get a better result
classifier2 = Classify[classifierTraining, FeatureExtractor -> "IndicatorVector"];
cm2 = ClassifierMeasurements[classifier2, classifierTesting];
cm2["Accuracy"]
(* 0.942197 *)
Now let's see how this can be done with a NN model.
The definition of the class encoders was ok
buyingEncoder = NetEncoder[{"Class", {"vhigh", "high", "med", "low"}, "UnitVector"}];
maintEncoder = buyingEncoder;
doorsEncoder = NetEncoder[{"Class", {"2", "3", "4", "5more"}, "UnitVector"}];
personsEncoder = NetEncoder[{"Class", {"2", "4", "more"}, "UnitVector"}];
lugBootEncoder = NetEncoder[{"Class", {"small", "med", "big"}, "UnitVector"}];
safetyEncoder = NetEncoder[{"Class", {"low", "med", "high"}, "UnitVector"}];
Unfortunately NetChain does not support multiple inputs so we need to create a graph
net = NetInitialize@NetGraph[
{CatenateLayer[], LinearLayer[30], ElementwiseLayer[Ramp], LinearLayer[4], SoftmaxLayer[]},
{
(NetPort /@ {"Buying", "MainT", "Doors", "Persons", "LugBoot", "Safety"}) ->
1 -> 2 -> 3 -> 4 -> 5
},
"Buying" -> buyingEncoder,
"MainT" -> maintEncoder,
"Doors" -> doorsEncoder,
"Persons" -> personsEncoder,
"LugBoot" -> lugBootEncoder,
"Safety" -> safetyEncoder,
"Output" -> NetDecoder[{"Class", {"acc", "good", "unacc", "vgood"}}]
]
You can still use it on untagged data
net[{"vhigh", "vhigh", "2", "2", "small", "low"}]
(* "vgood" *)
For the training though, we need to put the data in a format that plays well with the different network ports
{netTraining, netValidation} = AssociationThread[
{"Buying", "MainT", "Doors", "Persons", "LugBoot", "Safety", "Output"},
Transpose[#]
] & /@ {trainingData, validationData};
Now we can train, stopping when the error on the validation set stops improving (I noticed that the loss can go much lower but you don't get any concrete benefit and you might start overfitting the small validation set)
trainingRes = NetTrain[net, netTraining, All, ValidationSet -> netValidation,
TrainingStoppingCriterion -> <|"Criterion" -> "ErrorRate", "Patience" -> 100|>]
The trained model can now be extracted and used
trainedNet = trainingRes["TrainedNet"];
trainedNet[{"vhigh", "vhigh", "2", "2", "small", "low"}]
(* "unacc" *)
It also gets even higher accuracy than the second classifier
cmNet = ClassifierMeasurements[trainedNet, classifierTesting];
cmNet["Accuracy"]
(* 0.991329 *)
OK now that all is said and done we can go back to Classify again and produce something of similar accuracy without all the network construction effort
classifier3 =
Classify[classifierTraining, FeatureExtractor -> "IndicatorVector",
Method -> {"NeuralNetwork", "NetworkDepth" -> 2, MaxTrainingRounds -> 200}]
cm3 = ClassifierMeasurements[classifier3, classifierTesting];
cm3["Accuracy"]
(* 0.99422 *)