#! -*- coding: utf-8 -*-

#---------------------------------
# モジュールのインポート
#---------------------------------
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.preprocessing.image import ImageDataGenerator

#---------------------------------
# クラス; 学習モジュール基底クラス
#---------------------------------
class Trainer():
	# --- コンストラクタ ---
	def __init__(self, output_dir=None, model_file=None, optimizer='adam'):
		# --- 出力ディレクトリ作成 ---
		self.output_dir = output_dir
		if (output_dir is not None):
			os.makedirs(output_dir, exist_ok=True)
		
		# --- モデル構築 ---
		def _load_model(model_file):
			if (model_file is not None):
				return None
			else:
				return None
		
		self.model = _load_model(model_file)
		if (self.model is not None):
			self._compile_model(optimizer)
		return
	
	# --- モデルの構成 ---
	#   * lr_decay: 学習率減衰する(=True)，しない(=False; default)を指定
	def _compile_model(self, optimizer='adam'):
		
		if (optimizer == 'adam'):
			opt = tf.keras.optimizers.Adam()
		elif (optimizer == 'sgd'):
			opt = tf.keras.optimizers.SGD()
		elif (optimizer == 'adam_lrs'):
			# --- parameters ---
			#  https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/schedules/ExponentialDecay
			#    but, initial learning rate is default of Adam()
			lr_schedule = tf.keras.optimizers.schedules.ExponentialDecay(
				0.001,
				decay_steps=1000,
				decay_rate=0.90,
				staircase=True)
			opt = tf.keras.optimizers.Adam(learning_rate=lr_schedule)
		elif (optimizer == 'sgd_lrs'):
			# --- parameters ---
			#  https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/schedules/ExponentialDecay
			#    but, initial learning rate is default of Adam()
			lr_schedule = tf.keras.optimizers.schedules.ExponentialDecay(
				0.01,
				decay_steps=1000,
				decay_rate=0.9,
				staircase=True)
			opt = tf.keras.optimizers.SGD(learning_rate=lr_schedule)
		else:
			print('[ERROR] Unknown optimizer: {}'.format(optimizer))
			quit()
		
		self.model.compile(
			optimizer=opt,
			loss = 'sparse_categorical_crossentropy',
			metrics=['accuracy'])
		
		return
	
	# --- 学習 ---
	def fit(self, x_train, y_train, x_val=None, y_val=None, x_test=None, y_test=None,
			da_enable=False,
			batch_size=32, epochs=1000000):
		# --- 学習 ---
		os.makedirs(os.path.join(self.output_dir, 'checkpoints'), exist_ok=True)
		checkpoint_path = os.path.join(self.output_dir, 'checkpoints', 'model.ckpt')
		cp_callback = keras.callbacks.ModelCheckpoint(checkpoint_path, save_weights_only=True, verbose=1)
		es_callback = keras.callbacks.EarlyStopping(monitor='val_loss', min_delta=0, patience=5, verbose=1, mode='auto')
		
		if (da_enable):
			# --- no tuning ---
			datagen = ImageDataGenerator(
				rotation_range=10,
				width_shift_range=0.2,
				height_shift_range=0.2,
				horizontal_flip=True)
		else:
			datagen = ImageDataGenerator()
		datagen.fit(x_train)
		
		if ((x_val is not None) and (y_val is not None)):
			history = self.model.fit(datagen.flow(x_train, y_train, batch_size=batch_size),
						steps_per_epoch=len(x_train)/batch_size, validation_data=(x_val, y_val),
						epochs=epochs, callbacks=[cp_callback, es_callback])
		else:
			history = self.model.fit(datagen.flow(x_train, y_train, batch_size=batch_size),
						steps_per_epoch=len(x_train)/batch_size, validation_split=0.2,
						epochs=epochs, callbacks=[cp_callback, es_callback])
		
		# --- 学習結果を評価 ---
		if ((x_test is not None) and (y_test is not None)):
			test_loss, test_acc = self.model.evaluate(x_test, y_test, verbose=2)
			print('Test Accuracy: {}'.format(test_acc))
			print('Test Loss: {}'.format(test_loss))
		
		# --- メトリクスを保存 ---
		metrics = history.history
		os.makedirs(os.path.join(self.output_dir, 'metrics'), exist_ok=True)
		df_metrics = pd.DataFrame(metrics)
		df_metrics.to_csv(os.path.join(self.output_dir, 'metrics', 'metrics.csv'), index_label='epoch')
		
		epoch = df_metrics.index.values
		for column in df_metrics.columns:
			plt.figure()
			plt.plot(epoch, df_metrics[column])
			plt.xlabel(column)
			plt.ylabel('epoch')
			plt.grid(True)
			plt.tight_layout()
			
			graph_name = os.path.join(self.output_dir, 'metrics', '{}.png'.format(column))
			plt.savefig(graph_name)
			
			plt.close()
		
		return
	
	# --- 推論 ---
	def predict(self, x_test):
		predictions = self.model.predict(x_test)
		return predictions
		
	# --- モデル保存 ---
	def save_model(self):
		# --- 保存先ディレクトリ作成 ---
		model_dir = os.path.join(self.output_dir, 'models')
		os.makedirs(os.path.join(model_dir, 'checkpoint'), exist_ok=True)
		os.makedirs(os.path.join(model_dir, 'saved_model'), exist_ok=True)
		os.makedirs(os.path.join(model_dir, 'hdf5'), exist_ok=True)
		
		# --- checkpoint ---
		self.model.save_weights(os.path.join(model_dir, 'checkpoint', 'model.ckpt'))
		
		# --- saved_model ---
		self.model.save(os.path.join(model_dir, 'saved_model'))
		
		# --- hdf5 ---
		self.model.save(os.path.join(model_dir, 'hdf5', 'model.h5'))
		
		return
		
	# --- ラベルインデックス取得 ---
	def GetLabelIndex(self, label, onehot=True):
		if (onehot):
			label = np.argmax(label, axis=1)
		n_category = max(label)+1
		
		return np.array([np.arange(len(label))[label==i] for i in range(n_category)])

