活动介绍
file-type

DS3231实时时钟模块编程操作指南

ZIP文件

下载需积分: 5 | 3KB | 更新于2024-11-17 | 129 浏览量 | 0 下载量 举报 收藏
download 立即下载
这份资源是关于如何使用DS3231实时时钟(RTC)模块,它是一个电子设备,用于保持时间的准确记录,即便在主电源丢失的情况下也能通过备用电池继续运行。在这个压缩包中,提供了一个适用于MicroPython环境的驱动程序,可用于设置和读取DS3231模块的时间。以下是这个资源所涵盖知识点的详细说明: 1. DS3231 RTC模块概述 DS3231是一款带有集成温度补偿晶振的高精度实时时钟芯片,它能够为系统提供精确的时间和日期信息。该模块通常包括以下特性: - 时间和日期管理,包括秒、分、时、日、月、年,并考虑闰年。 - 内置温度补偿晶振(TCXO),以提高时钟精度。 - 可通过I2C接口与微控制器进行通信。 - 内部集成的可充电电池,用于断电后的时钟维持。 2. MicroPython编程基础 MicroPython是Python 3编程语言的一个精简而高效的实现,它被设计为在微控制器和受限系统上运行。在这个上下文中,它是用来编写代码以与DS3231模块通信的工具。MicroPython的特点包括: - 一个小巧的Python解释器,可以在硬件资源有限的设备上运行。 - 标准Python库的一部分,以及针对微控制器功能的补充模块。 - 提供串行通信、GPIO控制、ADC读取、定时器、中断等硬件操作的能力。 3. 设置和读取时间的方法 在提供的文件列表中,"ds3231.py" 是一个Python库,用于简化与DS3231模块的交互。通过编写或使用这个库,用户可以轻松地完成以下任务: - 初始化DS3231模块,并设置当前的时间和日期。 - 读取当前的实时时间。 - 如果需要,可以调整时区和夏令时等。 "boot.py" 和 "main.py" 文件用于初始化和运行MicroPython程序。在这些文件中,可能会包含必要的代码来导入"ds3231.py"库,并在设备启动时自动执行时间设置或读取时间的功能。 4. 驱动程序和固件文件 "pybcdc.inf" 文件是一个设备驱动程序安装信息文件,它通常用于Windows操作系统中,以确保与硬件设备的兼容性和正确安装。在MicroPython的上下文中,这个文件可能不是必须的,但它表明这个资源可能与硬件设备的Windows驱动程序有关。 5. 文档和说明 "README.txt" 文件包含了关于如何使用这个资源的详细说明。它可能提供了以下内容: - 如何连接DS3231模块到微控制器。 - 如何在MicroPython环境下安装和导入"ds3231.py"库。 - 示例代码,展示如何设置和读取时间。 - 如何进行故障排除或寻求进一步的帮助。 总体来说,这个压缩包非常适合初学者或者需要在项目中集成时间管理功能的开发者。通过学习和应用这些知识,用户可以实现一个精确、可靠的时钟系统,这对于需要时间记录功能的应用是至关重要的。

相关推荐

filetype

