-
Notifications
You must be signed in to change notification settings - Fork 0
/
EfficientNetB3.py
314 lines (291 loc) · 15.5 KB
/
EfficientNetB3.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
import numpy as np
import pandas as pd
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import time
import matplotlib.pyplot as plt
import cv2
import seaborn as sns
sns.set_style('darkgrid')
import shutil
from sklearn.metrics import confusion_matrix, classification_report
from sklearn.model_selection import train_test_split
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.layers import Dense, Activation,Dropout,Conv2D, MaxPooling2D,BatchNormalization
from tensorflow.keras.optimizers import Adam, Adamax
from tensorflow.keras.metrics import categorical_crossentropy
from tensorflow.keras import regularizers
from tensorflow.keras.models import Model
sdir = '/home/praem90/packages/EachOneTeachOne/ImageClassification/Grapevine_Leaves_Image_Dataset/'
classlist=sorted(os.listdir(sdir))
filepaths = []
labels=[]
for klass in classlist:
classpath=os.path.join(sdir,klass)
if os.path.isdir(classpath):
flist=os.listdir(classpath)
for f in flist:
fpath=os.path.join(classpath,f)
filepaths.append(fpath)
labels.append(klass)
Fseries=pd.Series(filepaths, name='filepaths')
Lseries=pd.Series(labels, name='labels')
df=pd.concat([Fseries, Lseries], axis=1)
train_df, dummy_df=train_test_split(df, train_size=.9, shuffle=True, random_state=12, stratify=df['labels'])
valid_df, test_df=train_test_split(dummy_df, train_size=.5, shuffle=True, random_state=12, stratify=dummy_df['labels'])
print('train_df lenght: ', len(train_df), ' test_df length: ', len(test_df), ' valid_df length: ', len(valid_df))
# get the number of classes and the images count for each class in train_df
classes=sorted(list(train_df['labels'].unique()))
class_count = len(classes)
print('The number of classes in the dataset is: ', class_count)
groups=train_df.groupby('labels')
countlist=[]
classlist=[]
for label in sorted(list(train_df['labels'].unique())):
group=groups.get_group(label)
countlist.append(len(group))
classlist.append(label)
Catseries=pd.Series(classlist, name='Category')
Countseries=pd.Series(countlist, name='Image Count')
crop_df=pd.concat([Catseries, Countseries], axis=1)
print(crop_df.head(class_count))
# labels=crop_df['Category']
# fig = plt.figure(figsize=(6,6))
# sizes=crop_df['Image Count']
# x=np.arange(len(labels))
# plt.xticks(np.arange(class_count)+.5, classes, rotation=90)
# plt.title('Image Sample distribution', color='blue', fontsize=16)
# plt.bar(x,sizes, tick_label=crop_df['Category'])
# plt.xlabel('Tumor Type')
# plt.ylabel('Image Samples')
# plt.show()
# get the classes with the minimum and maximum number of train images
max_value=np.max(countlist)
max_index=countlist.index(max_value)
max_class=classlist[max_index]
min_value=np.min(countlist)
min_index=countlist.index(min_value)
min_class=classlist[min_index]
print(max_class, ' has the most images= ',max_value, ' ', min_class, ' has the least images= ', min_value)
# lets get the average height and width of a sample of the train images
ht=0
wt=0
# select 100 random samples of train_df
train_df_sample=train_df.sample(n=100, random_state=123,axis=0)
for i in range (len(train_df_sample)):
fpath=train_df_sample['filepaths'].iloc[i]
img=plt.imread(fpath)
shape=img.shape
ht += shape[0]
wt += shape[1]
print('average height= ', ht//100, ' average width= ', wt//100, 'aspect ratio= ', ht/wt)
def balance(df, n, working_dir, img_size):
df=df.copy()
print('Initial length of dataframe is ', len(df))
aug_dir=os.path.join(working_dir, 'aug')# directory to store augmented images
if os.path.isdir(aug_dir):# start with an empty directory
shutil.rmtree(aug_dir)
os.mkdir(aug_dir)
for label in df['labels'].unique():
dir_path=os.path.join(aug_dir,label)
os.mkdir(dir_path) # make class directories within aug directory
# create and store the augmented images
total=0
gen=ImageDataGenerator(horizontal_flip=True, rotation_range=20, width_shift_range=.2,
height_shift_range=.2, zoom_range=.2)
groups=df.groupby('labels') # group by class
for label in df['labels'].unique(): # for every class
group=groups.get_group(label) # a dataframe holding only rows with the specified label
sample_count=len(group) # determine how many samples there are in this class
if sample_count< n: # if the class has less than target number of images
aug_img_count=0
delta=n - sample_count # number of augmented images to create
target_dir=os.path.join(aug_dir, label) # define where to write the images
msg='{0:40s} for class {1:^30s} creating {2:^5s} augmented images'.format(' ', label, str(delta))
print(msg, '\r', end='') # prints over on the same line
aug_gen=gen.flow_from_dataframe( group, x_col='filepaths', y_col=None, target_size=img_size,
class_mode=None, batch_size=1, shuffle=False,
save_to_dir=target_dir, save_prefix='aug-', color_mode='rgb',
save_format='jpg')
while aug_img_count<delta:
images=next(aug_gen)
aug_img_count += len(images)
total +=aug_img_count
print('Total Augmented images created= ', total)
# create aug_df and merge with train_df to create composite training set ndf
aug_fpaths=[]
aug_labels=[]
classlist=os.listdir(aug_dir)
for klass in classlist:
classpath=os.path.join(aug_dir, klass)
flist=os.listdir(classpath)
for f in flist:
fpath=os.path.join(classpath,f)
aug_fpaths.append(fpath)
aug_labels.append(klass)
Fseries=pd.Series(aug_fpaths, name='filepaths')
Lseries=pd.Series(aug_labels, name='labels')
aug_df=pd.concat([Fseries, Lseries], axis=1)
df=pd.concat([df,aug_df], axis=0).reset_index(drop=True)
print('Length of augmented dataframe is now ', len(df))
return df
n=200 # number of samples in each class
working_dir=r'./' # directory to store augmented images
img_size=(200,200) # size of augmented images
train_df=balance(train_df, n, working_dir, img_size)
working_dir=r'./'
img_size=(200,200)
batch_size=16 # We will use and EfficientetB3 model, with image size of (200, 250) this size should not cause resource error
trgen=ImageDataGenerator(horizontal_flip=True,rotation_range=20, width_shift_range=.2,
height_shift_range=.2, zoom_range=.2 )
t_and_v_gen=ImageDataGenerator()
msg='{0:70s} for train generator'.format(' ')
print(msg, '\r', end='') # prints over on the same line
train_gen=trgen.flow_from_dataframe(train_df, x_col='filepaths', y_col='labels', target_size=img_size,
class_mode='categorical', color_mode='rgb', shuffle=True, batch_size=batch_size)
msg='{0:70s} for valid generator'.format(' ')
print(msg, '\r', end='') # prints over on the same line
valid_gen=t_and_v_gen.flow_from_dataframe(valid_df, x_col='filepaths', y_col='labels', target_size=img_size,
class_mode='categorical', color_mode='rgb', shuffle=False, batch_size=batch_size)
# for the test_gen we want to calculate the batch size and test steps such that batch_size X test_steps= number of samples in test set
# this insures that we go through all the sample in the test set exactly once.
length=len(test_df)
test_batch_size=sorted([int(length/n) for n in range(1,length+1) if length % n ==0 and length/n<=80],reverse=True)[0]
test_steps=int(length/test_batch_size)
msg='{0:70s} for test generator'.format(' ')
print(msg, '\r', end='') # prints over on the same line
test_gen=t_and_v_gen.flow_from_dataframe(test_df, x_col='filepaths', y_col='labels', target_size=img_size,
class_mode='categorical', color_mode='rgb', shuffle=False, batch_size=test_batch_size)
# from the generator we can get information we will need later
classes=list(train_gen.class_indices.keys())
class_indices=list(train_gen.class_indices.values())
class_count=len(classes)
labels=test_gen.labels
print ( 'test batch size: ' ,test_batch_size, ' test steps: ', test_steps, ' number of classes : ', class_count)
img_shape=(img_size[0], img_size[1], 3)
model_name='EfficientNetB3'
base_model=tf.keras.applications.efficientnet.EfficientNetB3(include_top=False, weights="imagenet",input_shape=img_shape, pooling='max')
# Note you are always told NOT to make the base model trainable initially- that is WRONG you get better results leaving it trainable
base_model.trainable=True
x=base_model.output
x=BatchNormalization(axis=-1, momentum=0.99, epsilon=0.001 )(x)
x = Dense(256, kernel_regularizer = regularizers.l2(l = 0.016),activity_regularizer=regularizers.l1(0.006),
bias_regularizer=regularizers.l1(0.006) ,activation='relu')(x)
x=Dropout(rate=.4, seed=123)(x)
output=Dense(class_count, activation='softmax')(x)
model=Model(inputs=base_model.input, outputs=output)
lr=.001 # start with this learning rate
model.compile(Adamax(learning_rate=lr), loss='categorical_crossentropy', metrics=['accuracy'])
class LR_ASK(keras.callbacks.Callback):
def __init__ (self, model, epochs, ask_epoch): # initialization of the callback
super(LR_ASK, self).__init__()
self.model=model
self.ask_epoch=ask_epoch
self.epochs=epochs
self.ask=True # if True query the user on a specified epoch
self.lowest_vloss=np.inf
self.best_weights=self.model.get_weights() # set best weights to model's initial weights
self.best_epoch=1
def on_train_begin(self, logs=None): # this runs on the beginning of training
if self.ask_epoch == 0:
print('you set ask_epoch = 0, ask_epoch will be set to 1', flush=True)
self.ask_epoch=1
if self.ask_epoch >= self.epochs: # you are running for epochs but ask_epoch>epochs
print('ask_epoch >= epochs, will train for ', epochs, ' epochs', flush=True)
self.ask=False # do not query the user
if self.epochs == 1:
self.ask=False # running only for 1 epoch so do not query user
else:
print('Training will proceed until epoch', ask_epoch,' then you will be asked to')
print(' enter H to halt training or enter an integer for how many more epochs to run then be asked again')
self.start_time= time.time() # set the time at which training started
def on_train_end(self, logs=None): # runs at the end of training
print('loading model with weights from epoch ', self.best_epoch)
self.model.set_weights(self.best_weights) # set the weights of the model to the best weights
tr_duration=time.time() - self.start_time # determine how long the training cycle lasted
hours = tr_duration // 3600
minutes = (tr_duration - (hours * 3600)) // 60
seconds = tr_duration - ((hours * 3600) + (minutes * 60))
msg = f'training elapsed time was {str(hours)} hours, {minutes:4.1f} minutes, {seconds:4.2f} seconds)'
print (msg, flush=True) # print out training duration time
def on_epoch_end(self, epoch, logs=None): # method runs on the end of each epoch
v_loss=logs.get('val_loss') # get the validation loss for this epoch
if v_loss< self.lowest_vloss:
self.lowest_vloss=v_loss
self.best_weights=self.model.get_weights() # set best weights to model's initial weights
self.best_epoch=epoch + 1
print (f'\n validation loss of {v_loss:7.4f} is below lowest loss, saving weights from epoch {str(epoch + 1):3s} as best weights')
else:
print (f'\n validation loss of {v_loss:7.4f} is above lowest loss of {self.lowest_vloss:7.4f} keeping weights from epoch {str(self.best_epoch)} as best weights')
if self.ask: # are the conditions right to query the user?
if epoch + 1 ==self.ask_epoch: # is this epoch the one for quering the user?
print('\n Enter H to end training or an integer for the number of additional epochs to run then ask again')
ans=input()
if ans == 'H' or ans =='h' or ans == '0': # quit training for these conditions
print ('you entered ', ans, ' Training halted on epoch ', epoch+1, ' due to user input\n', flush=True)
self.model.stop_training = True # halt training
else: # user wants to continue training
self.ask_epoch += int(ans)
if self.ask_epoch > self.epochs:
print('\nYou specified maximum epochs of as ', self.epochs, ' cannot train for ', self.ask_epoch, flush =True)
else:
print ('you entered ', ans, ' Training will continue to epoch ', self.ask_epoch, flush=True)
lr=float(tf.keras.backend.get_value(self.model.optimizer.lr)) # get the current learning rate
print(f'current LR is {lr:7.5f} hit enter to keep this LR or enter a new LR')
ans=input(' ')
if ans =='':
print (f'keeping current LR of {lr:7.5f}')
else:
new_lr=float(ans)
tf.keras.backend.set_value(self.model.optimizer.lr, new_lr) # set the learning rate in the optimizer
print(' changing LR to ', ans)
epochs=40
ask_epoch=5
ask=LR_ASK(model, epochs, ask_epoch)
#rlronp=tf.keras.callbacks.ReduceLROnPlateau(monitor="val_loss", factor=0.5, patience=2,verbose=1)
#callbacks=[rlronp, ask]
callbacks=[ask]
history=model.fit(x=train_gen, epochs=epochs, verbose=1, callbacks=callbacks, validation_data=valid_gen,
validation_steps=None, shuffle=False, initial_epoch=0)
# def predictor(test_gen, test_steps):
# y_pred= []
# y_true=test_gen.labels
# classes=list(train_gen.class_indices.keys())
# class_count=len(classes)
# errors=0
# preds=model.predict(test_gen, steps=test_steps, verbose=1)
# tests=len(preds)
# for i, p in enumerate(preds):
# pred_index=np.argmax(p)
# true_index=test_gen.labels[i] # labels are integer values
# if pred_index != true_index: # a misclassification has occurred
# errors=errors + 1
# y_pred.append(pred_index)
# acc=( 1-errors/tests) * 100
# print(f'there were {errors} errors in {tests} tests for an accuracy of {acc:6.2f}')
# ypred=np.array(y_pred)
# ytrue=np.array(y_true)
# if class_count <=30:
# cm = confusion_matrix(ytrue, ypred )
# # plot the confusion matrix
# plt.figure(figsize=(12, 8))
# sns.heatmap(cm, annot=True, vmin=0, fmt='g', cmap='Blues', cbar=False)
# plt.xticks(np.arange(class_count)+.5, classes, rotation=90)
# plt.yticks(np.arange(class_count)+.5, classes, rotation=0)
# plt.xlabel("Predicted")
# plt.ylabel("Actual")
# plt.title("Confusion Matrix")
# plt.show()
# clr = classification_report(y_true, y_pred, target_names=classes, digits= 4) # create classification report
# print("Classification Report:\n----------------------\n", clr)
# return errors, tests
# errors, tests=predictor(test_gen, test_steps)
# acc=str(( 1-errors/tests) * 100)
# index=acc.rfind('.')
# acc=acc[:index + 3]
save_id= 'efficientnet_2.h5'
model_save_loc=os.path.join(working_dir, save_id)
model.save(model_save_loc)
print ('model was saved as ' , model_save_loc )