#---------------------------------
# クラス; ResNet学習モジュール
#---------------------------------
class TrainerResNet(Trainer):
	# --- コンストラクタ ---
	def __init__(self, input_shape, classes, output_dir=None, model_type='custom', optimizer='adam'):
		# --- Residual Block ---
		#  * アプリケーションからkeras.applications.resnet.ResNetにアクセスできない為，
		#    必要なモジュールをTensorFlow公式からコピー
		#      https://github.com/tensorflow/tensorflow/blob/v2.5.0/tensorflow/python/keras/applications/resnet.py#L212
		def block1(x, filters, kernel_size=3, stride=1, conv_shortcut=True, name=None):
			bn_axis = 3
			
			if conv_shortcut:
				shortcut = keras.layers.Conv2D(4 * filters, 1, strides=stride, name=name + '_0_conv')(x)
				shortcut = keras.layers.BatchNormalization(axis=bn_axis, epsilon=1.001e-5, name=name + '_0_bn')(shortcut)
			else:
				shortcut = x

			x = keras.layers.Conv2D(filters, 1, strides=stride, name=name + '_1_conv')(x)
			x = keras.layers.BatchNormalization(axis=bn_axis, epsilon=1.001e-5, name=name + '_1_bn')(x)
			x = keras.layers.Activation('relu', name=name + '_1_relu')(x)

			x = keras.layers.Conv2D(filters, kernel_size, padding='SAME', name=name + '_2_conv')(x)
			x = keras.layers.BatchNormalization(axis=bn_axis, epsilon=1.001e-5, name=name + '_2_bn')(x)
			x = keras.layers.Activation('relu', name=name + '_2_relu')(x)

			x = keras.layers.Conv2D(4 * filters, 1, name=name + '_3_conv')(x)
			x = keras.layers.BatchNormalization(axis=bn_axis, epsilon=1.001e-5, name=name + '_3_bn')(x)

			x = keras.layers.Add(name=name + '_add')([shortcut, x])
			x = keras.layers.Activation('relu', name=name + '_out')(x)
			return x
		
		# --- Residual Block stack ---
		#  * アプリケーションからkeras.applications.resnet.ResNetにアクセスできない為，
		#    必要なモジュールをTensorFlow公式からコピー
		#      https://github.com/tensorflow/tensorflow/blob/v2.5.0/tensorflow/python/keras/applications/resnet.py#L257
		def stack1(x, filters, blocks, stride1=2, name=None):
			x = block1(x, filters, stride=stride1, name=name + '_block1')
			for i in range(2, blocks + 1):
				x = block1(x, filters, conv_shortcut=False, name=name + '_block' + str(i))
			return x
		
		# --- モデル構築 ---
		#  * stack_fn()の関数ポインタを引数に設定してカスタマイズ
		def _load_model(input_shape, classes, stack_fn):
			input = keras.layers.Input(shape=input_shape)
			bn_axis = 3
			
			x = keras.layers.ZeroPadding2D(padding=((3, 3), (3, 3)), name='conv1_pad')(input)
			x = keras.layers.Conv2D(64, 7, strides=2, use_bias=True, name='conv1_conv')(x)

			x = keras.layers.BatchNormalization(axis=bn_axis, epsilon=1.001e-5, name='conv1_bn')(x)
			x = keras.layers.Activation('relu', name='conv1_relu')(x)

			x = keras.layers.ZeroPadding2D(padding=((1, 1), (1, 1)), name='pool1_pad')(x)
			x = keras.layers.MaxPooling2D(3, strides=2, name='pool1_pool')(x)

			x = stack_fn(x)

			x = keras.layers.GlobalAveragePooling2D(name='avg_pool')(x)
			x = keras.layers.Dense(classes, activation='softmax', name='predictions')(x)
			
			model = keras.models.Model(input, x)
			model.summary()
			
			return model
			
		def _load_model_resnet50(input_shape, classes, dbg_mode=1):
			# --- TensorFlowのResNet50のモデル ---
			#  https://www.tensorflow.org/api_docs/python/tf/keras/applications/resnet50/ResNet50
			#    dbg_mode=0: original ResNet50, dbg_mode=11: custom ResNet50
			if (dbg_mode == 0):
				print('[INFO] Load ResNet50 model from keras.applications')
				model = keras.applications.resnet50.ResNet50()
			elif (dbg_mode == 1):
				def stack_fn(x):
					x = stack1(x, 64, 3, stride1=1, name='conv2')
					x = stack1(x, 128, 4, name='conv3')
					x = stack1(x, 256, 6, name='conv4')
					return stack1(x, 512, 3, name='conv5')
				
				print('[INFO] Load ResNet50 model (custom implementation)')
				model = _load_model(input_shape, classes, stack_fn)
				
			return model
		
		# --- 基底クラスの初期化 ---
		super().__init__(output_dir)
		
		# --- モデル構築 ---
		if (model_type == 'custom'):
			def stack_fn(x):
				x = stack1(x, 32, 3, stride1=1, name='conv2')
				return stack1(x, 64, 4, name='conv3')
			
			self.model = _load_model(input_shape, classes, stack_fn)
			self._compile_model(optimizer=optimizer)
		elif (model_type == 'resnet50'):
			self.model = _load_model_resnet50(input_shape, classes, dbg_mode=1)
			self._compile_model(optimizer=optimizer)
		else:
			print('[ERROR] Unknown model_type: {}'.format(model_type))
			return
			
		if (self.output_dir is not None):
			keras.utils.plot_model(self.model, os.path.join(self.output_dir, 'plot_model.png'), show_shapes=True)
		
		return
	
#---------------------------------
# クラス; CNN学習モジュール
#---------------------------------
class TrainerCNN(Trainer):
	# --- コンストラクタ ---
	def __init__(self, input_shape, output_dir=None, optimizer='adam'):
		# --- モデル構築 ---
		def _load_model(input_shape):
			model = keras.models.Sequential()
			model.add(keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=input_shape))
			model.add(keras.layers.MaxPooling2D((2, 2)))
			model.add(keras.layers.Conv2D(64, (3, 3), activation='relu'))
			model.add(keras.layers.MaxPooling2D((2, 2)))
			model.add(keras.layers.Conv2D(64, (3, 3), activation='relu'))
			model.add(keras.layers.MaxPooling2D((2, 2)))
			model.add(keras.layers.Flatten(input_shape=input_shape))
			model.add(keras.layers.Dense(64, activation='relu'))
			model.add(keras.layers.Dense(10, activation='softmax'))
			
			model.summary()
			
			return model
		
		# --- 基底クラスの初期化 ---
		super().__init__(output_dir)
		
		# --- モデル構築 ---
		self.model = _load_model(input_shape)
		self._compile_model(optimizer=optimizer)
		if (self.output_dir is not None):
			keras.utils.plot_model(self.model, os.path.join(self.output_dir, 'plot_model.png'), show_shapes=True)
		
		return
	