Use the credictcard-reduced.csv dataset ([Data description] and build Five classification models. Please plot confusion matrix, and evaluate your model performance using accuracy, precision, recall, F-score (70 points). A list of classification models can be found我现在需要完成上面的任务。已知导入的数据是这样的 Time V1 V2 V3 V4 V5 V6 V7 V8 V9 ... V21 V22 V23 V24 V25 V26 V27 V28 Amount Class 0 406 -2.312227 1.951992 -1.609851 3.997906 -0.522188 -1.426545 -2.537387 1.391657 -2.770089 ... 0.517232 -0.035049 -0.465211 0.320198 0.044519 0.177840 0.261145 -0.143276 0.00 1 names = [ "Nearest Neighbors", "Linear SVM", "RBF SVM", "Gaussian Process", "Decision Tree", "Random Forest", "Neural Net", "AdaBoost", "Naive Bayes", "QDA", ] classifiers = [ KNeighborsClassifier(3), SVC(kernel="linear", C=0.025, random_state=42), SVC(gamma=2, C=1, random_state=42), GaussianProcessClassifier(1.0 * RBF(1.0), random_state=42), DecisionTreeClassifier(max_depth=5, random_state=42), RandomForestClassifier( max_depth=5, n_estimators=10, max_features=1, random_state=42 ), MLPClassifier(alpha=1, max_iter=1000, random_state=42), AdaBoostClassifier(random_state=42), GaussianNB(), QuadraticDiscriminantAnalysis(), ] X, y = make_classification( n_features=2, n_redundant=0, n_informative=2, random_state=1, n_clusters_per_class=1 ) rng = np.random.RandomState(2) X += 2 * rng.uniform(size=X.shape) linearly_separable = (X, y) datasets = [ make_moons(noise=0.3, random_state=0), make_circles(noise=0.2, factor=0.5, random_state=1), linearly_separable, ] figure = plt.figure(figsize=(27, 9)) i = 1 # iterate over datasets for ds_cnt, ds in enumerate(datasets): # preprocess dataset, split into training and test part X, y = ds X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.4, random_state=42 ) x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5 y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5 # just plot the dataset first cm = plt.cm.RdBu cm_bright = ListedColormap(["#FF0000", "#0000FF"]) ax = plt.subplot(len(datasets), len(classifiers) + 1, i) if ds_cnt == 0: ax.set_title("Input data") # Plot the training points ax.scatter(X_train[:, 0], X_train[:, 1], c=y_train, cmap=cm_bright, edgecolors="k") # Plot the testing points ax.scatter( X_test[:, 0], X_test[:, 1], c=y_test, cmap=cm_bright, alpha=0.6, edgecolors="k" ) ax.set_xlim(x_min, x_max) ax.set_ylim(y_min, y_max) ax.set_xticks(()) ax.set_yticks(()) i += 1 # iterate over classifiers for name, clf in zip(names, classifiers): ax = plt.subplot(len(datasets), len(classifiers) + 1, i) clf = make_pipeline(StandardScaler(), clf) clf.fit(X_train, y_train) score = clf.score(X_test, y_test) DecisionBoundaryDisplay.from_estimator( clf, X, cmap=cm, alpha=0.8, ax=ax, eps=0.5 ) # Plot the training points ax.scatter( X_train[:, 0], X_train[:, 1], c=y_train, cmap=cm_bright, edgecolors="k" ) # Plot the testing points ax.scatter( X_test[:, 0], X_test[:, 1], c=y_test, cmap=cm_bright, edgecolors="k", alpha=0.6, ) ax.set_xlim(x_min, x_max) ax.set_ylim(y_min, y_max) ax.set_xticks(()) ax.set_yticks(()) if ds_cnt == 0: ax.set_title(name) ax.text( x_max - 0.3, y_min + 0.3, ("%.2f" % score).lstrip("0"), size=15, horizontalalignment="right", ) i += 1 plt.tight_layout() plt.show() 老师给的模板是这样的,请帮我写类似的代码

filetype

def build_data_dict(paths, labels): data = [] skipped = 0 for path, label in zip(paths, labels): try: base_name = os.path.basename(path) file_id = base_name.replace(".nii.gz", "") mask_path = path.replace(".nii.gz", "mask.nii.gz") if not os.path.exists(mask_path): print(f"❌ 缺失 mask 文件: {mask_path}") skipped += 1 continue img_nii = nib.load(path) mask_nii = nib.load(mask_path) if np.all(mask_nii.get_fdata() == 0): print(f"⚠️ 掩膜全零,跳过: {mask_path}") skipped += 1 continue data.append({ "image": path, "mask": mask_path, "label": label, "id": file_id, "original_affine": np.array(img_nii.affine)[:4, :4].astype(np.float32), "original_shape": img_nii.shape, "mask_original_affine": np.array(mask_nii.affine)[:4, :4].astype(np.float32) }) except Exception as e: print(f"❌ 构建失败: {path},原因: {e}") skipped += 1 print(f"✅ 构建完成,有效样本: {len(data)},跳过: {skipped}") return data class SyncAffined(MapTransform): def __init__(self, keys, atol=1e-2, logger=None): super().__init__(keys) self.orientation = Orientationd(keys=keys, axcodes="RAS") self.resample = ResampleToMatchd(keys=["mask"], key_dst="image", mode="nearest") self.atol = atol self.logger = logger def __call__(self, data): try: data = self.orientation(data) a1 = data["image_meta_dict"]["affine"] a2 = data["mask_meta_dict"]["affine"] if isinstance(a1, torch.Tensor): a1 = a1.numpy() if isinstance(a2, torch.Tensor): a2 = a2.numpy() if not np.allclose(a1, a2, atol=self.atol): data = self.resample(data) return data except Exception as e: if self.logger: self.logger.error(f"Error during SyncAffined processing: {e}") raise def get_transforms(): deterministic_transforms = Compose([ LoadImaged(keys=["image", "mask"], image_only=False, reader="ITKReader"), EnsureChannelFirstd(keys=["image", "mask"]), SyncAffined(keys=["image", "mask"], atol=1e-2), Spacingd(keys=["image", "mask"], pixdim=(1.0, 1.0, 1.0), mode=("bilinear", "nearest")), CropForegroundd(keys=["image", "mask"], source_key="mask", margin=10, allow_smaller=True), ResizeWithPadOrCropd(keys=["image", "mask"], spatial_size=(64, 64, 64)), ScaleIntensityRanged(keys=["image"], a_min=20, a_max=80, b_min=0.0, b_max=1.0, clip=True), ToTensord(keys=["image", "mask"]) ]) augmentation_transforms = Compose([ RandFlipd(keys=["image", "mask"], prob=0.2, spatial_axis=[0, 1, 2]), RandAffined( keys=["image", "mask"], prob=0.3, rotate_range=(-0.2, 0.2), scale_range=(0.8, 1.2), shear_range=(-0.1, 0.1, -0.1, 0.1, -0.1, 0.1), translate_range=(5, 5, 5), mode=("bilinear", "nearest"), padding_mode="border", spatial_size=(64, 64, 64) ), Lambdad(keys=["label"], func=lambda x: torch.tensor(x, dtype=torch.long).squeeze(0)) ]) return deterministic_transforms, augmentation_transforms deterministic_transforms, augmentation_transforms = get_transforms() data_dir = "D:/monaisj/train" class_dirs = sorted([d for d in os.listdir(data_dir) if os.path.isdir(os.path.join(data_dir, d))]) image_paths, labels = [], [] for class_name in class_dirs: class_path = os.path.join(data_dir, class_name) nii_files = glob.glob(os.path.join(class_path, "*.nii.gz")) for nii_file in nii_files: if 'mask' not in nii_file: image_paths.append(nii_file) labels.append(int(class_name)) # 分层划分 sss = StratifiedShuffleSplit(n_splits=1, test_size=0.3, random_state=42) train_indices, val_indices = next(sss.split(image_paths, labels)) train_paths = [image_paths[i] for i in train_indices] val_paths = [image_paths[i] for i in val_indices] train_labels = [labels[i] for i in train_indices] val_labels = [labels[i] for i in val_indices] train_files = build_data_dict(train_paths, train_labels) val_files = build_data_dict(val_paths, val_labels) # -------------------- 数据集加载 -------------------- train_ds = CacheDataset(data=train_files, transform=deterministic_transforms, cache_rate=0.8) train_ds = Dataset(train_ds, transform=augmentation_transforms) train_loader = DataLoader(train_ds, batch_size=8, shuffle=True, num_workers=0) val_ds = CacheDataset(data=val_files, transform=deterministic_transforms, cache_rate=1.0) val_loader = DataLoader(val_ds, batch_size=8, shuffle=False, num_workers=0) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = monai.networks.nets.resnet18(pretrained=False, spatial_dims=3, n_input_channels=1, num_classes=2).to(device) weights_path = "D:/MedicalNet/pretrain/resnet_50_epoch_110_batch_0.pth" if os.path.exists(weights_path): state_dict = torch.load(weights_path, map_location=device, weights_only=True) new_state_dict = {} for k, v in state_dict.items(): name = k.replace("model.", "").replace("module.", "") new_state_dict[name] = v model.load_state_dict(new_state_dict, strict=False) print(f"加载权重成功: {weights_path}") else: print(f"权重文件不存在: {weights_path}") def init_weights(m): if isinstance(m, torch.nn.Linear): torch.nn.init.kaiming_normal_(m.weight) torch.nn.init.constant_(m.bias, 0.0) model.fc.apply(init_weights) class_weights = torch.tensor([ len(train_labels)/(2.0 * np.bincount(train_labels)[0]), len(train_labels)/(2.0 * np.bincount(train_labels)[1]) ], dtype=torch.float32).to(device) loss_fn = CrossEntropyLoss(weight=class_weights) def compute_metrics(labels, preds, probs): cm = confusion_matrix(labels, preds) tn, fp, fn, tp = cm.ravel() sensitivity = tp / (tp + fn) if (tp + fn) > 0 else 0 specificity = tn / (tn + fp) if (tn + fp) > 0 else 0 accuracy = (tp + tn) / (tp + tn + fp + fn) auc = roc_auc_score(labels, probs) return sensitivity, specificity, accuracy, auc def freeze_layers(model, freeze_patterns=None, unfreeze_patterns=None): """按模式冻结/解冻层""" for name, param in model.named_parameters(): param.requires_grad = False if unfreeze_patterns: for pattern in unfreeze_patterns: if pattern in name: param.requires_grad = True break if "fc" in name: param.requires_grad = True def train_model(model, stage=1, epochs=50, init_lr=1e-3, eta_min=1e-5, data_dir="D:/monaisj/"): # 阶段配置 if stage == 1: # 阶段1:仅训练分类头 freeze_layers(model, unfreeze_patterns=["fc"]) print("🔒 阶段1:冻结骨干,仅训练分类头") lr = init_lr # 较高学习率 elif stage == 2: # 阶段2:解冻高层 freeze_layers(model, unfreeze_patterns=["layer3","layer4", "fc"]) print("🔓 阶段2:解冻高层(layer3/layer4)") lr = init_lr * 0.1 # 降低学习率 elif stage == 3: # 阶段3:全解冻 for param in model.parameters(): param.requires_grad = True print("🔥 阶段3:解冻全网络") lr = init_lr * 0.01 # 更低学习率 else: raise ValueError(f"未知阶段: {stage}") # 创建优化器(仅优化需要梯度的参数) params_to_optimize = [p for p in model.parameters() if p.requires_grad] optimizer = AdamW(params_to_optimize, lr=lr, weight_decay=1e-4) scheduler = CosineAnnealingLR(optimizer, T_max=epochs, eta_min=eta_min) best_val_auc = 0.0 # 初始化历史记录 history = { 'train_loss': [], 'train_acc': [], 'train_auc': [], 'train_sensitivity': [], 'train_specificity': [], 'val_loss': [], 'val_acc': [], 'val_auc': [], 'val_sensitivity': [], 'val_specificity': [], 'train_true_labels': None, 'train_probs': [], 'val_true_labels': None, 'val_probs': [] } for epoch in range(epochs): # ================== 训练阶段 ================== model.train() epoch_loss = 0.0 train_preds, train_labels, train_probs = [], [], [] scaler = GradScaler() for batch in train_loader: images = batch["image"].to(device, non_blocking=True) labels = batch["label"].long().to(device) optimizer.zero_grad(set_to_none=True) outputs = model(images) loss = loss_fn(outputs, labels) scaler.scale(loss).backward() scaler.unscale_(optimizer) torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) scaler.step(optimizer) scaler.update() epoch_loss += loss.item() * images.size(0) # 收集训练数据 preds = torch.argmax(outputs, dim=1).cpu().numpy() probs = torch.softmax(outputs, dim=1)[:, 1].detach().cpu().numpy() train_preds.extend(preds) train_labels.extend(labels.cpu().numpy()) train_probs.extend(probs) # 记录训练集标签(仅第一次) if epoch == 0: history['train_true_labels'] = train_labels history['train_probs'].append(train_probs) # 计算训练指标 train_loss = epoch_loss / len(train_loader) train_acc = accuracy_score(train_labels, train_preds) train_sensitivity, train_specificity, _, train_auc = compute_metrics( train_labels, train_preds, train_probs ) # ================== 验证阶段 ================== model.eval() val_loss = 0.0 val_preds, val_labels, val_probs = [], [], [] with torch.no_grad(): for batch in val_loader: images = batch["image"].to(device, non_blocking=True) labels = batch["label"].long().to(device) outputs = model(images) loss = loss_fn(outputs, labels) val_loss += loss.item() * images.size(0) # 收集验证数据 probs = torch.softmax(outputs, dim=1)[:, 1].cpu().numpy() preds = torch.argmax(outputs, dim=1).cpu().numpy() val_preds.extend(preds) val_labels.extend(labels.cpu().numpy()) val_probs.extend(probs) # 记录验证集标签(仅第一次) if epoch == 0: history['val_true_labels'] = val_labels history['val_probs'].append(val_probs) # 计算验证指标 val_loss = val_loss / len(val_loader) val_acc = accuracy_score(val_labels, val_preds) val_sensitivity, val_specificity, _, val_auc = compute_metrics( val_labels, val_preds, val_probs ) # ================== 更新学习率和保存模型 ================== scheduler.step() # 保存每个epoch的模型 save_path = os.path.join(data_dir, f"best_model_stage{stage}_epoch{epoch+1}.pth") torch.save(model.state_dict(), save_path) print(f"✅ 保存模型到 {save_path}") # ================== 记录历史数据 ================== history['train_loss'].append(train_loss) history['train_acc'].append(train_acc) history['train_auc'].append(train_auc) history['train_sensitivity'].append(train_sensitivity) history['train_specificity'].append(train_specificity) history['val_loss'].append(val_loss) history['val_acc'].append(val_acc) history['val_auc'].append(val_auc) history['val_sensitivity'].append(val_sensitivity) history['val_specificity'].append(val_specificity) # 打印进度 current_lr = optimizer.param_groups[0]['lr'] print(f"Epoch {epoch+1}/{epochs} [Stage {stage}]") print(f"Train Loss: {train_loss:.4f} | Acc: {train_acc:.4f} | AUC: {train_auc:.4f}") print(f"Val Loss: {val_loss:.4f} | Acc: {val_acc:.4f} | AUC: {val_auc:.4f}") print(f"当前学习率: {current_lr:.2e}") print("-"*50) return history SEED =123456 random.seed(SEED) np.random.seed(SEED) # 阶段一:冻结骨干,仅训练分类头 print("\n开始阶段一训练(冻结骨干网络)") stage1_history = train_model(model, stage=1, epochs=40, init_lr=1e-2, eta_min=1e-2) print("\n开始阶段二训练(解冻高层)") # 加载阶段一最佳模型 # best_stage1_epoch = np.argmax([auc for auc in stage1_history['val_auc']]) + 1 model.load_state_dict(torch.load(f"D:/monaisj/best_model_stage1_epoch19.pth",map_location=device, weights_only=True)) stage2_history = train_model(model, stage=2, epochs=30, init_lr=1e-3, eta_min=1e-4) model.load_state_dict(torch.load(f"D:/monaisj/best_model_stage2_epoch20.pth",map_location=device, weights_only=True)) stage3_history = train_model(model, stage=3, epochs=30, init_lr=1e-4, eta_min=1e-5) 加载最佳模型,写一段代码帮我生成grad—cam热图,a图为预处理后根据掩膜裁剪后的输入模型图,b图是掩膜图,c图是cam图,d图是输入模型图和cam的叠加图

filetype

import os import numpy as np import torch from torch.utils.data import DataLoader import monai from monai.transforms import ( Compose, LoadImaged, EnsureChannelFirstd, Spacingd, Orientationd, ScaleIntensityRanged, RandCropByPosNegLabeld, RandFlipd, RandRotate90d, EnsureTyped, Activations, AsDiscrete, Resized ) from monai.data import CacheDataset, list_data_collate, decollate_batch from monai.networks.nets import UNet from monai.losses import DiceLoss from monai.metrics import DiceMetric from glob import glob from sklearn.model_selection import train_test_split from monai.data import PersistentDataset # 配置参数 root_dir = "datasets/LiTS/processed" images_dir = os.path.join(root_dir, "images") labels_dir = os.path.join(root_dir, "labels") max_epochs = 100 batch_size = 2 learning_rate = 1e-4 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 数据增强与预处理 train_transforms = Compose([ LoadImaged(keys=["image", "label"]), EnsureChannelFirstd(keys=["image", "label"]), Orientationd(keys=["image", "label"], axcodes="RAS"), Spacingd(keys=["image", "label"], pixdim=(1.5, 1.5, 2.0), mode=("bilinear", "nearest")), Resized(keys=["image", "label"], spatial_size=(128, 128, 64), mode=("trilinear", "nearest")), ScaleIntensityRanged(keys=["image"], a_min=-200, a_max=200, b_min=0.0, b_max=1.0, clip=True), RandCropByPosNegLabeld( keys=["image", "label"], label_key="label", spatial_size=(96, 96, 48), pos=1.0, neg=1.0, num_samples=4, image_key="image", image_threshold=0, ), RandFlipd(keys=["image", "label"], prob=0.5, spatial_axis=0), RandFlipd(keys=["image", "label"], prob=0.5, spatial_axis=1), RandFlipd(keys=["image", "label"], prob=0.5, spatial_axis=2), RandRotate90d(keys=["image", "label"], prob=0.5, max_k=3), EnsureTyped(keys=["image", "label"]), ]) val_transforms = Compose([ LoadImaged(keys=["image", "label"]), EnsureChannelFirstd(keys=["image", "label"]), Orientationd(keys=["image", "label"], axcodes="RAS"), Spacingd(keys=["image", "label"], pixdim=(1.5, 1.5, 2.0), mode=("bilinear", "nearest")), Resized(keys=["image", "label"], spatial_size=(128, 128, 64), mode=("trilinear", "nearest")), ScaleIntensityRanged(keys=["image"], a_min=-200, a_max=200, b_min=0.0, b_max=1.0, clip=True), EnsureTyped(keys=["image", "label"]), ]) # 读取数据文件 images = sorted(glob(os.path.join(images_dir, "*.nii.gz"))) labels = sorted(glob(os.path.join(labels_dir, "*.nii.gz"))) data = [{"image": img, "label": lbl} for img, lbl in zip(images, labels)] # 划分训练和验证集 train_files, val_files = train_test_split(data, test_size=0.2, random_state=42) # 使用 CacheDataset 加速 train_ds = PersistentDataset(data=train_files, transform=train_transforms, cache_dir="./cache/train") val_ds = PersistentDataset(data=val_files, transform=val_transforms, cache_dir="./cache/val") train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True, num_workers=4, collate_fn=list_data_collate) val_loader = DataLoader(val_ds, batch_size=1, shuffle=False, num_workers=2) # 定义模型 model = UNet( spatial_dims=3, in_channels=1, out_channels=3, # 改为你实际类别数(如二分类 out_channels=2) channels=(16, 32, 64, 128, 256), strides=(2, 2, 2, 2), num_res_units=2, ).to(device) # 损失函数与优化器 loss_function = DiceLoss(to_onehot_y=True, softmax=True) optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate) # 评估指标 post_pred = Compose([ Activations(softmax=True), AsDiscrete(argmax=True) # 注意和 out_channels 保持一致 ]) post_label = AsDiscrete() # 直接用整数标签,不需要再one-hot dice_metric = DiceMetric(include_background=True, reduction="mean", get_not_nans=False) # 训练与验证循环 best_metric = -1 best_metric_epoch = -1 for epoch in range(max_epochs): print(f"Epoch {epoch+1}/{max_epochs}") model.train() epoch_loss = 0 step = 0 for batch_data in train_loader: step += 1 inputs, labels = batch_data["image"].to(device), batch_data["label"].to(device) optimizer.zero_grad() outputs = model(inputs) loss = loss_function(outputs, labels) loss.backward() optimizer.step() epoch_loss += loss.item() if step % 10 == 0: print(f" Step {step}, Loss: {loss.item():.4f}") epoch_loss /= step print(f"Epoch {epoch+1} average loss: {epoch_loss:.4f}") # 验证 # 验证 model.eval() with torch.no_grad(): for val_data in val_loader: val_images, val_labels = val_data["image"].to(device), val_data["label"].to(device) val_outputs = model(val_images) val_outputs = post_pred(val_outputs) val_labels = post_label(val_labels) dice_metric(y_pred=val_outputs, y=val_labels) metric = dice_metric.aggregate().item() dice_metric.reset() print(f"Validation Dice: {metric:.4f}") if metric > best_metric: best_metric = metric best_metric_epoch = epoch + 1 torch.save(model.state_dict(), "best_metric_model.pth") print(f"Saved new best model at epoch {best_metric_epoch}") print(f"Training complete! Best Dice: {best_metric:.4f} at epoch {best_metric_epoch}")我现在的代码输出不对,怎么解决呀?Epoch 1 average loss: 0.7576 Validation Dice: 0.0000 Saved new best model at epoch 1 Epoch 2/100 Step 10, Loss: 0.7425 Step 20, Loss: 0.7278 Step 30, Loss: 0.6964 Step 40, Loss: 0.6948 Step 50, Loss: 0.7049 Epoch 2 average loss: 0.7084 Validation Dice: 0.0000 Epoch 3/100 Step 10, Loss: 0.6911 Step 20, Loss: 0.6930 Step 30, Loss: 0.6829 Step 40, Loss: 0.6752 Step 50, Loss: 0.6822 Epoch 3 average loss: 0.6755 Validation Dice: 0.0000 Epoch 4/100 Step 10, Loss: 0.7019 Step 20, Loss: 0.6269 Step 30, Loss: 0.6283 Step 40, Loss: 0.6590 Step 50, Loss: 0.6610 Epoch 4 average loss: 0.6543 Validation Dice: 0.0000 Epoch 5/100 Step 10, Loss: 0.6341 Step 20, Loss: 0.6197 Step 30, Loss: 0.6523 Step 40, Loss: 0.6199 Step 50, Loss: 0.6229 Epoch 5 average loss: 0.6232 Validation Dice: 0.0000 Epoch 6/100 Step 10, Loss: 0.5624 Step 20, Loss: 0.5802 Step 30, Loss: 0.5987 Step 40, Loss: 0.5503 Step 50, Loss: 0.5978 Epoch 6 average loss: 0.5990 Validation Dice: 0.0000 Epoch 7/100 Step 10, Loss: 0.6362 Step 20, Loss: 0.5811 Step 30, Loss: 0.5381 Step 40, Loss: 0.5772 Step 50, Loss: 0.5711 Epoch 7 average loss: 0.5834 Validation Dice: 0.0000 Epoch 8/100 Step 10, Loss: 0.5849 Step 20, Loss: 0.5292 Step 30, Loss: 0.5606 Step 40, Loss: 0.5549 Step 50, Loss: 0.5663 Epoch 8 average loss: 0.5535 Validation Dice: 0.0000 Epoch 9/100 Step 10, Loss: 0.5517你看看