Repository: kserve/kserve

Issue:
Alibi explainer example times out
/kind bug

**What steps did you take and what happened:**

I am rolling out the Alibi Explainer example (as explained here https://github.com/kubeflow/kfserving/tree/v0.6.0/docs/samples/explanation/alibi/imagenet), although I have had to slightly adapt the InferenceService spec (see https://github.com/kubeflow/kfserving/issues/1707) as follows:

```
apiVersion: "serving.kubeflow.org/v1beta1"
kind: "InferenceService"
metadata:
  name: imagenet
  namespace: karl-schriek
spec:
  predictor:
    tensorflow:
      storageUri: "gs://seldon-models/tfserving/imagenet/model"
      resources:
        requests:
          cpu: 0.1
          memory: 5Gi                        
        limits:
          memory: 10Gi
  explainer:
    alibi:
      type: AnchorImages
      storageUri: "gs://seldon-models/tfserving/imagenet/explainer"
      config:
        batch_size: "1" # reduced from 25 in attempt to prevent timeout
        stop_on_first: "True"
        min_samples_start: "1"  # added in attempt to prevent timeout
      resources:
        requests:
          cpu: 0.5
          memory: 5Gi            
        limits:
          memory: 10Gi    
```

Predictor and explainer both roll out successfully. Requesting predictions work fine. Requesting an explaination on a single image results in an eventual timeout. The explainer Pod reports the following:

```
[I 210709 11:10:54 anchor_images:47] Calling explain on image of shape ((1, 299, 299, 3),)
[I 210709 11:10:54 anchor_images:48] anchor image call with {'batch_size': 1, 'stop_on_first': True}
```

After about 20  minutes it eventually also says the following:

```
skimage.measure.label's indexing starts from 0. In future version it will start from 1. To disable this warning, explicitely set the `start_label` parameter to 1.
[E 210709 11:30:11 web:1793] Uncaught exception POST /v1/models/imagenet:explain (127.0.0.1)
    HTTPServerRequest(protocol='http', host='imagenet-explainer-default.karl-schriek.svc.cluster.local', method='POST', uri='/v1/models/imagenet:explain', version='HTTP/1.1', remote_ip='127.0.0.1')
    Traceback (most recent call last):
      File "/usr/local/lib/python3.7/site-packages/tornado/web.py", line 1704, in _execute
        result = await result
      File "/kfserving/kfserving/handlers/http.py", line 105, in post
        response = await model(body, model_type=ModelType.EXPLAINER)
      File "/kfserving/kfserving/kfmodel.py", line 58, in __call__
        else self.explain(request)
      File "/alibiexplainer/alibiexplainer/explainer.py", line 84, in explain
        explanation = self.wrapper.explain(request["instances"])
      File "/alibiexplainer/alibiexplainer/anchor_images.py", line 49, in explain
        anchor_exp = self.anchors_image.explain(arr[0], **self.kwargs)
      File "/usr/local/lib/python3.7/site-packages/alibi/explainers/anchor_image.py", line 409, in explain
        return self.build_explanation(image, result, self.instance_label, params)
      File "/usr/local/lib/python3.7/site-packages/alibi/explainers/anchor_image.py", line 447, in build_explanation
        explanation = Explanation(meta=copy.deepcopy(self.meta), data=data)
    AttributeError: 'AnchorImage' object has no attribute 'meta'
[E 210709 11:30:11 web:2243] 500 POST /v1/models/imagenet:explain (127.0.0.1) 1157265.30ms
```

The documentation states that the explanation may take a while, but surely it shouldn't time out with just a single image? This looks like a timeout, but I am not really sure if the `AttributeError: 'AnchorImage' object has no attribute 'meta'` suggests otherwise.

**What did you expect to happen:**

I would expect the request to succeed within a reasonable space of time.


- Istio Version: 1.10.2
- Knative Version: 0.24
- KFServing Version: 0.6.0
- Kubeflow version: 1.3+
- Kubernetes version: 1.20



Discussion:
/assign @cliveseldon 
@karlschriek Which version of Alibi-Explain did you use to train your imagenet explainer?
@cliveseldon I didn't train my own explainer, the above describes what happens when attempt to use an example explainer from "gs://seldon-models/tfserving/imagenet/explainer".
ok but the example has `storageUri: "gs://seldon-models/tfserving/imagenet/alibi/0.4.0"`

Not ` "gs://seldon-models/tfserving/imagenet/explainer"`

We need to update the kfserving integration to the latest version of Alibi - as its presently has examples based on 0.4.0
`gs://seldon-models/tfserving/imagenet/alibi/0.4.0` also gave me trouble, see https://github.com/kubeflow/kfserving/issues/1707. I found the reference to `gs://seldon-models/tfserving/imagenet/explainer` here: https://docs.seldon.io/projects/seldon-core/en/v0.4.1/examples/explainer_examples.html
I think the issue may be the Alibi version build in the image is not tied to a particular version but a minimum version

https://github.com/kubeflow/kfserving/blob/v0.6.0/docs/samples/explanation/alibi/imagenet/requirements.txt

So I assume its running the latest Alibi. This may be incompatible with saved explainers from an older Alibi.

I think we need to fix the Alibi version and recreate some of the examples with that version.
That would be great. The example documentation at the moment is a bit inconsistent as well, so it is a bit hard to figure out what to use. The README refers to 0.4.0 while https://github.com/kubeflow/kfserving/blob/v0.6.0/docs/samples/explanation/alibi/imagenet/imagenet.yaml refers to 0.5.2.

Resolving patch:
diff --git a/docs/samples/explanation/alibi/imagenet/train_explainer.py b/docs/samples/explanation/alibi/imagenet/train_explainer.py
deleted file mode 100644
--- a/docs/samples/explanation/alibi/imagenet/train_explainer.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from tensorflow.keras.applications.inception_v3 import InceptionV3
-from alibi.explainers import AnchorImage
-import dill
-
-model = InceptionV3(weights='imagenet')
-
-segmentation_fn = 'slic'
-kwargs = {'n_segments': 15, 'compactness': 20, 'sigma': .5}
-image_shape = (299, 299, 3)
-explainer = AnchorImage(lambda x: model.predict(x), image_shape, segmentation_fn=segmentation_fn,
-                        segmentation_kwargs=kwargs,
-                        images_background=None)
-
-
-explainer.predict_fn = None  # Clear explainer predict_fn as its a lambda and will be reset when loaded
-with open("explainer.dill", 'wb') as f:
-    dill.dump(explainer, f)
diff --git a/python/alibiexplainer/alibiexplainer/anchor_text.py b/python/alibiexplainer/alibiexplainer/anchor_text.py
--- a/python/alibiexplainer/alibiexplainer/anchor_text.py
+++ b/python/alibiexplainer/alibiexplainer/anchor_text.py
@@ -45,7 +45,9 @@ def __init__(
 
     def explain(self, inputs: List) -> Explanation:
         if self.anchors_text is None:
-            self.anchors_text = alibi.explainers.AnchorText(self.nlp, self.predict_fn)
+            self.anchors_text = alibi.explainers.AnchorText(predictor=self.predict_fn,
+                                                            sampling_strategy='unknown',
+                                                            nlp=self.nlp)
 
         # We assume the input has batch dimension but Alibi explainers presently assume no batch
         input_words = inputs[0]
diff --git a/python/alibiexplainer/setup.py b/python/alibiexplainer/setup.py
--- a/python/alibiexplainer/setup.py
+++ b/python/alibiexplainer/setup.py
@@ -36,7 +36,7 @@
         "kfserving>=0.6.0",
         "pandas>=0.24.2",
         "nest_asyncio>=1.4.0",
-        "alibi==0.5.5",
+        "alibi==0.6.0",
         "scikit-learn == 0.20.3",
         "argparse>=1.4.0",
         "requests>=2.22.0",
@@ -44,8 +44,9 @@
         "dill>=0.3.0",
         "grpcio>=1.22.0",
         "xgboost==1.0.2",
-        "shap==0.36.0",
-        "numpy<1.19.0"
+        "shap==0.39.0",
+        "numpy<1.19.0",
+        'spacy[lookups]>=2.0.0, <4.0.0'
     ],
     tests_require=tests_require,
     extras_require={'test': tests_require}


Test patch:
diff --git a/docs/samples/explanation/alibi/imagenet/test_imagenet.py b/docs/samples/explanation/alibi/imagenet/test_imagenet.py
deleted file mode 100644
--- a/docs/samples/explanation/alibi/imagenet/test_imagenet.py
+++ /dev/null
@@ -1,83 +0,0 @@
-import argparse
-import matplotlib.pyplot as plt
-from tensorflow.keras.applications.inception_v3 import preprocess_input, decode_predictions
-import numpy as np
-import requests
-import json
-import os
-from PIL import Image
-import ssl
-ssl._create_default_https_context = ssl._create_unverified_context
-
-PREDICT_TEMPLATE = 'http://{0}/v1/models/imagenet:predict'
-EXPLAIN_TEMPLATE = 'http://{0}/v1/models/imagenet:explain'
-
-
-def get_image_data():
-    data = []
-    image_shape = (299, 299, 3)
-    target_size = image_shape[:2]
-    image = Image.open("./cat-prediction.png").convert('RGB')
-    image = np.expand_dims(image.resize(target_size), axis=0)
-    data.append(image)
-    data = np.concatenate(data, axis=0)
-    return data
-
-
-def predict(cluster_ip):
-    data = get_image_data()
-    images = preprocess_input(data)
-
-    payload = {
-      "instances": [images[0].tolist()]
-    }
-
-    # sending post request to TensorFlow Serving server
-    headers = {'Host': 'imagenet.default.example.com'}
-    url = PREDICT_TEMPLATE.format(cluster_ip)
-    print("Calling ", url)
-    r = requests.post(url, json=payload, headers=headers)
-    resp_json = json.loads(r.content.decode('utf-8'))
-    preds = np.array(resp_json["predictions"])
-    label = decode_predictions(preds, top=1)
-
-    plt.imshow(data[0])
-    plt.title(label[0])
-    plt.show()
-
-
-def explain(cluster_ip):
-    data = get_image_data()
-    images = preprocess_input(data)
-
-    payload = {
-        "instances": [images[0].tolist()]
-    }
-
-    # sending post request to TensorFlow Serving server
-    headers = {'Host': 'imagenet.default.example.com'}
-    url = EXPLAIN_TEMPLATE.format(cluster_ip)
-    print("Calling ", url)
-    r = requests.post(url, json=payload, headers=headers)
-    if r.status_code == 200:
-        explanation = json.loads(r.content.decode('utf-8'))
-
-        f, axarr = plt.subplots(1, 2)
-        axarr[0].imshow(data[0])
-        axarr[1].imshow(explanation['data']['anchor'])
-        plt.show()
-    else:
-        print("Received response code and content", r.status_code, r.content)
-
-
-parser = argparse.ArgumentParser()
-parser.add_argument('--cluster_ip', default=os.environ.get("CLUSTER_IP"), help='Cluster IP of Istio Ingress Gateway')
-parser.add_argument('--op', choices=["predict", "explain"], default="predict",
-                    help='Operation to run')
-args, _ = parser.parse_known_args()
-
-if __name__ == "__main__":
-    if args.op == "predict":
-        predict(args.cluster_ip)
-    elif args.op == "explain":
-        explain(args.cluster_ip)
diff --git a/pkg/apis/serving/v1alpha2/inferenceservice_conversion_test.go b/pkg/apis/serving/v1alpha2/inferenceservice_conversion_test.go
--- a/pkg/apis/serving/v1alpha2/inferenceservice_conversion_test.go
+++ b/pkg/apis/serving/v1alpha2/inferenceservice_conversion_test.go
@@ -336,18 +336,20 @@ func TestInferenceServiceConversion(t *testing.T) {
 							ContainerConcurrency: proto.Int64(1),
 						},
 						Alibi: &v1beta1.AlibiExplainerSpec{
-							Type:           v1beta1.AlibiAnchorsTabularExplainer,
-							StorageURI:     "s3://test/mnist/explainer",
-							RuntimeVersion: proto.String("0.4.0"),
-							Container: v1.Container{
-								Resources: v1.ResourceRequirements{
-									Requests: v1.ResourceList{
-										v1.ResourceCPU:    resource.MustParse("1"),
-										v1.ResourceMemory: resource.MustParse("2Gi"),
-									},
-									Limits: v1.ResourceList{
-										v1.ResourceCPU:    resource.MustParse("1"),
-										v1.ResourceMemory: resource.MustParse("2Gi"),
+							Type: v1beta1.AlibiAnchorsTabularExplainer,
+							ExplainerExtensionSpec: v1beta1.ExplainerExtensionSpec{
+								StorageURI:     "s3://test/mnist/explainer",
+								RuntimeVersion: proto.String("0.4.0"),
+								Container: v1.Container{
+									Resources: v1.ResourceRequirements{
+										Requests: v1.ResourceList{
+											v1.ResourceCPU:    resource.MustParse("1"),
+											v1.ResourceMemory: resource.MustParse("2Gi"),
+										},
+										Limits: v1.ResourceList{
+											v1.ResourceCPU:    resource.MustParse("1"),
+											v1.ResourceMemory: resource.MustParse("2Gi"),
+										},
 									},
 								},
 							},
diff --git a/pkg/apis/serving/v1beta1/explainer_alibi_test.go b/pkg/apis/serving/v1beta1/explainer_alibi_test.go
--- a/pkg/apis/serving/v1beta1/explainer_alibi_test.go
+++ b/pkg/apis/serving/v1beta1/explainer_alibi_test.go
@@ -46,8 +46,10 @@ func TestAlibiValidation(t *testing.T) {
 		"AcceptGoodRuntimeVersion": {
 			spec: ExplainerSpec{
 				Alibi: &AlibiExplainerSpec{
-					Type:           "AnchorTabular",
-					RuntimeVersion: proto.String("latest"),
+					Type: "AnchorTabular",
+					ExplainerExtensionSpec: ExplainerExtensionSpec{
+						RuntimeVersion: proto.String("latest"),
+					},
 				},
 			},
 			matcher: gomega.BeNil(),
@@ -55,8 +57,10 @@ func TestAlibiValidation(t *testing.T) {
 		"ValidStorageUri": {
 			spec: ExplainerSpec{
 				Alibi: &AlibiExplainerSpec{
-					Type:       "AnchorTabular",
-					StorageURI: "s3://modelzoo",
+					Type: "AnchorTabular",
+					ExplainerExtensionSpec: ExplainerExtensionSpec{
+						StorageURI: "s3://modelzoo",
+					},
 				},
 			},
 			matcher: gomega.BeNil(),
@@ -64,7 +68,9 @@ func TestAlibiValidation(t *testing.T) {
 		"InvalidStorageUri": {
 			spec: ExplainerSpec{
 				Alibi: &AlibiExplainerSpec{
-					StorageURI: "hdfs://modelzoo",
+					ExplainerExtensionSpec: ExplainerExtensionSpec{
+						StorageURI: "hdfs://modelzoo",
+					},
 				},
 			},
 			matcher: gomega.Not(gomega.BeNil()),
@@ -76,7 +82,9 @@ func TestAlibiValidation(t *testing.T) {
 					MaxReplicas: 2,
 				},
 				Alibi: &AlibiExplainerSpec{
-					StorageURI: "hdfs://modelzoo",
+					ExplainerExtensionSpec: ExplainerExtensionSpec{
+						StorageURI: "hdfs://modelzoo",
+					},
 				},
 			},
 			matcher: gomega.Not(gomega.BeNil()),
@@ -88,7 +96,9 @@ func TestAlibiValidation(t *testing.T) {
 					ContainerConcurrency: proto.Int64(-1),
 				},
 				Alibi: &AlibiExplainerSpec{
-					StorageURI: "hdfs://modelzoo",
+					ExplainerExtensionSpec: ExplainerExtensionSpec{
+						StorageURI: "hdfs://modelzoo",
+					},
 				},
 			},
 			matcher: gomega.Not(gomega.BeNil()),
@@ -130,12 +140,14 @@ func TestAlibiDefaulter(t *testing.T) {
 			},
 			expected: ExplainerSpec{
 				Alibi: &AlibiExplainerSpec{
-					RuntimeVersion: proto.String("v0.4.0"),
-					Container: v1.Container{
-						Name: constants.InferenceServiceContainerName,
-						Resources: v1.ResourceRequirements{
-							Requests: defaultResource,
-							Limits:   defaultResource,
+					ExplainerExtensionSpec: ExplainerExtensionSpec{
+						RuntimeVersion: proto.String("v0.4.0"),
+						Container: v1.Container{
+							Name: constants.InferenceServiceContainerName,
+							Resources: v1.ResourceRequirements{
+								Requests: defaultResource,
+								Limits:   defaultResource,
+							},
 						},
 					},
 				},
@@ -144,17 +156,21 @@ func TestAlibiDefaulter(t *testing.T) {
 		"DefaultResources": {
 			spec: ExplainerSpec{
 				Alibi: &AlibiExplainerSpec{
-					RuntimeVersion: proto.String("v0.3.0"),
+					ExplainerExtensionSpec: ExplainerExtensionSpec{
+						RuntimeVersion: proto.String("v0.3.0"),
+					},
 				},
 			},
 			expected: ExplainerSpec{
 				Alibi: &AlibiExplainerSpec{
-					RuntimeVersion: proto.String("v0.3.0"),
-					Container: v1.Container{
-						Name: constants.InferenceServiceContainerName,
-						Resources: v1.ResourceRequirements{
-							Requests: defaultResource,
-							Limits:   defaultResource,
+					ExplainerExtensionSpec: ExplainerExtensionSpec{
+						RuntimeVersion: proto.String("v0.3.0"),
+						Container: v1.Container{
+							Name: constants.InferenceServiceContainerName,
+							Resources: v1.ResourceRequirements{
+								Requests: defaultResource,
+								Limits:   defaultResource,
+							},
 						},
 					},
 				},
@@ -221,10 +237,12 @@ func TestCreateAlibiModelServingContainer(t *testing.T) {
 					},
 					Explainer: &ExplainerSpec{
 						Alibi: &AlibiExplainerSpec{
-							Type:       AlibiAnchorsTabularExplainer,
-							StorageURI: "s3://explainer",
-							Container: v1.Container{
-								Resources: requestedResource,
+							Type: AlibiAnchorsTabularExplainer,
+							ExplainerExtensionSpec: ExplainerExtensionSpec{
+								StorageURI: "s3://explainer",
+								Container: v1.Container{
+									Resources: requestedResource,
+								},
 							},
 						},
 					},
@@ -266,12 +284,14 @@ func TestCreateAlibiModelServingContainer(t *testing.T) {
 					},
 					Explainer: &ExplainerSpec{
 						Alibi: &AlibiExplainerSpec{
-							Type:           AlibiAnchorsTabularExplainer,
-							StorageURI:     "s3://explainer",
-							RuntimeVersion: proto.String("v0.4.0"),
-							Container: v1.Container{
-								Image:     "explainer:0.1.0",
-								Resources: requestedResource,
+							Type: AlibiAnchorsTabularExplainer,
+							ExplainerExtensionSpec: ExplainerExtensionSpec{
+								StorageURI:     "s3://explainer",
+								RuntimeVersion: proto.String("v0.4.0"),
+								Container: v1.Container{
+									Image:     "explainer:0.1.0",
+									Resources: requestedResource,
+								},
 							},
 						},
 					},
@@ -319,12 +339,14 @@ func TestCreateAlibiModelServingContainer(t *testing.T) {
 							ContainerConcurrency: proto.Int64(2),
 						},
 						Alibi: &AlibiExplainerSpec{
-							Type:           AlibiAnchorsTabularExplainer,
-							StorageURI:     "s3://explainer",
-							RuntimeVersion: proto.String("v0.4.0"),
-							Container: v1.Container{
-								Image:     "explainer:0.1.0",
-								Resources: requestedResource,
+							Type: AlibiAnchorsTabularExplainer,
+							ExplainerExtensionSpec: ExplainerExtensionSpec{
+								StorageURI:     "s3://explainer",
+								RuntimeVersion: proto.String("v0.4.0"),
+								Container: v1.Container{
+									Image:     "explainer:0.1.0",
+									Resources: requestedResource,
+								},
 							},
 						},
 					},
@@ -382,8 +404,10 @@ func TestAlibiIsMMS(t *testing.T) {
 		"AcceptGoodRuntimeVersion": {
 			spec: ExplainerSpec{
 				Alibi: &AlibiExplainerSpec{
-					Type:           "AnchorTabular",
-					RuntimeVersion: proto.String("latest"),
+					Type: "AnchorTabular",
+					ExplainerExtensionSpec: ExplainerExtensionSpec{
+						RuntimeVersion: proto.String("latest"),
+					},
 				},
 			},
 			expected: mssCase,
@@ -391,8 +415,10 @@ func TestAlibiIsMMS(t *testing.T) {
 		"ValidStorageUri": {
 			spec: ExplainerSpec{
 				Alibi: &AlibiExplainerSpec{
-					Type:       "AnchorTabular",
-					StorageURI: "s3://modelzoo",
+					Type: "AnchorTabular",
+					ExplainerExtensionSpec: ExplainerExtensionSpec{
+						StorageURI: "s3://modelzoo",
+					},
 				},
 			},
 			expected: mssCase,
diff --git a/pkg/apis/serving/v1beta1/inference_service_defaults_test.go b/pkg/apis/serving/v1beta1/inference_service_defaults_test.go
--- a/pkg/apis/serving/v1beta1/inference_service_defaults_test.go
+++ b/pkg/apis/serving/v1beta1/inference_service_defaults_test.go
@@ -72,7 +72,9 @@ func TestInferenceServiceDefaults(t *testing.T) {
 			},
 			Explainer: &ExplainerSpec{
 				Alibi: &AlibiExplainerSpec{
-					StorageURI: "gs://testbucket/testmodel",
+					ExplainerExtensionSpec: ExplainerExtensionSpec{
+						StorageURI: "gs://testbucket/testmodel",
+					},
 				},
 			},
 		},
diff --git a/pkg/apis/serving/v1beta1/inference_service_validation_test.go b/pkg/apis/serving/v1beta1/inference_service_validation_test.go
--- a/pkg/apis/serving/v1beta1/inference_service_validation_test.go
+++ b/pkg/apis/serving/v1beta1/inference_service_validation_test.go
@@ -183,7 +183,9 @@ func TestBadReplicaValues(t *testing.T) {
 
 	isvc.Spec.Explainer = &ExplainerSpec{
 		Alibi: &AlibiExplainerSpec{
-			StorageURI: "gs://testbucket/testmodel",
+			ExplainerExtensionSpec: ExplainerExtensionSpec{
+				StorageURI: "gs://testbucket/testmodel",
+			},
 		},
 	}
 	isvc.Spec.Explainer.MinReplicas = GetIntReference(-1)
@@ -231,7 +233,9 @@ func TestGoodExplainer(t *testing.T) {
 	isvc := makeTestInferenceService()
 	isvc.Spec.Explainer = &ExplainerSpec{
 		Alibi: &AlibiExplainerSpec{
-			StorageURI: "gs://testbucket/testmodel",
+			ExplainerExtensionSpec: ExplainerExtensionSpec{
+				StorageURI: "gs://testbucket/testmodel",
+			},
 		},
 	}
 	g.Expect(isvc.ValidateCreate()).Should(gomega.Succeed())
diff --git a/pkg/controller/v1beta1/inferenceservice/controller_test.go b/pkg/controller/v1beta1/inferenceservice/controller_test.go
--- a/pkg/controller/v1beta1/inferenceservice/controller_test.go
+++ b/pkg/controller/v1beta1/inferenceservice/controller_test.go
@@ -551,12 +551,14 @@ var _ = Describe("v1beta1 inference service controller", func() {
 							MaxReplicas: 3,
 						},
 						Alibi: &v1beta1.AlibiExplainerSpec{
-							Type:           v1beta1.AlibiAnchorsTabularExplainer,
-							StorageURI:     "s3://test/mnist/explainer",
-							RuntimeVersion: proto.String("0.4.0"),
-							Container: v1.Container{
-								Name:      "kfserving-contaienr",
-								Resources: defaultResource,
+							Type: v1beta1.AlibiAnchorsTabularExplainer,
+							ExplainerExtensionSpec: v1beta1.ExplainerExtensionSpec{
+								StorageURI:     "s3://test/mnist/explainer",
+								RuntimeVersion: proto.String("0.4.0"),
+								Container: v1.Container{
+									Name:      "kfserving-contaienr",
+									Resources: defaultResource,
+								},
 							},
 						},
 					},
diff --git a/python/alibiexplainer/tests/test_anchor_images.py b/python/alibiexplainer/tests/test_anchor_images.py
--- a/python/alibiexplainer/tests/test_anchor_images.py
+++ b/python/alibiexplainer/tests/test_anchor_images.py
@@ -14,66 +14,36 @@
 
 from alibiexplainer.anchor_images import AnchorImages
 import os
-from tensorflow.keras.applications.inception_v3 import InceptionV3, preprocess_input
+import tensorflow as tf
 import json
 import numpy as np
 import kfserving
 import dill
-import PIL
-import random
-import requests
-from requests import RequestException
-from io import BytesIO
 
-IMAGENET_EXPLAINER_URI = "gs://seldon-models/tfserving/imagenet/explainer-py36-0.5.2"
-ADULT_MODEL_URI = "gs://seldon-models/sklearn/income/model"
+
+CIFAR10_EXPLAINER_URI = "gs://seldon-models/tfserving/cifar10/explainer-py36-0.5.2"
 EXPLAINER_FILENAME = "explainer.dill"
 
 
-def test_anchor_images():
+def test_cifar10_images():  # pylint: disable-msg=too-many-locals
     os.environ.clear()
     alibi_model = os.path.join(
-        kfserving.Storage.download(IMAGENET_EXPLAINER_URI), EXPLAINER_FILENAME
+        kfserving.Storage.download(CIFAR10_EXPLAINER_URI), EXPLAINER_FILENAME
     )
     with open(alibi_model, "rb") as f:
-        model = InceptionV3(weights="imagenet")
         alibi_model = dill.load(f)
+        url = "https://storage.googleapis.com/seldon-models/alibi-detect/classifier/"
+        path_model = os.path.join(url, "cifar10", "resnet32", "model.h5")
+        save_path = tf.keras.utils.get_file("resnet32", path_model)
+        model = tf.keras.models.load_model(save_path)
+        _, test = tf.keras.datasets.cifar10.load_data()
+        X_test, _ = test
+        X_test = X_test.astype("float32") / 255
+        idx = 12
+        test_example = X_test[idx: idx + 1]
         anchor_images = AnchorImages(
-            lambda x: model.predict(x), alibi_model, batch_size=25, stop_on_first=True
-        )
-        image_shape = (299, 299, 3)
-        # the image downloader comes from seldonio/alibi
-        # https://github.com/SeldonIO/alibi/blob/76e6192b6d78848dd47c11ba6f6348ca94c424c6/alibi/datasets.py#L104-L125
-        img_urls = json.load(open('alibiexplainer/tests/persian_cat.json'))
-        seed = 2
-        random.seed(seed)
-        random.shuffle(img_urls)
-        data = []
-        nb = 0
-        nb_images = 10
-        target_size = image_shape[:2]
-        min_std = 10.
-        for img_url in img_urls:
-            try:
-                resp = requests.get(img_url, timeout=2)
-                resp.raise_for_status()
-            except RequestException:
-                continue
-            try:
-                image = PIL.Image.open(BytesIO(resp.content)).convert('RGB')
-            except OSError:
-                continue
-            image = np.expand_dims(image.resize(target_size), axis=0)
-            if np.std(image) < min_std:  # do not include empty images
-                continue
-            data.append(image)
-            nb += 1
-            if nb == nb_images:
-                break
-        data = np.concatenate(data, axis=0)
-        images = preprocess_input(data)
-        print(images.shape)
+            lambda x: model.predict(x), alibi_model)  # pylint: disable-msg=unnecessary-lambda
         np.random.seed(0)
-        explanation = anchor_images.explain(images[0:1])
+        explanation = anchor_images.explain(test_example)
         exp_json = json.loads(explanation.to_json())
         assert exp_json["data"]["precision"] > 0.9
diff --git a/python/alibiexplainer/tests/test_anchor_tabular.py b/python/alibiexplainer/tests/test_anchor_tabular.py
--- a/python/alibiexplainer/tests/test_anchor_tabular.py
+++ b/python/alibiexplainer/tests/test_anchor_tabular.py
@@ -22,7 +22,7 @@
 import json
 from .utils import Predictor
 
-ADULT_EXPLAINER_URI = "gs://seldon-models/sklearn/income/explainer-py36-0.5.2"
+ADULT_EXPLAINER_URI = "gs://seldon-models/sklearn/income/explainer-py37-0.6.0"
 ADULT_MODEL_URI = "gs://seldon-models/sklearn/income/model"
 EXPLAINER_FILENAME = "explainer.dill"
 
diff --git a/test/e2e/explainer/test_tabular_explainer.py b/test/e2e/explainer/test_tabular_explainer.py
--- a/test/e2e/explainer/test_tabular_explainer.py
+++ b/test/e2e/explainer/test_tabular_explainer.py
@@ -50,7 +50,7 @@ def test_tabular_explainer():
         alibi=V1beta1AlibiExplainerSpec(
             name='kfserving-container',
             type='AnchorTabular',
-            storage_uri='gs://seldon-models/sklearn/income/explainer-py36-0.5.2',
+            storage_uri='gs://seldon-models/sklearn/income/explainer-py37-0.6.0',
             resources=V1ResourceRequirements(
                 requests={'cpu': '100m', 'memory': '1Gi'},
                 limits={'cpu': '100m', 'memory': '1Gi'}