#---------------------------------
# クラス; MLP学習モジュール
#---------------------------------
class TrainerMLP(Trainer):
	# --- コンストラクタ ---
	def __init__(self, input_shape, output_dir=None, optimizer='adam'):
		# --- モデル構築 ---
		def _load_model(input_shape):
			model = keras.models.Sequential()
			model.add(keras.layers.Flatten(input_shape=input_shape))
			model.add(keras.layers.Dense(128, activation='relu'))
			model.add(keras.layers.Dense(10, activation='softmax'))
			
			model.summary()
			
			return model
		
		# --- 基底クラスの初期化 ---
		super().__init__(output_dir)
		
		# --- モデル構築 ---
		self.model = _load_model(input_shape)
		self._compile_model(optimizer=optimizer)
		if (self.output_dir is not None):
			keras.utils.plot_model(self.model, os.path.join(self.output_dir, 'plot_model.png'), show_shapes=True)
		
		return
	


#---------------------------------
# メイン処理; Trainerモジュールテスト
#---------------------------------
def main():
	import argparse
	def _argparse():
		parser = argparse.ArgumentParser(description='Trainerモジュールテスト\n'
					'  * test_mode=\'ResNet\': ResNetのモデル構造確認(ResNet50の構造をTensorFlow公開モデルと比較)',
					formatter_class=argparse.RawTextHelpFormatter)

		# --- 引数を追加 ---
		parser.add_argument('--test_mode', dest='test_mode', type=str, default='ResNet', required=False, \
				help='テストモード(ResNet)')

		args = parser.parse_args()
		return args

	# --- 引数処理 ---
	args = _argparse()
	print(args.test_mode)
	
	# --- モジュールテスト ---
	if (args.test_mode == 'ResNet'):
		trainer = TrainerResNet([224, 224, 3], 1000, output_dir=None, model_type='resnet50')
	else:
		print('[ERROR] Unknown test_mode: {}'.format(args.test_mode))
	
	return

	
if __name__ == '__main__':
	main()

