{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.10","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":6927,"databundleVersionId":45059,"sourceType":"competition"}],"dockerImageVersionId":30097,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 图像分割","metadata":{}},{"cell_type":"code","source":"import numpy as np \nimport pandas as pd \nimport tensorflow as tf\nfrom zipfile import ZipFile \nimport keras.backend as K\nfrom sklearn.model_selection import train_test_split\nimport matplotlib.pyplot as plt\nimport os","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:50:45.544190Z","iopub.execute_input":"2024-05-09T17:50:45.544550Z","iopub.status.idle":"2024-05-09T17:50:45.549017Z","shell.execute_reply.started":"2024-05-09T17:50:45.544519Z","shell.execute_reply":"2024-05-09T17:50:45.548177Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"解压文件","metadata":{}},{"cell_type":"code","source":"train_zip = \"/kaggle/input/carvana-image-masking-challenge/train.zip\"\n\n# # ZipFile 类打开了 train_zip 文件\n# with 语句用于创建一个运行时上下文，在执行完毕后会自动关闭文件，这样可以确保资源被正确释放\nwith ZipFile(train_zip, 'r') as zip_: \n    zip_.extractall('/kaggle/working') #  # 将压缩文件中的所有内容解压缩到指定的目录 '/kaggle/working'","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:50:45.553544Z","iopub.execute_input":"2024-05-09T17:50:45.553824Z","iopub.status.idle":"2024-05-09T17:50:51.248620Z","shell.execute_reply.started":"2024-05-09T17:50:45.553798Z","shell.execute_reply":"2024-05-09T17:50:51.247787Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_mask_zip = \"/kaggle/input/carvana-image-masking-challenge/train_masks.zip\"\n\n\nwith ZipFile(train_mask_zip, 'r') as zip_: \n    zip_.extractall('/kaggle/working')","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:50:51.250008Z","iopub.execute_input":"2024-05-09T17:50:51.250293Z","iopub.status.idle":"2024-05-09T17:50:53.464761Z","shell.execute_reply.started":"2024-05-09T17:50:51.250265Z","shell.execute_reply":"2024-05-09T17:50:53.463714Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 打印两个目录中的文件数量\nprint(\"Train set:  \", len(os.listdir(\"/kaggle/working/train\")))\nprint(\"Train masks:\", len(os.listdir(\"/kaggle/working/train_masks\")))","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:50:53.466583Z","iopub.execute_input":"2024-05-09T17:50:53.466846Z","iopub.status.idle":"2024-05-09T17:50:53.478605Z","shell.execute_reply.started":"2024-05-09T17:50:53.466821Z","shell.execute_reply":"2024-05-09T17:50:53.477544Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 用于保存每个文件的车辆 ID 和文件路径\ncar_ids = []\npaths = []\n\n# os.walk() 函数遍历指定目录 '/kaggle/working/train' 及其子目录。在每次迭代中，dirname 是当前目录的路径，filenames 是当前目录中的文件名列表\nfor dirname, _, filenames in os.walk('/kaggle/working/train'):\n    for filename in filenames:\n        path = os.path.join(dirname, filename)    \n        paths.append(path)\n        \n        car_id = filename.split(\".\")[0]\n        car_ids.append(car_id)\n\nd = {\"id\": car_ids, \"car_path\": paths} # 创建了一个字典 d，其中包含两个键值对，分别是 \"id\" 和 \"car_path\"\ndf = pd.DataFrame(data = d) # DataFrame 函数将字典 d 转换为 DataFrame 对象。\ndf = df.set_index('id') # DataFrame 的索引设置为车辆 ID 列，即将车辆 ID 作为索引\ndf","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:50:53.479956Z","iopub.execute_input":"2024-05-09T17:50:53.480231Z","iopub.status.idle":"2024-05-09T17:50:53.519480Z","shell.execute_reply.started":"2024-05-09T17:50:53.480206Z","shell.execute_reply":"2024-05-09T17:50:53.518659Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"car_ids = []\nmask_path = []\nfor dirname, _, filenames in os.walk('/kaggle/working/train_masks'):\n    for filename in filenames:\n        path = os.path.join(dirname, filename)\n        mask_path.append(path)\n        \n        car_id = filename.split(\".\")[0]\n        car_id = car_id.split(\"_mask\")[0]\n        car_ids.append(car_id)\n\n        \nd = {\"id\": car_ids,\"mask_path\": mask_path}\nmask_df = pd.DataFrame(data = d)\nmask_df = mask_df.set_index('id')\nmask_df","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:50:53.520535Z","iopub.execute_input":"2024-05-09T17:50:53.520789Z","iopub.status.idle":"2024-05-09T17:50:53.560844Z","shell.execute_reply.started":"2024-05-09T17:50:53.520766Z","shell.execute_reply":"2024-05-09T17:50:53.560001Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df[\"mask_path\"] = mask_df[\"mask_path\"]\ndf","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:50:53.561883Z","iopub.execute_input":"2024-05-09T17:50:53.562134Z","iopub.status.idle":"2024-05-09T17:50:53.577937Z","shell.execute_reply.started":"2024-05-09T17:50:53.562110Z","shell.execute_reply":"2024-05-09T17:50:53.576973Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img_size = [256,256]\n\ndef data_augmentation(car_img, mask_img): # 通过随机地对输入的车辆图像和掩膜图像进行水平翻转来增加数据的多样性\n\n    if tf.random.uniform(()) > 0.5: # 50%的概率执行数据增强操作\n        car_img = tf.image.flip_left_right(car_img) # 用于实现图像的水平翻转\n        mask_img = tf.image.flip_left_right(mask_img)\n\n    return car_img, mask_img\n\ndef preprocessing(car_path, mask_path):\n    car_img = tf.io.read_file(car_path) # 读取车辆图像的文件内容，并将其保存在变量 car_img 中\n    car_img = tf.image.decode_jpeg(car_img, channels=3) # 将读取的图像内容解码为 JPEG 格式的图像，并指定通道数为 3（RGB 彩色图像）\n    car_img = tf.image.resize(car_img, img_size) # 用于确保所有图像都具有相同的尺寸\n    car_img = tf.cast(car_img, tf.float32) / 255.0 # 将图像数据类型转换为 tf.float32，并将像素值缩放到 [0, 1] 的范围内，以便进行神经网络的训练\n    \n    mask_img = tf.io.read_file(mask_path)\n    mask_img = tf.image.decode_jpeg(mask_img, channels=3)\n    mask_img = tf.image.resize(mask_img, img_size)\n    mask_img = mask_img[:,:,:1]  # 这一行保留掩膜图像的第一个通道，丢弃其他通道\n    mask_img = tf.math.sign(mask_img) # 将掩膜图像的像素值转换为 -1 或 1，将所有非零像素值转换为 1，零值保持不变。这种处理可能是为了将掩膜图像的像素值归一化为 -1 和 1，以便在神经网络中使用\n    \n    \n    return car_img, mask_img\n\ndef create_dataset(df, train = False):\n    if not train: # 如果 train 参数为 False，则表示在创建验证集或测试集的数据集，不进行数据增强\n        ds = tf.data.Dataset.from_tensor_slices((df[\"car_path\"].values, df[\"mask_path\"].values)) # 创建了一个 TensorFlow 数据集对象 ds，每个样本是一对 (car_path, mask_path)\n        ds = ds.map(preprocessing, tf.data.AUTOTUNE) # 将预处理函数 preprocessing 应用于数据集中的每个样本，tf.data.AUTOTUNE 用于自动调整处理的并行性能\n    else:\n        ds = tf.data.Dataset.from_tensor_slices((df[\"car_path\"].values, df[\"mask_path\"].values))\n        ds = ds.map(preprocessing, tf.data.AUTOTUNE)\n        ds = ds.map(data_augmentation, tf.data.AUTOTUNE) # 除了preprocessing外还调用了 data_augmentation 函数进行数据增强\n\n    return ds","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:50:53.579009Z","iopub.execute_input":"2024-05-09T17:50:53.579268Z","iopub.status.idle":"2024-05-09T17:50:53.591821Z","shell.execute_reply.started":"2024-05-09T17:50:53.579243Z","shell.execute_reply":"2024-05-09T17:50:53.590742Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 分割成训练集 train_df 和验证集 valid_df \n# 指定了随机种子为 42，这意味着每次运行该代码时，使用 train_test_split 函数进行数据集分割时的随机过程都将按照相同的规则进行，因此得到的训练集和验证集划分将是相同的，从而保证了结果的可重现性 验证集的大小为原始数据集大小的 25%\ntrain_df, valid_df = train_test_split(df, random_state=42, test_size=.25) \ntrain = create_dataset(train_df, train = True) # 调用 create_dataset 函数创建训练集。参数 train=True 表示要在训练集中进行数据增强操作\nvalid = create_dataset(valid_df)","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:50:53.594075Z","iopub.execute_input":"2024-05-09T17:50:53.594355Z","iopub.status.idle":"2024-05-09T17:50:53.660673Z","shell.execute_reply.started":"2024-05-09T17:50:53.594329Z","shell.execute_reply":"2024-05-09T17:50:53.659883Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"TRAIN_LENGTH = len(train_df)\nBATCH_SIZE = 16 # 批处理大小，即每次训练时模型处理的样本数量\nBUFFER_SIZE = 1000 # 设置了缓冲区大小，用于对数据进行随机打乱（shuffle）。在训练模型时，通常会在每个周期（epoch）开始时对数据进行随机打乱，以增加数据的随机性","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:50:53.662011Z","iopub.execute_input":"2024-05-09T17:50:53.662279Z","iopub.status.idle":"2024-05-09T17:50:53.666064Z","shell.execute_reply.started":"2024-05-09T17:50:53.662254Z","shell.execute_reply":"2024-05-09T17:50:53.665196Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# cache() 方法将数据缓存到内存中，这样可以加快训练过程，因为数据不需要每次都从磁盘中读取。\n# shuffle(BUFFER_SIZE) 方法将数据集中的样本随机洗牌，BUFFER_SIZE 是一个参数，表示用于洗牌的缓冲区大小。\n# 最终，train_dataset 变量保存了已经经过缓存、洗牌、分批和重复处理的训练数据集。\ntrain_dataset = train.cache().shuffle(BUFFER_SIZE).batch(BATCH_SIZE).repeat()\n\n# prefetch(buffer_size=tf.data.AUTOTUNE) 方法用于数据预取。这样做的好处是，当模型正在训练时，数据可以在后台预取和处理，从而减少训练时的延迟\ntrain_dataset = train_dataset.prefetch(buffer_size=tf.data.AUTOTUNE)\n\nvalid_dataset = valid.batch(BATCH_SIZE)","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:50:53.667006Z","iopub.execute_input":"2024-05-09T17:50:53.667281Z","iopub.status.idle":"2024-05-09T17:50:53.680190Z","shell.execute_reply.started":"2024-05-09T17:50:53.667244Z","shell.execute_reply":"2024-05-09T17:50:53.679457Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def display(display_list):\n    plt.figure(figsize=(15, 15))\n\n    title = ['Input Image', 'True Mask', 'Predicted Mask']\n\n    for i in range(len(display_list)):\n        plt.subplot(1, len(display_list), i+1)\n        plt.title(title[i])\n        plt.imshow(tf.keras.preprocessing.image.array_to_img(display_list[i])) # array_to_img() 函数将 TensorFlow 数组表示的图像转换为可显示的图像对象\n        plt.axis('off')\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:50:53.681508Z","iopub.execute_input":"2024-05-09T17:50:53.681819Z","iopub.status.idle":"2024-05-09T17:50:53.687845Z","shell.execute_reply.started":"2024-05-09T17:50:53.681792Z","shell.execute_reply":"2024-05-09T17:50:53.687051Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for i in range(5):\n   for image, mask in train.take(i):\n        sample_image, sample_mask = image, mask\n        display([sample_image, sample_mask])","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:50:53.688814Z","iopub.execute_input":"2024-05-09T17:50:53.689095Z","iopub.status.idle":"2024-05-09T17:50:56.358299Z","shell.execute_reply.started":"2024-05-09T17:50:53.689056Z","shell.execute_reply":"2024-05-09T17:50:56.357374Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# U-Net 模型","metadata":{}},{"cell_type":"markdown","source":"我们将使用U-Net模型。U-Net由编码器（下采样器）和解码器（上采样器）组成。为了学习鲁棒特征，并减少可训练参数的数量，可以使用预训练的模型作为编码器。编码器将是一个经过预训练的MobileNetV2模型，该模型已准备好并可在tf.keras.应用程序中使用。","metadata":{}},{"cell_type":"code","source":"# 使用了 TensorFlow 中提供的 MobileNetV2 模型，作为基础模型 输入图像的尺寸为 256x256 像素，且有 3 个通道（RGB）\n# include_top=False 表示不包含模型的顶部（全连接层），因为在这个场景中我们只需要使用卷积部分进行特征提取，而不需要进行分类\nbase_model = tf.keras.applications.MobileNetV2(input_shape=[256, 256, 3], include_top=False)\n\n# Use the activations of these layers\nlayer_names = [\n    'block_1_expand_relu',   # 64x64\n    'block_3_expand_relu',   # 32x32\n    'block_6_expand_relu',   # 16x16\n    'block_13_expand_relu',  # 8x8\n    'block_16_project',      # 4x4\n]\n\n# 针对每个指定的层名称，从 MobileNetV2 模型中获取该层的输出，并将这些输出存储在 base_model_outputs 列表中\nbase_model_outputs = [base_model.get_layer(name).output for name in layer_names]\n\n# 创建了一个特征提取模型，并将 MobileNetV2 模型的输入和指定层的输出作为输入和输出\ndown_stack = tf.keras.Model(inputs=base_model.input, outputs=base_model_outputs) # 特征提取模型的输入也就是 MobileNetV2 模型的输入，特征提取模型的输出就是 MobileNetV2 模型指定层的输出\ndown_stack.trainable = False # 意味着在训练过程中这些参数不会被更新","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:50:56.359944Z","iopub.execute_input":"2024-05-09T17:50:56.360325Z","iopub.status.idle":"2024-05-09T17:50:57.390737Z","shell.execute_reply.started":"2024-05-09T17:50:56.360279Z","shell.execute_reply":"2024-05-09T17:50:57.389898Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 定义了一个函数 upsample，用于创建上采样层，并使用该函数创建了一个上采样网络 up_stack\n# 四个参数：filters 表示输出通道数，size 表示卷积核大小，norm_type 表示归一化类型（默认为 'batchnorm'），apply_dropout 表示是否应用 dropout（默认为 False）\ndef upsample(filters, size, norm_type='batchnorm', apply_dropout=False):\n    \n    # 使用正态分布随机初始化器来初始化卷积层的权重 表示均值为 0，标准差为 0.02 的正态分布\n    initializer = tf.random_normal_initializer(0., 0.02)\n    \n    # 调用 tf.keras.Sequential() 函数，创建了一个空的序列模型 result 该模型目前是空的，还没有包含任何层，我们可以随后向其中添加各种类型的层，例如全连接层、卷积层、池化层等，以构建完整的神经网络模型\n    result = tf.keras.Sequential()\n    \n    # 向序列模型 result 中添加了一个反卷积层（转置卷积层）\n    result.add(\n      tf.keras.layers.Conv2DTranspose(filters, size, strides=2, # filters: 表示输出通道数，即卷积核的数量。size: 表示卷积核的大小。strides=2: 表示每次卷积核在输入特征图上移动 2 个像素\n                                      padding='same', # 表示填充方式，设置为 'same' 表示使用零填充，使得输入和输出的特征图尺寸相同\n                                      kernel_initializer=initializer, # 表示权重初始化器，使用了之前定义的 initializer，即均值为 0，标准差为 0.02 的正态分布\n                                      use_bias=False)) # 表示是否使用偏置项，默认为 True，但这里设置为 False，意味着该层不会添加偏置项\n    \n    # 向序列模型 result 中添加了归一化层 \n    # 检查归一化类型是否为批归一化\n    if norm_type.lower() == 'batchnorm': # 将 norm_type 参数转换为小写，以确保比较时不区分大小写 \n        result.add(tf.keras.layers.BatchNormalization()) # 如果是批归一化，则通过添加一个批归一化层到模型中。这将标准化层输入，使其具有零均值和单位方差，并学习缩放和偏移以保持表征的表达能力\n    #  检查归一化类型是否为批归一化\n    elif norm_type.lower() == 'instancenorm':\n        result.add(InstanceNormalization()) # 如果是实例归一化，则通过 InstanceNormalization() 添加一个实例归一化层到模型中\n\n    # 根据 apply_dropout 参数决定是否在序列模型 result 中添加 dropout 层和 ReLU 激活函数层，并最终返回构建好的序列模型\n    if apply_dropout:\n        result.add(tf.keras.layers.Dropout(0.5)) # 表示在训练过程中随机丢弃 50% 的输入单元\n        result.add(tf.keras.layers.ReLU()) # 紧接着在 dropout 层之后添加一个 ReLU 激活函数层\n\n    return result\n\n# 定义了一个从 4x4 到 64x64 的上采样过程，即每个上采样层将输入特征图的尺寸扩大一倍，同时减少通道数，直到最后输出尺寸为 64x64\nup_stack = [\n    upsample(512, 3),  # 4x4 -> 8x8\n    upsample(256, 3),  # 8x8 -> 16x16\n    upsample(128, 3),  # 16x16 -> 32x32\n    upsample(64, 3),   # 32x32 -> 64x64\n]","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:50:57.392166Z","iopub.execute_input":"2024-05-09T17:50:57.392500Z","iopub.status.idle":"2024-05-09T17:50:57.426322Z","shell.execute_reply.started":"2024-05-09T17:50:57.392460Z","shell.execute_reply":"2024-05-09T17:50:57.425464Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 定义了一个 U-Net 模型的函数 unet_model，该模型用于进行图像分割任务\ndef unet_model(output_channels): # 接受一个参数 output_channels，表示输出特征图的通道数\n    inputs = tf.keras.layers.Input(shape=[256, 256, 3]) # 输入尺寸为 256x256，通道数为 3（RGB 图像\n\n    skips = down_stack(inputs) # down_stack 是之前定义的特征提取模型，它将输入图像逐步降采样，并返回每个降采样阶段的特征图。skips 变量存储了这些特征图\n    x = skips[-1] # x 变量保存了最后一个降采样阶段的特征图\n    skips = reversed(skips[:-1]) # skips 变量用于保存除最后一个外的所有降采样阶段的特征图，并且通过 reversed 函数逆序排列，以便在后续的上采样过程中使用\n\n  # 这段代码使用 up_stack 中的上采样层逐步将特征图进行上采样，并且与相应的降采样阶段的特征图进行连接，建立了跳跃连接（skip connections）。\n    for up, skip in zip(up_stack, skips):\n        x = up(x) # 在每一步循环中，将输入特征图 x 通过上采样层 up 进行上采样，得到上采样后的特征图 x\n        concat = tf.keras.layers.Concatenate()\n        x = concat([x, skip]) # 创建一个 Concatenate 层用于拼接特征图。然后，将上采样后的特征图 x 和对应的降采样阶段的特征图 skip 拼接在一起，得到的结果赋值给 x\n\n  # 使用了一个反卷积层（转置卷积层），将上采样后的特征图扩大一倍。输出通道数为 output_channels，卷积核大小为 3x3，步长为 2，激活函数为 sigmoid，用于产生分割结果。\n    last = tf.keras.layers.Conv2DTranspose(\n      output_channels, 3, strides=2, activation='sigmoid',\n      padding='same')  # 最终的输出特征图尺寸为 128x128\n\n    x = last(x)\n\n    return tf.keras.Model(inputs=inputs, outputs=x)","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:50:57.427610Z","iopub.execute_input":"2024-05-09T17:50:57.427884Z","iopub.status.idle":"2024-05-09T17:50:57.435034Z","shell.execute_reply.started":"2024-05-09T17:50:57.427857Z","shell.execute_reply":"2024-05-09T17:50:57.434182Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 训练模型","metadata":{}},{"cell_type":"code","source":"# 定义了 Dice 相似系数（Dice coefficient）和 Dice 损失函数，并使用这些函数来编译 U-Net 模型\n\ndef dice_coef(y_true, y_pred, smooth=1): # y_true 是真实标签，y_pred 是模型的预测结果 smooth 是一个平滑因子，用于避免分母为零的情况\n    intersection = K.sum(y_true * y_pred, axis=[1,2,3]) # 计算交集，通过对 y_true 和 y_pred 逐元素相乘，然后在轴 [1,2,3] 上求和，得到交集\n    union = K.sum(y_true, axis=[1,2,3]) + K.sum(y_pred, axis=[1,2,3])  # 计算并集，通过分别对 y_true 和 y_pred 在轴 [1,2,3] 上求和，然后将结果相加，得到并集\n    return K.mean( (2. * intersection + smooth) / (union + smooth), axis=0) # 计算 Dice 相似系数的值，根据公式 (2 * |X ∩ Y| + smooth) / (|X| + |Y| + smooth) 计算出交集和并集的比值，然后对结果取平均，得到最终的 Dice 相似系数值\n\ndef dice_loss(in_gt, in_pred):\n    return 1-dice_coef(in_gt, in_pred) # 它是 Dice 相似系数的补数，即 1 减去 Dice 相似系数。这种损失函数的设计使得在训练过程中最小化 Dice 损失函数的值，相当于最大化 Dice 相似系数\n\nmodel = unet_model(1) # 创建了一个 U-Net 模型的实例，并将其赋值给变量 model。在创建模型时，指定了输出通道数为 1，这通常用于二元分类任务，例如图像分割中的背景与前景分割\n\n# compile 函数用于配置模型的训练方法\nmodel.compile(optimizer='adam', # 指定了优化器为 Adam。Adam 是一种基于梯度下降的优化算法，通常用于训练深度学习模型\n              loss = dice_loss, # 指定了损失函数为之前定义的 dice_loss 函数。这个损失函数用于衡量模型预测结果与真实标签之间的差异\n              metrics=[dice_coef,'binary_accuracy']) # Dice 相似系数用于评估分割的准确性，二元准确率用于评估模型对于二元分类任务的准确性 \n\ntf.keras.utils.plot_model(model, show_shapes=True) # 使用 tf.keras.utils.plot_model 函数来可视化模型的结构，设置 show_shapes=True 参数以显示每个层的输入和输出形状","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:50:57.436261Z","iopub.execute_input":"2024-05-09T17:50:57.436583Z","iopub.status.idle":"2024-05-09T17:50:58.417728Z","shell.execute_reply.started":"2024-05-09T17:50:57.436556Z","shell.execute_reply":"2024-05-09T17:50:58.416648Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"让我们尝试一下模型，看看它在训练前预测了什么","metadata":{}},{"cell_type":"code","source":"for images, masks in train_dataset.take(1):\n    for img, mask in zip(images, masks):\n        sample_image = img\n        sample_mask = mask\n        break\ndef visualize(display_list):\n    plt.figure(figsize=(15, 15))\n    title = ['Input Image', 'True Mask', 'Predicted Mask']\n    for i in range(len(display_list)):\n        plt.subplot(1, len(display_list), i+1)\n        plt.title(title[i])\n        plt.imshow(tf.keras.preprocessing.image.array_to_img(display_list[i]))\n        plt.axis('off')\n    plt.show()\n\ndef show_predictions(sample_image, sample_mask):\n    pred_mask = model.predict(sample_image[tf.newaxis, ...]) # 使用 tf.newaxis 将图像样本转换为形状为 (1, height, width, channels) 的张量，以匹配模型的输入要求\n    pred_mask = pred_mask.reshape(img_size[0],img_size[1],1) # 将预测的掩膜重新调整为与输入图像相同的形状，以便与真实掩膜进行对比和可视化\n    visualize([sample_image, sample_mask, pred_mask])\n    \nshow_predictions(sample_image, sample_mask)","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:50:58.419642Z","iopub.execute_input":"2024-05-09T17:50:58.420062Z","iopub.status.idle":"2024-05-09T17:51:11.507605Z","shell.execute_reply.started":"2024-05-09T17:50:58.420016Z","shell.execute_reply":"2024-05-09T17:51:11.506790Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model.summary()","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:51:11.508711Z","iopub.execute_input":"2024-05-09T17:51:11.508969Z","iopub.status.idle":"2024-05-09T17:51:11.529676Z","shell.execute_reply.started":"2024-05-09T17:51:11.508943Z","shell.execute_reply":"2024-05-09T17:51:11.528807Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"让我们观察模型在训练时如何改进。为了完成此任务，下面定义了一个回调函数。","metadata":{}},{"cell_type":"code","source":"# atience=4 表示如果在连续 4 个周期内验证损失没有改善，则停止训练\n# restore_best_weights=True 表示在停止训练时恢复到验证损失最低的那个周期的模型权重\nearly_stop = tf.keras.callbacks.EarlyStopping(patience=4,restore_best_weights=True)\n\nclass DisplayCallback(tf.keras.callbacks.Callback): # 定义了一个名为 DisplayCallback 的自定义回调类，继承自 tf.keras.callbacks.Callback 类\n    def on_epoch_begin(self, epoch, logs=None): # 重写了 on_epoch_begin 方法。每个周期开始时调用该方法\n        if (epoch + 1) % 3 == 0: # 如果当前周期的索引（epoch）加一能够被 3 整除，就调用 show_predictions 函数显示当前模型的预测结果\n            show_predictions(sample_image, sample_mask)\n            \n# 设置了总的训练周期数 EPOCHS 和每个周期的步数 STEPS_PER_EPOCH。这些值将用于模型的训练过程\nEPOCHS = 15\nSTEPS_PER_EPOCH = TRAIN_LENGTH // BATCH_SIZE\n\nmodel_history = model.fit(train_dataset, epochs=EPOCHS,\n                          steps_per_epoch=STEPS_PER_EPOCH,\n                          validation_data=valid_dataset,\n                          callbacks=[DisplayCallback(), early_stop])","metadata":{"execution":{"iopub.status.busy":"2024-05-09T17:51:11.530976Z","iopub.execute_input":"2024-05-09T17:51:11.531256Z","iopub.status.idle":"2024-05-09T17:51:31.548004Z","shell.execute_reply.started":"2024-05-09T17:51:11.531230Z","shell.execute_reply":"2024-05-09T17:51:31.543610Z"},"trusted":true},"execution_count":null,"outputs":[]}]}