Repository: oppia/oppia

Issue:
Incorrect validate() in extensions.classifiers.LDAStringClassifier
In LDAStringClassifier file, validate() function has the following tests:

```
for list_property in list_properties:
            if list_property not in classifier_data:
                raise utils.ValidationError(
                    'Expected %s to be a key in classifier_data' %
                    list_property)
            if not isinstance(classifier_data[list_property], list):
                raise utils.ValidationError(
                    'Expected %s to be a list, received %s' % (
                        list_property, classifier_data[list_property]))
            for value in classifier_data[list_property]:
                if not isinstance(value, int):
                    raise utils.ValidationError(
                        'Expected values of %s to be a int, received %s' % (
                            list_property, value))
```

where list_properties are :
 list_properties = [
            '_w_dp',
            '_b_dl',
            '_l_dp',
            '_c_dl',
            '_c_lw',
            '_c_l'
        ]

The testing is incorrect as each element of _w_dp is not an integer (_w_dp is a List of List of integers) and all the other list properties are Numpy arrays. Consequently, the validate() function doesn't return True for any trained classifier.

To reproduce (classifier_services.py) :
```
classifier_id = classifier_models.ClassifierModel._generate_id(exp_id)
        sc = classifier_registry.Registry.get_classifier_by_id(
            feconf.INTERACTION_CLASSIFIER_MAPPING['TextInput'])
        sc.train(training_examples)
        classifier = classifier_domain.Classifier(classifier_id,exp_id,exploration.version,'random',feconf.INTERACTION_CLASSIFIER_MAPPING['TextInput'],sc.to_dict(),1)
        save_classifier(classifier)
```
After training the clasifier, save_classifier() doesn't work because validate() function always raises an exception.

Possible fix: Figure out the actual types of all attributes in list_properties and validate accordingly.

@seanlip, @anmolshkl . Your thoughts? 


Discussion:
@giritheja can you please take a look at this? We missed this in the PR but we really need to identify which variables are of type numpy.array and validate them properly.

Resolving patch:
diff --git a/extensions/classifiers/LDAStringClassifier/LDAStringClassifier.py b/extensions/classifiers/LDAStringClassifier/LDAStringClassifier.py
--- a/extensions/classifiers/LDAStringClassifier/LDAStringClassifier.py
+++ b/extensions/classifiers/LDAStringClassifier/LDAStringClassifier.py
@@ -623,12 +623,16 @@ def validate(self, classifier_data):
             '_label_to_id',
             '_word_to_id'
         ]
-        list_properties = [
+        list_of_list_int_properties = [
             '_w_dp',
-            '_b_dl',
             '_l_dp',
             '_c_dl',
-            '_c_lw',
+            '_c_lw'
+        ]
+        list_of_list_bool_properties = [
+            '_b_dl'
+        ]
+        list_properties = [
             '_c_l'
         ]
 
@@ -685,3 +689,37 @@ def validate(self, classifier_data):
                     raise utils.ValidationError(
                         'Expected values of %s to be a int, received %s' % (
                             list_property, value))
+
+        for list_of_list_property in list_of_list_int_properties:
+            if list_of_list_property not in classifier_data:
+                raise utils.ValidationError(
+                    'Expected %s to be a key in classifier_data' %
+                    list_of_list_property)
+            if not isinstance(classifier_data[list_of_list_property], list):
+                raise utils.ValidationError(
+                    'Expected %s to be a list, received %s' % (
+                        list_of_list_property,
+                        classifier_data[list_of_list_property]))
+            for inner_list in classifier_data[list_of_list_property]:
+                for value in inner_list:
+                    if not isinstance(value, int):
+                        raise utils.ValidationError(
+                            'Expected values of %s to be a int, received %s' % (
+                                list_of_list_property, value))
+
+        for list_of_list_property in list_of_list_bool_properties:
+            if list_of_list_property not in classifier_data:
+                raise utils.ValidationError(
+                    'Expected %s to be a key in classifier_data' %
+                    list_of_list_property)
+            if not isinstance(classifier_data[list_of_list_property], list):
+                raise utils.ValidationError(
+                    'Expected %s to be a list, received %s' % (
+                        list_of_list_property,
+                        classifier_data[list_of_list_property]))
+            for inner_list in classifier_data[list_of_list_property]:
+                for value in inner_list:
+                    if not isinstance(value, bool):
+                        raise utils.ValidationError(
+                            'Expected values of %s to be bool, received %s' % (
+                                list_of_list_property, value))
diff --git a/extensions/classifiers/LDAStringClassifier/LDAStringClassifierTest.py b/extensions/classifiers/LDAStringClassifier/LDAStringClassifierTest.py
--- a/extensions/classifiers/LDAStringClassifier/LDAStringClassifierTest.py
+++ b/extensions/classifiers/LDAStringClassifier/LDAStringClassifierTest.py
@@ -310,7 +310,7 @@ def test_validate(self):
 
         # Verify validation error is raised when the list values are string
         # instead of int.
-        classifier_data['_w_dp'] = ['abc']
+        classifier_data['_w_dp'] = [['abc']]
         with self.assertRaisesRegexp(utils.ValidationError, (
             'Expected values of _w_dp to be a int')):
             self.classifier.validate(classifier_data)


Test patch: