{"cells":[{"metadata":{"trusted":true},"cell_type":"code","source":"!pip install -q efficientnet","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#导入核心库\nimport tensorflow as tf # gg\nimport efficientnet.tfkeras as efn # 核心网络\nimport numpy as np # 矩阵处理\nfrom sklearn.metrics import f1_score, precision_score, recall_score, confusion_matrix\n# 高级统计数据\nimport matplotlib.pyplot as plt # 画图\nimport re # 统计图片数量\nimport json # 保存一些信息\nfrom math import cos,pi,e # 构造学习率调度函数\nfrom time import perf_counter # 用于计算时间\nprint('Tensorflow ver:',tf.__version__) # 导入完成！","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"try:\n    #解析TPU芯片集群\n    tpu = tf.distribute.cluster_resolver.TPUClusterResolver()\n    #kaggle不需要喂参数，他默认好了\n    tf.config.experimental_connect_to_cluster(tpu)\n    # 配置运算器\n    tf.tpu.experimental.initialize_tpu_system(tpu)\n    # 初始化tpu系统\n    print('TPU configured, at',tpu.master())\nexcept ValueError: #本地运行，CPU\n    tpu = None\n    print('TPUs not found! Using CPU')\n\n#获得分配策略，进行并行运算，以提升训练速度\nif tpu:\n    strategy = tf.distribute.experimental.TPUStrategy(tpu)\nelse:\n    strategy = tf.distribute.get_strategy()\n\nprint('Succeed distributing TPUs!')\nprint(\"REPLICAS: \", strategy.num_replicas_in_sync)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#后面很多核心函数都有优化参数，根据性能自动选择\nAUTO = tf.data.experimental.AUTOTUNE\nprint('AUTO:',AUTO)\n\n#超参数定义\n#strategy来自tpu_settings.py\nBATCH_SIZE = 16 * strategy.num_replicas_in_sync # 根据cpu/tpu自动调整batch大小\nEPOCHS = 12 # 训练周次，对应这个模型，12周最好\n\n\nIMAGE_SIZE = [512,512] #手动修改此处图像大小，进行训练\nWIDTH = IMAGE_SIZE[0]\nHEIGHT = IMAGE_SIZE[1]\nCHANNELS = 3\n# label数字对应CLASSES下标，得到label名字\nCLASSES = ['pink primrose',    'hard-leaved pocket orchid', 'canterbury bells', 'sweet pea',     'wild geranium',     'tiger lily',           'moon orchid',              'bird of paradise', 'monkshood',        'globe thistle',         # 00 - 09\n           'snapdragon',       \"colt's foot\",               'king protea',      'spear thistle', 'yellow iris',       'globe-flower',         'purple coneflower',        'peruvian lily',    'balloon flower',   'giant white arum lily', # 10 - 19\n           'fire lily',        'pincushion flower',         'fritillary',       'red ginger',    'grape hyacinth',    'corn poppy',           'prince of wales feathers', 'stemless gentian', 'artichoke',        'sweet william',         # 20 - 29\n           'carnation',        'garden phlox',              'love in the mist', 'cosmos',        'alpine sea holly',  'ruby-lipped cattleya', 'cape flower',              'great masterwort', 'siam tulip',       'lenten rose',           # 30 - 39\n           'barberton daisy',  'daffodil',                  'sword lily',       'poinsettia',    'bolero deep blue',  'wallflower',           'marigold',                 'buttercup',        'daisy',            'common dandelion',      # 40 - 49\n           'petunia',          'wild pansy',                'primula',          'sunflower',     'lilac hibiscus',    'bishop of llandaff',   'gaura',                    'geranium',         'orange dahlia',    'pink-yellow dahlia',    # 50 - 59\n           'cautleya spicata', 'japanese anemone',          'black-eyed susan', 'silverbush',    'californian poppy', 'osteospermum',         'spring crocus',            'iris',             'windflower',       'tree poppy',            # 60 - 69\n           'gazania',          'azalea',                    'water lily',       'rose',          'thorn apple',       'morning glory',        'passion flower',           'lotus',            'toad lily',        'anthurium',             # 70 - 79\n           'frangipani',       'clematis',                  'hibiscus',         'columbine',     'desert-rose',       'tree mallow',          'magnolia',                 'cyclamen ',        'watercress',       'canna lily',            # 80 - 89\n           'hippeastrum ',     'bee balm',                  'pink quill',       'foxglove',      'bougainvillea',     'camellia',             'mallow',                   'mexican petunia',  'bromelia',         'blanket flower',        # 90 - 99\n           'trumpet creeper',  'blackberry lily',           'common tulip',     'wild rose'\n           ]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 加载官方数据集\ntry: #Running in Kaggle kernel\n    from kaggle_datasets import KaggleDatasets\n    BASE = KaggleDatasets().get_gcs_path('flower-classification-with-tpus')\nexcept ModuleNotFoundError: # Running at my mac\n    BASE = \"/Users/astzls/Downloads/flower\"\n\nPATH_SELECT = { # 根据图像大小选择路径\n    192: BASE + '/tfrecords-jpeg-192x192',\n    224: BASE + '/tfrecords-jpeg-224x224',\n    331: BASE + '/tfrecords-jpeg-331x331',\n    512: BASE + '/tfrecords-jpeg-512x512'\n}\nIMAGE_PATH = PATH_SELECT[IMAGE_SIZE[0]]\n\n#此处利用tf.io的库函数\n#读出文件集方式很多种，也可以用os+re库进行\nTRAINING_FILENAMES = tf.io.gfile.glob(IMAGE_PATH + '/train/*.tfrec')\nVALIDATION_FILENAMES = tf.io.gfile.glob(IMAGE_PATH + '/val/*.tfrec')\nTEST_FILENAMES = tf.io.gfile.glob(IMAGE_PATH + '/test/*.tfrec')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 统计image数量，用于计算每个epoch随机批梯度下降的次数，保证所有图片都能被训练\n# 每个tfrec文件前面都是图片的数量，如0-512x512-123.tfrec，123就是图片数量\ndef count_data_items(filenames):\n    n = [int(re.compile(r\"-([0-9]*)\\.\").search(filename).group(1)) for filename in filenames]\n    return np.sum(n)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#下面是核心函数构造，自顶向下\ndef get_training_dataset(do_aug=True):\n    #严格遵循代码开头注释流程\n    # do_aug means do augmentation\n    dataset = load_dataset(TRAINING_FILENAMES, labeled=True) #labeled参数是用来判断数据有没有label的，比如test dataset就没有label\n    #装载分离好等数据，每个example含image，class等等，其中kaggle说image的图像是jpeg格式的\n    if do_aug:\n        dataset = dataset.map(data_augmentation, num_parallel_calls=AUTO)\n    #进行数据扩容，此步非常重要！可大大提升训练精度，已被广泛使用，不用你就out了\n    dataset = dataset.shuffle(2048)\n    # 每次取2048个examples，打乱。这个值不用过大也不要过小\n    dataset = dataset.batch(BATCH_SIZE)\n    #批处理\n    dataset = dataset.repeat()\n    #repeat无参数说明无限重复dataset。放心，不会内存溢出，只是懒循环罢了\n    dataset = dataset.prefetch(AUTO)\n    #根据cpu决定是否提前准备数据。为什么要这么做？原因是我想采用tpu进行训练，那么在tpu在训练时，cpu可以预先把下一批图像load到内存，当\n    # tpu训练好了，直接又能进行下一批当训练，减少了训练时间。\n    # 还是那句话，不用，你就out了 \n    return dataset\n\ndef get_validation_dataset(order=False):\n    dataset = load_dataset(VALIDATION_FILENAMES, labeled=True,ordered=order)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.cache() #cache dataset到memory，加速训练时间，只有3712张\n    dataset = dataset.prefetch(AUTO) \n    return dataset\n\ndef get_test_dataset(order=False):\n    dataset = load_dataset(TEST_FILENAMES, labeled=False,ordered=order)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.prefetch(AUTO) \n    return dataset\n\n#完善核心函数中的小函数\ndef load_dataset(filenames, labeled=True, ordered=False):\n    #ordered参数是指并行读取数据时不必在意是否按照原来顺序，加快速度\n    #顺序不重要的= =\n\n    #不在意顺序预处理\n    ignore_order = tf.data.Options()\n    if not ordered:\n        ignore_order.experimental_deterministic = False #详见help\n\n    #利用API导入初始TFrec文件集数据\n    dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTO)\n    #设置dataset，让他保持并行读出来的顺序就行了\n    dataset = dataset.with_options(ignore_order)\n    #根据label决定传入带标签的解析函数，还是不带标签（test只有id）的解析函数\n    dataset = dataset.map(read_labeled_tfrecord if labeled\n                     else read_unlabeled_tfrecord, \n                     num_parallel_calls=AUTO)\n    return dataset #现在dataset的每个data有两个部分了，一个是image，一个是class或id号\n\ndef data_augmentation(image, label):\n    #所谓数据扩容，就是把原来的照片左移或右移，或上下左右反转一下，就得到了“新”图像\n    #此方法利用了现实世界的平移不变形和空间不变形\n\n    image = tf.image.random_flip_left_right(image) \n    image = tf.image.random_flip_up_down(image)\n    image = tf.image.random_brightness(image, 0.1)\n\n    # 还有下列api进行更无聊的处理\n    # 'random_contrast', 对比度\n    # 'random_crop', 缩放\n    # 'random_hue', 色相\n    # 'random_jpeg_quality',  图片质量\n    # 'random_saturation' 饱和度\n    return image, label   \n\ndef decode_image(image_data):\n    #由于给的图像是jpeg格式，故用对应api进行处理。\n    #为什么要decode，因为他把图片写成bytes串了\n    image = tf.image.decode_jpeg(image_data, channels=3)\n    #得到tf.Tensor形式的image\n    image = tf.cast(image, tf.float32)\n    image /= 255.0\n    #将image的每个数值调整在[0,1]之间，方便训练\n    image = tf.reshape(image, [*IMAGE_SIZE, 3])\n    #reshape此部非常重要，调试的时候被坑了，老是说什么shape不匹配\n    return image\n\ndef read_labeled_tfrecord(example):\n    FEATURE = {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"class\": tf.io.FixedLenFeature([], tf.int64)  \n    }\n    example = tf.io.parse_single_example(example, FEATURE)\n    image = decode_image(example['image'])\n    label = tf.cast(example['class'], tf.int32)\n    return image, label #返回一个以 图像数组和标签形式的数据集\n\ndef read_unlabeled_tfrecord(example):\n    FEATURE = {\n        \"image\": tf.io.FixedLenFeature([], tf.string), \n        \"id\": tf.io.FixedLenFeature([], tf.string)\n    }\n    example = tf.io.parse_single_example(example, FEATURE)\n    image = decode_image(example['image'])\n    idnum = example['id']\n    return image, idnum\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 一些展示训练结果的函数\ndef show_acc_loss(history):\n    acc = history.history['sparse_categorical_accuracy']\n    val_acc = history.history['val_sparse_categorical_accuracy']\n\n    loss = history.history['loss']\n    val_loss = history.history['val_loss']\n\n    epochs = range(1,len(acc)+1)\n\n    plt.plot(epochs,acc,'bo',label='Training acc')\n    plt.plot(epochs,val_acc,'b',label='Validation acc')\n    plt.xlabel('Epochs')\n    plt.ylabel('Accuracy')\n    plt.title('Efficient net b7')\n    plt.legend()\n    plt.savefig('acc.jpg')\n\n    plt.figure()\n\n    plt.plot(epochs,loss,'bo',label='Training loss')\n    plt.plot(epochs,val_loss,'b',label='Validation loss')\n    plt.xlabel('Epochs')\n    plt.ylabel('Loss & CE')\n    plt.title('Efficient net b7')\n    plt.legend()\n\n    plt.savefig('loss.jpg')\n\n    plt.show()\n\ndef display_confusion_matrix(cmat, score, precision, recall):\n    plt.figure(figsize=(15,15))\n    ax = plt.gca()\n\n    ax.matshow(cmat, cmap='Reds')\n    ax.set_xticks(range(len(CLASSES)))\n    ax.set_xticklabels(CLASSES, fontdict={'fontsize': 7})\n    plt.setp(ax.get_xticklabels(), rotation=45, ha=\"left\", rotation_mode=\"anchor\")\n    ax.set_yticks(range(len(CLASSES)))\n    ax.set_yticklabels(CLASSES, fontdict={'fontsize': 7})\n    plt.setp(ax.get_yticklabels(), rotation=45, ha=\"right\", rotation_mode=\"anchor\")\n\n    titlestring = \"\"\n    if score is not None:\n        titlestring += 'f1 = {:.3f} '.format(score)\n    if precision is not None:\n        titlestring += '\\nprecision = {:.3f} '.format(precision)\n    if recall is not None:\n        titlestring += '\\nrecall = {:.3f} '.format(recall)\n    if len(titlestring) > 0:\n        ax.text(101, 1, titlestring, fontdict={'fontsize': 18, 'horizontalalignment':'right', 'verticalalignment':'top', 'color':'#804040'})\n    plt.savefig('confusion_matrix.jpg')\n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 创建efnb7 & custom classifier\nwith strategy.scope():\n    conv_base = efn.EfficientNetB7(\n        input_shape=(512, 512, 3),\n        weights='imagenet',\n        include_top=False\n    )\n    # acc means val_acc here, not train acc.\n    # Full training acc -> 95.4% (10epochs)\n    # fine-tune at b7a acc -> 93.59% 12epochs\n    # b6a loss -> 0.2687 - acc: 0.9467 11 epochs\n    # b5a loss -> 0.2417 - acc: 0.9537 12e pochs\n    # b4a loss -> 0.2411 - acc: 0.9539 12 epochs\n    # b2a loss -> 0.2212 - acc: 0.9582 13 epochs\n    set_trainable = False\n    for layer in conv_base.layers:\n        if layer.name == 'block2a_expand_conv':\n            set_trainable = True\n        if set_trainable:\n            layer.trainable = True\n        else:\n            layer.trainable = False\n\n    # dropout : val_acc\n    # 0 : 95.8\n    # 0.2 : 95.82\n    # Conclusion: No improved adding dropout at classifier.\n\n    # 第四轮lr不能大于2e-4,后面几轮学习率必须每一轮都要下降\n    model = tf.keras.Sequential([\n        conv_base,\n        tf.keras.layers.GlobalAveragePooling2D(),\n        tf.keras.layers.Dropout(0.1), #可有可无的dropout，但多少好那么一点点。。\n        tf.keras.layers.Dense(104, activation='softmax')\n    ])\n\n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(),\n        loss = 'sparse_categorical_crossentropy',\n        metrics=['sparse_categorical_accuracy']\n    )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 设置学习率调度器\n# 事实证明，sigmoid更平滑些\n\ndef lrfn(epoch): #用cos进行退火\n    LR_START = 2.5e-4\n    LR_MIN = 1e-5\n\n    lr = cos(epoch*pi/18) * LR_START\n\n    return lr if lr>LR_MIN else LR_MIN\n\ndef lrfn1(epoch): # 用sigmoid进行学习率退火\n    lr_start = 2.5e-4\n    lr_min = 1e-5\n\n    lr = lr_start / (1 + e ** (epoch - 5))\n    return lr if lr > lr_min else lr_min\n\ncallbacks_list = [\n    tf.keras.callbacks.EarlyStopping(\n        monitor='val_loss',\n        patience=2\n    ),\n    tf.keras.callbacks.LearningRateScheduler(\n        lrfn1,\n        verbose=1\n    )\n]\n\nepochs=[x for x in range(12)]\nlr_sigmoid=[lrfn1(x) for x in epochs]\nplt.plot(epochs,lr_sigmoid,'b')\nplt.xlabel('Epoch')\nplt.ylabel('lr')\nplt.title('Sigmoid lr scheduler')\nplt.savefig('lr_sche.jpg')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#开 始 训 练 ?\nnums_image = count_data_items(TRAINING_FILENAMES)\nSTEPS_PER_EPOCH = nums_image // BATCH_SIZE #每个epoch所要重复获取数据然后训练的次数\nprint('Total {} images will be training..'.format(nums_image))\n\n# 这里不能数据增强，否则loss会出现Nan，具体原因时添加了额外训练集\n# 增强这些训练集就会这样，但官方的没事，我很奇怪\n# 2020.5.7:可以使用随机左右翻转augment\nhistory = model.fit(get_training_dataset(do_aug=True),\n                    steps_per_epoch=STEPS_PER_EPOCH, \n                    epochs=EPOCHS,\n                    validation_data=get_validation_dataset(),\n                    callbacks=callbacks_list,\n                    verbose=2\n                    )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 保存权重就好\n# 如果整个model保存下来，tf2.1在load_model会出现错误，算是bug\nprint('Model saving...')\nmodel.save_weights('my_weights.h5')\nshow_acc_loss(history)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#prediction test_dataset\nprint('Preparing test dataset...')\ntest_ds = get_test_dataset(order=True)\n\nprint('Predicting test dataset...')\ntest_images = test_ds.map(lambda image, idnum: image)\nprobabilities = model.predict(test_images)\npredictions = np.argmax(probabilities, axis=-1)\n\nprint('Generating submission.csv file...')\nnums_test_images = count_data_items(TEST_FILENAMES)\ntest_ids = test_ds.map(lambda image, idnum: idnum).unbatch()\ntest_ids = next(iter(test_ids.batch(nums_test_images))).numpy().astype('U')\nnp.savetxt('submission.csv', np.rec.fromarrays([test_ids, predictions]), fmt=['%s', '%d'], delimiter=',', header='id,label', comments='')\nprint('Successfully generating csv submission file!')\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 加载验证集,为了得到f1 score\nprint('Preparing dataset...')\ndataset = get_validation_dataset(order=True)\nimages = dataset.map(lambda image, label: image)\nlabels = dataset.map(lambda image, label: label).unbatch()\n\n# 用验证集预测\nprint('Predicting...')\nprediction_start = perf_counter()\noutput = model.predict(images) # output.shape = (3712,104)\nprediction_end = perf_counter()\nprint('In {:.1f}s compeleted!'.format(prediction_end - prediction_start))\nprediction = np.argmax(output,axis=-1)\n\n# 直接获得正确标签整个整体，batch的话还要写多一个循环\nnums_validation_images = count_data_items(VALIDATION_FILENAMES)\ncorrect_labels = next(iter(labels.batch(nums_validation_images))).numpy()\n\nmistake = [0 for _ in range(len(CLASSES))] # 104 classes\nprint('Computing mistakes...')\nfor i in range(nums_validation_images):\n    if prediction[i] != correct_labels[i]:\n        mistake[correct_labels[i]]+=1\n\nwith open('mistake.json','w') as f:\n    json.dump(mistake,f)\n\n\nclasses = [x for x in range(104)]\nplt.bar(classes,mistake)\nplt.xlabel('CLASSES')\nplt.ylabel('Mistake count')\nplt.title('Mistakes each class')\nplt.savefig('mistake.jpeg')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#spawn confusion matrix\nprint('Spawning confusion matrix...')\nconfusion_mat = confusion_matrix(correct_labels, prediction, labels=range(len(CLASSES)))\nscore = f1_score(correct_labels, prediction, labels=range(len(CLASSES)), average='macro')\nprecision = precision_score(correct_labels, prediction, labels=range(len(CLASSES)), average='macro')\nrecall = recall_score(correct_labels, prediction, labels=range(len(CLASSES)), average='macro')\nconfusion_mat = (confusion_mat.T / confusion_mat.sum(axis=1)).T # normalized\ndisplay_confusion_matrix(confusion_mat, score, precision, recall)\nprint('f1 score: {:.3f}, precision: {:.3f}, recall: {:.3f}'.format(score, precision, recall))\n","execution_count":null,"outputs":[]}],"metadata":{"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":3}},"nbformat":4,"nbformat_minor":4}