pandas.DataFrame matplotlib.pyplot.plot tensorflow.keras.layers.ZeroPadding2D tensorflow.keras.optimizers.SGD tensorflow.keras.applications.resnet50.ResNet50 matplotlib.pyplot.tight_layout tensorflow.keras.preprocessing.image.ImageDataGenerator tensorflow.keras.layers.Conv2D numpy.argmax matplotlib.pyplot.close tensorflow.keras.layers.Add tensorflow.keras.callbacks.EarlyStopping tensorflow.keras.models.Sequential tensorflow.keras.layers.Flatten matplotlib.pyplot.figure tensorflow.keras.callbacks.ModelCheckpoint tensorflow.keras.models.Model tensorflow.keras.layers.Dense matplotlib.pyplot.savefig matplotlib.pyplot.ylabel tensorflow.keras.layers.Activation tensorflow.keras.layers.GlobalAveragePooling2D tensorflow.keras.optimizers.schedules.ExponentialDecay tensorflow.keras.optimizers.Adam tensorflow.keras.layers.BatchNormalization matplotlib.pyplot.grid matplotlib.pyplot.xlabel tensorflow.keras.layers.MaxPooling2D tensorflow.keras.layers.Input
[(83, 'os.path.join', 'os.path.join', (['self.output_dir', '"""checkpoints"""', '"""model.ckpt"""'], {}), False, 'import os\n'), (84, 'tensorflow.keras.callbacks.ModelCheckpoint', 'keras.callbacks.ModelCheckpoint', (['checkpoint_path'], {'save_weights_only': '(True)', 'verbose': '(1)'}), False, 'from tensorflow import keras\n'), (85, 'tensorflow.keras.callbacks.EarlyStopping', 'keras.callbacks.EarlyStopping', ([], {'monitor': '"""val_loss"""', 'min_delta': '(0)', 'patience': '(5)', 'verbose': '(1)', 'mode': '"""auto"""'}), False, 'from tensorflow import keras\n'), (116, 'pandas.DataFrame', 'pd.DataFrame', (['metrics'], {}), True, 'import pandas as pd\n'), (143, 'os.path.join', 'os.path.join', (['self.output_dir', '"""models"""'], {}), False, 'import os\n'), (349, 'argparse.ArgumentParser', 'argparse.ArgumentParser', ([], {'description': '"""Trainerモジュールテスト\n  * test_mode=\'ResNet\': ResNetのモデル構造確認(ResNet50の構造をTensorFlow公開モデルと比較)"""', 'formatter_class': 'argparse.RawTextHelpFormatter'}), False, 'import argparse\n'), (24, 'os.makedirs', 'os.makedirs', (['output_dir'], {'exist_ok': '(True)'}), False, 'import os\n'), (43, 'tensorflow.keras.optimizers.Adam', 'tf.keras.optimizers.Adam', ([], {}), True, 'import tensorflow as tf\n'), (82, 'os.path.join', 'os.path.join', (['self.output_dir', '"""checkpoints"""'], {}), False, 'import os\n'), (89, 'tensorflow.keras.preprocessing.image.ImageDataGenerator', 'ImageDataGenerator', ([], {'rotation_range': '(10)', 'width_shift_range': '(0.2)', 'height_shift_range': '(0.2)', 'horizontal_flip': '(True)'}), False, 'from tensorflow.keras.preprocessing.image import ImageDataGenerator\n'), (95, 'tensorflow.keras.preprocessing.image.ImageDataGenerator', 'ImageDataGenerator', ([], {}), False, 'from tensorflow.keras.preprocessing.image import ImageDataGenerator\n'), (115, 'os.path.join', 'os.path.join', (['self.output_dir', '"""metrics"""'], {}), False, 'import os\n'), (117, 'os.path.join', 'os.path.join', (['self.output_dir', '"""metrics"""', '"""metrics.csv"""'], {}), False, 'import os\n'), (121, 'matplotlib.pyplot.figure', 'plt.figure', ([], {}), True, 'import matplotlib.pyplot as plt\n'), (122, 'matplotlib.pyplot.plot', 'plt.plot', (['epoch', 'df_metrics[column]'], {}), True, 'import matplotlib.pyplot as plt\n'), (123, 'matplotlib.pyplot.xlabel', 'plt.xlabel', (['column'], {}), True, 'import matplotlib.pyplot as plt\n'), (124, 'matplotlib.pyplot.ylabel', 'plt.ylabel', (['"""epoch"""'], {}), True, 'import matplotlib.pyplot as plt\n'), (125, 'matplotlib.pyplot.grid', 'plt.grid', (['(True)'], {}), True, 'import matplotlib.pyplot as plt\n'), (126, 'matplotlib.pyplot.tight_layout', 'plt.tight_layout', ([], {}), True, 'import matplotlib.pyplot as plt\n'), (129, 'matplotlib.pyplot.savefig', 'plt.savefig', (['graph_name'], {}), True, 'import matplotlib.pyplot as plt\n'), (131, 'matplotlib.pyplot.close', 'plt.close', ([], {}), True, 'import matplotlib.pyplot as plt\n'), (144, 'os.path.join', 'os.path.join', (['model_dir', '"""checkpoint"""'], {}), False, 'import os\n'), (145, 'os.path.join', 'os.path.join', (['model_dir', '"""saved_model"""'], {}), False, 'import os\n'), (146, 'os.path.join', 'os.path.join', (['model_dir', '"""hdf5"""'], {}), False, 'import os\n'), (149, 'os.path.join', 'os.path.join', (['model_dir', '"""checkpoint"""', '"""model.ckpt"""'], {}), False, 'import os\n'), (152, 'os.path.join', 'os.path.join', (['model_dir', '"""saved_model"""'], {}), False, 'import os\n'), (155, 'os.path.join', 'os.path.join', (['model_dir', '"""hdf5"""', '"""model.h5"""'], {}), False, 'import os\n'), (162, 'numpy.argmax', 'np.argmax', (['label'], {'axis': '(1)'}), True, 'import numpy as np\n'), (214, 'tensorflow.keras.layers.Input', 'keras.layers.Input', ([], {'shape': 'input_shape'}), False, 'from tensorflow import keras\n'), (231, 'tensorflow.keras.models.Model', 'keras.models.Model', (['input', 'x'], {}), False, 'from tensorflow import keras\n'), (286, 'tensorflow.keras.models.Sequential', 'keras.models.Sequential', ([], {}), False, 'from tensorflow import keras\n'), (321, 'tensorflow.keras.models.Sequential', 'keras.models.Sequential', ([], {}), False, 'from tensorflow import keras\n'), (45, 'tensorflow.keras.optimizers.SGD', 'tf.keras.optimizers.SGD', ([], {}), True, 'import tensorflow as tf\n'), (186, 'tensorflow.keras.layers.Conv2D', 'keras.layers.Conv2D', (['filters', '(1)'], {'strides': 'stride', 'name': "(name + '_1_conv')"}), False, 'from tensorflow import keras\n'), (187, 'tensorflow.keras.layers.BatchNormalization', 'keras.layers.BatchNormalization', ([], {'axis': 'bn_axis', 'epsilon': '(1.001e-05)', 'name': "(name + '_1_bn')"}), False, 'from tensorflow import keras\n'), (188, 'tensorflow.keras.layers.Activation', 'keras.layers.Activation', (['"""relu"""'], {'name': "(name + '_1_relu')"}), False, 'from tensorflow import keras\n'), (190, 'tensorflow.keras.layers.Conv2D', 'keras.layers.Conv2D', (['filters', 'kernel_size'], {'padding': '"""SAME"""', 'name': "(name + '_2_conv')"}), False, 'from tensorflow import keras\n'), (191, 'tensorflow.keras.layers.BatchNormalization', 'keras.layers.BatchNormalization', ([], {'axis': 'bn_axis', 'epsilon': '(1.001e-05)', 'name': "(name + '_2_bn')"}), False, 'from tensorflow import keras\n'), (192, 'tensorflow.keras.layers.Activation', 'keras.layers.Activation', (['"""relu"""'], {'name': "(name + '_2_relu')"}), False, 'from tensorflow import keras\n'), (194, 'tensorflow.keras.layers.Conv2D', 'keras.layers.Conv2D', (['(4 * filters)', '(1)'], {'name': "(name + '_3_conv')"}), False, 'from tensorflow import keras\n'), (195, 'tensorflow.keras.layers.BatchNormalization', 'keras.layers.BatchNormalization', ([], {'axis': 'bn_axis', 'epsilon': '(1.001e-05)', 'name': "(name + '_3_bn')"}), False, 'from tensorflow import keras\n'), (197, 'tensorflow.keras.layers.Add', 'keras.layers.Add', ([], {'name': "(name + '_add')"}), False, 'from tensorflow import keras\n'), (198, 'tensorflow.keras.layers.Activation', 'keras.layers.Activation', (['"""relu"""'], {'name': "(name + '_out')"}), False, 'from tensorflow import keras\n'), (217, 'tensorflow.keras.layers.ZeroPadding2D', 'keras.layers.ZeroPadding2D', ([], {'padding': '((3, 3), (3, 3))', 'name': '"""conv1_pad"""'}), False, 'from tensorflow import keras\n'), (218, 'tensorflow.keras.layers.Conv2D', 'keras.layers.Conv2D', (['(64)', '(7)'], {'strides': '(2)', 'use_bias': '(True)', 'name': '"""conv1_conv"""'}), False, 'from tensorflow import keras\n'), (220, 'tensorflow.keras.layers.BatchNormalization', 'keras.layers.BatchNormalization', ([], {'axis': 'bn_axis', 'epsilon': '(1.001e-05)', 'name': '"""conv1_bn"""'}), False, 'from tensorflow import keras\n'), (221, 'tensorflow.keras.layers.Activation', 'keras.layers.Activation', (['"""relu"""'], {'name': '"""conv1_relu"""'}), False, 'from tensorflow import keras\n'), (223, 'tensorflow.keras.layers.ZeroPadding2D', 'keras.layers.ZeroPadding2D', ([], {'padding': '((1, 1), (1, 1))', 'name': '"""pool1_pad"""'}), False, 'from tensorflow import keras\n'), (224, 'tensorflow.keras.layers.MaxPooling2D', 'keras.layers.MaxPooling2D', (['(3)'], {'strides': '(2)', 'name': '"""pool1_pool"""'}), False, 'from tensorflow import keras\n'), (228, 'tensorflow.keras.layers.GlobalAveragePooling2D', 'keras.layers.GlobalAveragePooling2D', ([], {'name': '"""avg_pool"""'}), False, 'from tensorflow import keras\n'), (229, 'tensorflow.keras.layers.Dense', 'keras.layers.Dense', (['classes'], {'activation': '"""softmax"""', 'name': '"""predictions"""'}), False, 'from tensorflow import keras\n'), (242, 'tensorflow.keras.applications.resnet50.ResNet50', 'keras.applications.resnet50.ResNet50', ([], {}), False, 'from tensorflow import keras\n'), (274, 'os.path.join', 'os.path.join', (['self.output_dir', '"""plot_model.png"""'], {}), False, 'import os\n'), (287, 'tensorflow.keras.layers.Conv2D', 'keras.layers.Conv2D', (['(32)', '(3, 3)'], {'activation': '"""relu"""', 'input_shape': 'input_shape'}), False, 'from tensorflow import keras\n'), (288, 'tensorflow.keras.layers.MaxPooling2D', 'keras.layers.MaxPooling2D', (['(2, 2)'], {}), False, 'from tensorflow import keras\n'), (289, 'tensorflow.keras.layers.Conv2D', 'keras.layers.Conv2D', (['(64)', '(3, 3)'], {'activation': '"""relu"""'}), False, 'from tensorflow import keras\n'), (290, 'tensorflow.keras.layers.MaxPooling2D', 'keras.layers.MaxPooling2D', (['(2, 2)'], {}), False, 'from tensorflow import keras\n'), (291, 'tensorflow.keras.layers.Conv2D', 'keras.layers.Conv2D', (['(64)', '(3, 3)'], {'activation': '"""relu"""'}), False, 'from tensorflow import keras\n'), (292, 'tensorflow.keras.layers.MaxPooling2D', 'keras.layers.MaxPooling2D', (['(2, 2)'], {}), False, 'from tensorflow import keras\n'), (293, 'tensorflow.keras.layers.Flatten', 'keras.layers.Flatten', ([], {'input_shape': 'input_shape'}), False, 'from tensorflow import keras\n'), (294, 'tensorflow.keras.layers.Dense', 'keras.layers.Dense', (['(64)'], {'activation': '"""relu"""'}), False, 'from tensorflow import keras\n'), (295, 'tensorflow.keras.layers.Dense', 'keras.layers.Dense', (['(10)'], {'activation': '"""softmax"""'}), False, 'from tensorflow import keras\n'), (308, 'os.path.join', 'os.path.join', (['self.output_dir', '"""plot_model.png"""'], {}), False, 'import os\n'), (322, 'tensorflow.keras.layers.Flatten', 'keras.layers.Flatten', ([], {'input_shape': 'input_shape'}), False, 'from tensorflow import keras\n'), (323, 'tensorflow.keras.layers.Dense', 'keras.layers.Dense', (['(128)'], {'activation': '"""relu"""'}), False, 'from tensorflow import keras\n'), (324, 'tensorflow.keras.layers.Dense', 'keras.layers.Dense', (['(10)'], {'activation': '"""softmax"""'}), False, 'from tensorflow import keras\n'), (337, 'os.path.join', 'os.path.join', (['self.output_dir', '"""plot_model.png"""'], {}), False, 'import os\n'), (50, 'tensorflow.keras.optimizers.schedules.ExponentialDecay', 'tf.keras.optimizers.schedules.ExponentialDecay', (['(0.001)'], {'decay_steps': '(1000)', 'decay_rate': '(0.9)', 'staircase': '(True)'}), True, 'import tensorflow as tf\n'), (55, 'tensorflow.keras.optimizers.Adam', 'tf.keras.optimizers.Adam', ([], {'learning_rate': 'lr_schedule'}), True, 'import tensorflow as tf\n'), (181, 'tensorflow.keras.layers.Conv2D', 'keras.layers.Conv2D', (['(4 * filters)', '(1)'], {'strides': 'stride', 'name': "(name + '_0_conv')"}), False, 'from tensorflow import keras\n'), (182, 'tensorflow.keras.layers.BatchNormalization', 'keras.layers.BatchNormalization', ([], {'axis': 'bn_axis', 'epsilon': '(1.001e-05)', 'name': "(name + '_0_bn')"}), False, 'from tensorflow import keras\n'), (60, 'tensorflow.keras.optimizers.schedules.ExponentialDecay', 'tf.keras.optimizers.schedules.ExponentialDecay', (['(0.01)'], {'decay_steps': '(1000)', 'decay_rate': '(0.9)', 'staircase': '(True)'}), True, 'import tensorflow as tf\n'), (65, 'tensorflow.keras.optimizers.SGD', 'tf.keras.optimizers.SGD', ([], {'learning_rate': 'lr_schedule'}), True, 'import tensorflow as tf\n')]