'''
train a simple deep CNN on the CIFAR10 small images dataset
Epoch 40/100 loss: 0.0023 - acc: 0.7956 - val_loss: 6.4754 - val_acc: 0.5930
Epoch 100/100 loss: 0.8003 - acc: 0.7375 - val_loss: 0.7079 - val_acc: 0.7730
'''
import keras
import ipdb
from keras.datasets import cifar10
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Conv2D, MaxPooling2D
import os

batch_size = 32
num_classes = 10
epochs = 10
data_augmentation = True
num_predictions = 20
save_dir = os.path.join(os.getcwd(), 'saved_models')
model_name = 'keras_cifar10_trained_model.h5'

(x_train, y_train), (x_test, y_test) = cifar10.load_data()
print("x_train/y_train shape is {}/{} and its sample num is {}/{}".format(x_train.shape, y_train.shape, x_train.shape[0], y_train.shape[0]))

# before y_train.shape=(5000,1)
# after y_train.shape=(5000,10)
# Convert class vevtors to binary class matrics
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255

model = Sequential()
model.add(Conv2D(32, (3, 3), padding='same', input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes))
model.add(Activation('softmax'))

model.summary()

# intiate RMSprop optimizer
opt = keras.optimizers.rmsprop(lr=0.0001, decay=1e-6)

model.compile(loss='categorical_crossentropy',
        optimizer=opt,
        metrics=['accuracy'])



if not data_augmentation:
    print('Not using data augmentation')
    model.fit(x_train, y_train,
            batch_size=batch_size,
            epochs=epochs,
            verbose=1,
            validation_data=(x_test, y_test),
            shuffle=True)

else:
    print("Use real-time data augmentation")
    # 用以生成一个batch的图像数据，支持实时数据提升。训练时该函数会无限生成数据，直到达到规定的epoch次数为止
    datagen = ImageDataGenerator(
            featurewise_center=False,# 是输入的数据集去中心化（均值为0），按feature执行
            samplewise_center=False, # 是输入的数据每个样本均值为0
            featurewise_std_normalization=False, # 将输入除以数据集的标准差以完成标准化，按feature执行
            samplewise_std_normalization=False, # 将每个样本除以自身的标准差
            zca_whitening=False, # 对输入数据施加ZCA白化
            zca_epsilon=1e-6, # ZCA使用的eposilion
            rotation_range=0., # 整数，数据提升时图片随机转动的角度
            width_shift_range=0.1, # 浮点数，图片宽度的某个比例，数据提升时图片水平偏移的幅度
            height_shift_range=0.1, # 浮点数，图片高度的某个比例，数据提升时图片竖直偏移的幅度
            shear_range=0., # 浮点数，剪切强度（逆时针方向的剪切变换角度
            zoom_range=0., # 浮点数或形如[lower,upper]的列表，随机缩放的幅度，若为浮点数，则相当于[lower,upper] = [1 - zoom_range, 1+zoom_range]
            channel_shift_range=0., #浮点数，随机通道偏移的幅度
            fill_mode='nearest', # ‘constant’，‘nearest’，‘reflect’或‘wrap’之一，当进行变换时超出边界的点将根据本参数给定的方法进行处理
            cval=0., # 浮点数或整数，当fill_mode=constant时，指定要向超出边界的点填充的值
            horizontal_flip=True, # 布尔值，进行随机水平翻转
            vertical_flip=False, # 布尔值，进行随机竖直翻转
            rescale=None, #  重放缩因子,默认为None. 如果为None或0则不进行放缩,否则会将该数值乘到数据上(在应用其他变换之前)
            preprocessing_function=None, # 将被应用于每个输入的函数。该函数将在图片缩放和数据提升之后运行。该函数接受一个参数，为一张图片（秩为3的numpy array），并且输出一个具有相同shape的numpy array
            data_format=None, #“channel_last”对应原本的“tf”，“channel_first”对应原本的“th”。以128x128的RGB图像为例，“channel_first”应将数据组织为（3,128,128），而“channel_last”应将数据组织为（128,128,3）
            #  validation_split=0.0
            )
    # compute quantities required for featured for validation (std, mean, and pricipal components if ZCA whitening od apploed
    datagen.fit(x_train)

    ipdb.set_trace()
    # Fit the model on the batches generated by datagen.flow()
    history = model.fit_generator(datagen.flow(x_train, y_train,batch_size=batch_size),
                        epochs=epochs,
                        validation_data=(x_test, y_test),
                        workers=4)
    print(history.history)
    if not os.path.isdir(save_dir):
        os.makedirs(save_dir)
    model_path = os.path.join(save_dir, model_name)
    model.save(model_path)

    print('Saved trained model at ', model_path)

    scores = model.evaluate(x_test, y_test, verbose=1)
    print('Test loss is {}\tTest accuracy is {}'.format(scores[0], scores[1]))


'''
感悟：
    1. 有些层没有参数 比如：activation flatten dropout poolinglayer
    2. ImageDataGenerator is a difficult problem
    在数据集不够多的情况下，可以使用ImageDataGenerator()来扩大数据集防止搭建的网络出现过拟合现象
    ImageDataGeneoator()的方法：
        fit():计算依赖于数据的变换所需要的统计信息(均值方差等),只有使用featurewise_center，featurewise_std_normalization或zca_whitening参数时需要此函数。
        flow(): 接收numpy数组和标签为参数,生成经过数据扩展或标准化后的batch数据,并在一个无限循环中不断的返回数据

    3. the usage of model.save()
    4. fit_generator() 中的 data.flow() 与图片生成器配合使用 workers：最大进程数
        利用Python的生成器，逐个生成数据的batch并进行训练。生成器与模型将并行执行以提高效率。例如，该函数允许我们在CPU上进行实时的数据提升，同时在GPU上进行模型训练
    5. history
    history.params
        {'epochs': 10, 'do_validation': True, 'verbose': 1, 'steps': 1563, 'metrics': ['loss', 'acc', 'val_loss', 'val_acc']}
    history.history
        {'val_loss': [1.5857057516098023, 1.4015170768737792, 1.2782825870513916, 1.2111952907562256, 1.1039800045013428, 1.0538769172668456, 1.0395847120285033, 0.9748391864776611, 0.9348366659164429, 0.9208588556289673], 'loss': [1.884297137184143, 1.589861270904541, 1.4617259770202637, 1.3676968635559081, 1.2881453887939454, 1.2273330616378784, 1.1687487312316895, 1.1224711576843263, 1.0902145845031739, 1.053252790145874], 'val_acc': [0.4312, 0.5001, 0.5515, 0.574, 0.611, 0.6296, 0.6303, 0.6567, 0.6701, 0.6784], 'acc': [0.30792, 0.4239, 0.4737, 0.51096, 0.5434, 0.5626, 0.5874, 0.60368, 0.6148, 0.62802]}

'''

