├── 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 --------------------------------------------------------------------------------