{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"### 定義常量","metadata":{}},{"cell_type":"code","source":"%reset y #清空所有變數\n\nfrom datetime import datetime\nimport pytz\n\ndef version_timestamp_generator():\n    # 当前日期和时间\n    target_timezone = pytz.timezone('Asia/Taipei')\n    now = datetime.now().astimezone(target_timezone)\n    print(f'Version: {now.strftime(\"%Y%m%d_%H:%M:%S\")}')\n    return now.strftime(\"%Y%m%d_%H:%M:%S\")\n    \n\nclass constant(object):\n    __slots__ = ()\n    WORK_DIR = \"/kaggle/working\"\n    DATASET_DIR = \"/kaggle/working/dataset\"\n    OUTPUT_DIR = \"/kaggle/working/output\"\n    DATA_DIR = \"/kaggle/working/data\"\n    INPUT_DIR = \"/kaggle/input\"\n    TRAIN_DIR = \"/kaggle/working/train\"\n    TEST_DIR = \"/kaggle/working/test1\"\n    VERSION = version_timestamp_generator()\n    \nPATH = constant()\nprint(f'WORK_DIR: {PATH.WORK_DIR}')\nprint(f'DATASET_DIR: {PATH.DATASET_DIR}')\nprint(f'OUTPUT_DIR: {PATH.OUTPUT_DIR}')\nprint(f'DATA_DIR: {PATH.DATA_DIR}')\nprint(f'INPUT_DIR: {PATH.INPUT_DIR}')\nprint(f'TRAIN_DIR: {PATH.TRAIN_DIR}')\nprint(f'TEST_DIR: {PATH.TEST_DIR}')\nprint(f'VERSION: {PATH.VERSION}')","metadata":{"execution":{"iopub.status.busy":"2023-07-04T02:06:07.954041Z","iopub.execute_input":"2023-07-04T02:06:07.955316Z","iopub.status.idle":"2023-07-04T02:06:10.442462Z","shell.execute_reply.started":"2023-07-04T02:06:07.955261Z","shell.execute_reply":"2023-07-04T02:06:10.441320Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 解壓縮資料集到 /kaggle/working","metadata":{}},{"cell_type":"code","source":"import os\nimport zipfile\n\n# 創建dataset, output目錄\nif not os.path.exists(PATH.DATASET_DIR):\n    os.makedirs(PATH.DATASET_DIR)\n\nif not os.path.exists(PATH.OUTPUT_DIR):\n    os.makedirs(PATH.OUTPUT_DIR)\n    \nif not os.path.exists(PATH.DATA_DIR):\n    os.makedirs(PATH.DATA_DIR)\n\n#解壓縮檔案\nif not os.path.exists(PATH.TRAIN_DIR) or len(os.listdir(PATH.TRAIN_DIR)) == 0:\n    zip_path = os.path.join(PATH.INPUT_DIR,'dogs-vs-cats','train.zip')\n    zf = zipfile.ZipFile(zip_path, 'r')\n    zf.extractall(PATH.WORK_DIR) #解壓到train目錄\n    zf.close()\n    \nif not os.path.exists(PATH.TEST_DIR) or len(os.listdir(PATH.TEST_DIR)) == 0:\n    zip_path = os.path.join(PATH.INPUT_DIR,'dogs-vs-cats','test1.zip')\n    zf = zipfile.ZipFile(zip_path, 'r')\n    zf.extractall(PATH.WORK_DIR) #解壓到train目錄\n    zf.close()\n    \n    \nprint('目錄創建完畢')\nprint('資料集解壓縮完畢')\nprint(f'count of TRAIN folder: {len(os.listdir(PATH.TRAIN_DIR))}')\nprint(f'count of TEST folder: {len(os.listdir(PATH.TEST_DIR))}')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2023-07-03T01:28:43.027980Z","iopub.execute_input":"2023-07-03T01:28:43.028327Z","iopub.status.idle":"2023-07-03T01:28:43.080927Z","shell.execute_reply.started":"2023-07-03T01:28:43.028297Z","shell.execute_reply":"2023-07-03T01:28:43.079761Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 載入套件\n\n[Tensorflow和Tensorflow I/O 版本兼容對應](https://github.com/tensorflow/io/#tensorflow-version-compatibility)  \n這邊要讓Tensorflow和Tensorflow I/O版本兼容","metadata":{}},{"cell_type":"code","source":"!pip install --upgrade tensorflow-io #更新(0.31 -> 0.32, 對應tensorflow 2.12版本)\n\nimport importlib\nimport tensorflow as tf\n\n# 清除缓存\nimportlib.invalidate_caches()\n\n# 重新導入模块\nimport tensorflow_io as tfio\n\nprint(f'\\nTensorflow-io: {tfio.__version__} has been updated.(Tensorflow version: {tf.__version__})')\nprint(f'TensorFlow Compatibility: tensorflow: 2.12.x')","metadata":{"execution":{"iopub.status.busy":"2023-07-04T02:06:21.338656Z","iopub.execute_input":"2023-07-04T02:06:21.339059Z","iopub.status.idle":"2023-07-04T02:06:33.623502Z","shell.execute_reply.started":"2023-07-04T02:06:21.339027Z","shell.execute_reply":"2023-07-04T02:06:33.622197Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\nimport cv2\nimport tensorflow as tf\nfrom PIL import Image","metadata":{"execution":{"iopub.status.busy":"2023-07-04T02:06:33.627772Z","iopub.execute_input":"2023-07-04T02:06:33.628088Z","iopub.status.idle":"2023-07-04T02:06:33.633704Z","shell.execute_reply.started":"2023-07-04T02:06:33.628059Z","shell.execute_reply":"2023-07-04T02:06:33.632720Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from tensorflow.keras.preprocessing.image import load_img, img_to_array, array_to_img\nfrom tensorflow.keras.optimizers.legacy import Adam\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.models import load_model\nfrom tensorflow.keras.layers import Dense, Flatten, Dropout, Activation, Conv2D, MaxPooling2D","metadata":{"execution":{"iopub.status.busy":"2023-07-04T02:06:33.635249Z","iopub.execute_input":"2023-07-04T02:06:33.636157Z","iopub.status.idle":"2023-07-04T02:06:33.646093Z","shell.execute_reply.started":"2023-07-04T02:06:33.636117Z","shell.execute_reply":"2023-07-04T02:06:33.645160Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 打印原始圖片","metadata":{}},{"cell_type":"code","source":"import os\nimport glob\nimport numpy as np\nfrom PIL import Image\n\npreview_img_num = 10;\n\n#建立子圖\nfig, axes = plt.subplots(nrows=1, ncols=preview_img_num)\n\n#印出原始圖片\nprint('原始圖片:')\nfor index, path in enumerate(os.listdir(PATH.TRAIN_DIR)):\n    img_path = os.path.join(PATH.TRAIN_DIR,path)\n    img = Image.open(img_path)\n    axes[index].imshow(img)\n    axes[index].axis('off') #關閉坐標軸\n    if index == preview_img_num - 1: break\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-06-30T07:54:13.190602Z","iopub.execute_input":"2023-06-30T07:54:13.191013Z","iopub.status.idle":"2023-06-30T07:54:13.996800Z","shell.execute_reply.started":"2023-06-30T07:54:13.190978Z","shell.execute_reply":"2023-06-30T07:54:13.995640Z"},"jupyter":{"source_hidden":true,"outputs_hidden":true},"collapsed":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 對原始數據做分析\n對圖片尺寸進行分析","metadata":{}},{"cell_type":"code","source":"dict_lables = {\"cat\": 0, \"dog\": 1}\nall_widths = []\nall_heights = []\ncat_widths = []\ncat_heights = []\ndog_widths = []\ndog_heights = []\n\nfor i, filename in enumerate(os.listdir(PATH.TRAIN_DIR)):\n        category = filename.split('.')[0]\n        category = dict_lables[category]\n        img = cv2.imread(os.path.join(PATH.TRAIN_DIR, filename))\n        width, height = img.shape[1], img.shape[0]\n        all_widths.append(width)\n        all_heights.append(height)\n        if(category == 0):\n            cat_widths.append(width)\n            cat_heights.append(height)\n        else:\n            dog_widths.append(width)\n            dog_heights.append(height)\n\ndef plot_subplot_for_size_distribution(data, title, plt_count):\n    plt.subplot(3, 2, plt_count)\n    color = 'green' if plt_count % 2 == 0 else 'blue'\n    plt.hist(data, bins=20, color=color, alpha=0.5)\n    xlabel = 'height' if plt_count % 2 == 0 else 'width'\n    plt.xlabel(xlabel)\n    plt.ylabel('Count')\n    plt.title(f'{title} Distribution')   \n    plt.ylim([0, 25000]) # 設定y軸範圍\n\n# 繪製直方圖\nplot_data = [all_widths, all_heights, cat_widths, cat_heights, dog_widths, dog_heights]\nplot_title = ['All Widths', 'All Heights', 'Cat Widths', 'Cat Heights', 'Dog Widths', 'Dog Heights']\n\nplt.figure(figsize=(10, 10))\n\nfor i,data in enumerate(plot_data):\n    plot_subplot_for_size_distribution(data, plot_title[i], i+1)\n\n# 調整子圖間距\nplt.subplots_adjust(wspace=1, hspace=1)\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-06-30T08:35:51.154542Z","iopub.execute_input":"2023-06-30T08:35:51.155647Z","iopub.status.idle":"2023-06-30T08:36:45.830823Z","shell.execute_reply.started":"2023-06-30T08:35:51.155529Z","shell.execute_reply":"2023-06-30T08:36:45.829609Z"},"jupyter":{"outputs_hidden":true,"source_hidden":true},"collapsed":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 資料擴增(在資料預處理之前)","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.preprocessing.image import ImageDataGenerator\nimport random\nimport gc\nimport os\n\n# 生成指定數量的資料擴增後圖片\nnum_augmented_images = 100\n\ndict_lables = {\"cat\": 0, \"dog\": 1}\nsize = (64,64)\nargument_data = []\nargument_label = []\n\n# 重建資料擴增資料夾\n!rm -rf /kaggle/working/data/argument_data\nARGUMENT_DATA_FOLDER = os.path.join(PATH.DATA_DIR,'argument_data')\nos.makedirs(ARGUMENT_DATA_FOLDER, exist_ok=True)\n\n# 資料擴增參數設置\ndatagen = ImageDataGenerator(\n    rotation_range=5,  # 隨機旋轉範圍（角度0~180）\n    width_shift_range=0.05,  # 隨機水平平移範圍（相對於圖片寬度的比例）\n    height_shift_range=0.05,  # 隨機垂直平移範圍（相對於圖片高度的比例）\n    shear_range=0.1,  # 隨機剪切強度（角度）\n    zoom_range=0.1,  # 隨機縮放範圍\n    horizontal_flip=True,  # 隨機水平翻轉\n    vertical_flip=True,  # 隨機垂直翻轉\n    fill_mode='nearest'  # 填充新生成的像素的方法\n)\n\nimg_list = np.array(os.listdir(PATH.TRAIN_DIR))\n\n\ndef generate_argument_data(num_augmented_images):\n    # 隨機取數\n    selected_indices = random.sample(range(len(img_list)), num_augmented_images)\n    print(f'隨機選擇 {num_augmented_images} 個數字，開始進行資料擴增...')\n    for index in selected_indices:\n        img_path = os.path.join(PATH.TRAIN_DIR, img_list[index])\n        category = img_list[index].split('.')[0]\n        img = Image.open(img_path)\n        img = img.resize(size)\n        img = np.array(img)\n        img = np.expand_dims(img, axis=0)\n        \n        # 生成資料擴增後的圖片(batch_size每次生成一張)\n        aug_images = datagen.flow(img, batch_size=1, save_to_dir=ARGUMENT_DATA_FOLDER,\n                              save_prefix=f'{category}.', save_format='jpg')\n        next(aug_images)\n    print(f'資料擴增結束')\n    \ndef preprocess_argument_data():\n    for file in os.listdir(ARGUMENT_DATA_FOLDER):\n        img_path = os.path.join(ARGUMENT_DATA_FOLDER, file)\n        category = file.split('.')[0]\n        category = dict_lables[category]\n        img = Image.open(img_path)\n        x = img_to_array(img)\n        argument_data.append(x)\n        argument_label.append(category)\n    print('資料預處理結束')\n    \n# 顯示資料擴增圖片\ndef show_argument_data(preview_img_num):\n    #建立子圖\n    fig, axes = plt.subplots(nrows=1, ncols=preview_img_num)\n    #印出原始圖片\n    print('部分資料擴增(預處理後)圖片:')\n    for i in range(preview_img_num):\n        img = array_to_img(argument_data[i])\n        axes[i].imshow(img)\n        axes[i].axis('off') #關閉坐標軸\n    plt.show()\n        \n    \n# 執行define 程式\ngenerate_argument_data(num_augmented_images)\npreprocess_argument_data()\nargument_data = np.array(argument_data) / 255.0 #正規化\nshow_argument_data(10) # 顯示10筆資料擴增圖片\n\nprint(f'生成資料筆數: {len(argument_data)}(data), {len(argument_label)}(label)')\ngc.collect()","metadata":{"execution":{"iopub.status.busy":"2023-07-03T09:30:29.583433Z","iopub.execute_input":"2023-07-03T09:30:29.583836Z","iopub.status.idle":"2023-07-03T09:30:31.041257Z","shell.execute_reply.started":"2023-07-03T09:30:29.583804Z","shell.execute_reply":"2023-07-03T09:30:31.039412Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 將資料擴增的資料更新到現有舊資料中","metadata":{}},{"cell_type":"code","source":"import joblib\nimport gc\n\n\n# 加載舊資料\nold_data = joblib.load(os.path.join(PATH.DATA_DIR, 'data.pkl'))\nold_label = joblib.load(os.path.join(PATH.DATA_DIR, 'label.pkl'))\n\n# 資料合併\ndata = np.concatenate((old_data, argument_data), axis=0)\nlabel = np.concatenate((old_label, argument_label), axis=0)\n\n# 打印資料\nprint(f'old data len: {len(old_data)}, new arugument data len: {len(argument_data)}')\nprint(f'old label len: {len(old_label)}, new arugument label len: {len(argument_label)}')\nprint(f'After combine len: {len(data)}(data), {len(label)}(label)')\n\n# 儲存擴增資料\ndef save_updated_dataset(folder, file, filename):\n    FOLDER_PATH = os.path.join(PATH.DATA_DIR,folder)\n    os.makedirs(FOLDER_PATH, exist_ok=True)\n    FILE_PATH = os.path.join(FOLDER_PATH, filename)\n    joblib.dump(file, FILE_PATH)\n    print(f'Save {filename} in {FOLDER_PATH}')\n    \nsave_updated_dataset('argu100', data, 'data.pkl')\nsave_updated_dataset('argu100', label, 'label.pkl')\n\n\n# 釋放內存\ndel old_data\ndel old_label\ndel argument_data\ndel argument_label\ndel data\ndel label\n\ngc.collect()","metadata":{"execution":{"iopub.status.busy":"2023-07-03T09:30:59.287756Z","iopub.execute_input":"2023-07-03T09:30:59.288126Z","iopub.status.idle":"2023-07-03T09:31:02.147322Z","shell.execute_reply.started":"2023-07-03T09:30:59.288102Z","shell.execute_reply":"2023-07-03T09:31:02.145640Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 資料預處理成64*64，並做正規化\n\n這裡加入對數據進行分析  \n由於訓練資料集只有標籤，沒有其他特徵，只能對標籤進行個數計算  \n貓和狗的資料量是一樣的","metadata":{}},{"cell_type":"code","source":"dict_lables = {\"cat\": 0, \"dog\": 1}\nsize = (64, 64)\ndata = []\nlabel = []\n\ndef create_train_data(IMG_FOLDER_PATH):\n    for path in os.listdir(IMG_FOLDER_PATH):\n        category = path.split('.')[0]\n        category = dict_lables[category]\n        img = Image.open(os.path.join(IMG_FOLDER_PATH, path))\n        img = img.resize(size) #圖片重新resize為64*64\n        x = img_to_array(img) #將圖片轉為陣列型態,之後要輸入到CNN模型中\n        data.append(x) #預先處理好的圖片們存到陣列中\n        label.append(category) #標籤也存到陣列中\n\n# create_train_data(PATH.TRAIN_DIR)\ncreate_train_data('/kaggle/working/data/argument_data') # 對資料擴增的資料進行預處理\n\n# 資料正規化\ndef normalize_preprocessed_data():\n    global data\n    data = np.array(data) / 255.0\n\nnormalize_preprocessed_data()\n\nprint(f'len of data: {len(data)}')\nprint(f'len of label: {len(label)}')\n\n# 畫出資料分布\nplt.bar('cat', label.count(0))\nplt.bar('dog', label.count(1))\nplt.xlabel('label')\nplt.ylabel('count')\nplt.title('Train Data')\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-07-03T02:48:25.439499Z","iopub.execute_input":"2023-07-03T02:48:25.439970Z","iopub.status.idle":"2023-07-03T02:48:25.943038Z","shell.execute_reply.started":"2023-07-03T02:48:25.439935Z","shell.execute_reply":"2023-07-03T02:48:25.941888Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 印出部分**預處理**好的圖片","metadata":{}},{"cell_type":"code","source":"preview_img_num = 10\n\n#建立子圖\nfig, axes = plt.subplots(nrows=1, ncols=preview_img_num)\n\n\n\n#印出原始圖片\nprint('部分預處理圖片:')\nfor i in range(preview_img_num):\n    img = array_to_img(data[i])\n    axes[i].imshow(img)\n    axes[i].axis('off') #關閉坐標軸\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-07-03T02:48:33.442790Z","iopub.execute_input":"2023-07-03T02:48:33.443193Z","iopub.status.idle":"2023-07-03T02:48:33.761442Z","shell.execute_reply.started":"2023-07-03T02:48:33.443162Z","shell.execute_reply":"2023-07-03T02:48:33.760254Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 儲存預處理資料","metadata":{}},{"cell_type":"code","source":"import joblib\nimport gc\n\ndef save_preprocessed_data(filename, file):\n    FILE_PATH = os.path.join(PATH.DATA_DIR, filename)\n    joblib.dump(file, FILE_PATH)\n\nsave_preprocessed_data('data.pkl',data)\nsave_preprocessed_data('label.pkl', label)\n\n# 釋放內存\ndel data\ndel label\n\ngc.collect()","metadata":{"execution":{"iopub.status.busy":"2023-06-29T07:13:06.994825Z","iopub.execute_input":"2023-06-29T07:13:06.996402Z","iopub.status.idle":"2023-06-29T07:13:10.863279Z","shell.execute_reply.started":"2023-06-29T07:13:06.996325Z","shell.execute_reply":"2023-06-29T07:13:10.861736Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 定義模型函數\n使用L2正則化，避免過擬合\n\n這邊要特別提到[Adam](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam)套件，若用`tensorflow.keras.optimizers.lengency`的`Adam`  \n在`joblib.load(file)`的時候會報錯`Attribute 'build' not found`  \n所以改為`tensorflow.keras.optimizers`對應tensorflow 2.12版本","metadata":{}},{"cell_type":"code","source":"import joblib\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Dense, Flatten, Conv2D, MaxPooling2D\nfrom tensorflow.keras.regularizers import L2 # 導入正則化套件\n\n# 定義正則化係數(限制模型複雜度)\nl2 = 0.0022\n\n# 建立模型\ndef create_classifier():\n    classifier = Sequential() # Keras建立線性堆疊模型(用於建立FNN, CNN)\n    classifier.add(Conv2D(32,(3,3),input_shape=(64,64,3),activation = 'relu', kernel_regularizer=L2(l2))) #加入卷積層\n    classifier.add(MaxPooling2D(pool_size=(2,2),strides=2)) # 加入池化層用於採樣(if stride not given it equal to pool filter size)\n    classifier.add(Conv2D(32,(3,3),activation = 'relu', kernel_regularizer=L2(l2)))\n    classifier.add(MaxPooling2D(pool_size=(2,2),strides=2))\n    classifier.add(Flatten()) # 全連接層進行平坦化\n    classifier.add(Dense(units=128,activation='relu', kernel_regularizer=L2(l2))) # 全連接層定義\n    classifier.add(Dense(units=64,activation='relu', kernel_regularizer=L2(l2))) # 全連接層定義\n    classifier.add(Dense(units=1,activation='sigmoid')) # 全連接層定義\n    adam = Adam(learning_rate=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-07, amsgrad=False) # 配置adam優化器\n    classifier.compile(optimizer=adam,loss='binary_crossentropy',metrics=['accuracy'])\n    return classifier\n    \n# 保存模型\ndef save_classifier(filename, classifier):\n    MODEL_PATH = os.path.join(PATH.OUTPUT_DIR, PATH.VERSION ,'models')\n    os.makedirs(MODEL_PATH, exist_ok=True)\n    classifier.save(os.path.join(MODEL_PATH, filename))\n    print(f'{filename} has saved in {MODEL_PATH}')\n    \n# 保存數據\ndef save_file(obj, folder, filename):\n    FOLDER_PATH = os.path.join(PATH.OUTPUT_DIR, PATH.VERSION ,folder)\n    os.makedirs(FOLDER_PATH, exist_ok=True)\n    FILE_PATH = os.path.join(FOLDER_PATH, filename)\n    joblib.dump(obj,FILE_PATH)\n    print(f'{filename} has saved in Folder(Output): {folder}.')\n        \n# 加載預處理資料 \ndef load_data(filename, *args):\n    if len(args)!=0:\n        FILE_PATH = os.path.join(args[0], filename)\n    else:\n        FILE_PATH = os.path.join(PATH.DATA_DIR, filename)\n    loaded_data = joblib.load(FILE_PATH)\n    print(f'{filename} has loaded.')\n    return loaded_data\n        ","metadata":{"execution":{"iopub.status.busy":"2023-07-04T02:06:38.717660Z","iopub.execute_input":"2023-07-04T02:06:38.718079Z","iopub.status.idle":"2023-07-04T02:06:38.735734Z","shell.execute_reply.started":"2023-07-04T02:06:38.718047Z","shell.execute_reply":"2023-07-04T02:06:38.734869Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 進行10-fold cross-validation\n此步驟會產生10個模型\n\n\n`sklearn.metrics.f1_score` 二分類使用  \n`Sensitivity` = `Recall`","metadata":{}},{"cell_type":"code","source":"import os\nimport keras\nimport joblib\nimport numpy as np\nimport gc\nfrom sklearn.neighbors import KNeighborsClassifier\nfrom sklearn.metrics import classification_report, confusion_matrix\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score, log_loss, precision_score, recall_score, f1_score, roc_auc_score\nfrom sklearn.model_selection import KFold\n\n\n\n# 資料轉型和代入變數\nprint(f'VERSION: {PATH.VERSION}')\nX_train = load_data('data.pkl','/kaggle/working/data/argu100')\ny_train = np.array(load_data('label.pkl','/kaggle/working/data/argu100'))\n\n\n# 定義使用的評估指標\nscoring_metrics = {\n    'Accuracy': accuracy_score,\n    'Loss':log_loss,\n    'Precision': precision_score,   \n    'Sensitivity':recall_score,\n    'Specificity':[],\n    'Recall': recall_score,\n    'F1-score': f1_score,\n    'AUC': roc_auc_score,\n    'cm':[]\n}\n\n\n# 其他常數\nk = 10\nfold_no = 1\n\n# 創建K-fold 物件\nkf = KFold(n_splits=k)\n\n# 保存指標結果\nresults = {metric: [] for metric in scoring_metrics}\n\n# 進行 K 折交叉驗證\nfor train_index, val_index in kf.split(X_train):\n    \n    classifier = create_classifier() #創建新的model\n    print(f'{\"=\"*20} Fold no: {fold_no} {\"=\"*20}')\n    \n    print(f'train_index: {train_index}')\n    print(f'val_index: {val_index}')\n    \n    # 劃分訓練集和驗證集\n    X_train_fold, X_val_fold = X_train[train_index], X_train[val_index]\n    y_train_fold, y_val_fold = y_train[train_index], y_train[val_index]\n    print(f'X train len: {len(X_train_fold)}, X valid len: {len(X_val_fold)}')\n    print(f'Y train len: {len(y_train_fold)}, Y valid len: {len(y_val_fold)}')\n    \n    # 訓練模型(設置驗證資料集,不會影響model predict)\n    history = classifier.fit(X_train_fold,y_train_fold, batch_size=32, epochs=10, verbose=1,validation_data=(X_val_fold, y_val_fold))\n    \n    save_classifier(f'model_{fold_no}.h5', classifier)\n    save_file(history, 'histories', f'history_{fold_no}.pkl')  # 保存模型訓練歷史資料\n    \n    # 在驗證集上預測\n    y_pred_fold = classifier.predict(X_val_fold)\n    \n    print(f'驗證集預測({len(y_pred_fold)}):\\n{y_pred_fold}')\n    \n    # 因模型輸出的數據是浮點數(可能為0/1的概率值)，須轉為整數\n    y_pred_fold_binary = (y_pred_fold > 0.5).astype(int)\n    print(f'驗證集預測二進制({len(y_pred_fold_binary)}):\\n{y_pred_fold_binary}')\n    \n    # 計算混淆矩陣\n    cm = confusion_matrix(y_val_fold, y_pred_fold_binary) # 建立混淆矩陣\n    print(f'混淆矩陣為:\\n{cm}')\n    \n    \n    # 計算評估指標的值並保存\n    for metric in scoring_metrics:     \n        if metric == 'Specificity':\n            tn = cm[0,0] # True Nagative\n            fp = cm[0,1] # False Positive\n            specificity = tn / (tn + fp)\n            results[metric].append(specificity)\n        elif metric == 'cm':\n            results[metric].append(cm)\n        elif metric == 'Loss':\n            score = scoring_metrics[metric](y_val_fold, y_pred_fold) # 輸入的是0或1的概率值\n            results[metric].append(score)\n        else:\n            score = scoring_metrics[metric](y_val_fold, y_pred_fold_binary)\n            results[metric].append(score)\n    \n    print(f'results:\\n{results}')\n    \n    fold_no = fold_no + 1 # increase fold num\n    \n    gc.collect() # 回收資源\n\n# 印出每个评估指标的平均值\nfor metric in results:\n    average_score = sum(results[metric]) / k\n    print(f'{metric}: {average_score}')\n    \nsave_file(results, 'results', 'results.pkl')\n","metadata":{"execution":{"iopub.status.busy":"2023-07-04T02:06:41.910312Z","iopub.execute_input":"2023-07-04T02:06:41.910680Z","iopub.status.idle":"2023-07-04T02:18:13.998208Z","shell.execute_reply.started":"2023-07-04T02:06:41.910648Z","shell.execute_reply":"2023-07-04T02:18:13.997159Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 模型評估","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.models import load_model\nimport joblib\nimport os\n\n# 加載分類器(模型)\ndef load_classifier(fold_no):\n    VERSION = os.listdir(PATH.OUTPUT_DIR)\n    print(VERSION)\n    version_index = input('Please choose a version by index(0...)')\n    FILE_PATH = os.path.join(PATH.OUTPUT_DIR,'models', VERSION[version_index], f'model_{fold_no}.h5')\n    classifier = load_model(FILE_PATH)\n    return classifier\n    \n# 加載資料\ndef load_file(folder, filename):\n    VERSION = os.listdir(PATH.OUTPUT_DIR)\n    print(VERSION)\n    version_index = (int)(input('Please choose a version by index(0...)'))\n    print(f'You choose {VERSION[version_index]}')\n    if folder == 'histories':\n        loaded_file = []\n        for i in range(10):\n            FILE_PATH = os.path.join(PATH.OUTPUT_DIR, VERSION[version_index], folder, f'history_{i+1}.pkl')\n            file = joblib.load(FILE_PATH)\n            loaded_file.append(file)\n        \n    else:\n        FILE_PATH = os.path.join(PATH.OUTPUT_DIR, VERSION[version_index], folder, filename)\n        loaded_file = joblib.load(FILE_PATH)\n    return loaded_file","metadata":{"execution":{"iopub.status.busy":"2023-07-04T02:18:46.085293Z","iopub.execute_input":"2023-07-04T02:18:46.085696Z","iopub.status.idle":"2023-07-04T02:18:46.095845Z","shell.execute_reply.started":"2023-07-04T02:18:46.085663Z","shell.execute_reply":"2023-07-04T02:18:46.094843Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"results = load_file('results', 'results.pkl')\n\ndef print_metrics_without_cm():\n    for metric in results:\n        if metric != 'cm':\n            print(metric)\n            s = \"\"\n            for value in results[metric]:\n                s += str(value) + \",\"\n            print(s)\n            \ndef print_cm():\n    for cm in results['cm']:\n        for unit in cm:\n            print(unit)\n    \nprint_metrics_without_cm()\nprint_cm()","metadata":{"execution":{"iopub.status.busy":"2023-07-04T02:18:48.950827Z","iopub.execute_input":"2023-07-04T02:18:48.951540Z","iopub.status.idle":"2023-07-04T02:18:56.500390Z","shell.execute_reply.started":"2023-07-04T02:18:48.951505Z","shell.execute_reply":"2023-07-04T02:18:56.499387Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 繪製模型曲線","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport os\nimport cv2\n\ndef plot_history(history):\n    # 獲取train_loss, val_loss, train_acc, val_acc\n    train_loss = history.history['loss']\n    val_loss = history.history['val_loss']\n    train_acc = history.history['accuracy']\n    val_acc = history.history['val_accuracy']\n    \n    # 繪製loss曲線\n    fig = plt.figure(figsize=(10, 4))\n    plt.subplot(1, 2, 1)\n    plt.plot(train_loss, label='Training Loss')\n    plt.plot(val_loss, label='Validation Loss')\n    plt.title('Loss')\n    plt.xlabel('Epochs')\n    plt.ylabel('Loss')\n    plt.legend()\n\n    # 繪製acc曲線\n    plt.subplot(1, 2, 2)\n    plt.plot(train_acc, label='Training Accuracy')\n    plt.plot(val_acc, label='Validation Accuracy')\n    plt.title('Accuracy')\n    plt.xlabel('Epochs')\n    plt.ylabel('Accuracy')\n    plt.legend()\n\n    # 顯示圖片\n    plt.tight_layout()\n    plt.show()\n    \n    # 返回fig對象\n    return fig\n    \n# 儲存圖片    \ndef save_fig(folder, filename, fig):\n    FOLDER_PATH = os.path.join(PATH.OUTPUT_DIR, PATH.VERSION, folder)\n    os.makedirs(FOLDER_PATH, exist_ok=True)\n    FILE_PATH = os.path.join(FOLDER_PATH, filename)\n    fig.savefig(FILE_PATH)\n    \n    \n    \n# 繪製歷史曲線\nhistories = load_file('histories', None)\nfor i,history in enumerate(histories):\n    fig = plot_history(history)\n    print(i)\n    save_fig('figs',f'fold_{i+1}.png', fig)\n    \n# 產生output下載檔案\n# !zip output /kaggle/working/output -r","metadata":{"execution":{"iopub.status.busy":"2023-07-04T02:19:17.252816Z","iopub.execute_input":"2023-07-04T02:19:17.253236Z","iopub.status.idle":"2023-07-04T02:19:29.666456Z","shell.execute_reply.started":"2023-07-04T02:19:17.253201Z","shell.execute_reply":"2023-07-04T02:19:29.665270Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 對10折交叉的每一折資料進行分析","metadata":{}},{"cell_type":"code","source":"# 畫出貓狗數量分布\ndef plot_label_distribution(fold_no, train_label, val_label):\n    plt.subplot(1,2,1)\n    plt.bar('cat', np.sum(train_label == 0))\n    plt.bar('dog', np.sum(train_label == 1))\n    plt.xlabel('label')\n    plt.ylabel('count')\n    plt.title('Train data')\n    \n    plt.subplot(1,2,2)\n    plt.bar('cat', np.sum(val_label == 0))\n    plt.bar('dog', np.sum(val_label == 1))\n    plt.xlabel('label')\n    plt.ylabel('count')\n    plt.title('Validation data')\n    \n    # 調整子圖間距\n    plt.subplots_adjust(wspace=1, hspace=1)\n    #儲存子圖\n    FOLDER_PATH = os.path.join(PATH.OUTPUT_DIR,'each_fold')\n    os.makedirs(FOLDER_PATH, exist_ok=True) #若不存在則創建目錄\n    FIG_PATH = os.path.join(FOLDER_PATH, f'fold{fold_no}_distribition.png')\n    plt.savefig(FIG_PATH)\n    # 顯示圖片\n    plt.show()\n    \n    ","metadata":{"execution":{"iopub.status.busy":"2023-07-03T00:24:23.389065Z","iopub.execute_input":"2023-07-03T00:24:23.390146Z","iopub.status.idle":"2023-07-03T00:24:23.398589Z","shell.execute_reply.started":"2023-07-03T00:24:23.390099Z","shell.execute_reply":"2023-07-03T00:24:23.397281Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport keras\nimport joblib\nimport numpy as np\nimport gc\nfrom sklearn.neighbors import KNeighborsClassifier\nfrom sklearn.metrics import classification_report, confusion_matrix\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score, log_loss, precision_score, recall_score, f1_score, roc_auc_score\nfrom sklearn.model_selection import KFold\n\n# 資料轉型和代入變數\nX_train = load_data('data.pkl')\ny_train = np.array(load_data('label.pkl'))\n\n# 其他常數\nk = 10\nfold_no = 1\n\n# 創建K-fold 物件\nkf = KFold(n_splits=k)\n\n# 進行 K 折交叉驗證\nfor train_index, val_index in kf.split(X_train):\n    \n   \n    print(f'{\"=\"*20} Fold no: {fold_no} {\"=\"*20}')\n    \n    # 劃分訓練集和驗證集\n    X_train_fold, X_val_fold = X_train[train_index], X_train[val_index]\n    y_train_fold, y_val_fold = y_train[train_index], y_train[val_index]\n    print(f'X train len: {len(X_train_fold)}, X valid len: {len(X_val_fold)}')\n    print(f'Y train len: {len(y_train_fold)}, Y valid len: {len(y_val_fold)}')\n    plot_label_distribution(fold_no, y_train_fold, y_val_fold)\n    \n    \n    \n    fold_no = fold_no + 1 # increase fold num\n\n\n","metadata":{"execution":{"iopub.status.busy":"2023-07-03T00:24:27.716503Z","iopub.execute_input":"2023-07-03T00:24:27.716856Z","iopub.status.idle":"2023-07-03T00:24:37.106172Z","shell.execute_reply.started":"2023-07-03T00:24:27.716829Z","shell.execute_reply":"2023-07-03T00:24:37.105007Z"},"trusted":true},"execution_count":null,"outputs":[]}]}