├── README.md
├── classify.py
├── dreieck.jpg
├── image.png
├── plotly-test.py
├── retrain.py
├── run_train_and_classify_befehl.txt
└── train.sh
/README.md:
--------------------------------------------------------------------------------
1 | # tensorflow-image-classifier
2 |
3 |
4 | Described here at the bottom half:
5 | https://medium.com/@m_ko/deep-learning-with-tensorflow-part-2-image-classification-58fcdffa7b84
6 |
7 | A generic image classifier program using Tensorflow (https://www.tensorflow.org/) and the pre-trained Deep Learning Convolutional Neural Network model called Inception (https://research.googleblog.com/2016/03/train-your-own-image-classifier-with.html).
8 |
9 | This model has been pre-trained for the ImageNet (http://image-net.org/) data, it can differentiate between 1,000 different classes
10 | The program applies Transfer Learning to this existing model and re-trains it to classify a new set of images.
11 |
12 | This is a generic setup and can be used to classify almost any kind of image.
13 |
14 | ## Installation
15 | Make sure you have Python (https://www.python.org/) installed, then install Tensorflow (https://www.tensorflow.org/install/) on your system, and clone this repo.
16 |
17 |
18 |
19 | ## Usage
20 |
21 | The usage is described in this article at the bottom half, simply follow the steps:
22 | https://medium.com/@m_ko/deep-learning-with-tensorflow-part-2-image-classification-58fcdffa7b84
23 |
24 |
25 | If you wanted to use a video as input and look at it frame-by-frame, check out this repository:
26 | https://github.com/koflerm/tensorflow-video-classifier
27 |
28 |
29 | ## License
30 | MIT License
31 |
32 | Copyright (c) 2017 Matteo Kofler
33 |
34 | Permission is hereby granted, free of charge, to any person obtaining a copy
35 | of this software and associated documentation files (the "Software"), to deal
36 | in the Software without restriction, including without limitation the rights
37 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
38 | copies of the Software, and to permit persons to whom the Software is
39 | furnished to do so, subject to the following conditions:
40 |
41 | The above copyright notice and this permission notice shall be included in all
42 | copies or substantial portions of the Software.
43 |
44 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
45 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
46 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
47 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
48 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
49 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
50 | SOFTWARE.
51 |
52 |
--------------------------------------------------------------------------------
/classify.py:
--------------------------------------------------------------------------------
1 | import tensorflow as tf
2 | import sys
3 | import os
4 |
5 | # speicherorte fuer trainierten graph und labels in train.sh festlegen ##
6 |
7 | # Disable tensorflow compilation warnings
8 | os.environ['TF_CPP_MIN_LOG_LEVEL']='2'
9 | import tensorflow as tf
10 |
11 | image_path = sys.argv[1]
12 | # angabe in console als argument nach dem aufruf
13 |
14 |
15 | #bilddatei readen
16 | image_data = tf.gfile.FastGFile(image_path, 'rb').read()
17 |
18 | # holt labels aus file in array
19 | label_lines = [line.rstrip() for line
20 | in tf.gfile.GFile("tf_files/retrained_labels.txt")]
21 | # !! labels befinden sich jeweils in eigenen lines -> keine aenderung in retrain.py noetig -> falsche darstellung im windows editor !!
22 |
23 | # graph einlesen, wurde in train.sh -> call retrain.py trainiert
24 | with tf.gfile.FastGFile("tf_files/retrained_graph.pb", 'rb') as f:
25 |
26 | graph_def = tf.GraphDef() ## The graph-graph_def is a saved copy of a TensorFlow graph; objektinitialisierung
27 | graph_def.ParseFromString(f.read()) #Parse serialized protocol buffer data into variable
28 | _ = tf.import_graph_def(graph_def, name='') # import a serialized TensorFlow GraphDef protocol buffer, extract objects in the GraphDef as tf.Tensor
29 |
30 | #https://github.com/Hvass-Labs/TensorFlow-Tutorials/blob/master/inception.py ; ab zeile 276
31 |
32 | with tf.Session() as sess:
33 |
34 | softmax_tensor = sess.graph.get_tensor_by_name('final_result:0')
35 | # return: Tensor("final_result:0", shape=(?, 4), dtype=float32); stringname definiert in retrain.py, zeile 1064
36 |
37 | predictions = sess.run(softmax_tensor, \
38 | {'DecodeJpeg/contents:0': image_data})
39 | # gibt prediction values in array zuerueck:
40 |
41 | top_k = predictions[0].argsort()[-len(predictions[0]):][::-1]
42 | # sortierung; circle -> 0, plus -> 1, square -> 2, triangle -> 3; array return bsp [3 1 2 0] -> sortiert nach groesster uebereinstimmmung
43 |
44 | # output
45 | for node_id in top_k:
46 | human_string = label_lines[node_id]
47 | score = predictions[0][node_id]
48 | print('%s (score = %.5f)' % (human_string, score))
--------------------------------------------------------------------------------
/dreieck.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burliEnterprises/tensorflow-image-classifier/8ff564ed859316e116181403a36b510dd128116b/dreieck.jpg
--------------------------------------------------------------------------------
/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burliEnterprises/tensorflow-image-classifier/8ff564ed859316e116181403a36b510dd128116b/image.png
--------------------------------------------------------------------------------
/plotly-test.py:
--------------------------------------------------------------------------------
1 | import plotly.graph_objs as go
2 | import plotly.plotly as py
3 |
4 | import numpy as np
5 |
6 | colorscale = [[0, '#FAEE1C'], [0.33, '#F3558E'], [0.66, '#9C1DE7'], [1, '#581B98']]
7 | trace1 = go.Scatter(
8 | y = np.random.randn(500),
9 | mode='markers',
10 | marker=dict(
11 | size='16',
12 | color = np.random.randn(500),
13 | colorscale=colorscale,
14 | showscale=True
15 | )
16 | )
17 | data = [trace1]
18 | url_1 = py.plot(data, filename='scatter-for-dashboard', auto_open=False)
19 | py.iplot(data, filename='scatter-for-dashboard')
--------------------------------------------------------------------------------
/retrain.py:
--------------------------------------------------------------------------------
1 | # Copyright 2015 The TensorFlow Authors. All Rights Reserved.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | # ==============================================================================
15 | """Simple transfer learning with an Inception v3 architecture model.
16 |
17 | With support for TensorBoard.
18 |
19 | This example shows how to take a Inception v3 architecture model trained on
20 | ImageNet images, and train a new top layer that can recognize other classes of
21 | images.
22 |
23 | The top layer receives as input a 2048-dimensional vector for each image. We
24 | train a softmax layer on top of this representation. Assuming the softmax layer
25 | contains N labels, this corresponds to learning N + 2048*N model parameters
26 | corresponding to the learned biases and weights.
27 |
28 | Here's an example, which assumes you have a folder containing class-named
29 | subfolders, each full of images for each label. The example folder flower_photos
30 | should have a structure like this:
31 |
32 | ~/flower_photos/daisy/photo1.jpg
33 | ~/flower_photos/daisy/photo2.jpg
34 | ...
35 | ~/flower_photos/rose/anotherphoto77.jpg
36 | ...
37 | ~/flower_photos/sunflower/somepicture.jpg
38 |
39 | The subfolder names are important, since they define what label is applied to
40 | each image, but the filenames themselves don't matter. Once your images are
41 | prepared, you can run the training with a command like this:
42 |
43 |
44 | ```bash
45 | bazel build tensorflow/examples/image_retraining:retrain && \
46 | bazel-bin/tensorflow/examples/image_retraining/retrain \
47 | --image_dir ~/flower_photos
48 | ```
49 |
50 | Or, if you have a pip installation of tensorflow, `retrain.py` can be run
51 | without bazel:
52 |
53 | ```bash
54 | python tensorflow/examples/image_retraining/retrain.py \
55 | --image_dir ~/flower_photos
56 | ```
57 |
58 | You can replace the image_dir argument with any folder containing subfolders of
59 | images. The label for each image is taken from the name of the subfolder it's
60 | in.
61 |
62 | This produces a new model file that can be loaded and run by any TensorFlow
63 | program, for example the label_image sample code.
64 |
65 |
66 | To use with TensorBoard:
67 |
68 | By default, this script will log summaries to /tmp/retrain_logs directory
69 |
70 | Visualize the summaries with this command:
71 |
72 | tensorboard --logdir /tmp/retrain_logs
73 |
74 | """
75 | from __future__ import absolute_import
76 | from __future__ import division
77 | from __future__ import print_function
78 |
79 | import argparse
80 | from datetime import datetime
81 | import hashlib
82 | import os.path
83 | import random
84 | import re
85 | import struct
86 | import sys
87 | import tarfile
88 |
89 | import numpy as np
90 | from six.moves import urllib
91 | import tensorflow as tf
92 |
93 | from tensorflow.python.framework import graph_util
94 | from tensorflow.python.framework import tensor_shape
95 | from tensorflow.python.platform import gfile
96 | from tensorflow.python.util import compat
97 |
98 | FLAGS = None
99 |
100 | # These are all parameters that are tied to the particular model architecture
101 | # we're using for Inception v3. These include things like tensor names and their
102 | # sizes. If you want to adapt this script to work with another model, you will
103 | # need to update these to reflect the values in the network you're using.
104 | # pylint: disable=line-too-long
105 | DATA_URL = 'http://download.tensorflow.org/models/image/imagenet/inception-2015-12-05.tgz'
106 | # pylint: enable=line-too-long
107 | BOTTLENECK_TENSOR_NAME = 'pool_3/_reshape:0'
108 | BOTTLENECK_TENSOR_SIZE = 2048
109 | MODEL_INPUT_WIDTH = 299
110 | MODEL_INPUT_HEIGHT = 299
111 | MODEL_INPUT_DEPTH = 3
112 | JPEG_DATA_TENSOR_NAME = 'DecodeJpeg/contents:0'
113 | RESIZED_INPUT_TENSOR_NAME = 'ResizeBilinear:0'
114 | MAX_NUM_IMAGES_PER_CLASS = 2 ** 27 - 1 # ~134M
115 |
116 |
117 | def create_image_lists(image_dir, testing_percentage, validation_percentage):
118 | """Builds a list of training images from the file system.
119 |
120 | Analyzes the sub folders in the image directory, splits them into stable
121 | training, testing, and validation sets, and returns a data structure
122 | describing the lists of images for each label and their paths.
123 |
124 | Args:
125 | image_dir: String path to a folder containing subfolders of images.
126 | testing_percentage: Integer percentage of the images to reserve for tests.
127 | validation_percentage: Integer percentage of images reserved for validation.
128 |
129 | Returns:
130 | A dictionary containing an entry for each label subfolder, with images split
131 | into training, testing, and validation sets within each label.
132 | """
133 | if not gfile.Exists(image_dir):
134 | print("Image directory '" + image_dir + "' not found.")
135 | return None
136 | result = {}
137 | sub_dirs = [x[0] for x in gfile.Walk(image_dir)]
138 | # The root directory comes first, so skip it.
139 | is_root_dir = True
140 | for sub_dir in sub_dirs:
141 | if is_root_dir:
142 | is_root_dir = False
143 | continue
144 | extensions = ['jpg', 'jpeg', 'JPG', 'JPEG']
145 | file_list = []
146 | dir_name = os.path.basename(sub_dir)
147 | if dir_name == image_dir:
148 | continue
149 | print("Looking for images in '" + dir_name + "'")
150 | for extension in extensions:
151 | file_glob = os.path.join(image_dir, dir_name, '*.' + extension)
152 | file_list.extend(gfile.Glob(file_glob))
153 | if not file_list:
154 | print('No files found')
155 | continue
156 | if len(file_list) < 20:
157 | print('WARNING: Folder has less than 20 images, which may cause issues.')
158 | elif len(file_list) > MAX_NUM_IMAGES_PER_CLASS:
159 | print('WARNING: Folder {} has more than {} images. Some images will '
160 | 'never be selected.'.format(dir_name, MAX_NUM_IMAGES_PER_CLASS))
161 | label_name = re.sub(r'[^a-z0-9]+', ' ', dir_name.lower())
162 | training_images = []
163 | testing_images = []
164 | validation_images = []
165 | for file_name in file_list:
166 | base_name = os.path.basename(file_name)
167 | # We want to ignore anything after '_nohash_' in the file name when
168 | # deciding which set to put an image in, the data set creator has a way of
169 | # grouping photos that are close variations of each other. For example
170 | # this is used in the plant disease data set to group multiple pictures of
171 | # the same leaf.
172 | hash_name = re.sub(r'_nohash_.*$', '', file_name)
173 | # This looks a bit magical, but we need to decide whether this file should
174 | # go into the training, testing, or validation sets, and we want to keep
175 | # existing files in the same set even if more files are subsequently
176 | # added.
177 | # To do that, we need a stable way of deciding based on just the file name
178 | # itself, so we do a hash of that and then use that to generate a
179 | # probability value that we use to assign it.
180 | hash_name_hashed = hashlib.sha1(compat.as_bytes(hash_name)).hexdigest()
181 | percentage_hash = ((int(hash_name_hashed, 16) %
182 | (MAX_NUM_IMAGES_PER_CLASS + 1)) *
183 | (100.0 / MAX_NUM_IMAGES_PER_CLASS))
184 | if percentage_hash < validation_percentage:
185 | validation_images.append(base_name)
186 | elif percentage_hash < (testing_percentage + validation_percentage):
187 | testing_images.append(base_name)
188 | else:
189 | training_images.append(base_name)
190 | result[label_name] = {
191 | 'dir': dir_name,
192 | 'training': training_images,
193 | 'testing': testing_images,
194 | 'validation': validation_images,
195 | }
196 | return result
197 |
198 |
199 | def get_image_path(image_lists, label_name, index, image_dir, category):
200 | """"Returns a path to an image for a label at the given index.
201 |
202 | Args:
203 | image_lists: Dictionary of training images for each label.
204 | label_name: Label string we want to get an image for.
205 | index: Int offset of the image we want. This will be moduloed by the
206 | available number of images for the label, so it can be arbitrarily large.
207 | image_dir: Root folder string of the subfolders containing the training
208 | images.
209 | category: Name string of set to pull images from - training, testing, or
210 | validation.
211 |
212 | Returns:
213 | File system path string to an image that meets the requested parameters.
214 |
215 | """
216 | if label_name not in image_lists:
217 | tf.logging.fatal('Label does not exist %s.', label_name)
218 | label_lists = image_lists[label_name]
219 | if category not in label_lists:
220 | tf.logging.fatal('Category does not exist %s.', category)
221 | category_list = label_lists[category]
222 | if not category_list:
223 | tf.logging.fatal('Label %s has no images in the category %s.',
224 | label_name, category)
225 | mod_index = index % len(category_list)
226 | base_name = category_list[mod_index]
227 | sub_dir = label_lists['dir']
228 | full_path = os.path.join(image_dir, sub_dir, base_name)
229 | return full_path
230 |
231 |
232 | def get_bottleneck_path(image_lists, label_name, index, bottleneck_dir,
233 | category):
234 | """"Returns a path to a bottleneck file for a label at the given index.
235 |
236 | Args:
237 | image_lists: Dictionary of training images for each label.
238 | label_name: Label string we want to get an image for.
239 | index: Integer offset of the image we want. This will be moduloed by the
240 | available number of images for the label, so it can be arbitrarily large.
241 | bottleneck_dir: Folder string holding cached files of bottleneck values.
242 | category: Name string of set to pull images from - training, testing, or
243 | validation.
244 |
245 | Returns:
246 | File system path string to an image that meets the requested parameters.
247 | """
248 | return get_image_path(image_lists, label_name, index, bottleneck_dir,
249 | category) + '.txt'
250 |
251 |
252 | def create_inception_graph():
253 | """"Creates a graph from saved GraphDef file and returns a Graph object.
254 |
255 | Returns:
256 | Graph holding the trained Inception network, and various tensors we'll be
257 | manipulating.
258 | """
259 | with tf.Graph().as_default() as graph:
260 | model_filename = os.path.join(
261 | FLAGS.model_dir, 'classify_image_graph_def.pb')
262 | with gfile.FastGFile(model_filename, 'rb') as f:
263 | graph_def = tf.GraphDef()
264 | graph_def.ParseFromString(f.read())
265 | bottleneck_tensor, jpeg_data_tensor, resized_input_tensor = (
266 | tf.import_graph_def(graph_def, name='', return_elements=[
267 | BOTTLENECK_TENSOR_NAME, JPEG_DATA_TENSOR_NAME,
268 | RESIZED_INPUT_TENSOR_NAME]))
269 | return graph, bottleneck_tensor, jpeg_data_tensor, resized_input_tensor
270 |
271 |
272 | def run_bottleneck_on_image(sess, image_data, image_data_tensor,
273 | bottleneck_tensor):
274 | """Runs inference on an image to extract the 'bottleneck' summary layer.
275 |
276 | Args:
277 | sess: Current active TensorFlow Session.
278 | image_data: String of raw JPEG data.
279 | image_data_tensor: Input data layer in the graph.
280 | bottleneck_tensor: Layer before the final softmax.
281 |
282 | Returns:
283 | Numpy array of bottleneck values.
284 | """
285 | bottleneck_values = sess.run(
286 | bottleneck_tensor,
287 | {image_data_tensor: image_data})
288 | bottleneck_values = np.squeeze(bottleneck_values)
289 | return bottleneck_values
290 |
291 |
292 | def maybe_download_and_extract():
293 | """Download and extract model tar file.
294 |
295 | If the pretrained model we're using doesn't already exist, this function
296 | downloads it from the TensorFlow.org website and unpacks it into a directory.
297 | """
298 | dest_directory = FLAGS.model_dir
299 | if not os.path.exists(dest_directory):
300 | os.makedirs(dest_directory)
301 | filename = DATA_URL.split('/')[-1]
302 | filepath = os.path.join(dest_directory, filename)
303 | if not os.path.exists(filepath):
304 |
305 | def _progress(count, block_size, total_size):
306 | sys.stdout.write('\r>> Downloading %s %.1f%%' %
307 | (filename,
308 | float(count * block_size) / float(total_size) * 100.0))
309 | sys.stdout.flush()
310 |
311 | filepath, _ = urllib.request.urlretrieve(DATA_URL,
312 | filepath,
313 | _progress)
314 | print()
315 | statinfo = os.stat(filepath)
316 | print('Successfully downloaded', filename, statinfo.st_size, 'bytes.')
317 | tarfile.open(filepath, 'r:gz').extractall(dest_directory)
318 |
319 |
320 | def ensure_dir_exists(dir_name):
321 | """Makes sure the folder exists on disk.
322 |
323 | Args:
324 | dir_name: Path string to the folder we want to create.
325 | """
326 | if not os.path.exists(dir_name):
327 | os.makedirs(dir_name)
328 |
329 |
330 | def write_list_of_floats_to_file(list_of_floats, file_path):
331 | """Writes a given list of floats to a binary file.
332 |
333 | Args:
334 | list_of_floats: List of floats we want to write to a file.
335 | file_path: Path to a file where list of floats will be stored.
336 |
337 | """
338 |
339 | s = struct.pack('d' * BOTTLENECK_TENSOR_SIZE, *list_of_floats)
340 | with open(file_path, 'wb') as f:
341 | f.write(s)
342 |
343 |
344 | def read_list_of_floats_from_file(file_path):
345 | """Reads list of floats from a given file.
346 |
347 | Args:
348 | file_path: Path to a file where list of floats was stored.
349 | Returns:
350 | Array of bottleneck values (list of floats).
351 |
352 | """
353 |
354 | with open(file_path, 'rb') as f:
355 | s = struct.unpack('d' * BOTTLENECK_TENSOR_SIZE, f.read())
356 | return list(s)
357 |
358 |
359 | bottleneck_path_2_bottleneck_values = {}
360 |
361 |
362 | def create_bottleneck_file(bottleneck_path, image_lists, label_name, index,
363 | image_dir, category, sess, jpeg_data_tensor,
364 | bottleneck_tensor):
365 | """Create a single bottleneck file."""
366 | print('Creating bottleneck at ' + bottleneck_path)
367 | image_path = get_image_path(image_lists, label_name, index,
368 | image_dir, category)
369 | if not gfile.Exists(image_path):
370 | tf.logging.fatal('File does not exist %s', image_path)
371 | image_data = gfile.FastGFile(image_path, 'rb').read()
372 | try:
373 | bottleneck_values = run_bottleneck_on_image(
374 | sess, image_data, jpeg_data_tensor, bottleneck_tensor)
375 | except:
376 | raise RuntimeError('Error during processing file %s' % image_path)
377 |
378 | bottleneck_string = ','.join(str(x) for x in bottleneck_values)
379 | with open(bottleneck_path, 'w') as bottleneck_file:
380 | bottleneck_file.write(bottleneck_string)
381 |
382 |
383 | def get_or_create_bottleneck(sess, image_lists, label_name, index, image_dir,
384 | category, bottleneck_dir, jpeg_data_tensor,
385 | bottleneck_tensor):
386 | """Retrieves or calculates bottleneck values for an image.
387 |
388 | If a cached version of the bottleneck data exists on-disk, return that,
389 | otherwise calculate the data and save it to disk for future use.
390 |
391 | Args:
392 | sess: The current active TensorFlow Session.
393 | image_lists: Dictionary of training images for each label.
394 | label_name: Label string we want to get an image for.
395 | index: Integer offset of the image we want. This will be modulo-ed by the
396 | available number of images for the label, so it can be arbitrarily large.
397 | image_dir: Root folder string of the subfolders containing the training
398 | images.
399 | category: Name string of which set to pull images from - training, testing,
400 | or validation.
401 | bottleneck_dir: Folder string holding cached files of bottleneck values.
402 | jpeg_data_tensor: The tensor to feed loaded jpeg data into.
403 | bottleneck_tensor: The output tensor for the bottleneck values.
404 |
405 | Returns:
406 | Numpy array of values produced by the bottleneck layer for the image.
407 | """
408 | label_lists = image_lists[label_name]
409 | sub_dir = label_lists['dir']
410 | sub_dir_path = os.path.join(bottleneck_dir, sub_dir)
411 | ensure_dir_exists(sub_dir_path)
412 | bottleneck_path = get_bottleneck_path(image_lists, label_name, index,
413 | bottleneck_dir, category)
414 | if not os.path.exists(bottleneck_path):
415 | create_bottleneck_file(bottleneck_path, image_lists, label_name, index,
416 | image_dir, category, sess, jpeg_data_tensor,
417 | bottleneck_tensor)
418 | with open(bottleneck_path, 'r') as bottleneck_file:
419 | bottleneck_string = bottleneck_file.read()
420 | did_hit_error = False
421 | try:
422 | bottleneck_values = [float(x) for x in bottleneck_string.split(',')]
423 | except ValueError:
424 | print('Invalid float found, recreating bottleneck')
425 | did_hit_error = True
426 | if did_hit_error:
427 | create_bottleneck_file(bottleneck_path, image_lists, label_name, index,
428 | image_dir, category, sess, jpeg_data_tensor,
429 | bottleneck_tensor)
430 | with open(bottleneck_path, 'r') as bottleneck_file:
431 | bottleneck_string = bottleneck_file.read()
432 | # Allow exceptions to propagate here, since they shouldn't happen after a
433 | # fresh creation
434 | bottleneck_values = [float(x) for x in bottleneck_string.split(',')]
435 | return bottleneck_values
436 |
437 |
438 | def cache_bottlenecks(sess, image_lists, image_dir, bottleneck_dir,
439 | jpeg_data_tensor, bottleneck_tensor):
440 | """Ensures all the training, testing, and validation bottlenecks are cached.
441 |
442 | Because we're likely to read the same image multiple times (if there are no
443 | distortions applied during training) it can speed things up a lot if we
444 | calculate the bottleneck layer values once for each image during
445 | preprocessing, and then just read those cached values repeatedly during
446 | training. Here we go through all the images we've found, calculate those
447 | values, and save them off.
448 |
449 | Args:
450 | sess: The current active TensorFlow Session.
451 | image_lists: Dictionary of training images for each label.
452 | image_dir: Root folder string of the subfolders containing the training
453 | images.
454 | bottleneck_dir: Folder string holding cached files of bottleneck values.
455 | jpeg_data_tensor: Input tensor for jpeg data from file.
456 | bottleneck_tensor: The penultimate output layer of the graph.
457 |
458 | Returns:
459 | Nothing.
460 | """
461 | how_many_bottlenecks = 0
462 | ensure_dir_exists(bottleneck_dir)
463 | for label_name, label_lists in image_lists.items():
464 | for category in ['training', 'testing', 'validation']:
465 | category_list = label_lists[category]
466 | for index, unused_base_name in enumerate(category_list):
467 | get_or_create_bottleneck(sess, image_lists, label_name, index,
468 | image_dir, category, bottleneck_dir,
469 | jpeg_data_tensor, bottleneck_tensor)
470 |
471 | how_many_bottlenecks += 1
472 | if how_many_bottlenecks % 100 == 0:
473 | print(str(how_many_bottlenecks) + ' bottleneck files created.')
474 |
475 |
476 | def get_random_cached_bottlenecks(sess, image_lists, how_many, category,
477 | bottleneck_dir, image_dir, jpeg_data_tensor,
478 | bottleneck_tensor):
479 | """Retrieves bottleneck values for cached images.
480 |
481 | If no distortions are being applied, this function can retrieve the cached
482 | bottleneck values directly from disk for images. It picks a random set of
483 | images from the specified category.
484 |
485 | Args:
486 | sess: Current TensorFlow Session.
487 | image_lists: Dictionary of training images for each label.
488 | how_many: If positive, a random sample of this size will be chosen.
489 | If negative, all bottlenecks will be retrieved.
490 | category: Name string of which set to pull from - training, testing, or
491 | validation.
492 | bottleneck_dir: Folder string holding cached files of bottleneck values.
493 | image_dir: Root folder string of the subfolders containing the training
494 | images.
495 | jpeg_data_tensor: The layer to feed jpeg image data into.
496 | bottleneck_tensor: The bottleneck output layer of the CNN graph.
497 |
498 | Returns:
499 | List of bottleneck arrays, their corresponding ground truths, and the
500 | relevant filenames.
501 | """
502 | class_count = len(image_lists.keys())
503 | bottlenecks = []
504 | ground_truths = []
505 | filenames = []
506 | if how_many >= 0:
507 | # Retrieve a random sample of bottlenecks.
508 | for unused_i in range(how_many):
509 | label_index = random.randrange(class_count)
510 | label_name = list(image_lists.keys())[label_index]
511 | image_index = random.randrange(MAX_NUM_IMAGES_PER_CLASS + 1)
512 | image_name = get_image_path(image_lists, label_name, image_index,
513 | image_dir, category)
514 | bottleneck = get_or_create_bottleneck(sess, image_lists, label_name,
515 | image_index, image_dir, category,
516 | bottleneck_dir, jpeg_data_tensor,
517 | bottleneck_tensor)
518 | ground_truth = np.zeros(class_count, dtype=np.float32)
519 | ground_truth[label_index] = 1.0
520 | bottlenecks.append(bottleneck)
521 | ground_truths.append(ground_truth)
522 | filenames.append(image_name)
523 | else:
524 | # Retrieve all bottlenecks.
525 | for label_index, label_name in enumerate(image_lists.keys()):
526 | for image_index, image_name in enumerate(
527 | image_lists[label_name][category]):
528 | image_name = get_image_path(image_lists, label_name, image_index,
529 | image_dir, category)
530 | bottleneck = get_or_create_bottleneck(sess, image_lists, label_name,
531 | image_index, image_dir, category,
532 | bottleneck_dir, jpeg_data_tensor,
533 | bottleneck_tensor)
534 | ground_truth = np.zeros(class_count, dtype=np.float32)
535 | ground_truth[label_index] = 1.0
536 | bottlenecks.append(bottleneck)
537 | ground_truths.append(ground_truth)
538 | filenames.append(image_name)
539 | return bottlenecks, ground_truths, filenames
540 |
541 |
542 | def get_random_distorted_bottlenecks(
543 | sess, image_lists, how_many, category, image_dir, input_jpeg_tensor,
544 | distorted_image, resized_input_tensor, bottleneck_tensor):
545 | """Retrieves bottleneck values for training images, after distortions.
546 |
547 | If we're training with distortions like crops, scales, or flips, we have to
548 | recalculate the full model for every image, and so we can't use cached
549 | bottleneck values. Instead we find random images for the requested category,
550 | run them through the distortion graph, and then the full graph to get the
551 | bottleneck results for each.
552 |
553 | Args:
554 | sess: Current TensorFlow Session.
555 | image_lists: Dictionary of training images for each label.
556 | how_many: The integer number of bottleneck values to return.
557 | category: Name string of which set of images to fetch - training, testing,
558 | or validation.
559 | image_dir: Root folder string of the subfolders containing the training
560 | images.
561 | input_jpeg_tensor: The input layer we feed the image data to.
562 | distorted_image: The output node of the distortion graph.
563 | resized_input_tensor: The input node of the recognition graph.
564 | bottleneck_tensor: The bottleneck output layer of the CNN graph.
565 |
566 | Returns:
567 | List of bottleneck arrays and their corresponding ground truths.
568 | """
569 | class_count = len(image_lists.keys())
570 | bottlenecks = []
571 | ground_truths = []
572 | for unused_i in range(how_many):
573 | label_index = random.randrange(class_count)
574 | label_name = list(image_lists.keys())[label_index]
575 | image_index = random.randrange(MAX_NUM_IMAGES_PER_CLASS + 1)
576 | image_path = get_image_path(image_lists, label_name, image_index, image_dir,
577 | category)
578 | if not gfile.Exists(image_path):
579 | tf.logging.fatal('File does not exist %s', image_path)
580 | jpeg_data = gfile.FastGFile(image_path, 'rb').read()
581 | # Note that we materialize the distorted_image_data as a numpy array before
582 | # sending running inference on the image. This involves 2 memory copies and
583 | # might be optimized in other implementations.
584 | distorted_image_data = sess.run(distorted_image,
585 | {input_jpeg_tensor: jpeg_data})
586 | bottleneck = run_bottleneck_on_image(sess, distorted_image_data,
587 | resized_input_tensor,
588 | bottleneck_tensor)
589 | ground_truth = np.zeros(class_count, dtype=np.float32)
590 | ground_truth[label_index] = 1.0
591 | bottlenecks.append(bottleneck)
592 | ground_truths.append(ground_truth)
593 | return bottlenecks, ground_truths
594 |
595 |
596 | def should_distort_images(flip_left_right, random_crop, random_scale,
597 | random_brightness):
598 | """Whether any distortions are enabled, from the input flags.
599 |
600 | Args:
601 | flip_left_right: Boolean whether to randomly mirror images horizontally.
602 | random_crop: Integer percentage setting the total margin used around the
603 | crop box.
604 | random_scale: Integer percentage of how much to vary the scale by.
605 | random_brightness: Integer range to randomly multiply the pixel values by.
606 |
607 | Returns:
608 | Boolean value indicating whether any distortions should be applied.
609 | """
610 | return (flip_left_right or (random_crop != 0) or (random_scale != 0) or
611 | (random_brightness != 0))
612 |
613 |
614 | def add_input_distortions(flip_left_right, random_crop, random_scale,
615 | random_brightness):
616 | """Creates the operations to apply the specified distortions.
617 |
618 | During training it can help to improve the results if we run the images
619 | through simple distortions like crops, scales, and flips. These reflect the
620 | kind of variations we expect in the real world, and so can help train the
621 | model to cope with natural data more effectively. Here we take the supplied
622 | parameters and construct a network of operations to apply them to an image.
623 |
624 | Cropping
625 | ~~~~~~~~
626 |
627 | Cropping is done by placing a bounding box at a random position in the full
628 | image. The cropping parameter controls the size of that box relative to the
629 | input image. If it's zero, then the box is the same size as the input and no
630 | cropping is performed. If the value is 50%, then the crop box will be half the
631 | width and height of the input. In a diagram it looks like this:
632 |
633 | < width >
634 | +---------------------+
635 | | |
636 | | width - crop% |
637 | | < > |
638 | | +------+ |
639 | | | | |
640 | | | | |
641 | | | | |
642 | | +------+ |
643 | | |
644 | | |
645 | +---------------------+
646 |
647 | Scaling
648 | ~~~~~~~
649 |
650 | Scaling is a lot like cropping, except that the bounding box is always
651 | centered and its size varies randomly within the given range. For example if
652 | the scale percentage is zero, then the bounding box is the same size as the
653 | input and no scaling is applied. If it's 50%, then the bounding box will be in
654 | a random range between half the width and height and full size.
655 |
656 | Args:
657 | flip_left_right: Boolean whether to randomly mirror images horizontally.
658 | random_crop: Integer percentage setting the total margin used around the
659 | crop box.
660 | random_scale: Integer percentage of how much to vary the scale by.
661 | random_brightness: Integer range to randomly multiply the pixel values by.
662 | graph.
663 |
664 | Returns:
665 | The jpeg input layer and the distorted result tensor.
666 | """
667 |
668 | jpeg_data = tf.placeholder(tf.string, name='DistortJPGInput')
669 | decoded_image = tf.image.decode_jpeg(jpeg_data, channels=MODEL_INPUT_DEPTH)
670 | decoded_image_as_float = tf.cast(decoded_image, dtype=tf.float32)
671 | decoded_image_4d = tf.expand_dims(decoded_image_as_float, 0)
672 | margin_scale = 1.0 + (random_crop / 100.0)
673 | resize_scale = 1.0 + (random_scale / 100.0)
674 | margin_scale_value = tf.constant(margin_scale)
675 | resize_scale_value = tf.random_uniform(tensor_shape.scalar(),
676 | minval=1.0,
677 | maxval=resize_scale)
678 | scale_value = tf.multiply(margin_scale_value, resize_scale_value)
679 | precrop_width = tf.multiply(scale_value, MODEL_INPUT_WIDTH)
680 | precrop_height = tf.multiply(scale_value, MODEL_INPUT_HEIGHT)
681 | precrop_shape = tf.stack([precrop_height, precrop_width])
682 | precrop_shape_as_int = tf.cast(precrop_shape, dtype=tf.int32)
683 | precropped_image = tf.image.resize_bilinear(decoded_image_4d,
684 | precrop_shape_as_int)
685 | precropped_image_3d = tf.squeeze(precropped_image, squeeze_dims=[0])
686 | cropped_image = tf.random_crop(precropped_image_3d,
687 | [MODEL_INPUT_HEIGHT, MODEL_INPUT_WIDTH,
688 | MODEL_INPUT_DEPTH])
689 | if flip_left_right:
690 | flipped_image = tf.image.random_flip_left_right(cropped_image)
691 | else:
692 | flipped_image = cropped_image
693 | brightness_min = 1.0 - (random_brightness / 100.0)
694 | brightness_max = 1.0 + (random_brightness / 100.0)
695 | brightness_value = tf.random_uniform(tensor_shape.scalar(),
696 | minval=brightness_min,
697 | maxval=brightness_max)
698 | brightened_image = tf.multiply(flipped_image, brightness_value)
699 | distort_result = tf.expand_dims(brightened_image, 0, name='DistortResult')
700 | return jpeg_data, distort_result
701 |
702 |
703 | def variable_summaries(var):
704 | """Attach a lot of summaries to a Tensor (for TensorBoard visualization)."""
705 | with tf.name_scope('summaries'):
706 | mean = tf.reduce_mean(var)
707 | tf.summary.scalar('mean', mean)
708 | with tf.name_scope('stddev'):
709 | stddev = tf.sqrt(tf.reduce_mean(tf.square(var - mean)))
710 | tf.summary.scalar('stddev', stddev)
711 | tf.summary.scalar('max', tf.reduce_max(var))
712 | tf.summary.scalar('min', tf.reduce_min(var))
713 | tf.summary.histogram('histogram', var)
714 |
715 |
716 | def add_final_training_ops(class_count, final_tensor_name, bottleneck_tensor):
717 | """Adds a new softmax and fully-connected layer for training.
718 |
719 | We need to retrain the top layer to identify our new classes, so this function
720 | adds the right operations to the graph, along with some variables to hold the
721 | weights, and then sets up all the gradients for the backward pass.
722 |
723 | The set up for the softmax and fully-connected layers is based on:
724 | https://tensorflow.org/versions/master/tutorials/mnist/beginners/index.html
725 |
726 | Args:
727 | class_count: Integer of how many categories of things we're trying to
728 | recognize.
729 | final_tensor_name: Name string for the new final node that produces results.
730 | bottleneck_tensor: The output of the main CNN graph.
731 |
732 | Returns:
733 | The tensors for the training and cross entropy results, and tensors for the
734 | bottleneck input and ground truth input.
735 | """
736 | with tf.name_scope('input'):
737 | bottleneck_input = tf.placeholder_with_default(
738 | bottleneck_tensor, shape=[None, BOTTLENECK_TENSOR_SIZE],
739 | name='BottleneckInputPlaceholder')
740 |
741 | ground_truth_input = tf.placeholder(tf.float32,
742 | [None, class_count],
743 | name='GroundTruthInput')
744 |
745 | # Organizing the following ops as `final_training_ops` so they're easier
746 | # to see in TensorBoard
747 | layer_name = 'final_training_ops'
748 | with tf.name_scope(layer_name):
749 | with tf.name_scope('weights'):
750 | initial_value = tf.truncated_normal([BOTTLENECK_TENSOR_SIZE, class_count],
751 | stddev=0.001)
752 |
753 | layer_weights = tf.Variable(initial_value, name='final_weights')
754 |
755 | variable_summaries(layer_weights)
756 | with tf.name_scope('biases'):
757 | layer_biases = tf.Variable(tf.zeros([class_count]), name='final_biases')
758 | variable_summaries(layer_biases)
759 | with tf.name_scope('Wx_plus_b'):
760 | logits = tf.matmul(bottleneck_input, layer_weights) + layer_biases
761 | tf.summary.histogram('pre_activations', logits)
762 |
763 | final_tensor = tf.nn.softmax(logits, name=final_tensor_name)
764 | tf.summary.histogram('activations', final_tensor)
765 |
766 | with tf.name_scope('cross_entropy'):
767 | cross_entropy = tf.nn.softmax_cross_entropy_with_logits(
768 | labels=ground_truth_input, logits=logits)
769 | with tf.name_scope('total'):
770 | cross_entropy_mean = tf.reduce_mean(cross_entropy)
771 | tf.summary.scalar('cross_entropy', cross_entropy_mean)
772 |
773 | with tf.name_scope('train'):
774 | optimizer = tf.train.GradientDescentOptimizer(FLAGS.learning_rate)
775 | train_step = optimizer.minimize(cross_entropy_mean)
776 |
777 | return (train_step, cross_entropy_mean, bottleneck_input, ground_truth_input,
778 | final_tensor)
779 |
780 |
781 | def add_evaluation_step(result_tensor, ground_truth_tensor):
782 | """Inserts the operations we need to evaluate the accuracy of our results.
783 |
784 | Args:
785 | result_tensor: The new final node that produces results.
786 | ground_truth_tensor: The node we feed ground truth data
787 | into.
788 |
789 | Returns:
790 | Tuple of (evaluation step, prediction).
791 | """
792 | with tf.name_scope('accuracy'):
793 | with tf.name_scope('correct_prediction'):
794 | prediction = tf.argmax(result_tensor, 1)
795 | correct_prediction = tf.equal(
796 | prediction, tf.argmax(ground_truth_tensor, 1))
797 | with tf.name_scope('accuracy'):
798 | evaluation_step = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
799 | tf.summary.scalar('accuracy', evaluation_step)
800 | return evaluation_step, prediction
801 |
802 |
803 | def main(_):
804 | # Setup the directory we'll write summaries to for TensorBoard
805 | if tf.gfile.Exists(FLAGS.summaries_dir):
806 | tf.gfile.DeleteRecursively(FLAGS.summaries_dir)
807 | tf.gfile.MakeDirs(FLAGS.summaries_dir)
808 |
809 | # Set up the pre-trained graph.
810 | maybe_download_and_extract()
811 | graph, bottleneck_tensor, jpeg_data_tensor, resized_image_tensor = (
812 | create_inception_graph())
813 |
814 | # Look at the folder structure, and create lists of all the images.
815 | image_lists = create_image_lists(FLAGS.image_dir, FLAGS.testing_percentage,
816 | FLAGS.validation_percentage)
817 | class_count = len(image_lists.keys())
818 | if class_count == 0:
819 | print('No valid folders of images found at ' + FLAGS.image_dir)
820 | return -1
821 | if class_count == 1:
822 | print('Only one valid folder of images found at ' + FLAGS.image_dir +
823 | ' - multiple classes are needed for classification.')
824 | return -1
825 |
826 | # See if the command-line flags mean we're applying any distortions.
827 | do_distort_images = should_distort_images(
828 | FLAGS.flip_left_right, FLAGS.random_crop, FLAGS.random_scale,
829 | FLAGS.random_brightness)
830 |
831 | with tf.Session(graph=graph) as sess:
832 |
833 | if do_distort_images:
834 | # We will be applying distortions, so setup the operations we'll need.
835 | (distorted_jpeg_data_tensor,
836 | distorted_image_tensor) = add_input_distortions(
837 | FLAGS.flip_left_right, FLAGS.random_crop,
838 | FLAGS.random_scale, FLAGS.random_brightness)
839 | else:
840 | # We'll make sure we've calculated the 'bottleneck' image summaries and
841 | # cached them on disk.
842 | cache_bottlenecks(sess, image_lists, FLAGS.image_dir,
843 | FLAGS.bottleneck_dir, jpeg_data_tensor,
844 | bottleneck_tensor)
845 |
846 | # Add the new layer that we'll be training.
847 | (train_step, cross_entropy, bottleneck_input, ground_truth_input,
848 | final_tensor) = add_final_training_ops(len(image_lists.keys()),
849 | FLAGS.final_tensor_name,
850 | bottleneck_tensor)
851 |
852 | # Create the operations we need to evaluate the accuracy of our new layer.
853 | evaluation_step, prediction = add_evaluation_step(
854 | final_tensor, ground_truth_input)
855 |
856 | # Merge all the summaries and write them out to the summaries_dir
857 | merged = tf.summary.merge_all()
858 | train_writer = tf.summary.FileWriter(FLAGS.summaries_dir + '/train',
859 | sess.graph)
860 |
861 | validation_writer = tf.summary.FileWriter(
862 | FLAGS.summaries_dir + '/validation')
863 |
864 | # Set up all our weights to their initial default values.
865 | init = tf.global_variables_initializer()
866 | sess.run(init)
867 |
868 | # Run the training for as many cycles as requested on the command line.
869 | for i in range(FLAGS.how_many_training_steps):
870 | # Get a batch of input bottleneck values, either calculated fresh every
871 | # time with distortions applied, or from the cache stored on disk.
872 | if do_distort_images:
873 | (train_bottlenecks,
874 | train_ground_truth) = get_random_distorted_bottlenecks(
875 | sess, image_lists, FLAGS.train_batch_size, 'training',
876 | FLAGS.image_dir, distorted_jpeg_data_tensor,
877 | distorted_image_tensor, resized_image_tensor, bottleneck_tensor)
878 | else:
879 | (train_bottlenecks,
880 | train_ground_truth, _) = get_random_cached_bottlenecks(
881 | sess, image_lists, FLAGS.train_batch_size, 'training',
882 | FLAGS.bottleneck_dir, FLAGS.image_dir, jpeg_data_tensor,
883 | bottleneck_tensor)
884 | # Feed the bottlenecks and ground truth into the graph, and run a training
885 | # step. Capture training summaries for TensorBoard with the `merged` op.
886 |
887 | train_summary, _ = sess.run(
888 | [merged, train_step],
889 | feed_dict={bottleneck_input: train_bottlenecks,
890 | ground_truth_input: train_ground_truth})
891 | train_writer.add_summary(train_summary, i)
892 |
893 | # !! ausgabe der trainingsgenaugikeit *gaendert
894 | is_last_step = (i + 1 == FLAGS.how_many_training_steps)
895 | if (i % FLAGS.eval_step_interval) == 0 or is_last_step:
896 | train_accuracy, cross_entropy_value = sess.run(
897 | [evaluation_step, cross_entropy],
898 | feed_dict={bottleneck_input: train_bottlenecks,
899 | ground_truth_input: train_ground_truth})
900 | print('%s: Step %d: Train accuracy = %.1f%%' % (datetime.now(), i,
901 | train_accuracy * 100))
902 | print('%s: Step %d: Cross entropy = %f' % (datetime.now(), i,
903 | cross_entropy_value))
904 | validation_bottlenecks, validation_ground_truth, _ = (
905 | get_random_cached_bottlenecks(
906 | sess, image_lists, FLAGS.validation_batch_size, 'validation',
907 | FLAGS.bottleneck_dir, FLAGS.image_dir, jpeg_data_tensor,
908 | bottleneck_tensor))
909 | # Run a validation step and capture training summaries for TensorBoard
910 | # with the `merged` op.
911 | validation_summary, validation_accuracy = sess.run(
912 | [merged, evaluation_step],
913 | feed_dict={bottleneck_input: validation_bottlenecks,
914 | ground_truth_input: validation_ground_truth})
915 | validation_writer.add_summary(validation_summary, i)
916 | print('%s: Step %d: Validation accuracy = %.1f%% (N=%d)' %
917 | (datetime.now(), i, validation_accuracy * 100,
918 | len(validation_bottlenecks)))
919 |
920 | # We've completed all our training, so run a final test evaluation on
921 | # some new images we haven't used before.
922 | test_bottlenecks, test_ground_truth, test_filenames = (
923 | get_random_cached_bottlenecks(sess, image_lists, FLAGS.test_batch_size,
924 | 'testing', FLAGS.bottleneck_dir,
925 | FLAGS.image_dir, jpeg_data_tensor,
926 | bottleneck_tensor))
927 | test_accuracy, predictions = sess.run(
928 | [evaluation_step, prediction],
929 | feed_dict={bottleneck_input: test_bottlenecks,
930 | ground_truth_input: test_ground_truth})
931 | print('Final test accuracy = %.1f%% (N=%d)' % (
932 | test_accuracy * 100, len(test_bottlenecks)))
933 |
934 | if FLAGS.print_misclassified_test_images:
935 | print('=== MISCLASSIFIED TEST IMAGES ===')
936 | for i, test_filename in enumerate(test_filenames):
937 | if predictions[i] != test_ground_truth[i].argmax():
938 | print('%70s %s' % (test_filename,
939 | list(image_lists.keys())[predictions[i]]))
940 |
941 | # Write out the trained graph and labels with the weights stored as
942 | # constants.
943 | output_graph_def = graph_util.convert_variables_to_constants(
944 | sess, graph.as_graph_def(), [FLAGS.final_tensor_name])
945 | with gfile.FastGFile(FLAGS.output_graph, 'wb') as f:
946 | f.write(output_graph_def.SerializeToString())
947 | with gfile.FastGFile(FLAGS.output_labels, 'w') as f:
948 | f.write('\n'.join(image_lists.keys()) + '\n')
949 |
950 |
951 | if __name__ == '__main__':
952 | parser = argparse.ArgumentParser()
953 | parser.add_argument(
954 | '--image_dir', # definiert in train.sh
955 | type=str,
956 | default='',
957 | help='Path to folders of labeled images.'
958 | )
959 | parser.add_argument(
960 | '--output_graph',
961 | type=str,
962 | default='/tmp/output_graph.pb',
963 | help='Where to save the trained graph.'
964 | )
965 | parser.add_argument(
966 | '--output_labels',
967 | type=str,
968 | default='/tmp/output_labels.txt',
969 | help='Where to save the trained graph\'s labels.'
970 | )
971 | parser.add_argument(
972 | '--summaries_dir',
973 | type=str,
974 | default='/tmp/retrain_logs',
975 | help='Where to save summary logs for TensorBoard.'
976 | )
977 | parser.add_argument(
978 | '--how_many_training_steps',
979 | type=int,
980 | default=4000,
981 | help='How many training steps to run before ending.'
982 | )
983 | parser.add_argument(
984 | '--learning_rate',
985 | type=float,
986 | default=0.01,
987 | help='How large a learning rate to use when training.'
988 | )
989 | parser.add_argument(
990 | '--testing_percentage',
991 | type=int,
992 | default=10,
993 | help='What percentage of images to use as a test set.'
994 | )
995 | parser.add_argument(
996 | '--validation_percentage',
997 | type=int,
998 | default=10,
999 | help='What percentage of images to use as a validation set.'
1000 | )
1001 | parser.add_argument(
1002 | '--eval_step_interval',
1003 | type=int,
1004 | default=10,
1005 | help='How often to evaluate the training results.'
1006 | )
1007 | #geadded in shell befehl *geaendert
1008 | parser.add_argument(
1009 | '--train_batch_size',
1010 | type=int,
1011 | default=100,
1012 | help='How many images to train on at a time.'
1013 | )
1014 | parser.add_argument(
1015 | '--test_batch_size',
1016 | type=int,
1017 | default=-1,
1018 | help="""\
1019 | How many images to test on. This test set is only used once, to evaluate
1020 | the final accuracy of the model after training completes.
1021 | A value of -1 causes the entire test set to be used, which leads to more
1022 | stable results across runs.\
1023 | """
1024 | )
1025 | parser.add_argument(
1026 | '--validation_batch_size',
1027 | type=int,
1028 | default=100,
1029 | help="""\
1030 | How many images to use in an evaluation batch. This validation set is
1031 | used much more often than the test set, and is an early indicator of how
1032 | accurate the model is during training.
1033 | A value of -1 causes the entire validation set to be used, which leads to
1034 | more stable results across training iterations, but may be slower on large
1035 | training sets.\
1036 | """
1037 | )
1038 | parser.add_argument(
1039 | '--print_misclassified_test_images',
1040 | default=False,
1041 | help="""\
1042 | Whether to print out a list of all misclassified test images.\
1043 | """,
1044 | action='store_true'
1045 | )
1046 | parser.add_argument(
1047 | '--model_dir',
1048 | type=str,
1049 | default='/tmp/imagenet',
1050 | help="""\
1051 | Path to classify_image_graph_def.pb,
1052 | imagenet_synset_to_human_label_map.txt, and
1053 | imagenet_2012_challenge_label_map_proto.pbtxt.\
1054 | """
1055 | )
1056 | parser.add_argument(
1057 | '--bottleneck_dir',
1058 | type=str,
1059 | default='/tmp/bottleneck',
1060 | help='Path to cache bottleneck layer values as files.'
1061 | )
1062 | parser.add_argument(
1063 | '--final_tensor_name',
1064 | type=str,
1065 | default='final_result',
1066 | help="""\
1067 | The name of the output classification layer in the retrained graph.\
1068 | """
1069 | )
1070 | parser.add_argument(
1071 | '--flip_left_right',
1072 | default=False,
1073 | help="""\
1074 | Whether to randomly flip half of the training images horizontally.\
1075 | """,
1076 | action='store_true'
1077 | )
1078 | parser.add_argument(
1079 | '--random_crop',
1080 | type=int,
1081 | default=0,
1082 | help="""\
1083 | A percentage determining how much of a margin to randomly crop off the
1084 | training images.\
1085 | """
1086 | )
1087 | parser.add_argument(
1088 | '--random_scale',
1089 | type=int,
1090 | default=0,
1091 | help="""\
1092 | A percentage determining how much to randomly scale up the size of the
1093 | training images by.\
1094 | """
1095 | )
1096 | parser.add_argument(
1097 | '--random_brightness',
1098 | type=int,
1099 | default=0,
1100 | help="""\
1101 | A percentage determining how much to randomly multiply the training image
1102 | input pixels up or down by.\
1103 | """
1104 | )
1105 | FLAGS, unparsed = parser.parse_known_args()
1106 | tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)
--------------------------------------------------------------------------------
/run_train_and_classify_befehl.txt:
--------------------------------------------------------------------------------
1 | python retrain.py --bottleneck_dir=tf_files/bottlenecks --how_many_training_steps=500 --model_dir=inception --summaries_dir=tf_files/training_summaries/basic --output_graph=tf_files/retrained_graph.pb --output_labels=tf_files/retrained_labels.txt --image_dir=training_dataset --eval_step_interval=100 & python classify.py dreieck.jpg
--------------------------------------------------------------------------------
/train.sh:
--------------------------------------------------------------------------------
1 | python retrain.py \
2 | --bottleneck_dir=tf_files/bottlenecks \
3 | --how_many_training_steps=500 \
4 | --model_dir=inception \
5 | --summaries_dir=tf_files/training_summaries/basic \
6 | --output_graph=tf_files/retrained_graph.pb \
7 | --output_labels=tf_files/retrained_labels.txt \
8 | --image_dir=training_dataset
--------------------------------------------------------------------------------