kamangir commited on
Commit
c18b721
1 Parent(s): 31f74fe

validation - kamangir/bolt#689

Browse files
abcli/fashion_mnist.sh CHANGED
@@ -1,15 +1,15 @@
1
  #! /usr/bin/env bash
2
 
3
  function fashion_mnist() {
4
- abcli_fashion_mnist $@
5
- }
6
-
7
- function abcli_fashion_mnist() {
8
  local task=$(abcli_unpack_keyword $1 help)
9
 
10
  if [ $task == "help" ] ; then
11
- abcli_help_line "fashion_mnist task_1" \
12
- "run fashion_mnist task_1."
 
 
 
 
13
 
14
  if [ "$(abcli_keyword_is $2 verbose)" == true ] ; then
15
  python3 -m fashion_mnist --help
@@ -18,12 +18,34 @@ function abcli_fashion_mnist() {
18
  return
19
  fi
20
 
21
- if [ "$task" == "task_1" ] ; then
22
  python3 -m fashion_mnist \
23
- task_1 \
 
24
  ${@:2}
25
  return
26
  fi
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  abcli_log_error "-fashion_mnist: $task: command not found."
 
 
 
 
29
  }
 
1
  #! /usr/bin/env bash
2
 
3
  function fashion_mnist() {
 
 
 
 
4
  local task=$(abcli_unpack_keyword $1 help)
5
 
6
  if [ $task == "help" ] ; then
7
+ abcli_help_line "fashion_mnist ingest" \
8
+ "ingest fashion_mnist data."
9
+ abcli_help_line "fashion_mnist predict object_1" \
10
+ "run fashion_mnist model object_1 predict."
11
+ abcli_help_line "fashion_mnist train" \
12
+ "train fashion_mnist."
13
 
14
  if [ "$(abcli_keyword_is $2 verbose)" == true ] ; then
15
  python3 -m fashion_mnist --help
 
18
  return
19
  fi
20
 
21
+ if [ "$task" == "ingest" ] ; then
22
  python3 -m fashion_mnist \
23
+ thing \
24
+ --destination $abcli_object_path \
25
  ${@:2}
26
  return
27
  fi
28
 
29
+ if [ "$task" == "predict" ] ; then
30
+ abcli_fashion_mnist ingest
31
+ abcli_image_classifier_predict ${@:2}
32
+ fi
33
+
34
+ if [ "$task" == "train" ] ; then
35
+ abcli_fashion_mnist ingest
36
+ abcli_image_classifier_train \
37
+ "$2" \
38
+ "$3" \
39
+ "$4" \
40
+ --color 0 \
41
+ --convnet 0 \
42
+ ${@:5}
43
+ return
44
+ fi
45
+
46
  abcli_log_error "-fashion_mnist: $task: command not found."
47
+ }
48
+
49
+ function abcli_fashion_mnist() {
50
+ fashion_mnist $@
51
  }
abcli/image_classifier.sh ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #! /usr/bin/env bash
2
+
3
+ function abcli_image_classifier() {
4
+ local task=$(abcli_unpack_keyword "$1" help)
5
+
6
+ if [ "$task" == "help" ] ; then
7
+ abcli_help_line "$abcli_cli_name image_classifier describe object_1" \
8
+ "describe model object_1."
9
+ abcli_help_line "$abcli_cli_name image_classifier predict object_1 object_2" \
10
+ "run image_classifier model object_1 predict on data object_2."
11
+ abcli_help_line "$abcli_cli_name image_classifier train object_1" \
12
+ "train image_classifier on data object_1."
13
+
14
+ if [ "$(abcli_keyword_is $2 verbose)" == true ] ; then
15
+ python3 -m fashion_mnist.image_classifier --help
16
+ fi
17
+ return
18
+ fi
19
+
20
+ if [[ $(type -t abcli_image_classifier_$task) == "function" ]] ; then
21
+ abcli_image_classifier_$task ${@:2}
22
+ return
23
+ fi
24
+
25
+ if [ "$task" == "describe" ] ; then
26
+ local model_object_name="$2"
27
+
28
+ abcli_download $model_object_name
29
+
30
+ python3 -m fashion_mnist.image_classifier \
31
+ describe \
32
+ --model_path $abcli_object_root/$model_object_name \
33
+ ${@:3}
34
+
35
+ return
36
+ fi
37
+
38
+ abcli_log_error "-fashion_mnist: image-classifier: $task: command not found."
39
+ }
40
+
41
+ function abcli_image_classifier_predict() {
42
+ local model_object=$(abcli_clarify_object "$1")
43
+ local data_object=$(abcli_clarify_object "$2")
44
+
45
+ abcli_download $model_object
46
+ abcli_download $data_object
47
+
48
+ abcli_log "image_classifier($model_object).predict($data_object)"
49
+
50
+ if [ ! -f "$abcli_object_root/$data_object/test_images.pyndarray" ] ; then
51
+ python3 -m fashion_mnist.image_classifier \
52
+ preprocess \
53
+ --infer_annotation 0 \
54
+ --model_path $abcli_object_root/$model_object \
55
+ --objects $abcli_object_root/$data_object \
56
+ --output_path $abcli_object_root/$data_object \
57
+ --purpose predict \
58
+ ${@:3}
59
+ fi
60
+
61
+ cp -v ../$data_object/*.pyndarray .
62
+ cp -v ../$model_object/class_names.json .
63
+
64
+ python3 -m fashion_mnist.image_classifier \
65
+ predict \
66
+ --data_path $abcli_object_root/$data_object \
67
+ --model_path $abcli_object_root/$model_object \
68
+ --output_path $abcli_object_path \
69
+ ${@:4}
70
+ }
71
+
72
+ function abcli_image_classifier_train() {
73
+ local data_object=$(abcli_clarify_object "$1" $abcli_object_name)
74
+
75
+ abcli_download $data_object
76
+
77
+ local options=$2
78
+ local do_validate=$(abcli_option_int "$options" "validate" 0)
79
+
80
+ local extra_args=""
81
+ if [ "$do_validate" == true ] ; then
82
+ local extra_args="--epochs 2"
83
+ fi
84
+
85
+ python3 -m fashion_mnist.image_classifier \
86
+ train \
87
+ --color 1 \
88
+ --data_path $abcli_object_root/$data_object \
89
+ --model_path $abcli_object_path \
90
+ $extra_args \
91
+ ${@:3}
92
+ }
fashion_mnist/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
  name = "fashion_mnist"
2
 
3
- version = "1.1.22"
4
 
5
  description = "fashion-mnist + hugging-face + awesome-bash-cli"
 
1
  name = "fashion_mnist"
2
 
3
+ version = "1.1.25"
4
 
5
  description = "fashion-mnist + hugging-face + awesome-bash-cli"
fashion_mnist/image_classifier/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .. import name as parent_name
2
+
3
+ name = f"{parent_name}.image_classifier"
fashion_mnist/image_classifier/__main__.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import cv2
3
+ from functools import reduce
4
+ import matplotlib.pyplot as plt
5
+ import numpy as np
6
+ import os
7
+ import os.path
8
+ import tensorflow as tf
9
+ from tqdm import *
10
+ import re
11
+ import time
12
+ from . import *
13
+ from abcli import objects
14
+ from abcli import cache
15
+ from abcli import file
16
+ from abcli.tasks import host
17
+ from abcli import graphics
18
+ from abcli.options import Options
19
+ from abcli import path
20
+ from abcli.storage import instance as storage
21
+ from abcli import string
22
+ from abcli.plugins import tags
23
+
24
+ import abcli.logging
25
+ import logging
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ parser = argparse.ArgumentParser(name)
31
+ parser.add_argument(
32
+ "task",
33
+ type=str,
34
+ default="",
35
+ help="describe,eval,ingest,predict,preprocess,train",
36
+ )
37
+ parser.add_argument(
38
+ "--objects",
39
+ type=str,
40
+ default="",
41
+ )
42
+ parser.add_argument(
43
+ "--color",
44
+ type=int,
45
+ default=0,
46
+ help="0/1",
47
+ )
48
+ parser.add_argument(
49
+ "--convnet",
50
+ type=int,
51
+ default=1,
52
+ help="0/1",
53
+ )
54
+ parser.add_argument(
55
+ "--count",
56
+ type=int,
57
+ default=-1,
58
+ )
59
+ parser.add_argument(
60
+ "--data_path",
61
+ type=str,
62
+ default="",
63
+ )
64
+ parser.add_argument(
65
+ "--epochs",
66
+ default=10,
67
+ type=int,
68
+ help="",
69
+ )
70
+ parser.add_argument(
71
+ "--exclude",
72
+ type=str,
73
+ default="",
74
+ )
75
+ parser.add_argument(
76
+ "--include",
77
+ type=str,
78
+ default="",
79
+ )
80
+ parser.add_argument(
81
+ "--infer_annotation",
82
+ type=int,
83
+ default=1,
84
+ help="0/1",
85
+ )
86
+ parser.add_argument(
87
+ "--input_path",
88
+ type=str,
89
+ default="",
90
+ )
91
+ parser.add_argument(
92
+ "--model_path",
93
+ type=str,
94
+ default="",
95
+ )
96
+ parser.add_argument(
97
+ "--negative",
98
+ type=int,
99
+ default=0,
100
+ help="0/1",
101
+ )
102
+ parser.add_argument(
103
+ "--non_empty",
104
+ type=int,
105
+ default=0,
106
+ help="0/1",
107
+ )
108
+ parser.add_argument(
109
+ "--output_path",
110
+ type=str,
111
+ default="",
112
+ )
113
+ parser.add_argument(
114
+ "--positive",
115
+ type=int,
116
+ default=0,
117
+ help="0/1",
118
+ )
119
+ parser.add_argument(
120
+ "--purpose",
121
+ type=str,
122
+ default="",
123
+ help="predict/train",
124
+ )
125
+ parser.add_argument(
126
+ "--test_size",
127
+ type=float,
128
+ default=1.0 / 6,
129
+ )
130
+ parser.add_argument(
131
+ "--window_size",
132
+ type=int,
133
+ default=28,
134
+ )
135
+ args = parser.parse_args()
136
+
137
+ success = False
138
+ if args.task == "describe":
139
+ image_classifier().load(args.model_path)
140
+ success = True
141
+ elif args.task == "eval":
142
+ success = eval(args.input_path, args.output_path)
143
+ elif args.task == "ingest":
144
+ success = ingest(
145
+ args.include,
146
+ args.output_path,
147
+ {
148
+ "count": args.count,
149
+ "exclude": args.exclude,
150
+ "negative": args.negative,
151
+ "non_empty": args.non_empty,
152
+ "positive": args.positive,
153
+ "test_size": args.test_size,
154
+ },
155
+ )
156
+ elif args.task == "predict":
157
+ classifier = image_classifier()
158
+
159
+ if classifier.load(args.model_path):
160
+ success, test_images = file.load(
161
+ "{}/test_images.pyndarray".format(args.data_path)
162
+ )
163
+
164
+ if success:
165
+ logger.info("test_images: {}".format(string.pretty_size_of_matrix(test_images)))
166
+
167
+ _, test_labels = file.load(
168
+ "{}/test_labels.pyndarray".format(args.data_path),
169
+ civilized=True,
170
+ default=None,
171
+ )
172
+
173
+ test_images = test_images / 255.0
174
+
175
+ success = classifier.predict(test_images, test_labels, args.output_path)
176
+ elif args.task == "preprocess":
177
+ success = preprocess(
178
+ args.output_path,
179
+ {
180
+ "objects": args.objects,
181
+ "infer_annotation": args.infer_annotation,
182
+ "purpose": args.purpose,
183
+ "window_size": args.window_size,
184
+ },
185
+ )
186
+ elif args.task == "train":
187
+ classifier = image_classifier()
188
+ success = classifier.train(
189
+ args.data_path,
190
+ args.model_path,
191
+ {"color": args.color, "convnet": args.convnet, "epochs": args.epochs},
192
+ )
193
+ else:
194
+ logger.error(f"-{name}: {args.task}: command not found.")
195
+
196
+ if not success:
197
+ logger.error(f"-{name}: {args.task}: failed.")
fashion_mnist/image_classifier/classes.py ADDED
@@ -0,0 +1,425 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .plot import *
2
+ from abcli import file
3
+ from abcli import string
4
+ import numpy as np
5
+ import matplotlib.pyplot as plt
6
+ import abcli.logging
7
+ import logging
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class Image_Classifier(object):
13
+ def __init__(self):
14
+ self.class_names = []
15
+ self.model = None
16
+ self.params = {"convnet": False}
17
+
18
+ self.object_name = ""
19
+ self.model_size = ""
20
+
21
+ def load(self, model_path):
22
+ success, self.class_names = file.load_json(f"{model_path}/class_names.json")
23
+ if not success:
24
+ return False
25
+
26
+ success, self.params = file.load_json(f"{model_path}/params.json", default={})
27
+ if not success:
28
+ return False
29
+
30
+ self.model_size = file.size(f"{model_path}/image_classifier/model")
31
+
32
+ try:
33
+ self.model = tf.keras.models.load_model(
34
+ f"{model_path}/image_classifier/model"
35
+ )
36
+ except:
37
+ from abcli.logging import crash_report
38
+
39
+ crash_report("image_classifier.load({}) failed".format(model_path))
40
+ return False
41
+
42
+ self.window_size = int(
43
+ cache.read("{}.window_size".format(path.name(model_path)))
44
+ )
45
+
46
+ logger.info(
47
+ "{}.load({}x{}:{}): {}{} class(es): {}".format(
48
+ self.__class__.__name__,
49
+ self.window_size,
50
+ self.window_size,
51
+ path.name(model_path),
52
+ "convnet - " if self.params["convnet"] else "",
53
+ len(self.class_names),
54
+ ",".join(self.class_names),
55
+ )
56
+ )
57
+ self.model.summary()
58
+
59
+ self.object_name = path.name(model_path)
60
+
61
+ return True
62
+
63
+ def predict(self, test_images, test_labels, output_path="", options=""):
64
+ options = Options(options).default("cache", False).default("page_count", -1)
65
+
66
+ logger.info(
67
+ "image_classifier.predict({},{}){}".format(
68
+ string.pretty_size_of_matrix(test_images),
69
+ string.pretty_size_of_matrix(test_labels),
70
+ "-> {}".format(output_path) if output_path else "",
71
+ )
72
+ )
73
+
74
+ prediction_time = time.time()
75
+ predictions = self.model.predict(test_images)
76
+ prediction_time = (time.time() - prediction_time) / test_images.shape[0]
77
+ logger.info(
78
+ "image_classifier.predict(): {} / frame".format(
79
+ string.pretty_duration(prediction_time, include_ms=True)
80
+ )
81
+ )
82
+
83
+ if not output_path:
84
+ return True
85
+
86
+ if not file.save("{}/predictions.pyndarray".format(output_path), predictions):
87
+ return False
88
+
89
+ if test_labels is not None:
90
+ from sklearn.metrics import confusion_matrix
91
+
92
+ logger.info("image_classifier.predict(): rendering confusion_matrix...")
93
+
94
+ cm = confusion_matrix(
95
+ test_labels,
96
+ np.argmax(predictions, axis=1),
97
+ labels=range(len(self.class_names)),
98
+ # normalize="true",
99
+ )
100
+ cm = cm / np.sum(cm, axis=1)[:, np.newaxis]
101
+ logger.debug("confusion_matrix: {}".format(cm))
102
+
103
+ if options["cache"]:
104
+ if not cache.write("{}.confusion_matrix".format(self.object_name), cm):
105
+ return False
106
+
107
+ if not file.save("{}/confusion_matrix.pyndarray".format(output_path), cm):
108
+ return False
109
+
110
+ if not graphics.render_confusion_matrix(
111
+ cm,
112
+ self.class_names,
113
+ "{}/Data/0/info.jpg".format(output_path),
114
+ {
115
+ "header": [
116
+ " | ".join(host.signature()),
117
+ " | ".join(objects.signature()),
118
+ ],
119
+ "footer": self.signature(prediction_time),
120
+ },
121
+ ):
122
+ return False
123
+
124
+ if test_labels is not None:
125
+ logger.info(
126
+ "image_classifier.predict(): rendering test_labels distribution..."
127
+ )
128
+
129
+ # accepting the risk that if test_labels does not contain any of the largest index
130
+ # this function will return False.
131
+ distribution = np.bincount(test_labels)
132
+ distribution = distribution / np.sum(distribution)
133
+
134
+ if not graphics.render_distribution(
135
+ distribution,
136
+ self.class_names,
137
+ "{}/Data/1/info.jpg".format(output_path),
138
+ {
139
+ "header": [
140
+ " | ".join(host.signature()),
141
+ " | ".join(objects.signature()),
142
+ ],
143
+ "footer": self.signature(prediction_time),
144
+ "title": "distribution of test_labels",
145
+ },
146
+ ):
147
+ return False
148
+
149
+ max_index = test_images.shape[0]
150
+ if options["page_count"] != -1:
151
+ max_index = min(24 * options["page_count"], max_index)
152
+ offset = int(np.max(np.array(objects.list_of_frames(output_path) + [-1]))) + 1
153
+ logger.info(
154
+ "image_classifier.predict(offset={}): rendering {} frame(s)...".format(
155
+ offset, max_index
156
+ )
157
+ )
158
+ for index in tqdm(range(0, max_index, 24)):
159
+ self.render(
160
+ predictions[index : index + 24],
161
+ None if test_labels is None else test_labels[index : index + 24],
162
+ test_images[index : index + 24],
163
+ "{}/Data/{}/info.jpg".format(output_path, int(index / 24) + offset),
164
+ prediction_time,
165
+ )
166
+
167
+ return True
168
+
169
+ def predict_frame(self, frame):
170
+ prediction_time = time.time()
171
+ try:
172
+ prediction = self.model.predict(
173
+ np.expand_dims(
174
+ cv2.resize(frame, (self.window_size, self.window_size)) / 255.0,
175
+ axis=0,
176
+ )
177
+ )
178
+ except:
179
+ from abcli.logging import crash_report
180
+
181
+ crash_report("image_classifier.predict_frame() crashed.")
182
+ return False, -1
183
+
184
+ prediction_time = time.time() - prediction_time
185
+
186
+ output = np.argmax(prediction)
187
+
188
+ logger.info(
189
+ "image_classifier.prediction: [{}] -> {} - took {}".format(
190
+ ",".join(
191
+ [
192
+ "{}:{:.2f}".format(class_name, value)
193
+ for class_name, value in zip(self.class_names, prediction[0])
194
+ ]
195
+ ),
196
+ self.class_names[output],
197
+ string.pretty_duration(
198
+ prediction_time,
199
+ include_ms=True,
200
+ short=True,
201
+ ),
202
+ )
203
+ )
204
+
205
+ return True, output
206
+
207
+ def render(
208
+ self,
209
+ predictions,
210
+ test_labels,
211
+ test_images,
212
+ output_filename="",
213
+ prediction_time=0,
214
+ ):
215
+ num_rows = 4
216
+ num_cols = 6
217
+ num_images = num_rows * num_cols
218
+ plt.figure(figsize=(2 * 2 * num_cols, 2 * num_rows))
219
+ for i in range(min(num_images, len(predictions))):
220
+ plt.subplot(num_rows, 2 * num_cols, 2 * i + 1)
221
+ plot_image(i, predictions[i], test_labels, test_images, self.class_names)
222
+ plt.subplot(num_rows, 2 * num_cols, 2 * i + 2)
223
+ plot_value_array(i, predictions[i], test_labels)
224
+ plt.tight_layout()
225
+
226
+ if output_filename:
227
+ filename_ = file.auxiliary("prediction", "png")
228
+ plt.savefig(filename_)
229
+ plt.close()
230
+
231
+ success, image = file.load_image(filename_)
232
+ if success:
233
+ image = graphics.add_signature(
234
+ image,
235
+ [" | ".join(host.signature()), " | ".join(objects.signature())],
236
+ self.signature(prediction_time),
237
+ )
238
+ file.save_image(output_filename, image)
239
+
240
+ def save(self, model_path):
241
+ model_filename = "{}/image_classifier/model".format(model_path)
242
+ file.prepare_for_saving(model_filename)
243
+ try:
244
+ self.model.save(model_filename)
245
+ logger.info("image_classifier.model -> {}".format(model_filename))
246
+ except:
247
+ from abcli.logging import crash_report
248
+
249
+ crash_report("image_classifier.save({}) failed".format(model_path))
250
+ return False
251
+
252
+ self.object_name = path.name(model_path)
253
+
254
+ self.model_size = file.size("{}/image_classifier/model".format(model_path))
255
+
256
+ if not file.save_json(
257
+ "{}/class_names.json".format(model_path), self.class_names
258
+ ):
259
+ return False
260
+
261
+ if not file.save_json("{}/params.json".format(model_path), self.params):
262
+ return False
263
+
264
+ return True
265
+
266
+ def signature(self, prediction_time):
267
+ return [
268
+ " | ".join(
269
+ [
270
+ "image_classifier",
271
+ self.object_name,
272
+ string.pretty_bytes(self.model_size) if self.model_size else "",
273
+ string.pretty_size(self.input_shape),
274
+ "/".join(string.shorten(self.class_names)),
275
+ "took {} / frame".format(
276
+ string.pretty_duration(
277
+ prediction_time,
278
+ include_ms=True,
279
+ longest=True,
280
+ short=True,
281
+ )
282
+ ),
283
+ ]
284
+ )
285
+ ]
286
+
287
+ @staticmethod
288
+ def train(data_path, model_path, options=""):
289
+ options = (
290
+ Options(options)
291
+ .default("color", False)
292
+ .default("convnet", True)
293
+ .default("epochs", 10)
294
+ )
295
+
296
+ classifier = image_classifier()
297
+ classifier.params["convnet"] = options["convnet"]
298
+
299
+ logger.info(
300
+ "image_classifier.train({}) -{}> {}".format(
301
+ data_path,
302
+ "convnet-" if classifier.params["convnet"] else "",
303
+ model_path,
304
+ )
305
+ )
306
+
307
+ success, train_images = file.load("{}/train_images.pyndarray".format(data_path))
308
+ if success:
309
+ success, train_labels = file.load(f"{data_path}/train_labels.pyndarray")
310
+ if success:
311
+ success, test_images = file.load(f"{data_path}/test_images.pyndarray")
312
+ if success:
313
+ success, test_labels = file.load(f"{data_path}/test_labels.pyndarray")
314
+ if success:
315
+ success, classifier.class_names = file.load_json(
316
+ f"{data_path}/class_names.json"
317
+ )
318
+ if not success:
319
+ return False
320
+
321
+ from tensorflow.keras.utils import to_categorical
322
+
323
+ train_labels = to_categorical(train_labels)
324
+ test_labels = to_categorical(test_labels)
325
+
326
+ window_size = train_images.shape[1]
327
+ input_shape = (
328
+ (window_size, window_size, 3)
329
+ if options["color"]
330
+ else (window_size, window_size, 1)
331
+ if options["convnet"]
332
+ else (window_size, window_size)
333
+ )
334
+ logger.info(f"input_shape:{string.pretty_size(input_shape)}")
335
+
336
+ if options["convnet"] and not options["color"]:
337
+ train_images = np.expand_dims(train_images, axis=3)
338
+ test_images = np.expand_dims(test_images, axis=3)
339
+
340
+ for name, thing in zip(
341
+ "train_images,train_labels,test_images,test_labels".split(","),
342
+ [train_images, train_labels, test_images, test_labels],
343
+ ):
344
+ logger.info("{}: {}".format(name, string.pretty_size_of_matrix(thing)))
345
+ logger.info(
346
+ "{} class(es): {}".format(
347
+ len(classifier.class_names), classifier.class_names
348
+ )
349
+ )
350
+
351
+ train_images = train_images / 255.0
352
+ test_images = test_images / 255.0
353
+
354
+ if options["convnet"]:
355
+ # https://medium.com/swlh/convolutional-neural-networks-for-multiclass-image-classification-a-beginners-guide-to-6dbc09fabbd
356
+ classifier.model = tf.keras.Sequential(
357
+ [
358
+ tf.keras.layers.Conv2D(
359
+ filters=48,
360
+ kernel_size=3,
361
+ activation="relu",
362
+ input_shape=input_shape,
363
+ ),
364
+ tf.keras.layers.MaxPool2D(pool_size=2, strides=2),
365
+ tf.keras.layers.Conv2D(
366
+ filters=48, kernel_size=3, activation="relu"
367
+ ),
368
+ tf.keras.layers.MaxPool2D(pool_size=2, strides=2),
369
+ tf.keras.layers.Conv2D(
370
+ filters=32, kernel_size=3, activation="relu"
371
+ ),
372
+ tf.keras.layers.MaxPool2D(pool_size=2, strides=2),
373
+ tf.keras.layers.Flatten(),
374
+ tf.keras.layers.Dense(128, activation="relu"),
375
+ tf.keras.layers.Dense(64, activation="relu"),
376
+ tf.keras.layers.Dense(len(classifier.class_names)),
377
+ tf.keras.layers.Activation("softmax"),
378
+ ]
379
+ )
380
+ else:
381
+ # https://github.com/gato/tensor-on-pi/blob/master/Convolutional%20Neural%20Network%20digit%20predictor.ipynb
382
+ classifier.model = tf.keras.Sequential(
383
+ [
384
+ tf.keras.layers.Flatten(input_shape=input_shape),
385
+ tf.keras.layers.Dense(128, activation="relu"),
386
+ tf.keras.layers.Dense(len(classifier.class_names)),
387
+ tf.keras.layers.Activation("softmax"),
388
+ ]
389
+ )
390
+
391
+ classifier.model.summary()
392
+
393
+ classifier.model.compile(
394
+ optimizer="adam",
395
+ loss=tf.keras.losses.categorical_crossentropy,
396
+ metrics=["accuracy"],
397
+ )
398
+
399
+ classifier.model.fit(train_images, train_labels, epochs=options["epochs"])
400
+
401
+ test_accuracy = float(
402
+ classifier.model.evaluate(test_images, test_labels, verbose=2)[1]
403
+ )
404
+ logger.info("test accuracy: {:.4f}".format(test_accuracy))
405
+
406
+ if not file.save_json(
407
+ f"{model_path}/eval.json",
408
+ {"metrics": {"test_accuracy": test_accuracy}},
409
+ ):
410
+ return False
411
+
412
+ if not classifier.save(model_path):
413
+ return False
414
+
415
+ return classifier.predict(
416
+ test_images,
417
+ np.argmax(test_labels, axis=1),
418
+ model_path,
419
+ cache=True,
420
+ page_count=10,
421
+ )
422
+
423
+ @property
424
+ def input_shape(self):
425
+ return self.model.layers[0].input_shape[1:] if self.model.layers else []
fashion_mnist/image_classifier/funcs.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from . import *
2
+ from abcli import file
3
+ from abcli import string
4
+ import cv2
5
+ import numpy as np
6
+ import os.path
7
+ import abcli.logging
8
+ import logging
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def eval(input_path, output_path):
14
+ from sklearn.metrics import accuracy_score
15
+
16
+ report = {"accuracy": None}
17
+
18
+ success, ground_truth = file.load(f"{input_path}/test_labels.pyndarray")
19
+ if success:
20
+ logger.info(
21
+ "groundtruth: {} - {}".format(
22
+ string.pretty_size_of_matrix(ground_truth),
23
+ ",".join([str(value) for value in ground_truth[:10]] + ["..."]),
24
+ )
25
+ )
26
+ success, predictions = file.load(f"{input_path}/predictions.pyndarray")
27
+
28
+ if success:
29
+ predictions = np.argmax(predictions, axis=1).astype(np.uint8)
30
+ logger.info(
31
+ "predictions: {} - {}".format(
32
+ string.pretty_size_of_matrix(predictions),
33
+ ",".join([str(value) for value in predictions[:10]] + ["..."]),
34
+ )
35
+ )
36
+
37
+ report["accuracy"] = accuracy_score(predictions, ground_truth)
38
+
39
+ logger.info(
40
+ "image_classifier.eval({}->{}): {:.2f}%".format(
41
+ input_path, output_path, 100 * report["accuracy"]
42
+ )
43
+ )
44
+
45
+ return file.save_json(os.path.join(output_path, "evaluation_report.json"), report)
46
+
47
+
48
+ def preprocess(
49
+ output_path,
50
+ objects="",
51
+ infer_annotation=True,
52
+ purpose="predict",
53
+ test_size=1.0 / 6,
54
+ window_size=28,
55
+ ):
56
+ if objects:
57
+ logger.info(
58
+ "image_classifier.preprocess({}{})->{} - {}x{} - for {}".format(
59
+ ",".join(objects),
60
+ " + annotation" if infer_annotation else "",
61
+ output_path,
62
+ window_size,
63
+ window_size,
64
+ purpose,
65
+ )
66
+ )
67
+
68
+ annotations = []
69
+ list_of_images = []
70
+ for index, object in enumerate(objects):
71
+ list_of_images_ = [
72
+ "{}/Data/{}/camera.jpg".format(object, frame)
73
+ for frame in objects.list_of_frames(object)
74
+ ]
75
+
76
+ annotations += len(list_of_images_) * [index]
77
+ list_of_images += list_of_images_
78
+
79
+ annotations = np.array(annotations) if infer_annotation else []
80
+ else:
81
+ logger.info(
82
+ "image_classifier.preprocess({}) - {}x{} - for {}".format(
83
+ output_path,
84
+ window_size,
85
+ window_size,
86
+ purpose,
87
+ )
88
+ )
89
+
90
+ list_of_images = [
91
+ "{}/Data/{}/camera.jpg".format(output_path, frame)
92
+ for frame in objects.list_of_frames(output_path)
93
+ ]
94
+
95
+ annotations = np.array(
96
+ file.load_json(
97
+ f"{output_path}/annotations.json".format(),
98
+ civilized=True,
99
+ default=None,
100
+ )[1]
101
+ ).astype(np.uint8)
102
+
103
+ if len(annotations) and len(list_of_images) != len(annotations):
104
+ logger.error(
105
+ f"-{name}: preprocess: mismatch between frame and annotation counts: {len(list_of_images):,g} != {len(annotations):,g}"
106
+ )
107
+ return False
108
+ logger.info("{:,} frame(s)".format(len(list_of_images)))
109
+
110
+ tensor = np.zeros(
111
+ (len(list_of_images), window_size, window_size, 3),
112
+ dtype=np.uint8,
113
+ )
114
+
115
+ error_count = 0
116
+ for index, filename in enumerate(list_of_images):
117
+ logger.info("+= {}".format(filename))
118
+ success_, image = file.load_image(filename)
119
+ if success_:
120
+ try:
121
+ tensor[index, :, :, :] = cv2.resize(image, (window_size, window_size))
122
+ except:
123
+ from abcli.logging import crash_report
124
+
125
+ crash_report("image_classifier.preprocess() failed")
126
+ success_ = False
127
+
128
+ if not success_:
129
+ error_count += 1
130
+ logger.info(
131
+ "tensor: {}{}".format(
132
+ string.pretty_size_of_matrix(tensor),
133
+ " {} error(s)".format(error_count) if error_count else "",
134
+ )
135
+ )
136
+
137
+ success = False
138
+ if purpose == "predict":
139
+ if not file.save("{}/test_images.pyndarray".format(output_path), tensor):
140
+ return False
141
+ if len(annotations):
142
+ if not file.save(
143
+ "{}/test_labels.pyndarray".format(output_path), annotations
144
+ ):
145
+ return False
146
+ success = True
147
+ elif purpose == "train":
148
+ if not len(annotations):
149
+ logger.error(f"-{name}: preprocess: annotations are not provided.")
150
+ return False
151
+
152
+ from sklearn.model_selection import train_test_split
153
+
154
+ (
155
+ tensor_train,
156
+ tensor_test,
157
+ annotations_train,
158
+ annotations_test,
159
+ ) = train_test_split(tensor, annotations, test_size=test_size)
160
+ logger.info(
161
+ "test-train split: {:.0f}%-{:.0f}% ".format(
162
+ len(annotations_test) / len(annotations) * 100,
163
+ len(annotations_train) / len(annotations) * 100,
164
+ )
165
+ )
166
+ logger.info(
167
+ "tensor_train: {}".format(string.pretty_size_of_matrix(tensor_train))
168
+ )
169
+ logger.info("tensor_test: {}".format(string.pretty_size_of_matrix(tensor_test)))
170
+ logger.info(
171
+ "annotations_train: {}".format(
172
+ string.pretty_size_of_matrix(annotations_train)
173
+ )
174
+ )
175
+ logger.info(
176
+ "annotations_test: {}".format(
177
+ string.pretty_size_of_matrix(annotations_test)
178
+ )
179
+ )
180
+
181
+ success = (
182
+ file.save("{}/train_images.pyndarray".format(output_path), tensor_train)
183
+ and file.save("{}/test_images.pyndarray".format(output_path), tensor_test)
184
+ and file.save(
185
+ "{}/train_labels.pyndarray".format(output_path), annotations_train
186
+ )
187
+ and file.save(
188
+ "{}/test_labels.pyndarray".format(output_path), annotations_test
189
+ )
190
+ )
191
+ else:
192
+ logger.error(f"-{name}: preprocess: {purpose}: purpose not found.")
193
+
194
+ return success
fashion_mnist/image_classifier/plot.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from abcli import string
2
+ import matplotlib.pyplot as plt
3
+ import numpy as np
4
+ import abcli.logging
5
+ import logging
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ def plot_image(i, predictions_array, true_label, image, class_names):
11
+ plt.grid(False)
12
+ plt.xticks([])
13
+ plt.yticks([])
14
+
15
+ plt.imshow(image[i], cmap=plt.cm.binary)
16
+
17
+ predicted_label = np.argmax(predictions_array)
18
+
19
+ if true_label is None:
20
+ color = "black"
21
+ elif predicted_label == true_label[i]:
22
+ color = "blue"
23
+ else:
24
+ color = "red"
25
+
26
+ plt.xlabel(
27
+ "{} {:2.0f}%{}".format(
28
+ string.shorten(class_names[predicted_label]),
29
+ 100 * np.max(predictions_array),
30
+ ""
31
+ if true_label is None
32
+ else " ({})".format(string.shorten(class_names[true_label[i]])),
33
+ ),
34
+ color=color,
35
+ )
36
+
37
+
38
+ def plot_value_array(i, predictions_array, true_label):
39
+ plt.grid(False)
40
+ plt.xticks(range(len(predictions_array)))
41
+ plt.yticks([])
42
+ handle = plt.bar(range(len(predictions_array)), predictions_array, color="#777777")
43
+ plt.ylim([0, 1])
44
+ predicted_label = np.argmax(predictions_array)
45
+
46
+ handle[predicted_label].set_color("green")
47
+ if true_label is not None:
48
+ handle[true_label[i]].set_color("blue")