diff --git a/mindspore/python/mindspore/Yolo v7 code comments for the blog of netural network learning (yolo v7 学习神经网络模型构建用代码评注).py b/mindspore/python/mindspore/Yolo v7 code comments for the blog of netural network learning (yolo v7 学习神经网络模型构建用代码评注).py new file mode 100644 index 00000000000..0caa58ed0cc --- /dev/null +++ b/mindspore/python/mindspore/Yolo v7 code comments for the blog of netural network learning (yolo v7 学习神经网络模型构建用代码评注).py @@ -0,0 +1,1954 @@ +# YOLO V7 --- 花园宝宝 + +# 注意:此代码仅用于辅助神经网络学习的理解,因此不能实际运行,且我们不具备运行条件 +# Notice:This code can only use for the neutral network learning blog.So, it can't actually be run. + +""" +数据集处理部分 +""" + +# 导入download函数。这意味着我们可以使用download函数来执行与下载相关的操作 +from download import download + +dataset_url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/ssd_datasets.zip" +path = "./" +path = download(dataset_url, path, kind="zip", replace=True) + +coco_root = "./datasets/" +anno_json = "./datasets/annotations/instances_val2017.json" + +train_cls = ['background', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', + 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', + 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', + 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', + 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', + 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', + 'kite', 'baseball bat', 'baseball glove', 'skateboard', + 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', + 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', + 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', + 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', + 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', + 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', + 'refrigerator', 'book', 'clock', 'vase', 'scissors', + 'teddy bear', 'hair drier', 'toothbrush'] + +# 创建一个名为train_cls_dict的空字典 +train_cls_dict = {} +# 然后遍历train_cls列表中的每个类别,将类别添加到字典中,并为其分配一个唯一的整数值(即键值) +for i, cls in enumerate(train_cls): + # 这样,我们可以通过类别名称轻松地获取其对应的整数值 + train_cls_dict[cls] = i + +import cv2 +import numpy as np + +# 定义一个函数_rand,参数a和b的默认值为0和1,返回一个0到1之间的随机数 +def _rand(a=0., b=1.): + # a b分别表示随机数的下限和上限。保证了随机数不会超出取值范围 + return np.random.rand() * (b - a) + a + +# 计算两个集合的交集 +def intersect(box_a, box_b): + """Compute the intersect of two sets of boxes.""" + # 计算两个边框的边界坐标,即max_yx和min_yx + # max_yx表示两个边框的右下角坐标 + max_yx = np.minimum(box_a[:, 2:4], box_b[2:4]) + # min_yx表示两个边框的左上角坐标 + min_yx = np.maximum(box_a[:, :2], box_b[:2]) + # 将交集区域的坐标限制在有效范围 + inter = np.clip((max_yx - min_yx), a_min=0, a_max=np.inf) + # 计算并返回交集区域的面积 + return inter[:, 0] * inter[:, 1] + +# 计算两个框集之间的Jaccard重叠率 +def jaccard_numpy(box_a, box_b): + """ + Compute the jaccard overlap of two sets of boxes. + Jaccard重叠率是一种衡量两个集合相似程度的指标,它的值在0到1之间,其中0表示两个集合没有交 + 集,1表示两个集合完全重叠 + """ + # 计算两个框集的交集面积,用于进行Jaccard重叠率的计算 + inter = intersect(box_a, box_b) + # 计算框集a的面积 + area_a = ((box_a[:, 2] - box_a[:, 0]) * + (box_a[:, 3] - box_a[:, 1])) + # 计算框集b的面积 + area_b = ((box_b[2] - box_b[0]) * + (box_b[3] - box_b[1])) + # 计算两个框集并的面积 + union = area_a + area_b - inter + # Jaccard重叠率计算公式为交集/并集 + return inter / union + +# 从图像中随机裁剪出部分区域,同时调整相应的框坐标 +def random_sample_crop(image, boxes): + """ + Crop images and boxes randomly. + 从图像中随机裁剪出部分区域,同时调整相应的框坐标。 + + 裁剪后的区域需要保留原始图像的宽高比,即裁剪后的宽度和高度需要满足一定的比例要求。 + 裁剪后的区域需要包含原始图像中的所有框。 + 裁剪后的区域需要满足一定的IoU(交并比)要求,这里的IoU值是从[0.1, 0.3, 0.5, 0.7, 0.9]中随机选择的 + """ + # 获取图像得到宽和高 + height, width, _ = image.shape + # 随机获取一个IoU值,作为IoU阈值 + # 如果IoU阈值为None,那么在计算IoU时,将不会对候选框进行过滤。这意味着候选框将被视为满足条件的框。但可能计算结果不够准确 + min_iou = np.random.choice([None, 0.1, 0.3, 0.5, 0.7, 0.9]) + + # IoU为None时,返回图像与裁剪的框 + if min_iou is None: + return image, boxes + + # 使用循环,循环50次,生成50个随机候选框 + for _ in range(50): + # 将image赋值给image_t,方便后续操作 + image_t = image + # 生成一个0.3到1.0之间的小数,乘以原始图像的宽度,作为候选框的宽度 + w = _rand(0.3, 1.0) * width + # 生成一个0.3到1.0之间的小数,乘以原始图像的高度,作为候选框的高度 + h = _rand(0.3, 1.0) * height + # 检查候选框的宽度和高度比例是否满足条件。如果比例小于0.5或者大于2,则跳过当前循环,进入下一个循环。 + if h / w < 0.5 or h / w > 2: + # 如果候选框不满足条件,则跳过当前循环,进入下一个循环。 + continue + + # 随机生成一个0到1之间的小数,乘以原始图像的宽度减去候选框的宽度,作为候选框的左边界。 + left = _rand() * (width - w) + # 随机生成一个0到1之间的小数,乘以原始图像的高度减去候选框的高度,作为候选框的上边界。 + top = _rand() * (height - h) + # 将候选框的左边界、上边界、右边界和下边界转换为一个NumPy数组,并将其转换为整数。 + rect = np.array([int(top), int(left), int(top + h), int(left + w)]) + # 使用jaccard_numpy函数计算候选框与所有候选框之间的IoU值,并将结果存储在变量overlap中。 + overlap = jaccard_numpy(boxes, rect) + + # 丢弃一些不满足条件的候选框 + # 创建一个布尔数组,用于存储IoU值大于0的候选框的下标 + drop_mask = overlap > 0 + # 检查布尔数组中是否有任何元素为True + if not drop_mask.any(): + # 如果没有,则跳过当前循环 + continue + + # 检查满足条件的候选框中IoU值的最小值是否小于min_iou和满足条件的候选框中IoU值的最大值是否大于min_iou+0.2 + if overlap[drop_mask].min() < min_iou and overlap[drop_mask].max() > (min_iou + 0.2): + # 如果超出IoU阈值,跳过当前循环 + continue + + # 根据候选框的坐标信息,裁剪原始图像,并将裁剪后的图像赋值给变量image_t + image_t = image_t[rect[0]:rect[2], rect[1]:rect[3], :] + # 计算候选框的中心坐标 + centers = (boxes[:, :2] + boxes[:, 2:4]) / 2.0 + # 检查候选框是否覆盖了图片的中心坐标区域 + # 矩形 rect 的左上角 x 坐标小于 centers 中每个点的 x 坐标且左上角 y 坐标小于 centers 中每个点的 y 坐标 + m1 = (rect[0] < centers[:, 0]) * (rect[1] < centers[:, 1]) + # 矩形 rect 的右下角角 x 坐标大于 centers 中每个点的 x 坐标且右下角 y 坐标大于 centers 中每个点的 y 坐标 + m2 = (rect[2] > centers[:, 0]) * (rect[3] > centers[:, 1]) + + # mask为布尔数组,要求m1,m2和drop_mask都为True + mask = m1 * m2 * drop_mask + + # 如果没有合格的候选框,重新开始循环 + if not mask.any(): + continue + + # 只对满足条件的 boxes 进行处理,将其转换为所需的格式 + # 复制一份新的 boxes,避免修改原始数据 + boxes_t = boxes[mask, :].copy() + # 将 boxes 中的 x1 和 y1 坐标分别与 rect 的 x1 和 y1 坐标进行比较,取较大值作为新的 x1 和 y1 坐标 + boxes_t[:, :2] = np.maximum(boxes_t[:, :2], rect[:2]) + # 将 boxes 中的 x1 和 y1 坐标减去 rect 的 x1 和 y1 坐标,得到相对于 rect 左上角的位置 + boxes_t[:, :2] -= rect[:2] + # 将 boxes 中的 x2 和 y2 坐标分别与 rect 的 x2 和 y2 坐标进行比较,取较小值作为新的 x2 和 y2 坐标 + boxes_t[:, 2:4] = np.minimum(boxes_t[:, 2:4], rect[2:4]) + # 将 boxes 中的 x2 和 y2 坐标减去 rect 的 x1 和 y1 坐标,得到相对于 rect 左上角的位置 + boxes_t[:, 2:4] -= rect[:2] + + # 返回处理后的图像和 boxes + return image_t, boxes_t + # 返回原始的图像和 boxes + return image, boxes + +def yolo_bboxes_encode(boxes): + """ + Labels anchors with ground truth inputs. + 用于对 YOLO 模型中的锚点进行编码。 + """ + + def jaccard_with_anchors(bbox): + """ + Compute jaccard score a box and the anchors. + 用于计算锚点和真实边界框之间的交并比(IOU) + """ + # Intersection bbox and volume. + # 计算锚点 y1 和真实边界框 bbox[0] 之间的最大值,作为交集区域的左上角 ymin + ymin = np.maximum(y1, bbox[0]) + # 计算锚点 x1 和真实边界框 bbox[1] 之间的最大值,作为交集区域的左上角 xmin + xmin = np.maximum(x1, bbox[1]) + # 计算锚点 y2 和真实边界框 bbox[2] 之间的最小值,作为交集区域的右下角 ymax + ymax = np.minimum(y2, bbox[2]) + # 计算锚点 x2 和真实边界框 bbox[3] 之间的最小值,作为交集区域的右下角 xmax + xmax = np.minimum(x2, bbox[3]) + # 计算交集区域的宽度 w + w = np.maximum(xmax - xmin, 0.) + # 计算交集区域的高度 h + h = np.maximum(ymax - ymin, 0.) + + # Volumes. + # 计算交集面积 + inter_vol = h * w + # 计算并集面积 + union_vol = vol_anchors + (bbox[2] - bbox[0]) * (bbox[3] - bbox[1]) - inter_vol + # 计算交并比 + jaccard = inter_vol / union_vol + # 把数组压缩为单个值,并返回 + return np.squeeze(jaccard) + + # 存储预测分数 + pre_scores = np.zeros((8732), dtype=np.float32) + # 存储锚点标签 + t_boxes = np.zeros((8732, 4), dtype=np.float32) + # 存储候选锚点 + t_label = np.zeros((8732), dtype=np.int64) + # 遍历输入的边界框 + for bbox in boxes: + # 获取边界框标签 + label = int(bbox[4]) + # 计算与锚点的交并比 + scores = jaccard_with_anchors(bbox) + # 找到具有最高匹配分数的锚点 + idx = np.argmax(scores) + # 并将该锚点的匹配分数设置为2.0。 + scores[idx] = 2.0 + # 创建一个掩码,用于过滤出分数大于匹配阈值的锚点 + mask = (scores > matching_threshold) + # 过滤出分数大于前一次匹配分数的锚点 + mask = mask & (scores > pre_scores) + # 将 pre_scores 中分数小于当前锚点分数的锚点的分数设置为当前锚点分数 + pre_scores = np.maximum(pre_scores, scores * mask) + # 将 t_label 中分数小于当前锚点分数的锚点的标签设置为当前锚点标签 + t_label = mask * label + (1 - mask) * t_label + # 遍历4个坐标轴(i从0到3),分别更新每个坐标轴的值 + for i in range(4): + # 对于每个坐标轴,如果该坐标轴在当前锚点的掩码(mask)中为真,则使用当前锚点 + # 的坐标值更新该坐标轴;否则,使用候选锚点的坐标值更新该坐标轴 + t_boxes[:, i] = mask * bbox[i] + (1 - mask) * t_boxes[:, i] + + # 找到标签(t_label)中不为0的元素的位置,并存储在index + index = np.nonzero(t_label) + + # 将候选锚点的坐标(t_boxes)从矩形的左上角和右下角坐标转换为矩形的左下角和右上角坐标(tlbr) + # 创建一个大小为8732x4的空数组(bboxes),用于存储转换后的坐标 + bboxes = np.zeros((8732, 4), dtype=np.float32) + # 然后,将 t_boxes 中的左上角坐标和右下角坐标相加,除以2,得到转换后的左下角坐标 + bboxes[:, [0, 1]] = (t_boxes[:, [0, 1]] + t_boxes[:, [2, 3]]) / 2 + # 将 t_boxes 中的右下角坐标和左上角坐标相减,得到转换后的右上角坐标 + bboxes[:, [2, 3]] = t_boxes[:, [2, 3]] - t_boxes[:, [0, 1]] + + # Encode features. + # 编码候选锚点的坐标(bboxes)和默认锚点的坐标(default_boxes),以便输入到后续的损失函数中 + # 提取候选锚点位置 + bboxes_t = bboxes[index] + # 提取默认锚点位置 + default_boxes_t = default_boxes[index] + # 计算偏移量相对于默认锚点缩放后的结果,被除数为偏移量 + bboxes_t[:, :2] = (bboxes_t[:, :2] - default_boxes_t[:, :2]) / (default_boxes_t[:, 2:] * 0.1) + # 对缩放因子进行非负数处理,如果为负数或0,则将其值置为0.000001 + tmp = np.maximum(bboxes_t[:, 2:4] / default_boxes_t[:, 2:4], 0.000001) + # 对缩放因子进行取对数处理 + bboxes_t[:, 2:4] = np.log(tmp) / 0.2 + # 将编码后的宽度和高度赋值回 bboxes 中原来的位置 + bboxes[index] = bboxes_t + + # 得到候选锚点匹配的标签数量,并将匹配的标签、锚点坐标和匹配数量作为返回值返回 + num_match = np.array([len(np.nonzero(t_label)[0])], dtype=np.int32) + return bboxes, t_label.astype(np.int32), num_match + +def preprocess_fn(img_id, image, box, is_training): + """ + Preprocess function for dataset. + 对输入的图像和标签进行预处理 + + 参数: + img_id:图像 ID + image:图像 + box:图像中的框 + is_training:当前是否在训练阶段 + """ + # 设置 OpenCV 库的线程数量为 2,以提高处理速度 + cv2.setNumThreads(2) + + # 对输入的图像进行预处理,使其符合输入网络的尺寸要求 + def _infer_data(image, input_shape): + # 计算图像的宽度和高度 + img_h, img_w, _ = image.shape + # 计算网络输入的尺寸 + input_h, input_w = input_shape + + # 将图像调整为网络输入的尺寸,将结果存储在 image 中 + image = cv2.resize(image, (input_w, input_h)) + + # 如果图像的通道数为 1,则将其扩展为三通道 + # 因为在 YOLOv7 中,输入的图像需要为三通道的 RGB 图像 + if len(image.shape) == 2: + image = np.expand_dims(image, axis=-1) + image = np.concatenate([image, image, image], axis=-1) + + # 将图像 ID、处理后的图像和图像尺寸作为元组返回 + return img_id, image, np.array((img_h, img_w), np.float32) + + # 对输入的图像和标签进行数据增强操作 + def _data_aug(image, box, is_training, image_size=(300, 300)): + # 计算图像的宽度和高度 + ih, iw, _ = image.shape + # 定义图像大小,image_size=(300,300) + h, w = image_size + # 如果当前处于预测阶段 + if not is_training: + # 调用 _infer_data 对图像进行预处理 + return _infer_data(image, image_size) + # 将图像和标签转换为浮点数类型 + box = box.astype(np.float32) + # 进行随机裁剪 + image, box = random_sample_crop(image, box) + # 计算裁剪后的图像尺寸 + ih, iw, _ = image.shape + # 对图像进行缩放 + image = cv2.resize(image, (w, h)) + # 布尔值,判断是否进行翻转 + flip = _rand() < .5 + # 如果随机数小于0.5 + if flip: + # 进行翻转,增加数据集的多样性 + image = cv2.flip(image, 1, dst=None) + # 如果图像的通道数为 1,则将其扩展为三通道 + if len(image.shape) == 2: + image = np.expand_dims(image, axis=-1) + image = np.concatenate([image, image, image], axis=-1) + # 对框进行归一化处理,将坐标值归一化到 [0, 1] 范围内 + box[:, [0, 2]] = box[:, [0, 2]] / ih + box[:, [1, 3]] = box[:, [1, 3]] / iw + # 如果进行了翻转 + if flip: + # 将框的 y1 和 y2 坐标交换 + box[:, [1, 3]] = 1 - box[:, [3, 1]] + # 对标签进行编码处理,使用 YOLO 算法对框进行编码 + box, label, num_match = yolo_bboxes_encode(box) + # 返回处理后的图像、框、标签和匹配数量 + return image, box, label, num_match + + # 调用内部函数 _data_aug 返回数据增强后的结果 + return _data_aug(image, box, is_training, image_size=[300, 300]) + +""" +数据集创建部分 +""" + +from mindspore import Tensor +from mindspore.dataset import MindDataset +from mindspore.dataset.vision import Decode, HWC2CHW, Normalize, RandomColorAdjust + + +def create_yolo_dataset(mindrecord_file, batch_size=32, device_num=1, rank=0, + is_training=True, num_parallel_workers=1, use_multiprocessing=True): + """ + Create YOLO dataset with MindDataset. + 用于创建 YOLO 数据集。 + + 参数: + mindrecord_file:MindRecord 文件路径,用于加载数据集。 + batch_size:批次大小,即每批数据的样本数。 + device_num:设备数量,即使用的 GPU 数量。 + rank:当前设备编号,用于多 GPU 训练时区分数据。 + is_training:布尔值,表示当前是否处于训练阶段。 + num_parallel_workers:并行工作数量,用于数据预处理时的并发处理。 + use_multiprocessing:布尔值,表示是否使用多进程处理数据。 + """ + # 使用MindDataset类创建一个名为dataset的对象 + dataset = MindDataset(mindrecord_file, columns_list=["img_id", "image", "annotation"], num_shards=device_num, + shard_id=rank, num_parallel_workers=num_parallel_workers, shuffle=is_training) + + # 使用Decode类,创建一个名为decode的解码操作对象 + decode = Decode() + # 使用 map 方法将解码操作应用于数据集中的 "image" 列 + # 数据集中的图像数据就会被解码,可以进行后续的处理和训练 + dataset = dataset.map(operations=decode, input_columns=["image"]) + + # 定义了一个变换操作,将图像的HWC格式转换为CHW格式 + change_swap_op = HWC2CHW() + # 数据处理操作是根据随机抽样过的ImageNet训练图像计算 + # 定义了一个数据处理操作,对图像进行归一化处理 + normalize_op = Normalize(mean=[0.485 * 255, 0.456 * 255, 0.406 * 255], + std=[0.229 * 255, 0.224 * 255, 0.225 * 255]) + # 定义了一个数据处理操作,对图像进行随机色彩调整 + color_adjust_op = RandomColorAdjust(brightness=0.4, contrast=0.4, saturation=0.4) + # 定义了一个组合处理函数,它将多个数据处理操作组合在一起 + compose_map_func = (lambda img_id, image, annotation: preprocess_fn(img_id, image, annotation, is_training)) + + # 如果当前正在训练 + if is_training: + # 在训练过程中,返回四个字段的数据 + output_columns = ["image", "box", "label", "num_match"] + # 在训练过程中,会对图像进行色彩调整、归一化和格式转换等操作 + trans = [color_adjust_op, normalize_op, change_swap_op] + # 如果当前不在训练 + else: + # 在验证或测试过程中,需要返回这三个字段的数据 + output_columns = ["img_id", "image", "image_shape"] + # 在验证或测试过程中,会对图像进行归一化和格式转换等操作 + trans = [normalize_op, change_swap_op] + + # 对数据集应用不同的数据处理操作 + dataset = dataset.map(operations=compose_map_func, input_columns=["img_id", "image", "annotation"], + output_columns=output_columns, python_multiprocessing=use_multiprocessing, + num_parallel_workers=num_parallel_workers) + + dataset = dataset.map(operations=trans, input_columns=["image"], python_multiprocessing=use_multiprocessing, + num_parallel_workers=num_parallel_workers) + + # 将数据集按照指定的批次大小进行打包 + dataset = dataset.batch(batch_size, drop_remainder=True) + # 将数据集返回 + return dataset + + +# 创建一个名为concat的ops.Concat对象,其中axis=-1表示沿最后一个维度进行连接 +concat = ops.Concat(axis=-1) +# 将DetectionBlock对象(模型网络构建中的框生成函数)应用到scale='l'上,并将结果赋值给xxxx变量 +xxxx = DetectionBlock(scale='l').xxxx +# 将DetectionBlock对象应用到scale='l'上,并将结果赋值给xxx变量 +xxx = DetectionBlock(scale='l').xxx +# 将xxx赋值给box_xywh变量 +box_xywh = xxx +# 将box_xywh赋值给default_boxes变量 +default_boxes = box_xywh + +# 从box_xywh中提取宽度的一半,并将结果赋值给x1 +x1 = box_xywh[..., 0:1] - box_xywh[..., 2:3] / 2 +# 从box_xywh中提取高度的一半,并将结果赋值给y1 +y1 = box_xywh[..., 1:2] - box_xywh[..., 3:4] / 2 +# 从box_xywh中提取宽度的一半,并将结果赋值给x2 +x2 = box_xywh[..., 0:1] + box_xywh[..., 2:3] / 2 +# 从box_xywh中提取高度的一半,并将结果赋值给y2 +y2 = box_xywh[..., 1:2] + box_xywh[..., 3:4] / 2 +# 计算预测框的体积,即将x2和x1之间的宽度乘以y2和y1之间的高度 +vol_anchors = (x2 - x1) * (y2 - y1) +# 将matching_threshold设置为0.5 +matching_threshold = 0.5 + +""" +以下为神经网络模型构建部分 +""" + +import mindspore as ms +import mindspore.nn as nn +import mindspore.ops as ops +import numpy as np +""" +1.导入所需的库和模块: + mindspore:MindSpore是一个全场景深度学习框架,用于训练和推理。 + nn:MindSpore的神经网络模块。 + ops:MindSpore的运算模块。 + numpy:用于处理数值计算。 + +2.从src文件夹中的loss文件夹中导入两个损失函数: + ConfidenceLoss:用于计算置信度损失。 + ClassLoss:用于计算分类损失。 + +3.从model_utils文件夹中的config文件夹中导入一个配置对象: + config:包含了一些配置参数,如学习率、batch size等。 +""" + +from src.loss import ConfidenceLoss, ClassLoss + +from model_utils.config import config as default_config + +# yolo v7 神经网络的构建,便于读者理解的版本 +class Yolov7(nn.Cell): + def __init__(self, x): + self.config = default_config + self.config.out_channel = (self.config.num_classes + 5) * 3 + # Yolov7 backbone: block1~4 + # CBS-2卷积模块 + self.b1 = CBS2(3, 64) + # CBS-3卷积模块 + self.b2 = CBS3(64, 64) + # CBS-2卷积模块 + self.b3 = CBS2(64, 128) + # CBS-3卷积模块 + self.b4 = CBS3(128, 128) + # 创建一个Elan神经网络模块,输入维度为128 + self.elanA = Elan(128) + # 创建一个MP1神经网络模块,输入维度为256 + self.mp1A = MP1(256) + self.elanB = Elan(256) + # CBS-1卷积模块 + self.bout1 = CBS1(512, 128) + + self.mp1B = MP1(512) + self.elanC = Elan(512) + # CBS-1卷积模块 + self.bout2 = CBS1(1024, 256) + + self.mp1C = MP1(1024) + self.elanD = Elan(1024) + # 创建一个SPPCSPC神经网络模块,输入维度为1024 + self.sppcspc = SPPCSPC() + # upsample操作 + self.upA = ops.interpolate(input, size=(20,), mode='nearest') + # 创建一个Elan-E神经网络模块,输入维度为512 + self.elanWA = ElanW(512) + self.upB = ops.interpolate(input, size=(40,), mode='nearest') + self.elanWB = ElanW(256) + # 创建一个MP2神经网络模块,输入维度为128 + self.mp2A = MP2(128) + self.elanWC = ElanW(512) + self.mp2B = MP2(256) + self.elanWD = ElanW(1024) + # 创建一个REP神经网络模块,输入维度为128 + self.repA = REP(128) + # 创建一个CBM神经网络模块,输入维度为256 + self.cbmA = CBM(256) + self.repB = REP(256) + self.cbmB = CBM(512) + self.repC = REP(512) + self.cbmC = CBM(1024) + # 定义三个不同的输出维度 + shape3 = [20, 20, (80 + 5)] + shape4 = [40, 40, (80 + 5)] + shape5 = [80, 80, (80 + 5)] + # 然后,使用YoloBlock类创建了三个YoloBlock实例,并以刚刚创建的形状为输入 + self.back_block1 = YoloBlock(shape3, 3) + self.back_block2 = YoloBlock(shape4, 3) + self.back_block3 = YoloBlock(shape5, 3) + + # 定义concat操作,会在神经网络层之间用到 + self.concat = ops.Concat(axis=1) + + def construct(self, x): + # 创建yolo v7神经网络,以下为创建的代码 + # headbone1-4 + x = self.b1(x) + block1 = x + x = self.b2(x) + block2 = x + x = self.b3(x) + block3 = x + x = self.b4(x) + block4 = x + # elan+mp1 + block5,x = self.elanA(x) + block6,x = self.mp1A(x) + block7,x = self.elanB(x) + xout1 = self.bout1(block7) + block9,x = self.mp1B(x) + block10,x = self.elanC(x) + xout2 = self.bout2(block10) + block12,x = self.mp1C(x) + block13,x = self.elanD(x) + # 进入head模块 + block14,x = self.sppcspc(x) + # UpSample函数 + block15,x = self.upA(x) + # 与C3合并 + x = ops.cat((x, xout1)) + block16,x = self.elanWA(x) + # UpSample函数 + block17,x = self.upB(x) + # 与C4合并 + x = ops.cat((x, xout2)) + block18,x = self.elanWB(x) + # first output,shape3 = [20, 20, (80 + 5)],第一输出,形状为shape3 + block19 = self.repA(x) + block20 = self.cbmA(block19) + + block21,x = self.mp2A(x) + x = ops.cat((x, block16)) + block22,x = self.elanWC(x) + # second output,shape4 = [40, 40, (80 + 5)],第二输出,形状为shape4 + block23 = self.repB(x) + block24 = self.cbmB(block23) + + block25,x = self.mp2B(x) + x = ops.cat((x, block14)) + block26,x = self.elanWD(x) + # third output,shape5 = [80, 80, (80 + 5)],第三输出,形状为shape5 + block27 = self.repC(x) + block28 = self.cbmC(block27) + + # boxes,主要用于将YoloBlock的输出传递给后续处理 + small_object_output = self.back_block1(block20) + medium_object_output = self.back_block2(block24) + big_object_output = self.back_block3(block28) + # 返回三个输出 + return small_object_output, medium_object_output, big_object_output + +# blocks: Elan +class Elan(nn.Cell): + # Elan 神经网络模块的构建 + def __init__(self, x): + # 定义了6个计算层(b0-b6),每个计算层由一个或多个计算块组成。计算块的名称是CBS(Convolutional Block Structure),表示它们是基于卷积的块结构 + super(Elan, self).__init__() + self.b0 = CBS1(x, x/2) + self.b1 = CBS1(x, x/2) + self.b2 = CBS2(x/2, x/2) + self.b3 = CBS2(x/2, x/2) + self.b4 = CBS2(x/2, x/2) + self.b5 = CBS2(x/2, x/2) + self.b6 = CBS1(2*x, 2*x) + + def construct(self, x): + # 首先,将输入张量x传递给第一个计算层b0。然后,将输出张量x传递给下一个计算层b1,并将其存储在block1变量中。 + # 接着,将输入张量x传递给下一个计算层b2,并将其存储在block2变量中。以此类推,直到构建出所有计算层 + x0 = x + x0 = self.b0(x0) + x = self.b1(x) + block1 = x + x = self.b2(x) + block2 = x + x = self.b3(x) + block3 = x + x = self.b4(x) + block4 = x + x = self.b5(x) + block5 = x + # 最后,将block1、block3和block5连接在一起,并将结果传递给b6计算层。b6计算层的输出就是Elan模块的最终输出 + x = ops.cat((x0, block1, block3, block5)) + x = self.b6(x) + block6 = x + return x + +class MP1(nn.Cell): + # MP1 神经网络模块的构建 + def __init__(self, x): + # MP1模块的输入是一个张量x,输出也是张量x + super(MP1, self).__init__() + # 首先,将输入张量x传递给第一个计算层b0_1,它是一个2x2的最大池化操作,用于减少特征图的尺寸 + self.b0_1 = ms.nn.MaxPool2d(kernel_size=2, stride=2) + # 定义了4个计算层(b0-b3),每个计算层由一个或多个计算块组成。计算块的名称是CBS(Convolutional Block Structure),表示它们是基于卷积的块结构 + self.b0_2 = CBS1(x, x/2) + self.b1 = CBS1(x, x/2) + self.b2 = CBS3(x/2, x/2) + + def construct(self, x): + x0 = x + # x0为池化线路的计算值 + x0 = self.b0_1(x0) + x0 = self.b0_2(x0) + # x为另一线路的计算值 + x = self.b1(x) + block1 = x + x = self.b2(x) + block2 = x + # 合并两线路的值 + x = ops.cat((x0, block2)) + return x + +class MP2(nn.Cell): + # MP2 神经网络模块的构建 + def __init__(self, x): + # MP1模块的输入是一个张量x,输出是张量2x + super(MP2, self).__init__() + # 首先,将输入张量x传递给第一个计算层b0_1,它是一个2x2的最大池化操作,用于减少特征图的尺寸 + self.b0_1 = ms.nn.MaxPool2d(kernel_size=2, stride=2) + # 定义了4个计算层(b0-b3),每个计算层由一个或多个计算块组成。计算块的名称是CBS(Convolutional Block Structure),表示它们是基于卷积的块结构 + self.b0_2 = CBS1(x, x) + self.b1 = CBS1(x, x) + self.b2 = CBS3(x, x) + + def construct(self, x): + x0 = x + # x0为池化线路的计算值 + x0 = self.b0_1(x0) + x0 = self.b0_2(x0) + # x为另一线路的计算值 + x = self.b1(x) + block1 = x + x = self.b2(x) + block2 = x + # 合并两线路的值 + x = ops.cat((x0, block2)) + return x + +# 定义SPPCSPC类,用于构建SPPCSPC神经网络模块 +class SPPCSPC(nn.Cell): + # SPPCSPC 神经网络模块的构建,它包含了一些基本操作,如卷积层、残差模块(Residual Module)和最大池化层 + def __init__(self): + super(SPPCSPC, self).__init__() + # 定义第一个CBS1模块 + self.b0 = CBS1(1024, 1024) + # 定义第二个CBS1模块 + self.b1 = CBS1(1024, 512) + # 定义第三个CBS2模块 + self.b2 = CBS2(512, 512) + # 定义第四个CBS1模块 + self.b3 = CBS1(512, 512) + # 定义第一个池化层 + self.bpool1 = ms.nn.MaxPool2d(kernel_size=5, stride=1 ,padding = 5 //2 ) + # 定义第二个池化层 + self.bpool2 = ms.nn.MaxPool2d(kernel_size=9, stride=1 ,padding = 9 //2 ) + # 定义第三个池化层 + self.bpool3 = ms.nn.MaxPool2d(kernel_size=13, stride=1 ,padding = 13 //2 ) + # 定义第五个CBS1模块 + self.b4 = CBS1(2048, 512) + # 定义第六个CBS2模块 + self.b5 = CBS2(512, 512) + # 定义第七个CBS1模块 + self.b6 = CBS1(512, 512) + + def construct(self, x): + # 将输入x赋值给x0,在这个方法中,我们首先将输入数据x传递给x0,然后对x0进行卷积操作(self.b0) + x0 = x + # 执行第一个CBS1模块 + x0 = self.b0(x0) + # 然后,我们将输入数据x,并使用残差模块(self.b1)对x进行处理。接着,我们使用最大池化层(self.bpool1)对x进行处理 + x = self.b1(x) + # 将x的值赋值给block1 + block1 = x + # 执行第二个CBS2模块 + x = self.b2(x) + # 将x的值赋值给block2 + block2 = x + # 执行第三个CBS1模块 + x = self.b3(x) + # 将x的值赋值给block3 + block3 = x + # 执行第一个池化层 + xp1 = self.bpool1(x) + # 执行第二个池化层 + xp2 = self.bpool2(x) + # 执行第三个池化层 + xp3 = self.bpool3(x) + # 将block3、xp1、xp2、xp3的值拼接起来 + x = ops.cat((block3, xp1, xp2, xp3)) + # 执行第五个CBS1模块 + x = self.b4(x) + # 将x的值赋值给block4 + block4 = x + # 执行第六个CBS2模块 + x = self.b5(x) + # 将x的值赋值给block5 + block5 = x + # 将x0和x拼接起来 + x = ops.cat((x0, x)) + # 执行第七个CBS1模块 + x = self.b6(x) + # 将x的值赋值给block6 + block6 = x + # 返回计算后x + return x + + +# 定义 ElanW 类,继承自 nn.Cell +class ElanW(nn.Cell): + # ElanW 神经网络模块的构建 + def __init__(self, x): + super(ElanW, self).__init__() + # 构建第一个卷积层 + self.b0 = CBS1(x, x/2) + # 构建第二个卷积层 + self.b1 = CBS1(x, x/2) + # 构建第三个卷积层 + self.b2 = CBS2(x/2, x/4) + # 构建第四个卷积层 + self.b3 = CBS2(x/4, x/4) + # 构建第五个卷积层 + self.b4 = CBS2(x/4, x/4) + # 构建第六个卷积层 + self.b5 = CBS2(x/4, x/4) + # 构建第七个卷积层 + self.b6 = CBS1(2*x, x/2) + + def construct(self, x): + # 保存输入 + x0 = x + # 第一个卷积层 + x0 = self.b0(x0) + # 第二个卷积层 + x = self.b1(x) + # 保存第二个卷积层的输出 + block1 = x + # 第三个卷积层 + x = self.b2(x) + # 保存第三个卷积层的输出 + block2 = x + # 第四个卷积层 + x = self.b3(x) + # 保存第四个卷积层的输出 + block3 = x + # 第五个卷积层 + x = self.b4(x) + # 保存第五个卷积层的输出 + block4 = x + # 第六个卷积层 + x = self.b5(x) + # 保存第六个卷积层的输出 + block5 = x + # 将输入和输出拼接 + x = ops.cat((x0, block1, block2, block3, block4, block5)) + # 第七个卷积层 + x = self.b6(x) + # 保存第七个卷积层的输出 + block6 = x + # 返回拼接后的输出 + return x + +# 定义REP类,用于构建REP神经网络模块 +class REP(nn.Cell): + # REP 神经网络模块的构建 + def __init__(self, x): + super(REP, self).__init__() + # 构建第一个CBS1模块,输入为x,输出为x/2 + self.b1_1 = CBS1(x, x/2) + # 构建第一个BatchNorm2d模块,输入为x/2,输出为x/2 + self.b1_2 = nn.BatchNorm2d(x/2) + # 构建第二个CBS2模块,输入为x,输出为x/2 + self.b2_1 = CBS2(x,x/2) + # 构建第二个BatchNorm2d模块,输入为x/2,输出为x/2 + self.b2_2 = nn.BatchNorm2d(x/2) + # 构建第三个BatchNorm2d模块,输入为x,输出为x + self.b3_1 = nn.BatchNorm2d(x) + + # deploy过程所用卷积层 + self.deployb_1 = CBS2(x,x/2) + self.deployb_2 = nn.BatchNorm2d(x/2) + + + def construct(self, x): + # 将输入x赋值给x1,x2,x3 + x1=x + x2=x + x3=x + # 调用第一个CBS1模块,输入为x1,输出为x1 + x1 = self.b1_1(x1) + # 调用第一个BatchNorm2d模块,输入为x1,输出为x1 + x1 = self.b1_2(x1) + # 将x1赋值给block1 + block1 = x1 + # 调用第二个CBS2模块,输入为x2,输出为x2 + x2 = self.b2_1(x2) + # 调用第二个BatchNorm2d模块,输入为x2,输出为x2 + x2 = self.b2_2(x2) + # 将x2赋值给block2 + block2 = x2 + # 调用第三个BatchNorm2d模块,输入为x3,输出为x3 + x3 = self.b3_1(x3) + # 将x3赋值给block3 + block3 = x3 + # 将block1,block2,block3相加,输出x维度为初始的两倍 + x = np.add(block1, block2, block3) + # 返回x + return x + + # 推理模块,包含一个3x3的卷积,stride(步长为1)。是由训练模块重参数化转换而来。 + def deploy(self,x): + # 调用CBS2模块,输入为x,输出维度为1/2 + x = self.deployb_1(x) + # 调用BatchNorm2d模块,输入为x,输出为x + x = self.deployb_2(x) + + return x + + +# 定义CBM类,用于构建CBM神经网络模块 +class CBM(nn.Cell): + # CBM 神经网络模块的构建 + def __init__(self, x): + super(CBM, self).__init__() + # 定义第一个卷积层,输入通道数为x,输出通道数为x,卷积核大小为1,填充为1,dilation为6,pad_mode为pad + self.b1_1 = nn.Conv2d(in_channels=x, out_channels=x,kernel_size=1, padding=1, dilation=6, pad_mode='pad') + # 定义第一个batchnorm层,输入通道数为x + self.b1_2 = nn.BatchNorm2d(x) + # 定义第一个sigmoid层 + self.b1_3 = nn.Sigmoid() + + + def construct(self, x): + # 执行第一个卷积层 + x = self.b1_1(x) + # 执行第一个batchnorm层 + x = self.b1_2(x) + # 执行第一个sigmoid层 + x = self.b1_3(x) + # 将x赋值给block1 + block1 = x + # 返回x + return x + +class CBS1(nn.Cell): + # 这里对应结构图部分的CBS1,CBS = conv+BN+SiLU + def __init__(self, c1, c2): # channels_in, channels_out + super().__init__() + # 卷积层,用于提取特征,卷积核大小为1,步长为1 + self.conv = nn.Conv2d(in_channels=c1,out_channels=c2,kernel_size=1, padding=1, dilation=6, pad_mode='pad') + # 批量归一化层,用于对输入数据进行归一化处理,使得数据的均值为 0,方差为 1的正态分布,通道数为c2 + self.bn = nn.BatchNorm2d(c2) + # 激活函数层,用于对输出数据进行激活处理 + self.sil = nn.SiLU() + # nn.SiLU()一种激活函数(S形加权线性单元)。 + + def construct(self, x): + # 将输入依次传入三个不同的层 + x = self.conv(x) + x = self.bn(x) + x = self.sil(x) + return x + +class CBS2(nn.Cell): + # 这里对应结构图部分的CBS2,CBS = conv+BN+SiLU + def __init__(self, c1, c2): # channels_in, channels_out + super().__init__() + # 卷积层,用于提取特征,卷积核大小为3,步长为1 + self.conv = nn.Conv2d(in_channels=c1,out_channels=c2,kernel_size=3, padding=1, dilation=6, pad_mode='pad') + # 批量归一化层,用于对输入数据进行归一化处理,使得数据的均值为 0,方差为 1的正态分布,通道数为c2 + self.bn = nn.BatchNorm2d(c2) + # 激活函数层,用于对输出数据进行激活处理 + self.sil = nn.SiLU() + # nn.SiLU()一种激活函数(S形加权线性单元)。 + + def construct(self, x): + # 将输入依次传入三个不同的层 + x = self.conv(x) + x = self.bn(x) + x = self.sil(x) + return x + +class CBS3(nn.Cell): + # 这里对应结构图部分的CBS3,CBS = conv+BN+SiLU + def __init__(self, c1, c2): # channels_in, channels_out + super().__init__() + # 卷积层,用于提取特征,卷积核大小为3,步长为2 + self.conv = nn.Conv2d(in_channels=c1,out_channels=c2,kernel_size=3, padding=2, dilation=6, pad_mode='pad') + # 批量归一化层,用于对输入数据进行归一化处理,使得数据的均值为 0,方差为 1的正态分布,通道数为c2 + self.bn = nn.BatchNorm2d(c2) + # 激活函数层,用于对输出数据进行激活处理 + self.sil = nn.SiLU() + # nn.SiLU()一种激活函数(S形加权线性单元)。 + + def construct(self, x): + # 将输入依次传入三个不同的层 + x = self.conv(x) + x = self.bn(x) + x = self.sil(x) + return x + + +""" +定义了一个名为YoloBlock的类,该类继承自nn.Cell。YoloBlock类用于构建YOLOv7中的一个块,该块包含一个卷积层。 + +YoloBlock类的构造函数接受两个参数:in_channels(整数类型)和out_channels(整数类型)。这些参数用于设置卷积层的输入和输出通道数。 + +YoloBlock类的construct方法用于构建卷积层,并返回结果 +""" +class YoloBlock(nn.Cell): + """ + YoloBlock for YOLOv7. + + Args: + in_channels: Integer. Input channel. + out_channels: Integer. Output channel. + + Returns: + Tuple, tuple of output tensor,(f1,f2,f3). + + Examples: + YoloBlock(12, 255) + + """ + def __init__(self, in_channels, out_channels): + super(YoloBlock, self).__init__() + + self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, has_bias=True) + + def construct(self, x): + """construct method""" + + out = self.conv(x) + return out + +""" +这段代码定义了一个名为DetectionBlock的类,该类继承自nn.Cell。DetectionBlock类用于构建YOLOv7检测网络,这个类包含一些用于初始化DetectionBlock的属性,以及一些用于在预测时调整边界框大小的方法。用于最终输出检测结果。 + +DetectionBlock类的构造函数接受三个参数:scale(字符串类型),config(配置类类型,默认为default_config)和is_training(布尔类型,默认为True)。这些参数用于设置检测网络的参数。 + +首先对输入特征x进行reshape和transpose操作,使其具有正确的形状。然后,它计算网格坐标grid_x和grid_y,并将它们拼接在一起。接下来,它计算边界框box_xy、box_wh和box_confidence以及box_probs。最后,根据self.conf_training的值,如果为True,则返回预测结果;否则返回拼接后的结果 + +类中xxx参数保存了检测框的坐标信息,在数据处理过程中会用到,xxxx保存了该函数计算的全部信息,同样也将在数据处理过程中用到。 +""" +class DetectionBlock(nn.Cell): + """ + YOLOv7 detection Network. It will finally output the detection result. + + Args: + scale: Character. + config: config, Configuration instance. + is_training: Bool, Whether train or not, default True. + + Returns: + Tuple, tuple of output tensor,(f1,f2,f3). + + Examples: + DetectionBlock(scale='l',stride=32) + """ + + def __init__(self, scale, config=default_config, is_training=True): + super(DetectionBlock, self).__init__() + self.config = config + # 代码定义了三个常量,用于确定要使用的边界框大小。这些常量分别表示小、中和大尺度的边界框大小。然后,代码定义了这些尺度的x和y轴比例以及偏移量。 + if scale == 's': + # idx常量,用于存储要使用的边界框大小 + idx = (0, 1, 2) + # x和y轴比例 + self.scale_x_y = 1.2 + # 偏移量 + self.offset_x_y = 0.1 + elif scale == 'm': + idx = (3, 4, 5) + self.scale_x_y = 1.1 + self.offset_x_y = 0.05 + elif scale == 'l': + idx = (6, 7, 8) + self.scale_x_y = 1.05 + self.offset_x_y = 0.025 + else: + # 如果scale为其他参数,则抛出KeyError,提示不合法 + raise KeyError("Invalid scale value for DetectionBlock") + # anchors的成员变量,它是一个张量包含锚定框的各项信息,包含要使用的锚点的大小。这些锚点的大小是由config文件中anchor_scales[i]计算的, + # 其中i是idx中的一个索引。anchors的类型为ms.float32 + self.anchors = ms.Tensor([self.config.anchor_scales[i] for i in idx], ms.float32) + # 接下来,代码定义了两个名为num_anchors_per_scale和num_attrib的成员变量,分别表示每个尺度的锚点数量和预测的属性数量 + self.num_anchors_per_scale = 3 + self.num_attrib = 4+1+self.config.num_classes + # 然后,代码定义了一个名为lambda_coord的成员变量,它表示预测的坐标值的重要性 + self.lambda_coord = 1 + """ + 以下代码用于定义计算所需函数操作 + """ + + # 接下来,代码定义了四个成员变量,分别表示sigmoid激活函数、reshape操作、tile操作和concat操作。这些操作将在预测过程中使用 + self.sigmoid = nn.Sigmoid() + self.reshape = ops.Reshape() + self.tile = ops.Tile() + self.concat = ops.Concat(axis=-1) + self.pow = ops.Pow() + self.transpose = ops.Transpose() + self.exp = ops.Exp() + # 定义了两个参数分别用于存储xy,wh坐标,以及全部参数 + self.xxx = ms.Tensor(None, ms.float32) + self.xxxx = ms.Tensor(None, ms.float32) + # 最后,代码定义了一个名为conf_training的成员变量,它表示是否在训练过程中使用置信度损失 + self.conf_training = is_training + + def construct(self, x, input_shape): + """construct method""" + # 首先获取输入张量的batch大小(num_batch)和特征图的网格大小(grid_size) + num_batch = x.shape[0] + grid_size = x.shape[2:4] + """ + 以下代码用于计算边界框 + """ + # Reshape and transpose the feature to [n, grid_size[0], grid_size[1], 3, num_attrib] + # 然后,它将输入张量(x)重塑为一个新的张量,以便在后面的计算中使用。 + # 这个新的张量具有以下形状:[num_batch, grid_size[0], grid_size[1], 3, num_attrib]。其中,num_attrib表示每个网格单元的预测属性数量 + prediction = self.reshape(x, (num_batch, + self.num_anchors_per_scale, + self.num_attrib, + grid_size[0], + grid_size[1])) + prediction = self.transpose(prediction, (0, 3, 4, 1, 2)) + + # 接下来,代码使用self.reshape和self.transpose函数对特征张量进行重新形状和转置 + grid_x = ms.numpy.arange(grid_size[1]) + grid_y = ms.numpy.arange(grid_size[0]) + # Tensor of shape [grid_size[0], grid_size[1], 1, 1] representing the coordinate of x/y axis for each grid + # [batch, gridx, gridy, 1, 1] + + # self.reshape函数将x和y坐标从一维数组转换为具有五个维度的张量,以便在后面的计算中使用 + # self.tile函数将这个新的张量复制到与原始特征张量相同的形状,以便在后面的计算中使用 + grid_x = self.tile(self.reshape(grid_x, (1, 1, -1, 1, 1)), (1, grid_size[0], 1, 1, 1)) + grid_y = self.tile(self.reshape(grid_y, (1, -1, 1, 1, 1)), (1, 1, grid_size[1], 1, 1)) + # Shape is [grid_size[0], grid_size[1], 1, 2] + + # 接下来,代码将网格的x和y坐标合并成一个新的张量grid,其形状为[grid_size[0], grid_size[1], 1, 2]。 + # 其中,grid_size[0]和grid_size[1]分别表示特征图的x和y轴的网格数量 + grid = self.concat((grid_x, grid_y)) + + # 然后,代码从预测张量中提取边界框的x和y坐标(box_xy),宽度和高度(box_wh),置信度(box_confidence)和类概率(box_probs) + box_xy = prediction[:, :, :, :, :2] + box_wh = prediction[:, :, :, :, 2:4] + box_confidence = prediction[:, :, :, :, 4:5] + box_probs = prediction[:, :, :, :, 5:] + """ + 以下代码用于根据锚定框来计算预测框 + """ + + # gridsize1 is x + # gridsize0 is y + # 首先将预测的x和y坐标(box_xy)通过sigmoid函数进行激活,然后乘以一个缩放因子(self.scale_x_y),接着加上一个偏移量(self.offset_x_y), + # 最后加上网格坐标(grid)除以网格尺寸的平方(ops.cast(ops.tuple_to_array((grid_size[1], grid_size[0])), ms.float32)), + # 得到相对于图片尺寸的坐标 + box_xy = (self.scale_x_y * self.sigmoid(box_xy) - self.offset_x_y + grid) / \ + ops.cast(ops.tuple_to_array((grid_size[1], grid_size[0])), ms.float32) + # box_wh is w->h + # 接着,将预测的宽度和高度(box_wh)通过exp函数进行激活,然后乘以一个缩放因子(self.exp), + # 接着乘以预先定义的锚点(self.anchors)除以输入图片的尺寸(input_shape),得到相对于图片尺寸的宽度和高度 + box_wh = self.exp(box_wh) * self.anchors / input_shape + + # 然后,对置信度(box_confidence)和类别概率(box_probs)进行sigmoid激活 + box_confidence = self.sigmoid(box_confidence) + box_probs = self.sigmoid(box_probs) + + # 最后,判断是否为训练模式,如果是训练模式,则返回预测结果(prediction),xy坐标(box_xy),wh坐标(box_wh); + # 如果是预测模式,则返回xy坐标(box_xy),wh坐标(box_wh),置信度(box_confidence)和类别概率(box_probs)的拼接结果 + if self.conf_training: + return prediction, box_xy, box_wh + self.xxx = self.concat((box_xy, box_wh)) + self.xxxx = self.concat((box_xy, box_wh, box_confidence, box_probs)) + return self.concat((box_xy, box_wh, box_confidence, box_probs)) + + +""" +定义了一个名为YoloWithLossCell的神经网络层类,用于计算YOLOv7模型的损失。YoloWithLossCell类继承自nn.Cell,并实现了construct方法。 + +在construct方法中,首先将输入的input_shape转换为浮点数,然后调用yolo_network网络模型计算预测结果。接着,分别计算大、中、小目标层的损失,并将结果相加。最后,返回总损失 + +与mindspore.nn.loss模块实现的目标相同 +""" +class YoloWithLossCell(nn.Cell): + """YOLOV7 loss.""" + def __init__(self, network): + super(YoloWithLossCell, self).__init__() + # 将一个名为network的变量赋值给类实例的yolo_network属性 + self.yolo_network = network + # 将一个名为default_config的变量赋值给类实例的config属性 + self.config = default_config + # 创建一个名为loss_big的类实例,类名称为YoloLossBlock,参数为'l'和self.config + self.loss_big = YoloLossBlock('l', self.config) + # 创建一个名为loss_me的类实例,类名称为YoloLossBlock,参数为'm'和self.config + self.loss_me = YoloLossBlock('m', self.config) + # 创建一个名为loss_small的类实例,类名称为YoloLossBlock,参数为's'和self.config + self.loss_small = YoloLossBlock('s', self.config) + # 创建一个名为tenser_to_array的类实例,类名称为ops.TupleToArray(),用于将一个元组转换为数组 + self.tenser_to_array = ops.TupleToArray() + + def construct(self, x, y_true_0, y_true_1, y_true_2, gt_0, gt_1, gt_2, input_shape): + # 获取输入张量x的第二和第三维的大小 + input_shape = x.shape[2:4] + # 将input_shape转换为ms.float32类型的张量,并将第二维和第三维的大小乘以2 + # 将输入张量大小转换成yolo可接受大小 + input_shape = ops.cast(self.tenser_to_array(input_shape) * 2, ms.float32) + # 将输入张量x和input_shape作为参数传递给YOLO网络,得到输出yolo_out + yolo_out = self.yolo_network(x, input_shape) + # 计算损失函数loss_l,输出中第一个特征图的损失,将输出yolo_out[0]作为元组传递给损失函数 + loss_l = self.loss_big(*yolo_out[0], y_true_0, gt_0, input_shape) + # 计算损失函数loss_m,输出中第一个特征图的损失,将输出yolo_out[1]作为元组传递给损失函数 + loss_m = self.loss_me(*yolo_out[1], y_true_1, gt_1, input_shape) + # 计算损失函数loss_s,输出中第一个特征图的损失,将输出yolo_out[2]作为元组传递给损失函数 + loss_s = self.loss_small(*yolo_out[2], y_true_2, gt_2, input_shape) + return loss_l + loss_m + loss_s * 0.2 + +""" +定义了一个名为YoloLossBlock的类,该类继承自nn.Cell。YoloLossBlock类用于计算YOLOv7网络中的损失块。 + +YoloLossBlock类的__init__方法接收两个参数:scale和config。scale参数用于指定YOLOv7网络的scale,可以是's'(小)、'm'(中)或'l'(大)。 +config参数用于存储一些配置参数,例如anchor数量、ignore_threshold等。 + +YoloLossBlock类的construct方法接收以下参数:prediction、pred_xy、pred_wh、y_true、gt_box和input_shape。这些参数的说明如下: + +prediction:原始输出从YOLO模型中得到的特征图。 +pred_xy:经过sigmoid激活函数处理后的预测框中心坐标(x, y),其中x和y分别对应于横纵坐标。 +pred_wh:经过exp激活函数处理后的预测框宽度和高度(w, h),其中w和h分别对应于宽度和高度。 +y_true:经过归一化处理后的真实框信息,包括边界框的位置、宽度和高度,以及目标类别的概率。 +gt_box:真实框信息,已经过归一化处理。 +input_shape:输入图片的尺寸。 + +YoloLossBlock类的construct方法首先计算网格形状(grid_shape),然后将真实框信息gt_box扩展到一个额外的维度,以便进行广播。接下来,计算IOU(Intersection over Union), +并找到最佳IOU。然后,根据IOU计算损失,包括 confidence_loss(置信度损失)和 class_loss(类别损失)。最后,将损失除以批次大小,得到最终损失 +""" +class YoloLossBlock(nn.Cell): + """ + Loss block cell of YOLOV7 network. + """ + def __init__(self, scale, config=default_config): + super(YoloLossBlock, self).__init__() + self.config = config + # 如果scale的型号是s小号 + if scale == 's': + # anchor mask + # 将索引0、1、2的anchor用于检测 + idx = (0, 1, 2) + # 如果scale的型号是m中号 + elif scale == 'm': + # 将索引3、4、5的anchor用于检测 + idx = (3, 4, 5) + # 如果scale的型号是l大号 + elif scale == 'l': + # 将索引6、7、8的anchor用于检测 + idx = (6, 7, 8) + else: + # 抛出异常,表示无效的scale值 + raise KeyError("Invalid scale value fo r DetectionBlock") + # 通过elf.config.anchor_scales[i]中索引idx对应的anchor尺寸组合成一个张量,类型为float32 + self.anchors = ms.Tensor([self.config.anchor_scales[i] for i in idx], ms.float32) + # 定义self.ignore_threshold属性,用float32类型来表示损失阈值,用于忽视误检 + self.ignore_threshold = ms.Tensor(self.config.ignore_threshold, ms.float32) + # 将输入的张量按照指定的轴(在这里是-1)连接成一个张量 + self.concat = ops.Concat(axis=-1) + # 计算两个张量之间的交并比 + self.iou = Iou() + # 计算沿指定轴的最大值 + self.reduce_max = ops.ReduceMax(keep_dims=False) + # 计算置信度损失 + self.confidence_loss = ConfidenceLoss() + # 计算分类损失 + self.class_loss = ClassLoss() + # 计算沿指定轴的和 + self.reduce_sum = ops.ReduceSum() + # 根据文件选择一个张量 + self.select = ops.Select() + # 比较两个张量的元素是否相等 + self.equal = ops.Equal() + # 改变张量的形状 + self.reshape = ops.Reshape() + # 在张量的指定位置插入一个新的维度 + self.expand_dims = ops.ExpandDims() + # 创建一个与输入张量形状相同的全1张量 + self.ones_like = ops.OnesLike() + # 计算张量对数 + self.log = ops.Log() + # 将一个元组转化为数组 + self.tuple_to_array = ops.TupleToArray() + # 计算两个张量之间的归一化互信息 + self.g_iou = GIou() + + def construct(self, prediction, pred_xy, pred_wh, y_true, gt_box, input_shape): + """ + prediction : origin output from yolo + pred_xy: (sigmoid(xy)+grid)/grid_size + pred_wh: (exp(wh)*anchors)/input_shape + y_true : after normalize + gt_box: [batch, maxboxes, xyhw] after normalize + """ + # 从y_true的第二到第四维中提取目标掩码,即第四个和第五个通道 + object_mask = y_true[:, :, :, :, 4:5] + # 从y_true的第二到第四维中提取类别概率,即第五个到第N个通道 + class_probs = y_true[:, :, :, :, 5:] + # 从y_true的第二到第四维中提取真实边框,即前四个通道 + true_boxes = y_true[:, :, :, :, :4] + # 获取预测的张量形状,从第二到第四维 + grid_shape = prediction.shape[1:3] + # 将grid_shape转换为ms.float32类型的张量,并将结果赋值给grid_shape + grid_shape = ops.cast(self.tuple_to_array(grid_shape[::-1]), ms.float32) + # 将pred_xy和pred_wh张量连接成一个新张量pred_boxes + pred_boxes = self.concat((pred_xy, pred_wh)) + # 从y_true的第二到第四维中提取真实边框的尺寸,即第二个和第三个通道 + true_wh = y_true[:, :, :, :, 2:4] + # 使用self.select操作根据true_wh是否等于0来选择是否使用1替换0,从而避免除以0的错误 + true_wh = self.select(self.equal(true_wh, 0.0), + self.ones_like(true_wh), + true_wh) + # 将true_wh的真实边框的宽度和高度除以锚框的尺寸,然后计算它们的对数 + true_wh = self.log(true_wh / self.anchors * input_shape) + # 2-w*h for large picture, use small scale, since small obj need more precise + # 将y_true的第二和第三个通道相乘,然后减去2 + box_loss_scale = 2 - y_true[:, :, :, :, 2:3] * y_true[:, :, :, :, 3:4] + # 获取gt_box张量的形状 + gt_shape = gt_box.shape + # 将gt_box张量从形状(batch_size, num_true_boxes, 4)转换为(batch_size, 1, 1, 1, num_true_boxes, 4) + gt_box = self.reshape(gt_box, (gt_shape[0], 1, 1, 1, gt_shape[1], gt_shape[2])) + + # add one more dimension for broadcast + iou = self.iou(self.expand_dims(pred_boxes, -2), gt_box) + # gt_box is x,y,h,w after normalize + # [batch, grid[0], grid[1], num_anchor, num_gt] + best_iou = self.reduce_max(iou, -1) + # [batch, grid[0], grid[1], num_anchor] + + # ignore_mask IOU too small + ignore_mask = best_iou < self.ignore_threshold + ignore_mask = ops.cast(ignore_mask, ms.float32) + ignore_mask = self.expand_dims(ignore_mask, -1) + # ignore_mask backpro will cause a lot maximunGrad and minimumGrad time consume. + # so we turn off its gradient + ignore_mask = ops.stop_gradient(ignore_mask) + + confidence_loss = self.confidence_loss(object_mask, prediction[:, :, :, :, 4:5], ignore_mask) + class_loss = self.class_loss(object_mask, prediction[:, :, :, :, 5:], class_probs) + # 将object_mask张量从形状转换为指定形状 + object_mask_me = self.reshape(object_mask, (-1, 1)) # [8, 72, 72, 3, 1] + # 将box_loss_scale_me张量从形状转换为指定形状 + box_loss_scale_me = self.reshape(box_loss_scale, (-1, 1)) + # 将所有锚框的坐标合并到一个一维张量中,以便进行广播 + # xywh2x1y1x2y2是一个将坐标从(x, y, w, h)转换为(x1, y1, x2, y2)的函数 + pred_boxes_me = xywh2x1y1x2y2(pred_boxes) + # 转换形状保证形状不变 + pred_boxes_me = self.reshape(pred_boxes_me, (-1, 4)) + true_boxes_me = xywh2x1y1x2y2(true_boxes) + true_boxes_me = self.reshape(true_boxes_me, (-1, 4)) + # 使用g_iou函数计算预测的锚框和真实锚框之间的GIOU损失 + c_iou = self.g_iou(pred_boxes_me, true_boxes_me) + # + c_iou_loss = object_mask_me * box_loss_scale_me * (1 - c_iou) + # 使用reduce_sum函数计算c_iou_loss的标量值,(())表示对所有维度求和,即将所有锚框的损失相加 + c_iou_loss_me = self.reduce_sum(c_iou_loss, ()) + # 将c_iou_loss_me乘以4,然后将置信度损失和类别损失相加 + loss = c_iou_loss_me * 4 + confidence_loss + class_loss + # 获取预测的张量prediction的第一个维度的大小,即批次大小 + batch_size = prediction.shape[0] + # 返回loss值和批次大小 + return loss / batch_size + +""" +定义了一个名为Iou的类,该类继承自nn.Cell。Iou类用于计算两个矩形框的IOU(Intersection over Union)。 + +Iou类的__init__方法接收no参数。 + +Iou类的construct方法接收两个参数:box1和box2。box1是一个预测框,包含矩形框的中心坐标和宽度和高度(x_center, y_center, w, h);box2是一个真实框,包含矩形框的位置、宽度和高度。 + +首先,将box1和box2转换为topLeft和rightDown坐标。然后,计算两个矩形框的交集,并计算其宽度和高度。最后,计算交集面积除以两个矩形框的面积,得到IOU +""" +class Iou(nn.Cell): + """Calculate the iou of boxes""" + def __init__(self): + super(Iou, self).__init__() + # 定义self.min用于计算两个张量的最小值 + self.min = ops.Minimum() + # 定义self.max用于计算两个张量的最大值 + self.max = ops.Maximum() + # 从张量的某个维度中删除大小为1的维度,参数为-1 + self.squeeze = ops.Squeeze(-1) + + def construct(self, box1, box2): + """ + box1: pred_box [batch, gx, gy, anchors, 1, 4] ->4: [x_center, y_center, w, h] + box2: gt_box [batch, 1, 1, 1, maxbox, 4] + convert to topLeft and rightDown + """ + # 输入box1的xy中心坐标 + box1_xy = box1[:, :, :, :, :, :2] + # 输入box1的wh宽高 + box1_wh = box1[:, :, :, :, :, 2:4] + # 将box1的xy坐标减去宽高的一半,得到每个边界的左上角坐标 + box1_mins = box1_xy - box1_wh / ops.scalar_to_array(2.0) # topLeft + # 将box1的xy坐标加上宽高的一半,得到每个边界的右下角坐标 + box1_maxs = box1_xy + box1_wh / ops.scalar_to_array(2.0) # rightDown + # 输入box2的xy中心坐标 + box2_xy = box2[:, :, :, :, :, :2] + # 输入box2的wh宽高 + box2_wh = box2[:, :, :, :, :, 2:4] + # 将box2的xy坐标减去宽高的一半,得到每个边界的左上角坐标 + box2_mins = box2_xy - box2_wh / ops.scalar_to_array(2.0) + # 将box2的xy坐标加上宽高的一半,得到每个边界的右下角坐标 + box2_maxs = box2_xy + box2_wh / ops.scalar_to_array(2.0) + # 计算两个边界框的左上角坐标(box1_mins和box2_mins)之间的最大值,得到交集的左上角坐标 + intersect_mins = self.max(box1_mins, box2_mins) + # 计算两个边界框的右下角坐标(box1_maxs和box2_maxs)之间的最小值,得到交集的右下角坐标 + intersect_maxs = self.min(box1_maxs, box2_maxs) + # 计算两个边界框的右下角坐标(intersect_maxs)和左上角坐标(intersect_mins)之间的差值 + # 再减去交集的宽度和高度(intersect_wh),得到交集的宽度和高度 + intersect_wh = self.max(intersect_maxs - intersect_mins, ops.scalar_to_array(0.0)) + # self.squeeze: for effiecient slice + # 计算两个范围框的交集面积 + intersect_area = self.squeeze(intersect_wh[:, :, :, :, :, 0:1]) * \ + self.squeeze(intersect_wh[:, :, :, :, :, 1:2]) + # 计算第一个范围框的面积 + box1_area = self.squeeze(box1_wh[:, :, :, :, :, 0:1]) * \ + self.squeeze(box1_wh[:, :, :, :, :, 1:2]) + # 计算第二个范围框的面积 + box2_area = self.squeeze(box2_wh[:, :, :, :, :, 0:1]) * \ + self.squeeze(box2_wh[:, :, :, :, :, 1:2]) + # 计算两个范围框之间的IoU + # IoU的定义是交集面积除以并集面积(即两个范围框的面积之和减去交集面积) + iou = intersect_area / (box1_area + box2_area - intersect_area) + # iou : [batch, gx, gy, anchors, maxboxes] + return iou + +""" +名为GIou的神经网络层类,用于计算Generalized IoU(GIou)分数。GIou是一种评估边界框回归性能的指标,能够反映边界框的准确性和覆盖面积。 + +GIou类的construct方法接收两个输入:box_p和box_gt。box_p是一个表示预测边界框的Numpy数组,box_gt表示目标边界框的数组。 + +首先,box_p_area计算预测边界框的面积,通过计算宽度和高度的差值乘以深度。box_gt_area计算目标边界框的面积,同样通过计算宽度和高度的差值乘以深度。 + +接下来,x_1、x_2、y_1和y_2分别表示预测边界框和目标边界框在水平方向和垂直方向上的交集区域。intersection计算交集区域的宽度和高度乘积。 + +然后,xc_1、xc_2、yc_1和yc_2分别表示预测边界框和目标边界框在水平方向和垂直方向上的中心点。c_area计算中心点的宽度和高度乘积。 + +最后,union计算预测边界框和目标边界框的并集面积,再加上一个很小的常数以避免除以零的错误。iou计算交集区域与并集区域的比值,然后使用div和cast函数将结果转换为浮点数。res_mid0计算并集区域减去交集区域的差值,res_mid1计算差值除以并集区域面积的比值。giou计算IoU减去这个比值,最后使用clip_by_value函数将结果限制在-1到1之间。 + +返回giou作为计算结果 + +与mindspore.nn.metrix模块实现的目标相同 +""" +class GIou(nn.Cell): + """Calculating giou""" + def __init__(self): + super(GIou, self).__init__() + # 定义reshape,用于改变张量的形状 + self.reshape = ops.Reshape() + # 定义min,用于计算张量中元素的最小值 + self.min = ops.Minimum() + # 定义max,用于计算张量中元素的最大值 + self.max = ops.Maximum() + # 定义concat,用于链接两个张量 + self.concat = ops.Concat(axis=1) + # 定义mean,用于计算张量中元素的均值 + self.mean = ops.ReduceMean() + # 定义div,用于执行实数除法 + self.div = ops.RealDiv() + # 定义了一个非常小的常数,用于防止在计算的过程中出现除0的情况 + self.eps = 0.000001 + + def construct(self, box_p, box_gt): + """construct method""" + # 计算第一个边界框的面积 + box_p_area = (box_p[..., 2:3] - box_p[..., 0:1]) * (box_p[..., 3:4] - box_p[..., 1:2]) + # 计算第二个边界框的面积 + box_gt_area = (box_gt[..., 2:3] - box_gt[..., 0:1]) * (box_gt[..., 3:4] - box_gt[..., 1:2]) + # 计算交集区域的最左边边界,即两个边界框的x坐标较大值 + x_1 = self.max(box_p[..., 0:1], box_gt[..., 0:1]) + # 计算交集区域的最右边边界,即两个边界框的x坐标较小值 + x_2 = self.min(box_p[..., 2:3], box_gt[..., 2:3]) + # 计算交集区域的最上边边界,即两个边界框的y坐标较大值 + y_1 = self.max(box_p[..., 1:2], box_gt[..., 1:2]) + # 计算交集区域的最下边边界,即两个边界框的y坐标较小值 + y_2 = self.min(box_p[..., 3:4], box_gt[..., 3:4]) + # 计算交集区域的面积,高度乘宽度 + intersection = (y_2 - y_1) * (x_2 - x_1) + # 计算两个边界框的x坐标较大值 + xc_1 = self.min(box_p[..., 0:1], box_gt[..., 0:1]) + # 计算两个边界框的x坐标较小值 + xc_2 = self.max(box_p[..., 2:3], box_gt[..., 2:3]) + # 计算两个边界框的y坐标较大值 + yc_1 = self.min(box_p[..., 1:2], box_gt[..., 1:2]) + # 计算两个边界框的y坐标较小值 + yc_2 = self.max(box_p[..., 3:4], box_gt[..., 3:4]) + # 计算两个边界框的中心点坐标之间的面积 + c_area = (xc_2 - xc_1) * (yc_2 - yc_1) + # 计算两个边界框的并集面积 + union = box_p_area + box_gt_area - intersection + # 向并集面积中添加一个很小的常数self.eps,防止并集面积为0,避免后续计算出现除以零的错误 + union = union + self.eps + c_area = c_area + self.eps + # 计算两个边界框之间的iou + iou = self.div(ops.cast(intersection, ms.float32), ops.cast(union, ms.float32)) + # 计算两个边界框的中心点坐标之间的面积与并集面积的差 + res_mid0 = c_area - union + # 计算中间结果res_mid0除以c_area + res_mid1 = self.div(ops.cast(res_mid0, ms.float32), ops.cast(c_area, ms.float32)) + # 计算IOU减去中间结果res_mid1得到giou + giou = iou - res_mid1 + # 使用ops.clip_by_value函数将giou限制在-1到1之间 + giou = ops.clip_by_value(giou, -1.0, 1.0) + # 返回计算结果giou的值 + return giou + +""" +定义了一个名为xywh2x1y1x2y2的函数,用于将边界框的(宽、高)格式转换为(左上角横坐标、左上角纵坐标、右下角横坐标、右下角纵坐标)格式。 + +函数接收一个表示边界框的宽度和高度的Numpy数组box_xywh,然后使用ops.Concat函数将宽度和高度拼接在一起,再使用ops.Cast函数将结果转换为浮点数。最后返回拼接后的结果 +""" +def xywh2x1y1x2y2(box_xywh): + # 计算边界框的左上角x坐标,即中心点x坐标减去宽度的一半 + boxes_x1 = box_xywh[..., 0:1] - box_xywh[..., 2:3] / 2 + # 计算边界框的左上角y坐标,即中心点y坐标减去高度的一半 + boxes_y1 = box_xywh[..., 1:2] - box_xywh[..., 3:4] / 2 + # 计算边界框的右下角x坐标,即中心点x坐标加上宽度的一半 + boxes_x2 = box_xywh[..., 0:1] + box_xywh[..., 2:3] / 2 + # 计算边界框的右下角y坐标,即中心点y坐标加上宽度的一半 + boxes_y2 = box_xywh[..., 1:2] + box_xywh[..., 3:4] / 2 + # 使用ops.Concat函数将四个坐标合并成一个张量 + # 其中-1表示在最后一个维度(即batch维度)上合并 + boxes_x1y1x2y2 = ops.Concat(-1)((boxes_x1, boxes_y1, boxes_x2, boxes_y2)) + + return boxes_x1y1x2y2 + +""" +定义了一个名为YOLOV7的神经网络层类,用于构建YOLOv7模型。YOLOV7类继承自nn.Cell,并实现了construct方法。 + +在construct方法中,首先调用Yolov7网络模型计算输入x的预测结果,然后将结果返回。Yolov7网络包括一个特征提取层feature_map,以及三个预测层detect_1、detect_2和detect_3。每个预测层负责预测对应尺度的边界框 +""" +class YOLOV7(nn.Cell): + """ + YOLOV7 network. + + Args: + is_training: Bool. Whether train or not. + + Returns: + Cell, cell instance of YOLOV7 neural network. + + Examples: + YOLOV7s(True) + """ + + def __init__(self, is_training, version=0): + super(YOLOV7, self).__init__() + self.config = default_config + + # YOLOv7 network + self.shape = self.config.input_shape[version] + self.feature_map = Yolov7() + + # prediction on the default anchor boxes + self.detect_1 = DetectionBlock('l', is_training=is_training) + self.detect_2 = DetectionBlock('m', is_training=is_training) + self.detect_3 = DetectionBlock('s', is_training=is_training) + + def construct(self, x, input_shape): + small_object_output, medium_object_output, big_object_output = self.feature_map(x) + output_big = self.detect_1(big_object_output, input_shape) + output_me = self.detect_2(medium_object_output, input_shape) + output_small = self.detect_3(small_object_output, input_shape) + # big is the final output which has smallest feature map + return output_big, output_me, output_small + +""" +定义了一个名为YOLOV7s_Infer的神经网络层类,用于进行YOLOv7模型的推理。YOLOV7s_Infer类继承自nn.Cell,并实现了construct方法。 + +在construct方法中,首先调用YOLOV7网络模型计算输入x的预测结果,然后将结果返回 +""" +class YOLOV7s_Infer(nn.Cell): + """ + YOLOV7 Infer. + """ + + def __init__(self, input_shape, version=0): + super(YOLOV7s_Infer, self).__init__() + self.network = YOLOV7(is_training=False, version=version) + self.input_shape = input_shape + + def construct(self, x): + return self.network(x, self.input_shape) + + +""" +以下为模型的训练部分 +""" + +"""YoloV7 train.""" +import os +import time +import random +import mindspore as ms +import mindspore.nn as nn +import mindspore.communication as comm +from mindspore import ops, Tensor + +from src.yolo import YOLOV7, YoloWithLossCell +from src.logger import get_logger +from src.util import AverageMeter, get_param_groups, cpu_affinity +from src.lr_scheduler import get_lr +from src.yolo_dataset import create_yolo_dataset +from src.initializer import default_recurisive_init, load_yolov7_params + +from model_utils.config import config +from model_utils.device_adapter import get_device_id + +# only useful for huawei cloud modelarts. +from model_utils.moxing_adapter import moxing_wrapper, modelarts_pre_process, modelarts_post_process + + +def set_seed(seed=2): + # 主要用于设置随机数种子 + np.random.seed(seed) + random.seed(seed) + # 设置MindSpore的随机数种子。这可以帮助确保在分布式训练中得到可重复的结果 + ms.set_seed(seed) + + +def init_distribute(): + # 用于初始化分布式训练 + # 首先,使用comm.init()初始化MPI库 + comm.init() + # 然后获取当前进程的排名(config.rank)和组大小(config.group_size) + config.rank = comm.get_rank() + config.group_size = comm.get_group_size() + # 接下来,使用ms.set_auto_parallel_context()设置自动并行上下文,包括并行模式(parallel_mode=ms.ParallelMode.DATA_PARALLEL)、 + # 梯度平均策略(gradients_mean=True)和设备数量(device_num=config.group_size) + ms.set_auto_parallel_context(parallel_mode=ms.ParallelMode.DATA_PARALLEL, gradients_mean=True, + device_num=config.group_size) + + +def train_preprocess(): + # 用于训练前的预处理 + # 如果配置对象中的lr_scheduler为cosine_annealing且max_epoch大于T_max + if config.lr_scheduler == 'cosine_annealing' and config.max_epoch > config.T_max: + # 将T_max设置为max_epoch(最大步长) + config.T_max = config.max_epoch + + # 将配置对象中的lr_epochs字符串按逗号分隔,并将其转换为整数列表 + config.lr_epochs = list(map(int, config.lr_epochs.split(','))) + # 将配置对象中的data_dir和train2017拼接在一起,得到数据集的根目录 + config.data_root = os.path.join(config.data_dir, 'train2017') + # 将配置对象中的data_dir和annotations/instances_train2017.json拼接在一起,得到数据集的标注文件路径 + config.annFile = os.path.join(config.data_dir, 'annotations/instances_train2017.json') + # 调用get_device_id函数获取设备ID + device_id = get_device_id() + # 设置MindSpore计算模式、目标设备和设备ID + ms.set_context(mode=ms.GRAPH_MODE, device_target=config.device_target, device_id=device_id) + + # 如果是分布式训练模式 + if config.is_distributed: + # 初始化分布式训练 + # init distributed + init_distribute() + + # for promoting performance in GPU device + # 如果配置对象中的device_target为GPU且bind_cpu为True + if config.device_target == "GPU" and config.bind_cpu: + # 调用cpu_affinity函数设置CPU核线程绑定 + cpu_affinity(config.rank, min(config.group_size, config.device_num)) + + # logger module is managed by config, it is used in other function. e.x. config.logger.info("xxx") + # 调用get_logger函数获取一个日志记录器,用于记录训练过程中的信息 + config.logger = get_logger(config.output_dir, config.rank) + # 将配置对象保存到日志文件中 + config.logger.save_args(config) + + +def create_train_static_shape_cell(network, opt, config): + # 创建一个用于训练的静态形状单元 + # 定义一个TrainOneStepCell实现训练一个步骤的Cell,它接受三个参数:network(神经网络实现),opt(优化器,如SGD、Adam等)和config(配置对象) + network = nn.TrainOneStepCell(network, opt, config.loss_scale // 2) + # 将神经网络设置为训练模式 + network.set_train() + + # 返回训练的静态形状单元 + return network + +# 用于创建一个用于训练的静态形状函数。静态形状函数是指在训练过程中,神经网络的形状保持不变,即不会增加或减少通道、层数等 +def create_train_static_shape_fn(network, optimizer, config): + # 从MindSpore的amp模块中导入all_finite和StaticLossScaler类,用于检查梯度是否为无穷大和计算损失缩放 + from mindspore.amp import all_finite, StaticLossScaler + # 将神经网络设置为训练模式 + network.set_train() + + # 创建一个StaticLossScaler对象,用于计算损失缩放 + loss_scaler = StaticLossScaler(config.loss_scale // 2) + + # 如果为分布式训练 + if config.is_distributed: + # 获取自动并行上下文中的梯度平均值 + mean = ms.context.get_auto_parallel_context("gradients_mean") + # 获取自动并行上下文中的设备数量 + degree = ms.context.get_auto_parallel_context("device_num") + # 创建一个DistributedGradReducer对象,用于分布式时对梯度进行平均处理 + grad_reducer = nn.DistributedGradReducer(optimizer.parameters, mean, degree) + else: + # 否则,创建一个恒等函数,用于模拟分布式梯度归约 + grad_reducer = ops.functional.identity + + # 前向传播的定义 + def forward_func(*inputs): + # 计算神经网络的损失函数 + loss = network(*inputs) + # 返回损失函数 + return loss_scaler.scale(loss) + + # 计算神经网络各个参数的梯度 + grad_fn = ops.value_and_grad(forward_func, grad_position=None, weights=optimizer.parameters, has_aux=False) + + @ms.ms_function + # 它接受任意数量的输入并返回损失 + def train_step(*inputs): + # 计算神经网络的损失函数和各个参数的梯度 + loss, grads = grad_fn(*inputs) + # 对梯度进行 reduction,例如求和、平均等 + grads = grad_reducer(grads) + # :对梯度进行缩放,使其满足损失缩放因子 + unscaled_grads = loss_scaler.unscale(grads) + # 检查梯度是否为无穷大,如果是,则返回False + grads_finite = all_finite(unscaled_grads) + # 将损失函数与优化器(如SGD、Adam等)结合使用 + # _ = loss_scaler.adjust(grads_finite) + loss = ops.depend(loss, optimizer(unscaled_grads)) + + # 如果梯度为无穷大 + if not grads_finite: + # 打印一条消息,表示这一步溢出,仍然更新 + print("this step overflow, still update.") + + # 返回损失函数的缩放版本 + return loss_scaler.unscale(loss) + + return train_step + +def create_train_static_shape_fn_gradoperation_with_sens(network, optimizer, config): + # 用于创建一个训练步骤,该步骤使用梯度操作并使用敏感参数 + from mindspore.amp import all_finite + # 将网络结构 network 设置为训练模式 + network.set_train() + + # 如果为分布式训练,则使用 nn.DistributedGradReducer 对梯度进行平均处理 + if config.is_distributed: + mean = ms.context.get_auto_parallel_context("gradients_mean") + degree = ms.context.get_auto_parallel_context("device_num") + grad_reducer = nn.DistributedGradReducer(optimizer.parameters, mean, degree) + # 否则,使用 ops.functional.identity 作为梯度Reducer + else: + grad_reducer = ops.functional.identity + + # 使用 ops.GradOperation 和 get_by_list=True 参数创建一个梯度操作 + grad_fn = ops.GradOperation(get_by_list=True, sens_param=True)(network, optimizer.parameters) + # 从配置对象 config 中获取损失缩放值 sens_value + sens_value = config.loss_scale // 2 + + @ms.ms_function + # 定义一个名为train_step的函数,它接受任意数量的关键字参数。这些参数通常表示神经网络的输入 + def train_step(*inputs): + # 计算神经网络的损失函数 + loss = network(*inputs) + # 创建一个与损失函数的形状和数据类型相同的常量,用于计算梯度 + sens = ops.fill(loss.dtype, loss.shape, sens_value) + # 计算神经网络各个参数的梯度 + grads = grad_fn(*inputs, sens) + # 使用损失缩放 + grads = grad_reducer(grads) + # 检查梯度是否为无穷大 + grads_finite = all_finite(grads) + # 将损失函数与优化器(如SGD、Adam等)结合使用 + loss = ops.depend(loss, optimizer(grads)) + + # 如果梯度为无穷大,打印一条消息,表示这一步溢出,仍然更新 + if not grads_finite: + print("this step overflow, still update.") + + # 返回损失 + return loss + + return train_step + +def create_train_static_shape_fn_gradoperation_with_lossscale(network, optimizer, config): + # 用于创建一个训练步骤,该步骤使用梯度操作并使用损失缩放 + # 从 mindspore.amp 模块中导入 all_finite 和 StaticLossScaler + from mindspore.amp import all_finite, StaticLossScaler + # 将网络结构 network 设置为训练模式 + network.set_train() + + # 创建一个 StaticLossScaler 对象,用于对损失进行缩放 + loss_scaler = StaticLossScaler(config.loss_scale // 2) + + # 如果为分布式训练,则使用 nn.DistributedGradReducer 对梯度进行平均处理 + if config.is_distributed: + mean = ms.context.get_auto_parallel_context("gradients_mean") + degree = ms.context.get_auto_parallel_context("device_num") + grad_reducer = nn.DistributedGradReducer(optimizer.parameters, mean, degree) + # 否则,使用 ops.functional.identity 作为梯度Reduce + else: + grad_reducer = ops.functional.identity + + # 定义一个前向传播函数 forward_func,它接受任意数量的输入并返回损失 + def forward_func(*inputs): + # 在训练过程中,将网络输出与损失进行缩放 + loss = network(*inputs) + return loss_scaler.scale(loss) + + # 创建一个梯度函数 grad_fn,它接受任意数量的输入并返回梯度。使用 ops.GradOperation 和 get_by_list=True 参数创建一个梯度操作 + grad_fn = ops.GradOperation(get_by_list=True, sens_param=False)(forward_func, optimizer.parameters) + + # 使用 ms.ms_function 装饰器创建一个训练步骤函数 train_step + @ms.ms_function + # 它接受任意数量的输入并返回损失 + def train_step(*inputs): + # 首先调用前向传播函数计算损失 + loss = forward_func(*inputs) + # 然后调用梯度函数计算梯度 + grads = grad_fn(*inputs) + # 接着使用损失缩放器对梯度进行缩放 + grads = grad_reducer(grads) + # 最后使用优化器更新参数 + unscaled_grads = loss_scaler.unscale(grads) + grads_finite = all_finite(unscaled_grads) + loss = ops.depend(loss, optimizer(unscaled_grads)) + + # 同时,检查梯度是否为无穷大 + if not grads_finite: + # 如果是,则输出一条消息表示进行了溢出并仍然进行了更新 + print("this step overflow, still update.") + + # 最后,返回训练步骤函数 train_step + return loss_scaler.unscale(loss) + + return train_step + +# 该函数用于执行Yolo v7训练过程 +def run_train(): + """ + 以下代码初始化训练网络及其参数 + """ + # 首先,调用 train_preprocess 函数对数据进行预处理 + train_preprocess() + + # 然后,创建一个损失指标对象 loss_meter,一个网络对象 network + loss_meter = AverageMeter('loss') + network = YOLOV7(is_training=True, version=0) + # default is kaiming-normal + # 并使用 default_recurisive_init 函数对网络进行递归初始化 + default_recurisive_init(network) + # 接着,从配置文件中加载 YOLOV7 的参数,并将其设置为网络的参数 + load_yolov7_params(config, network) + network = YoloWithLossCell(network) + + # 接下来,创建一个训练数据集对象 ds,并根据配置的参数设置数据并行策略 + ds = create_yolo_dataset(image_dir=config.data_root, anno_path=config.annFile, is_training=True, + batch_size=config.per_batch_size, device_num=config.group_size, + rank=config.rank, config=config) + config.logger.info('Finish loading dataset') + + # 然后,获取每个训练步骤的步数 + steps_per_epoch = ds.get_dataset_size() + # 并设置学习率 + lr = get_lr(config, steps_per_epoch) + # 使用 nn.Momentum 优化器类,并使用 get_param_groups 函数获取网络的参数组,具体参数设置请参考Momemtum文件评注 + opt = nn.Momentum(params=get_param_groups(network), momentum=config.momentum, learning_rate=ms.Tensor(lr), + weight_decay=config.weight_decay, loss_scale=config.loss_scale) + + """ + 以下代码根据配置文件中定义的并行策略 config.ms_strategy 来创建训练步骤 + """ + + # 如果策略为 "StaticCell" + if config.ms_strategy == "StaticCell": + # 则使用 create_train_static_shape_cell 函数创建训练步骤 + train_step = create_train_static_shape_cell(network, opt, config) + # 如果策略为 "StaticShape" + elif config.ms_strategy == "StaticShape": + # 则根据是否使用梯度操作和损失缩放来选择创建训练步骤的函数 + if config.ms_use_gard_operation: + if config.ms_use_sens: + # 如果使用梯度操作,并且使用损失缩放,则使用 create_train_static_shape_fn_gradoperation_with_sens 函数 + train_step = create_train_static_shape_fn_gradoperation_with_sens(network, opt, config) + else: + # 否则,使用 create_train_static_shape_fn_gradoperation_with_lossscale 函数 + train_step = create_train_static_shape_fn_gradoperation_with_lossscale(network, opt, config) + else: + train_step = create_train_static_shape_fn(network, opt, config) + else: + # 如果策略不是 "StaticCell" 或 "StaticShape",则抛出异常 + raise NotImplementedError + + # 创建了一个数据加载器对象 data_loader,用于从数据集对象 ds 中读取数据 + data_loader = ds.create_tuple_iterator(do_copy=False) + # first_step 用于标记是否是第一个训练步骤 + first_step = True + # t_end 用于记录每步训练结束的时间 + t_end = time.time() + + # 最后,它创建一个数据加载器对象,并使用 for 循环进行训练 + for epoch_idx in range(config.max_epoch): + for step_idx, data in enumerate(data_loader): + # 在每个训练步骤中,它将图像数据传递给网络 + images = data[0] + input_shape = images.shape[2:4] + input_shape = ms.Tensor(tuple(input_shape[::-1]), ms.float32) + # 并计算损失 + loss = train_step(images, data[2], data[3], data[4], data[5], data[6], + data[7], input_shape) + # 然后,它更新损失指标对象 + loss_meter.update(loss.asnumpy()) + + # it is used for loss, performance output per config.log_interval steps. + # 并在满足日志间隔时输出损失、FPS 和每步时间 + if (epoch_idx * steps_per_epoch + step_idx) % config.log_interval == 0: + time_used = time.time() - t_end + if first_step: + fps = config.per_batch_size * config.group_size / time_used + per_step_time = time_used * 1000 + first_step = False + else: + fps = config.per_batch_size * config.log_interval * config.group_size / time_used + per_step_time = time_used / config.log_interval * 1000 + # 输出日志,它格式化输出当前的训练epoch数、训练步骤序号、损失指标对象 loss_meter 的值、FPS 和每步时间 + config.logger.info('epoch[{}], iter[{}], {}, fps:{:.2f} imgs/sec, ' + 'lr:{}, per step time: {}ms'.format(epoch_idx + 1, step_idx + 1, + loss_meter, fps, lr[step_idx], per_step_time)) + # 记录当前时间 t_end + t_end = time.time() + # 并重置损失指标对象 loss_meter + loss_meter.reset() + if config.rank == 0: + # 在训练完成后,如果它是主进程,它将保存训练后的模型 + ckpt_name = os.path.join(config.output_dir, "yolov5_{}_{}.ckpt".format(epoch_idx + 1, steps_per_epoch)) + ms.save_checkpoint(network, ckpt_name) + + # 输出训练结束的提示字符 + config.logger.info('==========end training===============') + +""" +以下为模型评估部分 +""" + +# 运行评估函数 +def run_eval(): + # 配置,通过创建这个类的实例,我们可以使用config对象来访问和设置YOLOv7模型的配置参数 + config = ConfigYOLOV7() + # 使用context.set_context设置运行时环境,包括模式(GRAPH_MODE)和设备类型(config.device_target) + context.set_context(mode=context.GRAPH_MODE, device_target=config.device_target, device_id=config.device_id) + + # 创建YOLOv7模型 + yolo = YOLOV7(config) + + # 加载模型权重 + load_checkpoint(config.checkpoint_path, yolo) + + # 创建数据集 + dataset = create_yolo_dataset(config, is_training=False) + + # 定义损失函数和指标名称(mAP_name = ["mAP", "mAP_50", "mAP_75", "mAP_small", "mAP_medium", "mAP_large"]) + sigmoid = Sigmoid() + mAP_name = ["mAP", "mAP_50", "mAP_75", "mAP_small", "mAP_medium", "mAP_large"] + mAP = [0.0] * len(mAP_name) + + # 开始评估 + for idx, data in enumerate(dataset.create_dict_iterator()): + # 获取样本的图像(image = data["image"])、图像尺寸(image_shape = data["image_shape"]) + # 、边界框(box = data["box"])、标签(label = data["label"]) + image = data["image"] + image_shape = data["image_shape"] + box = data["box"] + label = data["label"] + + # 前向推理 + outputs = yolo(image) + + # 计算损失函数 + for output in outputs: + # 将输出值进行sigmoid激活 + output = sigmoid(output) + # 计算损失 + loss = yolo.loss(output, box, label, config) + + # 计算指标 + # 遍历每一个输出 + for i, output in enumerate(outputs): + # 将输出转换为numpy数组 + output = output.asnumpy() + # 将box转换为numpy数组 + box = box.asnumpy() + # 将label转换为numpy数组 + label = label.asnumpy() + # 将image_shape转换为numpy数组 + image_shape = image_shape.asnumpy() + # 计算mAP + metrics_ = metrics(output, box, label, image_shape, config) + # 遍历每一个mAP + for j, metric in enumerate(metrics_): + # 将每一个mAP累加 + mAP[i] += metric + + # 计算map平均指标 + for i in range(len(mAP)): + mAP[i] /= (idx + 1) + + # 输出评估结果 + print("Evaluation result:") + # 循环打印结果 + for i, name in enumerate(mAP_name): + print("{}: {:.2f}".format(name, mAP[i] * 100)) + +# 当一个Python模块被导入到另一个模块中时,它的代码不会立即执行,而是会被缓存起来。只有在调用if __name__ == "__main__":时,才会执行该模块中的代码 +# 这意味着,只有当当前模块作为主程序运行时,才会执行run_eval()函数中的代码 +if __name__ == "__main__": + run_eval() \ No newline at end of file diff --git a/mindspore/python/mindspore/_checkparam.py b/mindspore/python/mindspore/_checkparam.py index cd31a46ffe3..f5240e99bea 100644 --- a/mindspore/python/mindspore/_checkparam.py +++ b/mindspore/python/mindspore/_checkparam.py @@ -12,37 +12,61 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ +# 该文件在validator类中定义了一批校验函数,主要用于定义检查和处理参数的类,以避免在调用函数或类时出现问题。import以后可以直接调用 """Check parameters.""" +# re:正则表达式库,用于处理字符串 import re +# inspect:用于获取函数或类信息 import inspect +# math:数学库,包含数学函数和常量 import math +# Enum:枚举类,用于定义一组命名的常量 from enum import Enum +# functools:函数工具库,提供装饰器、偏函数等函数式编程功能 from functools import reduce, wraps +# itertools:迭代工具库,提供一系列的迭代器函数,如repeat、zip_longest等 from itertools import repeat, zip_longest +# collections:集合库,包含deque、Iterable等集合类 from collections import deque from collections.abc import Iterable +# numpy:数值计算库,用于处理多维数组和矩阵运算 import numpy as np +# 其中context是mindspore库中的一个模块,用于设置运行时环境 from mindspore import context +# log:用于记录日志 from mindspore import log as logger +# dtype:用于表示数据类型的类,是numpy库的一部分 from mindspore.common import dtype as mstype +# 而_c_expression是mindspore库中的一个模块,其中定义了Tensor类的C++实现。使用Tensor_作为别名可以方便地引用Tensor类 from mindspore._c_expression import Tensor as Tensor_ class Rel(Enum): + # Rel枚举类,用于表示数值关系和逻辑关系。枚举类有13个成员,分别表示不同的关系类型。每个成员都有一个唯一的整数值,用于在代码中引用 """Numerical relationship between variables, logical relationship enumeration definition of range.""" # scalar compare + # 表示等于 EQ = 1 # == + # 表示不等于 NE = 2 # != + # 表示小于 LT = 3 # < + # 表示小于等于 LE = 4 # <= + # 表示大于 GT = 5 # > + # 表示大于等于 GE = 6 # >= # scalar range check + # 表示不包含 neither(两者都不-即开区间),即() INC_NEITHER = 7 # (), include neither + # 表示包含 left(左闭右开),即[) INC_LEFT = 8 # [), include left + # 表示包含 right(左开右闭),即(] INC_RIGHT = 9 # (], include right + # 表示包含 both(闭区间),即[] INC_BOTH = 10 # [], include both # collection in, not in IN = 11 @@ -50,15 +74,19 @@ class Rel(Enum): @staticmethod def get_strs(rel): + # 用于从rel_strs映射中获取关系字符串 """Get value from rel_strs.""" return rel_strs.get(rel, "") @staticmethod def get_fns(rel): + # 用于从rel_fns映射中获取关系函数 """Get value from rel_fns.""" + # 如果rel类型不存在,则返回一个名为lambda的匿名函数,该函数接受任意数量的参数,并返回False return rel_fns.get(rel, lambda *args: False) +# 定义了一个名为rel_fns的字典,用于将一般关系类型映射到相应的函数。字典的键是关系类型,值是对应的函数 rel_fns = { # scalar compare Rel.EQ: lambda x, y: x == y, @@ -77,6 +105,8 @@ rel_fns = { Rel.NOT_IN: lambda x, y: x not in y, } +# 定义了一个名为rel_strs的字典,用于将区间关系类型映射到相应的字符串。字典的键是关系类型,值是对应的字符串 +# 这些字符串用于在比较关系时输出相应的信息。例如,当关系类型为Rel.EQ时,输出的字符串为= {},其中{}将被实际比较的参数替换 rel_strs = { # scalar compare Rel.EQ: "= {}", @@ -98,11 +128,26 @@ rel_strs = { def _check_3d_int_or_tuple(arg_name, arg_value, prim_name, allow_five=False, ret_five=False, greater_zero=True, third_one=False, three_input=False): + # 用于检查一个参数是否为正整数或一个包含3个或5个正整数的元组。如果参数不符合要求,函数会抛出一个ValueError异常 + """ + 函数的参数包括: + + arg_name:参数的名称。 + arg_value:参数的值。 + prim_name:主函数的名称,用于在异常信息中使用。 + allow_five:是否允许参数为5个正整数。默认值为False。 + ret_five:是否返回5个整数。默认值为False。 + greater_zero:是否要求参数大于0。默认值为True。 + third_one:是否要求参数的第三个元素为1。默认值为False。 + three_input:是否要求参数为3个整数。默认值为False。 + """ + """ Checks whether an argument is a positive int or tuple with 3 or 5(when allow_five is True) positive int elements. """ def _raise_message(third_one_flag=False, three_input_flag=False): + # 首先检查参数的类型是否为整数或元组,然后根据不同的条件对参数进行进一步检查。如果参数不符合要求,函数会调用_raise_message函数,抛出一个ValueError异常 if third_one_flag: raise ValueError(f"For '{prim_name}' the depth of attr '{arg_name}' should be 1, but got {ret_value[-3]}") if three_input_flag: @@ -112,66 +157,116 @@ def _check_3d_int_or_tuple(arg_name, arg_value, prim_name, allow_five=False, ret f"{'or five ' if allow_five else ''}positive int numbers, but got {arg_value}") def _get_return_value(): + # 如果参数的类型为整数 if isinstance(arg_value, int): + # 函数会将其转换为5个整数的元组(如果ret_five为True)或3个整数的元组(否则) ret = (1, 1, arg_value, arg_value, arg_value) if ret_five else (arg_value, arg_value, arg_value) + # 如果参数的类型为元组,函数会检查元组的长度是否为3或5(如果allow_five为True),并根据长度分别进行处理。 elif len(arg_value) == 3: ret = (1, 1, arg_value[0], arg_value[1], arg_value[2]) if ret_five else arg_value elif len(arg_value) == 5: if not allow_five: + # 如果长度不符合要求,函数会调用_raise_message函数,抛出一个ValueError异常 _raise_message() ret = arg_value if ret_five else (arg_value[1], arg_value[2], arg_value[3]) else: + # 如果长度不符合要求,函数会调用_raise_message函数,抛出一个ValueError异常 _raise_message() return ret + # 检查输入参数arg_value的类型是否为整数或元组,并且参数arg_name和prim_name的类型分别为字符串和None Validator.check_value_type(arg_name, arg_value, (int, tuple), prim_name) + # 如果three_input为True且输入参数arg_value的类型为元组 if three_input and isinstance(arg_value, tuple): + # 那么检查元组的长度是否为3 if len(arg_value) != 3: + # 如果不是,则抛出一个异常 _raise_message(three_input_flag=three_input) + # 调用_get_return_value()函数获取满足条件的整数或元组ret_value ret_value = _get_return_value() + # 遍历ret_value中的每个元素item for item in ret_value: + # 如果item的类型为整数且不是布尔类型 if isinstance(item, int) and not isinstance(item, bool): + # 那么根据greater_zero的值判断item是否大于0,如果是,则继续下一次循环 if greater_zero and item > 0: continue + # 如果不是,则继续判断item是否大于等于0,如果是,则继续下一次循环 if not greater_zero and item >= 0: continue + # 如果不是,则抛出一个异常 _raise_message() + # 如果third_one为True if third_one: + # 函数还会检查返回的元组的第三个元素是否为1 if ret_value[-3] != 1: + # 如果不为1,则会再次调用_raise_message函数,抛出一个ValueError异常 _raise_message(third_one_flag=third_one) + # 最后,函数会返回一个元组,其中包含满足条件的整数或元组 return tuple(ret_value) def check_number(arg_value, value, rel, arg_type=int, arg_name=None, prim_name=None): + # 用于检查一个整数类型的参数是否符合给定的关系(如大于等于0),并返回该参数 + """ + 函数的参数包括: + + arg_value:待检查的整数参数。 + value:用于比较的关系值。 + rel:关系类型,表示参数的值应该符合的关系,如Rel.GE表示大于等于0。 + arg_type:参数的类型,默认为int。 + arg_name:参数的名称,默认为None。 + prim_name:主函数的名称,默认为None。 + """ + """ Check argument integer. Usage: - number = check_number(number, 0, Rel.GE, "number", None) """ + # 使用Rel.get_fns()函数获取与rel对应的关系函数,然后分别检查参数arg_value的类型是否为arg_type,以及是否为无限大或非数值类型 rel_fn = Rel.get_fns(rel) prim_name = f' in `{prim_name}`' if prim_name else '' arg_name = f'`{arg_name}`' if arg_name else '' prim_info = f'{arg_name}' + f'{prim_name}' + # 如果参数不符合要求,函数会抛出一个TypeError或ValueError异常 if isinstance(arg_value, arg_type): if math.isinf(arg_value) or math.isnan(arg_value) or np.isinf(arg_value) or np.isnan(arg_value): raise ValueError(f'f{prim_info} must be a legal value, but got `{arg_value}`.') else: raise TypeError(f'{prim_info} must be {arg_type.__name__}, but got `{type(arg_value).__name__}`') + # 使用isinstance()函数检查arg_value的类型是否为arg_type,或者arg_value的类型为bool。 type_mismatch = not isinstance(arg_value, arg_type) or isinstance(arg_value, bool) + # 然后,根据type_mismatch的值,使用TypeError或ValueError异常类型来判断应该抛出的异常。如果type_mismatch为True,则使用TypeError,否则使用ValueError type_except = TypeError if type_mismatch else ValueError + # 如果type_mismatch为True或者arg_value与value的关系不满足条件 if type_mismatch or not rel_fn(arg_value, value): + # 则使用Rel.get_strs()函数获取关系字符串,并使用format()方法将value插入到字符串中 rel_str = Rel.get_strs(rel).format(value) + # 然后,使用type_except抛出异常,异常信息包括参数名称、主函数名称、参数类型、参数值和类型名称 raise type_except(f'{prim_info} should be {arg_type.__name__} and must {rel_str}, ' f'but got `{arg_value}` with type `{type(arg_value).__name__}`.') + # 最后,函数返回输入参数arg_value return arg_value def check_is_number(arg_value, arg_type, arg_name=None, prim_name=None): + # 用于检查输入的值是否为浮点数类型。如果输入的值不是浮点数类型,函数会抛出一个TypeError异常 + + """ + 函数的参数包括: + + arg_value:待检查的输入值。 + arg_type:待检查的输入值类型,可以是float、int或其他数字类型。 + arg_name:输入值的名称,默认为None。 + prim_name:主函数的名称,默认为None。 + """ + """ Checks input value is float type or not. @@ -182,14 +277,31 @@ def check_is_number(arg_value, arg_type, arg_name=None, prim_name=None): """ prim_name = f"For \'{prim_name}\', the" if prim_name else 'The' arg_name = f"\'{arg_name}\'" if arg_name else 'input value' + # 使用isinstance()函数检查arg_value的类型是否为arg_type,并且不是布尔类型 if isinstance(arg_value, arg_type) and not isinstance(arg_value, bool): + # 如果arg_value的类型不是arg_type,或者arg_value是布尔类型,则函数会继续检查 + # 如果arg_value的类型是arg_type,并且不是无限大或非数值类型,则函数会返回arg_value。如果arg_value是无限大或非数值类型,则函数会抛出一个ValueError异常 if math.isinf(arg_value) or math.isnan(arg_value) or np.isinf(arg_value) or np.isnan(arg_value): raise ValueError(f'{prim_name} {arg_name} must be a legal float, but got `{arg_value}`.') return arg_value + # 如果arg_value的类型不是arg_type,或者是bool型,则函数会抛出一个TypeError异常,异常信息包括主函数名称、输入值名称、输入值类型和预期类型 raise TypeError(f'{prim_name} type of {arg_name} must be {arg_type.__name__}, but got `{type(arg_value).__name__}`') def check_number_range(arg_value, lower_limit, upper_limit, rel, value_type, arg_name=None, prim_name=None): + # 用于检查一个数值是否在给定的范围内。如果数值不符合要求,函数会抛出一个TypeError或ValueError异常 + """ + 函数的参数包括: + + arg_value:待检查的数值。 + lower_limit:下界。 + upper_limit:上界。 + rel:关系类型,表示数值应该符合的关系,如Rel.INC_NEITHER表示数值应该在上下界之间,包括上下界。 + value_type:数值的类型,如int或float。 + arg_name:数值的名称,默认为None。 + prim_name:主函数的名称,默认为None。 + """ + """ Method for checking whether an int value is in some range. @@ -197,48 +309,63 @@ def check_number_range(arg_value, lower_limit, upper_limit, rel, value_type, arg - number = check_number_range(number, 0.0, 1.0, Rel.INC_NEITHER, "number", float) # number in [0.0, 1.0] - number = check_number_range(number, 0, 1, Rel.INC_NEITHER, "number", int) # number in [0, 1] """ + # 获取参数值 rel_fn = Rel.get_fns(rel) prim_name = f'in `{prim_name}`' if prim_name else '' arg_name = f'`{arg_name}`' if arg_name else '' + # 使用isinstance()函数检查arg_value的类型是否为value_type,或者arg_value的类型为np.ndarray、np.generic或value_type的子类型,并且不是布尔类型 type_mismatch = not isinstance(arg_value, (np.ndarray, np.generic, value_type)) or isinstance(arg_value, bool) if type_mismatch: + # 如果arg_value的类型不符合要求,则函数会抛出一个TypeError异常 raise TypeError("{} {} must be `{}`, but got `{}`.".format( arg_name, prim_name, value_type.__name__, type(arg_value).__name__)) + # 如果arg_value的类型符合要求,但与下界或上界的关系不符合rel指定的关系 if not rel_fn(arg_value, lower_limit, upper_limit): + # 则函数会抛出一个ValueError异常。异常信息包括数值名称、主函数名称、数值类型、下界和上界以及数值值 rel_str = Rel.get_strs(rel).format(lower_limit, upper_limit) raise ValueError("{} {} should be in range of {}, but got {} with type `{}`.".format( arg_name, prim_name, rel_str, arg_value, type(arg_value).__name__)) + # 如果arg_value的类型和关系都符合要求,函数会直接返回输入值 return arg_value class Validator: + # Validator类的作用是检查输入参数是否有效,以确保程序的正确性 """validator for checking input parameters""" @staticmethod def check(arg_name, arg_value, value_name, value, rel=Rel.EQ, prim_name=None, excp_cls=ValueError): + # 用于比较两个整数或整数列表/元组之间的关系。这个方法不适合用于比较浮点数,因为它不考虑浮点数的精度误差 """ Method for judging relation between two int values or list/tuple made up of ints. This method is not suitable for judging relation between floats, since it does not consider float error. """ rel_fn = Rel.get_fns(rel) + # check方法首先使用Rel.get_fns(rel)获取比较关系的函数,然后使用该函数比较arg_value和value。 + # 如果比较结果不满足条件,将抛出excp_cls类型的异常,异常信息包括msg_prefix和arg_name、arg_value和value之间的关系。 if not rel_fn(arg_value, value): rel_str = Rel.get_strs(rel).format(f'{value_name}: {value}') msg_prefix = f'For \'{prim_name}\', the' if prim_name else "The" raise excp_cls(f'{msg_prefix} \'{arg_name}\' should be {rel_str}, but got {arg_value}.') + # 最后,check方法返回arg_value,表示检查通过 return arg_value @staticmethod def check_int(arg_value, value, rel, arg_name=None, prim_name=None): + # 用于检查输入的整数值是否满足给定的值 """ Checks input integer value `arg_value` compare to `value`. Usage: - number = check_int(number, 0, Rel.GE, "number", None) # number >= 0 """ + # check_int方法首先使用check_number方法将arg_value转换为整数类型,然后使用rel获取比较关系的函数, + # 最后使用该函数比较arg_value和value。如果比较结果不满足条件,将抛出异常,满足则返回输入值,表示检查通过 return check_number(arg_value, value, rel, int, arg_name, prim_name) @staticmethod def check_is_int(arg_value, arg_name=None, prim_name=None): + # 用于检查输入的值是否为整数类型 """ Checks input value is float type or not. @@ -247,20 +374,26 @@ class Validator: - number = check_is_int(number, int, "bias") - number = check_is_int(number, int, "bias", "bias_class") """ + # check_is_int方法首先使用check_is_number方法将arg_value转换为整数类型,然后检查转换后的值是否为整数类型。 + # 如果转换后的值不是整数类型,将抛出excp_cls类型的异常,异常信息包括arg_name和arg_value之间的关系,如果是则返回输入值,表示检查通过 return check_is_number(arg_value, int, arg_name, prim_name) @staticmethod def check_equal_int(arg_value, value, arg_name=None, prim_name=None): + # 用于检查输入的整数值是否等于给定的值---EQ """ Checks input integer value `arg_value` compare to `value`. Usage: - number = check_int(number, 0, Rel.GE, "number", None) # number >= 0 """ + # check_equal_int方法首先使用check_number方法将arg_value转换为整数类型,然后使用Rel.EQ获取相等的比较关系, + # 最后使用该函数比较arg_value和value。如果比较结果不满足条件,将抛出异常,如果满足则返回输入值,表示检查通过 return check_number(arg_value, value, Rel.EQ, int, arg_name, prim_name) @staticmethod def check_positive_int(arg_value, arg_name=None, prim_name=None): + # 用于检查输入的整数值是否为正整数---GT """ Check argument is positive integer, which mean arg_value > 0. @@ -268,10 +401,13 @@ class Validator: - number = check_positive_int(number) - number = check_positive_int(number, "bias") """ + # check_positive_int方法首先使用check_number方法将arg_value转换为整数类型,然后使用Rel.GT获取大于的比较关系, + # 最后使用该函数比较arg_value和0。如果比较结果不满足条件,将抛出异常,如果满足则返回输入值,表示检查通过 return check_number(arg_value, 0, Rel.GT, int, arg_name, prim_name) @staticmethod def check_negative_int(arg_value, arg_name=None, prim_name=None): + # 用于检查输入的整数值是否为负数---LE """ Check argument is negative integer, which mean arg_value < 0. @@ -279,10 +415,13 @@ class Validator: - number = check_negative_int(number) - number = check_negative_int(number, "bias") """ + # check_negative_int方法首先使用check_number方法将arg_value转换为整数类型,然后使用Rel.LT获取小于的比较关系, + # 最后使用该函数比较arg_value和0。如果比较结果不满足条件,将抛出异常,如果满足则返回输入值,表示检查通过 return check_number(arg_value, 0, Rel.LT, int, arg_name, prim_name) @staticmethod def check_non_positive_int(arg_value, arg_name=None, prim_name=None): + # 用于检查输入的整数值是否是非正数(即大于等于0)---LE """ Check argument is non-negative integer, which mean arg_value <= 0. @@ -290,10 +429,13 @@ class Validator: - number = check_non_positive_int(number) - number = check_non_positive_int(number, "bias") """ + # check_non_positive_int方法首先使用check_number方法将arg_value转换为整数类型,然后使用Rel.LE获取小于等于的比较关系, + # 最后使用该函数比较arg_value和0。如果比较结果不满足条件,将抛出异常,如果满足则返回输入值,表示检查通过 return check_number(arg_value, 0, Rel.LE, int, arg_name, prim_name) @staticmethod def check_non_negative_int(arg_value, arg_name=None, prim_name=None): + # 用于检查输入的整数值是否是自然数(即大于等于0)---GE """ Check argument is non-negative integer, which mean arg_value >= 0. @@ -301,20 +443,30 @@ class Validator: - number = check_non_negative_int(number) - number = check_non_negative_int(number, "bias") """ + # check_non_negative_int方法首先使用check_number方法将arg_value转换为整数类型,然后使用Rel.GE获取大于等于的比较关系, + # 最后使用该函数比较arg_value和0。如果比较结果不满足条件,将抛出异常,如果满足则返回输入值,表示检查通过 return check_number(arg_value, 0, Rel.GE, int, arg_name, prim_name) @staticmethod def check_float(arg_value, value, rel, arg_name=None, prim_name=None): + # 用于检查输入的浮点数值是否满足给定的条件value """ Checks input float value `arg_value` compare to `value`. Usage: - number = check_float(number, 0.0, Rel.GE, "number", None) # number >= 0 """ + # check_float方法首先使用check_number方法将arg_value转换为浮点类型,然后使用rel获取相应的比较关系 + # ,最后使用该函数比较arg_value和value。如果比较结果不满足条件,将抛出异常,如果满足则返回输入值,表示检查通过 return check_number(arg_value, value, rel, float, arg_name, prim_name) + """ + 以下静态方法与int中实现的大同小异,区别仅仅在于数据类型,就不多赘述了 + """ + @staticmethod def check_is_float(arg_value, arg_name=None, prim_name=None): + # 用于检查输入的值是否为浮点数类型 """ Checks input value is float type or not. @@ -327,6 +479,7 @@ class Validator: @staticmethod def check_positive_float(arg_value, arg_name=None, prim_name=None): + # 用于检查输入的整数值是否为正浮点数---GT """ Check argument is positive float, which mean arg_value > 0. @@ -339,6 +492,7 @@ class Validator: @staticmethod def check_negative_float(arg_value, arg_name=None, prim_name=None): + # 用于检查输入的整数值是否为负浮点数---LE """ Check argument is negative float, which mean arg_value < 0. @@ -350,6 +504,7 @@ class Validator: @staticmethod def check_non_positive_float(arg_value, arg_name=None, prim_name=None): + # 用于检查输入的整数值是否是非正浮点数(即小于等于0)---LE """ Check argument is non-negative float, which mean arg_value <= 0. @@ -361,6 +516,7 @@ class Validator: @staticmethod def check_non_negative_float(arg_value, arg_name=None, prim_name=None): + # 用于检查输入的整数值是否是非负浮点数(即大于等于0)---GE """ Check argument is non-negative float, which mean arg_value >= 0. @@ -372,22 +528,32 @@ class Validator: @staticmethod def check_number(arg_name, arg_value, value, rel, prim_name): + # 用于对输入的数值进行判断 """Number value judgment.""" rel_fn = Rel.get_fns(rel) + # check_number方法首先使用Rel.get_fns方法获取rel对应的比较函数(如Rel.GE对应的大于等于函数),然后使用该函数比较arg_value和value。 + # 如果比较结果不满足条件,将抛出ValueError异常 if not rel_fn(arg_value, value): rel_str = Rel.get_strs(rel).format(value) + # 异常信息包括参数名称、比较关系和实际值,其中rel_str表示比较关系的字符串表示 raise ValueError(f'For \'{prim_name}\' the argument `{arg_name}` must {rel_str}, but got {arg_value}.') + # 返回arg_value,表示检查通过 return arg_value @staticmethod def check_isinstance(arg_name, arg_value, classes): + # 用于检查输入的参数是否为指定类别的实例 """Check arg isinstance of classes""" + # 使用isinstance函数检查arg_value是否为classes类的实例。如果检查结果为False,将抛出ValueError异常 if not isinstance(arg_value, classes): + # 异常信息包括参数名称、预期类型和实际类型,其中classes表示要检查的参数所属的类别 raise ValueError(f'The argument `{arg_name}` should be isinstance of {classes}, but got {arg_value}.') + # 返回arg_value,表示检查通过 return arg_value @staticmethod def check_bool(arg_value, arg_name=None, prim_name=None): + # 用于检查输入的参数是否为布尔类型 """ Check argument is instance of bool. @@ -395,14 +561,22 @@ class Validator: - has_bias = check_bool(has_bias) - has_bias = check_bool(has_bias, "has_bias") """ + # 使用isinstance函数检查arg_value是否为布尔类型。如果检查结果为False,将抛出TypeError异常 if not isinstance(arg_value, bool): prim_name = f"For '{prim_name}', the" if prim_name else 'The' arg_name = f"'{arg_name}'" if arg_name else 'input value' + # 异常信息包括参数名称、预期类型和实际类型,其中prim_name表示要检查的参数所属的原始对象,arg_name表示要检查的参数的名称 raise TypeError(f"{prim_name} {arg_name} should be a bool, but got {type(arg_value).__name__}.") + # 返回arg_value,表示检查通过 return arg_value @staticmethod def check_int_range(arg_value, lower_limit, upper_limit, rel, arg_name=None, prim_name=None): + # 用于检查输入的整数值是否在指定的范围内 + + # arg_value表示要检查的参数的值,lower_limit表示下界,upper_limit表示上界,rel表示比较关系, + # arg_name表示要检查的参数的名称,prim_name表示要检查的参数所属的原始对象(如表、字段等),excp_cls表示抛出的异常类型 + """ Method for checking whether input value is in int range. @@ -410,10 +584,13 @@ class Validator: - number = check_int_range(number, 0, 1, Rel.INC_NEITHER) # number in [0, 1] - number = check_int_range(number, 0, 1, Rel.INC_NEITHER, "number") # number in [0, 1] """ + # 首先使用check_number_range方法将arg_value转换为整数类型,然后使用lower_limit和upper_limit分别计算下界和上界,接着使用rel获取相应的比较关系, + # 最后使用该函数比较arg_value和lower_limit、upper_limit。如果比较结果不满足条件,将抛出ValueError异常,如果满足,则返回输入值,表示检查通过 return check_number_range(arg_value, lower_limit, upper_limit, rel, int, arg_name, prim_name) @staticmethod def check_float_range(arg_value, lower_limit, upper_limit, rel, arg_name=None, prim_name=None): + # 用于检查输入的浮点值是否在指定的范围内,实现方法与int几乎相同 """ Method for checking whether input value is in float range. @@ -425,71 +602,122 @@ class Validator: @staticmethod def check_string(arg_value, valid_values, arg_name=None, prim_name=None): + # 用于检查输入的参数值是否在指定的列表中 + + # arg_value表示要检查的参数的值,valid_values表示允许的值列表 """ Check whether string is in some value list. Usage: - method = check_string(method, ["string1", "string2", "string3"], "method") """ + # 使用isinstance函数检查arg_value是否为字符串类型,如果检查结果为False,将抛出TypeError异常 + # 接着,如果arg_value在valid_values列表中,则返回arg_value if isinstance(arg_value, str) and arg_value in valid_values: return arg_value + # 获取名称 arg_name = arg_name if arg_name else "Parameter" msg_prefix = f'For \'{prim_name}\' the' if prim_name else "The" + # 否则,根据arg_name、prim_name和valid_values构造异常信息,并抛出ValueError异常 raise ValueError(f"{msg_prefix} '{arg_name}' should be str and must be in '{valid_values}'," f" but got '{arg_value}'.") @staticmethod def check_str_by_regular(target, reg=None, flag=re.ASCII, prim_name=None): + # 用于检查输入的字符串是否符合正则表达式规则 + # target表示要检查的参数的值,reg表示正则表达式规则,flag表示正则表达式的匹配标志,prim_name表示要检查的参数所属的原始对象(如表、字段等) + + # 首先检查reg是否为None if reg is None: + # 如果是,则将其设置为默认的正则表达式规则 # Named string regular expression reg = r"^\w+[0-9a-zA-Z\_\.]*$" + # 接着,使用re.match函数尝试将target与reg进行匹配 if re.match(reg, target, flag) is None: + # 如果匹配结果为None,即target不符合正则表达式规则,则根据prim_name构造异常信息,并抛出ValueError异常 prim_name = f'in `{prim_name}`' if prim_name else "" + # 异常信息包括参数名称、正则表达式规则和匹配标志 raise ValueError("'{}' {} is illegal, it should be match regular'{}' by flags'{}.'".format( target, prim_name, reg, flag)) + # 否则,返回True,表示检验通过 return True @staticmethod def check_file_name_by_regular(target, reg=None, prim_name=None): + # 用于检查输入的文件名是否符合正则表达式规则 + + # target表示要检查的文件名,reg表示正则表达式规则,prim_name表示要检查的文件名所属的原始对象(如表、字段等) """Check whether file name is legitimate.""" + # 先检查target是否为字符串类型 if not isinstance(target, str): + # 如果不是,则根据target构造异常信息,并抛出ValueError异常 raise ValueError("Args file_name {} must be string, please check it".format(target)) + # 接着,检查target是否以\或/结尾 if target.endswith("\\") or target.endswith("/"): + # 如果是,则抛出ValueError异常 raise ValueError("File name cannot be a directory path.") + # 检查reg是否为None if reg is None: + # 如果是,则将其设置为默认的正则表达式规则 reg = r"^[0-9a-zA-Z\_\-\.\:\/\\]+$" + # 最后,使用re.match函数尝试将target与reg进行匹配 if re.match(reg, target) is None: + # 如果匹配结果为None,即target不符合正则表达式规则,则根据prim_name构造异常信息,并抛出ValueError异常 prim_name = f'in `{prim_name}`' if prim_name else "" + # 异常信息包括文件名、正则表达式规则和参数名称 raise ValueError("'{}' {} is illegal, it should be match regular'{}'.".format( target, prim_name, reg)) + # 否则,返回True,表示检验通过 return True @staticmethod def check_pad_value_by_mode(pad_mode, padding, prim_name): + # 用于根据填充模式检查填充值是否合法 + # pad_mode表示填充模式,padding表示填充值,prim_name表示要检查的参数所属的原始对象(如表、字段等) """Validates value of padding according to pad_mode""" + # 首先检查pad_mode是否为'pad' if pad_mode != 'pad' and padding != 0: + # 如果pad_mode不是'pad',并且padding不等于0,则根据prim_name构造异常信息,并抛出ValueError异常,异常信息包括填充模式和参数名称 raise ValueError(f"For '{prim_name}', padding must be zero when pad_mode is '{pad_mode}'.") + # 否则,返回padding,表示检验通过 return padding @staticmethod def check_subclass(arg_name, type_, template_types, prim_name, addition_error_info=None): + # 用于检查给定类型是否为指定类型的子类 + + # arg_name表示要检查的参数的名称,type_表示要检查的类型,template_types表示允许的类型模板, + # prim_name表示要检查的参数所属的原始对象(如表、字段等),addition_error_info表示附加错误信息 """Checks whether some type is subclass of another type""" + # 首先检查template_types是否为可迭代类型 if not isinstance(template_types, Iterable): + # 如果不是,则将其设置为元组类型 template_types = (template_types,) hit = False for template_type in template_types: + # 先检查template_type类型是否正确 if isinstance(template_type, mstype.Type): + # 接着,使用mstype._issubclass_函数尝试将type_是否为template_types中的类型 if mstype._issubclass_(type_, template_type): # pylint: disable=W0212 + # 如果type_是template_types中的类型,则hit变量被设置为True,并跳出循环 hit = True break elif type_ is template_type: + # 又如果type_是template_type类型,则hit变量被设置为True,并跳出循环 hit = True break + # 如果hit变量为False if not hit: + # 则根据prim_name和addition_error_info构造异常信息,并抛出TypeError异常 + # 如果addition_error_info为None,则将其设置为空字符串 if addition_error_info is None: addition_error_info = '' + # 获取实际类型的字符串表示形式。如果类型是元组或列表类型,则使用type(type_).__name__来获取类型名称,否则使用str(type_)来获取类型名称 type_str = (type(type_).__name__ if isinstance(type_, (tuple, list)) else "") + str(type_) + # 异常信息包括参数名称、允许的类型模板和实际类型 + # 其中prim_name表示要检查的参数所属的原始对象,arg_name表示要检查的参数的名称, + # template_types表示允许的类型模板,type_表示实际类型,addition_error_info表示附加错误信息 raise TypeError(f"For '{prim_name}', the type of '{arg_name}'" f" should be {'one of ' if len(template_types) > 1 else ''}" f"{', '.join((str(x) for x in template_types))}, but got {type_str}" @@ -499,220 +727,338 @@ class Validator: @staticmethod def check_valid_input(arg_name, arg_value, prim_name): + # 用于检查输入的参数值是否有效 """Checks valid value.""" + # 如果arg_value为None,则抛出一个ValueError异常 if arg_value is None: raise ValueError(f"For \'{prim_name}\', the argument '{arg_name}' can not be None, but got {arg_value}.") return arg_value @staticmethod def check_types_same_and_valid(args, valid_values, prim_name): + # 用于检查输入的参数类型是否相同且有效 """Checks whether the types of inputs are the same and valid.""" def _check_type_valid(arg): + # 定义了一个内部函数_check_type_valid,用于检查输入参数的类型是否有效 arg_key, arg_val = arg elem_type = arg_val + # 首先使用Validator内部的.check_subclass函数检查参数类型是否为指定类型的子类。如果类型不是子类,则会抛出一个TypeError异常 Validator.check_subclass(arg_key, elem_type, valid_values, prim_name) return (arg_key, elem_type) def _check_types_same(arg1, arg2): + # 用于检查输入参数的类型是否相同 arg1_name, arg1_type = arg1 arg2_name, arg2_type = arg2 + # 首先检查两个参数的类型是否相同 if arg1_type != arg2_type: + # 如果类型不同,则抛出一个TypeError异常 raise TypeError(f"For '{prim_name}', type of '{arg2_name}' should be same as '{arg1_name}'," f" but got '{arg1_name}' with type {arg1_type}" f" and '{arg2_name}' with type {arg2_type}.") + # 然后,返回第一个参数的名称和类型元组 return arg1 + # 最后,定义了一个内部函数elem_types,用于将输入参数的名称和类型映射到_check_type_valid函数中进行处理 + # 首先使用map函数将args中的每个参数及其类型映射到_check_type_valid函数中进行处理,得到一个包含处理结果的列表 elem_types = map(_check_type_valid, args.items()) + # 然后,使用reduce函数将处理结果列表中的元素按照名称和类型进行合并,得到一个包含所有参数名称和类型的元组列表。最后,将合并后的元组列表中的第一个元素返回 reduce(_check_types_same, elem_types) @staticmethod def check_tensors_dtypes_same_and_valid(args, valid_dtypes, prim_name): + # 用于检查输入的张量元素的类型是否相同且有效 """Checks whether the element types of input tensors are the same and valid.""" + # 首先将valid_dtypes参数转换为可迭代类型,如果valid_dtypes不是可迭代类型,则将其设置为包含一个元素的元组 valid_dtypes = valid_dtypes if isinstance(valid_dtypes, Iterable) else [valid_dtypes] + # 然后,定义了一个包含valid_dtypes中指定类型的列表tensor_types,其中mstype.tensor_type函数用于将指定类型转换为mstype.Type类型 tensor_types = [mstype.tensor_type(t) for t in valid_dtypes] + # 最后,调用Validator.check_types_same_and_valid函数,将args中的每个参数及其类型映射到tensor_types中进行处理,得到一个包含处理结果的列表 Validator.check_types_same_and_valid(args, tensor_types, prim_name) @staticmethod def check_tensor_dtype_valid(arg_name, arg_type, valid_dtypes, prim_name): + # 用于检查输入的张量元素的类型是否有效 """Checks whether the element types of input tensors are valid.""" + # 首先将valid_dtypes参数转换为可迭代类型,如果valid_dtypes不是可迭代类型,则将其设置为包含一个元素的元组 valid_dtypes = valid_dtypes if isinstance(valid_dtypes, Iterable) else [valid_dtypes] + # 然后,定义了一个包含valid_dtypes中指定类型的列表tensor_types,其中mstype.tensor_type函数用于将指定类型转换为mstype.Type类型 tensor_types = [mstype.tensor_type(t) for t in valid_dtypes] + # 最后,调用Validator.check_subclass函数,将arg_name、arg_type和tensor_types作为参数传递,检查arg_type是否为tensor_types中指定的子类。如果arg_type不是子类,则会抛出一个TypeError异常 Validator.check_subclass(arg_name, arg_type, tensor_types, prim_name) @staticmethod def check_scalar_or_tensor_types_same(args, valid_values, prim_name, allow_mix=False): + # 用于检查输入的参数类型是否相同 """ Checks whether the types of inputs are the same. If the input args are tensors, checks their element types. If `allow_mix` is True, Tensor(float32) and float32 are type compatible, otherwise an exception will be raised. """ def _check_argument_type(arg): + # 用于检查输入参数的类型是否有效 + # 该函数接收一个参数arg,其中arg是一个键值对,表示要检查的参数名称和类型 arg_key, arg_val = arg + # 首先检查参数的类型是否为mstype.tensor类型 if isinstance(arg_val, type(mstype.tensor)): + # 如果是,则调用arg_val.element_type()获取其元素类型 arg_val = arg_val.element_type() + # 然后,检查参数类型是否为指定类型列表valid_values中的元素 if not arg_val in valid_values: + # 如果不是,则会抛出一个TypeError异常 raise TypeError(f'For \'{prim_name}\', the type of `{arg_key}` should be in {valid_values},' f' but got {arg_val}.') + # 最后,返回参数名称和类型元组 return arg def _check_types_same(arg1, arg2): + # 用于检查输入参数的类型是否相同 arg1_name, arg1_type = arg1 arg2_name, arg2_type = arg2 except_flag = False + # 首先检查两个参数的类型是否为mstype.tensor类型 if isinstance(arg1_type, type(mstype.tensor)) and isinstance(arg2_type, type(mstype.tensor)): + # 如果是,则分别调用arg1_type.element_type()和arg2_type.element_type()获取其元素类型 arg1_type = arg1_type.element_type() arg2_type = arg2_type.element_type() + # 然后,根据allow_mix参数的值,判断两个参数的类型是否相同,如果不相同,则会抛出一个TypeError异常 elif not (isinstance(arg1_type, type(mstype.tensor)) or isinstance(arg2_type, type(mstype.tensor))): pass + # 接着,根据allow_mix参数的值,判断两个参数的类型是否相同 elif allow_mix: + # 如果allow_mix为True,则两个参数的元素类型可以不同,只需判断它们是否为mstype.tensor类型即可 arg1_type = arg1_type.element_type() if isinstance(arg1_type, type(mstype.tensor)) else arg1_type arg2_type = arg2_type.element_type() if isinstance(arg2_type, type(mstype.tensor)) else arg2_type + # 如果allow_mix为False,则两个参数的类型必须相同。如果两个参数的类型不同,则抛出一个TypeError异常 else: except_flag = True + # 最后,如果两个参数的类型相同,则继续判断它们是否为mstype.tensor类型,如果是,取其元素类型进行比较 if except_flag or arg1_type != arg2_type: + # 如果两个参数的元素类型不同,则抛出一个TypeError异常 raise TypeError(f'For \'{prim_name}\' type of `{arg2_name}` should be same as `{arg1_name}`,' f' but `{arg1_name}` is {arg1_type} and `{arg2_name}` is {arg2_type}.') + # 最后,返回第一个参数的名称和类型元组 return arg1 + # 定义了一个内部函数reduce,用于将args中的每个参数及其类型映射到_check_argument_type函数中进行处理,得到一个包含处理结果的列表。 + # 然后,使用map函数将处理结果列表中的元素按照名称和类型进行合并,得到一个包含所有参数名称和类型的元组列表。 + # 最后,将合并后的元组列表中的第一个元素返回 reduce(_check_types_same, map(_check_argument_type, args.items())) @staticmethod def check_value_type(arg_name, arg_value, valid_types, prim_name=None): + # 用于检查一个值是否为指定类型,注:针对bool的检查可能会出错,因为bool是int的子类 """Checks whether a value is instance of some types.""" + # 首先,将valid_types参数转换为可迭代类型,如果valid_types不是可迭代类型,则将其设置为包含一个元素的元组 valid_types = valid_types if isinstance(valid_types, Iterable) else (valid_types,) + # 接着,定义了一个内部函数raise_error_msg,用于在检查失败时抛出错误消息 def raise_error_msg(): """func for raising error message when check failed""" + # 该函数首先获取有效的类型名称,然后构造错误消息字符串 type_names = [t.__name__ if hasattr(t, '__name__') else str(t) for t in valid_types] num_types = len(valid_types) msg_prefix = f"For '{prim_name}', the" if prim_name else "The" + # 最后抛出TypeError异常 raise TypeError(f'{msg_prefix} type of `{arg_name}` should be {"one of " if num_types > 1 else ""}' f'\'{type_names if num_types > 1 else type_names[0]}\', ' f'but got \'{arg_value}\' with type \'{type(arg_value).__name__}\'.') # Notice: bool is subclass of int, so `check_value_type('x', True, [int])` will check fail, and # `check_value_type('x', True, [bool, int])` will check pass + # 使用isinstance函数检查arg_value是否为bool类型 if isinstance(arg_value, bool) and bool not in tuple(valid_types): + # 如果arg_value为bool类型且bool不在valid_types中,则调用raise_error_msg函数报错 raise_error_msg() + # 接着,使用not关键字检查arg_value是否为valid_types中指定的类型 if not isinstance(arg_value, tuple(valid_types)): + # 如果arg_value不是valid_types中指定的类型,则调用raise_error_msg函数报错 raise_error_msg() + # 如果检查成功,则返回arg_value return arg_value @staticmethod def check_type_name(arg_name, arg_type, valid_types, prim_name): + # 用于检查一个类型是否在指定类型中 """Checks whether a type in some specified types""" + # 首先,将valid_types参数转换为可迭代类型,如果valid_types不是可迭代类型,则将其设置为包含一个元素的元组 valid_types = valid_types if isinstance(valid_types, Iterable) else (valid_types,) def raise_error_msg(): + # 接着,定义了一个内部函数raise_error_msg,用于在检查失败时抛出错误消息 """func for raising error message when check failed""" + # 该函数首先获取有效的类型名称,然后构造错误消息字符串 type_names = [t.__name__ if hasattr(t, '__name__') else t for t in valid_types] num_types = len(valid_types) msg_prefix = f"For '{prim_name}', the" if prim_name else "The" + # 最后抛出TypeError异常 raise TypeError(f"{msg_prefix} '{arg_name}' should be {'one of ' if num_types > 1 else ''}" f"{type_names if num_types > 1 else type_names[0]}, " f"but got {arg_type.__name__ if hasattr(arg_type, '__name__') else repr(arg_type)}.") + # 使用isinstance函数检查arg_type是否为mstype.tensor类型 if isinstance(arg_type, type(mstype.tensor)): + # 如果是,则调用arg_type.element_type()获取其元素类型 arg_type = arg_type.element_type() + # 接着,使用not in关键字检查arg_type是否在valid_types中 if arg_type not in valid_types: + # 如果arg_type不在valid_types中,则调用raise_error_msg函数报错 raise_error_msg() + # 如果检查成功,则返回arg_type return arg_type @staticmethod def check_reduce_shape(ori_shape, shape, axis, prim_name, arg_name1, arg_name2): + # 用于检查两个张量的形状是否满足给定条件 """Checks whether shape is ori_shape reduced on axis""" + # 用于存储原始的axis参数 axis_origin = axis + # 首先,将axis参数转换为可迭代类型,如果axis不是可迭代类型,则将其设置为包含一个元素的元组 axis = axis if isinstance(axis, Iterable) else (axis,) + # 然后,定义了一个内部变量exp_shape,用于存储原始形状中不在axis中的元素。使用列表推导式从ori_shape中创建exp_shape,其中i的值不是axis中的元素 exp_shape = [ori_shape[i] for i in range(len(ori_shape)) if i not in axis] + # 接下来,使用list函数检查shape是否与exp_shape相等 if list(shape) != exp_shape: + # 如果不相等,则抛出一个ValueError异常 + # 在异常消息中,使用f-string构造了错误消息字符串,其中prim_name表示算子的名称,arg_name1和arg_name2分别表示两个张量的名称 raise ValueError(f"For '{prim_name}', " f"the argument '{arg_name1}'.shape reduce on 'axis': {axis_origin} should " f"be equal to '{arg_name2}'.shape: {shape}, but got {ori_shape}.") @staticmethod def check_astype_dtype(dtype): + # 用于检查输入的dtype是否有效,并将其转换为mstype类型 """Check whether dtype is a valid input, and convert to mstype""" + # 首先,定义了一个内部变量all_types,其中包含mstype类型和int、float、bool三种Python类型 all_types = mstype.__dtype__ + ["int", "float", "bool"] + # 接着,使用isinstance函数检查dtype是否为字符串类型 if isinstance(dtype, str): + # 检查dtype是否存在于all_types中 if dtype.lower() not in all_types: + # 如果不是,则抛出一个TypeError异常 raise TypeError(f"`{dtype}` not understood.") + # 如果是,则将其转换为小写并检查是否在all_types中 dtype = mstype.pytype_to_dtype(np.dtype(dtype.lower())) + # 然后,使用isinstance函数检查dtype是否为类型类型 elif isinstance(dtype, type): + # 如果是,则将其转换为mstype类型 dtype = mstype.pytype_to_dtype(dtype) + # 最后,使用not in关键字检查dtype是否为mstype中的数字类型或bool类型 elif not dtype in mstype.number_type + (mstype.bool_,): + # 如果不是,则抛出一个TypeError异常 raise TypeError(f"`{dtype}` not understood.") + # 如果检查成功,则返回dtype return dtype @staticmethod def check_transpose_axis(axes, ndim): + # 用于检查tensor.transpose方法中的axes参数是否有效 """Check the axis argument for tensor.transpose""" + # 首先,使用not关键字检查axes是否为空或只有一个元素且该元素为None if not axes or (len(axes) == 1 and axes[0] is None): + # 如果是,则返回一个逆序的元组,表示转置后的轴顺序 return tuple(range(ndim-1, -1, -1)) + # 接着,使用len函数检查axes的长度是否为1 if len(axes) == 1: + # 如果是,则使用isinstance函数检查axes[0]是否为列表或元组类型 perm = axes[0] # if only one argument provided, it must be tuple or list + # 若输入参数是唯一的,那么它必须是元组或列表 if isinstance(perm, list): + # 如果是且为list,则将其转换为元组类型 perm = tuple(perm) else: if not isinstance(perm, tuple): + # 如果不是,则抛出一个TypeError异常 raise TypeError(f"The argument `axes` should be a tuple/list, " f"or series of int, but got {type(axes[0])}") + # 表示检查成功,并返回转换后的axes return perm # if multiple arguments provided, it must be `ndim` number of ints + # 使用len函数检查axes的长度是否与ndim相等 if len(axes) != ndim: + # 如果不是,则抛出一个ValueError异常 raise ValueError("The number of axes must be equal to the dimension of tensor.") + # 如果检查成功,则返回axes return axes @staticmethod def check_reshape_shp(shp): + # 用于检查tensor.reshape方法中的shp参数是否有效 """Check the shape argument for tensor.reshape""" + # 首先,使用len函数检查shp的长度是否为1 if len(shp) == 1: new_shape = shp[0] # if only one argument provided, it must be int, tuple or list + # 如果是,则使用isinstance函数检查shp[0]是否为整数类型或列表或元组类型 if isinstance(new_shape, int): + # 如果是int,则返回shp return shp if isinstance(new_shape, list): + # 如果是且为list,则将其转换为元组类型 new_shape = tuple(new_shape) else: if not isinstance(new_shape, tuple): + # 如果都不是,则抛出一个TypeError异常 raise TypeError( f"The argument `shape` should be an int, or tuple/list, " f"or series of int, but got {type(shp[0])}") + # 如果是tuple,则返回 return new_shape + # 如果长度不为一,则直接返回 return shp @staticmethod def check_flatten_order(order): + # 用于检查flatten函数的输入参数order(排序)是否有效 """Check flatten function input order""" + # 首先,使用isinstance函数检查order是否为字符串类型 if not isinstance(order, str): + # 如果不是,则抛出一个TypeError异常 raise TypeError(f"The order variable should be a string, but got {type(order)}") + # 接着,使用if语句检查order是否为'C'或'F' if order not in ('C', 'F'): + # 如果不是,则抛出一个ValueError异常 raise ValueError(f"only `C` and `F` are supported as order, but got {order}") + # 最后,如果检查成功,则返回order return order @staticmethod def check_swapaxes_axis(axes, ndim): + # 用于检查tensor.swapaxes方法中的axes参数是否有效 """Check all the axes argument for tensor.swapaxes""" + # 首先,使用isinstance函数检查axes是否为整数类型 if isinstance(axes, int): + # 如果是,则使用Validator.check_axis_in_range方法检查该轴是否在有效范围内,然后返回该轴 Validator.check_axis_in_range(axes, ndim) return axes % ndim + # 接着,使用if语句检查axes是否为元组或列表类型 if isinstance(axes, (tuple, list)): + # 如果是,则遍历axes中的每个轴,使用isinstance函数检查每个轴是否为整数类型 for axis in axes: if not isinstance(axis, int): + # 如果不是,则抛出一个TypeError异常 raise TypeError(f"The axis argument should be integer, but got {type(axis)}.") + # 如果类型无误,则使用Validator内部.check_axis_in_range方法检查axis是否在0到ndim-1之间,如果不是,则抛出一个ValueError异常 Validator.check_axis_in_range(axis, ndim) + # 然后,使用map函数将axes中的每个轴取余数 axes = tuple(map(lambda x: x % ndim, axes)) + # 最后返回axes return axes + # 最后,如果axes不是整数类型、元组或列表类型,则抛出一个TypeError异常 raise TypeError(f"The argument 'axes' should be integer, list or tuple for check, but got {type(axes)}.") @staticmethod def prepare_shape_for_squeeze(shape, axes): + # 用于根据输入的张量和给定的轴创建一个新的 squeezed形状 + + # 方法接收两个参数:shape和axes。其中,shape表示输入张量的形状,axes表示需要被压缩的轴 """ Creates the squeezed new shape based on the tensor and given axes. @@ -728,106 +1074,168 @@ class Validator: ndim = len(shape) # Convert to set + # 使用if语句检查axes是否为整数类型 if isinstance(axes, int): + # 检查轴绝对值的大小是否处于维度之间,且不等于负维度 if axes >= ndim or axes < -ndim: + # 如果是,则抛出ValueError异常 raise ValueError(f"The axis {axes} is out of bounds for tensor of dimension {ndim}") + # 如果是,则将其转换为集合类型 axes = {axes} + # 如果不是,则检查axes是否为列表或元组类型 elif isinstance(axes, (list, tuple)): + # 如果是,则遍历axes中的每个元素 for axis in axes: + # 检查轴绝对值的大小是否处于维度之间,且不等于负维度 if axis >= ndim or axis < -ndim: + # 如果是,则抛出ValueError异常 raise ValueError(f"The axis {axis} is out of bounds for tensor of dimension {ndim}") + # 然后将它们添加到集合中 axes = set(axes) else: + # 如果都不是,则抛出异常TypeError raise TypeError(f"Only int, tuple and list are allowed for axes, but got {type(axes)}") + # 然后,使用for循环遍历输入张量的每个维度 for idx, s in enumerate(shape): + # 如果当前维度不是1,或者它不在需要被压缩的轴的集合中,或者它不在需要被压缩的轴的集合中(将维度减去张量的维度得到负数) if s != 1 or (idx not in axes) and (idx - ndim not in axes): + # 则将该维度添加到新的形状列表中 new_shape.append(s) # if an axis is selected with shape entry greater than one, an error is raised. + # 最后,如果新的形状列表中的某个维度大于1 if s != 1 and ((idx in axes) or (idx - ndim in axes)): + # 则抛出一个ValueError异常 raise ValueError(f"The axis {axes} has shape entry {s} > 1, cannot be squeezed.") + # 如果检查成功,则返回新的形状列表 return tuple(new_shape) @staticmethod def check_axis_in_range(axis, ndim): + # 用于检查输入的axis是否在输入数组的维度范围内。ndim表示输入数组的维度 """Checks axes are with the bounds of ndim""" + # 使用isinstance函数检查axis是否为整数类型 if not isinstance(axis, int): + # 如果不是,则抛出一个TypeError异常,其中包含axis的类型信息 raise TypeError(f'The axes should be integers, not {type(axis)}') + # 接着,使用if语句检查axis是否在-ndim到ndim-1之间 if not -ndim <= axis < ndim: + # 如果不是,则抛出一个ValueError异常,其中包含axis的值和输入数组的维度ndim raise ValueError(f'The axis {axis} is out of bounds for array of dimension {ndim}') + # 最后,使用%操作符将axis模ndim,以确保axis始终在有效范围内。如果检查成功,则返回axis return axis % ndim @staticmethod def check_axis_valid(axes, ndim): + # 用于检查输入的axes是否有效,并返回可以传递给内置操作的axes(即非负整数或元组)。ndim表示输入张量的维度 """ Checks axes are valid given ndim, and returns axes that can be passed to the built-in operator (non-negative, int or tuple) """ + # 如果axes为None,则将axes设置为tuple(range(ndim)),并返回该值 if axes is None: axes = tuple(range(ndim)) return axes + # 如果axes是元组或列表类型 if isinstance(axes, (tuple, list)): + # 则遍历axes中的每个元素,使用Validator内部.check_axis_in_range方法检查每个元素是否在输入张量的有效范围内 for axis in axes: Validator.check_axis_in_range(axis, ndim) + # 然后将它们添加到新的元组中,接着,使用map函数将新的元组中的每个元素取余数,以确保它们始终在有效范围内 axes = tuple(map(lambda x: x % ndim, axes)) + # 最后,检查新元组中是否有重复的元素 if any(axes.count(el) > 1 for el in axes): + # 如果有,则抛出一个ValueError异常 raise ValueError('duplicate value in "axis"') + # 如果检查成功,则返回新元组 return axes + # 如果axes是整数类型,则使用Validator内部.check_axis_in_range方法检查axes是否在输入张量的有效范围内,然后将其转换为元组并返回 Validator.check_axis_in_range(axes, ndim) + # 如果axes既不是None也不是整数类型或元组类型,则会在上一行代码函数中抛出一个TypeError异常,其中包含axes的类型信息 + # 如果检查成功,则返回一个元组,元组中的元素是axes除以ndim的余数 return (axes % ndim,) @staticmethod def max_(*args): + # 获取最大值 return max(*args) @staticmethod def min_(*args): + # 获取最小值 return min(*args) @staticmethod def expanded_shape(ndim, axis_size, axis): + # 用于根据输入的维度ndim、轴大小axis_size和轴axis返回一个扩展后的形状 """ Returns a shape with size = 1 for all dimensions except at axis. """ + # 使用生成器表达式创建一个新的元组,其中每个元素根据输入的维度进行初始化 + # 对于每个维度,如果它等于输入的轴,则将其设置为axis_size;否则,将其设置为1。最后,将生成的元组转换为元组类型并返回 return tuple(axis_size if i == axis else 1 for i in range(ndim)) + # 例如,如果输入的维度为3,轴大小为2,轴为1,则方法将返回(2, 1, 1) @staticmethod def tuple_slice(tup, start, end): + # 用于从输入元组中获取从start到end的切片 """get sliced tuple from start and end.""" + # 使用tup[start:end]获取元组中从start到end的切片,并将其返回 return tup[start:end] + # 例如,如果输入的元组为(1, 2, 3, 4, 5),从索引2开始到索引4结束,则方法将返回(3, 4) @staticmethod def infer_out_shape(*shapes): + # 用于推断广播后输出张量的形状。如果输入张量的形状无法广播,则抛出一个ValueError异常 """ Returns shape of output after broadcasting. Raises ValueError if shapes cannot be broadcast. """ + # 使用deque数据结构来存储输出张量的形状 + # 注:deque是一个双端队列,可以在两端插入和删除元素 shape_out = deque() + # 使用map函数将输入的形状列表反转,然后使用zip_longest函数将它们连接在一起,并填充默认值(在这里是1) reversed_shapes = map(reversed, shapes) + # 接下来,使用生成器表达式遍历zip_longest函数的结果 for items in zip_longest(*reversed_shapes, fillvalue=1): + # 并获取每个元素的最大值 max_size = 0 if 0 in items else max(items) + # 如果最大值大于1,并且有任何一个元素不是1或最大值 if any(item not in (1, max_size) for item in items): + # 则抛出一个ValueError异常,其中包含输入张量的形状 raise ValueError(f'The operands could not be broadcast together with shapes {*shapes,}') + # 最后,将输出张量的形状存储在deque中 shape_out.appendleft(max_size) + # 并使用tuple函数将其转换为元组类型并返回 return tuple(shape_out) @staticmethod def get_log2_size(size): + # 用于计算输入size的以2为底的对数,向上取整 + # 使用math.log2(size)计算输入size的以2为底的对数,然后使用math.ceil函数将其向上取整,最后返回结果 return math.ceil(math.log2(size)) + # 例如,如果输入size为10,则方法将返回3,因为10的以2为底的对数为log2(10) = 3.5,向上取整后为4 @staticmethod def check_axis_type(axis, type_int=True, type_tuple=True, type_list=True): + # 用于检查输入的axis参数的类型 """Check axis argument type.""" + # 如果axis的类型是整数类型,并且type_int为True,则方法返回True if type_int and isinstance(axis, int): return True + # 如果axis的类型是元组类型,并且type_tuple为True,或者axis的类型是列表类型,并且type_list为True if (type_tuple and isinstance(axis, tuple)) or (type_list and isinstance(axis, list)): + # 则遍历axis中的每个元素,检查它们是否都是整数类型 for ax in axis: if not isinstance(ax, int): + # 否则抛出TypeError raise TypeError(f"Each axis should be integer, but got {type(ax)} in {axis}.") + # 如果所有元素都是整数类型,则方法返回True return True + # 如果以上条件均不满足 type_str = "" if type_int: type_str += "int, " @@ -835,177 +1243,287 @@ class Validator: type_str += "tuple, " if type_list: type_str += "list, " + # 则构造一个TypeError异常,其中包含axis的类型信息 raise TypeError(f"The axis should be {type_str}but got {type(axis)}.") @staticmethod def check_and_canonicalize_axes(axes, ndim): + # 用于检查输入的axes参数的类型和值是否有效。如果axes的类型或值无效,则方法抛出一个TypeError或ValueError异常 """Check whether the types and values of input axes are valid.""" + # 首先检查axes的类型是否为元组类型。如果是,则方法将其视为一个包含多个轴的元组;否则,将其视为一个包含一个轴的元组 axes = axes if isinstance(axes, tuple) else (axes,) new_axes = () + # 接下来,遍历axes中的每个轴,并检查其类型是否为整数类型 for ax in axes: if not isinstance(ax, int): + # 如果类型不是整数类型,则方法抛出一个TypeError异常,其中包含轴的类型信息 raise TypeError((f"Each axis should be integer, but got {type(ax)} in {axes}.")) + # 然后,检查每个轴的值是否在-ndim和ndim - 1之间 if not -ndim <= ax < ndim: + # 如果值超出这个范围,则方法抛出一个ValueError异常,其中包含轴的值和维度信息 raise ValueError(f'The axis {ax} is out of bounds for array of dimension {ndim}') + # 最后,方法将axes中的每个轴的值调整为非负数。如果轴的值小于0,则将其加上ndim,以使其在有效范围内 ax = ax if ax >= 0 else ax + ndim + # 将处理后的轴存储在new_axes元组中 new_axes += (ax,) + # 然后,检查new_axes中是否有重复的轴 if any(new_axes.count(el) > 1 for el in new_axes): + # 如果有,则抛出一个ValueError异常,其中包含重复的轴值 raise ValueError('duplicate value in "axis"') + # 最后,如果成功,返回处理后的new_axes元组 return new_axes @staticmethod def empty_compile(dtype, shape): + # 用于创建一个空的张量。如果输入的dtype和shape有效,则返回一个具有指定dtype和shape的Tensor对象;否则,抛出一个ValueError异常 """Returns an empty Tensor.""" + # 使用Tensor_(dtype, shape)创建一个具有指定dtype和shape的Tensor对象并返回 return Tensor_(dtype, shape) @staticmethod def check_type_support(dtype, device, supported_dtypes): + # 用于检查给定的数据类型是否被支持。如果数据类型被支持,则返回True,否则返回False """Checks whether the data type is supported.""" + # 首先检查dtype是否在supported_dtypes中。如果是,则返回True + # 否则,方法检查当前上下文中的设备目标是否与device相同。如果它们不同,则返回False + # 如果dtype不在supported_dtypes中且设备目标与device相同,则返回False + # 否则,返回True return dtype in supported_dtypes or not context.get_context('device_target') == device @staticmethod def check_sparse_tensor_input(indices, values, shape): + # 用于检查输入的indices、values是否为Tensor类型,并检查shape是否为tuple类型,如果不是,则方法抛出一个TypeError异常 """Common input check for SparseTensors.""" + # 首先检查indices是否为Tensor类型 if not isinstance(indices, Tensor_): + # 如果不是,则方法抛出一个TypeError异常,其中包含indices的类型信息 raise TypeError(f"indices should be Tensor, but got {type(indices)}.") + # 然后,方法检查values是否为Tensor类型 if not isinstance(values, Tensor_): + # 如果不是,则方法抛出一个TypeError异常,其中包含values的类型信息 raise TypeError(f"values should be Tensor, but got {type(values)}.") + # 最后,方法检查shape是否为元组类型 if not isinstance(shape, tuple): + # 如果不是,则方法抛出一个TypeError异常,其中包含shape的类型信息 raise TypeError(f"shape should be tuple, but got {type(shape)}.") @staticmethod def check_csr_tensor_input(indptr, indices, values, shape): + # 检查 CSRTensor(稀疏张量)的输入类型 + # CSRTensor 是 Scipy 中的一种稀疏张量表示方法,它由非零元素的行索引(indptr)、列索引(indices)和值(values)组成 """Checks inputs type for CSRTensor.""" + # 使用 if 语句检查 indptr 是否为 Tensor_ 类型的对象 if not isinstance(indptr, Tensor_): + # 如果不是,则抛出一个 TypeError 异常 raise TypeError(f"indptr should be Tensor, but got {type(indptr)}.") + # 接着,调用 Validator内部.check_sparse_tensor_input方法来检查其他三个参数(indices、values 和 shape)的类型是否符合要求,不满足则抛出TypeError Validator.check_sparse_tensor_input(indices, values, shape) @staticmethod def check_csr_tensor_shape(indptr_shp, indices_shp, values_shp, csr_shp): + # 检查 CSRTensor(稀疏张量)的输入张量的形状是否符合要求 """Checks input tensors' shapes for CSRTensor.""" + # 检查 CSRTensor 的形状(csr_shp)的长度是否为 2 if len(csr_shp) != 2: + # 如果不是,则抛出一个 ValueError 异常 raise ValueError(f"Currently only supports 2-dimensional csr tensor, but got shape length={len(csr_shp)}.") shape_size = 1 + # 接着,使用循环遍历 CSRTensor 的形状(csr_shp) for item in csr_shp: + # 检查每个元素的值是否为正整数且为整数类型 if item <= 0: + # 如果不是,则抛出一个 ValueError 异常 raise ValueError(f"The element of shape must be positive, but got {item}") if not isinstance(item, int): + # 如果不是,则抛出一个 TypeError 异常 raise TypeError(f"The element type of shape must be int, but got {type(item)}") + # 然后,计算 CSRTensor 的形状的总大小(shape_size) shape_size *= item + # 并与 values_shp[0] 进行比较 if shape_size < values_shp[0]: + # 如果 shape_size 小于 values_shp[0],则抛出一个 ValueError 异常 raise ValueError(f"Shape total size: {shape_size} is too small to hold {values_shp[0]} non-zero values.") + # 接着,检查 values、indices 和 indptr 张量的形状长度是否为 1 if len(values_shp) != 1: + # 如果不是,则抛出一个 ValueError 异常 raise ValueError(f"Values must be a 1-dimensional tensor, but got a {len(values_shp)} dimension tensor.") if len(indices_shp) != 1: raise ValueError(f"Indices must be a 1-dimensional tensor, but got a {len(indices_shp)} dimension tensor.") if len(indptr_shp) != 1: raise ValueError(f"Indptr must be a 1-dimensional tensor, but got a {len(indptr_shp)} dimension tensor.") + # 最后,检查 indptr 张量的长度是否为 csr_shp[0] + 1 if csr_shp[0] + 1 != indptr_shp[0]: + # 如果不是,则抛出一个 ValueError 异常 raise ValueError(f"Indptr must have length (1 + shape[0]), but got: {indptr_shp[0]}") + # 同时,检查 indices 和 values 张量的长度是否相等 if indices_shp[0] != values_shp[0]: err_msg1 = "Indices and values must equal in their shape, " err_msg2 = f"but got indices shape: {indices_shp[0]}, values shape: {values_shp[0]}." + # 如果不是,则抛出一个 ValueError 异常 + # 异常信息为 "Indices and values must equal in their shape, but got indices shape: {indices_shp[0]}, values shape: {values_shp[0]}." raise ValueError(err_msg1 + err_msg2) @staticmethod def check_csr_tensor_dtype(indptr_dtype, indices_dtype): + # 检查 CSRTensor(稀疏张量)的输入张量的数据类型是否符合要求 """Checks input tensors' data types for CSRTensor.""" + # 首先使用 if 语句检查 indptr 张量的数据类型是否为 int16、int32 或 int64 if indptr_dtype not in (mstype.int16, mstype.int32, mstype.int64): + # 如果不是,则抛出一个 TypeError 异常 raise TypeError(f"Indptr must have int16 or int32 or int64 data type, but got {indptr_dtype}.") + # 接着,检查 indices 张量的数据类型是否为 int16、int32 或 int64 if indices_dtype not in (mstype.int16, mstype.int32, mstype.int64): + # 如果不是,则抛出一个 TypeError 异常 raise TypeError(f"Indices must have int16 or int32 or int64 data type, but got {indices_dtype}.") @staticmethod def check_coo_tensor_input(indices, values, shape): + # 用来检查 COOTensor(稀疏张量)的输入类型,与csr的方法基本相同 + # COOTensor 是 Scipy 中的一种稀疏张量表示方法,它由非零元素的行索引(indices)和列索引(col_indices)以及值(values)组成 """Checks inputs type for COOTensor.""" + # 调用 Validator内部.check_sparse_tensor_input 方法来检查输入的 indices、values 和 shape 是否符合稀疏张量的要求 Validator.check_sparse_tensor_input(indices, values, shape) @staticmethod def check_coo_tensor_shape(indices_shp, values_shp, coo_shp): + # 用来检查 COOTensor(稀疏张量)的输入张量的形状是否符合要求 """Checks input tensors' shapes for COOTensor.""" + # 首先使用 if 语句检查 COOTensor 的形状(coo_shp)的长度是否为 2 if len(coo_shp) != 2: + # 如果不是,则抛出一个 ValueError 异常 raise ValueError(f"Currently only supports 2-dimensional COOTensor. COOTensor's `shape` should be a " \ f"tuple with length of 2, but got {coo_shp} with length of {len(coo_shp)}.") + # 接着,使用循环遍历 COOTensor 的形状(coo_shp),检查每个元素的值是否为正整数且为整数类型 for sh in coo_shp: if sh <= 0: + # 如果不是,则抛出一个 ValueError 异常 raise ValueError(f"For COOTensor, the element of `shape` must be positive, but got {sh} in {coo_shp}.") if not isinstance(sh, int): + # 如果不是,则抛出一个 TypeError 异常 raise TypeError(f"For COOTensor, the element type of `shape` must be int, but got {type(sh)}") + # 检查 indices 张量的形状长度是否为 2 if len(indices_shp) != 2: + # 如果不是,则抛出一个 ValueError 异常 raise ValueError(f"For COOTensor, `indices` must be a 2-dimensional tensor, but got a {len(indices_shp)}" \ f"dimension tensor.") + # 接着,检查 values 张量的形状长度是否为 1 if len(values_shp) != 1: + # 如果不是,则抛出一个 ValueError 异常 raise ValueError(f"For COOTensor, `values` must be a 1-dimensional tensor, but got a {len(values_shp)}" \ f"dimension tensor.") + # 最后,检查 indices 张量的行数(indices_shp[0])是否等于 values 张量的长度(values_shp[0]) if indices_shp[0] != values_shp[0]: + # 如果不是,则抛出一个 ValueError 异常 raise ValueError(f"For COOTensor, `indices.shape[0]` must be euqal to `values.shape[0]`, but got " \ f"`indices.shape[0]` = {indices_shp[0]} and `values.shape[0]` = {values_shp[0]}.") + # 此外,检查 indices 张量的第二维长度(indices_shp[1])是否为 2 if indices_shp[1] != 2: + # 如果不是,则抛出一个 ValueError 异常 raise ValueError(f"For COOTensor, `indices.shape[1]` must be 2, but got {indices_shp[1]}.") @staticmethod def check_coo_tensor_dtype(indices_dtype): + # 用来检查 COOTensor(稀疏张量)的输入张量的数据类型是否符合要求 """Checks input tensors' data types for COOTensor.""" + # 使用 if 语句检查 COOTensor 的行索引(indices)的数据类型是否为 int16、int32 或 int64 if indices_dtype not in (mstype.int16, mstype.int32, mstype.int64): + # 如果不是,则抛出一个 TypeError 异常 raise TypeError(f"For COOTensor, `indices` must have int16 or int32 or int64 data type, but got " \ f"{indices_dtype}.") def check_input_format(input_param): + # 用来判断输入格式是否为 NCHW + # NCHW 是 Mindspore 中的一种数据格式,表示张量中的第 1 维是 batch size,第 2 维是通道数,第 3 维和第 4 维是特征图的大小 """Judge input format.""" + # 使用 if 语句判断输入参数 input_param 是否为 "NCHW" if input_param == "NCHW": + # 如果是,则返回 "NCHW" return input_param + # 否则,抛出一个 ValueError 异常 raise ValueError("The data format must be NCHW.") def _expand_tuple(n_dimensions): + # 它是一个辅助函数,用于将输入的整数或元组扩展为具有指定维度(n_dimensions)的元组。 + # 这个函数的主要目的是在处理稀疏张量时,将输入的行索引和列索引从单个整数或元组扩展为具有相同维度(n_dimensions)的元组 """To expand an int number to tuple.""" + # 在函数内部,定义一个名为 convert 的内部函数,该函数将用于处理输入的整数或元组 def convert(m): + # 首先使用 if 语句检查输入 m 的类型 if not isinstance(m, tuple): + # 如果输入 m 不是整数或元组,则抛出一个 TypeError 异常 if isinstance(m, int) and not isinstance(m, bool): + # 如果满足要求,使用 repeat 函数将整数 m 重复 n_dimensions 次,然后返回生成的元组 return tuple(repeat(m, n_dimensions)) raise TypeError("Input type must be int or tuple[int].") + # 如果输入 m 是元组,但元组的长度与 n_dimensions 不匹配,则抛出一个 TypeError 异常 if not len(m) is n_dimensions: raise TypeError("Input tuple dimension is incorrect.") + # 然后,遍历元组中的每个元素 i,并检查其类型是否为整数 for i in m: if not isinstance(i, int) or isinstance(i, bool): + # 如果不是,则抛出一个 TypeError 异常 raise TypeError("Incorrect type inside of a tuple, must be int!") + # 最后,直接返回m return m + # 最后,返回 convert 函数,该函数将用于处理输入的整数或元组,并将它们扩展为具有指定维度(n_dimensions)的元组 return convert def _check_data_type_valid(data, valid_type): + # 用于检查输入数据的数据类型是否有效。这个函数的主要目的是在处理稀疏张量时,确保输入的行索引、列索引和值的数据类型是有效的 """Check data type valid.""" + # 如果 valid_type 为 None if valid_type is None: + # 则返回 data 为 None 的布尔值 return data is None + # 如果 data 的类型为 valid_type,并且 data 不是空(即 data.size == 0) if isinstance(data, valid_type): + # 使用 hasattr 函数检查输入的数据 data 是否具有 size 属性 if hasattr(data, 'size') and data.size == 0: + # 如果 data.size 的值为 0,则表示输入的数据为空。此时,使用 logger.critical 函数记录一个错误消息,该消息为 "Please provide non-empty data." msg = "Please provide non-empty data." logger.critical(msg) + # 然后使用 raise ValueError 函数抛出一个 ValueError 异常 raise ValueError(msg) + # 则返回 True return True + # 如果 data 的类型不是 valid_type,或者 data 是空的,则返回 False return False def check_input_data(*data, data_class): + # 用于检查输入的数据是否符合预期的数据类型 """Input data check.""" + # 使用 for 循环遍历变量 data 中的每个元素 item for item in data: + # 如果元素 item 是列表或元组类型 if isinstance(item, (list, tuple)): + # 则使用 for 循环遍历元素 item 中的每个元素 v for v in item: + # 并递归调用 check_input_data 函数,传入 v 和 data_class 参数 check_input_data(v, data_class=data_class) + # 如果元素 item 是字典类型,则使用 for 循环遍历字典的值 v elif isinstance(item, dict): for v in item.values(): + # 并递归调用 check_input_data 函数,传入 v 和 data_class 参数 check_input_data(v, data_class=data_class) + # 如果元素 item 的类型不是列表、元组或字典 else: + # 如果 data_class 是元组或列表 if isinstance(data_class, (tuple, list)): + # 则将 data_class 中的每个元素转换为字符串,以获取元素名称,如果 data_class 是其他类型,则直接使用其名称 ret = True in tuple(_check_data_type_valid(item, data_type) for data_type in data_class) else: + # 则使用 _check_data_type_valid 函数检查元素 item 的数据类型是否符合预期 ret = _check_data_type_valid(item, data_class) if not ret: + # 如果 _check_data_type_valid 函数返回 False,则根据 data_class 的类型生成错误消息,并抛出一个 ValueError 异常 data_class_str = tuple(i.__name__ if hasattr(i, '__name__') else i for i in data_class) \ if isinstance(data_class, (tuple, list)) else \ (data_class if data_class is None else data_class.__name__) @@ -1015,80 +1533,122 @@ def check_input_data(*data, data_class): def check_input_dataset(*dataset, dataset_type): + # 用于检查输入的数据集是否符合预期的数据类型 """Input dataset check.""" + # 如果 dataset 为空,则返回 False if not dataset: return False + # 使用 for 循环遍历变量 dataset 中的每个元素 item for item in dataset: + # 如果元素 item 的类型不是预期的数据类型 dataset_type,则返回 False if not isinstance(item, dataset_type): return False + # 如果循环结束后没有返回 False,则返回 True return True def check_output_data(data): + # 用于检查输出数据是否为空 """Output data check.""" if data is None: + # 如果输出数据为 None,则抛出一个 RuntimeError 异常,其中,str(data) 将输出数据转换为字符串 raise RuntimeError('Executor return data ' + str(data) + ', please check your net or input data.') - +# 定义了三个变量 once、twice 和 triple,它们的值分别是一个长度为 1、2 和 3 的元组 once = _expand_tuple(1) twice = _expand_tuple(2) triple = _expand_tuple(3) def args_type_check(*type_args, **type_kwargs): + # 用于检查函数参数的数据类型是否符合预期 + # 这个方法的主要目的是在函数调用时,确保输入参数的数据类型与函数签名中定义的参数类型匹配 """Check whether input data type is correct.""" + # 使用 type_check 函数定义一个内部函数,该函数接收一个参数 func,表示要装饰的函数 def type_check(func): + # 使用 inspect.signature 函数获取函数 func 的签名信息,并将其存储在变量 sig 中 sig = inspect.signature(func) + # 使用 sig.bind_partial 函数将传入的参数与函数签名进行绑定,并存储在变量 bound_types 中 bound_types = sig.bind_partial(*type_args, **type_kwargs).arguments + # 使用 wraps 函数装饰器将原始函数 func 的元数据(如名称、docstring 等)复制到装饰器函数 wrapper 中 @wraps(func) def wrapper(*args, **kwargs): + # 在函数 wrapper 中,定义一个内部函数 nonlocal_var,用于在函数内部使用 nonlocal 关键字修改外部变量 bound_types 的值 nonlocal bound_types + # 使用 sig.bind(*args, **kwargs) 函数将传入的参数与函数签名进行绑定,并存储在变量 bound_values 中 bound_values = sig.bind(*args, **kwargs) + # 获取函数参数的命名空间,并将其存储在变量 argument_dict 中 argument_dict = bound_values.arguments + # 如果函数参数bound_types的命名空间中包含 kwargs if "kwargs" in bound_types: + # 则将 kwargs 参数的类型赋值给 bound_types bound_types = bound_types["kwargs"] + # 如果函数参数argument_dict的命名空间中包含 kwargs if "kwargs" in argument_dict: + # 则将 kwargs 参数的类型赋值给 argument_dict argument_dict = argument_dict["kwargs"] + # 遍历参数字典 argument_dict,检查每个参数的类型是否与函数签名中定义的类型匹配 for name, value in argument_dict.items(): if name in bound_types: if value is not None and not isinstance(value, bound_types[name]): + # 如果类型不匹配,则抛出一个 TypeError 异常 raise TypeError("The argument {} must be {}, but got {}" .format(name, bound_types[name], type(value))) + # 调用传入的函数 func,并将传入的参数 args 和 kwargs 作为位置参数和关键字参数传递给函数 func return func(*args, **kwargs) + # 将函数 wrapper 的返回值作为最终结果返回 return wrapper + # 返回检查结果 return type_check - +# 空字典 _set_record = {} def args_unreset_check(*unreset_args, **unreset_kwargs): + # 用于检查传入的非重复设置属性 """Check the entered non repeatable setting properties.""" def unreset_check(func): + # 函数的目的是将函数func的签名(用inspect.signature(func)表示)绑定到传入的非重复设置属性上 sig = inspect.signature(func) bound_unreset = sig.bind_partial(*unreset_args, **unreset_kwargs).arguments + # unreset_check函数的返回值是一个新的函数wrapper,这个函数wrapper在调用func之前和之后分别执行一些操作。 + # 在调用func之前,wrapper函数会绑定函数func的参数(用sig.bind(*args, **kwargs)表示)到签名上,并将结果存储在bound_values变量中 @wraps(func) def wrapper(*args, **kwargs): + # 接下来,wrapper函数会检查bound_unreset字典 nonlocal bound_unreset + # 使用 sig.bind(*args, **kwargs) 函数将传入的参数与函数签名进行绑定,并存储在变量 bound_values 中 bound_values = sig.bind(*args, **kwargs) + # 获取函数参数的命名空间,并将其存储在变量 argument_dict 中 argument_dict = bound_values.arguments + # 如果bound_unreset中包含kwargs,那么它会将bound_unreset字典中的kwargs键值对提取出来,并将结果存储在bound_unreset变量中 if "kwargs" in bound_unreset: bound_unreset = bound_unreset["kwargs"] + # 如果argument_dict中包含kwargs,那么它会将argument_dict字典中的kwargs键值对提取出来,并将结果存储在bound_unreset变量中 if "kwargs" in argument_dict: argument_dict = argument_dict["kwargs"] + # 遍历bound_values中的参数名称和值 for name, value in argument_dict.items(): + # 如果参数名称在_set_record字典的键中 if name in _set_record.keys(): + # 那么它会抛出一个TypeError,其中包含非重复设置属性的名称和类型 raise TypeError('The argument {} is non-renewable parameter {}.'.format(name, bound_unreset[name])) + # 如果参数名称在bound_unreset字典中 if name in bound_unreset: + # 那么将参数名称和值存储在_set_record字典中 _set_record[name] = value + # 最后,wrapper函数将调用原始函数func并返回其结果 return func(*args, **kwargs) + # 将函数 wrapper 的返回值作为最终结果返回 return wrapper - + + # 装饰器args_unreset_check的返回语句 return unreset_check diff --git a/mindspore/python/mindspore/dataset/audio/__init__.py b/mindspore/python/mindspore/dataset/audio/__init__.py index 3c1d3075904..303c9ac047a 100644 --- a/mindspore/python/mindspore/dataset/audio/__init__.py +++ b/mindspore/python/mindspore/dataset/audio/__init__.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== + +# 解释音频增强模块的结构和功能 """ This module is to support audio augmentations. It includes two parts: transforms and utils. @@ -30,5 +32,21 @@ Descriptions of common data processing terms are as follows: - TensorOperation, the base class of all data processing operations implemented in C++. - AudioTensorOperation, the base class of all audio processing operations. It is a derived class of TensorOperation. """ +""" + 该模块用于支持音频增强。 + 它包括两个部分: + transforms和utils。 + + transforms是一个高性能处理模块,其中包含常见的音频操作。 + utils提供了一些用于音频处理的通用方法。 + 与相关API示例中常用的导入模块如下: + 导入mindspore.dataset as ds + 导入mindspore.dataset.audio.transforms as audio + 常见的数据处理术语的描述如下: + TensorOperation是所有在C++中实现的数据处理操作的基类。 + AudioTensorOperation是所有音频处理操作的基类,它是TensorOperation的派生类。 +""" +# 从当前目录下的子目录"./"中导入名为"transforms"的模块 from . import transforms +# 从当前目录下的子目录"./"中导入名为"utils"的模块,并将其中的所有内容(包括函数、类、变量等)导入到当前模块中 from .utils import * diff --git a/mindspore/python/mindspore/dataset/audio/transforms.py b/mindspore/python/mindspore/dataset/audio/transforms.py index 219be13b331..feaf97a39a7 100644 --- a/mindspore/python/mindspore/dataset/audio/transforms.py +++ b/mindspore/python/mindspore/dataset/audio/transforms.py @@ -49,50 +49,20 @@ class AudioTensorOperation(TensorOperation): def parse(self): raise NotImplementedError("AudioTensorOperation has to implement parse() method.") - +# 给音频波形施加双极点全通滤波器,其中心频率和带宽由入参指定。 class AllpassBiquad(AudioTensorOperation): - r""" - Design two-pole all-pass filter with central frequency and bandwidth for audio waveform. - - An all-pass filter changes the audio's frequency to phase relationship without changing - its frequency to amplitude relationship. The system function is: - - .. math:: - H(s) = \frac{s^2 - \frac{s}{Q} + 1}{s^2 + \frac{s}{Q} + 1} - - Similar to `SoX `_ implementation. - - Note: - The dimension of the audio waveform to be processed needs to be (..., time). - + ''' + 构建一个全透过比特平滑的复合提取器 + Args: - sample_rate (int): Sampling rate (in Hz), which can't be zero. - central_freq (float): Central frequency (in Hz). - Q (float, optional): `Quality factor `_ , - in range of (0, 1]. Default: 0.707. - - Raises: - TypeError: If `sample_rate` is not of type integer. - ValueError: If `sample_rate` is 0. - TypeError: If `central_freq` is not of type float. - TypeError: If `Q` is not of type float. - ValueError: If `Q` is not in range of (0, 1]. - RuntimeError: If input tensor is not in shape of <..., time>. - - Supported Platforms: - ``CPU`` - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[2.716064453125e-03, 6.34765625e-03], [9.246826171875e-03, 1.0894775390625e-02]]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.AllpassBiquad(44100, 200.0)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + sample_rate: 采样率 + central_freq: 中心频率 + Q: 浓度 + + ''' @check_allpass_biquad def __init__(self, sample_rate, central_freq, Q=0.707): + self.sample_rate = sample_rate self.central_freq = central_freq self.Q = Q @@ -104,50 +74,19 @@ class AllpassBiquad(AudioTensorOperation): DE_C_SCALE_TYPE = {ScaleType.POWER: cde.ScaleType.DE_SCALE_TYPE_POWER, ScaleType.MAGNITUDE: cde.ScaleType.DE_SCALE_TYPE_MAGNITUDE} - +# 将输入音频从振幅/功率标度转换为分贝标度。 class AmplitudeToDB(AudioTensorOperation): - r""" - Turn the input audio waveform from the amplitude/power scale to decibel scale. - - Note: - The dimension of the audio waveform to be processed needs to be (..., freq, time). - + + ''' Args: - stype (ScaleType, optional): Scale of the input waveform, which can be - ScaleType.POWER or ScaleType.MAGNITUDE. Default: ScaleType.POWER. - ref_value (float, optional): Multiplier reference value for generating - `db_multiplier`. Default: 1.0. The formula is - - :math:`\text{db_multiplier} = Log10(max(\text{ref_value}, amin))`. - - amin (float, optional): Lower bound to clamp the input waveform, which must - be greater than zero. Default: 1e-10. - top_db (float, optional): Minimum cut-off decibels, which must be non-negative. Default: 80.0. - - Raises: - TypeError: If `stype` is not of type :class:`mindspore.dataset.audio.utils.ScaleType`. - TypeError: If `ref_value` is not of type float. - ValueError: If `ref_value` is not a positive number. - TypeError: If `amin` is not of type float. - ValueError: If `amin` is not a positive number. - TypeError: If `top_db` is not of type float. - ValueError: If `top_db` is not a positive number. - RuntimeError: If input tensor is not in shape of <..., freq, time>. - - Supported Platforms: - ``CPU`` - - Examples: - >>> import numpy as np - >>> from mindspore.dataset.audio import ScaleType - >>> - >>> waveform = np.random.random([1, 400 // 2 + 1, 30]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.AmplitudeToDB(stype=ScaleType.POWER)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + stype: 输入音频的原始标度 + ref_value: 用于计算分贝系数 + amin: 波形取值下界,低于该值的波形将会被裁切 + top_db: 最小截止分贝值 + + ''' @check_amplitude_to_db + # 定义AmplitudeToDB类 def __init__(self, stype=ScaleType.POWER, ref_value=1.0, amin=1e-10, top_db=80.0): self.stype = stype self.ref_value = ref_value @@ -155,78 +94,18 @@ class AmplitudeToDB(AudioTensorOperation): self.top_db = top_db def parse(self): + # 返回AmplitudeToDBOperation类的实例 return cde.AmplitudeToDBOperation(DE_C_SCALE_TYPE[self.stype], self.ref_value, self.amin, self.top_db) - +# 计算复数序列的角度。 class Angle(AudioTensorOperation): - """ - Calculate the angle of complex number sequence. - - Note: - The dimension of the audio waveform to be processed needs to be (..., complex=2). - The first dimension represents the real part while the second represents the imaginary. - - Raises: - RuntimeError: If input tensor is not in shape of <..., complex=2>. - - Supported Platforms: - ``CPU`` - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[1.43, 5.434], [23.54, 89.38]]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.Angle()] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 角度转换,输入为一个音频张量,输出为一个角度张量 + ''' def parse(self): return cde.AngleOperation() - class BandBiquad(AudioTensorOperation): - """ - Design two-pole band-pass filter for audio waveform. - - The frequency response drops logarithmically around the center frequency. The - bandwidth gives the slope of the drop. The frequencies at band edge will be - half of their original amplitudes. - - Similar to `SoX `_ implementation. - - Note: - The dimension of the audio waveform to be processed needs to be (..., time). - - Args: - sample_rate (int): Sampling rate (in Hz), which can't be zero. - central_freq (float): Central frequency (in Hz). - Q (float, optional): `Quality factor `_ , - in range of (0, 1]. Default: 0.707. - noise (bool, optional) : If True, uses the alternate mode for un-pitched audio (e.g. percussion). - If False, uses mode oriented to pitched audio, i.e. voice, singing, or instrumental music. Default: False. - - Raises: - TypeError: If `sample_rate` is not of type integer. - ValueError: If `sample_rate` is 0. - TypeError: If `central_freq` is not of type float. - TypeError: If `Q` is not of type float. - ValueError: If `Q` is not in range of (0, 1]. - TypeError: If `noise` is not of type bool. - RuntimeError: If input tensor is not in shape of <..., time>. - - Supported Platforms: - ``CPU`` - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[2.716064453125e-03, 6.34765625e-03], [9.246826171875e-03, 1.0894775390625e-02]]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.BandBiquad(44100, 200.0)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - @check_band_biquad def __init__(self, sample_rate, central_freq, Q=0.707, noise=False): self.sample_rate = sample_rate @@ -237,56 +116,8 @@ class BandBiquad(AudioTensorOperation): def parse(self): return cde.BandBiquadOperation(self.sample_rate, self.central_freq, self.Q, self.noise) - +# 给音频波形施加双极点巴特沃斯(Butterworth)带通滤波器 class BandpassBiquad(AudioTensorOperation): - r""" - Design two-pole Butterworth band-pass filter for audio waveform. - - The frequency response of the Butterworth filter is maximally flat (i.e. has no ripples) - in the passband and rolls off towards zero in the stopband. - - The system function of Butterworth band-pass filter is: - - .. math:: - H(s) = \begin{cases} - \frac{s}{s^2 + \frac{s}{Q} + 1}, &\text{if const_skirt_gain=True}; \cr - \frac{\frac{s}{Q}}{s^2 + \frac{s}{Q} + 1}, &\text{if const_skirt_gain=False}. - \end{cases} - - Similar to `SoX `_ implementation. - - Note: - The dimension of the audio waveform to be processed needs to be (..., time). - - Args: - sample_rate (int): Sampling rate (in Hz), which can't be zero. - central_freq (float): Central frequency (in Hz). - Q (float, optional): `Quality factor `_ , - in range of (0, 1]. Default: 0.707. - const_skirt_gain (bool, optional) : If True, uses a constant skirt gain (peak gain = Q); - If False, uses a constant 0dB peak gain. Default: False. - - Raises: - TypeError: If `sample_rate` is not of type integer. - ValueError: If `sample_rate` is 0. - TypeError: If `central_freq` is not of type float. - TypeError: If `Q` is not of type float. - ValueError: If `Q` is not in range of (0, 1]. - TypeError: If `const_skirt_gain` is not of type bool. - RuntimeError: If input tensor is not in shape of <..., time>. - - Supported Platforms: - ``CPU`` - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[2.716064453125e-03, 6.34765625e-03], [9.246826171875e-03, 1.0894775390625e-02]]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.BandpassBiquad(44100, 200.0)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - @check_bandpass_biquad def __init__(self, sample_rate, central_freq, Q=0.707, const_skirt_gain=False): self.sample_rate = sample_rate @@ -297,52 +128,19 @@ class BandpassBiquad(AudioTensorOperation): def parse(self): return cde.BandpassBiquadOperation(self.sample_rate, self.central_freq, self.Q, self.const_skirt_gain) - +# 给音频波形施加双极点巴特沃斯(Butterworth)带通滤波器 class BandrejectBiquad(AudioTensorOperation): - r""" - Design two-pole Butterworth band-reject filter for audio waveform. - - The frequency response of the Butterworth filter is maximally flat (i.e. has no ripples) - in the passband and rolls off towards zero in the stopband. - - The system function of Butterworth band-reject filter is: - - .. math:: - H(s) = \frac{s^2 + 1}{s^2 + \frac{s}{Q} + 1} - - Similar to `SoX `_ implementation. - - Note: - The dimension of the audio waveform to be processed needs to be (..., time). - - Args: - sample_rate (int): Sampling rate (in Hz), which can't be zero. - central_freq (float): Central frequency (in Hz). - Q (float, optional): `Quality factor `_ , - in range of (0, 1]. Default: 0.707. - - Raises: - TypeError: If `sample_rate` is not of type integer. - ValueError: If `sample_rate` is 0. - TypeError: If `central_freq` is not of type float. - TypeError: If `Q` is not of type float. - ValueError: If `Q` is not in range of (0, 1]. - RuntimeError: If input tensor is not in shape of <..., time>. - - Supported Platforms: - ``CPU`` - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[2.716064453125e-03, 6.34765625e-03],[9.246826171875e-03, 1.0894775390625e-02]]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.BandrejectBiquad(44100, 200.0)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 构造一个带通滤波器 + ''' @check_bandreject_biquad def __init__(self, sample_rate, central_freq, Q=0.707): + ''' + 构造一个带通滤波器 + :param sample_rate: 样本采样率 + :param central_freq: 中心频率 + :param Q: 浓度 + ''' self.sample_rate = sample_rate self.central_freq = central_freq self.Q = Q @@ -350,52 +148,19 @@ class BandrejectBiquad(AudioTensorOperation): def parse(self): return cde.BandrejectBiquadOperation(self.sample_rate, self.central_freq, self.Q) - +# 给音频波形施加低音控制效果,即双极点低频搁架滤波器 class BassBiquad(AudioTensorOperation): - r""" - Design a bass tone-control effect, also known as two-pole low-shelf filter for audio waveform. - - A low-shelf filter passes all frequencies, but increase or reduces frequencies below the shelf - frequency by specified amount. The system function is: - - .. math:: - H(s) = A\frac{s^2 + \frac{\sqrt{A}}{Q}s + A}{As^2 + \frac{\sqrt{A}}{Q}s + 1} - - Similar to `SoX `_ implementation. - - Note: - The dimension of the audio waveform to be processed needs to be (..., time). - - Args: - sample_rate (int): Sampling rate (in Hz), which can't be zero. - gain (float): Desired gain at the boost (or attenuation) in dB. - central_freq (float, optional): Central frequency (in Hz). Default: 100.0. - Q (float, optional): `Quality factor `_ , - in range of (0, 1]. Default: 0.707. - - Raises: - TypeError: If `sample_rate` is not of type integer. - ValueError: If `sample_rate` is 0. - TypeError: If `gain` is not of type float. - TypeError: If `central_freq` is not of type float. - TypeError: If `Q` is not of type float. - ValueError: If `Q` is not in range of (0, 1]. - RuntimeError: If input tensor is not in shape of <..., time>. - - Supported Platforms: - ``CPU`` - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[2.716064453125e-03, 6.34765625e-03], [9.246826171875e-03, 1.0894775390625e-02]]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.BassBiquad(44100, 100.0)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 低音双二阶滤波器 + ''' @check_bass_biquad def __init__(self, sample_rate, gain, central_freq=100.0, Q=0.707): + ''' + :param sample_rate: 波形的采样率 + :param gain: 波形的增益 + :param central_freq: 波形的中心频率 + :param Q: 波形的比特率 + ''' self.sample_rate = sample_rate self.gain = gain self.central_freq = central_freq @@ -404,27 +169,20 @@ class BassBiquad(AudioTensorOperation): def parse(self): return cde.BassBiquadOperation(self.sample_rate, self.gain, self.central_freq, self.Q) - +# 给音频波形施加双二阶滤波器 class Biquad(TensorOperation): - """ - Perform a biquad filter of input audio. - - Args: - b0 (float): Numerator coefficient of current input, x[n]. - b1 (float): Numerator coefficient of input one time step ago x[n-1]. - b2 (float): Numerator coefficient of input two time steps ago x[n-2]. - a0 (float): Denominator coefficient of current output y[n], the value can't be zero, typically 1. - a1 (float): Denominator coefficient of current output y[n-1]. - a2 (float): Denominator coefficient of current output y[n-2]. - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[2.716064453125e-03, 6.34765625e-03], [9.246826171875e-03, 1.0894775390625e-02]]) - >>> biquad_op = audio.Biquad(0.01, 0.02, 0.13, 1, 0.12, 0.3) - >>> waveform_filtered = biquad_op(waveform) - """ - + ''' + Biquad类: + 参数: + b0:比特系数b0 + b1:比特系数b1 + b2:比特系数b2 + a0:比特系数a0 + a1:比特系数a1 + a2:比特系数a2 + 返回值: + cde.BiquadOperation + ''' @check_biquad def __init__(self, b0, b1, b2, a0, a1, a2): self.b0 = b0 @@ -437,37 +195,16 @@ class Biquad(TensorOperation): def parse(self): return cde.BiquadOperation(self.b0, self.b1, self.b2, self.a0, self.a1, self.a2) - +# 计算复数序列的范数 class ComplexNorm(AudioTensorOperation): - """ - Compute the norm of complex number sequence. - - Note: - The dimension of the audio waveform to be processed needs to be (..., complex=2). - The first dimension represents the real part while the second represents the imaginary. - - Args: - power (float, optional): Power of the norm, which must be non-negative. Default: 1.0. - - Raises: - TypeError: If `power` is not of type float. - ValueError: If `power` is a negative number. - RuntimeError: If input tensor is not in shape of <..., complex=2>. - - Supported Platforms: - ``CPU`` - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.random.random([2, 4, 2]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.ComplexNorm()] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 计算复数归一化的操作 + ''' @check_complex_norm def __init__(self, power=1.0): + ''' + :param power: 指数 + ''' self.power = power def parse(self): @@ -481,192 +218,103 @@ DE_C_BORDER_TYPE = { BorderType.SYMMETRIC: cde.BorderType.DE_BORDER_SYMMETRIC, } - +# 计算频谱的delta系数,也叫差分系数 class ComputeDeltas(AudioTensorOperation): - """ - Compute delta coefficients of a spectrogram. - - Args: - win_length (int): The window length used for computing delta, must be no less than 3 (default=5). - pad_mode (BorderType): Mode parameter passed to padding (default=BorderType.EDGE).It can be any of - [BorderType.CONSTANT, BorderType.EDGE, BorderType.REFLECT, BordBorderTypeer.SYMMETRIC]. - - - BorderType.CONSTANT, means it fills the border with constant values. - - - BorderType.EDGE, means it pads with the last value on the edge. - - - BorderType.REFLECT, means it reflects the values on the edge omitting the last - value of edge. - - - BorderType.SYMMETRIC, means it reflects the values on the edge repeating the last - value of edge. - - Examples: - >>> import numpy as np - >>> from mindspore.dataset.audio import BorderType - >>> - >>> waveform = np.random.random([1, 400//2+1, 30]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.ComputeDeltas(win_length=7, pad_mode = BorderType.EDGE)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 计算梯度 + ''' @check_compute_deltas def __init__(self, win_length=5, pad_mode=BorderType.EDGE): + ''' + 初始化函数 + :param win_length: 窗口长度 + :param pad_mode: 填充模式 + ''' self.win_len = win_length self.pad_mode = pad_mode def parse(self): return cde.ComputeDeltasOperation(self.win_len, DE_C_BORDER_TYPE[self.pad_mode]) - +# 给音频波形施加对比度增强效果 class Contrast(AudioTensorOperation): - """ - Apply contrast effect for audio waveform. - - Comparable with compression, this effect modifies an audio signal to make it sound louder. - - Similar to `SoX `_ implementation. - - Note: - The dimension of the audio waveform to be processed needs to be (..., time). - - Args: - enhancement_amount (float, optional): Controls the amount of the enhancement, - in range of [0, 100]. Default: 75.0. Note that `enhancement_amount` equal - to 0 still gives a significant contrast enhancement. - - Raises: - TypeError: If `enhancement_amount` is not of type float. - ValueError: If `enhancement_amount` is not in range [0, 100]. - RuntimeError: If input tensor is not in shape of <..., time>. - - Supported Platforms: - ``CPU`` - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[2.716064453125e-03, 6.34765625e-03], [9.246826171875e-03, 1.0894775390625e-02]]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.Contrast()] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 计算谱和音频的对比度 + ''' @check_contrast def __init__(self, enhancement_amount=75.0): + ''' + :param enhancement_amount: 对比度的增强量 + ''' self.enhancement_amount = enhancement_amount def parse(self): return cde.ContrastOperation(self.enhancement_amount) - +# 将音频波形从分贝转换为功率或振幅 class DBToAmplitude(AudioTensorOperation): - """ - Turn a waveform from the decibel scale to the power/amplitude scale. - - Args: - ref (float): Reference which the output will be scaled by. - power (float): If power equals 1, will compute DB to power. If 0.5, will compute DB to amplitude. - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[2.716064453125e-03, 6.34765625e-03], [9.246826171875e-03, 1.0894775390625e-02]]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.DBToAmplitude(0.5, 0.5)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 转换DB值到相位值 + ''' @check_db_to_amplitude def __init__(self, ref, power): + ''' + :param ref: 参考值 + :param power: 相位值 + ''' self.ref = ref self.power = power def parse(self): return cde.DBToAmplitudeOperation(self.ref, self.power) - +# 对输入音频波形施加直流移位 class DCShift(AudioTensorOperation): - """ - Apply a DC shift to the audio. - - Args: - shift (float): The amount to shift the audio, the value must be in the range [-2.0, 2.0]. - limiter_gain (float, optional): Used only on peaks to prevent clipping, - the value should be much less than 1, such as 0.05 or 0.02. - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([0.60, 0.97, -1.04, -1.26, 0.97, 0.91, 0.48, 0.93]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.DCShift(0.5, 0.02)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 计算音频的DC偏移量 + ''' @check_dc_shift def __init__(self, shift, limiter_gain=None): + ''' + :param shift: 音频的DC偏移量 + :param limiter_gain: 限制器的增益 + ''' self.shift = shift self.limiter_gain = limiter_gain if limiter_gain else shift def parse(self): return cde.DCShiftOperation(self.shift, self.limiter_gain) - +# 给音频波形施加CD(IEC 60908)去重音(一种高音衰减搁置滤波器)效果 class DeemphBiquad(AudioTensorOperation): - """ - Design two-pole deemph filter for audio waveform of dimension of (..., time). - - Args: - sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz), - the value must be 44100 or 48000. - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[2.716064453125e-03, 6.34765625e-03], [9.246826171875e-03, 1.0894775390625e-02]]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.DeemphBiquad(44100)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 计算DeemphBiquad操作 + ''' @check_deemph_biquad def __init__(self, sample_rate): + ''' + :param sample_rate: 波形的采样频率 + ''' self.sample_rate = sample_rate def parse(self): return cde.DeemphBiquadOperation(self.sample_rate) - +# 检测音调频率 class DetectPitchFrequency(AudioTensorOperation): - """ - Detect pitch frequency. - - It is implemented using normalized cross-correlation function and median smoothing. - - Args: - sample_rate (int): Sampling rate of the waveform, e.g. 44100 (Hz), the value can't be zero. - frame_time (float, optional): Duration of a frame, the value must be greater than zero (default=0.01). - win_length (int, optional): The window length for median smoothing (in number of frames), the value must be - greater than zero (default=30). - freq_low (int, optional): Lowest frequency that can be detected (Hz), the value must be greater than zero - (default=85). - freq_high (int, optional): Highest frequency that can be detected (Hz), the value must be greater than zero - (default=3400). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[0.716064e-03, 5.347656e-03, 6.246826e-03, 2.089477e-02, 7.138305e-02], - ... [4.156616e-02, 1.394653e-02, 3.550292e-02, 0.614379e-02, 3.840209e-02]]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.DetectPitchFrequency(30, 0.1, 3, 5, 25)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 计算音高频率的检测 + ''' @check_detect_pitch_frequency def __init__(self, sample_rate, frame_time=0.01, win_length=30, freq_low=85, freq_high=3400): + ''' + 初始化检测音高频率的操作 + :param sample_rate: 采样率 + :param frame_time: 滑动窗口的时间间隔 + :param win_length: 窗口的长度 + :param freq_low: 最低频率 + :param freq_high: 最高频率 + ''' self.sample_rate = sample_rate self.frame_time = frame_time self.win_length = win_length @@ -682,60 +330,41 @@ DE_C_DENSITY_FUNCTION = {DensityFunction.TPDF: cde.DensityFunction.DE_DENSITY_FU DensityFunction.RPDF: cde.DensityFunction.DE_DENSITY_FUNCTION_RPDF, DensityFunction.GPDF: cde.DensityFunction.DE_DENSITY_FUNCTION_GPDF} - +# 通过消除非线性截断失真,来抖动增加存储在特定位深的音频的动态感知范围 class Dither(AudioTensorOperation): - """ - Dither increases the perceived dynamic range of audio stored at a - particular bit-depth by eliminating nonlinear truncation distortion. - - Args: - density_function (DensityFunction, optional): The density function of a continuous - random variable. Can be one of DensityFunction.TPDF (Triangular Probability Density Function), - DensityFunction.RPDF (Rectangular Probability Density Function) or - DensityFunction.GPDF (Gaussian Probability Density Function) - (default=DensityFunction.TPDF). - noise_shaping (bool, optional): A filtering process that shapes the spectral - energy of quantisation error (default=False). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[1, 2, 3], [4, 5, 6]]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.Dither()] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 添加噪声的类 + ''' @check_dither def __init__(self, density_function=DensityFunction.TPDF, noise_shaping=False): + ''' + 初始化添加噪声的类 + :param density_function: 添加噪声的混合模式 + :param noise_shaping: 是否使用噪声矫正 + ''' self.density_function = density_function self.noise_shaping = noise_shaping def parse(self): + ''' + 解析添加噪声的类 + :return: + ''' return cde.DitherOperation(DE_C_DENSITY_FUNCTION[self.density_function], self.noise_shaping) - +# 给音频波形施加双二次均衡器滤波器 class EqualizerBiquad(AudioTensorOperation): - """ - Design biquad equalizer filter and perform filtering. Similar to SoX implementation. - - Args: - sample_rate (int): Sampling rate of the waveform, e.g. 44100 (Hz), the value can't be zero. - center_freq (float): Central frequency (in Hz). - gain (float): Desired gain at the boost (or attenuation) in dB. - Q (float, optional): https://en.wikipedia.org/wiki/Q_factor, range: (0, 1] (default=0.707). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[2.716064453125e-03, 6.34765625e-03], [9.246826171875e-03, 1.0894775390625e-02]]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.EqualizerBiquad(44100, 1500, 5.5, 0.7)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 比较器比特线的高通滤波器 + ''' @check_equalizer_biquad def __init__(self, sample_rate, center_freq, gain, Q=0.707): + ''' + :param sample_rate: 样本采样率 + :param center_freq: 输入频率 + :param gain: 比特率 + :param Q: 浓度 + ''' self.sample_rate = sample_rate self.center_freq = center_freq self.gain = gain @@ -751,44 +380,19 @@ DE_C_FADE_SHAPE = {FadeShape.QUARTER_SINE: cde.FadeShape.DE_FADE_SHAPE_QUARTER_S FadeShape.LOGARITHMIC: cde.FadeShape.DE_FADE_SHAPE_LOGARITHMIC, FadeShape.EXPONENTIAL: cde.FadeShape.DE_FADE_SHAPE_EXPONENTIAL} - +# 向波形添加淡入和/或淡出 class Fade(AudioTensorOperation): - """ - Add a fade in and/or fade out to an waveform. - - Args: - fade_in_len (int, optional): Length of fade-in (time frames), which must be non-negative (default=0). - fade_out_len (int, optional): Length of fade-out (time frames), which must be non-negative (default=0). - fade_shape (FadeShape, optional): Shape of fade (default=FadeShape.LINEAR). Can be one of - FadeShape.QUARTER_SINE, FadeShape.HALF_SINE, FadeShape.LINEAR, FadeShape.LOGARITHMIC or - FadeShape.EXPONENTIAL. - - -FadeShape.QUARTER_SINE, means it tend to 0 in an quarter sin function. - - -FadeShape.HALF_SINE, means it tend to 0 in an half sin function. - - -FadeShape.LINEAR, means it linear to 0. - - -FadeShape.LOGARITHMIC, means it tend to 0 in an logrithmic function. - - -FadeShape.EXPONENTIAL, means it tend to 0 in an exponential function. - - Raises: - RuntimeError: If fade_in_len exceeds waveform length. - RuntimeError: If fade_out_len exceeds waveform length. - - Examples: - >>> import numpy as np - >>> from mindspore.dataset.audio import FadeShape - >>> - >>> waveform = np.array([[2.716064453125e-03, 6.34765625e-03, 9.246826171875e-03, 1.0894775390625e-02]]) - >>> dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.Fade(fade_in_len=3, fade_out_len=2, fade_shape=FadeShape.LINEAR)] - >>> dataset = dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 淡入淡出操作 + ''' @check_fade def __init__(self, fade_in_len=0, fade_out_len=0, fade_shape=FadeShape.LINEAR): + ''' + Args: + fade_in_len: 淡入长度,默认为0 + fade_out_len: 淡出长度,默认为0 + fade_shape: 淡入淡出形状,默认为LINEAR + ''' self.fade_in_len = fade_in_len self.fade_out_len = fade_out_len self.fade_shape = fade_shape @@ -803,36 +407,26 @@ DE_C_MODULATION = {Modulation.SINUSOIDAL: cde.Modulation.DE_MODULATION_SINUSOIDA DE_C_INTERPOLATION = {Interpolation.LINEAR: cde.Interpolation.DE_INTERPOLATION_LINEAR, Interpolation.QUADRATIC: cde.Interpolation.DE_INTERPOLATION_QUADRATIC} - +# 给音频施加镶边效果 class Flanger(AudioTensorOperation): - """ - Apply a flanger effect to the audio. - - Args: - sample_rate (int): Sampling rate of the waveform, e.g. 44100 (Hz). - delay (float, optional): Desired delay in milliseconds (ms), range: [0, 30] (default=0.0). - depth (float, optional): Desired delay depth in milliseconds (ms), range: [0, 10] (default=2.0). - regen (float, optional): Desired regen (feedback gain) in dB, range: [-95, 95] (default=0.0). - width (float, optional): Desired width (delay gain) in dB, range: [0, 100] (default=71.0). - speed (float, optional): Modulation speed in Hz, range: [0.1, 10] (default=0.5). - phase (float, optional): Percentage phase-shift for multi-channel, range: [0, 100] (default=25.0). - modulation (Modulation, optional): Modulation of the input tensor (default=Modulation.SINUSOIDAL). - It can be one of Modulation.SINUSOIDAL or Modulation.TRIANGULAR. - interpolation (Interpolation, optional): Interpolation of the input tensor (default=Interpolation.LINEAR). - It can be one of Interpolation.LINEAR or Interpolation.QUADRATIC. - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[2.716064453125e-03, 6.34765625e-03], [9.246826171875e-03, 1.0894775390625e-02]]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.Flanger(44100)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + Flanger操作 + ''' @check_flanger def __init__(self, sample_rate, delay=0.0, depth=2.0, regen=0.0, width=71.0, speed=0.5, phase=25.0, modulation=Modulation.SINUSOIDAL, interpolation=Interpolation.LINEAR): + ''' + 初始化Flanger操作 + :param sample_rate: 样本频率 + :param delay: 延迟 + :param depth: 深度 + :param regen: 波缓冲 + :param width: 宽度 + :param speed: 速度 + :param phase: 偏移 + :param modulation: 模组 + :param interpolation: 插值 + ''' self.sample_rate = sample_rate self.delay = delay self.depth = depth @@ -848,53 +442,20 @@ class Flanger(AudioTensorOperation): self.phase, DE_C_MODULATION[self.modulation], DE_C_INTERPOLATION[self.interpolation]) - +# 给音频波形施加频域掩码 class FrequencyMasking(AudioTensorOperation): - """ - Apply masking to a spectrogram in the frequency domain. - - Note: - The dimension of the audio waveform to be processed needs to be (..., freq, time). - - Args: - iid_masks (bool, optional): Whether to apply different masks to each example/channel. Default: False. - freq_mask_param (int, optional): When `iid_masks` is True, length of the mask will be uniformly sampled - from [0, freq_mask_param]; When `iid_masks` is False, directly use it as length of the mask. - The value should be in range of [0, freq_length], where `freq_length` is the length of audio waveform - in frequency domain. Default: 0. - mask_start (int): Starting point to apply mask, only works when `iid_masks` is True. The value should - be in range of [0, freq_length - freq_mask_param], where `freq_length` is the length of audio waveform - in frequency domain. Default: 0. - mask_value (float, optional): Value to assign to the masked columns. Default: 0.0. - - Raises: - TypeError: If `iid_masks` is not of type bool. - TypeError: If `freq_mask_param` is not of type integer. - ValueError: If `freq_mask_param` is greater than the length of audio waveform in frequency domain. - TypeError: If `mask_start` is not of type integer. - ValueError: If `mask_start` is a negative number. - TypeError: If `mask_value` is not of type float. - ValueError: If `mask_value` is a negative number. - RuntimeError: If input tensor is not in shape of <..., freq, time>. - - Supported Platforms: - ``CPU`` - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.random.random([1, 3, 2]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.FrequencyMasking(freq_mask_param=1)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - - .. image:: frequency_masking_original.png - - .. image:: frequency_masking.png - """ - + ''' + 对音频数据进行频率掩码 + ''' @check_masking def __init__(self, iid_masks=False, freq_mask_param=0, mask_start=0, mask_value=0.0): + ''' + 频率掩码参数: + iid_masks:是否使用IID掩码 + freq_mask_param:频率掩码参数 + mask_start:掩码开始位置 + mask_value:掩码值 + ''' self.iid_masks = iid_masks self.frequency_mask_param = freq_mask_param self.mask_start = mask_start @@ -904,68 +465,41 @@ class FrequencyMasking(AudioTensorOperation): return cde.FrequencyMaskingOperation(self.iid_masks, self.frequency_mask_param, self.mask_start, self.mask_value) - +# 放大或衰减整个音频波形 class Gain(AudioTensorOperation): - """ - Apply amplification or attenuation to the whole waveform. - - Args: - gain_db (float): Gain adjustment in decibels (dB) (default=1.0). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[2.716064453125e-03, 6.34765625e-03], [9.246826171875e-03, 1.0894775390625e-02]]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.Gain(1.2)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 计算音频的增益 + ''' @check_gain def __init__(self, gain_db=1.0): + ''' + :param gain_db: 增益的数值,取值范围为[-100, 100] + ''' self.gain_db = gain_db def parse(self): return cde.GainOperation(self.gain_db) - +# 使用Griffin-Lim算法从线性幅度频谱图中计算信号波形 class GriffinLim(AudioTensorOperation): - r""" - Approximate magnitude spectrogram inversion using the GriffinLim algorithm. - - .. math:: - x(n)=\frac{\sum_{m=-\infty}^{\infty} w(m S-n) y_{w}(m S, n)}{\sum_{m=-\infty}^{\infty} w^{2}(m S-n)} - - where w represents the window function, y represents the reconstructed signal of each frame and x represents the - whole signal. - - Args: - n_fft (int, optional): Size of FFT (default=400). - n_iter (int, optional): Number of iteration for phase recovery (default=32). - win_length (int, optional): Window size for GriffinLim (default=None, will be set to n_fft). - hop_length (int, optional): Length of hop between STFT windows (default=None, will be set to win_length // 2). - window_type (WindowType, optional): Window type for GriffinLim, which can be WindowType.BARTLETT, - WindowType.BLACKMAN, WindowType.HAMMING, WindowType.HANN or WindowType.KAISER (default=WindowType.HANN). - Currently kaiser window is not supported on macOS. - power (float, optional): Exponent for the magnitude spectrogram (default=2.0). - momentum (float, optional): The momentum for fast Griffin-Lim (default=0.99). - length (int, optional): Length of the expected output waveform (default=None, will be set to the value of last - dimension of the stft matrix). - rand_init (bool, optional): Flag for random phase initialization or all-zero phase initialization - (default=True). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.random.random([201, 6]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.GriffinLim(n_fft=400)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + GriffinLim算法 + ''' @check_griffin_lim def __init__(self, n_fft=400, n_iter=32, win_length=None, hop_length=None, window_type=WindowType.HANN, power=2, momentum=0.99, length=None, rand_init=True): + ''' + 参数: + n_fft: 窗口的长度 + n_iter: 步长 + win_length: 窗口的长度,如果没有指定,则使用n_fft + hop_length: 窗口的步长,如果没有指定,则使用win_length的一半 + window_type: 窗口类型 + power: 窗口的加权平方根 + momentum: 梯度移动平均系数 + length: 数据长度,如果没有指定,则使用数据的长度 + rand_init: 是否使用随机初始化 + ''' self.n_fft = n_fft self.n_iter = n_iter self.win_length = win_length if win_length else self.n_fft @@ -981,27 +515,18 @@ class GriffinLim(AudioTensorOperation): DE_C_WINDOW_TYPE.get(self.window_type), self.power, self.momentum, self.length, self.rand_init) - +# 给音频波形上施加双二阶高通滤波器 class HighpassBiquad(AudioTensorOperation): - """ - Design biquad highpass filter and perform filtering. Similar to SoX implementation. - - Args: - sample_rate (int): Sampling rate of the waveform, e.g. 44100 (Hz), the value can't be zero. - cutoff_freq (float): Filter cutoff frequency (in Hz). - Q (float, optional): Quality factor, https://en.wikipedia.org/wiki/Q_factor, range: (0, 1] (default=0.707). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[2.716064453125e-03, 6.34765625e-03], [9.246826171875e-03, 1.0894775390625e-02]]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.HighpassBiquad(44100, 1500, 0.7)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 高通滤波器,可以用于高通滤波器的高通滤波器 + ''' @check_highpass_biquad def __init__(self, sample_rate, cutoff_freq, Q=0.707): + ''' + :param sample_rate: 样本采样率 + :param cutoff_freq: 过滤频率 + :param Q: 过滤系数 + ''' self.sample_rate = sample_rate self.cutoff_freq = cutoff_freq self.Q = Q @@ -1009,33 +534,18 @@ class HighpassBiquad(AudioTensorOperation): def parse(self): return cde.HighpassBiquadOperation(self.sample_rate, self.cutoff_freq, self.Q) - +# 根据指定的差分方程施加IIR滤波器 class LFilter(AudioTensorOperation): - """ - Design two-pole filter for audio waveform of dimension of (..., time). - - Args: - a_coeffs (sequence): denominator coefficients of difference equation of dimension of (n_order + 1). - Lower delays coefficients are first, e.g. [a0, a1, a2, ...]. - Must be same size as b_coeffs (pad with 0's as necessary). - b_coeffs (sequence): numerator coefficients of difference equation of dimension of (n_order + 1). - Lower delays coefficients are first, e.g. [b0, b1, b2, ...]. - Must be same size as a_coeffs (pad with 0's as necessary). - clamp (bool, optional): If True, clamp the output signal to be in the range [-1, 1] (default=True). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[2.716064453125e-03, 6.34765625e-03], [9.246826171875e-03, 1.0894775390625e-02]]) - >>> a_coeffs = [0.1, 0.2, 0.3] - >>> b_coeffs = [0.1, 0.2, 0.3] - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.LFilter(a_coeffs, b_coeffs)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 过滤器,可以通过调用LFilterOperation的parse方法获取 + ''' @check_lfilter def __init__(self, a_coeffs, b_coeffs, clamp=True): + ''' + :param a_coeffs: a_coeffs[0]为a的系数,a_coeffs[1]为b的系数,a_coeffs[2]为b的系数,... + :param b_coeffs: b_coeffs[0]为a的系数,b_coeffs[1]为b的系数,b_coeffs[2]为b的系数,... + :param clamp: 是否禁用clamp + ''' self.a_coeffs = a_coeffs self.b_coeffs = b_coeffs self.clamp = clamp @@ -1043,51 +553,19 @@ class LFilter(AudioTensorOperation): def parse(self): return cde.LFilterOperation(self.a_coeffs, self.b_coeffs, self.clamp) - +# 给音频波形施加双极点低通滤波器 class LowpassBiquad(AudioTensorOperation): - r""" - Design two-pole low-pass filter for audio waveform. - - A low-pass filter passes frequencies lower than a selected cutoff frequency - but attenuates frequencies higher than it. The system function is: - - .. math:: - H(s) = \frac{1}{s^2 + \frac{s}{Q} + 1} - - Similar to `SoX `_ implementation. - - Note: - The dimension of the audio waveform to be processed needs to be (..., time). - - Args: - sample_rate (int): Sampling rate (in Hz), which can't be zero. - cutoff_freq (float): Filter cutoff frequency (in Hz). - Q (float, optional): `Quality factor `_ , - in range of (0, 1]. Default: 0.707. - - Raises: - TypeError: If `sample_rate` is not of type integer. - ValueError: If `sample_rate` is 0. - TypeError: If `cutoff_freq` is not of type float. - TypeError: If `Q` is not of type float. - ValueError: If `Q` is not in range of (0, 1]. - RuntimeError: If input tensor is not in shape of <..., time>. - - Supported Platforms: - ``CPU`` - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[0.8236, 0.2049, 0.3335], [0.5933, 0.9911, 0.2482], - ... [0.3007, 0.9054, 0.7598], [0.5394, 0.2842, 0.5634], [0.6363, 0.2226, 0.2288]]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.LowpassBiquad(4000, 1500, 0.7)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 计算一个低通滤波器,参数为采样率,周期,Q值 + ''' @check_lowpass_biquad def __init__(self, sample_rate, cutoff_freq, Q=0.707): + ''' + 初始化低通滤波器 + :param sample_rate: 采样率 + :param cutoff_freq: 周期 + :param Q: Q值 + ''' self.sample_rate = sample_rate self.cutoff_freq = cutoff_freq self.Q = Q @@ -1095,52 +573,35 @@ class LowpassBiquad(AudioTensorOperation): def parse(self): return cde.LowpassBiquadOperation(self.sample_rate, self.cutoff_freq, self.Q) - +# 将shape为(..., 2)的复值光谱图分离,输出幅度和相位 class Magphase(AudioTensorOperation): - """ - Separate a complex-valued spectrogram with shape (..., 2) into its magnitude and phase. - - Args: - power (float): Power of the norm, which must be non-negative (default=1.0). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.random.random([2, 4, 2]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.Magphase()] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 计算输入的音频数据的相位 + ''' @check_magphase def __init__(self, power=1.0): + ''' + 初始化Magphase类 + :param power: 相位的平方根 + ''' self.power = power def parse(self): return cde.MagphaseOperation(self.power) - +# 对音频波形应用掩码 class MaskAlongAxis(AudioTensorOperation): - """ - Apply a mask along `axis`. Mask will be applied from indices `[mask_start, mask_start + mask_width)`. - - Args: - mask_start (int): Starting position of the mask, which must be non negative. - mask_width (int): The width of the mask, which must be non negative. - mask_value (float): Value to assign to the masked columns. - axis (int): Axis to apply masking on (1 for frequency and 2 for time). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.random.random([1, 20, 20]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.MaskAlongAxis(0, 10, 0.5, 1)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 按照指定的轴mask,从mask_start开始,按照mask_width宽度mask,mask_value值mask + ''' @check_mask_along_axis def __init__(self, mask_start, mask_width, mask_value, axis): + ''' + :param mask_start: 指定mask的起始位置 + :param mask_width: 指定mask的宽度 + :param mask_value: 指定mask的值 + :param axis: 指定mask的轴 + ''' self.mask_start = mask_start self.mask_width = mask_width self.mask_value = mask_value @@ -1149,30 +610,18 @@ class MaskAlongAxis(AudioTensorOperation): def parse(self): return cde.MaskAlongAxisOperation(self.mask_start, self.mask_width, self.mask_value, self.axis) - +# 对音频波形沿 axis 轴应用掩码 class MaskAlongAxisIID(AudioTensorOperation): - """ - Apply a mask along `axis`. Mask will be applied from indices `[mask_start, mask_start + mask_width)`, where - `mask_width` is sampled from `uniform[0, mask_param]`, and `mask_start` from `uniform[0, max_length - mask_width]`, - `max_length` is the number of columns of the specified axis of the spectrogram. - - Args: - mask_param (int): Number of columns to be masked, will be uniformly sampled from - [0, mask_param], must be non negative. - mask_value (float): Value to assign to the masked columns. - axis (int): Axis to apply masking on (1 for frequency and 2 for time). - - Examples: - >>> import numpy as np - >>> - >>> waveform= np.random.random([1, 20, 20]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.MaskAlongAxisIID(5, 0.5, 2)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 按照指定的维度mask,指定的值 + ''' @check_mask_along_axis_iid def __init__(self, mask_param, mask_value, axis): + ''' + :param mask_param: mask的参数 + :param mask_value: mask的值 + :param axis: mask的维度 + ''' self.mask_param = mask_param self.mask_value = mask_value self.axis = axis @@ -1187,35 +636,24 @@ DE_C_MEL_TYPE = {MelType.SLANEY: cde.MelType.DE_MEL_TYPE_SLANEY, DE_C_NORM_TYPE = {NormType.NONE: cde.NormType.DE_NORM_TYPE_NONE, NormType.SLANEY: cde.NormType.DE_NORM_TYPE_SLANEY} - +# 将普通STFT转换为梅尔尺度的STFT class MelScale(AudioTensorOperation): - """ - Convert normal STFT to STFT at the Mel scale. - - Args: - n_mels (int, optional): Number of mel filterbanks (default=128). - sample_rate (int, optional): Sample rate of audio signal (default=16000). - f_min (float, optional): Minimum frequency (default=0). - f_max (float, optional): Maximum frequency (default=None, will be set to sample_rate // 2). - n_stft (int, optional): Number of bins in STFT (default=201). - norm (NormType, optional): Type of norm, value should be NormType.SLANEY or NormType::NONE. - If norm is NormType.SLANEY, divide the triangular mel weight by the width of the mel band. - (default=NormType.NONE). - mel_type (MelType, optional): Type to use, value should be MelType.SLANEY or MelType.HTK (default=MelType.HTK). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[0.8236, 0.2049, 0.3335], [0.5933, 0.9911, 0.2482], - ... [0.3007, 0.9054, 0.7598], [0.5394, 0.2842, 0.5634], [0.6363, 0.2226, 0.2288]]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.MelScale(4000, 1500, 0.7)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + MelScale操作 + ''' @check_mel_scale def __init__(self, n_mels=128, sample_rate=16000, f_min=0, f_max=None, n_stft=201, norm=NormType.NONE, mel_type=MelType.HTK): + ''' + 初始化MelScale操作 + :param n_mels: mel空间的大小 + :param sample_rate: 采样率 + :param f_min: 将音频转换为mel空间的最小频率 + :param f_max: 将音频转换为mel空间的最大频率,如果没有指定则默认为采样率的一半 + :param n_stft: 将音频转换为mel空间的维度 + :param norm: 标准化类型 + :param mel_type: mel空间类型 + ''' self.n_mels = n_mels self.sample_rate = sample_rate self.f_min = f_min @@ -1228,111 +666,74 @@ class MelScale(AudioTensorOperation): return cde.MelScaleOperation(self.n_mels, self.sample_rate, self.f_min, self.f_max, self.n_stft, DE_C_NORM_TYPE[self.norm], DE_C_MEL_TYPE[self.mel_type]) - +# 解码mu-law编码的信号,参考 mu-law算法 class MuLawDecoding(AudioTensorOperation): - """ - Decode mu-law encoded signal. - - Args: - quantization_channels (int): Number of channels, which must be positive (Default: 256). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.random.random([1, 3, 4]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.MuLawDecoding()] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 解码MuLaw编码的数据 + ''' @check_mu_law_coding def __init__(self, quantization_channels=256): + ''' + 构造函数 + :param quantization_channels: 目标量化通道数 + ''' self.quantization_channels = quantization_channels def parse(self): return cde.MuLawDecodingOperation(self.quantization_channels) - +# 基于mu-law压缩的信号编码 class MuLawEncoding(AudioTensorOperation): - """ - Encode signal based on mu-law companding. - - Args: - quantization_channels (int): Number of channels, which must be positive (Default: 256). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.random.random([1, 3, 4]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.MuLawEncoding()] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 编码器 + ''' @check_mu_law_coding def __init__(self, quantization_channels=256): + ''' + 初始化编码器 + :param quantization_channels: 目标量化通道数 + ''' self.quantization_channels = quantization_channels def parse(self): return cde.MuLawEncodingOperation(self.quantization_channels) - +# 给音频波形施加过载效果 class Overdrive(AudioTensorOperation): - """ - Apply overdrive on input audio. - - Args: - gain (float): Desired gain at the boost (or attenuation) in dB, in range of [0, 100] (default=20.0). - color (float): Controls the amount of even harmonic content in the over-driven output, - in range of [0, 100] (default=20.0). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.Overdrive()] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 设置音频的音量和颜色 + ''' @check_overdrive def __init__(self, gain=20.0, color=20.0): + ''' + :param gain: 音量 + :param color: 颜色 + ''' self.gain = gain self.color = color def parse(self): return cde.OverdriveOperation(self.gain, self.color) - +# 给音频波形施加相位效果 class Phaser(AudioTensorOperation): - """ - Apply a phasing effect to the audio. - - Args: - sample_rate (int): Sampling rate of the waveform, e.g. 44100 (Hz). - gain_in (float): Desired input gain at the boost (or attenuation) in dB. - Allowed range of values is [0, 1] (default=0.4). - gain_out (float): Desired output gain at the boost (or attenuation) in dB. - Allowed range of values is [0, 1e9] (default=0.74). - delay_ms (float): Desired delay in milli seconds. Allowed range of values is [0, 5] (default=3.0). - decay (float): Desired decay relative to gain-in. Allowed range of values is [0, 0.99] (default=0.4). - mod_speed (float): Modulation speed in Hz. Allowed range of values is [0.1, 2] (default=0.5). - sinusoidal (bool): If True, use sinusoidal modulation (preferable for multiple instruments). - If False, use triangular modulation (gives single instruments a sharper - phasing effect) (default=True). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.Phaser(44100)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + Phaser操作: + ''' @check_phaser def __init__(self, sample_rate, gain_in=0.4, gain_out=0.74, delay_ms=3.0, decay=0.4, mod_speed=0.5, sinusoidal=True): + ''' + 初始化Phaser操作 + 参数: + sample_rate:采样率 + gain_in:输入增益 + gain_out:输出增益 + delay_ms:延迟毫秒数 + decay:衰减系数 + mod_speed:模拟速度 + sinusoidal:是否模拟 + ''' self.decay = decay self.delay_ms = delay_ms self.gain_in = gain_in @@ -1345,82 +746,53 @@ class Phaser(AudioTensorOperation): return cde.PhaserOperation(self.sample_rate, self.gain_in, self.gain_out, self.delay_ms, self.decay, self.mod_speed, self.sinusoidal) - +# 对给定的STFT频谱,在不改变音高的情况下以一定比率进行加速 class PhaseVocoder(AudioTensorOperation): - """ - Given a STFT tensor, speed up in time without modifying pitch by a factor of rate. - - Args: - rate (float): Speed-up factor. - phase_advance (numpy.ndarray): Expected phase advance in each bin in shape of (freq, 1). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.random.randn(2, 44, 10, 2) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> phase_advance = np.random.randn(44, 1) - >>> transforms = [audio.PhaseVocoder(rate=2, phase_advance=phase_advance)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 基于频谱的频谱转换 + ''' @check_phase_vocoder def __init__(self, rate, phase_advance): + ''' + :param rate: 采样率 + :param phase_advance: 频谱转换的时间间隔 + ''' self.rate = rate self.phase_advance = cde.Tensor(phase_advance) def parse(self): return cde.PhaseVocoderOperation(self.rate, self.phase_advance) - +# 对输入音频波形施加RIAA均衡 class RiaaBiquad(AudioTensorOperation): - """ - Apply RIAA vinyl playback equalization. Similar to SoX implementation. - - Args: - sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz), - can only be one of 44100, 48000, 88200, 96000. - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float64) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.RiaaBiquad(44100)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 构建一个RiaaBiquad操作 + ''' @check_riaa_biquad def __init__(self, sample_rate): + ''' + 构建一个RiaaBiquad操作 + :param sample_rate: 样本频率 + ''' self.sample_rate = sample_rate def parse(self): return cde.RiaaBiquadOperation(self.sample_rate) - +# 对每个话语应用滑动窗口倒谱均值(和可选方差)归一化 class SlidingWindowCmn(AudioTensorOperation): - """ - Apply sliding-window cepstral mean (and optionally variance) normalization per utterance. - - Args: - cmn_window (int, optional): Window in frames for running average CMN computation (default=600). - min_cmn_window (int, optional): Minimum CMN window used at start of decoding (adds latency only at start). - Only applicable if center is False, ignored if center is True (default=100). - center (bool, optional): If True, use a window centered on the current frame. If False, window is - to the left. (default=False). - norm_vars (bool, optional): If True, normalize variance to one. (default=False). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[[1, 2, 3], [4, 5, 6]]], dtype=np.float64) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.SlidingWindowCmn()] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 计算滑动窗口的CMN + ''' @check_sliding_window_cmn def __init__(self, cmn_window=600, min_cmn_window=100, center=False, norm_vars=False): + ''' + 设置滑动窗口的CMN参数 + :param cmn_window: 滑动窗口的长度 + :param min_cmn_window: 最小的CMN长度 + :param center: 是否在滑动窗口的中间 + :param norm_vars: 是否归一化变量 + ''' self.cmn_window = cmn_window self.min_cmn_window = min_cmn_window self.center = center @@ -1436,32 +808,21 @@ DE_C_WINDOW_TYPE = {WindowType.BARTLETT: cde.WindowType.DE_WINDOW_TYPE_BARTLETT, WindowType.HANN: cde.WindowType.DE_WINDOW_TYPE_HANN, WindowType.KAISER: cde.WindowType.DE_WINDOW_TYPE_KAISER} - +# 计算每个通道沿时间轴的频谱中心 class SpectralCentroid(TensorOperation): - """ - Create a spectral centroid from an audio signal. - - Args: - sample_rate (int): Sampling rate of the waveform, e.g. 44100 (Hz). - n_fft (int, optional): Size of FFT, creates n_fft // 2 + 1 bins (default=400). - win_length (int, optional): Window size (default=None, will use n_fft). - hop_length (int, optional): Length of hop between STFT windows (default=None, will use win_length // 2). - pad (int, optional): Two sided padding of signal (default=0). - window (WindowType, optional): Window function that is applied/multiplied to each frame/window, - which can be WindowType.BARTLETT, WindowType.BLACKMAN, WindowType.HAMMING, WindowType.HANN - or WindowType.KAISER (default=WindowType.HANN). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.random.random([5, 10, 20]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.SpectralCentroid(44100)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 计算振幅谱中心点 + ''' @check_spectral_centroid def __init__(self, sample_rate, n_fft=400, win_length=None, hop_length=None, pad=0, window=WindowType.HANN): + ''' + :param sample_rate: 样本采样率 + :param n_fft: 快速傅里叶变换的窗长度 + :param win_length: 窗长度,默认为n_fft + :param hop_length: 间隔长度,默认为win_length // 2 + :param pad: 填充,默认为0 + :param window: 窗函数,默认为HANN + ''' self.sample_rate = sample_rate self.pad = pad self.window = window @@ -1473,40 +834,27 @@ class SpectralCentroid(TensorOperation): return cde.SpectralCentroidOperation(self.sample_rate, self.n_fft, self.win_length, self.hop_length, self.pad, DE_C_WINDOW_TYPE[self.window]) - +# 从音频信号创建其频谱 class Spectrogram(TensorOperation): - """ - Create a spectrogram from an audio signal. - - Args: - n_fft (int, optional): Size of FFT, creates n_fft // 2 + 1 bins (default=400). - win_length (int, optional): Window size (default=None, will use n_fft). - hop_length (int, optional): Length of hop between STFT windows (default=None, will use win_length // 2). - pad (int): Two sided padding of signal (default=0). - window (WindowType, optional): Window function that is applied/multiplied to each frame/window, - which can be WindowType.BARTLETT, WindowType.BLACKMAN, WindowType.HAMMING, WindowType.HANN - or WindowType.KAISER (default=WindowType.HANN). Currently kaiser window is not supported on macOS. - power (float, optional): Exponent for the magnitude spectrogram, which must be greater - than or equal to 0, e.g., 1 for energy, 2 for power, etc. (default=2.0). - normalized (bool, optional): Whether to normalize by magnitude after stft (default=False). - center (bool, optional): Whether to pad waveform on both sides (default=True). - pad_mode (BorderType, optional): Controls the padding method used when center is True, - which can be BorderType.REFLECT, BorderType.CONSTANT, BorderType.EDGE, BorderType.SYMMETRIC - (default=BorderType.REFLECT). - onesided (bool, optional): Controls whether to return half of results to avoid redundancy (default=True). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.random.random([5, 10, 20]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.Spectrogram()] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 计算频谱图 + ''' @check_spectrogram def __init__(self, n_fft=400, win_length=None, hop_length=None, pad=0, window=WindowType.HANN, power=2.0, normalized=False, center=True, pad_mode=BorderType.REFLECT, onesided=True): + ''' + 初始化 + :param n_fft: 窗口大小 + :param win_length: 窗口长度,如果为None则使用窗口大小 + :param hop_length: 间隔长度 + :param pad: 填充 + :param window: 窗口类型 + :param power: 细胞平方 + :param normalized: 标准化 + :param center: 偏移 + :param pad_mode: 填充模式 + :param onesided: 奇数频谱 + ''' self.n_fft = n_fft self.win_length = win_length if win_length else n_fft self.hop_length = hop_length if hop_length else self.win_length // 2 @@ -1523,53 +871,20 @@ class Spectrogram(TensorOperation): DE_C_WINDOW_TYPE[self.window], self.power, self.normalized, self.center, DE_C_BORDER_TYPE[self.pad_mode], self.onesided) - +# 给音频波形施加时域掩码 class TimeMasking(AudioTensorOperation): - """ - Apply masking to a spectrogram in the time domain. - - Note: - The dimension of the audio waveform to be processed needs to be (..., freq, time). - - Args: - iid_masks (bool, optional): Whether to apply different masks to each example/channel. Default: False. - time_mask_param (int): When `iid_masks` is True, length of the mask will be uniformly sampled - from [0, time_mask_param]; When `iid_masks` is False, directly use it as length of the mask. - The value should be in range of [0, time_length], where `time_length` is the length of audio waveform - in time domain. Default: 0. - mask_start (int): Starting point to apply mask, only works when `iid_masks` is True. The value should - be in range of [0, time_length - time_mask_param], where `time_length` is the length of audio waveform - in time domain. Default: 0. - mask_value (float, optional): Value to assign to the masked columns. Default: 0.0. - - Raises: - TypeError: If `iid_masks` is not of type bool. - TypeError: If `time_mask_param` is not of type integer. - ValueError: If `time_mask_param` is greater than the length of audio waveform in time domain. - TypeError: If `mask_start` is not of type integer. - ValueError: If `mask_start` a negative number. - TypeError: If `mask_value` is not of type float. - ValueError: If `mask_value` is a negative number. - RuntimeError: If input tensor is not in shape of <..., freq, time>. - - Supported Platforms: - ``CPU`` - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.random.random([1, 3, 2]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.TimeMasking(time_mask_param=1)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - - .. image:: time_masking_original.png - - .. image:: time_masking.png - """ - + ''' + 掩码时间序列 + ''' @check_masking def __init__(self, iid_masks=False, time_mask_param=0, mask_start=0, mask_value=0.0): + ''' + 参数: + iid_masks:是否使用IID掩码 + time_mask_param:时间掩码参数 + mask_start:掩码起始位置 + mask_value:掩码值 + ''' self.iid_masks = iid_masks self.time_mask_param = time_mask_param self.mask_start = mask_start @@ -1578,51 +893,18 @@ class TimeMasking(AudioTensorOperation): def parse(self): return cde.TimeMaskingOperation(self.iid_masks, self.time_mask_param, self.mask_start, self.mask_value) - +# 以给定的比例拉伸音频短时傅里叶(Short Time Fourier Transform, STFT)频谱的时域,但不改变音频的音高 class TimeStretch(AudioTensorOperation): - """ - Stretch Short Time Fourier Transform (STFT) in time without modifying pitch for a given rate. - - Note: - The dimension of the audio waveform to be processed needs to be (..., freq, time, complex=2). - The first dimension represents the real part while the second represents the imaginary. - - Args: - hop_length (int, optional): Length of hop between STFT windows, i.e. the number of samples - between consecutive frames. Default: None, will use `n_freq - 1`. - n_freq (int, optional): Number of filter banks from STFT. Default: 201. - fixed_rate (float, optional): Rate to speed up or slow down by. Default: None, will keep - the original rate. - - Raises: - TypeError: If `hop_length` is not of type integer. - ValueError: If `hop_length` is not a positive number. - TypeError: If `n_freq` is not of type integer. - ValueError: If `n_freq` is not a positive number. - TypeError: If `fixed_rate` is not of type float. - ValueError: If `fixed_rate` is not a positive number. - RuntimeError: If input tensor is not in shape of <..., freq, num_frame, complex=2>. - - Supported Platforms: - ``CPU`` - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.random.random([1, 30]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.TimeStretch()] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - - .. image:: time_stretch_rate1.5.png - - .. image:: time_stretch_original.png - - .. image:: time_stretch_rate0.8.png - """ - + ''' + 按照指定的hop_length和n_freq,将音频转换成固定的比例比特率 + ''' @check_time_stretch def __init__(self, hop_length=None, n_freq=201, fixed_rate=None): + ''' + :param hop_length: 比特率的步长 + :param n_freq: 频率的数量 + :param fixed_rate: 固定的比例 + ''' self.n_freq = n_freq self.fixed_rate = fixed_rate @@ -1633,28 +915,20 @@ class TimeStretch(AudioTensorOperation): def parse(self): return cde.TimeStretchOperation(self.hop_length, self.n_freq, self.fixed_rate) - +# 给音频波形施加高音音调控制效果 class TrebleBiquad(AudioTensorOperation): - """ - Design a treble tone-control effect. Similar to SoX implementation. - - Args: - sample_rate (int): Sampling rate of the waveform, e.g. 44100 (Hz), the value can't be zero. - gain (float): Desired gain at the boost (or attenuation) in dB. - central_freq (float, optional): Central frequency (in Hz) (default=3000). - Q(float, optional): Quality factor, https://en.wikipedia.org/wiki/Q_factor, range: (0, 1] (default=0.707). - - Examples: - >>> import numpy as np - >>> - >>> waveform = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float64) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.TrebleBiquad(44100, 200.0)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 提供音频谱滤波器的类 + ''' @check_treble_biquad def __init__(self, sample_rate, gain, central_freq=3000, Q=0.707): + ''' + 初始化 + :param sample_rate: 样本采样率 + :param gain: 增益 + :param central_freq: 中心频率 + :param Q: 系数 + ''' self.sample_rate = sample_rate self.gain = gain self.central_freq = central_freq @@ -1668,31 +942,17 @@ DE_C_GAIN_TYPE = {GainType.AMPLITUDE: cde.GainType.DE_GAIN_TYPE_AMPLITUDE, GainType.POWER: cde.GainType.DE_GAIN_TYPE_POWER, GainType.DB: cde.GainType.DE_GAIN_TYPE_DB} - +# 调整波形的音量 class Vol(AudioTensorOperation): - """ - Apply amplification or attenuation to the whole waveform. - - Args: - gain (float): Value of gain adjustment. - If gain_type = amplitude, gain stands for nonnegative amplitude ratio. - If gain_type = power, gain stands for power. - If gain_type = db, gain stands for decibels. - gain_type (GainType, optional): Type of gain, contains the following three enumeration values - GainType.AMPLITUDE, GainType.POWER and GainType.DB (default=GainType.AMPLITUDE). - - Examples: - >>> import numpy as np - >>> from mindspore.dataset.audio import GainType - >>> - >>> waveform = np.random.random([20, 30]) - >>> numpy_slices_dataset = ds.NumpySlicesDataset(data=waveform, column_names=["audio"]) - >>> transforms = [audio.Vol(gain=10, gain_type=GainType.DB)] - >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms, input_columns=["audio"]) - """ - + ''' + 添加一个增益和增益类型的操作 + ''' @check_vol def __init__(self, gain, gain_type=GainType.AMPLITUDE): + ''' + :param gain: 增益值 + :param gain_type: 增益类型 + ''' self.gain = gain self.gain_type = gain_type diff --git a/mindspore/python/mindspore/dataset/audio/utils.py b/mindspore/python/mindspore/dataset/audio/utils.py index 9b0bebd5e03..a7a518e0906 100644 --- a/mindspore/python/mindspore/dataset/audio/utils.py +++ b/mindspore/python/mindspore/dataset/audio/utils.py @@ -21,18 +21,16 @@ import mindspore._c_dataengine as cde from mindspore.dataset.core.validator_helpers import check_non_negative_float32, check_non_negative_int32, \ check_pos_float32, check_pos_int32, type_check - +# 填充模式,可能的值为BorderType.CONSTANT, BorderType.EDGE, BorderType.REFLECT, BorderType.SYMMETRIC class BorderType(str, Enum): """ Padding Mode, BorderType Type. Possible enumeration values are: BorderType.CONSTANT, BorderType.EDGE, BorderType.REFLECT, BorderType.SYMMETRIC. - - - BorderType.CONSTANT: means it fills the border with constant values. - - BorderType.EDGE: means it pads with the last value on the edge. - - BorderType.REFLECT: means it reflects the values on the edge omitting the last value of edge. - - BorderType.SYMMETRIC: means it reflects the values on the edge repeating the last value of edge. - + BorderType.CONSTANT - 使用常量值填充。 + BorderType.EDGE - 使用各边的边界像素值填充。 + BorderType.REFLECT - 以各边的边界为轴进行镜像填充,忽略边界像素值。 例如,向 [1, 2, 3, 4] 的两边分别填充2个元素,结果为 [3, 2, 1, 2, 3, 4, 3, 2]。 + BorderType.SYMMETRIC - 以各边的边界为轴进行对称填充,包括边界像素值。 例如,向 [1, 2, 3, 4] 的两边分别填充2个元素,结果为 [2, 1, 1, 2, 3, 4, 4, 3] Note: This class derived from class str to support json serializable. """ CONSTANT: str = "constant" @@ -40,7 +38,7 @@ class BorderType(str, Enum): REFLECT: str = "reflect" SYMMETRIC: str = "symmetric" - +# 密度函数类型,可能的值为DensityFunction.TPDF, DensityFunction.RPDF, DensityFunction.GPDF. class DensityFunction(str, Enum): """ Density Functions. @@ -48,15 +46,15 @@ class DensityFunction(str, Enum): Possible enumeration values are: DensityFunction.TPDF, DensityFunction.RPDF, DensityFunction.GPDF. - - DensityFunction.TPDF: means triangular probability density function. - - DensityFunction.RPDF: means rectangular probability density function. - - DensityFunction.GPDF: means gaussian probability density function. + DensityFunction.TPDF:三角概率密度函数。 + DensityFunction.RPDF:矩形概率密度函数。 + DensityFunction.GPDF:高斯概率密度函数。 """ TPDF: str = "TPDF" RPDF: str = "RPDF" GPDF: str = "GPDF" - +# 淡入淡出形状,可能的值为FadeShape.QUARTER_SINE, FadeShape.HALF_SINE, FadeShape.LINEAR, FadeShape.LOGARITHMIC, FadeShape.EXPONENTIAL. class FadeShape(str, Enum): """ Fade Shapes. @@ -64,11 +62,11 @@ class FadeShape(str, Enum): Possible enumeration values are: FadeShape.QUARTER_SINE, FadeShape.HALF_SINE, FadeShape.LINEAR, FadeShape.LOGARITHMIC, FadeShape.EXPONENTIAL. - - FadeShape.QUARTER_SINE: means the fade shape is quarter_sine mode. - - FadeShape.HALF_SINE: means the fade shape is half_sine mode. - - FadeShape.LINEAR: means the fade shape is linear mode. - - FadeShape.LOGARITHMIC: means the fade shape is logarithmic mode. - - FadeShape.EXPONENTIAL: means the fade shape is exponential mode. + FadeShape.QUARTER_SINE:表示淡入淡出形状为四分之一正弦模式。 + FadeShape.HALF_SINE:表示淡入形状为半正弦模式。 + FadeShape.LINEAR:表示淡入淡出形状为线性模式。 + FadeShape.LOGARITHMIC:表示淡入淡出形状为对数模式。 + FadeShape.EXPONENTIAL:表示淡入淡出形状为指数模式。 """ QUARTER_SINE: str = "quarter_sine" HALF_SINE: str = "half_sine" @@ -76,100 +74,100 @@ class FadeShape(str, Enum): LOGARITHMIC: str = "logarithmic" EXPONENTIAL: str = "exponential" - +# 增益类型,可能的值为GainType.AMPLITUDE, GainType.POWER, GainType.DB class GainType(str, Enum): """" Gain Types. Possible enumeration values are: GainType.AMPLITUDE, GainType.POWER, GainType.DB. - - GainType.AMPLITUDE: means input gain type is amplitude. - - GainType.POWER: means input gain type is power. - - GainType.DB: means input gain type is decibel. + GainType.AMPLITUDE - 表示输入增益类型为振幅。 + GainType.POWER - 表示输入增益类型为功率。 + GainType.DB - 表示输入增益类型为分贝。 """ AMPLITUDE: str = "amplitude" POWER: str = "power" DB: str = "db" - +# 音频波形的插值模式,可能的值为Interpolation.LINEAR, Interpolation.QUADRATIC class Interpolation(str, Enum): """ Interpolation Type. Possible enumeration values are: Interpolation.LINEAR, Interpolation.QUADRATIC. - - Interpolation.LINEAR: means input interpolation type is linear. - - Interpolation.QUADRATIC: means input interpolation type is quadratic. + Interpolation.LINEAR - 插值模式为线性。 + Interpolation.QUADRATIC - 插值模式为二次型。 """ LINEAR: str = "linear" QUADRATIC: str = "quadratic" - +# 梅尔标度实现类型,可能的值为MelType.HTK, MelType.SLANEY class MelType(str, Enum): """ Mel Types. Possible enumeration values are: MelType.HTK, MelType.SLANEY. - - MelType.NONE: scale the input data with htk. - - MelType.ORTHO: scale the input data with slaney. + MelType.HTK - 隐马尔可夫工具包(HTK)实现,参考 HTK 。 + MelType.SLANEY - MATLAB听觉工具包的Slaney实现, 参考 Auditory Toolbox 。 """ HTK: str = "htk" SLANEY: str = "slaney" - +# 调制类型,可能的值为Modulation.SINUSOIDAL, Modulation.TRIANGULAR class Modulation(str, Enum): """ Modulation Type. Possible enumeration values are: Modulation.SINUSOIDAL, Modulation.TRIANGULAR. - - Modulation.SINUSOIDAL: means input modulation type is sinusoidal. - - Modulation.TRIANGULAR: means input modulation type is triangular. + Modulation.SINUSOIDAL - 表示输入调制类型为正弦。 + Modulation.TRIANGULAR - 表示输入调制类型为三角形。 """ SINUSOIDAL: str = "sinusoidal" TRIANGULAR: str = "triangular" - +# 标准化模式,可能的值为NormMode.ORTHO, NormMode.NONE class NormMode(str, Enum): """ Norm Types. Possible enumeration values are: NormMode.ORTHO, NormMode.NONE. - - NormMode.ORTHO: means the mode of input audio is ortho. - - NormMode.NONE: means the mode of input audio is none. + NormMode.ORTHO - 使用正交标准化的DCT基。 + NormMode.NONE - 不使用标准化。 """ ORTHO: str = "ortho" NONE: str = "none" - +# 标准化类型,可能的值为NormType.SLANEY, NormType.NONE class NormType(str, Enum): """ Norm Types. Possible enumeration values are: NormType.SLANEY, NormType.NONE. - - NormType.SLANEY: norm the input data with slaney. - - NormType.NONE: norm the input data with none. + NormType.SLANEY - 使用面积标准化。 + NormType.None - 不使用标准化。 """ SLANEY: str = "slaney" NONE: str = "none" - +# 音频标度枚举类,可能的值为ScaleType.POWER, ScaleType.MAGNITUDE class ScaleType(str, Enum): """ Scale Types. Possible enumeration values are: ScaleType.POWER, ScaleType.MAGNITUDE. - - ScaleType.POWER: means the scale of input audio is power. - - ScaleType.MAGNITUDE: means the scale of input audio is magnitude. + ScaleType.MAGNITUDE - 表示输入音频的标度为振幅。 + ScaleType.POWER - 表示输入音频的标度为功率。 """ POWER: str = "power" MAGNITUDE: str = "magnitude" - +# 窗函数类型,可能的值为WindowType.BARTLETT, WindowType.BLACKMAN, WindowType.HAMMING, WindowType.HANN, WindowType.KAISER class WindowType(str, Enum): """ Window Function types, @@ -177,11 +175,11 @@ class WindowType(str, Enum): Possible enumeration values are: WindowType.BARTLETT, WindowType.BLACKMAN, WindowType.HAMMING, WindowType.HANN, WindowType.KAISER. - - WindowType.BARTLETT: means the type of window function is Bartlett. - - WindowType.BLACKMAN: means the type of window function is Blackman. - - WindowType.HAMMING: means the type of window function is Hamming. - - WindowType.HANN: means the type of window function is Hann. - - WindowType.KAISER: means the type of window function is Kaiser, currently not supported on macOS. + WindowType.BARTLETT - Bartlettc窗函数。 + WindowType.BLACKMAN - Blackman窗函数。 + WindowType.HAMMING - Hamming窗函数。 + WindowType.HANN - Hann窗函数。 + WindowType.KAISER - Kaiser窗函数。当前,不支持在macOS上使用。 """ BARTLETT: str = "bartlett" BLACKMAN: str = "blackman" @@ -193,15 +191,15 @@ class WindowType(str, Enum): DE_C_NORM_MODE = {NormMode.ORTHO: cde.NormMode.DE_NORM_MODE_ORTHO, NormMode.NONE: cde.NormMode.DE_NORM_MODE_NONE} - +# 使用n_mels和n_mfcc创建一个dct矩阵 def create_dct(n_mfcc, n_mels, norm=NormMode.NONE): """ Create a DCT transformation matrix with shape (n_mels, n_mfcc), normalized depending on norm. Args: - n_mfcc (int): Number of mfc coefficients to retain, the value must be greater than 0. - n_mels (int): Number of mel filterbanks, the value must be greater than 0. - norm (NormMode): Normalization mode, can be NormMode.NONE or NormMode.ORTHO (default=NormMode.NONE). + n_mfcc (int): MFCC特征的维度, 参数必须大于0. + n_mels (int): Mel频谱的维度, 参数必须大于0. + norm (NormMode): 归一化模式, 见上。 Returns: numpy.ndarray, the transformation matrix, to be right-multiplied to row-wise data of size (n_mels, n_mfcc). @@ -212,6 +210,7 @@ def create_dct(n_mfcc, n_mels, norm=NormMode.NONE): >>> dct = create_dct(100, 200, NormMode.NONE) """ + # 判断n_mfcc, n_mels, norm的类型是否符合要求,否则抛出异常 if not isinstance(n_mfcc, int): raise TypeError("n_mfcc with value {0} is not of type {1}, but got {2}.".format( n_mfcc, int, type(n_mfcc))) @@ -240,13 +239,13 @@ def melscale_fbanks(n_freqs, f_min, f_max, n_mels, sample_rate, norm=NormType.NO Create a frequency transformation matrix with shape (n_freqs, n_mels). Args: - n_freqs (int): Number of frequency. - f_min (float): Minimum of frequency in Hz. - f_max (float): Maximum of frequency in Hz. - n_mels (int): Number of mel filterbanks. - sample_rate (int): Sample rate. - norm (NormType, optional): Norm to use, can be NormType.NONE or NormType.SLANEY (Default: NormType.NONE). - mel_type (MelType, optional): Scale to use, can be MelType.HTK or MelType.SLANEY (Default: NormType.SLANEY). + n_freqs (int): 特征频率数量 + f_min (float): 特征频率最小值 + f_max (float): 特征频率最大值 + n_mels (int): 相邻频谱的频率数量 + sample_rate (int): 采样率 + norm (NormType, optional): 归一化模式, 见上 + mel_type (MelType, optional): 梅尔标度实现类型,见上 Returns: numpy.ndarray, the frequency transformation matrix. @@ -257,6 +256,7 @@ def melscale_fbanks(n_freqs, f_min, f_max, n_mels, sample_rate, norm=NormType.NO >>> fbanks = melscale_fbanks(n_freqs=4096, f_min=0, f_max=8000, n_mels=40, sample_rate=16000) """ + # 检查下列参数的形式是否正确 type_check(n_freqs, (int,), "n_freqs") check_non_negative_int32(n_freqs, "n_freqs") diff --git a/mindspore/python/mindspore/dataset/callback/__init__.py b/mindspore/python/mindspore/dataset/callback/__init__.py index 2f67912a76b..af2f7e3fe49 100644 --- a/mindspore/python/mindspore/dataset/callback/__init__.py +++ b/mindspore/python/mindspore/dataset/callback/__init__.py @@ -12,7 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== +# 回调函数的初始化文件 """init file for Python callback""" +# 从当前目录下的子目录"./"中导入名为"ds_callback"的模块,并导入该模块中的名为"DSCallback"和"WaitedDSCallback"的类 from .ds_callback import DSCallback, WaitedDSCallback +# __all__是一个特殊属性,用于指定模块中需要导出的类和函数。在这里,将"DSCallback"和"WaitedDSCallback"定义为需要导出的类 __all__ = ["DSCallback", "WaitedDSCallback"] diff --git a/mindspore/python/mindspore/dataset/callback/ds_callback.py b/mindspore/python/mindspore/dataset/callback/ds_callback.py index 9f8da978c3a..5689e0f0d8c 100644 --- a/mindspore/python/mindspore/dataset/callback/ds_callback.py +++ b/mindspore/python/mindspore/dataset/callback/ds_callback.py @@ -49,6 +49,10 @@ class DSCallback: @check_callback def __init__(self, step_size=1): + ''' + 初始化DSCallback类 + :param step_size: 步长 + ''' self.step_size = step_size def ds_begin(self, ds_run_context): @@ -98,31 +102,45 @@ class DSCallback: Returns: _c_dataengine.PyDSCallback. """ + ''' + 创建运行对象 + ''' + + # 创建PyDSCallback对象 c_cb = PyDSCallback(self.step_size) at_least_one = False - if self.__class__.ds_begin != DSCallback.ds_begin: + # 如果父类的ds_begin方法不等于DSCallback的ds_begin方法,则设置begin方法 + if self.__class__.ds_begin!= DSCallback.ds_begin: c_cb.set_begin(self.ds_begin) at_least_one = True - if self.__class__.ds_epoch_begin != DSCallback.ds_epoch_begin: + # 如果父类的ds_epoch_begin方法不等于DSCallback的ds_epoch_begin方法,则设置epoch_begin方法 + if self.__class__.ds_epoch_begin!= DSCallback.ds_epoch_begin: c_cb.set_epoch_begin(self.ds_epoch_begin) at_least_one = True - if self.__class__.ds_epoch_end != DSCallback.ds_epoch_end: + + # 如果父类的ds_epoch_end方法不等于DSCallback的ds_epoch_end方法,则设置epoch_end方法 + if self.__class__.ds_epoch_end!= DSCallback.ds_epoch_end: c_cb.set_epoch_end(self.ds_epoch_end) at_least_one = True - if self.__class__.ds_step_begin != DSCallback.ds_step_begin: + # 如果父类的ds_step_begin方法不等于DSCallback的ds_step_begin方法,则设置step_begin方法 + if self.__class__.ds_step_begin!= DSCallback.ds_step_begin: c_cb.set_step_begin(self.ds_step_begin) at_least_one = True - if self.__class__.ds_step_end != DSCallback.ds_step_end: + + # 如果父类的ds_step_end方法不等于DSCallback的ds_step_end方法,则设置step_end方法 + if self.__class__.ds_step_end!= DSCallback.ds_step_end: c_cb.set_step_end(self.ds_step_end) at_least_one = True + # 如果没有至少覆盖一个方法,则抛出异常 if not at_least_one: raise AttributeError( "Inheriting Callback class without overriding any methods, check the usage of user defined Callback.") + # 返回PyDSCallback对象 return c_cb @@ -218,15 +236,26 @@ class WaitedDSCallback(Callback, DSCallback): >>> model.train(2, data, dataset_sink_mode=False, callbacks=[my_cb2, my_cb1]) """ + + ''' + 训练过程中等待DSCallback回调函数 + :param step_size: 步长 + :param step_event: 步长事件 + :param step_run_context: 步长运行上下文 + :param epoch_event: epoch事件 + :param epoch_run_context: epoch运行上下文 + + ''' + def __init__(self, step_size=1): super().__init__() self.step_size = step_size self.step_event = threading.Event() self.step_run_context = None - self.epoch_event = threading.Event() self.epoch_run_context = None + # 是否已经结束 self.training_ended = False def sync_epoch_begin(self, train_run_context, ds_run_context): @@ -246,7 +275,7 @@ class WaitedDSCallback(Callback, DSCallback): train_run_context: Include some information of the model with feedback from the previous step. ds_run_context: Include some information of the data pipeline. """ - + def epoch_end(self, run_context): """ Internal method, do not call/override. Defines epoch_end of Callback to release the wait in ds_epoch_begin. @@ -254,9 +283,11 @@ class WaitedDSCallback(Callback, DSCallback): Args: run_context: Include some information of the model. """ + # 将epoch_run_context赋值给self.epoch_run_context self.epoch_run_context = run_context + # 将epoch_event设置为True self.epoch_event.set() - + def ds_epoch_begin(self, ds_run_context): """ Internal method, do not call/override. Define mindspore.dataset.DSCallback.ds_epoch_begin @@ -265,13 +296,20 @@ class WaitedDSCallback(Callback, DSCallback): Args: ds_run_context: Include some information of the data pipeline. """ + # 如果当前epoch数大于1 if ds_run_context.cur_epoch_num > 1: + # 如果没有训练结束 if not self.training_ended: + # 等待epoch_event超时 success = self.epoch_event.wait(timeout=ds.config.get_callback_timeout()) + # 清除epoch_event self.epoch_event.clear() + # 如果超时 if not success: + # 抛出运行时错误 raise RuntimeError(f"ds_epoch_begin timed out after {ds.config.get_callback_timeout()} second(s).") # by the time this thread wakes up, self.epoch_run_context is already available + # 如果训练结束 self.sync_epoch_begin(self.epoch_run_context, ds_run_context) def step_end(self, run_context): @@ -281,7 +319,9 @@ class WaitedDSCallback(Callback, DSCallback): Args: run_context: Include some information of the model. """ + # 将run_context赋值给self.step_run_context self.step_run_context = run_context + # 设置self.step_event为True self.step_event.set() def ds_step_begin(self, ds_run_context): @@ -292,13 +332,18 @@ class WaitedDSCallback(Callback, DSCallback): Args: ds_run_context: Include some information of the data pipeline. """ + # 如果当前步数大于step_size,则等待 if ds_run_context.cur_step_num > self.step_size: + # 如果training_ended为False,则等待 if not self.training_ended: success = self.step_event.wait(timeout=ds.config.get_callback_timeout()) + # 清除step_event self.step_event.clear() + # 如果等待超时,则抛出异常 if not success: raise RuntimeError(f"ds_step_begin timed out after {ds.config.get_callback_timeout()} second(s).") # by the time this thread wakes up, self.epoch_run_context is already available + # 如果等待成功,则调用sync_step_begin self.sync_step_begin(self.step_run_context, ds_run_context) def create_runtime_obj(self): @@ -308,21 +353,26 @@ class WaitedDSCallback(Callback, DSCallback): Returns: _c_dataengine.PyDSCallback. """ + # 创建一个PyDSCallback对象,用于设置步长 c_cb = PyDSCallback(self.step_size) at_least_one = False - if self.__class__.sync_step_begin != WaitedDSCallback.sync_step_begin: + # 如果sync_step_begin不等于WaitedDSCallback.sync_step_begin,则设置step_begin + if self.__class__.sync_step_begin!= WaitedDSCallback.sync_step_begin: c_cb.set_step_begin(self.ds_step_begin) at_least_one = True - if self.__class__.sync_epoch_begin != WaitedDSCallback.sync_epoch_begin: + # 如果sync_epoch_begin不等于WaitedDSCallback.sync_epoch_begin,则设置epoch_begin + if self.__class__.sync_epoch_begin!= WaitedDSCallback.sync_epoch_begin: c_cb.set_epoch_begin(self.ds_epoch_begin) at_least_one = True + # 如果没有至少覆盖一个方法,则抛出异常 if not at_least_one: raise AttributeError( "Inheriting Callback class without overriding any methods, check the usage of user defined Callback.") + # 返回PyDSCallback对象 return c_cb def end(self, run_context): @@ -332,6 +382,9 @@ class WaitedDSCallback(Callback, DSCallback): Args: run_context: Include some information of the model. """ + # 调用epoch_end函数 self.epoch_end(run_context) + # 调用step_end函数 self.step_end(run_context) + # 将training_ended设置为True self.training_ended = True diff --git a/mindspore/python/mindspore/dataset/engine/__init__.py b/mindspore/python/mindspore/dataset/engine/__init__.py index fb6141cb8cd..b019615270e 100644 --- a/mindspore/python/mindspore/dataset/engine/__init__.py +++ b/mindspore/python/mindspore/dataset/engine/__init__.py @@ -13,6 +13,8 @@ # limitations under the License. # ============================================================================== +# 模块dataset/engine。它提供了一个高性能的数据集引擎,用于加载和处理各种格式的数据集,如ImageNet、TFData、MNIST、Cifar10/100、Manifest、MindRecord等。 +# 该引擎支持各种数据处理操作,如乱序、批次、重复、映射和组合等 """ Introduction to dataset/engine: @@ -22,21 +24,35 @@ high performance and parse data precisely. It also provides the following operations for users to preprocess data: shuffle, batch, repeat, map, and zip. """ +# 回调类,用于在数据处理过程中提供通知和控制 from ..callback import DSCallback, WaitedDSCallback from ..core import config +# 数据缓存客户端,用于缓存数据以提高数据加载速度 from .cache_client import DatasetCache +# 包含各种数据集类的模块,如ImageNet、TFData、MNIST、Cifar10/100、Manifest、MindRecord等 from .datasets import * +# 包含用于处理图像数据集的类 from .datasets_vision import * +# 包含用于处理文本数据集的类 from .datasets_text import * +# 包含用于处理音频数据集的类 from .datasets_audio import * +# 包含用于处理标准数据集格式的类 from .datasets_standard_format import * +# 包含用于处理用户自定义数据集的类 from .datasets_user_defined import * +# Graphdate是用于处理图数据的数据结构,从中导入了用于定义图数据的采样策略,和用于定义图数据输出的格式 from .graphdata import GraphData, SamplingStrategy, OutputFormat +# 用于迭代数据集的类 from .iterators import * +# 用于处理MindRecord数据集的类 from .obs.obs_mindrecord_dataset import * +# 用于从数据集中采样数据的类 from .samplers import * +# 用于数据序列化和反序列化的函数 from .serializer_deserializer import compare, deserialize, serialize, show +# 用于方便地调用这些类 __all__ = ["Caltech101Dataset", # Vision "Caltech256Dataset", # Vision "CelebADataset", # Vision diff --git a/mindspore/python/mindspore/dataset/engine/cache_admin.py b/mindspore/python/mindspore/dataset/engine/cache_admin.py index 4e31905c0af..dbad6528ce0 100644 --- a/mindspore/python/mindspore/dataset/engine/cache_admin.py +++ b/mindspore/python/mindspore/dataset/engine/cache_admin.py @@ -24,24 +24,34 @@ import mindspore def main(): + # 主要用于启动缓存服务 """Entry point for cache service""" + # 获取mindspore的bin目录 cache_admin_dir = os.path.join(os.path.dirname(mindspore.__file__), "bin") + # 设置当前工作目录为mindspore的bin目录 os.chdir(cache_admin_dir) + # 获取mindspore的cache_admin目录 cache_admin = os.path.join(cache_admin_dir, "cache_admin") + # 如果mindspore的cache_admin目录不存在,则抛出异常 if not os.path.exists(cache_admin): raise RuntimeError("Dataset cache is not supported on your mindspore version.") + # 获取mindspore的cache_server目录 cache_server = os.path.join(cache_admin_dir, "cache_server") + # 设置mindspore的cache_admin目录为可读写,以允许用户操作该目录 os.chmod(cache_admin, stat.S_IRWXU) + # 设置mindspore的cache_server目录为可读写,以允许用户操作该目录 os.chmod(cache_server, stat.S_IRWXU) - # set LD_LIBRARY_PATH for libpython*.so + # 设置LD_LIBRARY_PATH环境变量,以指定libpython*.so库的路径 python_lib_dir = os.path.join(os.path.dirname(mindspore.__file__), "../../..") os.environ['LD_LIBRARY_PATH'] = python_lib_dir + ":" + os.environ.get('LD_LIBRARY_PATH') # LD_PRELOAD libnnacl.so + # 设置LD_PRELOAD环境变量,以指定nnacl.so库的路径。 nnacl_lib = os.path.join(os.path.dirname(mindspore.__file__), "lib/libnnacl.so") os.environ['LD_PRELOAD'] = nnacl_lib + # 运行cache_admin命令,传入命令行参数,并将结果作为退出代码 sys.exit(subprocess.call([cache_admin] + sys.argv[1:], shell=False, env=os.environ)) diff --git a/mindspore/python/mindspore/dataset/engine/cache_client.py b/mindspore/python/mindspore/dataset/engine/cache_client.py index 077b5278cd0..87266e09a2e 100644 --- a/mindspore/python/mindspore/dataset/engine/cache_client.py +++ b/mindspore/python/mindspore/dataset/engine/cache_client.py @@ -53,6 +53,18 @@ class DatasetCache: def __init__(self, session_id, size=0, spilling=False, hostname=None, port=None, num_connections=None, prefetch_size=None): + ''' + 参数: + session_id (整数):缓存会话ID。 + size (整数):缓存大小,默认为0。 + spilling (布尔值):是否进行溢出处理,默认为False。 + hostname (字符串):缓存服务器的hostname,默认为None。 + port (整数):缓存服务器的端口号,默认为None。 + num_connections (整数):缓存服务器的连接数,默认为None。 + prefetch_size (整数):预取大小,默认为None。 + ''' + + # 检查参数类型和范围。 check_pos_uint32(session_id, "session_id") type_check(size, (int,), "size") if size != 0: @@ -76,24 +88,43 @@ class DatasetCache: self.port = port self.prefetch_size = prefetch_size self.num_connections = num_connections + # 创建一个CacheClient对象,用于与缓存服务器进行通信。 self.cache_client = CacheClient(session_id, size, spilling, hostname, port, num_connections, prefetch_size) def get_stat(self): - """Get the statistics from a cache.""" + ''' + 这个方法用于获取cache缓存的统计信息,如缓存中数据集的数量、已缓存的数据量等。 + ''' return self.cache_client.GetStat() def __deepcopy__(self, memodict): + ''' + 这个函数是一个深拷贝函数,用于在内存中深拷贝对象。当对DatasetCache对象进行深拷贝时,会同时复制其所有属性。 + ''' + # 如果id(self)在memodict字典中,函数直接返回memodict[id(self)],表示已经存在缓存结果,直接返回。 if id(self) in memodict: return memodict[id(self)] cls = self.__class__ + # 如果不存在id(self)在memodict字典中,那么创建一个新的类实例new_cache,这个实例的类名是cls。 new_cache = cls.__new__(cls) + # 将self对象的属性复制到new_cache中 memodict[id(self)] = new_cache + # 将self的session_id复制到新的类中 new_cache.session_id = copy.deepcopy(self.session_id, memodict) + # 将self的spilling复制到新的类中 new_cache.spilling = copy.deepcopy(self.spilling, memodict) + # 将self的size复制到新的类中 new_cache.size = copy.deepcopy(self.size, memodict) + # 将self的hostname复制到新的类中 new_cache.hostname = copy.deepcopy(self.hostname, memodict) + # 将self的port复制到新的类中 new_cache.port = copy.deepcopy(self.port, memodict) + # 将self的prefetch_size复制到新的类中 new_cache.prefetch_size = copy.deepcopy(self.prefetch_size, memodict) + # 将self的num_connections复制到新的类中 new_cache.num_connections = copy.deepcopy(self.num_connections, memodict) + # 将self的cache_client复制到新的类中 new_cache.cache_client = self.cache_client - return new_cache + # 这样,在后续遇到相同self对象时,可以直接从字典中获取缓存结果,而不需要重新计算耗时函数调用。 + # 返回新的类 + return new_cache \ No newline at end of file diff --git a/mindspore/python/mindspore/dataset/engine/datasets.py b/mindspore/python/mindspore/dataset/engine/datasets.py index 0566dcd96a3..b89e7648a3d 100644 --- a/mindspore/python/mindspore/dataset/engine/datasets.py +++ b/mindspore/python/mindspore/dataset/engine/datasets.py @@ -99,10 +99,15 @@ def _set_training_dataset(dataset): Args: dataset: the training dataset or iterator """ + """ + 用于设置用于训练恢复时使用的数据集。函数接收一个参数dataset,它是一个训练数据集或迭代器。 + 函数内部使用global关键字声明一个名为_train_dataset的全局变量,并将其值设置为传入的data、 + set参数。这样,在整个程序中,就可以通过_train_dataset变量访问和修改训练数据集了。 + """ global _train_dataset _train_dataset = dataset - +# 用于获取用于训练恢复时使用的数据集/迭代器。 def _get_training_dataset(): """ Get the dataset to be used when training recovery has occurred. @@ -112,8 +117,12 @@ def _get_training_dataset(): """ return _train_dataset - +# 用于重置训练数据集到给定的步数。 def _reset_training_dataset(step): + """ + 首先,它从_get_training_dataset函数获取训练数据集(如果存在),然后使用_reset方法重置数 + 据集到给定的步数。如果数据集不存在,函数将引发一个RuntimeError异常。 + """ """ Reset the training dataset to the given step number. @@ -122,17 +131,19 @@ def _reset_training_dataset(step): """ dataset = _get_training_dataset() if dataset is not None: + # 如果训练数据集不为空,则重置步骤 dataset._reset(step) # pylint: disable=W0212 else: + # 否则抛出异常 raise RuntimeError("Training dataset is not set.") class Shuffle(str, Enum): """Specify the shuffle mode. - - Shuffle.GLOBAL: Shuffle both the files and samples. - - Shuffle.FILES: Shuffle files only. - - Shuffle.INFILE: Shuffle data within each file. + Shuffle.GLOBAL:洗牌全局,即既洗牌文件又洗牌样本。 + Shuffle.FILES:洗牌文件,即只洗牌文件,不洗牌样本。 + Shuffle.INFILE:洗牌数据,即在文件内部洗牌,不洗牌文件。 """ GLOBAL: str = "global" FILES: str = "files" @@ -145,6 +156,10 @@ ShuffleToShuffleMode = {Shuffle.FILES: cde.ShuffleMode.FILES, def shuffle_to_shuffle_mode(shuffle): + """ + 用于将Shuffle枚举转换为C层使用的洗牌模式。函数接受一个shuffle参数,表示要转换的洗牌枚举。 + 根据shuffle的值,函数返回一个ShuffleMode枚举值,表示C层使用的洗牌模式。 + """ """ Shuffle Enum to Shuffle Mode @@ -154,18 +169,27 @@ def shuffle_to_shuffle_mode(shuffle): Returns: ShuffleMode, shuffle mode """ + # 定义全局模式 shuffle_mode = cde.ShuffleMode.GLOBAL # Global shuffle + # 判断shuffle是否为Shuffle枚举中的一个,如果不是则根据shuffle进行判断 if not isinstance(shuffle, Shuffle): + # 如果为None或者为真,则使用全局模式 if shuffle is None or shuffle: shuffle_mode = cde.ShuffleMode.GLOBAL # Global shuffle + # 否则,使用False模式 else: shuffle_mode = cde.ShuffleMode.FALSE # No shuffle + # 判断是否为ShuffleToShuffleMode中的某一个 else: shuffle_mode = ShuffleToShuffleMode[shuffle] + # 返回模式 return shuffle_mode def shuffle_to_bool(shuffle): + """ + 定义了一个名为shuffle_to_bool的函数,用于将Shuffle枚举转换为布尔值。 + """ """ Shuffle Enum to bool @@ -175,30 +199,40 @@ def shuffle_to_bool(shuffle): Returns: bool, True / False """ + # 如果shuffle不是bool或者Shuffle类型,抛出异常 if shuffle is not None and not isinstance(shuffle, (bool, Shuffle)): raise TypeError("shuffle must be of boolean or enum of 'Shuffle' values like 'Shuffle.GLOBAL' or " "'Shuffle.FILES' or 'Shuffle.INFILE'.") + # 将shuffle转换为bool shuffle_bool = True + # 如果shuffle不是Shuffle类型,且为None,则将shuffle_bool设置为None if not isinstance(shuffle, Shuffle): if shuffle is None: shuffle_bool = None + # 如果shuffle为True,则将shuffle_bool设置为True elif shuffle: shuffle_bool = True + # 如果shuffle为False,则将shuffle_bool设置为False else: shuffle_bool = False + # 如果shuffle是Shuffle类型,则将shuffle_bool设置为True else: shuffle_bool = True + # 返回shuffle_bool return shuffle_bool @check_zip def zip(datasets): + """ + 用于将输入的元组中的数据集zip在一起。 + """ """ Zip the datasets in the input tuple of datasets. Args: - datasets (tuple[Dataset]): A tuple of datasets to be zipped together. + datasets (tuple[Dataset]): 需要zipped的数据集元组 The number of datasets must be more than 1. Returns: @@ -212,16 +246,23 @@ def zip(datasets): >>> # Create a dataset which is the combination of dataset_1 and dataset_2 >>> dataset = ds.zip((dataset_1, dataset_2)) """ + # 如果数据集的数量小于1,抛出异常 + # 表示无法zippered的数据集数量不足 if len(datasets) <= 1: raise ValueError( "Can't zip empty or just one dataset!") + # 遍历数据集,如果不是Dataset类型,抛出异常,确保每个数据集都是一个Dataset类型 for dataset in datasets: if not isinstance(dataset, Dataset): raise TypeError("Invalid dataset, expected Dataset object, but got %s!" % type(dataset)) + # 返回ZipDataset对象 return ZipDataset(datasets) def _get_operator_process(): + """ + 用于传递子进程ID。 + """ """ Inner implemented method, mainly for passing sub-process id in C layer @@ -229,34 +270,52 @@ def _get_operator_process(): dict, mapping dict of operator id and corresponding process id. """ global _OP_PROCESS + # 定义全局变量_OP_PROCESS,用于存储子进程的信息。 process_info = _OP_PROCESS + # 获取_OP_PROCESS中的信息 op_process = dict() + # 定义op_process字典,用于存储子进程ID。 keys = process_info.keys() + # 获取process_info中的key,将它们存储在名为keys的列表中。 fetched_all = True + # 定义fetched_all变量,判断是否已经获取了所有子进程的信息。 for key in keys: try: + # 尝试获取key对应的op_process字典 op_process[key] = list(process_info[key][1]) + # 将op_process字典中的key转换为列表 item_full = (len(process_info[key][1]) == process_info[key][0]) + # 判断op_process字典中的key是否与process_info中的key相同 except KeyError as err: + # 如果获取key对应的op_process字典失败,抛出异常 raise err + # 将fetched_all变量设置为获取了所有子进程的信息 fetched_all = fetched_all and item_full + # 返回op_process字典和fetched_all变量 return op_process, fetched_all - def _set_dataset_permissions(file_name, num_files): + """ + 用于设置已保存数据集文件的权限。 + """ """ set saved dataset files' permissions to 600 the rule of dataset filenames should be the same as those in C++. """ + # 计算文件数量的位数 num_digits = len(str(num_files - 1)) + # 如果文件数量为1,则路径为文件名,否则为文件名加上0后的数字 if num_files == 1: paths = [file_name] else: paths = ["{}{}".format(file_name, str(x).rjust(num_digits, '0')) for x in range(num_files)] + # 遍历路径 for item in paths: + # 如果路径存在,则设置文件权限为600 if os.path.exists(item): os.chmod(item, stat.S_IRUSR | stat.S_IWUSR) + # 设置索引文件权限为600 index_file = item + ".db" if os.path.exists(index_file): os.chmod(index_file, stat.S_IRUSR | stat.S_IWUSR) @@ -343,6 +402,9 @@ class Dataset: self._sync = False def create_ir_tree(self): + """ + 用于构建IR树。 + """ """ Internal method to build an IR tree. @@ -350,112 +412,176 @@ class Dataset: DatasetNode, the root node of the IR tree. Dataset, the root dataset of the IR tree. """ + # 获取父节点 parent = self.parent + # 把父节点改为空 self.parent = [] + # 把当前节点拷贝一份,并将复制后的节点设置为新的根节点。 dataset = copy.deepcopy(self) + # 设置_OP_NAME global _OP_NAME + # 存储当前操作的ID。 _OP_NAME = Dataset._get_operator_id(dataset) + # 调用parse_tree方法,解析IR树 ir_tree = dataset.parse_tree() + # 把父节点改回原来的值 self.parent = parent + # 初始化设备信息 _init_device_info() + # 返回IR树和根节点 return ir_tree, dataset def close_pool(self): + """ + 用于关闭数据集中的多进程池。如果对多进程池熟悉,可以将其视为处理池对象的析构函数。 + """ """ Close multiprocessing pool in dataset. If you are familiar with multiprocessing library, you can regard this as a destructor for a processingPool object. """ - # del all the SharedQueue when close the pool + # 如果存在process_pool属性且其值不为空,则关闭多进程池并删除共享内存。 if hasattr(self, 'process_pool') and self.process_pool is not None: self.process_pool.close_pool() self.process_pool.delete_shared_memory() + # 遍历children属性中的每个子数据集,递归地调用其close_pool方法。 for child in self.children: child.close_pool() def notify_watchdog(self): + """ + 用于通知数据集中的看门狗线程关闭。在生成器数据集/映射/批次等需要获取数据集大小/输出形 + 状/输出类型/列名/类数的操作中,需要调用notify_watchdog方法手动关闭看门狗线程。 + """ """ Close watchdog thread in dataset. Now GeneratorDataset/map/batch will use a thread named watch_dog to monitor multiprocess, for get_dataset_size/output_shapes/output_types/get_col_name/num_classes, we need notify_watchdog to close watch_dog thread manually. """ + # 如果存在sample_fn属性且其值不为空则调用_abort_watchdog方法关闭看门狗线程。 if hasattr(self, 'sample_fn') and self.sample_fn is not None: + # sample_fn的多进程标志为真 if self.sample_fn.multi_process: + # 调用_abort_watchdog方法关闭看门狗线程 self.sample_fn._abort_watchdog() # pylint: disable=W0212 + + # 如果存在process_pool属性且其值不为空 if hasattr(self, 'process_pool') and self.process_pool is not None: + # 调用abort_watchdog方法关闭看门狗线程 self.process_pool.abort_watchdog() + # 遍历children属性中的每个子数据集,递归地调用其notify_watchdog方法。 for child in self.children: child.notify_watchdog() @staticmethod def _get_operator_id(dataset): + """ + 用于遍历IR树并获取每个操作的op_id + """ """ Internal method to iterate the tree and obtain op_id of each operator. Returns: Dataset, the root dataset of the tree. """ + # 创建一个字典,用于存储操作名称和操作ID op_name = dict() + # 创建一个字典,用于存储生成器进程 generator_process = dict() + # 将当前数据集添加到op_name字典中 op_name[str(dataset)] = 0 + # 初始化操作ID op_id = 1 def process_name(datasets, operator_id): + ''' + 用于计算操作名称 + ''' + # 如果datasets为空,则返回0。 if not datasets: return 0 temp = [] + # 遍历datasets中的每个数据集,将其children属性添加到temp列表中 for item in datasets: for d in item.children: temp.append(d) + # 将当前操作ID添加到op_name字典中,键为数据集的str表示,值为操作ID op_name[str(d)] = operator_id from mindspore.dataset.engine.datasets_user_defined import GeneratorDataset + # 如果当前数据集是一个GeneratorDataset,且其sample_fn和pids属性不为空 if isinstance(d, GeneratorDataset) and d.sample_fn and d.sample_fn.pids: + # 则将生成器进程添加到generator_process字典中,键为操作ID,值为一个列表,列表元素为num_parallel_workers和pids集合 generator_process[operator_id] = [d.num_parallel_workers, set(d.sample_fn.pids)] + # 更新操作ID operator_id = operator_id + 1 + # 返回op_name字典 return process_name(temp, operator_id) process_name([dataset], op_id) + # 如果generator_process存在,则更新_OP_PROCESS if generator_process: global _OP_PROCESS _OP_PROCESS.update(generator_process) + # 返回op_name return op_name def parse_tree(self): + """ + 用于解析API树(API树是一个表示数据处理流程的树形结构)并将其转换为IR树(IR树是一个表示数据处理流程的线性结构) + """ """ Internal method to parse the API tree into an IR tree. Returns: DatasetNode, the root node of the IR tree. """ + # 如果父节点的长度大于1,则抛出异常,一个节点不能有多个消费者 if len(self.parent) > 1: raise ValueError("The data pipeline is not a tree (i.e., one node has 2 consumers)") + # 将子节点的IR树解析成IR节点 ir_children = [d.parse_tree() for d in self.children] - # Bootstrap can only be performed on a copy of the original dataset node. - # Bootstrap on original dataset node will make all iterators share the same process pool + # 如果需要对原始数据集节点进行bootstrap,则对其进行bootstrap。 + # 在进行bootstrap操作时,只能对原始数据集节点的副本进行操作,而不能对原始数据集节点进行操作 + # 对原始数据集节点进行bootstrap操作可能会导致所有迭代器共享同一个进程池 + # 将IR树节点添加到原始数据集节点上 self.iterator_bootstrap() ir_node = self.parse(ir_children) + # 将IR节点的解析后的IR节点添加到原始数据集节点上 ir_node = self.post_parse(ir_node) return ir_node def __safe_deepcopy__(self, memodict, exclude=()): + ''' + 用于深拷贝,避免深拷贝对象的属性 + ''' + # 如果对象已经在内存中,则直接返回该对象 if id(self) in memodict: return memodict[id(self)] + # 创建一个新的对象,将其添加到内存中 cls = self.__class__ new_op = cls.__new__(cls) memodict[id(self)] = new_op + # 遍历对象的所有属性 for arg, value in self.__dict__.items(): + # 对于不需要深拷贝的属性,直接设置新对象的属性值 if arg in exclude: setattr(new_op, arg, value) + # 对于需要深拷贝的属性,尝试使用copy.deepcopy进行深拷贝 else: try: setattr(new_op, arg, copy.deepcopy(value, memodict)) + # 如果深拷贝失败,直接设置新对象的属性值 except TypeError: setattr(new_op, arg, value) + # 返回新对象 return new_op @staticmethod def _noop_mode(): + ''' + 用于判断当前角色是否为调度器或者调度器的pserver。如果是,则返回True,否则返回False + ''' if _is_role_sched() or _is_role_pserver(): return True return False @@ -464,9 +590,17 @@ class Dataset: pass def __add__(self, datasets): + ''' + 用于将两个数据集合拼接成一个新的数据集 + :param datasets: 两个数据集 + :return: 拼接后的数据集 + ''' return self.concat(datasets) def to_json(self, filename=""): + """ + 用于将一个Pipeline(管道)对象序列化为一个JSON字符串,并将结果dump到文件中 + """ """ Serialize a pipeline into JSON string and dump into file if filename is provided. @@ -476,7 +610,9 @@ class Dataset: Returns: str, JSON string of the pipeline. """ + # 创建IR树 ir_tree, _ = self.create_ir_tree() + # 将IR树转换为JSON字符串 return json.loads(ir_tree.to_json(filename)) @check_bucket_batch_by_length @@ -493,33 +629,19 @@ class Dataset: Each batch will be full, except one special case: the last batch for each bucket may not be full. Args: - column_names (list[str]): Columns passed to element_length_function. - bucket_boundaries (list[int]): A list consisting of the upper boundaries - of the buckets. Must be strictly increasing. If there are n boundaries, - n+1 buckets are created: One bucket for [0, bucket_boundaries[0]), one - bucket for [bucket_boundaries[i], bucket_boundaries[i+1]) for each - 0 0: absolute_sizes[0] += size_difference + # 遍历absolute_sizes列表,如果absolute_sizes[i]加上size_difference大于0,则将absolute_sizes[i] + # 加上size_difference,并跳出循环。 else: for i, _ in enumerate(absolute_sizes): if absolute_sizes[i] + size_difference > 0: absolute_sizes[i] += size_difference break - + # 检查计算出的绝对分割大小和是否与dataset大小相等,如果不相等,则引发RuntimeError。 if sum(absolute_sizes) != dataset_size: raise RuntimeError("Sum of calculated split sizes {} is not equal to dataset size {}." .format(absolute_sizes_sum, dataset_size)) @@ -1067,7 +1189,7 @@ class Dataset: @check_split def split(self, sizes, randomize=True): """ - Split the dataset into smaller, non-overlapping datasets. + 用于将dataset分为多个非重叠的子dataset。 This is a general purpose split function which can be called from any operator in the pipeline. There is another, optimized split function, which will be called automatically if ds.split is @@ -1118,38 +1240,49 @@ class Dataset: >>> dataset = ds.TextFileDataset(text_file_dataset_dir, shuffle=False) >>> train_dataset, test_dataset = dataset.split([0.9, 0.1]) """ + # 检查dataset是否已经被shuffled if self.is_shuffled(): + # 如果已经被shuffled,则发出警告 logger.warning("Dataset is shuffled before split.") + # 检查dataset是否已经被shard if self.is_sharded(): + # 如果已经被shard,则引发RuntimeError raise RuntimeError("Dataset should not be sharded before split.") + # 使用_get_absolute_split_sizes方法计算实际的分割大小 absolute_sizes = self._get_absolute_split_sizes(sizes) splits = [] rows_to_skip = 0 + # 遍历计算出的绝对分割大小 for size in absolute_sizes: + # 对每个子dataset进行复制 ds = copy.deepcopy(self) + # 对每个子dataset随机shuffle(如果需要) if randomize: # want to shuffle the same way every epoch before split # in alter_tree, shuffle buffer is minimum 10000, so use 10000 here ds = ds.shuffle(10000) ds.reshuffle_each_epoch = False + # 对每个子dataset跳过操作 if rows_to_skip > 0: ds = ds.skip(rows_to_skip) + # 将处理后的子dataset添加到splits列表中 ds = ds.take(size) splits.append(ds) - + + # 更新rows_to_skip rows_to_skip += size + # 返回一个包含分割后子dataset的元组 return tuple(splits) @check_zip_dataset def zip(self, datasets): """ - Zip the datasets in the sense of input tuple of datasets. Columns in the input datasets must have different - name. + 用于将多个dataset(元组或单个Dataset对象)拼接在一起,具有不同的名称的列。 Args: datasets (Union[tuple, class Dataset]): A tuple of datasets or a single class Dataset @@ -1162,10 +1295,13 @@ class Dataset: >>> # Create a dataset which is the combination of dataset and dataset_1 >>> dataset = dataset.zip(dataset_1) """ + # 如果输入的datasets是一个元组,那么将self和元组中的每个dataset拼接在一起,形成一个新的dataset。 if isinstance(datasets, tuple): datasets = (self, *datasets) + # 如果输入的datasets是一个Dataset对象,那么将self和输入的Dataset对象拼接在一起,形成一个新的dataset。 elif isinstance(datasets, Dataset): datasets = (self, datasets) + # 如果输入的datasets不是一个元组也不是Dataset对象,将引发TypeError。 else: raise TypeError("Invalid datasets, expected Dataset object or tuple of Dataset, but got %s!" % datasets) return ZipDataset(datasets) @@ -1173,7 +1309,7 @@ class Dataset: @check_concat def concat(self, datasets): """ - Concatenate the dataset objects in the input list. + 用于将多个Dataset对象连接在一起。 Performing "+" operation on dataset objects can achieve the same effect. Note: @@ -1192,8 +1328,10 @@ class Dataset: >>> # Create a dataset by concatenating dataset_1 and dataset_2 with concat operation >>> dataset = dataset_1.concat(dataset_2) """ + # 当datasets是一个Dataset或list对象时,concat方法会将当前的Dataset对象添加到输入列表的开头,然后返回一个新的ConcatDataset对象,该对象包含输入列表中的所有Dataset对象。 if isinstance(datasets, Dataset): datasets = [self] + [datasets] + elif isinstance(datasets, list): datasets = [self] + datasets else: @@ -1203,7 +1341,7 @@ class Dataset: @check_rename def rename(self, input_columns, output_columns): """ - Rename the columns in input datasets. + 用于将输入数据集中的列名更改。 Args: input_columns (Union[str, list[str]]): List of names of the input columns. @@ -1228,13 +1366,14 @@ class Dataset: @check_project def project(self, columns): """ - Project certain columns in input dataset. + 用于从输入数据集中选择特定列并将其传递到管道中。 The specified columns will be selected from the dataset and passed into the pipeline with the order specified. The other columns are discarded. Args: - columns(Union[str, list[str]]): List of names of the columns to project. + columns(Union[str, list[str]]): 一个字符串列表或一个字符串。如果是字符串 + 列表,则表示要选择的列的名称。如果是字符串,则表示要选择的单个列的名称。 Returns: Dataset, dataset projected. @@ -1252,11 +1391,11 @@ class Dataset: def apply(self, apply_func): """ - Apply a function in this dataset. + 用于将一个函数应用于输入数据集。 Args: - apply_func (function): A function that must take one 'Dataset' as an argument and - return a preprocessed 'Dataset'. + apply_func (function): 一个函数,该函数接受一个Dataset对象作为参数,并返 + 回一个已预处理过的Dataset对象。 Returns: Dataset, dataset applied by the function. @@ -1276,25 +1415,27 @@ class Dataset: TypeError: If apply_func is not a function. TypeError: If apply_func doesn't return a Dataset. """ - + # 检查apply_func参数是否是一个函数 if not hasattr(apply_func, '__call__'): + # 如果不是一个函数,则抛出一个TypeError raise TypeError("apply_func must be a function.") + # 将self(原始 dataset)传递给apply_func dataset = apply_func(self) + # 如果返回值不是一个Dataset对象,则抛出一个TypeError if not isinstance(dataset, Dataset): raise TypeError("apply_func must return a dataset.") + # 返回处理后的Dataset对象 return dataset @check_device_send def device_que(self, send_epoch_end=True, create_data_info_queue=False): """ - Return a transferred Dataset that transfers data through a device. + 用于将输入数据集转换为传输到设备的Dataset。 Args: - send_epoch_end (bool, optional): Whether to send end of sequence to device or not (default=True). - create_data_info_queue (bool, optional): Whether to create queue which stores - types and shapes of data or not(default=False). - + send_epoch_end (bool, optional): 是否在传输数据时发送结束序列。 + create_data_info_queue (bool, optional): 是否在在传输数据之前创建一个队列,用于存储数据类型和形状。 Note: If device is Ascend, features of data will be transferred one by one. The limitation of data transmission per time is 256M. @@ -1307,12 +1448,11 @@ class Dataset: @check_device_send def to_device(self, send_epoch_end=True, create_data_info_queue=False): """ - Transfer data from CPU to GPU or Ascend or other devices. + 用于将输入数据集转换为传输到设备的Dataset。 Args: - send_epoch_end (bool, optional): Whether to send the end of sequence to device or not (default=True). - create_data_info_queue (bool, optional): Whether to create queue which stores - types and shapes of data or not(default=False). + send_epoch_end (bool, optional): 是否在传输数据时发送结束序列。 + create_data_info_queue (bool, optional): 是否在在传输数据之前创建一个队列,用于存储数据类型和形状。 Note: If device is Ascend, features of data will be transferred one by one. The limitation @@ -1329,7 +1469,7 @@ class Dataset: @check_save def save(self, file_name, num_files=1, file_type='mindrecord'): """ - Save the dynamic data processed by the dataset pipeline in common dataset format. + 用于将处理后的数据保存到磁盘。 Supported dataset formats: 'mindrecord' only Implicit type casting exists when saving data as 'mindrecord'. The transform table shows how to do type casting. @@ -1396,22 +1536,30 @@ class Dataset: file_type (str, optional): Dataset format (default='mindrecord'). """ + # 使用self.create_ir_tree()方法创建一个名为ir_tree的树结构,以及一个名为api_tree的树结构。 ir_tree, api_tree = self.create_ir_tree() + # 创建一个名为runtime_context的Python运行时上下文对象 runtime_context = cde.PythonRuntimeContext() + # 使用Init()方法初始化 runtime_context.Init() + # 创建一个名为consumer的Python消费者对象,用于将数据保存到磁盘。 consumer = cde.PythonSaveToDisk(file_name, num_files, file_type) consumer.Init(ir_tree) + # 使用AssignConsumer()方法将consumer分配给runtime_context。 runtime_context.AssignConsumer(consumer) + # 使用Save()方法保存数据到磁盘。 consumer.Save() + # 设置文件的权限。 _set_dataset_permissions(file_name, num_files) + # 删除api_tree对象,因为它不再需要。 del api_tree @check_tuple_iterator def create_tuple_iterator(self, columns=None, num_epochs=-1, output_numpy=False, do_copy=True): """ - Create an iterator over the dataset. The datatype retrieved back will be a list of ndarrays. + 用于将处理后的数据保存到磁盘。 To specify which columns to list and the order needed, use columns_list. If columns_list is not provided, the order of the columns will remain unchanged. @@ -1438,25 +1586,28 @@ class Dataset: ... break """ + # 检查output_numpy参数是否为None,如果是,则将其设置为False。 if output_numpy is None: output_numpy = False + # 使用Dataset._noop_mode()方法检查是否处于无操作模式。 if Dataset._noop_mode(): + # 如果是,则返回一个DummyIterator对象,用于模拟迭代器的行为。 return DummyIterator(self, 'tuple') + # 否则,返回一个TupleIterator对象,该对象用于遍历数据集并返回一个包含多个numpy数组的列表。 return TupleIterator(self, columns, num_epochs, output_numpy, do_copy) @check_dict_iterator def create_dict_iterator(self, num_epochs=-1, output_numpy=False): """ - Create an iterator over the dataset. The data retrieved will be a dictionary datatype. + 用于创建一个迭代器,用于遍历数据集。 The order of the columns in the dictionary may not be the same as the original order. Args: - num_epochs (int, optional): Maximum number of epochs that iterator can be iterated - (default=-1, iterator can be iterated infinite number of epochs). - output_numpy (bool, optional): Whether or not to output NumPy datatype, - if output_numpy=False, iterator will output MSTensor (default=False). + num_epochs (int, optional): 这是一个整数参数,表示迭代的最大次数。默认值为-1,表示可以无限次迭代。 + output_numpy (bool, optional): 这是一个布尔参数,表示是否以NumPy数据类型输出。如果output_numpy为True,则迭 + 代器输出NumPy数组;如果为False,则迭代器输出MSTensor。默认值为False。 Returns: Iterator, dictionary iterator over the dataset. @@ -1470,15 +1621,20 @@ class Dataset: ... break """ + # 检查output_numpy参数是否为None if output_numpy is None: + # 将其设置为False output_numpy = False + # 根据Dataset._noop_mode()的返回值来判断是否为“空操作”模式 if Dataset._noop_mode(): + # 返回一个DummyIterator对象,用于模拟迭代的过程 return DummyIterator(self, 'dict') + # 返回一个DictIterator对象,用于实际遍历数据集。 return DictIterator(self, num_epochs, output_numpy) def __iter__(self): - """Create an iterator over the dataset.""" + """用于创建一个迭代器""" return self.create_tuple_iterator(num_epochs=1) @property @@ -1511,41 +1667,53 @@ class Dataset: return self._input_indexs @input_indexs.setter + # 用于设置或获取数据集的输入索引信息 def input_indexs(self, value): + # 将值输入值设置为数据集的输入索引信息 self._input_indexs = value + # 用于设置或获取数据集的批量大小 def copy_batch_size(self, value): + # 将输入值设置为数据集的批量大小 self._batch_size = value def _init_tree_getters(self): """ - Get pipeline information. + 用于初始化树获取器。 """ + # 创建一个IR树和一个API树 ir_tree, api_tree = self.create_ir_tree() + # 初始化一个Python运行时环境 runtime_context = cde.PythonRuntimeContext() runtime_context.Init() + # 分配一个树获取器到运行时环境 getter = cde.TreeGetters() getter.Init(ir_tree) runtime_context.AssignConsumer(getter) + # 返回树获取器和运行时环境以及API树 return getter, runtime_context, api_tree def __init_size_getter(self): """ - Get pipeline information. + 用于初始化数据集大小获取器 """ + # 创建IR树和一个API树 ir_tree, api_tree = self.create_ir_tree() + # 然后初始化一个Python运行时环境 runtime_context = cde.PythonRuntimeContext() runtime_context.Init() + # 分配一个数据集大小获取器到运行时环境 getter = cde.DatasetSizeGetters() getter.Init(ir_tree) runtime_context.AssignConsumer(getter) + # 返回数据集大小获取器和运行时环境以及API树 return getter, runtime_context, api_tree def get_col_names(self): """ - Return the names of the columns in dataset. + 用于获取数据集的列名。 Returns: list, list of column names in the dataset. @@ -1554,16 +1722,20 @@ class Dataset: >>> # dataset is an instance object of Dataset >>> col_names = dataset.get_col_names() """ + # 如果_col_names为None if self._col_names is None: + # 使用_init_tree_getters方法初始化树获取器 runtime_getter = self._init_tree_getters() + # 使用树获取器获取数据集的列名 self._col_names = runtime_getter[0].GetColumnNames() runtime_getter[2].close_pool() runtime_getter[2].notify_watchdog() + # 返回列名 return self._col_names def output_shapes(self): """ - Get the shapes of output data. + 用于获取输出数据的形状。 Returns: list, list of shapes of each column. @@ -1572,23 +1744,30 @@ class Dataset: >>> # dataset is an instance object of Dataset >>> output_shapes = dataset.output_shapes() """ + # 如果saved_output_shapes为None if self.saved_output_shapes is None: + # 使用_init_tree_getters方法初始化树获取器 runtime_getter = self._init_tree_getters() # We have a hang problem when two-level pipeline with multiprocessing, we need to extend the life cycle # of runtime_context. We found this hang problem only occur on output_types and output_shapes. self.runtime_context = runtime_getter[1] + # 使用树获取器获取输出数据的形状 self.saved_output_shapes = runtime_getter[0].GetOutputShapes() self.saved_output_types = runtime_getter[0].GetOutputTypes() runtime_getter[2].close_pool() runtime_getter[2].notify_watchdog() + # 删除runtime_context del self.runtime_context + # 如果dynamic_setting[0]为True if self.dynamic_setting[0]: + # 计算动态输出数据形状 self.saved_output_shapes, self.saved_min_shapes, self.saved_max_shapes = self._dynamic_output_shapes() + # 返回输出数据的形状 return self.saved_output_shapes def output_types(self): """ - Get the types of output data. + 用于获取输出数据的类型。 Returns: list, list of data types. @@ -1597,23 +1776,30 @@ class Dataset: >>> # dataset is an instance object of Dataset >>> output_types = dataset.output_types() """ + # 如果saved_output_types为None if self.saved_output_types is None: + # 初始化树获取器 runtime_getter = self._init_tree_getters() # We have a hang problem when two-level pipeline with multiprocessing, we need to extend the life cycle # of runtime_context. We found this hang problem only occur on output_types and output_shapes. self.runtime_context = runtime_getter[1] + # 使用树获取器获取输出数据的类型 self.saved_output_shapes = runtime_getter[0].GetOutputShapes() self.saved_output_types = runtime_getter[0].GetOutputTypes() runtime_getter[2].close_pool() runtime_getter[2].notify_watchdog() + # 删除runtime_context del self.runtime_context + # 如果dynamic_setting[0]为True if self.dynamic_setting[0]: + # 计算动态输出数据类型 self.saved_output_shapes, self.saved_min_shapes, self.saved_max_shapes = self._dynamic_output_shapes() + # 返回输出数据的类型 return self.saved_output_types def get_dataset_size(self): """ - Return the number of batches in an epoch. + 用于获取数据集的批量数量。 Returns: int, number of batches. @@ -1622,17 +1808,21 @@ class Dataset: >>> # dataset is an instance object of Dataset >>> dataset_size = dataset.get_dataset_size() """ + # 如果dataset_size为None if self.dataset_size is None: + # 初始化树获取器 runtime_getter = self.__init_size_getter() + # 使用树获取器获取数据集的批量数量 self.dataset_size = runtime_getter[0].GetDatasetSize(False) runtime_getter[2].close_pool() runtime_getter[2].notify_watchdog() + # 最后,返回批量数量 return self.dataset_size @deprecated("1.5") def set_dynamic_columns(self, columns=None): """ - Set dynamic shape information of source data, it should be set after the pipeline is defined. + 用于设置动态形状信息。(应该在定义管道之后调用) Args: columns (dict): A dict contains shape information of each column in dataset. @@ -1655,7 +1845,7 @@ class Dataset: def dynamic_min_max_shapes(self): """ - Get minimum and maximum data length of dynamic source data, for dynamic graph compilation. + 用于获取动态来源数据的最小和最大数据长度,用于动态图编译。 Returns: lists, min_shapes, max_shapes of source data. @@ -1671,97 +1861,125 @@ class Dataset: >>> dataset.set_dynamic_columns(columns={"data1": [16, None, 83], "data2": []}) >>> min_shapes, max_shapes = dataset.dynamic_min_max_shapes() """ + # 如果saved_min_shapes或saved_max_shapes为None if self.saved_min_shapes is None or self.saved_max_shapes is None: + # 计算动态输出数据形状,并将结果赋值给saved_min_shapes和saved_max_shapes self.saved_output_shapes, self.saved_min_shapes, self.saved_max_shapes = self._dynamic_output_shapes() return self.saved_min_shapes, self.saved_max_shapes @staticmethod + # 用于检查动态列名称是否与数据集列名称匹配。 def __check_dynamic_column_name(dynamic_columns, dataset_columns): + # 如果在动态列中找不到与数据集列名称匹配的列,将引发RuntimeError。 for column in dynamic_columns: if column not in dataset_columns: raise RuntimeError("dynamic column [" + column + "] does not match any column in dataset: " + str(dataset_columns)) @staticmethod + # 用于检查动态列的形状是否与数据集列的形状匹配。 def __check_dynamic_column_shape(data, col, dynamic_columns): shape_mismatch = "dynamic column [" + col + "] with shape " + str(dynamic_columns[col]) + \ " does not match dataset column [" + col + "] with shape " + str(list(data[col].shape)) - if data[col].ndim != len(dynamic_columns[col]): + # 检查data[col]的形状是否与dynamic_columns[col]的形状一致 + if data[col].ndim!= len(dynamic_columns[col]): + # 如果data[col]的形状不一致,抛出异常 raise RuntimeError(shape_mismatch) + # 遍历dynamic_columns[col],检查每一个元素是否与data[col]的形状一致 for dim in range(len(dynamic_columns[col])): - if dynamic_columns[col][dim] is not None and dynamic_columns[col][dim] != data[col].shape[dim]: + if dynamic_columns[col][dim] is not None and dynamic_columns[col][dim]!= data[col].shape[dim]: + # 如果data[col]的形状不一致,抛出异常 raise RuntimeError(shape_mismatch) def _dynamic_output_shapes(self): """ - Get dynamic information of source data. + 用于获取动态来源数据的形状信息。 Returns: lists, dynamic_shapes, min_shapes, max_shapes of source data. """ + # 如果dynamic_setting[1]为False,则抛出RuntimeError if not self.dynamic_setting[1]: raise RuntimeError("dynamic_columns is not set, call set_dynamic_columns() by final Dataset Op.") + # 如果saved_output_shapes和saved_min_shapes和saved_max_shapes都不为空,则返回saved_output_shapes, saved_min_shapes, saved_max_shapes if self.saved_output_shapes is not None and self.saved_min_shapes is not None and \ self.saved_max_shapes is not None: return self.saved_output_shapes, self.saved_min_shapes, self.saved_max_shapes + # 记录一个警告信息,表示正在计算动态来源数据的形状信息 logger.warning("Calculating dynamic shape of input data, this will take a few minutes...") - # Assume data1 shape is dynamic, data2 shape is fix + # 假设data1的形状是动态的,而data2的形状是固定的。 dynamic_columns = self.dynamic_setting[1] # ["data1", "data2"] + # 获取数据集的列名称 dataset_columns = self.get_col_names() + # 检查动态列的名称是否与数据集列名称匹配 Dataset.__check_dynamic_column_name(dynamic_columns, dataset_columns) # Shape[1] of data1 is variable # {"data1": {(batch_size, 100, feat_len), (16, 200, 83)}, "data2": {(batch_size, feat_len)}} + # 创建一个字典 column_shape_set = {col: set() for col in dataset_columns} dataset_size_counter = 0 + # 遍历数据集的迭代器 for data in self.create_dict_iterator(num_epochs=1, output_numpy=True): dataset_size_counter += 1 for col in data.keys(): if col in dynamic_columns: + # 检查每个列的形状是否与动态列设置相匹配 Dataset.__check_dynamic_column_shape(data, col, dynamic_columns) + # 对于每个匹配的列,它会将形状添加到column_shape_set中 column_shape_set[col].add(tuple(data[col].shape)) - # we get dataset_size after dryrun + # 增加数据集大小计数器 self.dataset_size = dataset_size_counter + # 用于处理数据集中的特征列的形状 + # 分别用于存储最小形状、最大形状和动态形状 min_shapes, max_shapes, dynamic_shapes = list(), list(), list() + # 遍历column_shape_set中的每个特征列 for col, shape_set in column_shape_set.items(): + # 检查是否具有多个形状 if len(shape_set) > 1: + # 检查列是否在dynamic_columes中 if col not in dynamic_columns: raise RuntimeError("column [" + col + "] has dynamic shape but not set by set_dynamic_columns()" + ", shapes of [" + col + "]: " + str(list(shape_set))) shape_npy = np.array(list(shape_set)) + # 计算这些形状的最大值和最小值 max_shape = shape_npy.max(axis=0) min_shape = shape_npy.min(axis=0) - # Set min shape to 1 due to unknown shuffle + # 检查dynamic_columns字典中是否存在当前特征列的键。 + # 如果不存在,那么将最小形状设置为1(因为未知shuffle) min_shape = np.where(np.equal(dynamic_columns[col], None), 1, min_shape) - # Set dynamic dim to -1 for ME + # 如果存在,将动态维度设置为-1(对于ME) dynamic_shape = np.where(np.equal(dynamic_columns[col], None), -1, dynamic_columns[col]) + # 将它们添加到相应的列表中 max_shapes.append(max_shape.tolist()) min_shapes.append(min_shape.tolist()) dynamic_shapes.append(dynamic_shape.tolist()) else: - # Also append fix shape to keep order of column shape + # 那么将特征列的固定形状添加到相应的列表中 fix_shape = list(list(shape_set)[0]) max_shapes.append(fix_shape) min_shapes.append(fix_shape) dynamic_shapes.append(fix_shape) if col in dynamic_columns: + # 检查dynamic_columns字典中是否存在该特征列的键 logger.warning("column [" + col + "] has no dynamic shape but set by set_dynamic_columns()") - # Set min shape to 1 due to unknown shuffle + # 将最小形状设置为1(因为未知shuffle) min_shapes[-1] = np.where(np.equal(dynamic_columns[col], None), 1, fix_shape).tolist() - # Set dynamic dim to -1 for ME + # 将动态维度设置为-1(对于ME dynamic_shapes[-1] = np.where(np.equal(dynamic_columns[col], None), -1, fix_shape).tolist() + # 返回处理后的动态形状、最小形状和最大形状 return dynamic_shapes, min_shapes, max_shapes def num_classes(self): """ - Get the number of classes in a dataset. + 用于从数据集中获取类别的数量。 Returns: int, number of classes. @@ -1770,61 +1988,85 @@ class Dataset: >>> # dataset is an instance object of Dataset >>> num_classes = dataset.num_classes() """ + # 检查_num_classes变量是否为None if self._num_classes is None: + # 初始化一个运行时获取器 runtime_getter = self._init_tree_getters() + # 获取类别的数量,并存储在_num_classes变量中 self._num_classes = runtime_getter[0].GetNumClasses() + # 关闭运行时获取器的线程池 runtime_getter[2].close_pool() + # 通知监视器 runtime_getter[2].notify_watchdog() if self._num_classes == -1: return None return self._num_classes + # 用于获取当前节点的同步通知器 def get_sync_notifiers(self): + # 检查当前节点是否有子节点 if self.children: + # 递归地调用子节点的get_sync_notifiers()方法 return self.children[0].get_sync_notifiers() + # 返回一个空字典 return {} + # 用于禁用当前节点的同步功能 def disable_sync(self): + # 检查当前节点是否有子节点 if self.children: + # 递归地调用子节点的disable_sync()方法 return self.children[0].disable_sync() + # 返回一个空字典 return {} + # 用于检查当前节点是否处于同步状态 def is_sync(self): + # 检查当前节点是否有子节点 if self.children: + # 递归地调用子节点的is_sync()方法 return self.children[0].is_sync() + # 返回False return False def sync_update(self, condition_name, num_batch=None, data=None): """ - Release a blocking condition and trigger callback with given data. + 用于释放阻塞条件并触发回调。 Args: - condition_name (str): The condition name that is used to toggle sending next row. - num_batch (Union[int, None]): The number of batches (rows) that are released. - When num_batch is None, it will default to the number specified by the - sync_wait operator (default=None). - data (Any): The data passed to the callback, user defined (default=None). + condition_name (str): 条件名称。 + num_batch (Union[int, None]): 批次数量。 + data (Any): 数据。 """ + # 检查num_batch是否为整数且大于0 if (not isinstance(num_batch, int) and num_batch is not None) or \ (isinstance(num_batch, int) and num_batch <= 0): # throwing exception, disable all sync_wait in pipeline + # 禁用所有同步等待在管道中 self.disable_sync() + # 抛出异常 raise RuntimeError("Sync_update batch size can only be positive integer, got : {}.".format(num_batch)) + # 获取当前节点的同步通知器字典 notifiers_dict = self.get_sync_notifiers() + # 检查条件名称是否为字符串 if not isinstance(condition_name, str): raise TypeError("Argument condition_name with value {} is not of type str, but got {}." .format(condition_name, type(condition_name))) + # 如果条件名称不在通知器字典中 if condition_name not in notifiers_dict: - # throwing exception, disable all sync_wait in pipeline + # 抛出运行时错误异常并禁用所有同步等待在管道中 self.disable_sync() raise RuntimeError("Condition name not found.") + # 如果num_batch不是None if num_batch is not None: + # 将其乘以当前批次大小,以便在回调函数中正确处理批次数据 num_batch *= self.get_batch_size() + # 使用通知器字典中的条件名称调用回调函数,并将num_batch和data作为参数传递。 notifiers_dict[condition_name](num_batch, data) def get_batch_size(self): """ - Return the size of batch. + 用于获取数据集批次大小。 Returns: int, the number of data in a batch. @@ -1833,16 +2075,20 @@ class Dataset: >>> # dataset is an instance object of Dataset >>> batch_size = dataset.get_batch_size() """ + # 检查_batch_size变量是否为None if self._batch_size is None: + # 初始化一个运行时获取器 runtime_getter = self._init_tree_getters() + # 从运行时获取器中获取批次大小 self._batch_size = runtime_getter[0].GetBatchSize() + # 如果没有批次大小,那么默认设置为1 if self._batch_size is None: self._batch_size = 1 return self._batch_size def get_repeat_count(self): """ - Get the replication times in RepeatDataset (default is 1). + 用于获取重复数据集重复次数。 Returns: int, the count of repeat. @@ -1851,16 +2097,20 @@ class Dataset: >>> # dataset is an instance object of Dataset >>> repeat_count = dataset.get_repeat_count() """ + # 检查_repeat_count变量是否为None if self._repeat_count is None: + # 初始化一个运行时获取器 runtime_getter = self._init_tree_getters() + # 从运行时获取器中获取重复次数 self._repeat_count = runtime_getter[0].GetRepeatCount() + # 如果没有重复次数,那么默认设置为1 if self._repeat_count is None: self._repeat_count = 1 return self._repeat_count def get_class_indexing(self): """ - Return the class index. + 用于获取数据集类别索引的函数。 Returns: dict, a str-to-int mapping from label name to index. @@ -1871,44 +2121,59 @@ class Dataset: >>> # dataset is an instance object of Dataset >>> class_indexing = dataset.get_class_indexing() """ + # 检查当前节点是否有子节点 if self.children: + # 递归地调用子节点的get_class_indexing()方法 return self.children[0].get_class_indexing() return {} def reset(self): """Reset the dataset for next epoch.""" + # 检查数据集或其子节点是否已经打乱 def is_shuffled(self): """Returns True if the dataset or its children is shuffled.""" + # 遍历当前节点的所有子节点 for input_dataset in self.children: + # 如果子节点的is_shuffled()方法返回True if input_dataset.is_shuffled(): + # 返回True return True - + #否则返回False return False + # 检查数据集或其子节点是否已经分片 def is_sharded(self): """Returns True if the dataset or its children is sharded.""" + # 遍历当前节点的所有子节点 for input_dataset in self.children: + # 如果子节点的is_sharded()方法返回True if input_dataset.is_sharded(): + # 返回True return True - + #否则返回False return False def parse(self, children=None): raise NotImplementedError("Dataset has to implement parse method.") + # 用于对解析后的数据进行后处理 def post_parse(self, ir_node): + # 检查cache变量是否存在 if self.cache: + # 如果存在,那么将IR节点设置为使用缓存客户端 ir_node = ir_node.set_cache_client(self.cache.cache_client) + # 检查num_parallel_workers变量是否存在 if self.num_parallel_workers: + # 如果存在,那么将IR节点设置为使用指定的并行工作进程数 ir_node = ir_node.set_num_workers(self.num_parallel_workers) - + # 返回处理后的IR节点 return ir_node class VisionBaseDataset(Dataset): """ - Abstract class to represent a vision source dataset which produces content to the data pipeline. + 一个用于处理视觉来源数据的抽象数据集。 """ def __init__(self, children=None, num_parallel_workers=None, cache=None): @@ -1920,7 +2185,7 @@ class VisionBaseDataset(Dataset): class TextBaseDataset(Dataset): """ - Abstract class to represent a text source dataset which produces content to the data pipeline. + 一个用于处理文本来源数据的抽象数据集。 """ def __init__(self, children=None, num_parallel_workers=None, cache=None): @@ -1931,7 +2196,7 @@ class TextBaseDataset(Dataset): def build_vocab(self, columns, freq_range, top_k, special_tokens, special_first): """ - Function to create a Vocab from source dataset. + 用于从数据集中创建一个Vocab。 Desired source dataset is a text type dataset. Build a vocab from a dataset. This would collect all the unique words in a dataset and return a vocab @@ -1939,16 +2204,11 @@ class TextBaseDataset(Dataset): Args: - columns(Union[str, list[str]]): Column names to get words from. - freq_range(tuple[int]): A tuple of integers (min_frequency, max_frequency). Words within the frequency - range will be stored. - Naturally 0 <= min_frequency <= max_frequency <= total_words. min_frequency/max_frequency - can be set to default, which corresponds to 0/total_words separately. - top_k(int): Number of words to be built into vocab. top_k most frequent words are - taken. The top_k is taken after freq_range. If not enough top_k, all words will be taken - special_tokens(list[str]): A list of strings, each one is a special token. - special_first(bool): Whether special_tokens will be prepended/appended to vocab, If special_tokens - is specified and special_first is set to default, special_tokens will be prepended. + columns(Union[str, list[str]]): 一个字符串或列表,用于指定从数据集中获取单词的列名。 + freq_range(tuple[int]): 一个整数元组,表示单词的频率范围。 + top_k(int): 一个整数,表示要构建的词汇表中的最大单词数。 + special_tokens(list[str]): 一个字符串列表,表示特殊令牌。 + special_first(bool): 一个布尔值,表示特殊令牌是否prepend/append到词汇表中。 Returns: Vocab, vocab built from the dataset. @@ -1969,54 +2229,58 @@ class TextBaseDataset(Dataset): ... special_first=True) """ + # 创建一个名为vocab的Vocab对象 vocab = cde.Vocab() columns = replace_none(columns, []) + # 检查columns参数是否为列表 if not isinstance(columns, list): + # 如果不是,则将其转换为列表 columns = [columns] + # 替换freq_range参数中的None值 freq_range = replace_none(freq_range, (0, 9223372036854775807)) if freq_range[0] is None: freq_range = (0, freq_range[1]) if freq_range[1] is None: + # 使其在0到9223372036854775807之间 freq_range = (freq_range[0], 9223372036854775807) special_tokens = replace_none(special_tokens, []) + # 替换top_k参数中的None值,使其等于9223372036854775807 top_k = replace_none(top_k, 9223372036854775807) + # 创建一个IR树和API树 ir_tree, api_tree = self.create_ir_tree() - # vocab node + # vocab node,用于存储词汇表的构建过程 vocab_node = cde.BuildVocabNode(ir_tree, vocab, columns, freq_range, top_k, special_tokens, special_first) + # 创建一个PythonRuntimeContext对象 runtime_context = cde.PythonRuntimeContext() runtime_context.Init() - # build vocab + # 构建词汇表 consumer = cde.PythonBuildVocabConsumer() consumer.Init(vocab_node) runtime_context.AssignConsumer(consumer) + # 启动Consumer对象,完成词汇表的构建 consumer.Start() - del api_tree - + return vocab def build_sentencepiece_vocab(self, columns, vocab_size, character_coverage, model_type, params): """ - Function to create a SentencePieceVocab from source dataset. + 用于从数据集中创建一个SentencePieceVocab。 Desired source dataset is a text type dataset. Args: - columns(list[str]): Column names to get words from. - vocab_size(int): Vocabulary size. - character_coverage(float): Percentage of characters covered by the model, must be between - 0.98 and 1.0 Good defaults are: 0.9995 for languages with rich character sets like - Japanese or Chinese character sets, and 1.0 for other languages with small character sets - like English or Latin. - model_type(SentencePieceModel): Model type. Choose from unigram (default), bpe, char, or word. + columns(list[str]): 指定词汇表的来源。 + vocab_size(int): 词汇大小。 + character_coverage(float): 字符覆盖率。 + model_type(SentencePieceModel): 模型类型。 Choose from unigram (default), bpe, char, or word. The input sentence must be pretokenized when using word type. - params(dict): Any extra optional parameters of sentencepiece library according to your raw data - + params(dict): 根据你的行数据的额外参数。 Returns: SentencePieceVocab, vocab built from the dataset. @@ -2027,35 +2291,43 @@ class TextBaseDataset(Dataset): >>> dataset = ds.TextFileDataset("/path/to/sentence/piece/vocab/file", shuffle=False) >>> dataset = dataset.build_sentencepiece_vocab(["text"], 5000, 0.9995, SentencePieceModel.UNIGRAM, {}) """ + # 检查model_type是否为SentencePieceModel中的值 if not isinstance(model_type, SentencePieceModel): raise TypeError("Argument model_type with value {0} is not of type SentencePieceModel, but got {1}."\ .format(model_type, type(model_type))) + # 将model_type从SentencePieceModel类型转换为DE_C_INTER_SENTENCEPIECE_MODE字典中的对应值 model_type = DE_C_INTER_SENTENCEPIECE_MODE[model_type] + # 创建一个名为vocab的SentencePieceVocab对象 vocab = cde.SentencePieceVocab() + # 创建一个IR树和API树 ir_tree, api_tree = self.create_ir_tree() - # vocab node + # 创建一个名为vocab_node的BuildSentenceVocabNode对象,用于存储词汇表的构建过程 vocab_node = cde.BuildSentenceVocabNode(ir_tree, vocab, columns, vocab_size, character_coverage, model_type, params) + # 创建一个PythonRuntimeContext对象 runtime_context = cde.PythonRuntimeContext() runtime_context.Init() - # build vocab + # 构建词汇表 consumer = cde.PythonBuildVocabConsumer() consumer.Init(vocab_node) runtime_context.AssignConsumer(consumer) + # 启动Consumer对象,完成词汇表的构建 consumer.Start() + # 删除API树 del api_tree + # 返回构建好的Vocab对象 return vocab class AudioBaseDataset(Dataset): """ - Abstract class to represent a audio source dataset which produces content to the data pipeline. + 一个用于处理音频来源数据的抽象数据集。 """ def __init__(self, children=None, num_parallel_workers=None, cache=None): @@ -2067,7 +2339,7 @@ class AudioBaseDataset(Dataset): class UnionBaseDataset(VisionBaseDataset, TextBaseDataset, AudioBaseDataset): """ - Abstract class to represent a union source dataset which produces content to the data pipeline. + 一个用于处理联合源数据的抽象数据集。 """ def __init__(self, children=None, num_parallel_workers=None, cache=None): @@ -2079,7 +2351,14 @@ class UnionBaseDataset(VisionBaseDataset, TextBaseDataset, AudioBaseDataset): class SourceDataset(Dataset): """ - Abstract class to represent a source dataset which produces content to the data pipeline. + 表示一个源数据集的抽象类。 + + 参数: + num_parallel_workers:设置数据集的并行工作进程数量。 + num_samples:设置数据集的样本数量。 + shuffle:设置数据集是否打乱数据。 + num_shards:设置数据集的分片数量 + shard_id:设置数据集的分片ID。 """ def __init__(self, num_parallel_workers=None, num_samples=None, shuffle=True, num_shards=None, shard_id=None, @@ -2093,12 +2372,16 @@ class SourceDataset(Dataset): raise TypeError("shuffle must be of boolean or enum of 'Shuffle' values like 'Shuffle.GLOBAL' or " "'Shuffle.FILES' or 'Shuffle.INFILE'.") + # 定义了一个名为shuffle_flag,表示数据打乱的方式 self.shuffle_flag = 2 # Global shuffle + # shuffle参数可以设置为True、False或Shuffle枚举中的一个值 if not isinstance(shuffle, Shuffle): + # 如果shuffle参数为True或未设置,则使用全局打乱 if shuffle is None or shuffle: self.shuffle_flag = 2 # Global shuffle else: self.shuffle_flag = 0 # No shuffle + # 如果shuffle参数为False else: if shuffle == Shuffle.GLOBAL: self.shuffle_flag = 2 # Global shuffle @@ -2113,38 +2396,53 @@ class SourceDataset(Dataset): @staticmethod def _find_files(patterns): """ - Utility function to search for files with the given glob patterns. + 用于根据给定的glob模式搜索文件。 Args: - patterns (Union[str, list[str]]): String or list of patterns to be searched. + patterns (Union[str, list[str]]): 用于被搜索的字符串或元组。 Returns: list, list of files. """ - + # 检查patterns是否是一个列表 if not isinstance(patterns, list): + # 如果不是,将其转换为列表 patterns = [patterns] + # 定义了两个空列表 file_list = [] unmatched_patterns = [] + # 遍历输入的 patterns 列表中的每个模式 for pattern in patterns: + # 查找与模式匹配的所有文件,recursive=True 参数表示递归搜索,以便在子目录中也查找文件 matches = [match for match in glob.glob(pattern, recursive=True) if os.path.isfile(match)] + # 对于找到的每个匹配文件,检查它是否是一个文件 if matches: + # 如果是文件,将其添加到 file_list 列表中 file_list.extend(matches) + # 如果 matches 列表为空 else: + # 将当前模式添加到 unmatched_patterns 列表中 unmatched_patterns.append(pattern) + # 检查 unmatched_patterns列表是否为空 if unmatched_patterns: + # 不为空时抛出异常 raise ValueError("The following patterns did not match any files: {}.".format(unmatched_patterns)) - if file_list: # not empty + # 如果 file_list 非空 + if file_list: + #返回 file_list return file_list + # 否则,抛出一个 ValueError 异常 raise ValueError("The list of path names matching the patterns is empty.") + # 用于检查一个对象是否已经打乱 def is_shuffled(self): return self.shuffle_flag > 0 + # 用于检查一个对象是否已经分片 def is_sharded(self): if self.num_shards is not None: return self.num_shards > 1 @@ -2153,7 +2451,16 @@ class SourceDataset(Dataset): class MappableDataset(SourceDataset): """ - Abstract class to represent a source dataset which supports use of samplers. + 为源数据集提供一个支持采样器的抽象类。 + + 参数: + num_parallel_workers: 用于指定数据并行处理的线程数。 + sampler: 用于指定采样器。 + num_samples: 用于指定采样的样本数量。 + shuffle: 用于指定是否打乱数据。 + num_shards: 用于指定分片的数量。 + shard_id: 用于指定分片的ID。 + cache: 用于指定缓存。 """ def parse(self, children=None): @@ -2183,6 +2490,7 @@ class MappableDataset(SourceDataset): new_sampler.add_child(self.sampler) self.sampler = new_sampler + # 用于为当前数据集添加一个子采样器 def use_sampler(self, new_sampler): """ Replace the last child sampler of the current dataset, remaining the parent sampler unchanged. @@ -2196,35 +2504,38 @@ class MappableDataset(SourceDataset): >>> new_sampler = ds.DistributedSampler(10, 2) >>> dataset.use_sampler(new_sampler) """ + # 检查new_sampler是否为空 if new_sampler is None: + # 抛出一个类型错误异常 raise TypeError("Input sampler can not be None.") + # 检查输入的 new_sampler 是否是一个 Sampler 类的实例 if not isinstance(new_sampler, (samplers.BuiltinSampler, samplers.Sampler)): + # 如果不是,则抛出一个类型错误异常 raise TypeError("Input sampler is not an instance of a sampler.") + # 当前数据集的 dataset_size 属性设置为空 self.dataset_size = None + # 将子采样器添加到当前采样器的子采样器列表中 self.sampler = self.sampler.child_sampler + # 将输入的 new_sampler 设置为当前数据集的 sampler 属性 self.add_sampler(new_sampler) + # 用于检查当前数据集是否已经打乱 def is_shuffled(self): return self.sampler.is_shuffled() + # 用于检查当前数据集是否已经分片 def is_sharded(self): return self.sampler.is_sharded() @check_split def split(self, sizes, randomize=True): """ - Split the dataset into smaller, non-overlapping datasets. + 用于将数据集划分为多个非重叠子数据集 Args: - sizes (Union[list[int], list[float]]): If a list of integers [s1, s2, …, sn] is - provided, the dataset will be split into n datasets of size s1, size s2, …, size sn - respectively. If the sum of all sizes does not equal the original dataset size, an - error will occur. - If a list of floats [f1, f2, …, fn] is provided, all floats must be between 0 and 1 - and must sum to 1, otherwise an error will occur. The dataset will be split into n - Datasets of size round(f1*K), round(f2*K), …, round(fn*K) where K is the size of the - original dataset. + sizes (Union[list[int], list[float]]): 表示子数据集的大小。它可以是一个整数列表,其中每个元素表示一个 + 子数据集的大小;也可以是一个浮点数列表,其中每个元素表示一个子数据集的大小占原始数据集大小的比例。 If after rounding: - Any size equals 0, an error will occur. @@ -2232,9 +2543,8 @@ class MappableDataset(SourceDataset): - The sum of split sizes > K, the difference will be removed from the first large enough split such that it will have at least 1 row after removing the difference. - randomize (bool, optional): Determines whether or not to split the data randomly (default=True). - If True, the data will be randomly split. Otherwise, each split will be created with - consecutive rows from the dataset. + randomize (bool, optional): 表示是否对子数据集进行随机排序。如果为 True,则对子数据集进行随机排序;如果 + 为 False,则子数据集按照原始顺序排列。 Note: 1. There is an optimized split function, which will be called automatically when the dataset @@ -2273,25 +2583,38 @@ class MappableDataset(SourceDataset): >>> train_sampler = ds.DistributedSampler(10, 2) >>> train_dataset.use_sampler(train_sampler) """ + # 如果数据集打乱 if self.is_shuffled(): + # 产生一个警告 logger.warning("Dataset is shuffled before split.") + # 如果数据集已经分片 if self.is_sharded(): + # 抛出一个异常 raise RuntimeError("Dataset should not be sharded before split.") + # 计算绝对大小的列表 absolute_sizes = self._get_absolute_split_sizes(sizes) + # 初始化一个空列表,用于存储划分的子数据集 splits = [] + # 初始化一个变量,用于存储当前子数据集的起始索引 current_split_start_index = 0 + # 遍历 absolute_sizes 列表中的每个大小 for size in absolute_sizes: + # 创建一个新的 Dataset 实例 ds ds = copy.deepcopy(self) + # 将 dataset_size 设置为 None ds.dataset_size = None + # 如果 randomize 为 True if randomize: # want to shuffle the same way every epoch before split, we are assuming # that the user will call set_seed + # 添加一个 RandomSampler 实例到 ds 中 random_sampler = samplers.RandomSampler() random_sampler.reshuffle_each_epoch = False ds.add_sampler(random_sampler) + # 为当前子数据集添加一个 SequentialSampler 实例,按照原始数据的顺序对数据进行采样 subset_sampler = samplers.SequentialSampler(current_split_start_index, size) ds.add_sampler(subset_sampler) @@ -2299,16 +2622,28 @@ class MappableDataset(SourceDataset): # get rid of the sequential sampler instead of something we need ds.add_sampler(samplers.SequentialSampler()) + # 将当前子数据集添加到 splits 列表中 splits.append(ds) + # 更新为当前子数据集的结束索引加1 current_split_start_index += size - + # 返回一个包含所有划分的子数据集的元组 return tuple(splits) class BucketBatchByLengthDataset(UnionBaseDataset): """ - The result of applying BucketBatchByLength operator to the input dataset. + 用于将输入数据集按照指定的桶边界和批次大小进行分桶处理。 + + 参数: + input_dataset: 输入数据集。 + column_names: 表示输入数据集中需要分桶的列名。 + bucket_boundaries: 表示桶边界列表,用于将数据分桶。 + bucket_batch_sizes: 表示每个桶的批次大小列表。 + element_length_function: 表示计算数据元素长度的函数。 + pad_info: 表示填充信息的字典。 + pad_to_bucket_boundary: 表示是否将数据填充到桶边界。 + drop_remainder: 表示是否丢弃剩余的数据。 """ def __init__(self, input_dataset, column_names, bucket_boundaries, bucket_batch_sizes, element_length_function, @@ -2331,21 +2666,28 @@ class BucketBatchByLengthDataset(UnionBaseDataset): def _check_shm_usage(num_worker, queue_size, max_rowsize, num_queues=1): """ - Check sufficient shared memory is available for shared memory queues - when training in parallel mode. + 用于检查是否有足够的共享内存可用,以便在并行训练模式下使用共享内存队列。 """ + # 定义共享内存的使用率阈值为80% threshold_ratio = 0.8 + # 检查操作系统是否为 Windows 或 Darwin if platform.system().lower() not in {"windows", "darwin"}: + # 获取设备的数量 device_num = _get_device_num() # In the cluster, _get_device_num indicates the number of the entire cluster. The maximum number of cards # on the ascend server is 8. + # 根据设备数量调整阈值比例 if device_num > 1 and context.get_context("device_target") == "Ascend": device_num = min(device_num, 8) + # 计算所需的共享内存大小 shm_estimate_usage = device_num * num_worker * num_queues * \ (queue_size + 2) * max_rowsize * 1024 * 1024 try: + # 获取 '/dev/shm' 中的可用共享内存大小 shm_available = psutil.disk_usage('/dev/shm').free + # 如果所需的共享内存大小超过 80% 阈值 if shm_estimate_usage >= threshold_ratio * shm_available: + # 抛出一个异常,提示用户需要减少内存使用或调整参数 raise RuntimeError( "Insufficient shared memory available. Required: {}, Available: {}. " "The required memory can't exceed 80% of the available shared memory, " @@ -2354,41 +2696,28 @@ def _check_shm_usage(num_worker, queue_size, max_rowsize, num_queues=1): "2. reduce prefetch size by set_prefetch_size().\n" "3. disable shared memory by set_enable_shared_mem()." .format(shm_estimate_usage, shm_available)) + # 如果 '/dev/shm' 不存在,则抛出一个异常 except FileNotFoundError: raise RuntimeError("Expected /dev/shm to exist.") class BatchDataset(UnionBaseDataset): """ - The result of applying Batch operator to the input dataset. + 用于将输入数据集按照指定的批次大小进行分批处理。 Args: - input_dataset (Dataset): Input Dataset to be batched. - batch_size (Union[int, function]): The number of rows each batch is created with. An - int or callable which takes exactly 1 parameter, BatchInfo. - drop_remainder (bool, optional): Determines whether or not to drop the last - possibly incomplete batch (default=False). If True, and if there are less - than batch_size rows available to make the last batch, then those rows will - be dropped and not propagated to the child node. - num_parallel_workers (int, optional): Number of workers to process the dataset in parallel (default=None). - per_batch_map (callable, optional): Per batch map callable. A callable which takes - (list[Tensor], list[Tensor], ..., BatchInfo) as input parameters. Each list[Tensor] represents a batch of - Tensors on a given column. The number of lists should match with number of entries in input_columns. The - last parameter of the callable must always be a BatchInfo object. - input_columns (Union[str, list[str]], optional): List of names of the input columns. The size of the list must - match with signature of per_batch_map callable. - output_columns (Union[str, list[str]], optional): List of names assigned to the columns outputted by - the last operation. This parameter is mandatory if len(input_columns) != - len(output_columns). The size of this list must match the number of output - columns of the last operation. (default=None, output columns will have the same - name as the input columns, i.e., the columns will be replaced). - column_order (Union[str, list[str]], optional): Specifies the list of all the columns you need in the whole - dataset. The parameter is required when len(input_column) != len(output_column). Caution: the list here - is not just the columns specified in parameter input_columns and output_columns. - pad_info (dict, optional): Whether to perform padding on selected columns. pad_info={"col1":([224,224],0)} - will pad column with name "col1" to a tensor of size [224,224] and fill the missing with 0. - max_rowsize(int, optional): Maximum size of row in MB that is used for shared memory allocation to copy - data between processes. This is only used if python_multiprocessing is set to True (default=16). + input_dataset (Dataset): 输入数据集。 + batch_size (Union[int, function]): 表示批次大小。如果 batch_size 是整数,则表示每个批次包含的行数;如果 batch_size + 是函数,则表示根据 BatchInfo 参数计算批次大小。 + drop_remainder (bool, optional): 表示是否丢弃剩余的不完整的批次。 + num_parallel_workers (int, optional): 表示用于处理数据集的并行工作进程数量。 + per_batch_map (callable, optional): 表示每个批次映射函数。一个函数,接受一个元组参数,其中包含当前批次的数据和批次信息。 + input_columns (Union[str, list[str]], optional): 表示输入列名列表。 + output_columns (Union[str, list[str]], optional): 表示输出列名列表。 + column_order (Union[str, list[str]], optional): 表示所需的数据列顺序。 + pad_info (dict, optional): 表示是否进行填充。pad_info 是一个字典,其中键是列名,值是一个包含填充信息的元组。例如, + {"col1":([224,224],0)} 表示将列 "col1" 填充为大小为 [224,224] 的张量,并在缺失处填充 0。 + max_rowsize(int, optional): 表示用于共享内存分配的最大行大小(以 MB 为单位)。 """ @@ -2429,7 +2758,7 @@ class BatchDataset(UnionBaseDataset): @staticmethod def _is_ancestor_of_repeat(dataset): """ - Utility function to find the case where repeat is used before batch. + 用于检查给定的数据集中是否使用了重复(repeat)操作。 Args: dataset (Dataset): Dataset to be checked. @@ -2437,27 +2766,38 @@ class BatchDataset(UnionBaseDataset): Returns: bool, whether repeat is used before batch. """ + # 检查dataset是否为RepeatDataset类型 if isinstance(dataset, RepeatDataset): + # 如果是,则返回True,表示给定的数据集中使用了重复操作 return True + # 初始化一个布尔值flag,用于存储结果 flag = False + # 遍历dataset的子数据集 for input_dataset in dataset.children: + # 将每次调用返回的布尔值逐个或运算,最后得到flag的值。 flag = flag | BatchDataset._is_ancestor_of_repeat(input_dataset) + # 返回flag的值 return flag @staticmethod def _update_batch_size_for_syncwait(dataset, batch_size): """ - Utility function to notify batch size to sync_wait. + 用于通知给定的数据集中使用的批量大小。 Args: dataset (Dataset): Dataset to be checked. batch_size (int): batch size to notify. """ + # 检查dataset是否为SyncWaitDataset类型 if isinstance(dataset, SyncWaitDataset): + # 将batch_size作为参数传递 dataset.update_sync_batch_size(batch_size) + # 遍历dataset的子数据集, for input_dataset in dataset.children: + # 对每个子数据集递归地调用_update_batch_size_for_syncwait函数 BatchDataset._update_batch_size_for_syncwait(input_dataset, batch_size) + # 用于实现深拷贝 def __deepcopy__(self, memodict): return self.__safe_deepcopy__(memodict, exclude=("per_batch_map", "batch_size_func", "__transfer_dataset__")) @@ -2466,52 +2806,57 @@ class BatchDataset(UnionBaseDataset): # This method will create per iterator process pool and bind pyfunc execution to the pool. def iterator_bootstrap(self): """ - Per iterator bootstrap callback. + 用于执行迭代器初始化。 """ + # 检查self是否使用了python_multiprocessing if self.python_multiprocessing: + # 如果per_batch_map为None if self.per_batch_map is None: + # 警告用户per_batch_map没有指定 logger.warning("per_batch_map is None so python_multiprocessing is ignored for batch.") return - # If user didn't specify num_parallel_workers, set it to default + # 如果num_parallel_workers没有指定 if self.num_parallel_workers is None: + # 获取当前系统可以使用的最大进程数,并将其赋值 self.num_parallel_workers = get_num_parallel_workers() + # 创建了一个_PythonMultiprocessing对象,用于处理多进程 self.process_pool = _PythonMultiprocessing(str(self), self.num_parallel_workers, [self.per_batch_map], self.max_rowsize * self.batch_size) - # Wrap per_batch_map into _PythonCallable + # 将self.per_batch_map包装为一个_PythonCallable对象 self.per_batch_map = _PythonCallable(self.per_batch_map, 0, self.process_pool) else: if self.per_batch_map is not None: + # 将self.per_batch_map包装为一个FuncWrapper对象,以便在处理单进程时使用。 self.per_batch_map = FuncWrapper(self.per_batch_map) class BatchInfo(cde.CBatchInfo): """ - Only the batch size function and per_batch_map of the batch operator can dynamically adjust parameters - based on the number of batches and epochs during training. + 提供了一个动态调整参数的接口,以便在训练过程中根据批次和周期数量进行调整。 """ def get_batch_num(self): """ - Return the batch number of the current batch. + 获取当前批次的编号。 """ return def get_epoch_num(self): """ - Return the epoch number of the current batch. + 获取当前周期的编号。 """ return class BlockReleasePair: """ - The blocking condition class used by SyncWaitDataset. + 用于实现阻塞条件。 Args: - init_release_rows (int): Number of lines to allow through the pipeline. - callback (function): The callback function that will be called when release is called (default=None). + init_release_rows (int): 初始释放行数。 + callback (function): 回调函数,默认为None。 """ def __init__(self, init_release_rows, callback=None): @@ -2523,66 +2868,84 @@ class BlockReleasePair: self.default_rows = init_release_rows self.disable = False + # 用于实现深拷贝 def __deepcopy__(self, memodict): return self + # 用于重置阻塞条件 def reset(self): + # 获取cv对象的锁 with self.cv: + # 将row_count设置为-default_row self.row_count = -self.default_rows + # 通知所有等待的线程,这样阻塞条件就重置为了初始状态。 self.cv.notify_all() + # 用于更新批次大小 def update_batched_size(self, batch_size): - # sanity check + # 检查batch_size是否为整数且大于0 if isinstance(batch_size, int) and batch_size <= 0: raise ValueError("batch_size need to be greater than 0.") - # should only use before the pipeline creates + # 在管道创建之前使用,因为它会改变阻塞条件的性质 self.row_count *= batch_size self.default_rows *= batch_size def block_func(self): """ - Function for handing blocking condition. + 用于处理阻塞条件。 Returns: bool, True. """ + # 获取cv对象的锁 with self.cv: - # if disable is true, the always evaluate to true + # 如果disable为True,那么条件总是为真 not_time_out = self.cv.wait_for(lambda: (self.row_count < 0 or self.disable), timeout=get_callback_timeout()) - # time_out will be False if time out occurs + # 等待超时,记录一个警告日志 if not not_time_out: logger.warning("Timeout happened in sync_wait, maybe dataset.sync_update(condition=...) " "is not added after dataset.create_dict_iterator(...), now disabling lock.") + # 将disable设置为 True self.disable = True + # 将row_count加1 self.row_count += 1 return True + # 用于释放 def release_func(self, pass_rows=None, data=None): + # 执行完内部代码后,将自动释放资源 + # self.cv表示一个条件变量,用于等待其他线程释放资源 with self.cv: + # 如果pass_rows为None,则将其设置为self.default_rows if pass_rows is None: pass_rows = self.default_rows + # 将self.row_count减去pass_row self.row_count -= pass_rows if self.callback is not None: self.callback(data) + # 通知所有等待在self.cv上的线程,可以继续执行 self.cv.notify_all() + # 用于禁用锁 def disable_lock(self): with self.cv: + # 将self.disable设置为True self.disable = True + # 通知所有等待在self.cv上的线程,可以继续执行 self.cv.notify_all() class SyncWaitDataset(UnionBaseDataset): """ - The result of adding a blocking condition to the input Dataset. + 用于在输入数据集中添加一个阻塞条件,以便在每次epoch开始时等待特定条件成立 Args: - input_dataset (Dataset): Input dataset to apply flow control. - num_batch (int): Number of batches without blocking at the start of each epoch. - condition_name (str): Condition name that is used to toggle sending next row. - callback (function): Callback function that will be invoked when sync_update is called (default=None). + input_dataset (Dataset): 输入数据集。 + num_batch (int): 没有阻塞的批次数。 + condition_name (str): 条件名称。 + callback (function): 回调函数。 Raises: RuntimeError: If condition name already exists. @@ -2603,53 +2966,67 @@ class SyncWaitDataset(UnionBaseDataset): "If dataset.sync_update(condition=%s) has already been added, you can ignore the info.", condition_name, condition_name) + # 返回一个字典,其中包含了输入数据集的同步通知器中的所有条件名称及其对应的回调函数 def parse(self, children=None): return cde.SyncWaitNode(children[0], self._condition_name, self._pair.block_func) + # 将输入数据集的同步通知器与self._pair.release_func字典合并,返回一个新的字典 def get_sync_notifiers(self): return {**self.children[0].get_sync_notifiers(), **{self._condition_name: self._pair.release_func}} + # 返回True,表示SyncWaitDataset类是同步的 def is_sync(self): return True - + + # 用于更新没有阻塞的批次数 def update_sync_batch_size(self, batch_size): + # 检查batch_size是否为整数且大于0 if isinstance(batch_size, int) and batch_size <= 0: raise ValueError("num_batch need to be greater than 0.") + # 调用self._pair.update_batched_size方法更新没有阻塞的批次数 self._pair.update_batched_size(batch_size) + # 用于禁用同步 def disable_sync(self): + # 记录一条日志,表示正在禁用同步 logger.info("Disabling Sync") + # 禁用同步锁 self._pair.disable_lock() @staticmethod def _is_ancestor_of_batch(dataset): """ - Utility function to find the case where sync_wait is used before batch. + 用于检查sync_wait是否在batch之前使用 Args: - dataset (Dataset): Dataset to be checked. + dataset (Dataset): 被检查的数据集。 Returns: bool, whether sync_wait is used before batch. """ + # 检查dataset是否为BatchDataset的实例 if isinstance(dataset, BatchDataset): + # 如果是,则返回True return True flag = False + # 递归地检查dataset的子数据集,直到找到SyncWaitDataset实例 for input_dataset in dataset.children: + # 如果在某个子数据集中找到SyncWaitDataset实例,则将flag设置为True flag = flag | SyncWaitDataset._is_ancestor_of_batch(input_dataset) return flag + # 用于初始化迭代器 def iterator_bootstrap(self): self._pair.reset() class ShuffleDataset(UnionBaseDataset): """ - The result of applying Shuffle operator to the input Dataset. + 用于对输入的数据集进行打乱 Args: - input_dataset (Dataset): Input Dataset to be shuffled. - buffer_size (int): Size of the buffer. + input_dataset (Dataset): 输入的数据集。 + buffer_size (int): 缓冲区大小。 Raises: RuntimeError: If exist sync operators before shuffle. @@ -2672,17 +3049,17 @@ class ShuffleDataset(UnionBaseDataset): # Pyfunc collection for multiprocess pyfunc # This global variable will only be used within subprocesses -_GLOBAL_PYFUNC_LIST = [] -_ARGS_QUEUE = [] -_RET_QUEUE = [] -_OP_NAME = dict() -_OP_PROCESS = dict() -_LOCK = threading.Lock() +_GLOBAL_PYFUNC_LIST = []# 用于存储全局的Pyfunc集合 +_ARGS_QUEUE = []# 用于存储参数队列 +_RET_QUEUE = []# 用于存储返回队列 +_OP_NAME = dict()# 用于存储操作名称 +_OP_PROCESS = dict()# 用于存储操作进程 +_LOCK = threading.Lock()# 用于线程安全地访问全局变量 -# Pyfunc worker init function -# Python multiprocessing library forbid sending lambda function through pipe. -# This init function allow us to add all Python function to a global collection and then fork afterwards. +# 用于初始化Python多进程工作进程 +# Python的multiprocessing库不允许通过管道发送lambda函数 +# 允许我们将所有Python函数添加到全局集合中,然后在后续的 fork 操作之后。 def _pyfunc_worker_init(pyfunc_list, args_queue, ret_queue): # Some threads in multiprocess.pool can't process sigint signal, # and will occur hang problem, so ctrl+c will pass to parent process. @@ -2699,83 +3076,120 @@ def _pyfunc_worker_init(pyfunc_list, args_queue, ret_queue): # All exceptions will be raised to main processes def _pyfunc_worker_exec(index, qid, *args): """ - Internal function for call certain pyfunc in Python process. + 用于在Python工作进程中执行特定的Python函数。 """ # Some threads in multiprocess.pool can't process sigint signal, # and will occur hang problem, so ctrl+c will pass to parent process. + # 使用signal.signal方法设置Ctrl+C信号的处理函数为忽略 + # 在多进程工作进程中,如果发生Ctrl+C信号,程序会直接退出,而不会导致死锁 signal.signal(signal.SIGINT, signal.SIG_IGN) + # 如果qid不等于-1 if qid != -1: - # Pass arguments through the Queue instead of directly to remote process + # 通过队列_ARGS_QUEUE[qid]传递参数 + # 确保在多进程情况下,参数传递的可靠性 args = _ARGS_QUEUE[qid].get() try: + # 将获取到的任务参数(args)传递给_GLOBAL_PYFUNC_LIST[index]中存储的Python函数 r = _GLOBAL_PYFUNC_LIST[index](*args) except Exception: + # 如果执行过程中出现异常,使用ExceptionHandler函数处理异常 return ExceptionHandler(where="in map(or batch) worker and execute python function") + # 如果函数执行结果是一个元组 if isinstance(r, tuple): + # 将其放入队列_RET_QUEUE[qid]中 _RET_QUEUE[qid].put(r) else: + # 否则,将执行结果放入一个包含单个元素的元组中,然后将其放入队列_RET_QUEUE[qid]中 _RET_QUEUE[qid].put((r,)) + # 返回进程ID return [qid] - # not using shared memory for passing arguments, call function directly + # 如果当前进程没有使用共享内存来传递参数 result = None try: + # 直接调用_GLOBAL_PYFUNC_LIST[index]中存储的Python函数 result = _GLOBAL_PYFUNC_LIST[index](*args) except Exception: + # 如果执行过程中出现异常,使用ExceptionHandler函数处理异常 result = ExceptionHandler(where="in map(or batch) worker and execute python function") + # 返回函数执行结果 return result # PythonCallable wrapper for multiprocess pyfunc class _PythonCallable: """ - Internal Python function wrapper for multiprocessing pyfunc. + 用于封装用户提供的Python函数,以便在多进程环境中使用 """ def __init__(self, py_callable, idx, pool=None): - # Original Python callable from user. + # 原始Python调用able从用户提供。 self.py_callable = py_callable - # Process pool created for current iterator. + # 用于当前迭代器的进程池。 self.pool = pool - # Python callable index for subprocess _GLOBAL_PYFUNC_LIST + # Python调用able在_GLOBAL_PYFUNC_LIST中的索引。 self.idx = idx + # 当在多进程环境中调用_PythonCallable对象时,会检查当前进程的池是否正在运行,以及是否需要清理迭代器 def __call__(self, *args): + # 如果池正在运行并且不需要清理迭代器,那么使用pool.execute方法执行原始Python调用able,并将结果返回给调用者。 if self.pool.is_running() and check_iterator_cleanup() is False: try: return self.pool.execute(self.py_callable, self.idx, *args) except multiprocessing.TimeoutError: return self.py_callable(*args) - # Invoke original Python callable in master process in case the pool is gone. + # 如果池已经结束或者需要清理迭代器,那么直接调用原始Python调用able。 return self.py_callable(*args) + # 将调用者提供的Python函数转换为JSON格式 def to_json(self): return self.py_callable.to_json() class _PythonMultiprocessing(cde.PythonMultiprocessingRuntime): """ - A wrapper to multiprocessing.pool that performs cleanup and ensure proper termination of forked processes. + 实现多进程环境下的异常处理和清理工作。 + 参数: + op_name:操作名称。 + num_parallel_workers:并行工作进程数量。 + operations:一个包含多个Python函数的列表,这些函数将会在多进程环境中并发执行。 + max_row_size:每个进程处理的最大行数。 + process_pool:一个进程池对象,用于管理多进程任务。 + op_id:一个整数,表示当前正在处理的操作ID。 + arg_q_list:一个列表,用于存储任务参数队列。 + res_q_list:一个列表,用于存储任务结果队列。 + queues_map:一个字典,用于存储任务队列的映射关系。 + next_queue:一个整数,表示下一个要使用的队列ID。 + eot:一个标志位,表示是否已经到达结束位置。 + watch_dog:一个定时器对象,用于监控工作进程的运行状态。 + workers:一个列表,用于存储工作进程对象。 + ppid:一个整数,表示当前进程的ID。 + hook:一个对象,用于处理异常。 """ class _ExceptHookHandler: """ - Internal class ExceptionHandler + 一个内部类ExceptionHandler,用于处理异常。 """ def __init__(self): sys.excepthook = self.__handler_exception @staticmethod + # 用于在退出多进程池之前执行一些清理工作 def mp_pool_exit_preprocess(): + # 检查check_iterator_cleanup()的结果是否为False if check_iterator_cleanup() is False: - # Set the iterator_cleanup flag to True before exiting, and wait 3s for all apply_async - # applied to the multiprocessing task to prevent multiprocessing from hang when exiting + # 设置iterator_cleanup标志为True,并等待3秒钟,以便所有正在应用的apply_async任务完成 + # 这样可以避免多进程环境下的潜在问题 _set_iterator_cleanup() time.sleep(3) + # 用于处理异常 def __handler_exception(self, ex_type, value, tb): + # 当发生未捕获的异常时,它将记录一个严重错误消息 logger.critical("Uncaught exception: ", exc_info=(ex_type, value, tb)) + # 调用mp_pool_exit_preprocess方法进行一些清理工作 self.mp_pool_exit_preprocess() def __init__(self, op_name, num_parallel_workers, operations, max_row_size=16): @@ -2799,120 +3213,166 @@ class _PythonMultiprocessing(cde.PythonMultiprocessingRuntime): self.ppid = os.getpid() self.hook = None + # 用于启动一个新的多进程池 def launch(self, op_id=-1): + # 将op_id设置为当前操作的ID self.op_id = op_id + # 记录一个信息日志,表示正在启动一个新的多进程池 logger.info("Launching new Python Multiprocessing pool for Op:" + str(self.op_id)) + # 调用create_pool方法创建一个进程池 self.create_pool() + # 用于创建一个多进程池 def create_pool(self): """ Returns: """ + # 它检查是否启用了共享内存 if get_enable_shared_mem(): + # 如果是,则调用create_shared_memory方法创建共享内存 self.create_shared_memory() + # 如果进程池已经创建过,它会抛出一个异常 if self.process_pool is not None: raise Exception("Pool was already created, close it first.") - # Let gc collect unrefrenced memory to avoid child processes in the pool to do it + # 清除垃圾回收器的缓存,以确保子进程在池中运行时没有未引用的内存 gc.collect() - # Construct python multiprocessing pool. - # The _pyfunc_worker_init is used to pass lambda function to subprocesses. + # 使用multiprocessing.Pool创建一个进程池 + # processes:并行工作进程数量。 + # initializer:一个初始化函数,用于传递lambda函数给子进程。 + # initargs:一个元组,用于传递给初始化函数的参数。 self.process_pool = multiprocessing.Pool(processes=self.num_parallel_workers, initializer=_pyfunc_worker_init, initargs=(self.operations, self.arg_q_list, self.res_q_list)) + # 收集工作进程的详细信息 self.gather_workers_info() - + # 创建一个_PythonMultiprocessing._ExceptHookHandler对象作为异常处理器 self.hook = _PythonMultiprocessing._ExceptHookHandler() - # The op (Map, Batch, etc) multiprocessing will launch a watch dog thread for monitoring sub processes + # 启动一个监视器线程来监控子进程的运行状态 self._launch_watch_dog() + # 退出时调用mp_pool_exit_preprocess方法进行一些清理工作 atexit.register(self.hook.mp_pool_exit_preprocess) - # If Python version greater than 3.8, we need to close ThreadPool in atexit for unclean pool teardown. + # 在Python版本大于3.8的情况下,还需要关闭ThreadPool以避免不干净的池退化。 if sys.version_info >= (3, 8): atexit.register(self.process_pool.close) + # 用于终止一个多进程池 def terminate(self): + # 记录一个信息日志,表示正在终止一个多进程池 logger.info("Terminating Python Multiprocessing pool for Op:" + str(self.op_id)) + # 关闭进程池 self.close_pool() + # 中止监视器线程 self.abort_watchdog() + # 删除共享内存 self.delete_shared_memory() + # 将process_pool设置为None self.process_pool = None + # 用于从多进程池中获取进程ID def get_pids(self): - # obtain process IDs from multiprocessing.pool + # 提取所有进程的ID return [w.pid for w in self.workers] + # 用于增加多进程池中的工作进程数量 def add_new_workers(self, num_new_workers): + # 记录一个信息日志,表示正在增加一个操作的并行工作进程数量 logger.info( "Increasing num_parallel_workers of Python Multiprocessing pool for Op:" + str(self.op_id) + ", old num_workers=" + str(self.num_parallel_workers) + " new num_workers" + str(self.num_parallel_workers + num_new_workers) + ".") + # 调用terminate方法关闭进程池 self.terminate() + # 增加num_parallel_workers的值 self.num_parallel_workers += num_new_workers + # 重新启动进程池 self.launch(self.op_id) + # 用于减少多进程池中的工作进程数量 def remove_workers(self, num_removed_workers): + # 记录一个信息日志,表示正在减少一个操作的并行工作进程数量 logger.info( "Decreasing num_parallel_workers of Python Multiprocessing pool for Op:" + str(self.op_id) + ", old num_workers=" + str(self.num_parallel_workers) + " new num_workers" + str(self.num_parallel_workers - num_removed_workers) + ".") + # 调用terminate方法关闭进程池 self.terminate() + # 减少num_parallel_workers的值 self.num_parallel_workers -= num_removed_workers + # 重新启动进程池 self.launch(self.op_id) + # 用于检查进程池是否已启用 def is_mp_enabled(self): return self.process_pool is not None + # 用于创建一个共享内存区域,用于存储并行处理任务的结果 def create_shared_memory(self): + # 检查共享内存的使用情况,确保至少有 self.num_parallel_workers 个可用的共享内存区域 + # 每个区域的最大行大小为 self.max_row_size,同时最多有两个队列 _check_shm_usage(self.num_parallel_workers, 1, self.max_row_size, 2) + # 分别用于存储任务参数和结果 self.arg_q_list = [] self.res_q_list = [] + # 创建了一个字典,用于存储每个工作进程的队列映射关系 self.queues_map = {} self.next_queue = 0 + # 遍历 self.num_parallel_workers 次,创建了一个个 _SharedQueue 对象,并将它们添加到相应的列表和字典中 for _ in range(self.num_parallel_workers): self.arg_q_list.append(_SharedQueue(1, max_rowsize=self.max_row_size)) self.res_q_list.append(_SharedQueue(1, max_rowsize=self.max_row_size)) def delete_shared_memory(self): """ - Call this method to delete any shared memory created for this pool. + 用于删除之前创建的共享内存区域。 """ + # 检查是否有 self.arg_q_list 和 self.res_q_list 属性 if hasattr(self, 'arg_q_list') and self.arg_q_list is not None: + # 遍历相应的列表 arg_q_list_len = len(self.arg_q_list) for idx in range(arg_q_list_len): + # 逐个删除队列对象 del self.arg_q_list[arg_q_list_len - idx - 1] del self.arg_q_list if hasattr(self, 'res_q_list') and self.res_q_list is not None: + # 遍历相应的列表 res_q_list_len = len(self.res_q_list) for idx in range(res_q_list_len): + # 逐个删除队列对象 del self.res_q_list[res_q_list_len - idx - 1] del self.res_q_list - # recreate the lists for next pool creation + # 重新创建这些列表,用于下一个池创建 self.arg_q_list = [] self.res_q_list = [] def gather_workers_info(self): """ - Collect the PIDs of the children processes. + 用于收集子进程的 PID """ + # 从 self.process_pool 的 _pool 属性中获取所有子进程,将其存储在 self.workers 列表中 self.workers = [w for w in self.process_pool._pool] # pylint: disable=W0212 + # 获取所有子进程的 PID ,存储在 pids 变量中 pids = self.get_pids() + # 记录一条日志,包含操作 ID、子进程的 PID 和子进程列表 logger.info("Op: " + str(self.op_id) + " Python multiprocessing pool workers' PIDs: " + str(pids)) def execute(self, py_callable, idx, *args): """ - Execute + 用于执行一个 Python 调用able。 """ + # 检查是否正在运行且检查迭代器是否已清理 if self.is_running() and check_iterator_cleanup() is False: + # 使用 _send() 方法将任务参数发送到子进程,获取结果队列的 ID result, qid, ret = self._send(py_callable, idx, *args) if ret: return result @@ -2920,60 +3380,76 @@ class _PythonMultiprocessing(cde.PythonMultiprocessingRuntime): # todo this check might be wrong while check_iterator_cleanup() is False: try: + # 将结果队列的 ID 作为参数,使用 _receive() 方法从子进程中获取结果 return self._receive(result, qid) except multiprocessing.TimeoutError: continue except KeyboardInterrupt: + # 设置迭代器清理标志 _set_iterator_cleanup() + # 关闭进程池 self.close_pool() + # 抛出一个异常 raise Exception("Multiprocess Op worker receives KeyboardInterrupt.") return (None,) return None def _send(self, py_callable, idx, *args): """ - The map/batch operator will use multiprocessing-pool apply_async interface to execute python function - in a sub process, apply_async will release GIL temporarily. For better performance, we use shared memory - feature and pass shared queue instead of multiprocess args. + 使用多进程池的 apply_async 接口。多进程池会释放 GIL 临时释放 GIL,以提高性能。为此,我 + 们使用了共享内存功能,并将共享队列作为参数传递,而不是使用多进程参数。 """ ret = False qid = None + # 检查是否有可用的共享内存队列 if self.arg_q_list: tid = threading.get_ident() - # Need to register each thread to use a different queue to send data to pool + # 使用线程 ID 注册每个线程,确保每个线程使用不同的队列发送数据到进程池 if tid not in self.queues_map: + # 将任务参数放入相应的队列中 qid = self.next_queue self.next_queue += 1 self.queues_map[tid] = qid + # 没有可用的共享内存队列时 else: + # 从线程 ID 对应的映射中获取队列 ID qid = self.queues_map[tid] self.arg_q_list[qid].put(args) - # This call will send the tensors along with Python callable index to the process pool. - # Block, yield GIL. Current thread will reacquire GIL once result is returned. + # 检查是否正在运行且检查迭代器是否已清理 if self.is_running() and check_iterator_cleanup() is False: + # 将任务参数发送到子进程 + # 这个方法会阻塞,释放 GIL,当前线程会在结果返回时重新获取 GIL。 result = self.process_pool.apply_async(_pyfunc_worker_exec, [idx, qid, []]) else: ret = True result = py_callable(*args) else: result = self.process_pool.apply_async(_pyfunc_worker_exec, [idx, -1, *args]) + # 如果发送成功,它将返回子进程的结果和队列 ID,以及一个标志,表示是否需要继续发送任务参数 + # 否则返回一个标志,表示发送成功。 return result, qid, ret def _receive(self, result, qid): """ - The map/batch operator will use multiprocessing-pool get interface to sync output data from a sub process, - get interface will reacquire GIL. For better performance, we use shared memory feature and get data from - shared queue directly. + 使用多进程池的 get 接口。多进程池会释放 GIL 临时释放 GIL,以提高性能。为此,我们 + 使用了共享内存功能,并将共享队列作为参数传递,而不是使用多进程参数。 """ + # 检查是否有可用的共享内存队列 if self.arg_q_list: + # 将使用队列 ID 从相应的队列中获取结果 r = result.get(30) + # 尝试获取结果,如果在 30 秒内无法获取到结果,它会抛出一个异常 if isinstance(r, ExceptionHandler): r.reraise() + # 获取到的结果的队列 ID 不等于传入的队列 ID, if r[0] != qid: + # 抛出一个异常 raise Exception("In PyCallable, got results from wrong thread") r = self.res_q_list[qid].get() + # 返回获取到的结果 return r + # 如果在 30 秒内无法获取到结果,它会抛出一个异常 r = result.get(30) if isinstance(r, ExceptionHandler): r.reraise() @@ -2983,15 +3459,18 @@ class _PythonMultiprocessing(cde.PythonMultiprocessingRuntime): @staticmethod def wait_pid(): """ - This function is used by the main process to release subprocess resources. + 用于等待子进程的退出。 """ try: while True: + # 传入 -1 和 os.WNOHANG 标志 child_pid, _ = os.waitpid(-1, os.WNOHANG) if child_pid == 0: break + # 捕获 OSError 异常时,会忽略这个错误 except OSError: - # waitpid may be failed for some reasons so we ignore this error + # os.waitpid() 函数可能在某些情况下失败 + # 不需要处理这个错误,所以使用 pass 语句忽略它 pass # Dataset need watch_dog thread to monitoring fork multi-processing, @@ -2999,200 +3478,238 @@ class _PythonMultiprocessing(cde.PythonMultiprocessingRuntime): @staticmethod def _watch_dog(eot, workers, pool=None): """ - This thread is for monitoring subprocesses forked by GeneratorDataset/map/batch + 用于设置和监控子进程的退出时间。 """ - if not isinstance(workers, list): - raise TypeError("[Internal Error] The 2nd parameter of watch dog thread should be list of process, " \ - "but got {}.".format(type(workers))) - if pool is not None and not isinstance(pool, multiprocessing.pool.Pool): - raise TypeError("[Internal Error] The 3rd parameter of watch dog thread should be multiprocessing.Pool, " \ - "but got {}".format(type(pool))) while not eot.is_set(): + # 用于存储子进程的退出时间 clear_subprocess_timeout = 0 - # Monitoring and count how many subprocesses already exit + # 监视和计数子进程退出数量 clear_subprocess_timeout = _PythonMultiprocessing._monitor_subprocess_exit(workers) - # If find subprocess exit, we will wait for 30s and do some waitpid operations + # 如果发现子进程退出,等待30秒并进行waitpid操作 + # 判断clear_subprocess_timeout是否大于0 if clear_subprocess_timeout > 0: + # 判断pool是否为None if pool is not None: # Python multiprocessing.pool has a bug, if sub process of pool is killed, pool will launch # a new sub process, so we have to set worker_handler._state to TERMINATE to stop relaunching. + # 判断pool的状态是否为RUN,如果是,则将其设置为TERMINATE,并将其_worker_handler的状态设置为TERMINATE if pool._state == RUN: # pylint: disable=W0212 pool._state = TERMINATE # pylint: disable=W0212 + # 重新启动pool中的子进程 pool._worker_handler._state = TERMINATE # pylint: disable=W0212 pool._worker_handler.join() # pylint: disable=W0212 + # 计算一个新的开始时间start start = time.time() + # 使用一个while循环来不断检查当前时间与开始时间的差值是否小于clear_subprocess_timeout while time.time() - start < clear_subprocess_timeout: # We need to distinguishing get_dataset_size or train finished normally and hang scenario. # If get_dataset_size or train finished normally, _stop_subprocess can be execute and # self.need_abort can be set to True. If main process is hang in get(), self.need_abort # will never set to True, then we wait for 30s and kill main process + # 判断eot是否已设置 if eot.is_set(): return - # Sometimes subprocess may be zombie, so in 30s we can wait and do some useful tasks(waitpid). + # 等待子进程的退出。这里可能遇到子进程为僵尸进程的情况,因此使用一个try语句来尝试等待子进程的退出。 _PythonMultiprocessing.wait_pid() # multiprocessing.queue may hang in .get() forever when put() process was killed. # We have to exit main process otherwise main process will hang. + # 如果pool不为None if pool is not None: + # 终止pool中的子进程 _PythonMultiprocessing._terminate_process(pool._pool) # pylint: disable=W0212 else: _PythonMultiprocessing._terminate_process(workers) + # 如果遇到子进程退出意外或被杀的情况,打印一条错误日志 logger.critical("The subprocess of dataset may exit unexpected or be killed, " "main process will exit. If this is not an artificial operation, you can use " "ds.config.set_enable_watchdog(False) to block this error.") + # 尝试终止主进程 os.kill(os.getpid(), signal.SIGTERM) @staticmethod - # Terminate subprocess launched by multiprocessing.pool + # 用于终止子进程 def _terminate_process(workers): + # 遍历workers列表 for w in workers: + # 检查每个子进程的exitcode是否为None if w.exitcode is None: + # 终止该子进程 w.terminate() + # 遍历workers列表 for w in workers: - if w._closed is False: # pylint: disable=W0212 - # We don't use w.join because join can only used in main process or join will raise an error. - w._popen.wait() # pylint: disable=W0212 + # 检查每个子进程的_closed属性是否为False + if w._closed is False: + # 不使用w.join是因为join只可以在主进程中使用,否则会引发error。 + w._popen.wait() - # Monitor the exit number of subprocesses @staticmethod def _monitor_subprocess_exit(workers): """ - To monitor whether process is exit. + 用于监控子进程的退出状态 Args: - workers (list of multiprocessing.Process): multiprocessing.Process. + workers (list of multiprocessing.Process): 包含需要监控的子进程对象。 Returns: int, the timeout(in seconds) when process exit. """ + # 遍历workers列表 for w in workers: + # 获取每个子进程的exit_code exit_code = w.exitcode if exit_code is not None: - # For kill -9, we can exit quickly + # 检查exit_code是否为-9 if exit_code == -9: return 1 - # For kill -15, we still exit after 30s + # 检查exit_code是否为-9 if exit_code == -15: return 30 + # 返回1或30,分别表示子进程被强制退出和超时退出。 + # 返回0,表示子进程仍在运行 return 0 # Monitor the exit status of main process @staticmethod def process_still_alive(ppid): """ - We always hit dead lock when we use psutil or w.exitcode to check whether a process is still alive. So we use - os.kill(ppid, 0) as the best solution when we want to check whether process is still alive. + 用于检查给定进程的父进程是否仍然存活。 """ try: + # 使用 os.kill(ppid, 0) 尝试发送一个空信号给父进程 os.kill(ppid, 0) + # 如果进程不存在或已经死亡,则引发 OSError 异常 except OSError: return False + # 如果进程仍然存活,则不会引发异常。 return True # When main process exit, subprocesses will be terminate @staticmethod def _clean_process(ppid, workers, pool=None): """ - This is the execute function of clean process, if we found main process is exit, we will clean subprocesses. + 用于清理进程,如果主进程退出,将会清理子进程。 - :param ppid: The process id of main process. - :param workers: The list of subprocesses. - :param pool: multiprocessing.Pool object, we can get list of subprocesses from _pool. + :param ppid: 主进程的进程ID。 + :param workers: 子进程的列表。 + :param pool: 一个 multiprocessing.Pool 对象,我们可以从它获取子进程列表。 """ + # 忽略 Ctrl+C 信号 signal.signal(signal.SIGINT, signal.SIG_IGN) + # 检查父进程是否仍然存活 while _PythonMultiprocessing.process_still_alive(ppid): + # 使用 time.sleep(0.1) 暂停执行,以避免过快的操作导致资源过载。 time.sleep(0.1) + # 检查 pool 是否为 None if pool is not None: # Python multiprocessing.pool has a bug, if sub process of pool is killed, pool will launch # a new sub process, so we have to set worker_handler._state to TERMINATE to stop relaunching. # But this pool is not the same object as it in main process, so we don't support kill main process then # kill subprocess. - if pool._state == RUN: # pylint: disable=W0212 - pool._state = TERMINATE # pylint: disable=W0212 - pool._worker_handler._state = TERMINATE # pylint: disable=W0212 - pool._worker_handler.join() # pylint: disable=W0212 + if pool._state == RUN: + # 将 pool._state 设置为 TERMINATE,阻止新子进程的启动 + pool._state = TERMINATE + pool._worker_handler._state = TERMINATE + # 等待子进程全部退出 + pool._worker_handler.join() + # 检查 pool 是否为 None if pool is not None: - _PythonMultiprocessing._terminate_process(pool._pool) # pylint: disable=W0212 + # 终止池中的所有子进程 + _PythonMultiprocessing._terminate_process(pool._pool) + # 如果 pool 为 None else: + # 终止所有子进程 _PythonMultiprocessing._terminate_process(workers) + # 终止主进程 os.kill(os.getpid(), signal.SIGTERM) def _launch_watch_dog(self): """ - We will launch a watchdog thread and a clean process to cleaning subprocess when there is process was killed. - The watchdog thread will cleanup subprocesses and main process when one of the subprocesses was killed. - The cleaning subprocess will cleanup subprocesses when main process was killed. + 用于启动一个看门狗线程和一个清理进程。看门狗线程用于监控子进程的运行状态,当子进程退出时,它会自动清理子进程。 + 清理进程则用于在主进程退出时清理子进程。 """ + # 检查当前操作系统的类型是否为 Windows if platform.system().lower() != 'windows': + # 如果不是 'Windows',将创建一个名为 self.cleaning_process 的子进程,将 self._clean_process 函数作为目标 self.cleaning_process = multiprocessing.Process(target=self._clean_process, args=(self.ppid, self.workers, self.process_pool)) + # 清理进程为守护进程,以便在主进程退出时自动退出 self.cleaning_process.daemon = True + # 启动清理进程 self.cleaning_process.start() + # 检查是否启用了看门狗功能 if get_enable_watchdog(): + # 如果启用了看门狗功能,创建一个名为 self.eot 的事件对象,用于通知看门狗线程可以退出 self.eot = threading.Event() + # 创建一个名为 self.watch_dog 的线程,并将其作为目标函数 self._watch_dog self.watch_dog = threading.Thread(target=self._watch_dog, args=(self.eot, self.workers + [self.cleaning_process], self.process_pool)) self.watch_dog.daemon = True + # 启动看门狗线程 self.watch_dog.start() + # 检查看门狗线程的 eot 事件是否已发送 def _abort_watchdog(self): + # 如果没有,它将设置 eot 事件 if not self.eot.is_set(): + # 通常在主进程退出时调用,以通知看门狗线程可以退出。 self.eot.set() def abort_watchdog(self): + # 检查是否有watch_dog和eot属性 if hasattr(self, 'watch_dog') and self.watch_dog is not None and hasattr(self, 'eot') and self.eot is not None: + # 如果有,则调用_abort_watchdog()方法 self._abort_watchdog() + # 检查是否有cleaning_process属性 if hasattr(self, 'cleaning_process') and self.cleaning_process is not None: + # 如果有,则调用_PythonMultiprocessing._terminate_process()方法,并传入[self.cleaning_process]作为参数 _PythonMultiprocessing._terminate_process([self.cleaning_process]) def is_running(self): - # note here: the RUN state of python3.7 and python3.8 is different: + # RUN的状态在python 3.7和3.8是不同的 # python3.7: RUN = 0 # python3.8: RUN = "RUN" - # so we use self.pool._state == RUN instead and we can't use _state == 0 any more. + # 使用self.pool._state == RUN 替代,且不能使用use _state == 0。 + # 检查process_pool是否为None,判断process_pool的状态是否为RUN if self.process_pool is not None and self.process_pool._state == RUN: # pylint: disable=W0212 return True return False def close_pool(self): + # 检查是否有process_pool属性 if hasattr(self, 'process_pool') and self.process_pool is not None: + # 调用close()方法关闭process_pool self.process_pool.close() + # 调用join()方法等待process_pool中的所有任务完成 self.process_pool.join() def __del__(self): - # Cleanup when the iter had been deleted from ITERATORS_LIST + # Iter对象被删除,清理任何在迭代过程中分配的资源 self.terminate() class MapDataset(UnionBaseDataset): """ - The result of applying the Map operator to the input Dataset. + 将输入数据集中的数据应用Map操作(operations)后,得到新的数据集。 Args: - input_dataset (Dataset): Input Dataset to be mapped. - operations (Union[list[TensorOperation], list[functions]]): A function mapping a nested structure of tensors - to another nested structure of tensor (default=None). - input_columns (Union[str, list[str]]): List of names of the input columns - (default=None, the operations will be applied on the first columns in the dataset). - The size of the list should match the number of inputs of the first operator. - output_columns (Union[str, list[str]], optional): List of names of the output columns. - The size of the list should match the number of outputs of the last operator - (default=None, output columns will be the input columns, i.e., the columns will - be replaced). - column_order (list[str], optional): Specifies the list of all the columns you need in the whole - dataset. The parameter is required when len(input_column) != len(output_column). Caution: the list here - is not just the columns specified in parameter input_columns and output_columns. - num_parallel_workers (int, optional): Number of workers to process the dataset - in parallel (default=None). - python_multiprocessing (bool, optional): Parallelize Python operations with multiple worker process. This - option could be beneficial if the Python operation is computational heavy (default=False). - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). - callbacks (DSCallback, list[DSCallback], optional): List of Dataset callbacks to be called (Default=None) - max_rowsize(int, optional): Maximum size of row in MB that is used for shared memory allocation to copy - data between processes. This is only used if python_multiprocessing is set to True (default=16). - offload (bool, optional): Flag to indicate whether offload is used (Default=None). + input_dataset (Dataset): 输入数据集,类型为Dataset。 + operations (Union[list[TensorOperation], list[functions]]): 操作列表,操作列表中的每个元素都是一个数据处理操作实 + 例,可以是c_transforms.TensorOperation或py_transforms.PyTensorOperation类型的对象,也可以是一个python函数或类方 + 法。 + input_columns (Union[str, list[str]]): 输入列名列表。如果输入列名列表和输出列名列表的长度不相等,必须在column_order + 参数中指定列名的顺序。 + output_columns (Union[str, list[str]], optional): 输出列名列表。如果输入列名列表和输出列名列表的长度不相等,必须 + 在column_order参数中指定列名的顺序 + column_order (list[str], optional): 列名顺序列表。如果输入列名列表和输出列名列表的长度不相等,必须在column_order参 + 数中指定列名的顺序。 + num_parallel_workers (int, optional): 并行工作进程数。 + python_multiprocessing (bool, optional): 是否使用Python多进程。 + cache (DatasetCache, optional): 数据缓存服务。 + callbacks (DSCallback, list[DSCallback], optional): 数据集回调列表。 + max_rowsize(int, optional): 每个进程的最大行大小。 + offload (bool, optional): 是否使用离散加载。 Raises: ValueError: If len(input_columns) != len(output_columns) and column_order is not specified. @@ -3203,9 +3720,11 @@ class MapDataset(UnionBaseDataset): offload=None): super().__init__(children=input_dataset, num_parallel_workers=num_parallel_workers, cache=cache) self.operations = to_list(operations) + # 检查operations参数列表中的每个元素是否为数据处理操作实例 for op in self.operations: # user define c_vision.HWC2CHW without parentheses is error - if type(op) == type: # pylint: disable=unidiomatic-typecheck + if type(op) == type: + # 抛出一个错误。 raise ValueError("Parameter operations's element of method map should be a dataset processing " "operation instance, but got: {}. It may be missing parentheses for " "instantiation.".format(op)) @@ -3219,12 +3738,14 @@ class MapDataset(UnionBaseDataset): self.output_columns = to_list(output_columns) self.column_order = replace_none(column_order, []) - # If output_columns were not provided then use input_columns + # 如果output_columns 没有被提供,则使用input_columns self.output_columns = self.input_columns if not self.output_columns else self.output_columns + # 检查input_columns和output_columns参数列表的长度是否相等。如果不等,并且没有指定column_order参数 if self.input_columns and self.output_columns \ and len(self.input_columns) != len(self.output_columns) \ and not self.column_order: + # 抛出一个错误 raise ValueError("When length of input_columns and output_columns are not equal," " column_order must be specified.") @@ -3235,60 +3756,75 @@ class MapDataset(UnionBaseDataset): self.max_rowsize = max_rowsize self.offload = offload + # 将operations属性中的数据处理操作实例解析为一个cde.MapNode对象 def parse(self, children=None): operations = [] + # 遍历operations属性中的每个操作 for op in self.operations: + # 具有parse方法,则调用其parse方法将操作解析为一个cde.MapNode对象 if op and getattr(op, 'parse', None): operations.append(op.parse()) + # 否则,将操作直接添加到operations列表中 else: operations.append(op) + # 创建一个回调列表,将callbacks属性中的数据集回调实例创建为一个cde.MapNode对象 callbacks = [cb.create_runtime_obj() for cb in self.callbacks] + # cde.MapNode对象表示将输入数据集中的数据应用操作后的数据集 return cde.MapNode(children[0], operations, self.input_columns, self.output_columns, self.column_order, callbacks, self.max_rowsize, OffloadToManualOffloadMode.get(self.offload), self.process_pool) + # 实现深拷贝 def __deepcopy__(self, memodict): return self.__safe_deepcopy__(memodict, exclude=("operations", "callbacks", "__transfer_dataset__")) - # Iterator bootstrap will be called on iterator construction. - # A deep copy of Dataset object is created prior of iterator_bootstrap. - # This method will create per iterator process pool and bind pyfunc execution to the pool. + # 创建迭代器时调用的一种回调方法 + # 在创建迭代器时,会首先创建一个深拷贝的Dataset对象 + # 这个方法会创建一个进程池,并将pyfunc执行绑定到该进程池上。 + # 在迭代器初始化时被调用,用于处理一些特定操作 def iterator_bootstrap(self): """ Per iterator bootstrap callback. """ + # 检查python_multiprocessing属性是否为True if self.python_multiprocessing: iter_specific_operations = [] callable_list = [] - # If user didn't specify num_parallel_workers, set it to default + # 检查num_parallel_workers属性是否为None if self.num_parallel_workers is None: + # 如果是,则使用get_num_parallel_workers()函数获取默认的并行工作进程数 self.num_parallel_workers = get_num_parallel_workers() - # Pass #1, look for Python callables and build list + # 查找Python可调用对象并构建列表 for op in self.operations: - # our c transforms is now callable and should not be run in Python multithreading + # c transforms 现在是可调用的,不应该在Python多线程中运行 if MapDataset.__operation_valid_for_multiprocessing(op): + # 将满足多进程条件的操作添加到callable_list中 callable_list.append(op) + # 如果callable_list不为空 if callable_list: + # 创建一个进程池,并传入相关参数 self.process_pool = _PythonMultiprocessing(str(self), self.num_parallel_workers, callable_list, self.max_rowsize) - # Pass #2 idx = 0 for op in self.operations: - # our c transforms is now callable and should not be run in Python multithreading + # c transforms 现在是可调用的,不应该在Python多线程中运行 if MapDataset.__operation_valid_for_multiprocessing(op): - # Wrap Python callable into _PythonCallable + # 将满足多进程条件的操作包装成_PythonCallable对象,添加到列表中 iter_specific_operations.append(_PythonCallable(op, idx, self.process_pool)) idx += 1 else: # CPP ops remain the same iter_specific_operations.append(op) + # 将iter_specific_operations列表作为operations列表 self.operations = iter_specific_operations @staticmethod + # 用于检查一个操作是否满足多进程条件 def __operation_valid_for_multiprocessing(op): + # 操作必须是一个可调用的对象(即实现了__call__方法),并且其名称中不包含"c_transform"字符串。 if callable(op) and str(op).find("c_transform") < 0: return True return False @@ -3296,15 +3832,13 @@ class MapDataset(UnionBaseDataset): class FilterDataset(UnionBaseDataset): """ - The result of applying filter predicate to the input Dataset. + 对输入的Dataset应用一个过滤器谓词,并将结果作为新的Dataset返回。 Args: - input_dataset (Dataset): Input Dataset to be mapped. - predicate (callable): Python callable which returns a boolean value. If False then filter the element. - input_columns (Union[str, list[str]], optional): List of names of the input columns - (default=None, the predicate will be applied to all columns in the dataset). - num_parallel_workers (int, optional): Number of workers to process the dataset - in parallel (default=None). + input_dataset (Dataset): 输入的Dataset对象。 + predicate (callable): 一个Python可调用对象,用于判断元素是否满足过滤条件。如果为False,则过滤该元素。 + input_columns (Union[str, list[str]], optional): 输入列名列表(默认值为None,表示应用到所有列)。 + num_parallel_workers (int, optional): 并行工作进程数(默认值为None,表示使用默认值)。 """ def __init__(self, input_dataset, predicate, input_columns=None, num_parallel_workers=None): @@ -3312,67 +3846,71 @@ class FilterDataset(UnionBaseDataset): self.predicate = lambda *args: bool(predicate(*args)) self.input_columns = to_list(input_columns) + # 根据输入的Dataset和predicate创建一个cde.FilterNode对象 def parse(self, children=None): return cde.FilterNode(children[0], self.predicate, self.input_columns) class RepeatDataset(UnionBaseDataset): """ - The result of applying Repeat operator to the input Dataset. + 对输入的Dataset重复指定次数,并将结果作为新的Dataset返回。 Args: - input_dataset (Dataset): Input Dataset to be repeated. - count (int): Number of times the dataset will be repeated (default=-1, repeat indefinitely). + input_dataset (Dataset): 输入的Dataset对象。 + count (int): 重复次数(默认值为-1,表示无限重复)。 """ def __init__(self, input_dataset, count): super().__init__(children=input_dataset) self.count = replace_none(count, -1) + # 用于根据输入的Dataset和count创建一个cde.RepeatNode对象 def parse(self, children=None): return cde.RepeatNode(children[0], self.count) class SkipDataset(UnionBaseDataset): """ - The result of applying Skip operator to the input Dataset. + 用于跳过输入的Dataset中的指定元素,并将结果作为新的Dataset返回 Args: - input_dataset (Dataset): Input dataset to have elements skipped. - count (int): Number of elements to be skipped in the dataset. + input_dataset (Dataset): 输入的Dataset对象。 + count (int): 要跳过的元素数量。 """ def __init__(self, input_dataset, count): super().__init__(input_dataset) self.count = count + # 用于根据输入的Dataset和count创建一个cde.SkipNode对象 def parse(self, children=None): return cde.SkipNode(children[0], self.count) class TakeDataset(UnionBaseDataset): """ - The result of applying Take operator to the input Dataset. + 用于从输入的Dataset中获取指定数量的元素,并将结果作为新的Dataset返回。 Args: - input_dataset (Dataset): Input Dataset to have elements taken from. - count (int): Number of elements to be taken from the dataset. + input_dataset (Dataset): 输入的Dataset对象。 + count (int): 要获取的元素数量。 """ def __init__(self, input_dataset, count): super().__init__(children=input_dataset) self.count = count + # 用于根据输入的Dataset和count创建一个cde.TakeNode对象 def parse(self, children=None): return cde.TakeNode(children[0], self.count) class ZipDataset(UnionBaseDataset): """ - The result of applying Zip operator to the input Dataset. + 用于将多个输入的Dataset对象组合在一起,并将结果作为新的Dataset返回。 Args: - datasets (tuple): A tuple of datasets to be zipped together. + datasets (tuple): 一个包含多个Dataset对象的元组。 Raises: TypeError: If dataset is not an instance of Dataset. @@ -3381,20 +3919,22 @@ class ZipDataset(UnionBaseDataset): def __init__(self, datasets): super().__init__(children=datasets) + # parse方法用于根据输入的datasets创建一个cde.ZipNode对象。 def parse(self, children=None): return cde.ZipNode(children) + # 用于检查ZipDataset中的所有子Dataset是否同步 def is_sync(self): + # 任何子Dataset同步,则返回True return any([c.is_sync() for c in self.children]) class ConcatDataset(UnionBaseDataset): """ - The result of applying concat dataset operator to the input Dataset. + 用于将多个输入的Dataset对象连接在一起,并将结果作为新的Dataset返回。 Args: - datasets (list): A list of datasets to be concatenated together. - + datasets (list): 一个包含多个Dataset对象的列表。 Raises: TypeError: If dataset is not an instance of Dataset. ValueError: If there is no samples in the one of the datasets. @@ -3402,40 +3942,52 @@ class ConcatDataset(UnionBaseDataset): def __init__(self, datasets): super().__init__(children=datasets) + # 查datasets中的每个元素是否为Dataset类型 for dataset in datasets: if not isinstance(dataset, Dataset): raise TypeError("Invalid dataset, expected Dataset object, but got %s!" % type(dataset)) self.datasets = datasets self._sampler = samplers.SequentialSampler(num_samples=None) + # 获取self.children列表中每个子对象的数据集的大小,存储在self.children_sizes_列表中 self.children_sizes_ = [c.get_dataset_size() for c in self.children] child_index = 0 for item in self.children_sizes_: + # 元素为0,则抛出一个ValueError异常,表示数据集中没有样本 if item == 0: raise ValueError("There are no samples in the dataset number %d. Please make sure there are " "valid samples in the dataset." % child_index) child_index += 1 - # _children_flag_and_nums: A list of pair.The first element of pair is flag that characterizes - # whether the dataset is mappable. The second element of pair is length of the dataset + # 用于定义一个名为_children_flag_and_nums的列表,该列表包含多个元组,每个元组包含两个整数。 + # 第一个整数表示数据集是否可映射的标志,第二个整数表示数据集的大小。 self._children_flag_and_nums = [] - # _children_start_end_index_: A list of pair.The elements of pair are used to characterize - # the valid position of the dataset corresponding to the subscript when sampling + # 用于定义一个名为_children_start_end_index_的列表,该列表包含多个元组,每个元组包含两个整数。 + # 第一个整数表示数据集的起始位置,第二个整数表示数据集的结束位置。这个列表用于描述数据集中每个子数 + # 据集的起始和结束位置,以便在采样时使用。 self._children_start_end_index_ = [] + # 遍历self.children列表中的每个子对象 for index, child in enumerate(self.children): + # 创建一个名为tem_list的临时列表,其包含两个-1整数 tem_list = [-1, -1] self._children_start_end_index_.append(tem_list) + # 获取当前子数据的数量 dataset_len = self.children_sizes_[index] from mindspore.dataset.engine.datasets_user_defined import GeneratorDataset + # 检查当前子数据集是否为GeneratorDataset类型且没有实现__getitem__方法。 if isinstance(child, GeneratorDataset) and not hasattr(child.source, "__getitem__"): + # 将dataset_len设置为0,并将self.children_sizes_列表中的相应元素设置为0 dataset_len = 0 self.children_sizes_[index] = 0 + # 检查当前子数据集是否为MappableDataset类型 if isinstance(child, MappableDataset): + # 将一个包含(0, dataset_len)的元组添加到_children_flag_and_nums列表中 self._children_flag_and_nums.append((0, dataset_len)) else: + # 将一个包含(1, dataset_len)的元组添加到_children_flag_and_nums列表中 self._children_flag_and_nums.append((1, dataset_len)) def parse(self, children=None): @@ -3443,7 +3995,7 @@ class ConcatDataset(UnionBaseDataset): def use_sampler(self, sampler): """ - Set the distributedSampler to concat dataset + 用于设置分布式采样器,以便将数据集连接在一起。 Args: sampler (Sampler): The sampler to use for the current dataset. @@ -3455,15 +4007,19 @@ class ConcatDataset(UnionBaseDataset): ValueError: If the parameter NumSamples of sampler is not None. ValueError: If num_shards <=0. """ + # 如果sampler不是DistributedSampler的实例,抛出一个TypeError异常 if not isinstance(sampler, samplers.DistributedSampler): raise TypeError("The parameter %s of concat must be DistributedSampler!" % sampler) - + + # 如果sampler的shuffle参数为True,抛出一个异常 if sampler.is_shuffled(): raise ValueError("The parameter shuffle of DistributedSampler must be False!") + # 如果sampler的NumSamples参数不为None,抛出一个异常 if sampler.num_shards <= 0: raise ValueError("The parameter num_shards of DistributedSampler must be positive int!") + # 如果num_shards小于等于0,如果num_shards小于等于0 if sampler.get_num_samples() is not None: raise ValueError("The parameter num_samples of DistributedSampler is not support to be set!") @@ -3471,42 +4027,53 @@ class ConcatDataset(UnionBaseDataset): self._sampler = sampler cumulative_samples_nums = 0 + # 遍历self.children for index, child in enumerate(self.children): + # 检查子数据集是否具有sampler属性且get_num_samples()方法返回不为None if hasattr(child, 'sampler') and child.sampler.get_num_samples() is not None: + # 抛出一个异常 raise ValueError("The parameter NumSamples of %s is not support to be set!" % child) + # 检查子数据集是BatchDataset类型 if isinstance(child, BatchDataset): + # 抛出一个异常 raise TypeError("The parameter %s of concat must not be BatchDataset!" % child) - # if child is mappable and the length is greater than 0 + # 检查子数据集是否为可映射且长度大于0 if not self._children_flag_and_nums[index][0] and self._children_flag_and_nums[index][1]: - + + # 计算子数据集的累积样本数量,将其与cumulative_samples_nums相加 tem_value = cumulative_samples_nums + self._children_flag_and_nums[index][1] + # 如果累积样本数量小于等于sampler.num_shards,那么如果累积样本数量小于sampler.num_shards if not self._children_flag_and_nums[index][1] >= sampler.num_shards: if tem_value < sampler.num_shards: + # 将子数据集的起始位置设置为累积样本数量,结束位置设置为累积样本数量加上子数据集的长度 self._children_start_end_index_[index][0] = cumulative_samples_nums self._children_start_end_index_[index][1] = tem_value else: + # 将子数据集的起始位置设置为累积样本数量,结束位置设置为累积样本数量对sampler.num_shards取模的结果 self._children_start_end_index_[index][0] = cumulative_samples_nums self._children_start_end_index_[index][1] = tem_value % sampler.num_shards + # 创建一个复制后的sampler对象,并将其设置为子数据集的采样器 tem_sampler = copy.deepcopy(sampler) tem_sampler.set_offset(cumulative_samples_nums) child.use_sampler(tem_sampler) + # 计算累积样本数量对sampler.num_shards取模的结果 cumulative_samples_nums += self.children_sizes_[index] cumulative_samples_nums %= sampler.num_shards class RenameDataset(UnionBaseDataset): """ - The result of applying Rename operator to the input Dataset. + 用于将输入数据集中的指定列重命名为新的列名。 Args: - input_dataset (Dataset): Input Dataset to be Renamed. - input_columns (Union[str, list[str]]): List of names of the input columns. - output_columns (Union[str, list[str]]): List of names of the output columns. + input_dataset (Dataset): 输入数据集。 + input_columns (Union[str, list[str]]): 输入列名。 + output_columns (Union[str, list[str]]): 输出列名。 """ def __init__(self, input_dataset, input_columns, output_columns): @@ -3514,10 +4081,12 @@ class RenameDataset(UnionBaseDataset): self.input_column_names = to_list(input_columns) self.output_column_names = to_list(output_columns) + # 返回一个cde.RenameNode对象,表示对输入数据集中的列进行重命名操作 def parse(self, children=None): return cde.RenameNode(children[0], self.input_column_names, self.output_column_names) +# 用于将输入的items转换为列表 def to_list(items): if items is None: return [] @@ -3530,24 +4099,25 @@ def to_list(items): class ProjectDataset(UnionBaseDataset): """ - The result of applying Project operator to the input Dataset. + 用于从输入数据集中选择指定的列。 Args: - input_dataset (Dataset): Input Dataset to be Projected. - columns (Union[str, list[str]]): List of names of the columns to project. + input_dataset (Dataset): 输入数据集。 + columns (Union[str, list[str]]): 要选择的列名。 """ def __init__(self, input_dataset, columns): super().__init__(children=input_dataset) self.columns = to_list(columns) + # 返回一个cde.ProjectNode对象,该对象表示对输入数据集中的列进行选择操作。 def parse(self, children=None): return cde.ProjectNode(children[0], self.columns) class _ToDevice: """ - Internal class to handle sending data to device. + 用于在将数据发送到设备(如GPU)之前处理数据。 """ def __init__(self, dataset, num_epochs): @@ -3562,60 +4132,65 @@ class _ToDevice: ITERATORS_LIST.append(weakref.ref(self)) _unset_iterator_cleanup() + # 用于将数据发送到设备 def send(self): self._to_device.Send() + # 用于重置数据发送到设备的状态 def _reset(self, step): self._to_device.Reset(step) def stop_send(self): """ - send stop send signal to pipeline, it is used when end of sequence is sent at the epoch end. + 用于向管道发送停止发送信号。在训练过程中,当一个训练步骤结束时,通常会发送一个表示序列结束的标记 """ self._to_device.StopSend() def continue_send(self): """ - send continue send signal to pipeline, it is used when end of sequence is sent at the epoch end. + 用于向管道发送继续发送信号。在训练过程中,当一个训练步骤结束时,通常会发送一个表示序列结束的标记。 """ self._to_device.ContinueSend() def get_data_info(self): """ - Get type and shape of current batch. + 用于获取当前批次的数据类型和形状。 """ return self._to_device.GetDataInfo() def release(self): """ - Manually terminate Device Queue instead of relying on out of scope destruction. + 用于释放设备队列 """ + # 检查是否存在_runtime_context和_to_device属性 if hasattr(self, '_runtime_context') and self._runtime_context: if hasattr(self, '_to_device') and self._to_device: + # 调用Terminate方法终止设备队列,并删除这些属性。 self._runtime_context.Terminate() del self._to_device del self._runtime_context + # 用于实现深拷贝 def __deepcopy__(self, memodict): return self def get_offload_model(self, col_names): """ - Get offload model containing removed offload ops from pipeline. + 用于获取一个包含删除Offload操作的模型。 """ offload_model = GetOffloadModel(self._to_device, col_names) + # 返回一个包含删除Offload操作的模型 return offload_model class TransferDataset(Dataset): """ - The result of applying TDT operator to the input Dataset. + 用于将输入的Dataset对象转换为适合设备(如Ascend、GPU或CPU)的数据格式。 Args: - input_dataset (Dataset): Input Dataset to be transferred. - send_epoch_end (bool, optional): Whether to send end of sequence to device or not (default=True). - create_data_info_queue (bool, optional): Whether to create queue which stores - types and shapes of data or not (default=False). + input_dataset (Dataset): 输入数据集。 + send_epoch_end (bool, optional): 表示是否发送结束标记。 + create_data_info_queue (bool, optional): 表示是否创建一个用于存储数据类型的队列。 Raises: TypeError: If device_type is empty. @@ -3637,58 +4212,76 @@ class TransferDataset(Dataset): def parse(self, children=None): total_batch = 0 if hasattr(self.children[0], "__total_batch__"): + # 检查children[0]是否有__total_batch__属性 total_batch = self.children[0].__total_batch__ + # 返回一个TransferNode实例 return cde.TransferNode(children[0], self.queue_name, self.device_type, self.device_id, self._send_epoch_end, total_batch, self._create_data_info_queue) + + # 调用这些方法时引发RuntimeError,因为TransferDataset不是一个可迭代的对象 + # 用于创建一个字典迭代器 def create_dict_iterator(self, num_epochs=-1, output_numpy=False): raise RuntimeError("TransferDataset is not iterable.") + # 用于创建一个元组迭代器 def create_tuple_iterator(self, columns=None, num_epochs=-1, output_numpy=False, do_copy=True): raise RuntimeError("TransferDataset is not iterable.") + # 用于在TransferDataset类中定义一个迭代器 def __iter__(self): raise RuntimeError("TransferDataset is not iterable.") + # 用于输出形状 def output_shapes(self): raise RuntimeError("TransferDataset does not support obtaining output_shapes.") + # 用于输出类型 def output_types(self): raise RuntimeError("TransferDataset does not support obtaining output_types.") @check_to_device_send def send(self, num_epochs=-1): """ - Send to device + 用于将数据发送到设备(如GPU)。 """ + # 检查Dataset类的_noop_mode是否为True if Dataset._noop_mode(): + # 直接返回,不进行任何操作 return + # 检查self._to_device是否已经设置了一个值 if self._to_device is not None: + # 如果有,则将其删除 del self._to_device + # 创建一个名为_ToDevice的类实例,并将self和num_epochs作为参数传递给它。 self._to_device = _ToDevice(self, num_epochs) self._to_device.send() + # 用于停止将数据发送到设备(如GPU)的进程 def stop_send(self): if self._to_device is not None: self._to_device.stop_send() + # 用于继续将数据发送到设备(如GPU)的进程 def continue_send(self): if self._to_device is not None: self._to_device.continue_send() + # 用于重置数据集的管道,以便从新的步骤开始 def _reset(self, step): if self._to_device is not None: logger.info("Reset the dataset pipeline to step " + str(step)) - self._to_device._reset(step) # pylint: disable=W0212 + self._to_device._reset(step) def get_data_info(self): """ - Get type and shape of current batch + 用于获取当前批次的数据类型和形状 """ if self._to_device is not None: return self._to_device.get_data_info() raise RuntimeError("Calling get_data_info with bad state.") + # 用于获取分库分表后的模型。 def get_offload_model(self): if self._to_device is not None: return self._to_device.get_offload_model(self.column_name) @@ -3697,7 +4290,7 @@ class TransferDataset(Dataset): def release(self): """ - Manually terminate Device Queue instead of relying on out of scope destruction. + 用于手动终止设备队列,而不是依赖 Out of scope destruction。 """ if self._to_device is not None: self._to_device.release() @@ -3705,10 +4298,10 @@ class TransferDataset(Dataset): class Schema: """ - Class to represent a schema of a dataset. + 用于表示一个数据集的schema。 Args: - schema_file(str): Path of the schema file (default=None). + schema_file(str): schema文件路径,默认为空。 Returns: Schema object, schema info about dataset. @@ -3732,37 +4325,41 @@ class Schema: @check_add_column def add_column(self, name, de_type, shape=None): """ - Add new column to the schema. + 用于向schema中添加一个新的列。 Args: - name (str): The new name of the column. - de_type (str): Data type of the column. - shape (list[int], optional): Shape of the column + name (str): 新列名。 + de_type (str): 列的数据类型。 + shape (list[int], optional): 列的形状,默认为空 (default=None, [-1] which is an unknown shape of rank 1). Raises: ValueError: If column type is unknown. """ if isinstance(de_type, typing.Type): + # 将de_type转换为mstype de_type = mstype_to_detype(de_type) + # 将de_type转换为字符串 col_type = str(de_type) else: + # 将de_type转换为字符串 col_type = str(cde.DataType(de_type)) + # 如果shape为None,则添加列 if shape is None: self.cpp_schema.add_column(name, col_type) + # 否则添加列,并设置shape else: self.cpp_schema.add_column(name, col_type, shape) def parse_columns(self, columns): """ - Parse the columns and add it to self. + 用于解析列信息并将其添加到self中。 Args: - columns (Union[dict, list[dict], tuple[dict]]): Dataset attribute information, decoded from schema file. + columns (Union[dict, list[dict], tuple[dict]]): 列表中的每个元素都是一个字典,字典中的name和type字段是 + 必填的,shape字段是可选的。 - - list[dict], 'name' and 'type' must be in keys, 'shape' optional. - - - dict, columns.keys() as name, columns.values() is dict, and 'type' inside, 'shape' optional. + - dict, 字典的键是列名,值是一个包含列类型和可选列形状的字典。 Raises: RuntimeError: If failed to parse columns. @@ -3778,20 +4375,22 @@ class Schema: >>> columns2 = {'image': {'shape': [3, 3], 'type': 'int8'}, 'label': {'shape': [1], 'type': 'int8'}} >>> schema.parse_columns(columns2) """ + # 将columns转换为JSON字符串,然后调用C++实现的parse_columns方法 self.cpp_schema.parse_columns(json.dumps(columns, indent=2)) def to_json(self): """ - Get a JSON string of the schema. + 用于将schema转换为JSON字符串。 Returns: str, JSON string of the schema. """ + # 调用C++实现的to_json方法,然后将返回的JSON字符串返回。 return self.cpp_schema.to_json() def from_json(self, json_obj): """ - Get schema file from JSON object. + 用于从JSON对象中获取schema文件。 Args: json_obj(dictionary): Object of JSON parsed. @@ -3801,26 +4400,35 @@ class Schema: RuntimeError: if dataset type is missing in the object. RuntimeError: if columns are missing in the object. """ + # 将传入的JSON对象转换为JSON字符串,然后调用C++实现的from_string方法,最后将返回的schema文件信息赋值给self self.cpp_schema.from_string(json.dumps(json_obj, indent=2)) + # 用于将schema转换为字符串并返回。 def __str__(self): return self.to_json() @staticmethod def get_num_rows(schema): schema_obj = schema + # 检查schema是否为Schema类的实例。 if not isinstance(schema_obj, Schema): + # 如果不是,将其转换为Schema类的实例 schema_obj = Schema(schema_obj) + # 调用C++实现的get_num_rows方法,并返回结果 return schema_obj.cpp_schema.get_num_rows() - +# 将输入对象(输入可以是JSON字符串或文件路径)解析为Dataset对象。 class DeserializedDataset(Dataset): def __init__(self, input_obj): super().__init__() self.input_obj = input_obj + # 用于将输入对象解析为Dataset对象 def parse(self, children=None): + # 检查输入对象是否为字典类型 if isinstance(self.input_obj, dict): + # 将其转换为JSON字符串,然后调用C++实现的from_json_string方法将JSON字符串解析为Dataset对象 json_str = json.dumps(self.input_obj) return cde.Dataset.from_json_string(json_str) + # 调用C++实现的from_json_file方法将文件路径解析为Dataset对象 return cde.Dataset.from_json_file(self.input_obj) diff --git a/mindspore/python/mindspore/dataset/engine/datasets_audio.py b/mindspore/python/mindspore/dataset/engine/datasets_audio.py index cc0ae48403d..2ed495b86b5 100644 --- a/mindspore/python/mindspore/dataset/engine/datasets_audio.py +++ b/mindspore/python/mindspore/dataset/engine/datasets_audio.py @@ -34,33 +34,26 @@ from ..core.validator_helpers import replace_none class CMUArcticDataset(MappableDataset, AudioBaseDataset): """ - A source dataset that reads and parses CMUArctic dataset. + 用于读取和解析 CMUArctic 数据集。 The generated dataset has four columns: :py:obj:`["waveform", "sample_rate", "transcript", "utterance_id"]`. - The tensor of column :py:obj:`waveform` is of the float32 type. - The tensor of column :py:obj:`sample_rate` is of a scalar of uint32 type. - The tensor of column :py:obj:`transcript` is of a scalar of string type. - The tensor of column :py:obj:`utterance_id` is of a scalar of string type. + The tensor of column :py:obj:`waveform` 浮点数类型 + The tensor of column :py:obj:`sample_rate` 无符号32位整数类型 + The tensor of column :py:obj:`transcript` 符串类型 + The tensor of column :py:obj:`utterance_id` 字符串类型 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - name (str, optional): Part of this dataset, can be 'aew', 'ahw', 'aup', 'awb', 'axb', 'bdl', - 'clb', 'eey', 'fem', 'gka', 'jmk', 'ksp', 'ljm', 'lnh', 'rms', 'rxr', 'slp' or 'slt' - (default=None, equal 'aew'). - num_samples (int, optional): The number of audio to be included in the dataset - (default=None, will read all audio). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, will use value set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing - (default=None, which means no cache is used). + dataset_dir (str): 指向包含数据集根目录的路径。 + name (str, optional): 部分数据集,可以是'aew'、'ahw'、'aup'、'awb'、'axb'、'bdl'、'clb'、'eey'、'fem'、'gka'、 + 'jmk'、'ksp'、'ljm'、'lnh'、'rms'、'rxr'或'slt',默认为None,等于'aew'。 + num_samples (int, optional): 要包含在数据集中的音频数量(默认为None,表示读取所有音频)。 + num_parallel_workers (int, optional): 用于读取数据的worker数量(默认为None,将使用配置文件中的值)。 + shuffle (bool, optional): 是否对数据集进行shuffle(默认为None,按照预期顺序处理数据)。 + sampler (Sampler, optional): 用于从数据集中选择样本的对象(默认为None,按照预期顺序处理数据)。 + num_shards (int, optional): 数据集将被分为的shard数量(默认为None)。当此参数指定时,num_samples表示每个shard中 + 的最大样本数量。 + shard_id (int, optional): shard ID,范围为0到num_shards - 1(默认为None)。当num_shards也指定时,此参数才有效。 + cache (DatasetCache, optional): 用于加速数据集处理的张量缓存对象(默认为None,不使用缓存)。 Raises: RuntimeError: If source raises an exception during execution. @@ -84,24 +77,12 @@ class CMUArcticDataset(MappableDataset, AudioBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + * - 当sampler为None,shuffle为None时,数据集的顺序是随机的。 + 当sampler为None,shuffle为True时,数据集的顺序是随机的。 + 当sampler为None,shuffle为False时,数据集的顺序是顺序处理的。 + * - 当sampler是一个sampler对象,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler是一个sampler对象,shuffle为True时,不允许使用sampler,因为可能会导致顺序不一致。 + 当sampler是一个sampler对象,shuffle为False时,不允许使用sampler,因为可能会导致顺序不一致。 Examples: >>> cmu_arctic_dataset_directory = "/path/to/cmu_arctic_dataset_directory" @@ -114,12 +95,9 @@ class CMUArcticDataset(MappableDataset, AudioBaseDataset): About CMUArctic dataset: - The CMU arctic databases are designed for the purpose of speech synthesis research. - These single speaker speech databases have been carefully recorded under studio conditions - and consist of approximately 1200 phonetically balanced English utterances. In addition to wavefiles, - the databases provide complete support for the Festival Speech Synthesis System, including pre-built - voices that may be used as is. The entire package is distributed as free software, without restriction - on commercial or non-commercial use. + CMU arctic databases 是一个用于语音合成研究的数据集。这个数据集由 John Kominek 和 Alan W Black 于 2003 年创 + 建,用于收集和发布预先录制好的单声道语音。此外,还提供了一个完整的支持语音合成系统的预先构建的音色库。该数据集完全 + 以免费形式提供,不限制商业和非商业使用。 You can construct the following directory structure from CMUArctic dataset and read by MindSpore's API. @@ -171,31 +149,27 @@ class CMUArcticDataset(MappableDataset, AudioBaseDataset): class GTZANDataset(MappableDataset, AudioBaseDataset): """ - A source dataset that reads and parses GTZAN dataset. + 读取和解析GTZAN数据集中的音频文件。 The generated dataset has three columns: :py:obj:`["waveform", "sample_rate", "label"]`. - The tensor of column :py:obj:`waveform` is of the float32 type. - The tensor of column :py:obj:`sample_rate` is of a scalar of uint32 type. - The tensor of column :py:obj:`label` is of a scalar of string type. + The tensor of column :py:obj:`waveform` 数据类型是float32 + The tensor of column :py:obj:`sample_rate` 数据类型是uint32类型的标量 + The tensor of column :py:obj:`label` 数据类型是字符串类型的标量 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Usage of this dataset, can be 'train', 'valid', 'test' or 'all' - (default=None, all samples). - num_samples (int, optional): The number of audio to be included in the dataset - (default=None, will read all audio). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, will use value set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing - (default=None, which means no cache is used). + dataset_dir (str): 数据集的根目录路径。 + usage (str, optional): 数据集的使用情况,可以是"train"(训练集)、"valid"(验证集)、"test" + (测试集)或"all"(所有样本)。 + num_samples (int, optional): 要包含在数据集中的音频数量(默认值为None,表示读取所有音频)。 + num_parallel_workers (int, optional): 用于读取数据的worker数量(默认值为None,将使用配置文件 + 中设置的值)。 + shuffle (bool, optional): 是否对数据集进行shuffle(默认值为None,按照预期顺序读取数据)。 + sampler (Sampler, optional): 用于从数据集中选择样本的对象(默认值为None,按照预期顺序读取数据)。 + num_shards (int, optional): 将数据集划分为的shard数量(默认值为None)。当此参数指定时, + num_samples表示每个shard中的最大样本数量。 + shard_id (int, optional): 当前shard的ID(默认值为None),当num_shards也被指定时,此参数必须同时 + 指定。 + cache (DatasetCache, optional): 用于加速数据集处理的张量缓存对象(默认值为None,表示不使用缓存)。 Raises: RuntimeError: If source raises an exception during execution. @@ -219,24 +193,12 @@ class GTZANDataset(MappableDataset, AudioBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + * - 当sampler为None,shuffle为None时,数据集的顺序是随机的。 + 当sampler为None,shuffle为True时,数据集的顺序是随机的。 + 当sampler为None,shuffle为False时,数据集的顺序是顺序处理的。 + * - 当sampler是一个sampler对象,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler是一个sampler对象,shuffle为True时,不允许使用sampler,因为可能会导致顺序不一致。 + 当sampler是一个sampler对象,shuffle为False时,不允许使用sampler,因为可能会导致顺序不一致。 Examples: >>> gtzan_dataset_directory = "/path/to/gtzan_dataset_directory" @@ -249,11 +211,9 @@ class GTZANDataset(MappableDataset, AudioBaseDataset): About GTZAN dataset: - The GTZAN dataset appears in at least 100 published works and is the most commonly used - public dataset for evaluation in machine listening research for music genre recognition. - It consists of 1000 audio tracks, each of which is 30 seconds long. It contains 10 genres (blues, - classical, country, disco, hiphop, jazz, metal, pop, reggae and reggae), each of which is - represented by 100 tracks. The tracks are all 22050Hz Mono 16-bit audio files in .wav format. + GTZAN数据集在至少100篇论文中出现,是最常用的用于机器听觉研究的音乐类别识别评估数据集。 + GTZAN数据集包含1000个音频文件,每个文件长度为30秒。它包含10个类别(蓝调、古典、乡村、迪斯科、爵士、金属、流 + 行、 Reggae 和 Reggae),每个类别都有100个音轨。音频文件是22050Hz Mono 16-bit音频文件,格式为.wav。 You can construct the following directory structure from GTZAN dataset and read by MindSpore's API. @@ -301,36 +261,33 @@ class GTZANDataset(MappableDataset, AudioBaseDataset): class LibriTTSDataset(MappableDataset, AudioBaseDataset): """ - A source dataset that reads and parses the LibriTTS dataset. + 用于读取和处理LibriTTS数据集。 The generated dataset has seven columns :py:obj:`['waveform', 'sample_rate', 'original_text', 'normalized_text', 'speaker_id', 'chapter_id', 'utterance_id']`. - The tensor of column :py:obj:`waveform` is of the float32 type. - The tensor of column :py:obj:`sample_rate` is of a scalar of uint32 type. - The tensor of column :py:obj:`original_text` is of a scalar of string type. - The tensor of column :py:obj:`normalized_text` is of a scalar of string type. - The tensor of column :py:obj:`speaker_id` is of a scalar of uint32 type. - The tensor of column :py:obj:`chapter_id` is of a scalar of uint32 type. - The tensor of column :py:obj:`utterance_id` is of a scalar of string type. + The tensor of column :py:obj:`waveform` 浮点数类型 + The tensor of column :py:obj:`sample_rate` 无符号整数(uint32) + The tensor of column :py:obj:`original_text` 字符串类型 + The tensor of column :py:obj:`normalized_text` 字符串类型 + The tensor of column :py:obj:`speaker_id` 无符号整数(uint32) + The tensor of column :py:obj:`chapter_id` 无符号整数(uint32) + The tensor of column :py:obj:`utterance_id` 字符串类型 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Part of this dataset, can be 'dev-clean', 'dev-other', 'test-clean', 'test-other', - 'train-clean-100', 'train-clean-360', 'train-other-500', or 'all' (default=None, equal 'all'). - num_samples (int, optional): The number of images to be included in the dataset - (default=None, will read all audio). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, will use value set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing - (default=None, which means no cache is used). + dataset_dir (str): 指向包含数据集根目录的路径字符串。 + usage (str, optional): 一个可选参数,表示要使用的数据集部分,可以是 'dev-clean'、'dev-other'、'test-clean'、 + 'test-other'、'train-clean-100'、'train-clean-360'、'train-other-500' 或 'all'(默认值为 None,等于 'all')。 + num_samples (int, optional): 一个可选参数,表示要包含在数据集中的音频数量(默认值为 None,表示读取所有音频)。 + num_parallel_workers (int, optional): 一个可选参数,表示读取数据的 worker 数量(默认值为 None,表示使用配置文 + 件中设置的值)。 + shuffle (bool, optional): 一个可选参数,表示是否对数据进行 shuffle(默认值为 None,表示按照预期顺序读取数据)。 + sampler (Sampler, optional): 一个可选参数,表示用于从数据集中选择样本的对象(默认值为 None,表示按照预期顺序读 + 取数据)。 + num_shards (int, optional): 一个可选参数,表示数据集将被分为的 shard 数量(默认值为 None)。当此参数指定时, + num_samples 表示每个 shard 中的最大样本数量。 + shard_id (int, optional): 一个可选参数,表示当前 shard 的 ID(默认值为 None),当 num_shards 也指定时,此参数只 + 能被指定。 + cache (DatasetCache, optional): 一个可选参数,用于加速数据集处理的可选张量缓存对象(默认值为 None,表示不使用缓存)。 Raises: RuntimeError: If source raises an exception during execution. @@ -351,27 +308,12 @@ class LibriTTSDataset(MappableDataset, AudioBaseDataset): :widths: 25 25 50 :header-rows: 1 - * - Parameter `sampler` - - Parameter `shuffle` - - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + * - 当sampler为None,shuffle为None时,数据集的顺序是随机的。 + 当sampler为None,shuffle为True时,数据集的顺序是随机的。 + 当sampler为None,shuffle为False时,数据集的顺序是顺序处理的。 + * - 当sampler是一个sampler对象,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler是一个sampler对象,shuffle为True时,不允许使用sampler,因为可能会导致顺序不一致。 + 当sampler是一个sampler对象,shuffle为False时,不允许使用sampler,因为可能会导致顺序不一致。 Examples: >>> libri_tts_dataset_dir = "/path/to/libri_tts_dataset_directory" @@ -384,10 +326,9 @@ class LibriTTSDataset(MappableDataset, AudioBaseDataset): About LibriTTS dataset: - LibriTTS is a multi-speaker English corpus of approximately 585 hours of read English speech at 24kHz - sampling rate, prepared by Heiga Zen with the assistance of Google Speech and Google Brain team members. - The LibriTTS corpus is designed for TTS research. It is derived from the original materials (mp3 audio - files from LibriVox and text files from Project Gutenberg) of the LibriSpeech corpus. + 这段代码描述了 LibriTTS 数据集,一个多说话者英语语料库,包含大约 585 小时以 24kHz 采样率读取的读音文。prepared + by Heiga Zen with the assistance of Google Speech 和 Google Brain 团队成员。LibriTTS 数据集设计用于 TTS 研 + 究。它是从原始资料(LibriVox 中的 mp3 音频文件和 Project Gutenberg 中的文本文件)中提取的。 You can construct the following directory structure from LibriTTS dataset and read by MindSpore's API. @@ -451,31 +392,29 @@ class LibriTTSDataset(MappableDataset, AudioBaseDataset): class LJSpeechDataset(MappableDataset, AudioBaseDataset): """ - A source dataset that reads and parses LJSpeech dataset. + 用于读取和解析 LJSpeech 数据集。 The generated dataset has four columns :py:obj:`[waveform, sample_rate, transcription, normalized_transcript]`. - The tensor of column :py:obj:`waveform` is a tensor of the float32 type. - The tensor of column :py:obj:`sample_rate` is a scalar of the int32 type. - The tensor of column :py:obj:`transcription` is a scalar of the string type. - The tensor of column :py:obj:`normalized_transcript` is a scalar of the string type. + The tensor of column :py:obj:`waveform` 浮点数类型 + The tensor of column :py:obj:`sample_rate` 无符号整数(uint32) + The tensor of column :py:obj:`transcription` 字符串类型 + The tensor of column :py:obj:`normalized_transcript` 字符串类型 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - num_samples (int, optional): The number of audios to be included in the dataset - (default=None, all audios). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - shuffle (bool, optional): Whether to perform shuffle on the dataset (default=None, expected - order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, `num_samples` reflects - the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str): 指向包含数据集根目录的路径字符串。 + num_samples (int, optional): 可选参数,表示要包含在数据集中的音频数量(默认值为 None,表示所有音频)。 + num_parallel_workers (int, optional): 可选参数,表示用于读取数据的工人数(默认值为 None,表示根据配 + 置文件设置的工人数)。 + shuffle (bool, optional): 可选参数,表示是否对数据进行洗牌(默认值为 None,表示根据预期顺序进行顺序读 + 取)。 + sampler (Sampler, optional): 可选参数,表示用于从数据集中选择样本的对象(默认值为 None,表示不使 + 用采样器)。 + num_shards (int, optional): 可选参数,表示将数据集分为的 shard 数量(默认值为 None)。当此参数指定时, + num_samples 表示每个 shard 中的最大样本数量。 + shard_id (int, optional): 可选参数,表示 shard 的 ID(默认值为 None)。当 num_shards 也指定时,此参 + 数只能用于指定 shard ID。 + cache (DatasetCache, optional): 可选参数,表示使用张量缓存服务加速数据处理(默认值为 None,表示不使用缓 + 存)。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -497,24 +436,12 @@ class LJSpeechDataset(MappableDataset, AudioBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + * - 当sampler为None,shuffle为None时,数据集的顺序是随机的。 + 当sampler为None,shuffle为True时,数据集的顺序是随机的。 + 当sampler为None,shuffle为False时,数据集的顺序是顺序处理的。 + * - 当sampler是一个sampler对象,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler是一个sampler对象,shuffle为True时,不允许使用sampler,因为可能会导致顺序不一致。 + 当sampler是一个sampler对象,shuffle为False时,不允许使用sampler,因为可能会导致顺序不一致。 Examples: >>> lj_speech_dataset_dir = "/path/to/lj_speech_dataset_directory" @@ -533,12 +460,11 @@ class LJSpeechDataset(MappableDataset, AudioBaseDataset): About LJSPEECH dataset: - This is a public domain speech dataset consisting of 13,100 short audio clips of a single speaker - reading passages from 7 non-fiction books. A transcription is provided for each clip. - Clips vary in length from 1 to 10 seconds and have a total length of approximately 24 hours. - - The texts were published between 1884 and 1964, and are in the public domain. - The audio was recorded in 2016-17 by the LibriVox project and is also in the public domain. + 数据集包含 13100 个短音频片段,由一个 single 说话者阅读 7 本书中的文本。对于每个片段,提供了一个文本翻译。 + 音频片段的 lengths 范围从 1 到 10 秒,总长度approximately 24 小时。 + + 文本内容随时间变化,公开domain。 + 音频片段由 The LibriVox 项目在 2016-17 年记录,也属于公开domain。 Here is the original LJSPEECH dataset structure. You can unzip the dataset files into the following directory structure and read by MindSpore's API. @@ -587,34 +513,26 @@ class LJSpeechDataset(MappableDataset, AudioBaseDataset): class SpeechCommandsDataset(MappableDataset, AudioBaseDataset): """ - A source dataset that reads and parses the SpeechCommands dataset. + 用于读取和解析 SpeechCommands 数据集。 The generated dataset has five columns :py:obj:`[waveform, sample_rate, label, speaker_id, utterance_number]`. - The tensor of column :py:obj:`waveform` is a vector of the float32 type. - The tensor of column :py:obj:`sample_rate` is a scalar of the int32 type. - The tensor of column :py:obj:`label` is a scalar of the string type. - The tensor of column :py:obj:`speaker_id` is a scalar of the string type. - The tensor of column :py:obj:`utterance_number` is a scalar of the int32 type. + The tensor of column :py:obj:`waveform` 浮点型数据 + The tensor of column :py:obj:`sample_rate` 无符号整数(uint32) + The tensor of column :py:obj:`label` 字符串类型 + The tensor of column :py:obj:`speaker_id` 字符串类型 + The tensor of column :py:obj:`utterance_number` 无符号整数(uint32) Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Usage of this dataset, can be 'train', 'test', 'valid' or 'all'. 'train' - will read from 84,843 samples, 'test' will read from 11,005 samples, 'valid' will read from 9,981 - test samples and 'all' will read from all 105,829 samples (default=None, will read all samples). - num_samples (int, optional): The number of samples to be included in the dataset - (default=None, will read all samples). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, will use value set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the dataset - (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This argument can only be specified - when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing - (default=None, which means no cache is used). + dataset_dir (str): SpeechCommands 数据集的根目录。 + usage (str, optional): 用于指定要读取的数据集部分,可以是 'train'、'test'、'valid' 或 'all'。 + num_samples (int, optional): 表示要包含在 dataset 中的样本数量,默认为 None,表示读取所有样本。 + num_parallel_workers (int, optional): 表示用于读取数据的 worker 数量,默认为 None,表示使用配置中的值。 + shuffle (bool, optional): 表示是否对 dataset 进行 shuffle,默认为 None,表示按照输入顺序读取。 + sampler (Sampler, optional): 表示用于从 dataset 中选择样本的 sampler 对象,默认为 None,表示不使用 sampler。 + num_shards (int, optional): N表示将 dataset 分为的 shard 数量,默认为 None。当这个参数被指定时,num_samples + 表示每个 shard 中的最大样本数量。 + shard_id (int, optional): 表示当前 shard 的 ID,当 num_shards 也指定时,这个参数必须被指定。 + cache (DatasetCache, optional): 表示用于加速 dataset 处理的 tensor 缓存对象,默认为 None,表示不使用缓存。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -636,24 +554,12 @@ class SpeechCommandsDataset(MappableDataset, AudioBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + * - 当sampler为None,shuffle为None时,数据集的顺序是随机的。 + 当sampler为None,shuffle为True时,数据集的顺序是随机的。 + 当sampler为None,shuffle为False时,数据集的顺序是顺序处理的。 + * - 当sampler是一个sampler对象,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler是一个sampler对象,shuffle为True时,不允许使用sampler,因为可能会导致顺序不一致。 + 当sampler是一个sampler对象,shuffle为False时,不允许使用sampler,因为可能会导致顺序不一致。 Examples: >>> speech_commands_dataset_dir = "/path/to/speech_commands_dataset_directory" @@ -666,8 +572,7 @@ class SpeechCommandsDataset(MappableDataset, AudioBaseDataset): About SpeechCommands dataset: - The SpeechCommands is database for limited_vocabulary speech recognition, containing 105,829 audio samples of - '.wav' format. + SpeechCommands 是一个用于有限词汇语音识别的数据库,包含 105,829 个 .wav 格式的音频样本。 Here is the original SpeechCommands dataset structure. You can unzip the dataset files into this directory structure and read by MindSpore's API. @@ -712,46 +617,33 @@ class SpeechCommandsDataset(MappableDataset, AudioBaseDataset): class TedliumDataset(MappableDataset, AudioBaseDataset): """ - A source dataset that reads and parses Tedlium dataset. - The columns of generated dataset depend on the source SPH files and the corresponding STM files. + 用于读取并解析 Tedlium 数据集。 + 生成的数据集的列取决于源 SPH 文件和对应的 STM 文件。 The generated dataset has six columns :py:obj:`[waveform, sample_rate, transcript, talk_id, speaker_id, identifier]`. - The tensor of column :py:obj:`waveform` is of the float32 type. - The tensor of column :py:obj:`sample_rate` is a scalar of the int32 type. - The tensor of column :py:obj:`transcript` is a scalar of the string type. - The tensor of column :py:obj:`talk_id` is a scalar of the string type. - The tensor of column :py:obj:`speaker_id` is a scalar of the string type. - The tensor of column :py:obj:`identifier` is a scalar of the string type. + The tensor of column :py:obj:`waveform` 浮点型数据 + The tensor of column :py:obj:`sample_rate` 无符号整数(uint32) + The tensor of column :py:obj:`transcript` 无符号整数(uint32) + The tensor of column :py:obj:`talk_id` 无符号整数(uint32) + The tensor of column :py:obj:`speaker_id` 无符号整数(uint32) + The tensor of column :py:obj:`identifier` 无符号整数(uint32) Args: - dataset_dir (str): Path to the root directory that contains the dataset. - release (str): Release of the dataset, can be 'release1', 'release2', 'release3'. - usage (str, optional): Usage of this dataset. - For release1 or release2, can be 'train', 'test', 'dev' or 'all'. - 'train' will read from train samples, - 'test' will read from test samples, - 'dev' will read from dev samples, - 'all' will read from all samples. - For release3, can only be 'all', it will read from data samples (default=None, all samples). - extensions (str): Extensions of the SPH files, only '.sph' is valid. - (default=None, ".sph"). - num_samples (int, optional): The number of audio samples to be included in the dataset - (default=None, all samples). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - shuffle (bool, optional): Whether to perform shuffle on the dataset (default=None, expected - order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, `num_samples` reflects - the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str): 表示数据集的根目录路径。 + release (str): 表示数据集的版本,可以是 'release1'、'release2'、'release3' 等。 + usage (str, optional): 表示要使用的数据集类型。 + 对于 'release1' 或 'release2',可以是 'train'、'test'、'dev' 或 'all'。 + 对于 'release3',只能为 'all'。 + extensions (str): 表示 SPH 文件的扩展名,只支持 '.sph'。 + num_samples (int, optional): 表示要包含在数据集中的音频样本数量。 + num_parallel_workers (int, optional): 表示用于读取数据的worker数量。 + shuffle (bool, optional): 表示是否对数据集进行shuffle。 + sampler (Sampler, optional): 用于从数据集中选择样本的对象。 + num_shards (int, optional): 表示数据集将被分为的shard数量。当此参数指定时,num_samples 表示每个shard中的最大样本数量。 + shard_id (int, optional): 表示当前shard的ID,当num_shards也被指定时,此参数才有效。 + cache (DatasetCache, optional): 用于加速数据集处理的张量缓存对象。 Raises: RuntimeError: If `dataset_dir` does not contain stm files. @@ -773,24 +665,12 @@ class TedliumDataset(MappableDataset, AudioBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + * - 当sampler为None,shuffle为None时,数据集的顺序是随机的。 + 当sampler为None,shuffle为True时,数据集的顺序是随机的。 + 当sampler为None,shuffle为False时,数据集的顺序是顺序处理的。 + * - 当sampler是一个sampler对象,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler是一个sampler对象,shuffle为True时,不允许使用sampler,因为可能会导致顺序不一致。 + 当sampler是一个sampler对象,shuffle为False时,不允许使用sampler,因为可能会导致顺序不一致。 Examples: >>> # 1) Get all train samples from TEDLIUM_release1 dataset in sequence. @@ -810,22 +690,18 @@ class TedliumDataset(MappableDataset, AudioBaseDataset): About TEDLIUM_release1 dataset: - The TED-LIUM corpus is English-language TED talks, with transcriptions, sampled at 16kHz. - It contains about 118 hours of speech. + TED-LIUM 语料库是一个英文的 TED talk 语料库,采样率为 16kHz。它包含大约 118 小时的语言数据。 About TEDLIUM_release2 dataset: - This is the TED-LIUM corpus release 2, licensed under Creative Commons BY-NC-ND 3.0. All talks and text are - property of TED Conferences LLC. The TED-LIUM corpus was made from audio talks and their transcriptions available - on the TED website. We have prepared and filtered these data in order to train acoustic models to participate to - the International Workshop on Spoken Language Translation 2011 (the LIUM English/French SLT system reached the - first rank in the SLT task). + 这是 TED-LIUM 语料库的第二个版本,根据 Creative Commons BY-NC-ND 3.0 许可发布。所有talk和文本属 TED Conferences LLC + 所有。TED-LIUM 语料库是从 TED 网站上的音频talk和它们的翻译文本中准备和过滤而来的数据。我们已准备并过滤了这些数据,以便训 + 练参与国际语音翻译比赛(2011 年国际语音翻译比赛第一名的 LIUM 英法双语系统)。 About TEDLIUM_release-3 dataset: - This is the TED-LIUM corpus release 3, licensed under Creative Commons BY-NC-ND 3.0. All talks and text are - property of TED Conferences LLC. This new TED-LIUM release was made through a collaboration between the Ubiqus - company and the LIUM (University of Le Mans, France). + 这是 TED-LIUM 语料库的第三个版本,根据 Creative Commons BY-NC-ND 3.0 许可发布。所有talk和文本属 TED Conferences LLC + 所有。这个新的 TED-LIUM 发布是通过与 Ubiqus 公司和 LIUM(法属勒马大学)之间的协作开发的。 You can unzip the dataset files into the following directory structure and read by MindSpore's API. @@ -924,30 +800,24 @@ class TedliumDataset(MappableDataset, AudioBaseDataset): class YesNoDataset(MappableDataset, AudioBaseDataset): """ - A source dataset that reads and parses the YesNo dataset. + 用于读取并解析 YesNo 数据集,生成相应的数据集。 The generated dataset has three columns :py:obj:`[waveform, sample_rate, labels]`. - The tensor of column :py:obj:`waveform` is a vector of the float32 type. - The tensor of column :py:obj:`sample_rate` is a scalar of the int32 type. - The tensor of column :py:obj:`labels` is a scalar of the int32 type. + The tensor of column :py:obj:`waveform` 浮点型数据 + The tensor of column :py:obj:`sample_rate` 无符号整数(uint32) + The tensor of column :py:obj:`labels` 无符号整数(uint32) Args: - dataset_dir (str): Path to the root directory that contains the dataset. - num_samples (int, optional): The number of images to be included in the dataset - (default=None, will read all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, will use value set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This argument can only - be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing - (default=None, which means no cache is used). - + dataset_dir (str): 指向数据集根目录的路径。这个目录应该包含数据集的图像或其他文件。 + num_samples (int, optional): 要包含在数据集中的图像数量(默认为None,表示读取所有图像)。 + num_parallel_workers (int, optional): 用于读取数据的worker数量(默认为None,表示使用配置文件中的值)。 + shuffle (bool, optional): 是否对数据进行shuffle(默认为None,表示按照预期顺序读取数据,即不shuffle)。 + sampler (Sampler, optional): 用于从数据集中选择样本的对象(默认为None,表示使用配置文件中的值)。 + num_shards (int, optional): 将数据集划分为shard的数量(默认为None)。当这个参数被指定时,num_samples表 + 示每个shard中的最大样本数量。 + shard_id (int, optional): 在num_shards中的shard ID(默认为None)。当这个参数也被指定时,shard_id必须 + 小于num_shards。 + cache (DatasetCache, optional): 用于加速数据集处理的张量缓存对象(默认为None,表示不使用缓存)。 Raises: RuntimeError: If `dataset_dir` does not contain data files. ValueError: If `num_parallel_workers` exceeds the max thread numbers. @@ -968,24 +838,12 @@ class YesNoDataset(MappableDataset, AudioBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + * - 当sampler为None,shuffle为None时,数据集的顺序是随机的。 + 当sampler为None,shuffle为True时,数据集的顺序是随机的。 + 当sampler为None,shuffle为False时,数据集的顺序是顺序处理的。 + * - 当sampler是一个sampler对象,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler是一个sampler对象,shuffle为True时,不允许使用sampler,因为可能会导致顺序不一致。 + 当sampler是一个sampler对象,shuffle为False时,不允许使用sampler,因为可能会导致顺序不一致。 Examples: >>> yes_no_dataset_dir = "/path/to/yes_no_dataset_directory" @@ -997,9 +855,8 @@ class YesNoDataset(MappableDataset, AudioBaseDataset): About YesNo dataset: - Yesno is an audio dataset consisting of 60 recordings of one individual saying yes or no in Hebrew; each - recording is eight words long. It was created for the Kaldi audio project by an author who wishes to - remain anonymous. + 这个数据集包含60个录音,每个录音由一个个人 saying yes或no 组成,每个录音长度为8个单词。这个数据集是为了 + Kaldi音频项目而创建的,由一个匿名作者创建。 Here is the original YesNo dataset structure. You can unzip the dataset files into this directory structure and read by MindSpore's API. diff --git a/mindspore/python/mindspore/dataset/engine/datasets_standard_format.py b/mindspore/python/mindspore/dataset/engine/datasets_standard_format.py index 1569c5d1ac3..6e2110c2396 100644 --- a/mindspore/python/mindspore/dataset/engine/datasets_standard_format.py +++ b/mindspore/python/mindspore/dataset/engine/datasets_standard_format.py @@ -40,40 +40,33 @@ from . import samplers class CSVDataset(SourceDataset, UnionBaseDataset): """ - A source dataset that reads and parses comma-separated values + 用于从CSV文件中读取数据并将其作为数据集。 `(CSV) `_ files as dataset. The columns of generated dataset depend on the source CSV files. Args: - dataset_files (Union[str, list[str]]): String or list of files to be read or glob strings to search - for a pattern of files. The list will be sorted in a lexicographical order. - field_delim (str, optional): A string that indicates the char delimiter to separate fields (default=','). - column_defaults (list, optional): List of default values for the CSV field (default=None). Each item - in the list is either a valid type (float, int, or string). If this is not provided, treats all - columns as string type. - column_names (list[str], optional): List of column names of the dataset (default=None). If this - is not provided, infers the column_names from the first row of CSV file. - num_samples (int, optional): The number of samples to be included in the dataset - (default=None, will include all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - shuffle (Union[bool, Shuffle level], optional): Perform reshuffling of the data every epoch - (default=Shuffle.GLOBAL). - If shuffle is False, no shuffling will be performed. - If shuffle is True, performs global shuffle. - There are three levels of shuffling, desired shuffle enum defined by mindspore.dataset.Shuffle. + dataset_files (Union[str, list[str]]): 一个字符串或列表,表示要读取或搜索的CSV文件。如果这是一个列表,它将 + 按字母顺序进行排序。 + field_delim (str, optional): 一个字符串,表示用于分隔CSV文件中的字段的分隔符(默认值为',')。 + column_defaults (list, optional): 一个列表,表示CSV文件中字段的默认值。每个项都是一个有效的类型(浮点数、整 + 数或字符串)。如果没有提供,则将所有列视为字符串类型。 + column_names (list[str], optional): 一个列表,表示CSV文件中列的名称。如果没有提供,将根据第一个行的内容推断 + 列名称。 + num_samples (int, optional): 一个整数,表示要包含在数据集中的样本数量。如果没有提供,将包含所有样本。 + num_parallel_workers (int, optional): 一个整数,表示用于读取数据的 worker 数量。如果没有提供,将使用配置中 + 的最大线程数。 + shuffle (Union[bool, Shuffle level], optional): 一个枚举值,表示是否对数据进行洗牌。 + shuffle值为False,不进行洗牌 + shuffle值为True,进行全局洗牌 + 有以下三种选项: + Shuffle.GLOBAL:既洗牌文件又洗牌样本。 + Shuffle.FILES:只洗牌文件。 + Shuffle.NONE:不洗牌。 - - Shuffle.GLOBAL: Shuffle both the files and samples, same as setting shuffle to True. - - - Shuffle.FILES: Shuffle files only. - - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + num_shards (int, optional): 一个整数,表示将数据集分为多少个分片。如果没有提供,则不进行分片。 + shard_id (int, optional): 一个整数,表示当前分片的ID。如果提供了num_shards,则必须提供shard_id。 + cache (DatasetCache, optional): 一个个DatasetCache对象,用于加速数据处理。如果没有提供,则不使用缓存。 Raises: RuntimeError: If dataset_files are not valid or do not exist. @@ -106,45 +99,35 @@ class CSVDataset(SourceDataset, UnionBaseDataset): class MindDataset(MappableDataset, UnionBaseDataset): """ - A source dataset that reads and parses MindRecord dataset. + 用于从MindRecord文件中读取数据并将其作为数据集。 The columns of generated dataset depend on the source MindRecord files. Args: - dataset_files (Union[str, list[str]]): If dataset_file is a str, it represents for - a file name of one component of a mindrecord source, other files with identical source - in the same path will be found and loaded automatically. If dataset_file is a list, - it represents for a list of dataset files to be read directly. - columns_list (list[str], optional): List of columns to be read (default=None). - num_parallel_workers (int, optional): The number of readers (default=None). - shuffle (Union[bool, Shuffle level], optional): Perform reshuffling of the data every epoch + dataset_files (Union[str, list[str]]): 一个字符串或列表,表示要读取或搜索的MindRecord文件。如果这 + 是一个列表,它将按字母顺序进行排序。 + columns_list (list[str], optional): 一个列表,表示要读取的列名。如果没有提供,将根据第一个行的内容推断列名称。 + num_parallel_workers (int, optional): 一个整数,表示用于读取数据的 worker 数量。如果没有提供,将使用配置中的 + 最大线程数。 + shuffle (Union[bool, Shuffle level], optional): 一个枚举值,表示是否对数据进行洗牌。 (default=None, performs global shuffle). - If shuffle is False, no shuffling will be performed. - If shuffle is True, performs global shuffle. - There are three levels of shuffling, desired shuffle enum defined by mindspore.dataset.Shuffle. + shuffle值为False,不进行洗牌 + shuffle值为True,进行全局洗牌 - - Shuffle.GLOBAL: Global shuffle of all rows of data in dataset, same as setting shuffle to True. + 有以下三种选项: + Shuffle.GLOBAL:全局洗牌,即洗牌文件和样本。 + Shuffle.FILES:只洗牌文件。 + Shuffle.INFILE:保留文件顺序但洗牌数据内部。 - - Shuffle.FILES: Shuffle the file sequence but keep the order of data within each file. - - - Shuffle.INFILE: Keep the file sequence the same but shuffle the data within each file. - - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, 'num_samples' reflects the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, sampler is exclusive - with shuffle and block_reader). Support list: SubsetRandomSampler, - PkSampler, RandomSampler, SequentialSampler, DistributedSampler. - padded_sample (dict, optional): Samples will be appended to dataset, where - keys are the same as column_list. - num_padded (int, optional): Number of padding samples. Dataset size - plus num_padded should be divisible by num_shards. - num_samples (int, optional): The number of samples to be included in the dataset - (default=None, all samples). - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + num_shards (int, optional): 一个整数,表示将数据集分为多少个分片。当这个参数指定时,num_samples表示每个分片的 + 最大样本数。 + shard_id (int, optional): 一个整数,表示当前分片的ID。如果提供了num_shards,则必须提供shard_id。 + sampler (Sampler, optional): 一个Sampler对象,用于选择样本。sampler和shuffle是互斥的,如果提供了sampler,则 + shuffle参数将被忽略。 + padded_sample (dict, optional): 一个字典,用于填充样本,其中键是列名,值是填充的值。 + num_padded (int, optional): 一个整数,表示要填充的样本数量。数据集大小加上num_padded应该可以被num_shards整除。 + num_samples (int, optional): 一个整数,表示要包含在数据集中的样本数量。如果没有提供,将包含所有样本。 + cache (DatasetCache, optional): 个DatasetCache对象,用于加速数据处理。如果没有提供,则不使用缓存。 Raises: ValueError: If dataset_files are not valid or do not exist. @@ -164,24 +147,12 @@ class MindDataset(MappableDataset, UnionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + * - 当sampler为None,shuffle为None时,数据集的顺序是随机的。 + 当sampler为None,shuffle为True时,数据集的顺序是随机的。 + 当sampler为None,shuffle为False时,数据集的顺序是顺序处理的。 + * - 当sampler是一个sampler对象,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler是一个sampler对象,shuffle为True时,不允许使用sampler,因为可能会导致顺序不一致。 + 当sampler是一个sampler对象,shuffle为False时,不允许使用sampler,因为可能会导致顺序不一致。 Examples: >>> mind_dataset_dir = ["/path/to/mind_dataset_file"] # contains 1 or multiple MindRecord files @@ -197,7 +168,9 @@ class MindDataset(MappableDataset, UnionBaseDataset): shard_id=None, sampler=None, padded_sample=None, num_padded=None, num_samples=None, cache=None): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle_to_bool(shuffle), num_shards=num_shards, shard_id=shard_id, cache=cache) + # 检查num_samples和shuffle是否同时指定 if num_samples and shuffle in (Shuffle.FILES, Shuffle.INFILE): + # 指定了Shuffle.FILES或Shuffle.INFILE策略和num_samples,则会引发一个错误 raise ValueError("'Shuffle.FILES' or 'Shuffle.INFILE' and 'num_samples' " "cannot be specified at the same time.") self.shuffle_option = shuffle @@ -206,29 +179,37 @@ class MindDataset(MappableDataset, UnionBaseDataset): self.load_dataset = False self.dataset_files = dataset_files + # 检查操作系统是否为"Windows" if platform.system().lower() == "windows": if isinstance(dataset_files, list): file_tuple = [] for item in dataset_files: + # 对dataset_files中的路径进行替换(将"\"替换为"/") item.replace("\\", "/") file_tuple.append(item) self.dataset_files = file_tuple else: self.dataset_files = dataset_files.replace("\\", "/") + # 无论dataset_files是一个列表还是一个字符串,它都会被处理为一个包含正确路径的字符串列表。 self.columns_list = replace_none(columns_list, []) if sampler is not None: + # 检查sampler是否是一个支持抽样的抽样器对象 if isinstance(sampler, ( samplers.SubsetRandomSampler, samplers.SubsetSampler, samplers.PKSampler, samplers.DistributedSampler, samplers.RandomSampler, samplers.SequentialSampler)) is False: + # 抛出一个错误 raise ValueError("The sampler is not supported yet.") + #初始化一个名为padded_sample的字典,用于存储填充后的样本 self.padded_sample = padded_sample self.num_padded = replace_none(num_padded, 0) + # 初始化一个名为new_padded_sample的字典,用于存储填充后的样本 self.new_padded_sample = {} + # 如果padded_sample不为空,则将padded_sample中的键值对转换为字节串,并将其添加到new_padded_sample中。 if padded_sample: for k, v in padded_sample.items(): if isinstance(v, np.ndarray): @@ -239,43 +220,34 @@ class MindDataset(MappableDataset, UnionBaseDataset): class TFRecordDataset(SourceDataset, UnionBaseDataset): """ - A source dataset that reads and parses datasets stored on disk in TFData format. + 用于读取和解析存储在磁盘上的TFData格式数据集。 The columns of generated dataset depend on the source TFRecord files. Args: - dataset_files (Union[str, list[str]]): String or list of files to be read or glob strings to search for a - pattern of files. The list will be sorted in a lexicographical order. - schema (Union[str, Schema], optional): Path to the JSON schema file or schema object (default=None). - If the schema is not provided, the meta data from the TFData file is considered the schema. - columns_list (list[str], optional): List of columns to be read (default=None, read all columns). - num_samples (int, optional): The number of samples (rows) to be included in the dataset (default=None). - If num_samples is None and numRows(parsed from schema) does not exist, read the full dataset; - If num_samples is None and numRows(parsed from schema) is greater than 0, read numRows rows; - If both num_samples and numRows(parsed from schema) are greater than 0, read num_samples rows. - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - shuffle (Union[bool, Shuffle level], optional): Perform reshuffling of the data every epoch - (default=Shuffle.GLOBAL). - If shuffle is False, no shuffling will be performed. - If shuffle is True, performs global shuffle. - There are three levels of shuffling, desired shuffle enum defined by mindspore.dataset.Shuffle. + dataset_files (Union[str, list[str]]): 一个字符串或一个包含字符串的列表,表示要读取或搜索的TFRecord文件。如 + 果列表包含多个文件,它们将按字典顺序排序。 + schema (Union[str, Schema], optional): 一个字符串或一个Schema对象,表示要使用的JSONschema文件或schema对象。 + 如果没有提供schema,则从TFData文件中使用元数据作为schema。 + columns_list (list[str], optional): 一个字符串列表,表示要读取的列名。如果没有提供,将读取所有列。 + num_samples (int, optional): 一个整数,表示要包含在数据集中的样本数量。如果没有提供,或者提供的数量为0,将读取 + 整个数据集。如果提供了num_samples且numRows(从schema中解析)大于0,将读取num_samples个样本。 + num_parallel_workers (int, optional): 一个整数,表示用于读取数据的worker数量。如果没有提供,将使用配置中的最 + 大线程数。 + shuffle (Union[bool, Shuffle level], optional): 一个枚举值,表示是否对数据进行洗牌。 + (default=None, performs global shuffle). + shuffle值为False,不进行洗牌 + shuffle值为True,进行全局洗牌 - - Shuffle.GLOBAL: Shuffle both the files and samples, same as setting shuffle to True. - - - Shuffle.FILES: Shuffle files only. - - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, `num_samples` reflects - the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - shard_equal_rows (bool, optional): Get equal rows for all shards(default=False). If shard_equal_rows - is false, number of rows of each shard may be not equal, and may lead to a failure in distributed training. - When the number of samples of per TFRecord file are not equal, it is suggested to set to true. - This argument should only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + 有以下三种选项: + Shuffle.GLOBAL:全局洗牌,即洗牌文件和样本。 + Shuffle.FILES:只洗牌文件。 + num_shards (int, optional): 一个整数,表示将数据集分为的shard数量。如果提供了num_shards,且没有提供shard_id, + 将引发一个RuntimeError。 + shard_id (int, optional): T一个整数,表示当前shard的ID。如果提供了shard_id,且没有提供num_shards,将引发一个RuntimeError。 + shard_equal_rows (bool, optional): 一个布尔值,表示每个shard是否具有相等的行数。如果设置为False,可能会导致在分 + 布式训练中的失败。当num_shards不等于1时,建议设置为True。 + cache (DatasetCache, optional): 个DatasetCache对象,用于加速数据处理。如果没有提供,则不使用缓存。 Raises: ValueError: If dataset_files are not valid or do not exist. @@ -327,39 +299,32 @@ class TFRecordDataset(SourceDataset, UnionBaseDataset): class OBSMindDataset(GeneratorDataset): """ - A source dataset that reads and parses MindRecord dataset which stored in OBS. - + 用于从OBS中读取和解析MindRecord数据集。 The columns of generated dataset depend on the source MindRecord files. Args: - dataset_files (list[str]): List of files in OBS to be read and file path is in - the format of s3://. - server (str): Endpoint for accessing OBS. For example: . - ak (str): Access key ID of OBS. - sk (str): Secret key ID of OBS. - sync_obs_path (str): OBS dir path used for synchronization, users need to - create it on OBS in advance. Path is in the format of s3://. - columns_list (list[str], optional): List of columns to be read (default=None, read all columns). - shuffle (Union[bool, Shuffle level], optional): Perform reshuffling of the data every epoch + dataset_files (list[str]): 一个字符串列表,表示要读取和搜索的MindRecord文件。每个文件路径都用s3://开头。 + server (str): OBS的终端地址,例如:https://your-endpoint:9000。 + ak (str): OBS的访问密钥ID。 + sk (str): OBS的密钥ID。 + sync_obs_path (str): OBS同步目录路径,用于同步数据。路径用s3://开头。 + columns_list (list[str], optional): 一个字符串列表,表示要读取的列名。如果没有提供,将读取所有列。 + shuffle (Union[bool, Shuffle level], optional): 一个枚举值,表示是否对数据进行洗牌。 (default=None, performs global shuffle). - If shuffle is False, no shuffling will be performed. - If shuffle is True, performs global shuffle. - There are three levels of shuffling, desired shuffle enum defined by mindspore.dataset.Shuffle. + shuffle值为False,不进行洗牌 + shuffle值为True,进行全局洗牌 - - Shuffle.GLOBAL: Global shuffle of all rows of data in dataset, same as setting shuffle to True. + 有以下三种选项: + Shuffle.GLOBAL:全局洗牌,即洗牌文件和样本。 + Shuffle.FILES:只洗牌文件。 + Shuffle.INFILE:保留文件顺序但洗牌数据内部。 - - Shuffle.FILES: Shuffle the file sequence but keep the order of data within each file. - - - Shuffle.INFILE: Keep the file sequence the same but shuffle the data within each file. - - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). - shard_id (int, optional): The shard ID within num_shards (default=None). This - argument can only be specified when num_shards is also specified. - shard_equal_rows (bool, optional): Get equal rows for all shards(default=True). If shard_equal_rows - is false, number of rows of each shard may be not equal, and may lead to a failure in distributed training. - When the number of samples of per MindRecord file are not equal, it is suggested to set to true. - This argument should only be specified when num_shards is also specified. + num_shards (int, optional): 一个整数,表示将数据集分为的shard数量。如果提供了num_shards,且没有提供shard_id, + 将引发一个RuntimeError。 + shard_id (int, optional): T一个整数,表示当前shard的ID。如果提供了shard_id,且没有提供num_shards, + 将引发一个RuntimeError。 + shard_equal_rows (bool, optional): 一个布尔值,表示每个shard是否具有相等的行数。如果设置为True,可能会导致在 + 分布式训练中的失败。当num_shards不等于1时,建议设置为True。 Raises: RuntimeError: If `sync_obs_path` do not exist. @@ -394,35 +359,48 @@ class OBSMindDataset(GeneratorDataset): shard_id=None, shard_equal_rows=True): + # 从config_loader模块中导入config对象 from .obs.config_loader import config + # 对config对象的AK、SK、SERVER和SYNC_OBS_PATH属性进行赋值 config.AK = ak config.SK = sk config.SERVER = server config.SYNC_OBS_PATH = sync_obs_path + # 判断shuffle参数的类型是否为bool或Shuffle枚举值 if shuffle is not None and not isinstance(shuffle, (bool, Shuffle)): + # 抛出一个TypeError异常 raise TypeError("shuffle must be of boolean or enum of 'Shuffle' values like 'Shuffle.GLOBAL' or " "'Shuffle.FILES'.") + # 将num_shards、shard_id和shuffle参数替换为None的值,以确保它们是整数类型 self.num_shards = replace_none(num_shards, 1) self.shard_id = replace_none(shard_id, 0) self.shuffle = replace_none(shuffle, True) + # 创建一个MindRecordFromOBS实例 dataset = MindRecordFromOBS(dataset_files, columns_list, shuffle, self.num_shards, self.shard_id, shard_equal_rows, config.DATASET_LOCAL_PATH) + # 判断columns_list参数是否为None,如果为None,则将dataset.get_col_names()赋值给columns_ if not columns_list: columns_list = dataset.get_col_names() else: + # 使用dataset.get_col_names()方法获取数据集中的所有列名 full_columns_list = dataset.get_col_names() + # 判断columns_list是否是full_columns_list的子集 if not set(columns_list).issubset(full_columns_list): raise ValueError("columns_list: {} can not found in MindRecord fields: {}".format(columns_list, full_columns_list)) super().__init__(source=dataset, column_names=columns_list, num_shards=None, shard_id=None, shuffle=False) - + # 用于向OBSMindDataset类添加一个新的采样器 + # 但是,这个函数没有被实现,即没有实现向OBSMindDataset类添加采样器的功能。当调用add_sampler函数时, + # 会抛出一个NotImplementedError异常,提示add_sampler没有被实现。 def add_sampler(self, new_sampler): raise NotImplementedError("add_sampler is not supported for OBSMindDataset.") - + # 用于向OBSMindDataset类使用一个新的采样器 + # 但是,这个函数没有被实现,即没有实现从OBSMindDataset类中使用采样器的功能。当调用use_sampler函数时, + # 会抛出一个NotImplementedError异常,提示use_sampler没有被实现。 def use_sampler(self, new_sampler): raise NotImplementedError("use_sampler is not supported for OBSMindDataset.") diff --git a/mindspore/python/mindspore/dataset/engine/datasets_text.py b/mindspore/python/mindspore/dataset/engine/datasets_text.py index 1b565814741..28af7f4d4dc 100644 --- a/mindspore/python/mindspore/dataset/engine/datasets_text.py +++ b/mindspore/python/mindspore/dataset/engine/datasets_text.py @@ -35,37 +35,38 @@ from .validators import check_imdb_dataset, check_iwslt2016_dataset, check_iwslt from ..core.validator_helpers import replace_none +# 本文件中代码为用于读取与解析各种文本类数据集的类 + + class AGNewsDataset(SourceDataset, TextBaseDataset): """ - A source dataset that reads and parses AG News datasets. + 用于读取和解析AG新闻数据集。 + 生成一个包含三个列的dataset。 The generated dataset has three columns: :py:obj:`[index, title, description]`. - The tensor of column :py:obj:`index` is of the string type. - The tensor of column :py:obj:`title` is of the string type. - The tensor of column :py:obj:`description` is of the string type. + The tensor of column :py:obj:`index` 字符串类型 + The tensor of column :py:obj:`title` 字符串类型 + The tensor of column :py:obj:`description` 字符串类型 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Acceptable usages include 'train', 'test' and 'all' (default=None, all samples). - num_samples (int, optional): Number of samples (rows) to read (default=None, reads the full dataset). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - shuffle (Union[bool, Shuffle level], optional): Perform reshuffling of the data every epoch - (default=Shuffle.GLOBAL). - If shuffle is False, no shuffling will be performed. - If shuffle is True, performs global shuffle. - There are three levels of shuffling, desired shuffle enum defined by mindspore.dataset.Shuffle. + dataset_dir (str): 路径到包含数据集的根目录。 + usage (str, optional): 可接受的使用范围,包括'train'(训练集)、'test'(测试集)和'all'(所有样本),默认 + 为None,表示读取所有样本。 + num_samples (int, optional): 要读取的样本数量(行数),默认为None,表示读取整个数据集。 + num_parallel_workers (int, optional): 用于读取数据的worker数量,默认为None,表示根据配置文件设置的worker数量。 + shuffle (Union[bool, Shuffle level], optional): 一个枚举值,表示是否对数据进行洗牌。 + (default=None, performs global shuffle). + shuffle值为False,不进行洗牌 + shuffle值为True,进行全局洗牌 - - Shuffle.GLOBAL: Shuffle both the files and samples, same as setting shuffle to True. + 有以下三种选项: + Shuffle.GLOBAL:全局洗牌,即洗牌文件和样本。 + Shuffle.FILES:只洗牌文件。 + Shuffle.INFILE:保留文件顺序但洗牌数据内部。 - - Shuffle.FILES: Shuffle files only. - - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, 'num_samples' reflects the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + num_shards (int, optional): 将数据集划分为多少个分片,默认为None,表示不进行分片。 + shard_id (int, optional): 当前分片的ID,当num_shards指定时,此参数必须指定。 + cache (DatasetCache, optional): 使用张量缓存服务加速数据处理,默认为None,表示不使用缓存。 Examples: >>> ag_news_dataset_dir = "/path/to/ag_news_dataset_file" @@ -73,16 +74,12 @@ class AGNewsDataset(SourceDataset, TextBaseDataset): About AGNews dataset: - AG is a collection of over 1 million news articles. The news articles were collected - by ComeToMyHead from over 2,000 news sources in over 1 year of activity. ComeToMyHead - is an academic news search engine that has been in operation since July 2004. - The dataset is provided by academics for research purposes such as data mining - (clustering, classification, etc.), information retrieval (ranking, searching, etc.), - xml, data compression, data streaming, and any other non-commercial activities. - AG's news topic classification dataset was constructed by selecting the four largest - classes from the original corpus. Each class contains 30,000 training samples and - 1,900 test samples. The total number of training samples in train.csv is 120,000 - and the number of test samples in test.csv is 7,600. + AGNews是一个包含1亿多条新闻文章的集合,这些文章是从2004年开始从2000多个新闻来源中收集的。ComeToMyHead是一个学术性的新闻搜 + 索引擎,已经运行了1年。 + + AGNews数据集提供给研究目的,包括数据挖掘(聚类、分类等)、信息检索(排序、搜索等)、XML、数据压缩、数据流等非商业activities。 + AG新闻主题分类数据集是从原始语料中选择四个最大的类别,每个类别包含30000训练样本和1900测试样本。训练样本的总数为120000,测试 + 样本的总数为7600。 You can unzip the dataset files into the following structure and read by MindSpore's API: @@ -112,54 +109,49 @@ class AGNewsDataset(SourceDataset, TextBaseDataset): @check_ag_news_dataset def __init__(self, dataset_dir, usage=None, num_samples=None, num_parallel_workers=None, shuffle=Shuffle.GLOBAL, num_shards=None, shard_id=None, cache=None): + # 调用父类的初始化方法 super().__init__(num_parallel_workers=num_parallel_workers, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 将self.dataset_dir赋值给dataset_dir self.dataset_dir = dataset_dir + # 将usage赋值给usage,将其替换为"all" self.usage = replace_none(usage, "all") - def parse(self, children=None): + # 返回cde.AGNewsNode return cde.AGNewsNode(self.dataset_dir, self.usage, self.num_samples, self.shuffle_flag, self.num_shards, self.shard_id) class AmazonReviewDataset(SourceDataset, TextBaseDataset): """ - A source dataset that reads and parses Amazon Review Polarity and Amazon Review Full datasets. + 用于读取和解析Amazon Review Polarity和Amazon Review Full数据集。 The generated dataset has three columns: :py:obj:`[label, title, content]`. - The tensor of column :py:obj:`label` is of the string type. - The tensor of column :py:obj:`title` is of the string type. - The tensor of column :py:obj:`content` is of the string type. + The tensor of column :py:obj:`label` 字符串类型 + The tensor of column :py:obj:`title` 字符串类型 + The tensor of column :py:obj:`content` 字符串类型 Args: - dataset_dir (str): Path to the root directory that contains the Amazon Review Polarity dataset - or the Amazon Review Full dataset. - usage (str, optional): Usage of this dataset, can be 'train', 'test' or 'all' (default= 'all'). - For Polarity dataset, 'train' will read from 3,600,000 train samples, - 'test' will read from 400,000 test samples, - 'all' will read from all 4,000,000 samples. - For Full dataset, 'train' will read from 3,000,000 train samples, - 'test' will read from 650,000 test samples, - 'all' will read from all 3,650,000 samples (default=None, all samples). - num_samples (int, optional): Number of samples (rows) to be read (default=None, reads the full dataset). - shuffle (Union[bool, Shuffle level], optional): Perform reshuffling of the data every epoch - (default=Shuffle.GLOBAL). - If shuffle is False, no shuffling will be performed. - If shuffle is True, performs global shuffle. - There are three levels of shuffling, desired shuffle enum defined by mindspore.dataset.Shuffle. + dataset_dir (str): 数据集的根目录,其中包含Amazon Review Polarity dataset或Amazon Review Full dataset。 + usage (str, optional): 数据集的用途,可以是'train'(训练集)、'test'(测试集)或'all'(全部样本),默认为'all'。 + 对于Polarity dataset,'train'将读取3,600,000训练样本,'test'将读取400,000测试样本,'all'将读取全部4,000,000样本。 + 对于Full dataset,'train'将读取3,000,000训练样本,'test'将读取650,000测试样本,'all'将读取全部3,650,000样本。 + num_samples (int, optional): 要读取的样本数量(行数),默认为None,表示读取整个数据集。 + shuffle (Union[bool, Shuffle level], optional): 一个枚举值,表示是否对数据进行洗牌。 + (default=None, performs global shuffle). + shuffle值为False,不进行洗牌 + shuffle值为True,进行全局洗牌 - - Shuffle.GLOBAL: Shuffle both the files and samples, same as setting shuffle to True. + 有以下三种选项: + Shuffle.GLOBAL:全局洗牌,即洗牌文件和样本。 + Shuffle.FILES:只洗牌文件。 + Shuffle.INFILE:保留文件顺序但洗牌数据内部 - - Shuffle.FILES: Shuffle files only. - - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the mindspore.dataset.config). - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing - (default=None, which means no cache is used). + num_shards (int, optional): 将数据集划分为多个分片(shard),默认为None。当这个参数指定时,num_samples表示每个分 + 片的最大样本数。 + shard_id (int, optional): 在num_shards中的分片ID,当num_shards也指定时,这个参数必须被指定。 + num_parallel_workers (int, optional): 用于读取数据的 worker 数量,默认为None,表示使用系统设置的 worker 数量。 + cache (DatasetCache, optional): 使用张量缓存服务加速数据集处理,默认为None,表示不使用缓存。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -173,12 +165,12 @@ class AmazonReviewDataset(SourceDataset, TextBaseDataset): About AmazonReview Dataset: - The Amazon reviews full dataset consists of reviews from Amazon. The data span a period of 18 years, including ~35 - million reviews up to March 2013. Reviews include product and user information, ratings, and a plaintext review. - The dataset is mainly used for text classification, given the content and title, predict the correct star rating. + Amazon Reviews Full dataset是一个包含来自Amazon的评论的完整数据集,时间跨度为18年,包括3500万条评论, + 直到2013年3月。这些评论包括产品信息和用户信息、评分和纯文本评论。这个数据集主要用于文本分类,给定内容和标 + 题,预测正确的星级评分。 - The Amazon reviews polarity dataset is constructed by taking review score 1 and 2 as negative, 4 and 5 as positive. - Samples of score 3 is ignored. In the dataset, class 1 is the negative and class 2 is the positive. + Amazon Reviews Polarity dataset是Amazon Reviews Full dataset的子集,将评分1和2视为负向,4和5视为正向。 + 在数据集中,类1表示负向,类2表示正向。 The Amazon Reviews Polarity and Amazon Reviews Full datasets have the same directory structures. You can unzip the dataset files into the following structure and read by MindSpore's API: @@ -208,50 +200,50 @@ class AmazonReviewDataset(SourceDataset, TextBaseDataset): @check_amazon_review_dataset def __init__(self, dataset_dir, usage=None, num_samples=None, num_parallel_workers=None, shuffle=Shuffle.GLOBAL, num_shards=None, shard_id=None, cache=None): + # 调用父类的初始化方法 super().__init__(num_parallel_workers=num_parallel_workers, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 将self.dataset_dir赋值给dataset_dir self.dataset_dir = dataset_dir + # 将usage赋值给usage,将其替换为"all" self.usage = replace_none(usage, 'all') def parse(self, children=None): + # 返回cde.AmazonReviewNode return cde.AmazonReviewNode(self.dataset_dir, self.usage, self.num_samples, self.shuffle_flag, self.num_shards, self.shard_id) class CLUEDataset(SourceDataset, TextBaseDataset): """ - A source dataset that reads and parses CLUE datasets. - Supported CLUE classification tasks: 'AFQMC', 'TNEWS', 'IFLYTEK', 'CMNLI', 'WSC' and 'CSL'. + 用于读取和解析CLUE数据集。 + CLUE是一个用于文本分类的任务集合,包括"AFQMC"、"TNEWS"、"IFLYTEK"、"CMNLI"和"WSC"等分类任务。 Args: - dataset_files (Union[str, list[str]]): String or list of files to be read or glob strings to search for - a pattern of files. The list will be sorted in a lexicographical order. - task (str, optional): The kind of task, one of 'AFQMC', 'TNEWS', 'IFLYTEK', 'CMNLI', 'WSC' and 'CSL'. - (default=AFQMC). - usage (str, optional): Specify the 'train', 'test' or 'eval' part of dataset (default='train'). - num_samples (int, optional): The number of samples to be included in the dataset - (default=None, will include all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - shuffle (Union[bool, Shuffle level], optional): Perform reshuffling of the data every epoch - (default=Shuffle.GLOBAL). - If shuffle is False, no shuffling will be performed. - If shuffle is True, performs global shuffle. - There are three levels of shuffling, desired shuffle enum defined by mindspore.dataset.Shuffle. + dataset_files (Union[str, list[str]]): 用于读取的文件或文件glob字符串列表,会按字典序排序。 + task (str, optional): 任务类型,可选值为"AFQMC"、"TNEWS"、"IFLYTEK"、"CMNLI"和"WSC",默认为"AFQMC"。 + usage (str, optional): 指定数据集的"train"、"test"或"eval"部分,默认为"train"。 + num_samples (int, optional): 要包含在数据集中的样本数量,默认为None,表示包含所有样本。 + num_parallel_workers (int, optional): 用于读取数据的worker数量,默认为None,表示使用系统设置的worker数量。 + shuffle (Union[bool, Shuffle level], optional): 一个枚举值,表示是否对数据进行洗牌。 + (default=None, performs global shuffle). + shuffle值为False,不进行洗牌 + shuffle值为True,进行全局洗牌 - - Shuffle.GLOBAL: Shuffle both the files and samples, same as setting shuffle to True. - - - Shuffle.FILES: Shuffle files only. - - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + 有以下三种选项: + Shuffle.GLOBAL:全局洗牌,即洗牌文件和样本。 + Shuffle.FILES:只洗牌文件。 + Shuffle.INFILE:保留文件顺序但洗牌数据内部。 + num_shards (int, optional): 数据集将被划分为多个分片(shard),默认为None。当这个参数指定时,num_samples表 + 示每个分片的最大样本数。 + shard_id (int, optional): 在num_shards中的分片ID,当num_shards也指定时,这个参数必须被指定。 + cache (DatasetCache, optional): 使用张量缓存服务加速数据集处理,默认为None,表示不使用缓存。 The generated dataset with different task setting has different output columns: + task:表示要处理的任务类型,包括"AFQMC"、"TNEWS"、"IFLYTEK"、"CMNLI"、"WSC"和"CSL"。 + usage:表示任务的使用方式,包括"train"和"test"。 + Output column:表示函数的输出列,不同的任务可能有不同的输出列。例如,对于文本分类任务,输出列可能包括"label"和"dtype=string"。 +-------------------------+------------------------------+-----------------------------+ | `task` | `usage` | Output column | +=========================+==============================+=============================+ @@ -407,9 +399,7 @@ class CLUEDataset(SourceDataset, TextBaseDataset): About CLUE dataset: - CLUE, a Chinese Language Understanding Evaluation benchmark. It contains multiple - tasks, including single-sentence classification, sentence pair classification, and machine - reading comprehension. + CLUE,汉语理解能力评估基准。它包含多个任务,包括单句分类、句对分类和机器阅读理解。 You can unzip the dataset files into the following structure and read by MindSpore's API, such as afqmc dataset: @@ -441,50 +431,49 @@ class CLUEDataset(SourceDataset, TextBaseDataset): @check_cluedataset def __init__(self, dataset_files, task='AFQMC', usage='train', num_samples=None, num_parallel_workers=None, shuffle=Shuffle.GLOBAL, num_shards=None, shard_id=None, cache=None): + # 调用父类的初始化方法 super().__init__(num_parallel_workers=num_parallel_workers, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) self.dataset_files = self._find_files(dataset_files) + # 根据dataset_files中的文件路径,查找数据集文件 self.usage = replace_none(usage, 'train') + # 替换None值为'train' self.task = replace_none(task, 'AFQMC') def parse(self, children=None): + # 返回cde.CLUENode return cde.CLUENode(self.dataset_files, self.task, self.usage, self.num_samples, self.shuffle_flag, self.num_shards, self.shard_id) class CoNLL2000Dataset(SourceDataset, TextBaseDataset): """ - A source dataset that reads and parses CoNLL2000 dataset. + 用于处理CoNLL2000数据集,这是一个关于自然语言处理任务的数据集。 The generated dataset has three columns: :py:obj:`[word, pos_tag, chunk_tag]`. - The tensor of column :py:obj:`word` is of the string type. - The tensor of column :py:obj:`pos_tag` is of the string type. - The tensor of column :py:obj:`chunk_tag` is of the string type. + The tensor of column :py:obj:`word` 字符串类型 + The tensor of column :py:obj:`pos_tag` 字符串类型 + The tensor of column :py:obj:`chunk_tag` 字符串类型 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Usage of this dataset, can be 'train', 'test', or 'all'. 'train' will read from - 8936 train samples, 'test' will read from 2,012 test samples, - 'all' will read from all 1,0948 samples (default=None, all samples). - num_samples (int, optional): Number of samples (rows) to read (default=None, reads the full dataset). - shuffle (Union[bool, Shuffle level], optional): Perform reshuffling of the data every epoch - (default=Shuffle.GLOBAL). - If shuffle is False, no shuffling will be performed. - If shuffle is True, performs global shuffle. - There are three levels of shuffling, desired shuffle enum defined by mindspore.dataset.Shuffle. + dataset_dir (str): 用于处理CoNLL2000数据集,这是一个关于自然语言处理任务的数据集。 + usage (str, optional): 数据集的使用情况,可选值为'train'、'test'或'all'。'train'表示读取训练集,'test'表 + 示读取测试集,'all'表示读取所有数据集。默认为None,表示读取所有数据集。 + num_samples (int, optional): 读取的样本数量,默认为None,表示读取所有样本。 + shuffle (Union[bool, Shuffle level], optional): 一个枚举值,表示是否对数据进行洗牌。 + (default=None, performs global shuffle). + shuffle值为False,不进行洗牌 + shuffle值为True,进行全局洗牌 - - Shuffle.GLOBAL: Shuffle both the files and samples, same as setting shuffle to True. + 有以下三种选项: + Shuffle.GLOBAL:全局洗牌,即洗牌文件和样本。 + Shuffle.FILES:只洗牌文件。 + Shuffle.INFILE:保留文件顺序但洗牌数据内部。 - - Shuffle.FILES: Shuffle files only. - - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing - (default=None, which means no cache is used). + num_shards (int, optional): 数据集分片数量,默认为None。当这个参数指定时,num_samples表示每个分片的最大样本数量。 + shard_id (int, optional): 当前分片ID,默认为None。当这个参数指定时,需要同时指定num_shards参数。 + num_parallel_workers (int, optional): 读取数据的worker数量,默认为None,由配置文件设置。 + cache (DatasetCache, optional): 是否使用tensor缓存服务加速数据集处理,默认为None,表示不使用缓存。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -500,51 +489,47 @@ class CoNLL2000Dataset(SourceDataset, TextBaseDataset): @check_conll2000_dataset def __init__(self, dataset_dir, usage=None, num_samples=None, shuffle=Shuffle.GLOBAL, num_shards=None, shard_id=None, num_parallel_workers=None, cache=None): + # 调用父类函数进行初始化 super().__init__(num_parallel_workers=num_parallel_workers, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) self.dataset_dir = dataset_dir self.usage = replace_none(usage, 'all') def parse(self, children=None): + # 返回cde.CONLL2000Node return cde.CoNLL2000Node(self.dataset_dir, self.usage, self.num_samples, self.shuffle_flag, self.num_shards, self.shard_id) class DBpediaDataset(SourceDataset, TextBaseDataset): """ - A source dataset that reads and parses the DBpedia dataset. + 用于读取和解析DBpedia数据集。 The generated dataset has three columns :py:obj:`[class, title, content]`. - The tensor of column :py:obj:`class` is of the string type. - The tensor of column :py:obj:`title` is of the string type. - The tensor of column :py:obj:`content` is of the string type. + The tensor of column :py:obj:`class` 字符串类型 + The tensor of column :py:obj:`title` 字符串类型 + The tensor of column :py:obj:`content` 字符串类型 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Usage of this dataset, can be 'train', 'test' or 'all'. - 'train' will read from 560,000 train samples, - 'test' will read from 70,000 test samples, - 'all' will read from all 630,000 samples (default=None, all samples). - num_samples (int, optional): The number of samples to be included in the dataset - (default=None, will include all text). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - shuffle (Union[bool, Shuffle level], optional): Perform reshuffling of the data every epoch - (default=Shuffle.GLOBAL). - If shuffle is False, no shuffling will be performed. - If shuffle is True, performs global shuffle. - There are three levels of shuffling, desired shuffle enum defined by mindspore.dataset.Shuffle. + dataset_dir (str): 数据集的根目录,用于存储所有数据文件。 + usage (str, optional): 指定要使用的数据集部分,可以是'train'(训练集)、'test'(测试集)或 + 'all'(所有数据集)。默认情况下,使用所有数据集。 + num_samples (int, optional): 指定要包含在数据集中的样本数量。如果没有指定,将包含所有样本。 + num_parallel_workers (int, optional): 指定读取数据的并发worker数量。如果没有指定,将使用配置中的worker数量。 + shuffle (Union[bool, Shuffle level], optional): 一个枚举值,表示是否对数据进行洗牌。 + (default=None, performs global shuffle). + shuffle值为False,不进行洗牌 + shuffle值为True,进行全局洗牌 - - Shuffle.GLOBAL: Shuffle both the files and samples, same as setting shuffle to True. + 有以下三种选项: + Shuffle.GLOBAL:全局洗牌,即洗牌文件和样本。 + Shuffle.FILES:只洗牌文件。 + Shuffle.INFILE:保留文件顺序但洗牌数据内部。 - - Shuffle.FILES: Shuffle files only. - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + num_shards (int, optional): 定将数据集划分为的shard数量。如果没有指定,将使用配置中的shard数量。 + shard_id (int, optional): 指定当前shard的ID,当num_shards也指定时,才能使用此参数。 + cache (DatasetCache, optional): 指定使用tensor缓存服务以加速数据集处理。如果没有指定,将不使用缓存。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -564,10 +549,8 @@ class DBpediaDataset(SourceDataset, TextBaseDataset): About DBpedia dataset: - The DBpedia dataset consists of 630,000 text samples in 14 classes, there are 560,000 samples in the train.csv - and 70,000 samples in the test.csv. - The 14 different classes represent Company, EducationaInstitution, Artist, Athlete, OfficeHolder, - MeanOfTransportation, Building, NaturalPlace, Village, Animal, Plant, Album, Film, WrittenWork. + DBpedia是一个大型语言模型训练数据集,包含630,000条文本样本,分为14个类别,包括公司、教育机构、艺术家、 + 运动员、政府官员、交通工具、建筑、自然地名、村庄、动物、植物、音乐专辑、电影和小说等。 Here is the original DBpedia dataset structure. You can unzip the dataset files into this directory structure and read by Mindspore's API. @@ -596,44 +579,44 @@ class DBpediaDataset(SourceDataset, TextBaseDataset): @check_dbpedia_dataset def __init__(self, dataset_dir, usage=None, num_samples=None, num_parallel_workers=None, shuffle=Shuffle.GLOBAL, num_shards=None, shard_id=None, cache=None): + # 调用父类构造函数 super().__init__(num_parallel_workers=num_parallel_workers, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) self.dataset_dir = dataset_dir self.usage = replace_none(usage, "all") def parse(self, children=None): + # 返回cde.DBpediaNode return cde.DBpediaNode(self.dataset_dir, self.usage, self.num_samples, self.shuffle_flag, self.num_shards, self.shard_id) class EnWik9Dataset(SourceDataset, TextBaseDataset): """ - A source dataset that reads and parses EnWik9 dataset. + 用于读取EnWik9数据集,并将其解析为文本数据。 The generated dataset has one column :py:obj:`[text]` with type string. Args: - dataset_dir (str): Path to the root directory that contains the dataset. - num_samples (int, optional): The number of samples to be included in the dataset - (default=None, will include all samples). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - shuffle (Union[bool, Shuffle level], optional): Perform reshuffling of the data every epoch - (default=True). - If shuffle is False, no shuffling will be performed. - If shuffle is True, performs global shuffle. - There are three levels of shuffling, desired shuffle enum defined by mindspore.dataset.Shuffle. + dataset_dir (str): 数据集的根目录,用于存储所有数据文件。 + usage (str, optional): 指定要使用的数据集部分,可以是'train'(训练集)、'test'(测试集)或 + 'all'(所有数据集)。默认情况下,使用所有数据集。 + num_samples (int, optional): 指定要包含在数据集中的样本数量。如果没有指定,将包含所有样本。 + num_parallel_workers (int, optional): 指定读取数据的并发worker数量。如果没有指定,将使用配置中的worker数量。 + shuffle (Union[bool, Shuffle level], optional): 一个枚举值,表示是否对数据进行洗牌。 + (default=None, performs global shuffle). + shuffle值为False,不进行洗牌 + shuffle值为True,进行全局洗牌 - - Shuffle.GLOBAL: Shuffle both the files and samples, same as setting shuffle to True. + 有以下三种选项: + Shuffle.GLOBAL:全局洗牌,即洗牌文件和样本。 + Shuffle.FILES:只洗牌文件。 + Shuffle.INFILE:保留文件顺序但洗牌数据内部。 - - Shuffle.FILES: Shuffle files only. - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing - (default=None, which means no cache is used). + num_shards (int, optional): 定将数据集划分为的shard数量。如果没有指定,将使用配置中的shard数量。 + shard_id (int, optional): 指定当前shard的ID,当num_shards也指定时,才能使用此参数。 + cache (DatasetCache, optional): 指定使用tensor缓存服务以加速数据集处理。如果没有指定,将不使用缓存。 Examples: >>> en_wik9_dataset_dir = "/path/to/en_wik9_dataset" @@ -642,13 +625,10 @@ class EnWik9Dataset(SourceDataset, TextBaseDataset): About EnWik9 dataset: - The data of EnWik9 is UTF-8 encoded XML consisting primarily of English text. It contains 243,426 article titles, - of which 85,560 are #REDIRECT to fix broken links, and the rest are regular articles. - - The data is UTF-8 clean. All characters are in the range U'0000 to U'10FFFF with valid encodings of 1 to - 4 bytes. The byte values 0xC0, 0xC1, and 0xF5-0xFF never occur. Also, in the Wikipedia dumps, - there are no control characters in the range 0x00-0x1F except for 0x09 (tab) and 0x0A (linefeed). - Linebreaks occur only on paragraph boundaries, so they always have a semantic purpose. + EnWik9数据集是一个英文的UTF-8编码的XML文件,其中包含243,426个文章标题,其中85,560个是#REDIRECT,其余的是 + regular articles。数据是干净的,所有字符都在U'0000到U'10FFFF的范围内,并且没有控制字符(除了0x09(制表符) + 和0x0A(换行符))。在Wikipedia数据集中,没有控制字符在0x00-0x1F范围内,除了0x09(制表符)和0x0A(换行符)。 + 换行符只在段落边界出现,并且具有语义目的。 You can unzip the dataset files into the following directory structure and read by MindSpore's API. @@ -673,42 +653,45 @@ class EnWik9Dataset(SourceDataset, TextBaseDataset): @check_en_wik9_dataset def __init__(self, dataset_dir, num_samples=None, num_parallel_workers=None, shuffle=True, num_shards=None, shard_id=None, cache=None): + # 调用父类构造函数 super().__init__(num_parallel_workers=num_parallel_workers, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) self.dataset_dir = dataset_dir def parse(self, children=None): + # 返回cde.EnWik9Node return cde.EnWik9Node(self.dataset_dir, self.num_samples, self.shuffle_flag, self.num_shards, self.shard_id) class IMDBDataset(MappableDataset, TextBaseDataset): """ - A source dataset that reads and parses Internet Movie Database (IMDb). + 用于读取Internet Movie Database (IMDb)数据集,并将其解析为文本数据。 The generated dataset has two columns: :py:obj:`[text, label]`. - The tensor of column :py:obj:`text` is of the string type. - The tensor of column :py:obj:`label` is of a scalar of uint32 type. + The tensor of column :py:obj:`text` 字符串类型 + The tensor of column :py:obj:`label` 无符号整数(uint32) Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Usage of this dataset, can be 'train', 'test' or 'all' - (default=None, will read all samples). - num_samples (int, optional): The number of images to be included in the dataset - (default=None, will read all samples). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, `num_samples` reflects - the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing - (default=None, which means no cache is used). + dataset_dir (str): 数据集的根目录,用于存储所有数据文件。 + usage (str, optional): 指定要使用的数据集部分,可以是'train'(训练集)、'test'(测试集)或 + 'all'(所有数据集)。默认情况下,使用所有数据集。 + num_samples (int, optional): 指定要包含在数据集中的样本数量。如果没有指定,将包含所有样本。 + num_parallel_workers (int, optional): 指定读取数据的并发worker数量。如果没有指定,将使用配置中的worker数量。 + shuffle (Union[bool, Shuffle level], optional): 一个枚举值,表示是否对数据进行洗牌。 + (default=None, performs global shuffle). + shuffle值为False,不进行洗牌 + shuffle值为True,进行全局洗牌 + + 有以下三种选项: + Shuffle.GLOBAL:全局洗牌,即洗牌文件和样本。 + Shuffle.FILES:只洗牌文件。 + Shuffle.INFILE:保留文件顺序但洗牌数据内部。 + + + num_shards (int, optional): 定将数据集划分为的shard数量。如果没有指定,将使用配置中的shard数量。 + shard_id (int, optional): 指定当前shard的ID,当num_shards也指定时,才能使用此参数。 + cache (DatasetCache, optional): 指定使用tensor缓存服务以加速数据集处理。如果没有指定,将不使用缓存。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -731,24 +714,12 @@ class IMDBDataset(MappableDataset, TextBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + * - 当sampler为None,shuffle为None时,数据集的顺序是随机的。 + 当sampler为None,shuffle为True时,数据集的顺序是随机的。 + 当sampler为None,shuffle为False时,数据集的顺序是顺序处理的。 + * - 当sampler是一个sampler对象,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler是一个sampler对象,shuffle为True时,不允许使用sampler,因为可能会导致顺序不一致。 + 当sampler是一个sampler对象,shuffle为False时,不允许使用sampler,因为可能会导致顺序不一致。 Examples: >>> imdb_dataset_dir = "/path/to/imdb_dataset_directory" @@ -761,10 +732,9 @@ class IMDBDataset(MappableDataset, TextBaseDataset): About IMDBDataset: - The IMDB dataset contains 50, 000 highly polarized reviews from the Internet Movie Database (IMDB). The dataset - was divided into 25 000 comments for training and 25 000 comments for testing, with both the training set and test - set containing 50% positive and 50% negative comments. Train labels and test labels are all lists of 0 and 1, where - 0 stands for negative and 1 for positive. + IMDB数据集包含了50, 000条极性化的评论,这些评论来自IMDB。IMDB数据集被划分为25, 000条训练评论和25, 000条测 + 试评论,其中训练集和测试集分别包含50%的正极性和50%的负极性评论。这意味着训练标签和测试标签都是一个包含0和1的 + 列表,其中0表示负极性,1表示正极性。 You can unzip the dataset files into this directory structure and read by MindSpore's API. @@ -813,6 +783,7 @@ class IMDBDataset(MappableDataset, TextBaseDataset): @check_imdb_dataset def __init__(self, dataset_dir, usage=None, num_samples=None, num_parallel_workers=None, shuffle=None, sampler=None, num_shards=None, shard_id=None, cache=None): + # 调用父类构造函数 super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) @@ -820,47 +791,45 @@ class IMDBDataset(MappableDataset, TextBaseDataset): self.usage = replace_none(usage, "all") def parse(self, children=None): + # 返回cde.IMDBNode return cde.IMDBNode(self.dataset_dir, self.usage, self.sampler) class IWSLT2016Dataset(SourceDataset, TextBaseDataset): """ - A source dataset that reads and parses IWSLT2016 datasets. + 用于读取和解析IWSLT2016数据集。 + + The generated dataset has two columns: :py:obj:`[text, translation]`. - The tensor of column :py:obj: `text` is of the string type. - The tensor of column :py:obj: `translation` is of the string type. + The tensor of column :py:obj: `text` 字符串类型 + The tensor of column :py:obj: `translation` 无符号整数(uint32) Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Acceptable usages include 'train', 'valid', 'test' and 'all' (default=None, all samples). - language_pair (sequence, optional): Sequence containing source and target language, supported values are - ('en', 'fr'), ('en', 'de'), ('en', 'cs'), ('en', 'ar'), ('fr', 'en'), ('de', 'en'), ('cs', 'en'), - ('ar', 'en') (default=('de', 'en')). - valid_set (str, optional): A string to identify validation set, when usage is valid or all, the validation set - of valid_set type will be read, supported values are 'dev2010', 'tst2010', 'tst2011', 'tst2012', 'tst2013' - and 'tst2014' (default='tst2013'). - test_set (str, optional): A string to identify test set, when usage is test or all, the test set of test_set - type will be read, supported values are 'dev2010', 'tst2010', 'tst2011', 'tst2012', 'tst2013' and 'tst2014' - (default='tst2014'). - num_samples (int, optional): Number of samples (rows) to read (default=None, reads the full dataset). - shuffle (Union[bool, Shuffle level], optional): Perform reshuffling of the data every epoch - (default=Shuffle.GLOBAL). - If shuffle is False, no shuffling will be performed. - If shuffle is True, performs global shuffle. - There are three levels of shuffling, desired shuffle enum defined by mindspore.dataset.Shuffle. + dataset_dir (str): IWSLT2016数据集的根目录。 + usage (str, optional): 可接受的使用类型,包括'train', 'valid', 'test'和'all'(默认为None,表示读取所有样本)。 + language_pair (sequence, optional): 包含源语言和目标语言的元组,支持值有('en', 'fr'), ('en', 'de'), ('en', 'cs'), + ('en', 'ar'), ('fr', 'en'), ('de', 'en'), ('cs', 'en'), ('ar', 'en')(默认为('de', 'en'))。 + valid_set (str, optional): 用于验证的集类型,当usage为'valid'或'all'时,将读取相应的验证集,支持值有'dev2010', + 'tst2010', 'tst2011', 'tst2012', 'tst2013'和'tst2014'(默认为'tst2013')。 + test_set (str, optional): 用于测试的集类型,当usage为'test'或'all'时,将读取相应的测试集,支持值有'dev2010', + 'tst2010', 'tst2011', 'tst2012', 'tst2013'和'tst2014'(默认为'tst2014')。 + num_samples (int, optional): 要读取的样本数量(默认为None,表示读取所有样本)。 + shuffle (Union[bool, Shuffle level], optional): 一个整数,表示用于读取数据的 worker 数量。如果没有提供,将使用配置中 + 的最大线程数。 + shuffle (Union[bool, Shuffle level], optional): 一个枚举值,表示是否对数据进行洗牌。 + shuffle值为False,不进行洗牌 + shuffle值为True,进行全局洗牌 + 有以下三种选项: + Shuffle.GLOBAL:既洗牌文件又洗牌样本。 + Shuffle.FILES:只洗牌文件。 + Shuffle.NONE:不洗牌。 - - Shuffle.GLOBAL: Shuffle both the files and samples, same as setting shuffle to True. - - - Shuffle.FILES: Shuffle files only. - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + num_shards (int, optional): 表示每个shard的最大样本数量。 + shard_id (int, optional): 当前shard的ID(默认为None,表示使用MindSpore的配置文件设置的shard ID)。 + num_parallel_workers (int, optional): 用于读取数据的worker数量(默认为None,表示使用MindSpore的配置文件设置 + 的worker数量)。 + cache (DatasetCache, optional): 使用tensor缓存服务加速数据处理(默认为None,表示不使用缓存)。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -875,10 +844,8 @@ class IWSLT2016Dataset(SourceDataset, TextBaseDataset): About IWSLT2016 dataset: - IWSLT is an international oral translation conference, a major annual scientific conference dedicated to all aspects - of oral translation. The MT task of the IWSLT evaluation activity constitutes a dataset, which can be publicly - obtained through the WIT3 website wit3.fbk.eu. The IWSLT2016 dataset includes translations from English to Arabic, - Czech, French, and German, and translations from Arabic, Czech, French, and German to English. + IWSLT(国际语音翻译)是一个国际语音翻译会议,是的重要年度科学会议之一, dedicates to all aspects of oral translation。IWSLT2016 + 数据集包括从英语到阿拉伯语、捷克语、法语、德语的翻译,以及从阿拉伯语、捷克语、法语和德语到英语的翻译。 You can unzip the original IWSLT2016 dataset files into this directory structure and read by MindSpore's API. After decompression, you also need to decompress the dataset to be read in the specified folder. For example, if you want @@ -936,6 +903,7 @@ class IWSLT2016Dataset(SourceDataset, TextBaseDataset): def __init__(self, dataset_dir, usage=None, language_pair=None, valid_set=None, test_set=None, num_samples=None, shuffle=Shuffle.GLOBAL, num_shards=None, shard_id=None, num_parallel_workers=None, cache=None): + # 调用父类构造函数 super().__init__(num_parallel_workers=num_parallel_workers, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) self.dataset_dir = dataset_dir @@ -945,43 +913,37 @@ class IWSLT2016Dataset(SourceDataset, TextBaseDataset): self.test_set = replace_none(test_set, 'tst2014') def parse(self, children=None): + # 返回cde.IWSLT2016Node return cde.IWSLT2016Node(self.dataset_dir, self.usage, self.language_pair, self.valid_set, self.test_set, self.num_samples, self.shuffle_flag, self.num_shards, self.shard_id) class IWSLT2017Dataset(SourceDataset, TextBaseDataset): """ - A source dataset that reads and parses IWSLT2017 datasets. + 用于读取和解析IWSLT2017(国际文摘语言翻译任务)数据集。 The generated dataset has two columns: :py:obj:`[text, translation]`. - The tensor of column :py:obj:`text` is of the string type. - The tensor of column :py:obj:`translation` is of the string type. + The tensor of column :py:obj:`text` 字符串类型 + The tensor of column :py:obj:`translation` 字符串类型 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Acceptable usages include 'train', 'valid', 'test' and 'all' (default=None, all samples). - language_pair (list, optional): List containing src and tgt language, supported values are ('en', 'nl'), + dataset_dir (str): 数据集的根目录。 + usage (str, optional): 可接受的使用方式包括'train'(训练集)、'valid'(验证集)、'test'(测试集)和 + 'all'(全部样本),默认为None,表示读取所有样本。 + language_pair (list, optional): 包含源语言和目标语言的列表,支持值有('en', 'nl'), ('en', 'de'), ('en', 'it'), ('en', 'ro'), ('nl', 'en'), ('nl', 'de'), ('nl', 'it'), ('nl', 'ro'), ('de', 'en'), ('de', 'nl'), ('de', 'it'), ('de', 'ro'), ('it', 'en'), ('it', 'nl'), ('it', 'de'), ('it', 'ro'), ('ro', 'en'), ('ro', 'nl'), ('ro', 'de'), ('ro', 'it') (default=('de', 'en')). - num_samples (int, optional): Number of samples (rows) to read (default=None, reads the full dataset). - shuffle (Union[bool, Shuffle level], optional): Perform reshuffling of the data every epoch - (default=Shuffle.GLOBAL). - If shuffle is False, no shuffling will be performed. - If shuffle is True, performs global shuffle. - There are three levels of shuffling, desired shuffle enum defined by mindspore.dataset.Shuffle. - - - Shuffle.GLOBAL: Shuffle both the files and samples, same as setting shuffle to True. - - - Shuffle.FILES: Shuffle files only. - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + num_samples (int, optional): 要读取的样本数量(行数),默认为None,表示读取整个数据集。 + shuffle (Union[bool, Shuffle level], optional): 是否进行随机洗牌,有三种级别: + Shuffle.GLOBAL:同时洗牌文件和样本。 + Shuffle.FILES:只洗牌文件。 + Shuffle.NONE:不进行洗牌。 + num_shards (int, optional): 整数,表示数据集将被划分为的份数。当这个参数被指定时,num_samples表示每个分区 + 的最大样本数。 + shard_id (int, optional): 整数,表示当前分区的ID,当num_shards也被指定时,这个参数才有效。 + num_parallel_workers (int, optional): 整数,表示用于读取数据的 worker 数量。 + cache (DatasetCache, optional): DatasetCache类的实例,用于加速数据集处理。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -995,10 +957,8 @@ class IWSLT2017Dataset(SourceDataset, TextBaseDataset): About IWSLT2017 dataset: - IWSLT is an international oral translation conference, a major annual scientific conference dedicated to all aspects - of oral translation. The MT task of the IWSLT evaluation activity constitutes a dataset, which can be publicly - obtained through the WIT3 website wit3.fbk.eu. The IWSLT2017 dataset involves German, English, Italian, Dutch, and - Romanian. The dataset includes translations in any two different languages. + IWSLT(国际语音翻译)是一个国际语音翻译会议,是针对语音翻译任务举办的重要年度科学会议。IWSLT2017数据集是一个包含德语、英语、意大 + 利语、荷兰语和罗马尼亚语的翻译数据集,其中包括两种不同的语言的翻译。 You can unzip the original IWSLT2017 dataset files into this directory structure and read by MindSpore's API. You need to decompress the dataset package in texts/DeEnItNlRo/DeEnItNlRo directory to get the DeEnItNlRo-DeEnItNlRo diff --git a/mindspore/python/mindspore/dataset/engine/datasets_user_defined.py b/mindspore/python/mindspore/dataset/engine/datasets_user_defined.py index a5cc35e5951..66a0226f275 100644 --- a/mindspore/python/mindspore/dataset/engine/datasets_user_defined.py +++ b/mindspore/python/mindspore/dataset/engine/datasets_user_defined.py @@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== + +# 定义一些类,用于帮助用户进行可扩展的数据集加载。它提供了一个基本的类结构,允许用户自定义数据集加载类,并使用GeneratorDataset来帮助加载数据。 +# 用户可以参考https://www.mindspore.cn/docs/programming_guide/en/master/dataset_loading.html#loading-user-defined-dataset来定义自己的数据集加载类 """ This file contains contains basic classes that help users do flexible dataset loading. You can define your own dataset loading class, and use GeneratorDataset to help load data. @@ -21,245 +24,399 @@ to help define your dataset loading. After declaring the dataset object, you can further apply dataset operations (e.g. filter, skip, concat, map, batch) on it. """ +# 1.builtins:Python的内置模块,提供了许多内建函数和数据类型 import builtins +# 2.math:Python的数学模块,提供了数学函数,如三角函数、指数函数等 import math +# 3.os:Python的操作系统模块,提供了操作系统相关的函数,如文件操作、进程操作等 import os +# 4.signal:Python的信号模块,提供了与信号相关的函数,如处理信号等 import signal +# 5.time:Python的时间模块,提供了时间相关的函数,如获取时间、延迟等 import time +# 6.multiprocessing:Python的多进程模块,提供了多进程相关的函数和类,如创建进程、进程间通信等 import multiprocessing from multiprocessing.util import Finalize +# 7.queue:Python的队列模块,提供了队列数据结构,如队列、优先队列等 import queue +# 8.functools:Python的函数工具模块,提供了函数装饰器、偏函数等 from functools import partial +# 9.threading:Python的线程模块,提供了线程相关的函数和类,如创建线程、线程间通信等 import threading +# 10.weakref:Python的弱引用模块,提供了弱引用对象的功能 import weakref +# 11.platform:Python的平台模块,提供了获取平台相关的信息,如操作系统、处理器等 import platform +# 12.psutil:Python的psutil模块,提供了获取系统资源使用情况的功能 import psutil +# 13.numpy:Python的numpy模块,提供了数值计算的功能,提供了多维数组对象和各种数值计算函数 import numpy as np +# 14.mindspore._c_dataengine:MindSpore的C++数据引擎模块,提供了C++实现的底层数据处理功能 import mindspore._c_dataengine as cde +# 15.mindspore.common:MindSpore的通用模块,提供了Tensor等数据类型 from mindspore.common import Tensor +# 16.mindspore:MindSpore的主模块,提供了数据集相关的类和方法 +# log:MindSpore的日志模块,提供了日志功能 from mindspore import log as logger +# 17..datasets:MindSpore的数据集模块,提供了数据集相关的类和方法 +# 18.UnionBaseDataset:MindSpore的数据集类,提供了数据集的基本结构和属性 +# 19.MappableDataset:MindSpore的可映射数据集类,提供了数据集的映射操作 +# 20.Schema:MindSpore的数据集结构类,用于定义数据集中的数据结构 +# 21.to_list:MindSpore的列表转换函数,用于将输入的参数转换为列表 +# 22._PythonMultiprocessing:MindSpore的多进程模块,提供了多进程相关的函数和类 +# 23._check_shm_usage:MindSpore的共享内存使用检查函数 from .datasets import UnionBaseDataset, MappableDataset, Schema, to_list, _PythonMultiprocessing, _check_shm_usage +# 24.samplers:MindSpore的采样器模块,提供了各种采样器,如DistributedSampler、PKSampler、RandomSampler等 from . import samplers +# 25._SharedQueue:MindSpore的共享队列类,提供了共享队列的实现 from .queue import _SharedQueue +# 26.validators(针对性):MindSpore的验证器模块,提供了各种验证器,如check_generatordataset、check_numpyslicesdataset、check_paddeddataset等 from .validators import check_generatordataset, check_numpyslicesdataset, check_paddeddataset +# 27.get_enable_shared_mem:MindSpore的共享内存配置函数,用于获取是否启用共享内存的配置 +# 28.get_prefetch_size:MindSpore的数据集预取大小配置函数,用于获取数据集的预取大小 +# 29.get_multiprocessing_timeout_interval:MindSpore的多进程超时配置函数,用于获取多进程的超时时间 +# 30.get_enable_watchdog:MindSpore的看门狗功能配置函数,用于获取是否启用看门狗功能的配置 from ..core.config import get_enable_shared_mem, get_prefetch_size, get_multiprocessing_timeout_interval, \ get_enable_watchdog +# 31.mstypelist_to_detypelist:这个函数的作用是将一个包含MindSpore Tensor类型的列表转换为包含Python字典的列表, +# 其中字典的键是MindSpore Tensor的名称,值是MindSpore Tensor的类型 from ..core.datatypes import mstypelist_to_detypelist +# 32.ExceptionHandler:这个类是一个异常处理类,用于在发生异常时捕获和处理异常 from ..core.py_util_helpers import ExceptionHandler def _iter_fn(dataset, num_samples): + # 生成器函数,用于迭代可迭代的数据集 + # 接收两个参数dataset和num_samples。 + # dataset是一个可迭代的数据集对象,num_samples是一个整数,表示需要迭代的样本数量 """ Generator function wrapper for iterable dataset. """ + # 判断num_samples是否为None或等于0 if num_samples is not None and num_samples != 0: + # 如果num_samples不是None或等于0,则创建一个迭代器ds_iter,用于遍历数据集dataset ds_iter = iter(dataset) + # 循环遍历num_samples次 for _ in range(num_samples): + # 使用try语句捕获异常 try: + # 尝试从迭代器ds_iter中获取下一个元素,并将其赋值给变量val val = next(ds_iter) + # 如果捕获到StopIteration异常,表示数据集已经遍历完成,此时退出循环 except StopIteration: return + # 将val转换为NumPy数组,并将其作为生成器的输出返回 # convert output tensors to ndarrays yield _convert_row(val) else: + # 如果num_samples为None或等于0,则直接遍历数据集dataset,并将其元素赋值给变量val for val in dataset: + # 将val转换为NumPy数组,并将其作为生成器的输出返回 # convert output tensors to ndarrays yield _convert_row(val) def _generator_fn(generator, num_samples): + # 生成器函数,用于迭代生成器函数数据集,与上一函数大同小异 + # 接收两个参数generator和num_samples。 + # generator是一个生成器函数,用于生成数据集,num_samples是一个整数,表示需要迭代的样本数量 """ Generator function wrapper for generator function dataset. """ + # 判断num_samples是否为None或等于0 if num_samples is not None and num_samples != 0: + # 如果num_samples不是None或等于0,则创建一个生成器迭代器gen_iter,用于遍历生成器函数generator生成的数据集 gen_iter = generator() + # 使用for循环遍历num_samples次 for _ in range(num_samples): + # 使用try语句捕获异常 try: + # 从生成器迭代器gen_iter中获取下一个元素 val = next(gen_iter) + # 如果捕获到StopIteration异常,表示生成器函数已经生成完毕,此时退出循环 except StopIteration: return + # 如果生成器函数已经生成完毕,则不返回任何值;否则,将val转换为NumPy数组,并将其作为生成器的输出返回 yield _convert_row(val) else: + # 如果num_samples为None或等于0,也创建一个生成器迭代器gen_iter gen_iter = generator() + # 遍历生成器函数generator生成的数据集,并将其元素赋值给变量val for val in gen_iter: + # 将val转换为NumPy数组,并将其作为生成器的输出返回 yield _convert_row(val) def _cpp_sampler_fn(sample_ids, dataset): + # 生成器函数,用于迭代使用C++实现的采样器生成的数据集 + # sample_ids是一个NumPy数组,表示需要采样的样本ID列表,dataset是一个可映射的数据集对象 """ Generator function wrapper for mappable dataset with cpp sampler. """ + # 判断sample_ids是否为NumPy数组。如果不是,则抛出RuntimeError异常,表示样本ID不是NumPy数组 if not isinstance(sample_ids, np.ndarray): raise RuntimeError("Sample IDs are not in a numpy array.") + # 判断sample_ids的大小是否为0。如果是,则抛出RuntimeError异常,表示传入了空的样本ID列表 if sample_ids.size == 0: raise RuntimeError("Sampler passed an empty sample IDs list.") + # 使用for循环遍历sample_ids中的每个元素i for i in sample_ids: + # 获取第i个元素,并将其赋值给变量val val = dataset[i] # convert output tensors to ndarrays + # 将val转换为NumPy数组并返回 yield _convert_row(val) def _cpp_sampler_fn_mp(sample_ids, sample_fn): + # 多进程生成器函数,用于迭代使用C++实现的采样器生成的数据集 """ Multiprocessing generator function wrapper for mappable dataset with cpp sampler. """ + # 判断sample_ids是否为NumPy数组。如果不是,则抛出RuntimeError异常,表示样本ID不是NumPy数组 if not isinstance(sample_ids, np.ndarray): raise RuntimeError("Sample IDs are not in a numpy array.") + # 判断sample_ids的大小是否为0。如果是,则抛出RuntimeError异常,表示传入了空的样本ID列表 if sample_ids.size == 0: raise RuntimeError("Sampler passed an empty sample IDs list.") + # 调用sample_fn对象的process方法,传入sample_ids作为参数,并将返回的结果作为生成器的输出返回 return sample_fn.process(sample_ids) def _fill_worker_indices(workers, indices, idx): + # 按照轮转顺序向工作进程的索引队列中添加索引 + # workers是一个队列对象,用于存储工作进程的引用。indices是一个列表,其中包含要添加到索引队列中的索引。idx是一个整数,表示当前正在处理的位置的索引 """ Worker index queue filler, fill worker index queue in round robin order. """ + # 首先计算工作进程的数量(num_worker) num_worker = len(workers) + # 然后在一个循环中不断向索引队列中添加索引,直到所有索引都被添加到队列中或者某个工作进程的队列被填满 while idx < len(indices): try: + # 在循环中,它使用取余数运算符(%)来确定当前要添加到哪个工作进程的队列中,然后尝试将索引添加到该队列中 workers[idx % num_worker].put(indices[idx]) idx += 1 + # 如果队列已满,它会跳过该索引并继续添加下一个索引 except queue.Full: break + # 返回索引位置 return idx def _convert_row(row): + # 将输入数据转换为NumPy数组 """ Convert Op return value to numpy """ + # 首先检查输入数据类型,确保它是一个整数、浮点数、字符串、字节、NumPy数组、MindSpore张量或这些类型的列表或元组 if isinstance(row, dict): + # 如果输入数据类型不符合要求,函数将引发一个TypeError异常 raise TypeError("Input data is expected to be " \ "int, float, str, bytes, numpy.ndarray, Tensor or list/tuple of them, but got dict.") # convert single item to np.array + # 如果输入数据是一个单个项目,函数将尝试将其转换为NumPy数组 prim_type = (int, float, str, bytes, np.ndarray, Tensor) if isinstance(row, prim_type): + # 如果输入数据是字节,函数将字节转换为NumPy数组 if isinstance(row, bytes): # bytes item = np.frombuffer(row, np.uint8) + # 如果输入数据是MindSpore张量,函数将张量转换为NumPy数组 elif isinstance(row, Tensor): # mindspore.Tensor item = row.asnumpy() + # 否则,函数将使用NumPy的array()函数将输入数据转换为NumPy数组 else: item = np.array(row, copy=False) + # 如果转换后的数组数据类型为'object' if item.dtype == 'object': + # 函数将引发一个TypeError异常,因为这种数据类型不符合要求 raise TypeError("Data type of the input or its converted Numpy array is expected to be " \ "int or float or str, but got {}.".format(item.dtype)) return tuple([item]) value = [] + # 如果输入数据是多个项目,函数将尝试将这些项目转换为NumPy数组 # convert each item to np.array + # 初始化索引 idx = 0 for x in row: + # 索引递增 idx += 1 + # 如果输入数据是字节,函数将字节转换为NumPy数组 if isinstance(x, bytes): # bytes value.append(np.frombuffer(x, np.uint8)) + # 如果输入数据是MindSpore张量,函数将张量转换为NumPy数组 elif isinstance(x, Tensor): # mindspore.Tensor value.append(x.asnumpy()) + # 如果输入数据是字典 elif isinstance(x, dict): + # 函数将引发一个TypeError异常,因为这种数据类型不符合要求 raise TypeError("The {}th item of input data is expected to be " \ "int, float, str, bytes, numpy.ndarray, Tensor, but got dict.".format(idx)) else: + # 将输入数据x转换为NumPy数组item item = np.array(x, copy=False) + # 检查转换后的数组item的数据类型是否为'object' if item.dtype == 'object': + # 如果是,则表示输入数据中存在非整数、非浮点数和非字符串类型的数据,不符合要求 raise TypeError("Data type of {}th item of the input or its converted Numpy array is expected to be " \ "int or float or str, but got {}.".format(idx, item.dtype)) + # 将数组添加到结果列表value中 value.append(item) + # 最后,将结果列表value转换为元组并返回 return tuple(value) class SamplerFn: + # 一个生成器函数的包装器,用于在多进程或多线程环境下运行生成器函数的master进程 """ Multiprocessing or multithread generator function wrapper master process. """ def __init__(self, dataset, num_worker, multi_process, max_rowsize): + # 该类是用于在多进程或多线程环境下运行生成器函数的包装器 + # 一个列表,用于存储工作进程的引用 self.workers = [] + # 工作进程的数量 self.num_worker = num_worker + # 布尔值,表示是否使用多进程 self.multi_process = multi_process + # 布尔值,表示是否需要在所有工作进程完成后调用join()方法 self.need_join = False + # 当前进程的ID self.ppid = os.getpid() + # 一个列表,用于存储工作进程的ID self.pids = [] + # 检查队列大小的间隔时间 self.check_interval = get_multiprocessing_timeout_interval() # the interval of check queue's size + # 布尔值,表示是否需要在所有工作进程完成后调用join()方法 self._final_join = True + # eof:一个事件对象,在多进程或多线程环境下通知生成器函数何时结束 # Event for end of epoch + # 如果使用多进程 if multi_process is True: + # 尝试创建一个multiprocessing.Event对象self.eof,用于在所有工作进程完成后通知主进程结束 try: self.eof = multiprocessing.Event() + # 如果创建multiprocessing.Event对象失败,则会引发一个RuntimeError异常 except Exception: + # 表示初始化multiprocessing.Event()失败,可能是由于共享内存不足,建议的共享内存大小至少为5 GB raise RuntimeError("Init multiprocessing.Event() failed, This might be caused by insufficient shm," + " and the recommended shm size is at least 5 GB.") + # 否则,使用threading.Event对象self.eof else: self.eof = threading.Event() # Create workers # get default queue size and adjust queuesize per worker if there are large # workers + # queue_size:队列大小 + # 获取预取大小(即队列大小) queue_size = get_prefetch_size() + # 然后,将队列大小与num_worker相除,以确定每个工作进程可以使用的最大队列大小 queue_size = min(queue_size, queue_size * 4 // num_worker) + # 最后,将队列大小设置为最大值为2或大于等于原始队列大小的值 queue_size = max(2, queue_size) + # 这样,可以确保每个工作进程的队列大小不超过其最大容量,同时避免因队列大小过大而导致的性能下降 + # 首先,检查multi_process和get_enable_shared_mem()的值 if multi_process and get_enable_shared_mem(): + # 调用_check_shm_usage()函数检查共享内存的使用情况 _check_shm_usage(num_worker, queue_size, max_rowsize) + # 循环创建num_worker个工作进程 for _ in range(num_worker): + # 如果multi_process为True(多线程) if multi_process is True: + # 尝试创建一个multiprocessing.Queue对象,用于在工作进程中传递数据 try: worker = _GeneratorWorkerMp(dataset, self.eof, max_rowsize, queue_size, self.ppid) + # 如果创建multiprocessing.Queue对象失败,则会引发一个RuntimeError异常 except Exception: + # 表示初始化multiprocessing.Queue()失败,可能是由于共享内存不足,建议的共享内存大小至少为5 GB raise RuntimeError("Init multiprocessing.Queue() failed, This might be caused by insufficient shm, " "and the recommended shm size is at least 5 GB.") + # daemon属性表示进程是否为守护进程。守护进程是在后台运行的进程,当主进程退出时,守护进程也会自动退出,而普通进程则需要等待其子进程全部退出后才会退出 + # 将worker.daemon属性设置为True,表示将工作进程设置为守护进程。这意味着当主进程退出时,工作进程也会自动退出,而不会影响其他进程的运行 + # 守护进程通常用于实现一些后台任务,例如监控日志、定期备份等,它们不会影响主进程的正常运行,但当主进程退出时,守护进程也会自动退出 worker.daemon = True # When multi processes fork a subprocess, the lock of the main process is copied to the subprocess, # which may cause deadlock. Therefore, the subprocess startup is performed in che initialization phase. # In this phase, the main process is not locked. + # 如果使用多进程,当主进程通过fork()创建子进程时,可能会将主进程的锁(例如线程锁或锁)复制到子进程中。这可能导致死锁,因为两个进程都试图访问相同的资源。 + # 因此,在创建子进程之前,需要将主进程的锁释放,以避免复制锁而导致死锁 + # 为了在初始化阶段释放主进程的锁,可以将子进程的启动过程放在主进程未锁定(即在start()方法中)执行 + # 这样,子进程可以独立地启动,而不会复制锁,从而避免死锁 worker.start() + + # 将工作进程的ID添加到self.pids列表中,并将self.need_join属性设置为True,表示在多进程环境下,是否需要在所有工作进程完成后,等待这些进程的退出 self.pids.append(worker.pid) self.need_join = True else: + # 否则,使用_GeneratorWorkerMt类创建一个工作进程 worker = _GeneratorWorkerMt(dataset, self.eof) + # 将worker.daemon属性设置为True,表示将工作进程设置为守护进程 worker.daemon = True + # 将工作进程添加到self.workers列表中 self.workers.append(worker) + # 调用self._launch_cleanup_worker()方法启动一个清理进程,该进程在所有工作进程完成后执行 self._launch_cleanup_worker(multi_process=multi_process) def process(self, indices): + # 启动子进程或子线程,填充索引队列,并从队列中获取结果 + # 参数indices,表示需要处理的索引列表 """ The main process, start the child process or child thread, and fill the index queue. Get the result and return. """ + # 遍历self.workers列表中的所有工作进程,检查它们的工作队列是否为空 for w in self.workers: # Check whether the queue of the subprocess is empty. + # 如果队列不为空,则抛出一个异常,表示队列中存在未处理的数据 if not w.queue_empty(): raise Exception("The queue of the subprocess is not empty.") # Start all workers + # 检查每个工作进程是否已启动。如果没有启动,则启动该进程 if not w.is_alive(): w.start() # Fill initial index queues idx_cursor = 0 + # 使用_fill_worker_indices函数填充初始索引队列 idx_cursor = _fill_worker_indices(self.workers, indices, idx_cursor) # Fetch results + # 遍历索引列表,获取每个索引对应的结果,并将其转换为适当的数据结构(例如,将元组转换为列表) for i in range(len(indices)): + # 如果eof标志为True,则停止子进程并返回 if self.eof.is_set(): self._stop_subprocess() return + # 如果使用多进程模式(self.multi_process为True),并且子进程的ID不存在,则停止子进程并返回 if self.multi_process is True and not psutil.pid_exists(self.workers[i % self.num_worker].pid): self._stop_subprocess() return # Fetch result and put index + # 尝试从工作进程的res_queue中获取结果。如果队列为空,则等待check_interval秒,然后继续尝试获取结果 try: # To avoid get timeout from queue, check the res_queue size. start_time = int(time.time()) wait_count = 1 + # 如果队列为空 while self.workers[i % self.num_worker].res_queue.empty(): time.sleep(0.1) cost_time = int(time.time()) - start_time if cost_time / self.check_interval >= wait_count: + # 等待次数递增 wait_count += 1 + # 解释:在多线程或多进程环境下,每个线程或进程都有自己的独立的内存空间,因此需要通过队列来传递数据。 + # 当从队列中获取数据时,可能会遇到队列为空的情况,此时需要等待一段时间后再次尝试获取数据。如果等待时间过长,可能会导致程序超时。 + # 因此,在获取数据时需要设置超时时间,并在超时后停止程序。 logger.warning("It has been waiting for " + str(cost_time) + "s because the multi " "thread/process of the generator generates data had been hung by gil lock. " "Check whether the source of generator has an infinite loop operation or the " @@ -267,42 +424,61 @@ class SamplerFn: "ds.config.set_multiprocessing_interval to adjust the output frequency of this " "log.") + # 从self.workers列表中索引i % self.num_worker对应的工作进程中获取结果 result = self.workers[i % self.num_worker].get() + # 如果获取到的结果是一个ExceptionHandler对象,则调用其reraise()方法重新抛出异常 if isinstance(result, ExceptionHandler): result.reraise() + # 如果获取结果超时,则停止子进程并抛出一个异常 except queue.Empty: self._stop_subprocess() raise Exception("Generator worker process timeout.") + # 如果被键盘输入打断,则停止子进程并抛出一个异常 except KeyboardInterrupt: self._stop_subprocess() raise Exception("Generator worker receives KeyboardInterrupt.") + # 如果eof标志为True,则停止子进程并返回 if self.eof.is_set(): self._stop_subprocess() return + # 如果idx_cursor小于索引列表的长度,则使用_fill_worker_indices函数填充索引队列 if idx_cursor < len(indices): idx_cursor = _fill_worker_indices(self.workers, indices, idx_cursor) + # 如果获取到结果,则将其转换为适当的数据结构,并将其添加到输出结果中 yield _convert_row(result) def _launch_cleanup_worker(self, multi_process): + # 该方法的主要目的是为了在主进程或子进程被终止时,启动一个新的进程或线程来执行清理工作 """ We need a extra thread and process if main process or subprocess was killed. Args: multi_process: Whether use multiprocess. """ + # 如果multi_process为True且系统平台不是Windows if multi_process is True and platform.system().lower() != 'windows': + # 则定义一个名为_clean_worker_func的函数,该函数将在子进程中执行清理工作 _clean_worker_func = _PythonMultiprocessing._clean_process # pylint: disable=W0212 + # 创建一个子进程,并将_clean_worker_func作为该进程的目标函数,将self.ppid和self.workers作为参数传递 self.cleaning_process = multiprocessing.Process(target=_clean_worker_func, args=(self.ppid, self.workers)) + # 将self.cleaning_process设置为守护进程,以便在主进程退出时,子进程也会自动退出 self.cleaning_process.daemon = True + # 启动守护进程 self.cleaning_process.start() + # 如果启用了看门狗功能 if get_enable_watchdog(): + # 则创建一个名为self.eot的事件,该事件将在子进程退出时被设置 self.eot = threading.Event() + # 创建一个名为self.watch_dog的线程,该线程将监控self.workers和self.cleaning_process的退出状态 self.watch_dog = threading.Thread(target=_PythonMultiprocessing._watch_dog, # pylint: disable=W0212 args=(self.eot, self.workers + [self.cleaning_process])) + # 将看门狗线程设置为守护进程 self.watch_dog.daemon = True + # 启动守护进程 self.watch_dog.start() + # 如果self._final_join为True,则创建一个名为self._jointhread的Finalize对象,该对象将确保在程序退出时,self.watch_dog线程也被终止 if self._final_join is True: self._jointhread = Finalize( self.watch_dog, self._finalize_join, @@ -311,186 +487,338 @@ class SamplerFn: ) def _stop_subprocess(self): + # 暂停子进程,该方法只允许在主进程中调用join方法 """Only the main process can call join.""" + # 如果主进程的父进程ID(self.ppid)与当前进程的ID(os.getpid())相等,并且self.need_join为True if self.need_join is True and self.ppid == os.getpid(): if hasattr(self, 'eof') and self.eof is not None and not self.eof.is_set(): + # 则设置self.eof标志,表示子进程需要停止 self.eof.set() + # 将self.need_join设置为False,表示不再需要等待子进程结束 self.need_join = False + # 遍历self.workers列表 for w in self.workers: + # 如果使用多进程模式(self.multi_process为True)且子进程的_closed属性为False if self.multi_process is True and hasattr(w, '_closed') and w._closed is False: # pylint: disable=W0212 + # 则尝试调用子进程的join方法 try: w.join() + # 如果join方法抛出异常,则忽略该异常 except Exception: # pylint: disable=W0703 # Block all errors when join continue + # 运行下面函数 self._abort_watchdog() def _abort_watchdog(self): + # 如果self.eot标志不为None,则设置self.eot标志,表示需要终止看门狗线程 if hasattr(self, 'eot') and self.eot is not None and not self.eot.is_set(): self.eot.set() + # 如果self.cleaning_process不为None,则调用_terminate_process方法来终止清理进程 if hasattr(self, 'cleaning_process') and self.cleaning_process is not None: _PythonMultiprocessing._terminate_process([self.cleaning_process]) # pylint: disable=W0212 @classmethod def _finalize_join(cls, twr, eot): + # 获取twr参数指向的线程对象 + # cls是一个类对象,twr是一个弱引用,指向一个线程对象,eot是一个事件对象,用于控制看门狗线程的终止 thread = twr() + # 如果该对象不为None,则尝试调用线程对象的join方法 if thread is not None: + # 如果eot标志不为None,则设置eot标志,表示需要终止看门狗线程 if eot is not None and not eot.is_set(): eot.set() thread.join() def __del__(self): + # 该方法在对象被销毁时调用 + # 调用_stop_subprocess方法来停止子进程 self._stop_subprocess() def _subprocess_handle(eof, signum, frame): + # 函数接受三个参数:eof、signum和frame。当子进程接收到一个终止信号(例如,SIGTERM或SIGINT)时,会调用这个函数 + # 创建一个新线程,该线程的目标函数是设置eof标志为True。这样,在主进程中就可以通过检查eof标志来判断子进程是否已经终止 threading.Thread(target=eof.set()).start() def _ignore_sigint(is_multiprocessing): + # 该函数接受一个参数is_multiprocessing。当is_multiprocessing为True时,函数会忽略SIGINT信号 """ We need to ignore sigint signal here so subprocesses can exit normally and clear. """ + # 在多进程模式下,当主进程接收到SIGINT信号时,会触发子进程的KeyboardInterrupt异常。 + # 为了避免这种问题,我们需要在子进程中忽略SIGINT信号,以便子进程可以正常退出并清理资源 if is_multiprocessing: signal.signal(signal.SIGINT, signal.SIG_IGN) def _main_process_already_exit(eof, is_multiprocessing, idx_queue, result_queue, ppid): + # 判断主进程是否已经退出 """ Judge whether main process already exit. """ + # 首先,检查eof标志是否已经设置为True,或者(在多进程模式下)平台系统不是Windows且父进程已经退出,如果是,则返回True,表示主进程已经退出 + # 如果主进程尚未退出,则检查is_multiprocessing标志。如果为True,则调用_PythonMultiprocessing.process_still_alive方法来检查父进程是否仍然存在。 + # 如果是,则返回False,表示主进程仍然存在 if eof.is_set() or (is_multiprocessing and platform.system().lower() != 'windows' and not _PythonMultiprocessing.process_still_alive(ppid)): + # 如果主进程仍然存在,则尝试取消idx_queue和result_queue的线程 Join 操作,以允许子进程正常退出 if is_multiprocessing: idx_queue.cancel_join_thread() result_queue.cancel_join_thread() return True + # 最后,返回False,表示主进程尚未退出 return False def _generator_worker_loop(dataset, idx_queue, result_queue, eof, is_multiprocessing, ppid=-1): + # 主要功能是多线程或多进程的生成器工作进程循环 """ Multithread or multiprocess generator worker process loop. """ + # 如果is_multiprocessing为True,则设置SIGTERM信号处理函数,以便在子进程中接收到SIGTERM信号时,可以调用_subprocess_handle函数 if is_multiprocessing: signal.signal(signal.SIGTERM, partial(_subprocess_handle, eof)) + # 进入一个死循环,循环内部执行以下操作 while True: + # 忽略SIGINT信号,以允许子进程正常退出 _ignore_sigint(is_multiprocessing=is_multiprocessing) # Fetch index, block + # 从idx_queue中获取一个索引,如果队列为空,则检查主进程是否已经退出 try: idx = idx_queue.get(timeout=1) except queue.Empty: + # 如果主进程已经退出,则返回 if _main_process_already_exit(eof, is_multiprocessing, idx_queue, result_queue, ppid) is True: return # If end-of-file (eof) is not set, continue to get data from idx_queue + # 否则,继续从idx_queue中获取数据 continue + # 如果获取到的索引为None,则表示队列为空 if idx is None: # When the queue is out of scope from master process, a None item can be fetched from the queue. # Upon receiving None, worker process should check if eof is set. + # 或者主进程已经退出。如果主进程尚未退出,则继续从idx_queue中获取数据 if not eof.is_set(): raise Exception("") return + # 如果eof标志已经设置 if eof.is_set(): + # 则取消idx_queue和result_queue的线程 Join 操作,然后返回 if is_multiprocessing: idx_queue.cancel_join_thread() result_queue.cancel_join_thread() return # Fetch data, any exception from __getitem__ will terminate worker and timeout master process + # 尝试获取数据,并使用dataset[idx]获取数据 try: result = dataset[idx] + # 如果获取数据时发生任何异常,则将异常传递给异常处理程序,并终止工作进程 except Exception: # pylint: disable=broad-except result = ExceptionHandler(where="in GeneratorDataset worker process") # Send data, block while True: + # 尝试将结果发送到result_queue中 try: result_queue.put(result, timeout=5) except queue.Full: + # 如果发送数据时发生queue.Full异常,则继续尝试发送数据,直到成功发送为止 if _main_process_already_exit(eof, is_multiprocessing, idx_queue, result_queue, ppid) is True: return # If eof is not set, continue to put data to result_queue continue break + # 删除已获取的索引和结果,以便释放内存 del result, idx class _GeneratorWorkerMt(threading.Thread): + # 为多线程生成器工作进程提供一些方法 """ Worker process for multi-thread Generator. """ + # 创建了两个队列idx_queue和res_queue,分别用于存储工作进程的索引和结果 def __init__(self, dataset, eof): self.idx_queue = queue.Queue(16) self.res_queue = queue.Queue(16) + # 初始化线程并传递以下参数: + # dataset:数据集对象。 + # self.idx_queue:用于存储工作进程索引的队列。 + # self.res_queue:用于存储工作进程结果的队列。 + # eof:一个用于控制工作进程退出的事件对象。 + # False:表示工作进程是单线程模式。 super().__init__(target=_generator_worker_loop, args=(dataset, self.idx_queue, self.res_queue, eof, False)) def put(self, item): + # 用于将数据项添加到索引队列中 """ Put function for worker index queue. Never block. Raise queue.Full on failure. """ + # 该方法使用queue.Queue.put_nowait方法,永远不会阻塞,如果队列为满,则会引发queue.Full异常 self.idx_queue.put_nowait(item) def get(self): + # 用于从结果队列中获取数据 """ Get function for worker result queue. Block with timeout. """ + # 该方法使用queue.Queue.get方法,会阻塞直到有数据可取或超时 return self.res_queue.get(timeout=30) def queue_empty(self): + # 用于检查索引队列和结果队列是否为空 if not self.idx_queue.empty(): + # 如果队列不为空,则会发出警告 logger.warning("idx_queue is not empty") return False if not self.res_queue.empty(): + # 如果队列不为空,则会发出警告 logger.warning("res_queue is not empty") return False return True class _GeneratorWorkerMp(multiprocessing.Process): + # 为多进程生成器工作进程提供一些方法 """ Worker process for multiprocess Generator. """ def __init__(self, dataset, eof, max_rowsize, queue_size, ppid): + # 两个队列idx_queue和res_queue,分别用于存储工作进程的索引和结果 self.idx_queue = multiprocessing.Queue(queue_size) + # 如果启用了共享内存,则使用_SharedQueue类创建 if get_enable_shared_mem(): self.res_queue = _SharedQueue(queue_size, max_rowsize=max_rowsize) + # 否则使用multiprocessing.Queue类创建 else: self.res_queue = multiprocessing.Queue(queue_size) + # 将队列的join_timeout属性设置为None,以便在get方法中等待结果时不会超时 self.idx_queue._joincancelled = True # pylint: disable=W0212 self.res_queue._joincancelled = True # pylint: disable=W0212 + # 初始化进程并传递以下参数: + # dataset:数据集对象。 + # self.idx_queue:用于存储工作进程索引的队列。 + # self.res_queue:用于存储工作进程结果的队列。 + # eof:一个用于控制工作进程退出的事件对象。 + # True:表示工作进程是多进程模式。 + # ppid:父进程的ID super().__init__(target=_generator_worker_loop, args=(dataset, self.idx_queue, self.res_queue, eof, True, ppid)) def put(self, item): + # 用于将数据项添加到索引队列中 """ Put function for worker index queue. Never block. Raise queue.Full on failure. """ + # 使用multiprocessing.Queue.put_nowait方法,永远不会阻塞,如果队列为满,则会引发queue.Full异常 self.idx_queue.put_nowait(item) def get(self): + # 用于从结果队列中获取数据 """ Get function for worker result queue. Block with timeout. """ # Relax 10s to 30s, since it sometimes will cause "Generator worker process timeout" # when we run too many iterators with infinite epoch(num_epoch=-1) + # 方法使用multiprocessing.Queue.get方法,会阻塞直到有数据可取或超时。在阻塞等待时,会尝试等待10-30s,循环过多会导致超时 return self.res_queue.get(timeout=30) def queue_empty(self): + # 用于检查索引队列和结果队列是否为空 if not self.idx_queue.empty(): + # 如果队列不为空,则会发出警告 logger.warning("idx_queue is not empty.") return False if not self.res_queue.empty(): + # 如果队列不为空,则会发出警告 logger.warning("res_queue is not empty.") return False return True def __del__(self): + # 用于在对象被销毁时删除队列和共享队列对象 # del all the Queue & SharedQueue when the iter had been deleted from ITERATORS_LIST + # 当迭代器从ITERATORS_LIST中删除时,会自动调用__del__方法,以便释放资源 del self.idx_queue del self.res_queue class GeneratorDataset(MappableDataset, UnionBaseDataset): + # 类的主要目的是创建一个数据集,该数据集从Python生成器中生成数据 + """ + 接受以下参数: + + a. source:生成数据的数据源,可以是生成器、可迭代对象或随机可访问对象。对于生成器,source()方法必须返回一个NumPy数组元组作为数据集中的每一行。对于可迭代对象,iter(source).next()方法必须返回一个NumPy数组元组作为数据集中的每一行。对于随机可访问对象,source[idx]方法必须返回一个NumPy数组元组作为数据集中的每一行。 + + b. column_names:数据集中的列名,可以是字符串或字符串列表(默认值为None)。用户必须提供 either column_names or schema。 + + c. column_types:数据集中列的数据类型,是一个数据类型列表(默认值为None)。如果提供了column_types,则会进行类型检查。 + + d. schema:JSON模式文件的路径或对象(默认值为None)。用户必须提供 either column_names or schema。如果同时提供了column_names和schema,则使用schema。 + + e. num_samples:数据集中的样本数量(默认值为None,表示所有图像)。 + + f. num_parallel_workers:并发工作进程的数量(默认值为1)。 + + g. shuffle:是否对数据集进行随机洗牌(默认值为None,表示根据输入顺序)。随机可访问输入对象时必需。 + + h. sampler:从数据集中选择样本的对象(默认值为None,表示按输入顺序)。随机可访问输入对象时必需。 + + i. num_shards:数据集分片数量(默认值为None,表示不分片)。随机可访问输入对象时必需。 + + j. shard_id:当前分片的ID(默认值为None,表示使用默认分片行为)。如果num_shards和shard_id都提供了,则必须指定shard_id。随机可访问输入对象时必需 + + 定义类的方法和属性,包括: + + a. _source:数据源对象。 + + b. _column_names:数据集中的列名列表。 + + c. _column_types:数据集中列的数据类型列表。 + + d. _schema:数据集的JSON模式对象。 + + e. _num_samples:数据集中的样本数量。 + + f. _num_parallel_workers:并发工作进程的数量。 + + g. _shuffle:数据集是否进行随机洗牌。 + + h. _sampler:从数据集中选择样本的对象。 + + i. _num_shards:数据集分片数量。 + + j. _shard_id:当前分片的ID。 + + k. _python_multiprocessing:是否使用多进程生成数据。 + + l. _max_rowsize:共享内存中数据行大小的最大值,以MB为单位。 + + 定义类的方法,包括: + + a. __init__:构造函数,参数解释如上所述。 + + b. _init_sampler:初始化采样器。 + + c. _init_schema:初始化数据集的JSON模式。 + + d. _init_column_names:初始化列名列表。 + + e. _init_column_types:初始化列的数据类型列表。 + + f. _init_num_samples:初始化样本数量。 + + g. _init_num_parallel_workers:初始化并发工作进程的数量。 + + h. _init_shuffle:初始化数据集是否进行随机洗牌。 + + i. _init_sampler:初始化从数据集中选择样本的选择器 + + """ + """ A source dataset that generates data from Python by invoking Python data source each epoch. @@ -629,18 +957,24 @@ class GeneratorDataset(MappableDataset, UnionBaseDataset): """ @check_generatordataset + # 创建一个数据集 + # 首先,使用@check_generatordataset装饰器装饰__init__方法。这个装饰器可能用于确保在创建GeneratorDataset实例时,输入参数是正确的 def __init__(self, source, column_names=None, column_types=None, schema=None, num_samples=None, num_parallel_workers=1, shuffle=None, sampler=None, num_shards=None, shard_id=None, python_multiprocessing=True, max_rowsize=6): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id) + # 如果source是builtins.zip对象,将其转换为列表,因为zip对象不支持重复迭代 if isinstance(source, builtins.zip): # Although zip is iteratable, it does not have the feature of repeated iteration, so pass it to the array. self.source = [item for item in source] else: self.source = source + # 初始化self.prepared_source为None,稍后将其设置为从C++发送的数据源 self.prepared_source = None # source to be sent to C++ + # 如果source包含网络计算操作(例如mindspore.nn、mindspore.ops或mindspore.numpy模块) if hasattr(self, 'operator_mixed') and getattr(self, 'operator_mixed') is True: + # 则将并发工作进程数设置为1,并发出警告,因为这些操作不支持多线程编译 self.num_parallel_workers = 1 logger.warning( "Input 'source' of 'GeneratorDataset' includes network computing operators like in mindspore.nn, " @@ -648,54 +982,82 @@ class GeneratorDataset(MappableDataset, UnionBaseDataset): " to replace it with python implemented operator like numpy etc. Here decrease 'num_parallel_workers' " "into 1.") + # 其属性表示是否使用多进程生成数据 self.python_multiprocessing = python_multiprocessing + # 将column_names转换为列表 self.column_names = to_list(column_names) + # 如果column_types不为None,将其转换为mstypelist_to_detypelist函数的输入列表 if column_types is not None: self.column_types = mstypelist_to_detypelist(column_types) else: + # 否则初始化为空列表 self.column_types = [] + # 如果schema不为None,将其设置为schema,并确保它是Schema对象 self.schema = schema if schema is not None: self.schema = schema + # 如果不为Schema对象 if not isinstance(schema, Schema): + # 强制转换为Schema对象 self.schema = Schema(schema) # Move get dataset_size by len from parse to here, because self.source will # lose attribution of '__len__' after deepcopy. + # 将get_dataset_size从parse函数移动到此处,因为self.source在deepcopy之后可能会丢失__len__属性 + # 初始化self.source_len self.source_len = -1 # unknown + # 如果source具有__len__属性,则将其设置为len(source) if hasattr(self.source, "__len__"): self.source_len = len(self.source) + # 设置self.max_rowsize属性,用于分配共享内存以复制数据 self.max_rowsize = max_rowsize + # 初始化self.sample_fn为None,稍后将其设置为采样函数 self.sample_fn = None def __deepcopy__(self, memodict): + # 用于实现对象的深拷贝。深拷贝是指在拷贝对象时,复制对象的所有属性以及对象的内部状态 + # 如果self已经在memodict中,直接返回memodict[id(self)] if id(self) in memodict: return memodict[id(self)] + # 进行安全深拷贝,这是通过调用object.__deepcopy__方法实现的。exclude参数指定要排除的属性,在这里我们排除了source和__transfer_dataset__属性 new_op = self.__safe_deepcopy__(memodict, exclude=("source", "__transfer_dataset__")) + # 初始化sample_fn为None sample_fn = None + # 如果new_op.sampler不为None且self.source具有__getitem__方法,说明self.source是一个随机可访问对象 if new_op.sampler is not None and hasattr(self.source, "__getitem__"): # The reason why there is a try catch here is because when the new op is being constructed with shared # memory enabled, there will be an exception thrown if there is not enough shared memory available + # 如果self.source_len为-1,抛出RuntimeError,因为尝试构造一个随机访问数据集,__len__方法是必要的 if self.source_len == -1: raise RuntimeError("Attempt to construct a random access dataset, '__len__' method is required!") try: + # 如果new_op.num_parallel_workers大于1 if new_op.num_parallel_workers > 1: + # 调用self.__validate_memory_usage()验证内存使用情况 self.__validate_memory_usage() - + # 创建一个SamplerFn对象(一个生成器函数的包装器,用于在多进程或多线程环境下运行生成器函数的master进程) sample_fn = SamplerFn(self.source, new_op.num_parallel_workers, self.python_multiprocessing, self.max_rowsize) + # 将new_op.prepared_source更新,该函数接受sample_ids作为参数,并调用_cpp_sampler_fn_mp函数 new_op.prepared_source = (lambda sample_ids: _cpp_sampler_fn_mp(sample_ids, sample_fn)) else: + # 否则,将new_op.prepared_source更新,该函数接受sample_ids作为参数,并调用_cpp_sampler_fn函数 new_op.prepared_source = (lambda sample_ids: _cpp_sampler_fn(sample_ids, self.source)) + # 将new_op.sample_fn设置为创建的SamplerFn对象 new_op.sample_fn = sample_fn + # 捕获RuntimeError异常并将其转换为Exception异常。这是因为在某些情况下,RuntimeError异常可能由C++代码引发, + # 而Python解释器无法识别这些异常。在这种情况下,将RuntimeError转换为Exception可以确保在Python代码中正确处理异常 except RuntimeError as e: + # 如果捕获到RuntimeError异常,使用str(e)将其转换为字符串 + # 使用raise Exception(str(e))重新引发Exception异常,其中包含原始的RuntimeError消息 raise Exception(str(e)) else: try: + # 如果new_op.sampler为None,将new_op.sampler设置为None,并将new_op.sample_fn设置为None new_op.sampler = None new_op.sample_fn = sample_fn new_op.source_len = min(new_op.source_len, @@ -703,70 +1065,104 @@ class GeneratorDataset(MappableDataset, UnionBaseDataset): iter(self.source) except TypeError: # Use generator function if input callable + # 如果self.source既不是随机可访问对象,也没有__iter__方法,则将new_op.prepared_source设置为一个新的函数,该函数不带参数,并调用_generator_fn函数 new_op.prepared_source = (lambda: _generator_fn(self.source, new_op.num_samples)) else: # Use iterator function if input is iterable # Random accessible input is also iterable + # 如果self.source不是随机可访问对象,但具有__iter__方法,则将new_op.prepared_source设置为一个新的函数,该函数接受一个参数,并调用_iter_fn函数 new_op.prepared_source = (lambda: _iter_fn(self.source, new_op.num_samples)) + # 返回新的new_op对象 return new_op + # 检查GeneratorDataset对象的sampler是否已设置为随机洗牌 def is_shuffled(self): + # 返回self.sampler.is_shuffled()的结果,即检查sampler对象是否设置为随机洗牌。 + # self.sampler是GeneratorDataset对象的一个属性,它存储着用于从数据源中选择样本的Sampler对象。 + # is_shuffled方法调用self.sampler.is_shuffled()来检查Sampler对象是否设置了随机洗牌。如果设置了随机洗牌,该方法返回True,否则返回False return self.sampler.is_shuffled() + # 检查GeneratorDataset对象的sampler是否已设置为分片 def is_sharded(self): + # 返回self.sampler.is_sharded()的结果,即检查sampler对象是否设置为分片。 + # is_sharded方法调用self.sampler.is_sharded()来检查Sampler对象是否设置了分片。如果设置了分片,该方法返回True,否则返回False return self.sampler.is_sharded() def parse(self, children=None): + # 用于将数据集对象转换为C++数据集对象的方法 + # 如果self.schema为None if self.schema is None: + # 则返回一个新的cde.GeneratorNode对象(cde.GeneratorNode是一个C++类,表示一个生成器节点,用于生成数据) return cde.GeneratorNode(self.prepared_source, self.column_names, self.column_types, self.source_len, self.sampler, self.num_parallel_workers) schema = self.schema + # 如果self.schema不是None,则检查self.schema是否为Schema对象 if isinstance(schema, Schema): + # 如果是,则将self.schema转换为C++格式的schema schema = self.schema.cpp_schema + # 最后,返回一个新的cde.GeneratorNode对象(cde.GeneratorNode是一个C++类,表示一个生成器节点,用于生成数据) + # 这里将schema作为参数传递给cde.GeneratorNode构造函数,而不是将self.schema直接作为属性传递,因为self.schema不是C++对象,而是Python对象 return cde.GeneratorNode(self.prepared_source, schema, self.source_len, self.sampler, self.num_parallel_workers) def __validate_memory_usage(self): + # 用于检查在多线程模式下内存使用情况 """ Check memory usage when mulit-processing mode, when 85% prompt warning and 100% raise error. """ + # 如果self.python_multiprocessing为True(多线程模式) if self.python_multiprocessing: # if use num_parallel_workers is to large when python_multiprocessing=True which would cause # OOM error get the num_shards + # 以下代码获取num_shards valid_num_shards = 1 + # 如果使用DistributedSampler,则使用num_shards传入 if isinstance(self.sampler, samplers.DistributedSampler): valid_num_shards = self.sampler.num_shards + # 否则当self.num_shards不为空时,使用self.num_shards传入 elif self.num_shards is not None: valid_num_shards = self.num_shards # get process memory usage + # 获取当前进程的内存使用情况,使用psutil.Process(os.getpid()).memory_info().rss process = psutil.Process(os.getpid()) + # 获取系统剩余内存,使用psutil.virtual_memory().free process_memory = process.memory_info().rss + # 计算可能使用的总内存,乘以self.num_parallel_workers、valid_num_shards和process_memory sys_memory_free = psutil.virtual_memory().free - total_memory_maybe_used = process_memory * self.num_parallel_workers * valid_num_shards + + # 如果总内存使用率超过85%,则执行以下操作 if total_memory_maybe_used / sys_memory_free > 0.85: + # 1.计算可以使用的最大并发进程数,使用`math.floor(sys_memory_free * 0.85 / valid_num_shards / process_memory)` valid_num_worker = math.floor(sys_memory_free * 0.85 / valid_num_shards / process_memory) + # 2.如果最大并发进程数小于等于0,则将最大并发进程数设置为1 valid_num_worker = 1 if valid_num_worker <= 0 else valid_num_worker + # 3.生成一个包含建议并发进程数的警告信息,使用`info`变量 info = "GeneratorDataset's num_parallel_workers: {} is too large which may cause a lot of memory " \ "occupation (>85%) or out of memory(OOM) during multiprocessing. Therefore, it is recommended " \ "to reduce num_parallel_workers to {} or smaller.".format(self.num_parallel_workers, valid_num_worker) + # 4.使用`logger.warning(info)`输出警告信息 logger.warning(info) class _NumpySlicesDataset: + # 主要用于处理Python数据,每次返回一行 """ Mainly for dealing with several kinds of formats of Python data, and return one row each time. """ def __init__(self, data, column_list=None): + # 首先,定义了self.column_list属性,将其初始化为None self.column_list = None # Convert dict data into tuple + # 然后,检查data是否为字典类型。如果是字典类型,则使用self.process_dict(data)方法将其转换为元组类型 if isinstance(data, dict): data = self.process_dict(data) + # 如果data已经是元组类型,则将其拆分为多个数组,并将它们添加到self.data元组中 if isinstance(data, tuple): self.data = () data_len = len(data) @@ -776,25 +1172,38 @@ class _NumpySlicesDataset: self.data = (np.array(data),) # check whether the data length in each column is equal + # 获取数据的长度 data_len = [len(data_item) for data_item in self.data] + # 检查self.data元组中每个数组的长度是否相等 if data_len[1:] != data_len[:-1]: + # 如果不相等,则抛出ValueError异常 raise ValueError("Data length in each column is not equal.") # Init column_name + # 如果column_list参数不为None if column_list is not None: + # 则将其赋值给self.column_list属性 self.column_list = column_list + # 如果column_list为None elif self.column_list is None: + # 则使用默认值创建一个column_list,其中包含column_0、column_1等 self.column_list = [] + # 获取数据组长度 column_num = len(self.data) + # 插入数据 for i in range(column_num): self.column_list.append("column_" + str(i)) def __getitem__(self, index): + # 首先,使用列表推导式将self.data元组中的每个数组切片,并将它们存储在data_row列表中 data_row = [d[index, ...] for d in self.data] + # 将data_row列表转换为元组类型 data_res = tuple(data_row) + # 并返回 return data_res def __len__(self): + # 返回self.data[0]的长度,即第一个数组的长度 return len(self.data[0]) def process_dict(self, input_data): @@ -802,27 +1211,40 @@ class _NumpySlicesDataset: Convert the dict like data into tuple format, when input is a tuple of dicts then compose it into a dict first. """ # Convert pandas like dict(has "values" column) into General dict + # 首先,使用list(input_data.keys())获取字典input_data的键列表 data_keys = list(input_data.keys()) + # 然后,使用字典的第一个键data_keys[0]获取字典的第一个值data_col data_col = input_data[data_keys[0]] + # 如果data_col具有values属性 if hasattr(data_col, "values"): + # 则创建一个新的字典new_dict new_dict = {} + # 将字典input_data中的键值对分别弹出,并将其添加到new_dict中 for key in data_keys: item1 = input_data.pop(key) new_dict[key] = item1.values + # 这样,字典input_data就被转换为了一个具有values属性的字典 input_data = new_dict # Convert the data in dict into tuple + # 如果字典input_data中的数据是字典类型,则将其转换为元组类型 data = () + # 首先,获取字典的键列表keys keys = list(input_data.keys()) self.column_list = keys for key in keys: + # 然后遍历keys中的每个键,将字典中的相应值转换为列表 value = input_data[key] + # 并将这些列表添加到data元组中 data = data + (list(value),) + # 最后,返回data元组 return data class NumpySlicesDataset(GeneratorDataset): + # NumpySlicesDataset类定义了一个用于处理Python数据的生成器,它继承自GeneratorDataset类, + # 并定义了三个属性:self.data、self.column_names和self.column_types,用于存储用户定义的Python数据、列名和列类型 """ Creates a dataset with given data slices, mainly for loading Python data into dataset. @@ -906,16 +1328,20 @@ class NumpySlicesDataset(GeneratorDataset): >>> dataset = ds.NumpySlicesDataset(data=dict(df), shuffle=False) """ + # 使用@check_numpyslicesdataset装饰器定义了一个名为__init__的方法,该方法将在创建NumpySlicesDataset对象时自动调用 @check_numpyslicesdataset def __init__(self, data, column_names=None, num_samples=None, num_parallel_workers=1, shuffle=None, sampler=None, num_shards=None, shard_id=None): + # 首先,使用_NumpySlicesDataset类创建一个名为dataset的对象,并将data和column_names作为参数传递 dataset = _NumpySlicesDataset(data, column_names) + # 然后,将dataset的column_list属性作为column_names参数传递给GeneratorDataset类的构造函数,并将其他参数作为默认值传递 super().__init__(dataset, column_names=dataset.column_list, num_samples=num_samples, num_parallel_workers=num_parallel_workers, shuffle=shuffle, sampler=sampler, num_shards=num_shards, shard_id=shard_id) class _PaddedDataset: + # 用于将用户提供的填充数据组合到初始数据集中 """ Mainly for combining false samples provided by users into a dataset. @@ -924,17 +1350,24 @@ class _PaddedDataset: """ def __init__(self, padded_samples): + # self.column_names属性用于存储填充数据的列名,它是一个列表,每个元素都是一个字符串,表示数据集中的列名。它通过从第一个填充样本中提取列名得到 self.column_names = list(padded_samples[0].keys()) + # self.padded_samples属性用于存储填充数据,它是一个列表,每个元素都是一个字典,表示一个填充样本。它通过接收用户提供的padded_samples参数得到 self.padded_samples = padded_samples + # 用于返回一个填充样本 def __getitem__(self, item): + # 它接收一个索引item作为参数,然后使用生成器表达式将填充样本的每个列值迭代出来,并返回一个元组 return (self.padded_samples[item][key] for key in self.column_names) + # 用于返回填充数据的样本数 def __len__(self): + # 它直接返回len(self.padded_samples),即填充数据的样本数 return len(self.padded_samples) class PaddedDataset(GeneratorDataset): + # 它继承自GeneratorDataset类。PaddedDataset类的主要作用是将用户提供的填充数据组合到初始数据集中,以便在后续的数据处理过程中使用 """ Creates a dataset with filler data provided by user. @@ -954,9 +1387,15 @@ class PaddedDataset(GeneratorDataset): >>> dataset = ds.PaddedDataset(padded_samples=data) """ + # 首先,使用@check_paddeddataset装饰器定义了一个名为__init__的方法,该方法将在创建PaddedDataset对象时自动调用 @check_paddeddataset def __init__(self, padded_samples): + # 它接收一个参数padded_samples,表示用户提供的填充数据。 dataset = _PaddedDataset(padded_samples) + # 然后,将dataset的column_names属性作为column_names参数传递给GeneratorDataset类的构造函数,并将num_shards、shard_id和shuffle参数设置为默认值 + # 调用super().__init__(),将dataset对象传递给GeneratorDataset类的构造函数,从而创建一个GeneratorDataset对象 super().__init__(dataset, column_names=dataset.column_names, num_shards=None, shard_id=None, shuffle=False) + # 名为_dataset_size的属性,用于存储填充数据的样本数。它通过调用_PaddedDataset类的__len__方法得到 self._dataset_size = len(dataset.padded_samples) + # 用于存储填充数据,它与传入的padded_samples参数相同 self.padded_samples = padded_samples diff --git a/mindspore/python/mindspore/dataset/engine/datasets_vision.py b/mindspore/python/mindspore/dataset/engine/datasets_vision.py index e8e772597a0..790d31f01ab 100644 --- a/mindspore/python/mindspore/dataset/engine/datasets_vision.py +++ b/mindspore/python/mindspore/dataset/engine/datasets_vision.py @@ -47,98 +47,132 @@ from ..core.validator_helpers import replace_none class _Caltech101Dataset: """ Mainly for loading Caltech101 Dataset, and return two rows each time. + 主要用于加载Caltech101数据集,每次返回两个数据行。 """ + # 初始化函数,用于初始化数据集 def __init__(self, dataset_dir, target_type="category", decode=False): + # 获取数据集的路径 self.dataset_dir = os.path.realpath(dataset_dir) + # 获取图像文件夹的路径 self.image_dir = os.path.join(self.dataset_dir, "101_ObjectCategories") + # 获取标注文件夹的路径 self.annotation_dir = os.path.join(self.dataset_dir, "Annotations") + # 获取目标类型 self.target_type = target_type + # 根据目标类型设置列名 if self.target_type == "category": self.column_names = ["image", "category"] elif self.target_type == "annotation": self.column_names = ["image", "annotation"] else: self.column_names = ["image", "category", "annotation"] + # 设置是否解码 self.decode = decode + # 获取类别列表 self.classes = sorted(os.listdir(self.image_dir)) + # 如果类别列表中有BACKGROUND_Google,则移除 if "BACKGROUND_Google" in self.classes: self.classes.remove("BACKGROUND_Google") + # 设置类别映射 name_map = {"Faces": "Faces_2", "Faces_easy": "Faces_3", "Motorbikes": "Motorbikes_16", "airplanes": "Airplanes_Side_2"} + # 获取标注类别列表 self.annotation_classes = [name_map[class_name] if class_name in name_map else class_name for class_name in self.classes] + # 初始化图像索引和图像标签 self.image_index = [] self.image_label = [] + # 遍历类别列表 for i, image_class in enumerate(self.classes): + # 获取类别子文件夹的路径 sub_dir = os.path.join(self.image_dir, image_class) + # 如果子文件夹不存在或者不可读,则跳过 if not os.path.isdir(sub_dir) or not os.access(sub_dir, os.R_OK): continue + # 获取类别子文件夹中的图像数量 num_images = len(os.listdir(sub_dir)) + # 获取图像索引 self.image_index.extend(range(1, num_images + 1)) + # 获取图像标签 self.image_label.extend(num_images * [i]) + # 定义__getitem__函数,用于获取指定索引的图像和标签 def __getitem__(self, index): + # 获取图像文件路径 image_file = os.path.join(self.image_dir, self.classes[self.image_label[index]], "image_{:04d}.jpg".format(self.image_index[index])) + # 如果图像文件不存在或者没有权限,则抛出异常 if not os.path.exists(image_file): raise ValueError("The image file {} does not exist or permission denied!".format(image_file)) + # 如果decode为True,则使用Image.open函数读取图像,并转换为RGB格式 if self.decode: image = np.asarray(Image.open(image_file).convert("RGB")) + # 如果decode为False,则从文件中读取图像 else: image = np.fromfile(image_file, dtype=np.uint8) + # 如果target_type为category,则返回图像和标签 if self.target_type == "category": return image, self.image_label[index] + # 获取标注文件路径 annotation_file = os.path.join(self.annotation_dir, self.annotation_classes[self.image_label[index]], "annotation_{:04d}.mat".format(self.image_index[index])) + # 如果标注文件不存在或者没有权限,则抛出异常 if not os.path.exists(annotation_file): raise ValueError("The annotation file {} does not exist or permission denied!".format(annotation_file)) + # 读取标注文件 annotation = loadmat(annotation_file)["obj_contour"] + # 如果target_type为annotation,则返回图像、标签和标注 if self.target_type == "annotation": return image, annotation + # 否则,返回图像、标签和标注 return image, self.image_label[index], annotation def __len__(self): + # 返回数据集中的图像数量 return len(self.image_index) class Caltech101Dataset(GeneratorDataset): """ A source dataset that reads and parses Caltech101 dataset. + 用于读取和解析Caltech101数据集 The columns of the generated dataset depend on the value of `target_type`. When `target_type` is 'category', the columns are :py:obj:`[image, category]`. When `target_type` is 'annotation', the columns are :py:obj:`[image, annotation]`. When `target_type` is 'all', the columns are :py:obj:`[image, category, annotation]`. + 生成的数据集的列数取决于target_type的值。 + 当target_type为'category'时,列是[image, category]; + 当target_type为'annotation'时,列是[image, annotation]; + 当target_type为'all'时,列是[image, category, annotation]。 The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`category` is of the uint32 type. The tensor of column :py:obj:`annotation` is a 2-dimensional ndarray that stores the contour of the image and consists of a series of points. + 生成的数据集中的图像张量是uint8类型, + 类别标签张量是uint32类型, + 注释张量是一个2维ndarray,其中存储了图像的轮廓,由一系列点组成。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. This root directory contains two - subdirectories, one is called 101_ObjectCategories, which stores images, - and the other is called Annotations, which stores annotations. - target_type (str, optional): Target of the image. If `target_type` is 'category', return category represents - the target class. If `target_type` is 'annotation', return annotation. - If `target_type` is 'all', return category and annotation (default=None, means 'category'). - num_samples (int, optional): The number of images to be included in the dataset - (default=None, all images). - num_parallel_workers (int, optional): Number of workers to read the data (default=1). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - decode (bool, optional): Whether or not to decode the images after reading (default=False). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, `num_samples` reflects - the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. + dataset_dir (str): Caltech101数据集的根目录,其中包含两个子目录,一个是名为101_ObjectCategories的 + 图像目录,另一个是名为Annotations的注释目录。 + target_type (str, optional): 图像的目标类型。如果target_type为'category',则返回表示目标类别的标签。 + 如果target_type为'annotation',则返回注释。如果target_type为'all',则返回类别和注释(默认为None, + 表示返回类别)。 + num_samples (int, optional): 数据集中的图像数量(默认为None,表示所有图像)。 + num_parallel_workers (int, optional): 读取数据的工作线程数量(默认为1)。 + shuffle (bool, optional): 是否对数据集进行随机洗牌(默认为None,根据参数顺序进行排序)。 + decode (bool, optional): 是否在读取图像后进行解码(默认为False)。 + sampler (Sampler, optional): 从数据集中选择样本的对象。 + num_shards (int, optional): 数据集将被分割成几个部分(默认为None)。当此参数指定时,num_samples表示 + 每个分片的最大样本数。 + shard_id (int, optional): 在num_shards个分片中,此参数表示当前分片的ID(默认为None)。此参数只能在 + 指定num_shards时使用。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -161,24 +195,12 @@ class Caltech101Dataset(GeneratorDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> caltech101_dataset_directory = "/path/to/caltech101_dataset_directory" @@ -195,7 +217,10 @@ class Caltech101Dataset(GeneratorDataset): Most categories have about 50 images. Collected in September 2003 by Fei-Fei Li, Marco Andreetto, and Marc 'Aurelio Ranzato. The size of each image is roughly 300 x 200 pixels. The official provides the contour data of each object in each picture, which is the annotation. - + Caltech101Dataset中包含了101个类别,每个类别有40到800张图片。大部分类别有50张图片。 + 图片的大小约为300x200像素。 + 图片的轮廓数据(即注释)是由Fei-Fei Li、Marco Andreetto和Marc 'Aurelio Ranzato在2003年收集的。 + .. code-block:: . @@ -236,16 +261,23 @@ class Caltech101Dataset(GeneratorDataset): """ @check_caltech101_dataset + # 初始化函数,传入参数:数据集路径,目标类型,样本数,并行工作数,洗牌,解码,采样器,分片数,分片id def __init__(self, dataset_dir, target_type=None, num_samples=None, num_parallel_workers=1, shuffle=None, decode=False, sampler=None, num_shards=None, shard_id=None): + # 初始化数据集路径 self.dataset_dir = dataset_dir + # 初始化目标类型,如果没有传入,则默认为category self.target_type = replace_none(target_type, "category") + # 初始化解码,如果没有传入,则默认为False self.decode = replace_none(decode, False) + # 初始化数据集 dataset = _Caltech101Dataset(self.dataset_dir, self.target_type, self.decode) + # 调用父类初始化函数 super().__init__(dataset, column_names=dataset.column_names, num_samples=num_samples, num_parallel_workers=num_parallel_workers, shuffle=shuffle, sampler=sampler, num_shards=num_shards, shard_id=shard_id) + # 定义一个函数,用于获取类别索引 def get_class_indexing(self): """ Get the class index. @@ -253,6 +285,7 @@ class Caltech101Dataset(GeneratorDataset): Returns: dict, a str-to-int mapping from label name to index. """ + # 创建一个字典,用于存储类别名称和索引的映射 class_dict = {'Faces': 0, 'Faces_easy': 1, 'Leopards': 2, 'Motorbikes': 3, 'accordion': 4, 'airplanes': 5, 'anchor': 6, 'ant': 7, 'barrel': 8, 'bass': 9, 'beaver': 10, 'binocular': 11, 'bonsai': 12, 'brain': 13, 'brontosaurus': 14, 'buddha': 15, 'butterfly': 16, 'camera': 17, 'cannon': 18, @@ -271,35 +304,34 @@ class Caltech101Dataset(GeneratorDataset): 'stegosaurus': 87, 'stop_sign': 88, 'strawberry': 89, 'sunflower': 90, 'tick': 91, 'trilobite': 92, 'umbrella': 93, 'watch': 94, 'water_lilly': 95, 'wheelchair': 96, 'wild_cat': 97, 'windsor_chair': 98, 'wrench': 99, 'yin_yang': 100} + # 返回类别字典 return class_dict class Caltech256Dataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses Caltech256 dataset. + 用于读取和解析Caltech256数据集。 The generated dataset has two columns: :py:obj:`[image, label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`label` is of the uint32 type. + 生成的数据集具有两个列:[image, label]。 + image列的类型为uint8, + label列的类型为uint32。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - num_samples (int, optional): The number of images to be included in the dataset - (default=None, all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - decode (bool, optional): Whether or not to decode the images after reading (default=False). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, `num_samples` reflects - the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str): Caltech256数据集的根目录。 + num_samples (int, optional): 数据集中的图像数量(默认为None,表示所有图像)。 + num_parallel_workers (int, optional): 读取数据的工作线程数量(默认为None,在配置中设置)。 + shuffle (bool, optional): 是否对数据集进行随机洗牌(默认为None,根据参数顺序进行排序)。 + decode (bool, optional): 是否在读取图像后进行解码(默认为False)。 + sampler (Sampler, optional): 从数据集中选择样本的对象。 + num_shards (int, optional): 数据集将被分割成几个部分(默认为None)。当此参数指定时,num_samples + 表示每个分片的最大样本数。 + shard_id (int, optional): 在num_shards个分片中,此参数表示当前分片的ID(默认为None)。此参数只能 + 在指定num_shards时使用。 + cache (DatasetCache, optional): 使用tensor缓存服务加速数据处理(默认为None,表示不使用缓存)。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -322,24 +354,12 @@ class Caltech256Dataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> caltech256_dataset_dir = "/path/to/caltech256_dataset_directory" @@ -352,6 +372,9 @@ class Caltech256Dataset(MappableDataset, VisionBaseDataset): Caltech-256 is an object recognition dataset containing 30,607 real-world images, of different sizes, spanning 257 classes (256 object classes and an additional clutter class). Each class is represented by at least 80 images. The dataset is a superset of the Caltech-101 dataset. + Caltech-256是一个包含30,607张图片的物体识别数据集,这些图片的大小不同,涵盖了257个类别(256个物体类别和一个散点类别)。 + 每个类别至少包含80张图片。 + Caltech-256是Caltech-101数据集的超集。 .. code-block:: @@ -389,15 +412,20 @@ class Caltech256Dataset(MappableDataset, VisionBaseDataset): """ @check_caltech256_dataset + # 初始化函数,传入参数:数据集目录,样本数,并行工作数,洗牌,解码,采样器,分片数,分片id,缓存 def __init__(self, dataset_dir, num_samples=None, num_parallel_workers=None, shuffle=None, decode=False, sampler=None, num_shards=None, shard_id=None, cache=None): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集路径 self.dataset_dir = dataset_dir + # 初始化解码参数 self.decode = replace_none(decode, False) + # 定义parse函数,用于解析children参数 def parse(self, children=None): + # 返回一个Caltech256Node实例,参数为self.dataset_dir, self.decode, self.sampler return cde.Caltech256Node(self.dataset_dir, self.decode, self.sampler) @@ -405,30 +433,30 @@ class CelebADataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses CelebA dataset. Only support to read `list_attr_celeba.txt` currently, which is the attribute annotations of the dataset. + 取和解析CelebA数据集。 + 只支持读取list_attr_celeba.txt,它是CelebA数据集的属性标注。 The generated dataset has two columns: :py:obj:`[image, attr]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`attr` is of the uint32 type and one hot encoded. + 生成的数据集具有两个列:[image, attr]。 + image列的类型为uint8, + attr列的类型为uint32且为one hot编码。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - num_parallel_workers (int, optional): Number of workers to read the data (default=None, will use value set in - the config). - shuffle (bool, optional): Whether to perform shuffle on the dataset (default=None). - usage (str, optional): Specify the 'train', 'valid', 'test' part or 'all' parts of dataset - (default= 'all', will read all samples). - sampler (Sampler, optional): Object used to choose samples from the dataset (default=None). - decode (bool, optional): decode the images after reading (default=False). - extensions (list[str], optional): List of file extensions to be included in the dataset (default=None). - num_samples (int, optional): The number of images to be included in the dataset - (default=None, will include all images). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, `num_samples` reflects - the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str): CelebA数据集的根目录。 + num_parallel_workers (int, optional): 读取数据的工作线程数量(默认为None,使用配置中的值)。 + shuffle (bool, optional): 是否对数据集进行随机洗牌(默认为None)。 + usage (str, optional): 指定数据集的'train'、'valid'、'test'部分或'all'部分(默认为'all',将读取所有样本)。 + sampler (Sampler, optional): 从数据集中选择样本的对象。 + decode (bool, optional): 在读取图像后是否进行解码(默认为False)。 + extensions (list[str], optional): 包含在数据集中的文件扩展名列表(默认为None)。 + num_samples (int, optional): 数据集中的图像数量(默认为None,表示所有图像)。 + num_shards (int, optional): 数据集将被分割成几个部分(默认为None)。当此参数指定时,num_samples表示每个 + 分片的最大样本数。 + shard_id (int, optional): 在num_shards个分片中,此参数表示当前分片的ID(默认为None)。此参数只能在指定num_shards + 时使用。 + cache (DatasetCache, optional): 使用tensor缓存服务加速数据处理(默认为None,表示不使用缓存)。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -451,24 +479,12 @@ class CelebADataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> celeba_dataset_dir = "/path/to/celeba_dataset_directory" @@ -493,6 +509,12 @@ class CelebADataset(MappableDataset, VisionBaseDataset): The dataset can be employed as the training and test sets for the following computer vision tasks: face attribute recognition, face detection, landmark (or facial part) localization, and face editing & synthesis. + CelebA是一个大型人脸属性数据集,其中包含超过20万张名人图像,每张图像都有40个属性标注。 + 该数据集涵盖了大型姿态变化和背景杂乱。CelebA具有大量的多样性、大规模和丰富的注释,包括 + -10,177个身份。 + -202,599张人脸图像。 + -5个关键点位置和40个二进制属性标注。 + CelebA可以作为人脸属性识别和人脸图像分类等计算机视觉任务的学习和测试数据集。 Original CelebA dataset structure: @@ -547,21 +569,31 @@ class CelebADataset(MappableDataset, VisionBaseDataset): """ @check_celebadataset + # 初始化函数,用于初始化数据集 def __init__(self, dataset_dir, num_parallel_workers=None, shuffle=None, usage='all', sampler=None, decode=False, extensions=None, num_samples=None, num_shards=None, shard_id=None, cache=None): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集路径 self.dataset_dir = dataset_dir + # 初始化解码参数 self.decode = replace_none(decode, False) + # 初始化扩展参数 self.extensions = replace_none(extensions, []) + # 初始化使用参数 self.usage = replace_none(usage, "all") + # 定义parse函数,用于解析children参数 def parse(self, children=None): + # 如果usage不是all,则获取dataset_dir if self.usage != "all": dataset_dir = os.path.realpath(self.dataset_dir) + # 获取partition_file的路径 partition_file = os.path.join(dataset_dir, "list_eval_partition.txt") + # 如果partition_file不存在,则抛出异常 if os.path.exists(partition_file) is False: raise RuntimeError("Partition file can not be found when usage is not 'all'.") + # 返回CelebANode对象 return cde.CelebANode(self.dataset_dir, self.usage, self.sampler, self.decode, self.extensions) @@ -570,31 +602,29 @@ class Cifar10Dataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses Cifar10 dataset. This api only supports parsing Cifar10 file in binary version now. + 用于读取和解析Cifar10数据集。 + 目前只支持读取二进制版本的Cifar10文件。 The generated dataset has two columns :py:obj:`[image, label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`label` is a scalar of the uint32 type. + 生成的数据集具有两个列:[image, label]。 + image列的类型为uint8, + label列的类型为uint32。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Usage of this dataset, can be 'train', 'test' or 'all' . 'train' will read from 50,000 - train samples, 'test' will read from 10,000 test samples, 'all' will read from all 60,000 samples - (default=None, all samples). - num_samples (int, optional): The number of images to be included in the dataset - (default=None, all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - shuffle (bool, optional): Whether to perform shuffle on the dataset (default=None, expected - order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, `num_samples` reflects - the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str): Cifar10数据集的根目录。 + usage (str, optional): 数据集的用途,可以是'train'(从50,000个训练样本中读取), + 'test'(从10,000个测试样本中读取)或'all'(从所有60,000个样本中读取,默认为'all')。 + num_samples (int, optional): 数据集中图像的数量(默认为None,表示所有图像)。 + num_parallel_workers (int, optional): 读取数据的工作线程数量(默认为None,使用配置中的值)。 + shuffle (bool, optional): 是否对数据集进行随机洗牌(默认为None,预期行为显示在表格中)。 + sampler (Sampler, optional): 从数据集中选择样本的对象。 + num_shards (int, optional): 数据集将被分割成几个部分(默认为None)。当此参数指定时,num_samples + 表示每个分片的最大样本数。 + shard_id (int, optional): 在num_shards个分片中,此参数表示当前分片的ID(默认为None)。此参数只能 + 在指定num_shards时使用。 + cache (DatasetCache, optional): 使用tensor缓存服务加速数据处理(默认为None,表示不使用缓存)。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -617,24 +647,12 @@ class Cifar10Dataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> cifar10_dataset_dir = "/path/to/cifar10_dataset_directory" @@ -655,6 +673,9 @@ class Cifar10Dataset(MappableDataset, VisionBaseDataset): The CIFAR-10 dataset consists of 60000 32x32 colour images in 10 classes, with 6000 images per class. There are 50000 training images and 10000 test images. The 10 different classes represent airplanes, cars, birds, cats, deer, dogs, frogs, horses, ships, and trucks. + CIFAR-10是一个包含60,000个32x32彩色图像的數據集,其中每个类别有6,000张图像。数据集分为50,000个训练图像和10,000个测试图像。 + 数据集中的10个不同类别分别代表飞机、车、鸟、猫、鹿、狗、青蛙、马、船和卡车。 + Here is the original CIFAR-10 dataset structure. You can unzip the dataset files into the following directory structure and read by MindSpore's API. @@ -691,9 +712,12 @@ class Cifar10Dataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集目录 self.dataset_dir = dataset_dir + # 初始化数据集使用情况 self.usage = replace_none(usage, "all") + # 解析Cifar10Node节点 def parse(self, children=None): return cde.Cifar10Node(self.dataset_dir, self.usage, self.sampler) @@ -701,31 +725,26 @@ class Cifar10Dataset(MappableDataset, VisionBaseDataset): class Cifar100Dataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses Cifar100 dataset. + 用于读取和解析Cifar100数据集。 The generated dataset has three columns :py:obj:`[image, coarse_label, fine_label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`coarse_label` and :py:obj:`fine_labels` are each a scalar of uint32 type. + 生成的数据集有三列:[image, coarse_label, fine_label]。 + image列的类型为uint8,coarse_label和fine_label列的类型为uint32。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Usage of this dataset, can be 'train', 'test' or 'all' . 'train' will read from 50,000 - train samples, 'test' will read from 10,000 test samples, 'all' will read from all 60,000 samples - (default=None, all samples). - num_samples (int, optional): The number of images to be included in the dataset - (default=None, all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - shuffle (bool, optional): Whether to perform shuffle on the dataset (default=None, expected - order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, 'num_samples' reflects - the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str): Cifar100数据集的根目录路径。 + usage (str, optional): 数据集的用法,可以是 'train'、'test' 或 'all'。'train' 将从50,000个训练样本中读取数 + 据,'test' 将从10,000个测试样本中读取数据,'all' 将从所有60,000个样本中读取数据(默认值为 None,所有样本)。 + num_samples (int, optional): 数据集中要包含的图像数量(默认值 None,所有图像)。 + num_parallel_workers (int, optional): 用于读取数据的工作线程数量(默认值 None,在配置中设置)。 + shuffle (bool, optional): 是否在数据集中进行随机洗牌(默认值 None,根据表中的预期顺序行为)。 + sampler (Sampler, optional): 从数据集中选择样本的对象(默认值 None,根据表中的预期顺序行为)。 + num_shards (int, optional): 数据集将被分成几个部分(默认值 None)。当此参数指定时,'num_samples' 反映每个 + 分片中的最大样本数。 + shard_id (int, optional): 在 num_shards 中的分片ID(默认值 None)。此参数仅在指定 num_shards 时可用。 + cache (DatasetCache, optional): 使用tensor缓存服务加速数据处理(默认值 None,表示没有缓存)。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -748,24 +767,12 @@ class Cifar100Dataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> cifar100_dataset_dir = "/path/to/cifar100_dataset_directory" @@ -784,6 +791,8 @@ class Cifar100Dataset(MappableDataset, VisionBaseDataset): each. There are 500 training images and 100 testing images per class. The 100 classes in the CIFAR-100 are grouped into 20 superclasses. Each image comes with a "fine" label (the class to which it belongs) and a "coarse" label (the superclass to which it belongs). + CIFAR-100是一个包含100个类,每个类有600张图像的数据集。数据集分为50,000个训练图像和10,000个测试图像。 + CIFAR-100中的100个类被分成了20个超类。每个图像都带有“精细”标签(即它所属的类别)和“粗”标签(即它所属的超类)。 Here is the original CIFAR-100 dataset structure. You can unzip the dataset files into the following directory structure and read by MindSpore's API. @@ -816,44 +825,46 @@ class Cifar100Dataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集目录 self.dataset_dir = dataset_dir + # 初始化数据集使用类型 self.usage = replace_none(usage, "all") + # 定义parse函数,用于解析Cifar100Node节点 def parse(self, children=None): + # 返回Cifar100Node节点 return cde.Cifar100Node(self.dataset_dir, self.usage, self.sampler) class CityscapesDataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses Cityscapes dataset. + 用于读取和解析Cityscapes数据集。 The generated dataset has two columns :py:obj:`[image, task]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`task` is of the uint8 type if task is not 'polygon' otherwise task is a string tensor with serialize json. + 生成的数据集有两列:[image, task]。 + image列的类型为uint8, + task列的类型为uint8, + 如果任务不是'polygon',则任务是一个字符串张量,否则任务是一个序列化为JSON的字符串张量。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str): Acceptable usages include 'train', 'test', 'val' or 'all' if quality_mode is 'fine' - otherwise 'train', 'train_extra', 'val' or 'all' (default= 'train'). - quality_mode (str): Acceptable quality_modes include 'fine' or 'coarse' (default= 'fine'). - task (str): Acceptable tasks include 'instance', 'semantic', 'polygon' or 'color' (default= 'instance'). - num_samples (int, optional): The number of images to be included in the dataset. - (default=None, all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - shuffle (bool, optional): Whether to perform shuffle on the dataset (default=None, expected - order behavior shown in the table). - decode (bool, optional): Decode the images after reading (default=False). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, `num_samples` reflects - the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str): Cityscapes数据集的根目录路径。 + usage (str): 数据集的用法,可以是 'train'、'test'、'val' 或 'all',取决于quality_mode的值。 + 如果quality_mode='fine',则可以使用'train'、'test'、'val'或'all';否则,可以使用'train'、'train_extra'、 + 'val'或'all'(默认值为 'train')。 + task (str): 可接受的quality_modes包括'fine'或'coarse'(默认值为'fine')。 + num_samples (int, optional): 数据集中要包含的图像数量(默认值 None,所有图像)。 + num_parallel_workers (int, optional): 用于读取数据的工作线程数量(默认值 None,在配置中设置)。 + shuffle (bool, optional): 是否在数据集中进行随机洗牌(默认值 None,根据表中的预期顺序行为)。 + decode (bool, optional): 读取图像后是否进行解码(默认值 False)。 + sampler (Sampler, optional): 从数据集中选择样本的对象(默认值 None,根据表中的预期顺序行为)。 + num_shards (int, optional): 数据集将被分成几个部分(默认值 None)。当此参数指定时,'num_samples' + 反映每个分片中的最大样本数。 + shard_id (int, optional): 在 num_shards 中的分片ID(默认值 None)。此参数仅在指定 num_shards 时可用。 + cache (DatasetCache, optional): 使用tensor缓存服务加速数据处理(默认值 None,表示没有缓存)。 Raises: RuntimeError: If `dataset_dir` is invalid or does not contain data files. @@ -879,24 +890,12 @@ class CityscapesDataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> cityscapes_dataset_dir = "/path/to/cityscapes_dataset_directory" @@ -921,6 +920,8 @@ class CityscapesDataset(MappableDataset, VisionBaseDataset): 19998 colour images with coarser polygonal annotations in 50 cities. There are 30 classes in this dataset and the polygonal annotations include dense semantic segmentation and instance segmentation for vehicle and people. + Cityscapes数据集包含5000张彩色图像,其中每张图像都有高质量的密集像素注释和19998张彩色图像,其中注释为多边形的。 + 该数据集有30个类别,包括车辆和人员的密集语义分割和实例分割注释。 You can unzip the dataset files into the following directory structure and read by MindSpore's API. @@ -983,46 +984,49 @@ class CityscapesDataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集目录 self.dataset_dir = dataset_dir + # 初始化任务 self.task = task + # 初始化质量模式 self.quality_mode = quality_mode + # 初始化使用 self.usage = usage + # 初始化解码 self.decode = replace_none(decode, False) - def parse(self, children=None): + # 定义一个函数parse,用于解析CityscapesNode +def parse(self, children=None): + # 返回CityscapesNode对象 return cde.CityscapesNode(self.dataset_dir, self.usage, self.quality_mode, self.task, self.decode, self.sampler) class CocoDataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses COCO dataset. + 用于读取和解析COCO数据集。 CocoDataset supports five kinds of tasks, which are Object Detection, Keypoint Detection, Stuff Segmentation, Panoptic Segmentation and Captioning of 2017 Train/Val/Test dataset. + CocoDataset支持五种类型的任务,分别是:物体检测、关键点检测、语义分割、全景分割和图像描述。这些任务分别对应COCO 2017的 + 训练/验证/测试数据集。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - annotation_file (str): Path to the annotation JSON file. - task (str, optional): Set the task type for reading COCO data. Supported task types: - 'Detection', 'Stuff', 'Panoptic', 'Keypoint' and 'Captioning' (default='Detection'). - num_samples (int, optional): The number of images to be included in the dataset - (default=None, all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the configuration file). - shuffle (bool, optional): Whether to perform shuffle on the dataset (default=None, expected - order behavior shown in the table). - decode (bool, optional): Decode the images after reading (default=False). - sampler (Sampler, optional): Object used to choose samples from the dataset - (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, `num_samples` reflects - the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). - extra_metadata(bool, optional): Flag to add extra meta-data to row. If True, an additional column will be - output at the end :py:obj:`[_meta-filename, dtype=string]` (default=False). + dataset_dir (str): 数据集根目录的路径。 + annotation_file (str): 注释JSON文件的路径。 + task (str, optional): 设置COCO数据的读取类型。支持的任务类型:'Detection'(物体检测)、'Stuff'(语义分割)、 + 'Panoptic'(全景分割)、'Keypoint'(关键点检测)和'Captioning'(图像描述)。(默认值 'Detection')。 + num_samples (int, optional): 数据集中要包含的图像数量(默认值 None,所有图像)。 + num_parallel_workers (int, optional): 用于读取数据的工作线程数量(默认值 None,在配置文件中设置)。 + shuffle (bool, optional): 是否在数据集中进行随机洗牌(默认值 None,根据表中的预期顺序行为)。 + decode (bool, optional): 读取图像后是否进行解码(默认值 False)。 + sampler (Sampler, optional): 从数据集中选择样本的对象(默认值 None,根据表中的预期顺序行为)。 + num_shards (int, optional): 数据集将被分成几个部分(默认值 None)。当此参数指定时,'num_samples' 反映每个 + 分片中的最大样本数。 + shard_id (int, optional): 在 num_shards 中的分片ID(默认值 None)。此参数仅在指定 num_shards 时可用。 + cache (DatasetCache, optional): 使用tensor缓存服务加速数据处理(默认值 None,表示没有缓存)。 + extra_metadata(bool, optional): 标志是否在行中添加额外的元数据。如果为True,则会输出一个额外的列: + [_meta-filename, dtype=string](默认值 False)。 The generated dataset with different task setting has different output columns: @@ -1087,24 +1091,12 @@ class CocoDataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> coco_dataset_dir = "/path/to/coco_dataset_directory/images" @@ -1144,6 +1136,15 @@ class CocoDataset(MappableDataset, VisionBaseDataset): 330K images (>200K labeled), 1.5 million object instances, 80 object categories, 91 stuff categories, 5 captions per image, 250,000 people with keypoints. In contrast to the popular ImageNet dataset, COCO has fewer categories but more instances in per category. + COCO(Microsoft Common Objects in Context)是一个大型物体检测、语义分割和描述数据集,具有以下特点: + 对象分割:对象分割数据集包含对图像中对象的分割标注。 + 上下文识别:数据集包含对图像中对象上下文的识别。 + 超像素语义分割:数据集包含基于超像素的语义分割标注。 + 超过200K张已标注图像:数据集包含330K张图像,其中约200K张已标注。 + 150万对象实例:数据集包含80个对象类别和91个语义类别,每个类别都有大量的实例。 + 5个描述性 caption 每张图像:数据集包含5个描述性 caption 每张图像,共250,000个 caption。 + 50,000个人关键点:数据集包含50,000个人关键点。 + 与ImageNet相比,COCO的类别数量较少但每个类别的实例数量更多:COCO只有80个对象类别,而ImageNet有1000个类别。 You can unzip the original COCO-2017 dataset files into this directory structure and read by MindSpore's API. @@ -1198,16 +1199,23 @@ class CocoDataset(MappableDataset, VisionBaseDataset): extra_metadata=False): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化dataset_dir self.dataset_dir = dataset_dir + # 初始化annotation_file self.annotation_file = annotation_file + # 初始化task self.task = replace_none(task, "Detection") + # 初始化decode self.decode = replace_none(decode, False) + # 初始化extra_metadata self.extra_metadata = extra_metadata def parse(self, children=None): + # 解析COCO数据集,返回CocoNode对象 return cde.CocoNode(self.dataset_dir, self.annotation_file, self.task, self.decode, self.sampler, self.extra_metadata) + # 定义一个函数,用于获取类索引 def get_class_indexing(self): """ Get the class index. @@ -1226,47 +1234,48 @@ class CocoDataset(MappableDataset, VisionBaseDataset): >>> >>> class_indexing = dataset.get_class_indexing() """ + # 检查当前的任务类型是否为 "Detection" 或 "Panoptic" if self.task not in {"Detection", "Panoptic"}: + # 如果不是,抛出异常 raise NotImplementedError("Only 'Detection' and 'Panoptic' support get_class_indexing.") + # 如果 _class_indexing 属性为 None if self._class_indexing is None: + # 调用 _init_tree_getters() 方法初始化类索引映射 runtime_getter = self._init_tree_getters() + # 将结果存储在 _class_indexing 属性中 self._class_indexing = dict(runtime_getter[0].GetClassIndexing()) + # 返回 _class_indexing 属性 return self._class_indexing class DIV2KDataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses DIV2KDataset dataset. + 用于读取和解析DIV2K数据集。 The generated dataset has two columns :py:obj:`[hr_image, lr_image]`. The tensor of column :py:obj:`hr_image` is of the uint8 type. The tensor of column :py:obj:`lr_image` is of the uint8 type. + 该数据集有两个列:hr_image和lr_image。 + hr_image列的类型为uint8, + lr_image列的类型也为uint8。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str): Acceptable usages include 'train', 'valid' or 'all' (default= 'train'). - downgrade (str): Acceptable downgrades include 'bicubic', 'unknown', 'mild', 'difficult' or - 'wild' (default= 'bicubic'). - scale (int): Acceptable scales include 2, 3, 4 or 8 (default=2). - When `downgrade` is 'bicubic', scale can be 2, 3, 4, 8. - When `downgrade` is 'unknown', scale can only be 2, 3, 4. - When `downgrade` is 'mild', 'difficult' or 'wild', scale can only be 4. - num_samples (int, optional): The number of images to be included in the dataset. - (default=None, all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - shuffle (bool, optional): Whether to perform shuffle on the dataset (default=None, expected - order behavior shown in the table). - decode (bool, optional): Decode the images after reading (default=False). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, `num_samples` reflects - the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str):表示数据集根目录的路径。 + usage (str):表示数据集的用法,可以是'train'、'valid'或'all'(默认='train')。 + downgrade (str):表示数据集的降级方式,可以是'bicubic'、'unknown'、'mild'、'difficult'或 + 'wild'(默认='bicubic')。 + scale (int):表示数据集的缩放比例,可以是2、3、4或8(默认=2)。当downgrade为'bicubic'时,scale + 可以取2、3、4或8。当downgrade为'unknown'、'mild'、'difficult'或'wild'时,scale只能取4。 + num_samples (int, optional):表示数据集中的图像数量(默认为None,表示所有图像)。 + num_parallel_workers (int, optional):表示读取数据的工作线程数量(默认为None,根据配置文件设置)。 + shuffle (bool, optional):表示是否对数据集进行随机打乱(默认为None,根据配置文件设置)。 + decode (bool, optional):表示是否对读取的图像进行解码(默认为False)。 + sampler (Sampler, optional):表示用于从数据集中选择样本的对象(默认为None,根据配置文件设置)。 + num_shards (int, optional):表示数据集将被分成多少个分片(默认为None)。当此参数指定时,num_samples + 表示每个分片的最大样本数。 + shard_id (int, optional):表示分片ID(默认为None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):表示用于加速数据集处理的缓存服务(默认为None,表示不使用缓存)。 Raises: RuntimeError: If `dataset_dir` is invalid or does not contain data files. @@ -1294,24 +1303,12 @@ class DIV2KDataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> div2k_dataset_dir = "/path/to/div2k_dataset_directory" @@ -1335,6 +1332,8 @@ class DIV2KDataset(MappableDataset, VisionBaseDataset): The DIV2K dataset consists of 1000 2K resolution images, among which 800 images are for training, 100 images are for validation and 100 images are for testing. NTIRE 2017 and NTIRE 2018 include only training dataset and validation dataset. + DIV2K数据集包含1000张2K分辨率的图像,其中800张图像用于训练,100张图像用于验证,100张图像用于测试。在NTIRE 2017和 + NTIRE 2018中,只使用了训练数据集和验证数据集。 You can unzip the dataset files into the following directory structure and read by MindSpore's API. @@ -1412,44 +1411,46 @@ class DIV2KDataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集路径 self.dataset_dir = dataset_dir + # 初始化数据集使用类型 self.usage = usage + # 初始化数据集缩放比例 self.scale = scale + # 初始化数据集降维方式 self.downgrade = downgrade + # 初始化数据集解码方式 self.decode = replace_none(decode, False) - def parse(self, children=None): + # 定义一个函数parse,用于解析参数,并返回一个DIV2KNode对象 +def parse(self, children=None): + # 解析参数,并返回一个DIV2KNode对象 return cde.DIV2KNode(self.dataset_dir, self.usage, self.downgrade, self.scale, self.decode, self.sampler) - class EMnistDataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses the EMNIST dataset. + 用于读取与解析EMNIST数据集 The generated dataset has two columns :py:obj:`[image, label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`label` is a scalar of the uint32 type. + 生成的数据集包含两个列:[image, label]。 + 其中,列image的类型为uint8, + 列label的类型为uint32,是一个标量。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - name (str): Name of splits for this dataset, can be 'byclass', 'bymerge', 'balanced', 'letters', 'digits' - or 'mnist'. - usage (str, optional): Usage of this dataset, can be 'train', 'test' or 'all'. - (default=None, will read all samples). - num_samples (int, optional): The number of images to be included in the dataset - (default=None, will read all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, will use value set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str):数据集根目录的路径。 + name (str):数据集的名称,可以是'byclass'、'bymerge'、'balanced'、'letters'、'digits'或'mnist'。 + usage (str, optional):数据集的用法,可以是'train'、'test'或'all'(默认=None,表示读取所有样本)。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示读取所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示 + 每个分片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):用于加速数据集处理的缓存服务(默认=None,表示不使用缓存)。 Raises: RuntimeError: If `sampler` and `shuffle` are specified at the same time. @@ -1469,24 +1470,12 @@ class EMnistDataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> emnist_dataset_dir = "/path/to/emnist_dataset_directory" @@ -1511,6 +1500,18 @@ class EMnistDataset(MappableDataset, VisionBaseDataset): Letters: 145,600 characters and 26 balanced classes. Digits: 280,000 characters and 10 balanced classes. MNIST: 70,000 characters and 10 balanced classes. + 这段代码是关于EMNIST数据集的介绍。EMNIST数据集是从NIST特殊数据库19中提取的,并将其转换为28x28像素的图像格 + 式和与MNIST数据集相同的数据结构。有关数据集内容和工作流程的更多信息,请参阅提供在 + https://arxiv.org/abs/1702.05373v1。 + + EMNIST中有六个不同的分割: + + 'byclass':814,255个字符和62个不平衡的类。 + 'bymerge':814,255个字符和47个不平衡的类。 + 'balanced':131,600个字符和47个平衡的类。 + 'letters':145,600个字符和26个平衡的类。 + 'digits':280,000个字符和10个平衡的类。 + 'mnist':70,000个字符和10个平衡的类。 Here is the original EMNIST dataset structure. You can unzip the dataset files into this directory structure and read by MindSpore's API. @@ -1546,41 +1547,43 @@ class EMnistDataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集路径 self.dataset_dir = dataset_dir + # 初始化数据集名称 self.name = name + # 初始化数据集使用情况 self.usage = replace_none(usage, "all") def parse(self, children=None): + # 解析子节点,返回一个EMnistNode对象 return cde.EMnistNode(self.dataset_dir, self.name, self.usage, self.sampler) class FakeImageDataset(MappableDataset, VisionBaseDataset): """ A source dataset for generating fake images. + 用于生成假图像的数据集 The generated dataset has two columns :py:obj:`[image, label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`label` is a scalar of the uint32 type. + 生成两个列:[image, label]。 + image列的tensor类型为uint8, + label列的tensor类型为uint32。 Args: - num_images (int, optional): Number of images to generate in the dataset (default=1000). - image_size (tuple, optional): Size of the fake image (default=(224, 224, 3)). - num_classes (int, optional): Number of classes in the dataset (default=10). - base_seed (int, optional): Offsets the index-based random seed used to generate each image (default=0). - num_samples (int, optional): The number of images to be included in the dataset - (default=None, will read all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, will use value set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + num_images (int, optional):生成的图像数量(默认为1000)。 + image_size (tuple, optional):生成的图像大小(默认为(224, 224, 3))。 + num_classes (int, optional):数据集中的类别数量(默认为10)。 + base_seed (int, optional):用于生成每个图像的随机种子偏移量(默认为0)。 + num_samples (int, optional):数据集中图像的数量(默认为None,表示读取所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认为None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认为None,根据配置文件设置)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认为None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认为None)。当此参数指定时,num_samples + 表示每个分片的最大样本数。 + shard_id (int, optional):分片ID(默认为None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):用于加速数据集处理的缓存服务(默认为None,表示不使用缓存)。 Raises: ValueError: If `num_parallel_workers` exceeds the max thread numbers. @@ -1601,24 +1604,12 @@ class FakeImageDataset(MappableDataset, VisionBaseDataset): * - Parameter 'sampler' - Parameter 'shuffle' - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> # Read 3 samples from FakeImage dataset @@ -1634,42 +1625,45 @@ class FakeImageDataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 设置图片数量 self.num_images = num_images + # 设置图片尺寸 self.image_size = image_size + # 设置类别数量 self.num_classes = num_classes + # 设置基础种子 self.base_seed = base_seed + # 定义一个parse函数,用于解析子节点 def parse(self, children=None): + # 返回一个FakeImageNode对象,该对象包含num_images, image_size, num_classes, base_seed, sampler参数 return cde.FakeImageNode(self.num_images, self.image_size, self.num_classes, self.base_seed, self.sampler) class FashionMnistDataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses the FASHION-MNIST dataset. + 用于读取和解析FASHION-MNIST数据集。 The generated dataset has two columns :py:obj:`[image, label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`label` is a scalar of the uint32 type. + 包含两个列:[image, label]。 + image列的类型为uint8, + label列的类型为uint32。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Usage of this dataset, can be 'train', 'test' or 'all'. 'train' will read from 60,000 - train samples, 'test' will read from 10,000 test samples, 'all' will read from all 70,000 samples. - (default=None, will read all samples) - num_samples (int, optional): The number of images to be included in the dataset - (default=None, will read all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, will use value set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str):数据集根目录的路径。 + usage (str, optional):数据集的用法,可以是'train'、'test'或'all'。'train'将读取60,000个训练样本, + 'test'将读取10,000个测试样本,'all'将读取所有70,000个样本(默认=None,表示读取所有样本)。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示读取所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示每个 + 分片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):使用tensor caching服务加速数据集处理(默认=None,表示不使用缓存)。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -1691,24 +1685,12 @@ class FashionMnistDataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> fashion_mnist_dataset_dir = "/path/to/fashion_mnist_dataset_directory" @@ -1724,6 +1706,9 @@ class FashionMnistDataset(MappableDataset, VisionBaseDataset): a test set of 10,000 examples. Each example is a 28x28 grayscale image, associated with a label from 10 classes. We intend Fashion-MNIST to serve as a direct drop-in replacement for the original MNIST dataset for benchmarking machine learning algorithms. It shares the same image size and structure of training and testing splits. + Fashion-MNIST是一个由Zalando公司提供的文章图像数据集,其中包含60,000个训练示例和10,000个测试示例。每个示例是一个28x28灰度 + 图像,并与10个类之一相关联。将Fashion-MNIST用作对原始的MNIST数据集的直接替代,用于评估机器学习算法。它们具有相同的图像大小 + 和训练和测试拆分。 You can unzip the dataset files into this directory structure and read by MindSpore's API. @@ -1757,41 +1742,42 @@ class FashionMnistDataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集路径 self.dataset_dir = dataset_dir + # 初始化数据集使用类型 self.usage = replace_none(usage, "all") + # 定义一个parse函数,用于解析children参数 def parse(self, children=None): + # 返回一个FashionMnistNode对象,该对象接收三个参数:self.dataset_dir, self.usage, self.sampler return cde.FashionMnistNode(self.dataset_dir, self.usage, self.sampler) class FlickrDataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses Flickr8k and Flickr30k dataset. + 用于读取与解析Flickr8k和Flickr30k数据集。 The generated dataset has two columns :py:obj:`[image, annotation]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`annotation` is a tensor which contains 5 annotations string, such as ["a", "b", "c", "d", "e"]. + 包含两个列:[image, annotation]。 + image列的类型为uint8, + annotation列的类型为一个包含5个字符串的张量,例如["a", "b", "c", "d", "e"]。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - annotation_file (str): Path to the root directory that contains the annotation. - num_samples (int, optional): The number of images to be included in the dataset. - (default=None, all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - shuffle (bool, optional): Whether to perform shuffle on the dataset (default=None, expected - order behavior shown in the table). - decode (bool, optional): Decode the images after reading (default=False). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, `num_samples` reflects - the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str):数据集根目录的路径。 + annotation_file (str):标注文件的路径。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示读取所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + decode (bool, optional):是否在读取图像后解码(默认=False)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示每个分 + 片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):使用tensor caching服务加速数据集处理(默认=None,表示不使用缓存)。 Raises: RuntimeError: If `dataset_dir` is not valid or does not contain data files. @@ -1815,24 +1801,12 @@ class FlickrDataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> flickr_dataset_dir = "/path/to/flickr_dataset_directory" @@ -1861,6 +1835,7 @@ class FlickrDataset(MappableDataset, VisionBaseDataset): The Flickr8k dataset consists of 8092 colour images. There are 40460 annotations in the Flickr8k.token.txt, each image has 5 annotations. + Flickr8k数据集包含8092张彩色图像。Flickr8k.token.txt中有40460个注释,每个图像有5个注释。 You can unzip the dataset files into the following directory structure and read by MindSpore's API. @@ -1896,6 +1871,7 @@ class FlickrDataset(MappableDataset, VisionBaseDataset): The Flickr30k dataset consists of 31783 colour images. There are 158915 annotations in the results_20130124.token, each image has 5 annotations. + Flickr30k数据集包含31783张彩色图像。results_20130124.token文件中有158915个注释,每个图像有5个注释。 You can unzip the dataset files into the following directory structure and read by MindSpore's API. @@ -1934,36 +1910,53 @@ class FlickrDataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集目录 self.dataset_dir = dataset_dir + # 初始化标注文件 self.annotation_file = annotation_file + # 初始化解码 self.decode = replace_none(decode, False) + # 定义parse函数,用于解析FlickrNode节点 def parse(self, children=None): + # 返回FlickrNode节点 return cde.FlickrNode(self.dataset_dir, self.annotation_file, self.decode, self.sampler) class _Flowers102Dataset: """ Mainly for loading Flowers102 Dataset, and return one row each time. + 加载Flowers102数据集,并返回每一行数据。 """ + # 初始化函数,用于初始化数据集 def __init__(self, dataset_dir, task, usage, decode): + # 获取数据集路径 self.dataset_dir = os.path.realpath(dataset_dir) + # 获取任务类型 self.task = task + # 获取使用类型 self.usage = usage + # 获取解码类型 self.decode = decode + # 根据任务类型,设置列名 if self.task == "Classification": self.column_names = ["image", "label"] else: self.column_names = ["image", "segmentation", "label"] + # 获取标签文件路径 labels_path = os.path.join(self.dataset_dir, "imagelabels.mat") + # 获取setid文件路径 setid_path = os.path.join(self.dataset_dir, "setid.mat") # minus one to transform 1~102 to 0 ~ 101 + # 将标签减一,转换为0~101 self.labels = (loadmat(labels_path)["labels"][0] - 1).astype(np.uint32) + # 加载setid文件 self.setid = loadmat(setid_path) + # 根据使用类型,设置索引 if self.usage == 'train': self.indices = self.setid["trnid"][0].tolist() elif self.usage == 'test': @@ -1977,62 +1970,81 @@ class _Flowers102Dataset: else: raise ValueError("Input usage is not within the valid set of ['train', 'valid', 'test', 'all'].") + # Flowers102数据集中获取指定索引的元素 def __getitem__(self, index): + # 使用self.indices[index]获取数据集中的索引值,该值在1到8189之间 # range: 1 ~ 8189 + # 使用os.path.join函数构建图像文件的路径,该路径位于数据集目录下的jpg文件夹中 + # 文件名为image_加上5个零填充的索引值(例如image_00001.jpg) image_path = os.path.join(self.dataset_dir, "jpg", "image_" + str(self.indices[index]).zfill(5) + ".jpg") + # 如果图像文件不存在,则抛出一个运行时异常 if not os.path.exists(image_path): raise RuntimeError("Can not find image file: " + image_path) + # 如果decode参数为True if self.decode is True: + # 使用PIL库中的Image.open函数以RGB格式打开图像文件,并将其转换为NumPy数组 image = np.asarray(Image.open(image_path).convert("RGB")) else: + # 使用np.fromfile函数从图像文件中读取数据,并将数据类型设置为np.uint8。 image = np.fromfile(image_path, dtype=np.uint8) + # 从self.labels中获取与索引值相对应的标签,并将其存储在label变量中 label = self.labels[self.indices[index] - 1] + # 检查self.task是否为"Segmentation" if self.task == "Segmentation": + # 使用os.path.join函数构建分割图像文件的路径,该路径位于数据集目录下的segmim文件夹中 + # 文件名为segmim_加上5个零填充的索引值(例如segmim_00001.jpg)。 segmentation_path = \ os.path.join(self.dataset_dir, "segmim", "segmim_" + str(self.indices[index]).zfill(5) + ".jpg") + # 如果分割图像文件不存在,则抛出一个运行时异常 if not os.path.exists(segmentation_path): raise RuntimeError("Can not find segmentation file: " + segmentation_path) + # 如果decode参数为True if self.decode is True: + # 使用PIL库中的Image.open函数以RGB格式打开分割图像文件,并将其转换为NumPy数组 segmentation = np.asarray(Image.open(segmentation_path).convert("RGB")) + # 否则,使用np.fromfile函数从分割图像文件中读取数据,并将数据类型设置为np.uint8 else: segmentation = np.fromfile(segmentation_path, dtype=np.uint8) + # 返回图像和分割图像(如果存在)以及相应的标签 return image, segmentation, label return image, label def __len__(self): + # 返回数据集中元素的个数 return len(self.indices) class Flowers102Dataset(GeneratorDataset): """ A source dataset that reads and parses Flowers102 dataset. + 用于读取与解析Flowers102数据集。 The generated dataset has two columns :py:obj:`[image, label]` or three :py:obj:`[image, segmentation, label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`segmentation` is of the uint8 type. The tensor of column :py:obj:`label` is a scalar or a tensor of the uint32 type. + 生成的数据集具有两个列或三个列:[image, label] 或 [image, segmentation, label]。 + image列的类型为uint8, + segmentation列的类型为uint8, + label列的类型为uint32或scalar。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - task (str): Specify the 'Classification' or 'Segmentation' task (default='Classification'). - usage (str): Specify the 'train', 'valid', 'test' part or 'all' parts of dataset - (default='all', will read all samples). - num_samples (int, optional): The number of samples to be included in the dataset (default=None, all images). - num_parallel_workers (int, optional): Number of subprocesses used to fetch the dataset in parallel (default=1). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset. Random accessible input is required. - (default=None, expected order behavior shown in the table). - decode (bool, optional): Whether or not to decode the images and segmentations after reading (default=False). - sampler (Union[Sampler, Iterable], optional): Object used to choose samples from the dataset. Random accessible - input is required (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - Random accessible input is required. When this argument is specified, 'num_samples' reflects the max - sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This argument must be specified only - when num_shards is also specified. Random accessible input is required. + dataset_dir (str):数据集根目录的路径。 + task (str):指定数据集的任务类型,可以是'Classification'或'Segmentation'(默认='Classification')。 + usage (str):指定数据集的组成部分,可以是'train'、'valid'、'test'或'all'(默认='all',表示读取所有样本)。 + num_samples (int, optional):数据集中样本的数量(默认=None,表示读取所有样本)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=1)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。随机可访问的输入是必需的。 + decode (bool, optional):是否在读取图像和分割图之后解码(默认=False)。随机可访问的输入是必需的。 + sampler (Union[Sampler, Iterable], optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + 随机可访问的输入是必需的。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。随机可访问的输入是必需的。当此参数指定时, + num_samples表示每个分片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。随机可访问的输入是必需的。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -2054,24 +2066,12 @@ class Flowers102Dataset(GeneratorDataset): * - Parameter 'sampler' - Parameter 'shuffle' - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> flowers102_dataset_dir = "/path/to/flowers102_dataset_directory" @@ -2082,9 +2082,9 @@ class Flowers102Dataset(GeneratorDataset): About Flowers102 dataset: - Flowers102 dataset consists of 102 flower categories. - The flowers commonly occur in the United Kingdom. - Each class consists of between 40 and 258 images. + Flowers102数据集包含102种花的类别。 + 这些花通常在英国出现。 + 每个类别中有40到258张图片。 Here is the original Flowers102 dataset structure. You can unzip the dataset files into this directory structure and read by MindSpore's API. @@ -2120,11 +2120,17 @@ class Flowers102Dataset(GeneratorDataset): @check_flowers102dataset def __init__(self, dataset_dir, task="Classification", usage="all", num_samples=None, num_parallel_workers=1, shuffle=None, decode=False, sampler=None, num_shards=None, shard_id=None): + # 获取dataset_dir的绝对路径 self.dataset_dir = os.path.realpath(dataset_dir) + # 如果task为空,则设置task为Classification self.task = replace_none(task, "Classification") + # 如果usage为空,则设置usage为all self.usage = replace_none(usage, "all") + # 如果decode为空,则设置decode为False self.decode = replace_none(decode, False) + # 创建_Flowers102Dataset实例 dataset = _Flowers102Dataset(self.dataset_dir, self.task, self.usage, self.decode) + # 调用父类构造函数 super().__init__(dataset, column_names=dataset.column_names, num_samples=num_samples, num_parallel_workers=num_parallel_workers, shuffle=shuffle, sampler=sampler, num_shards=num_shards, shard_id=shard_id) @@ -2132,10 +2138,12 @@ class Flowers102Dataset(GeneratorDataset): def get_class_indexing(self): """ Get the class index. + 获取索引字典。 Returns: dict, a str-to-int mapping from label name to index. """ + # 创建一个字典,用于存储类别名称和索引的映射 class_names = [ "pink primrose", "hard-leaved pocket orchid", "canterbury bells", "sweet pea", "english marigold", "tiger lily", "moon orchid", @@ -2164,9 +2172,11 @@ class Flowers102Dataset(GeneratorDataset): ] class_dict = {} + # for循环遍历class_names列表中的每个类别名称,其中enumerate()函数会返回一个包含索引和类名称的元组 for i, class_name in enumerate(class_names): + # 对于每个元组,它会将类别名称作为键,将索引作为值添加到class_dict字典中 class_dict[class_name] = i - + # 返回类别字典 return class_dict @@ -2174,35 +2184,30 @@ class ImageFolderDataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads images from a tree of directories. All images within one folder have the same label. + 用于从目录树中读取图像的源数据集。 + 在这个数据集中,所有位于同一文件夹中的图像都有相同的标签。 The generated dataset has two columns: :py:obj:`[image, label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`label` is of a scalar of uint32 type. + 生成的数据集具有两个列:[image, label]。 + image列的类型为uint8, + label列的类型为uint32或scalar。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - num_samples (int, optional): The number of images to be included in the dataset - (default=None, all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - extensions (list[str], optional): List of file extensions to be - included in the dataset (default=None). - class_indexing (dict, optional): A str-to-int mapping from folder name to index - (default=None, the folder names will be sorted - alphabetically and each class will be given a - unique index starting from 0). - decode (bool, optional): Decode the images after reading (default=False). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, `num_samples` reflects - the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str):数据集根目录的路径。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示读取所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + extensions (list[str], optional):要包含在数据集中的文件扩展名列表(默认=None)。 + class_indexing (dict, optional):一个从文件夹名称到索引的字典,用于将文件夹名称映射到唯一的整数索引 + (默认=None,文件夹名称将按字母顺序排序,每个类将获得一个唯一的索引从0开始)。 + decode (bool, optional):读取图像后是否解码(默认=False)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示每个分 + 片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):使用tensor caching服务加速数据集处理(默认=None,表示不使用缓存)。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -2226,24 +2231,12 @@ class ImageFolderDataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> image_folder_dataset_dir = "/path/to/image_folder_dataset_directory" @@ -2291,22 +2284,29 @@ class ImageFolderDataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集目录 self.dataset_dir = dataset_dir + # 初始化扩展名 self.extensions = replace_none(extensions, []) + # 初始化类别索引 self.class_indexing = replace_none(class_indexing, {}) + # 初始化解码 self.decode = replace_none(decode, False) + # 定义一个函数parse,用于解析参数,并返回一个ImageFolderNode对象 def parse(self, children=None): return cde.ImageFolderNode(self.dataset_dir, self.decode, self.sampler, self.extensions, self.class_indexing) - class KITTIDataset(MappableDataset): """ A source dataset that reads and parses the KITTI dataset. + 用于读取与解析KITTI数据集。 When usage is "train", the generated dataset has multiple columns: :py:obj:`[image, label, truncated, occluded, alpha, bbox, dimensions, location, rotation_y]`; When usage is "test", the generated dataset has only one column: :py:obj:`[image]`. + 当使用"train"时,生成的数据集具有多个列:[image, label, truncated, occluded, alpha, bbox, dimensions, location, rotation_y]。 + 当使用"test"时,生成的数据集只有1列:[image]。 The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`label` is of the uint32 type. The tensor of column :py:obj:`truncated` is of the float32 type. @@ -2316,27 +2316,29 @@ class KITTIDataset(MappableDataset): The tensor of column :py:obj:`dimensions` is of the float32 type. The tensor of column :py:obj:`location` is of the float32 type. The tensor of column :py:obj:`rotation_y` is of the float32 type. + image列的类型为uint8, + label列的类型为uint32, + truncated列的类型为float32, + occluded列的类型为uint32, + alpha列的类型为float32, + bbox列的类型为float32, + dimensions列的类型为float32, + location列的类型为float32, + rotation_y列的类型为float32。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Usage of this dataset, can be `train` or `test`. `train` will read 7481 - train samples, `test` will read from 7518 test samples without label (default=None, will use `train`). - num_samples (int, optional): The number of images to be included in the dataset - (default=None, will include all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - shuffle (bool, optional): Whether to perform shuffle on the dataset (default=None, expected - order behavior shown in the table). - decode (bool, optional): Decode the images after reading (default=False). - sampler (Sampler, optional): Object used to choose samples from the dataset - (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, 'num_samples' reflects - the max sample number of per shard. - shard_id (int, optional): The shard ID within num_shards (default=None). This - argument can only be specified when num_shards is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing - (default=None, which means no cache is used). + dataset_dir (str):数据集根目录的路径。 + usage (str, optional):数据集的用法,可以是train或test。train将读取7481个训练样本,test将读取7518个测试 + 样本(无标签,默认=None,使用train)。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示读取所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + decode (bool, optional):读取图像后是否解码(默认=False)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示每个分 + 片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):使用tensor caching服务加速数据集处理(默认=None,表示不使用缓存)。 Raises: RuntimeError: If `sampler` and `shuffle` are specified at the same time. @@ -2357,24 +2359,12 @@ class KITTIDataset(MappableDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> kitti_dataset_dir = "/path/to/kitti_dataset_directory" @@ -2397,6 +2387,11 @@ class KITTIDataset(MappableDataset): with three classes: road, vertical,and sky. Zhang et al. annotated 252 (140 for training and 112 for testing) acquisitions – RGB and Velodyne scans – from the tracking challenge for ten object categories: building, sky, road, vegetation, sidewalk, car, pedestrian, cyclist, sign/pole, and fence. + KITTI(卡尔斯鲁厄理工学院和丰田技术研究所)是一个流行的移动机器人学和自主驾驶数据集。它由一组交通场景的录像组成, + 包括多种传感器模式,如高分辨率RGB、灰度 stereo 相机和3D激光扫描仪。尽管KITTI本身不包含语义分割的ground truth, + 但许多研究人员已经对部分数据集进行了手动标注,以适应他们的需求。Álvarez等人为323张图像生成了路标检测的ground truth, + 分为三个类别:路、垂直和天空。张等人对252个(140个训练和112个测试) acquisition – RGB和Velodyne扫描仪 – 从跟踪挑 + 战中的十个对象类别进行了标注:建筑、天空、道路、植物、人行道、汽车、行人、自行车、标志/杆和栅栏。 You can unzip the original KITTI dataset files into this directory structure and read by MindSpore's API. @@ -2438,41 +2433,41 @@ class KITTIDataset(MappableDataset): decode=False, sampler=None, num_shards=None, shard_id=None, cache=None): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 设置数据集目录 self.dataset_dir = dataset_dir + # 设置数据集用途 self.usage = replace_none(usage, "train") + # 设置是否解码数据 self.decode = replace_none(decode, False) def parse(self, children=None): + # 解析子节点,返回一个KITTINode实例 return cde.KITTINode(self.dataset_dir, self.usage, self.decode, self.sampler) class KMnistDataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses the KMNIST dataset. + 用于读取和解析 KMNIST 数据集。 The generated dataset has two columns :py:obj:`[image, label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`label` is a scalar of the uint32 type. + 包括两个列:[image, label]。 + image列的tensor类型为uint8, + label列的tensor类型为uint32。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Usage of this dataset, can be 'train', 'test' or 'all' . 'train' will read from 60,000 - train samples, 'test' will read from 10,000 test samples, 'all' will read from all 70,000 samples. - (default=None, will read all samples) - num_samples (int, optional): The number of images to be included in the dataset - (default=None, will read all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, will use value set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str):数据集根目录的路径。 + usage (str, optional):数据集的用法,可以是'train'、'test'或'all'。'train'将读取60,000个训练样本, + 'test'将读取10,000个测试样本,'all'将读取所有70,000个样本(默认=None,表示读取所有样本)。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示读取所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示每个分 + 片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -2494,24 +2489,12 @@ class KMnistDataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> kmnist_dataset_dir = "/path/to/kmnist_dataset_directory" @@ -2525,6 +2508,8 @@ class KMnistDataset(MappableDataset, VisionBaseDataset): KMNIST is a dataset, adapted from Kuzushiji Dataset, as a drop-in replacement for MNIST dataset, which is the most famous dataset in the machine learning community. + KMNIST是从Kuzushiji数据集修改而来的,用作替换MNIST数据集中的一个drop-in替换。MNIST数据集是机器学习社 + 区中最知名的数据集之一。 Here is the original KMNIST dataset structure. You can unzip the dataset files into this directory structure and read by MindSpore's API. @@ -2560,48 +2545,49 @@ class KMnistDataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集路径 self.dataset_dir = dataset_dir + # 初始化数据集使用类型 self.usage = replace_none(usage, "all") + # 定义parse函数,用于解析children参数 def parse(self, children=None): + # 返回一个KMnistNode对象,该对象接收三个参数:self.dataset_dir, self.usage, self.sampler return cde.KMnistNode(self.dataset_dir, self.usage, self.sampler) class LFWDataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses the LFW dataset. + 用于读取和解析LFW数据集。 When task is "people", the generated dataset has two columns: :py:obj:`[image, label]`; When task is "pairs", the generated dataset has three columns: :py:obj:`[image1, image2, label]`. + 当任务为"people"时,生成的数据集包括两个列:[image, label]; + 当任务为"pairs"时,生成的数据集包括三个列:[image1, image2, label]。 The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`image1` is of the uint8 type. The tensor of column :py:obj:`image2` is of the uint8 type. The tensor of column :py:obj:`label` is a scalar of the uint32 type. + image列的tensor类型为uint8, + image1、image2列的tensor类型为uint8, + label列的tensor类型为uint32。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - task (str, optional): Set the task type of reading lfw data, support "people" and "pairs" - (default="people"). - usage (str, optional): The image split to use, support "10fold", "train", "test" and "all" - (default="all", will read samples including train and test). - image_set (str, optional): Image set of image funneling to use, support "original", "funneled" or - "deepfunneled" (default="funneled", will read "funneled" set). - num_samples (int, optional): The number of images to be included in the dataset - (default=None, all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - decode (bool, optional): Decode the images after reading (default=False). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, 'num_samples' reflects - the max sample number of per shard. - shard_id (int, optional): The shard ID within num_shards (default=None). This - argument can only be specified when num_shards is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing - (default=None, which means no cache is used). + dataset_dir (str):数据集根目录的路径。 + task (str, optional):设置LFW数据读取的任务类型,支持"people"和"pairs"(默认="people")。 + usage (str, optional):读取LFW数据的图像分割部分,支持"10fold"、"train"、"test"和"all"(默认="all",包 + 含训练和测试样本)。 + image_set (str, optional):用于图像汇流的图像集,支持"original"、"funneled"或"deepfunneled" + (默认="funneled",读取"funneled"集)。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示读取所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + decode (bool, optional):读取图像后是否解码(默认=False)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示每个分 + 片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 Raises: RuntimeError: If sampler and shuffle are specified at the same time. @@ -2617,24 +2603,12 @@ class LFWDataset(MappableDataset, VisionBaseDataset): * - Parameter 'sampler' - Parameter 'shuffle' - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> # 1) Read LFW People dataset @@ -2653,6 +2627,9 @@ class LFWDataset(MappableDataset, VisionBaseDataset): of Massachusetts, Amherst (specific references are in Acknowledgments section). 13,233 images of 5,749 people were detected and centered by the Viola Jones face detector and collected from the web. 1,680 of the people pictured have two or more distinct photos in the dataset. + LFW数据集,是一个用于无约束人脸识别的图像数据库。这个数据库是由马萨诸塞州的大学的研究人员创建和维护的。在数据集中共 + 有13,233张人脸图像,涉及5,749名个体。Viola Jones人脸检测器对这些图像进行了中心化处理,并从互联网上收集了这些图像。其中, + 有1,680名个体在数据集中有2张或更多照片。 You can unzip the original LFW dataset files into this directory structure and read by MindSpore's API. @@ -2711,46 +2688,46 @@ class LFWDataset(MappableDataset, VisionBaseDataset): shuffle=None, decode=False, sampler=None, num_shards=None, shard_id=None, cache=None): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 设置数据集路径 self.dataset_dir = dataset_dir + # 设置任务类型 self.task = replace_none(task, "people") + # 设置使用类型 self.usage = replace_none(usage, "all") + # 设置图像集类型 self.image_set = replace_none(image_set, "funneled") + # 设置是否解码图像 self.decode = replace_none(decode, False) def parse(self, children=None): + # 解析LFW数据集 return cde.LFWNode(self.dataset_dir, self.task, self.usage, self.image_set, self.decode, self.sampler) class LSUNDataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses the LSUN dataset. + 用于读取和解析LSUN数据集 The generated dataset has two columns: :py:obj:`[image, label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`label` is of a scalar of uint32 type. + 生成的数据集包括两个列:[image, label]。 + image列的tensor类型为uint8, + label列的tensor类型为uint32。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Usage of this dataset, can be `train`, `test`, `valid` or `all` - (default=None, will be set to `all`). - classes(Union[str, list[str]], optional): Choose the specific classes to load (default=None, means loading - all classes in root directory). - num_samples (int, optional): The number of images to be included in the dataset - (default=None, all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - decode (bool, optional): Decode the images after reading (default=False). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, 'num_samples' reflects - the max sample number of per shard. - shard_id (int, optional): The shard ID within num_shards (default=None). This - argument can only be specified when num_shards is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing - (default=None, which means no cache is used). + dataset_dir (str):数据集根目录的路径。 + usage (str, optional):数据集的用法,可以是train、test、valid或all(默认=None,表示加载所有类)。 + classes(Union[str, list[str]], optional):选择特定类进行加载(默认=None,表示加载根目录中的所有类)。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示加载所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + decode (bool, optional):读取图像后是否解码(默认=False)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示每个 + 分片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 Raises: RuntimeError: If 'sampler' and 'shuffle' are specified at the same time. @@ -2767,24 +2744,12 @@ class LSUNDataset(MappableDataset, VisionBaseDataset): * - Parameter 'sampler' - Parameter 'shuffle' - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> lsun_dataset_dir = "/path/to/lsun_dataset_directory" @@ -2805,6 +2770,8 @@ class LSUNDataset(MappableDataset, VisionBaseDataset): The LSUN dataset contains around one million labeled images for each of 10 scene categories and 20 object categories. The author experimented with training popular convolutional networks and found that they achieved substantial performance gains when trained on this dataset. + LSUN数据集提供了对分层处理的有效性和视觉识别研究进度的支持。LSUN数据集包含约一百万张已标注的图像,分别用于10个场景类 + 和20个物体类。作者尝试使用流行的卷积神经网络(CNN)进行训练,发现这些网络在LSUN数据集上取得了明显的性能提升。 You can unzip the original LSUN dataset files into this directory structure using official data.py and read by MindSpore's API. @@ -2842,45 +2809,47 @@ class LSUNDataset(MappableDataset, VisionBaseDataset): shuffle=None, decode=False, sampler=None, num_shards=None, shard_id=None, cache=None): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 设置数据集路径 self.dataset_dir = dataset_dir + # 设置数据集用法 self.usage = replace_none(usage, "all") + # 设置进行特定加载的类 self.classes = replace_none(classes, []) + # 设置是否解码图像 self.decode = replace_none(decode, False) + # 定义一个parse函数,用于解析参数 def parse(self, children=None): + # 返回一个LSUNNode对象,该对象接收4个参数:self.dataset_dir, self.usage, self.classes, self.decode, self.sampler return cde.LSUNNode(self.dataset_dir, self.usage, self.classes, self.decode, self.sampler) class ManifestDataset(MappableDataset, VisionBaseDataset): """ A source dataset for reading images from a Manifest file. + 用于从清单文件中读取图像的数据源。 The generated dataset has two columns: :py:obj:`[image, label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`label` is of a scalar of uint64 type. + 生成的数据集具有两个列:[image, label]。 + image列的类型为uint8, + label列的类型为uint64的标量。 Args: - dataset_file (str): File to be read. - usage (str, optional): Acceptable usages include 'train', 'eval' and 'inference' (default= 'train'). - num_samples (int, optional): The number of images to be included in the dataset. - (default=None, will include all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, will use value set in the config). - shuffle (bool, optional): Whether to perform shuffle on the dataset (default=None, expected - order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - class_indexing (dict, optional): A str-to-int mapping from label name to index - (default=None, the folder names will be sorted alphabetically and each - class will be given a unique index starting from 0). - decode (bool, optional): decode the images after reading (default=False). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, `num_samples` reflects - the max number of samples per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_file (str):要读取的文件路径。 + usage (str, optional):数据集的用法,可以是'train'、'eval'或'inference'(默认='train')。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示包含所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,使用配置文件中的值)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件中的设置)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件中的设置)。 + class_indexing (dict, optional):从标签名称到索引的字典(默认=None,标签名称将按字母顺序排序,每个类将获 + 得一个唯一的索引从0开始)。 + decode (bool, optional):读取图像后是否解码(默认=False)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示每个分 + 片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):使用tensor caching服务加速数据集处理(默认=None,表示不使用缓存)。 Raises: RuntimeError: If dataset_files are not valid or do not exist. @@ -2904,24 +2873,12 @@ class ManifestDataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> manifest_dataset_dir = "/path/to/manifest_dataset_file" @@ -2939,17 +2896,23 @@ class ManifestDataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化dataset_file self.dataset_file = dataset_file + # 初始化decode self.decode = replace_none(decode, False) + # 初始化usage self.usage = replace_none(usage, "train") + # 初始化class_indexing self.class_indexing = replace_none(class_indexing, {}) def parse(self, children=None): + # 解析子节点,并返回ManifestNode对象 return cde.ManifestNode(self.dataset_file, self.usage, self.sampler, self.class_indexing, self.decode) def get_class_indexing(self): """ Get the class index. + 用于获取类索引。 Returns: dict, a str-to-int mapping from label name to index. @@ -2960,43 +2923,47 @@ class ManifestDataset(MappableDataset, VisionBaseDataset): >>> dataset = ds.ManifestDataset(dataset_file=manifest_dataset_dir) >>> class_indexing = dataset.get_class_indexing() """ + # 检查class_indexing属性是否为空或未设置 if self.class_indexing is None or not self.class_indexing: + # 如果_class_indexing属性为空 if self._class_indexing is None: + # 调用_init_tree_getters方法以初始化树获取器 runtime_getter = self._init_tree_getters() + # 获取_class_indexing中每个元组的第一个元素 self._class_indexing = runtime_getter[0].GetClassIndexing() self.class_indexing = {} + # 将其添加到class_indexing字典中 for pair in self._class_indexing: + # 键为元组的第一个元素,值为元组的第二个元素的第一个元素 self.class_indexing[pair[0]] = pair[1][0] + # 返回class_indexing字典 return self.class_indexing class MnistDataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses the MNIST dataset. + 用于读取和解析MNIST数据集。 The generated dataset has two columns :py:obj:`[image, label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`label` is a scalar of the uint32 type. + 生成的数据集具有两个列:[image, label]。 + image列的类型为uint8, + label列的类型为uint32的标量。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Usage of this dataset, can be 'train', 'test' or 'all' . 'train' will read from 60,000 - train samples, 'test' will read from 10,000 test samples, 'all' will read from all 70,000 samples. - (default=None, will read all samples) - num_samples (int, optional): The number of images to be included in the dataset - (default=None, will read all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, will use value set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str):数据集根目录的路径。 + usage (str, optional):数据集的用法,可以是'train'、'test'或'all'。'train'将读取60,000个训练样本, + 'test'将读取10,000个测试样本,'all'将读取所有70,000个样本(默认=None,表示读取所有样本)。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示读取所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示每 + 个分片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):使用tensor caching服务加速数据集处理(默认=None,表示不使用缓存)。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -3019,24 +2986,12 @@ class MnistDataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> mnist_dataset_dir = "/path/to/mnist_dataset_directory" @@ -3051,6 +3006,8 @@ class MnistDataset(MappableDataset, VisionBaseDataset): The MNIST database of handwritten digits has a training set of 60,000 examples, and a test set of 10,000 examples. It is a subset of a larger set available from NIST. The digits have been size-normalized and centered in a fixed-size image. + MNIST数据集包含60,000个训练示例和10,000个测试示例。它是NIST提供的更大数据集中的一个子集。手写数字已 + 被尺寸归一化和居中在固定大小的图像上。 Here is the original MNIST dataset structure. You can unzip the dataset files into this directory structure and read by MindSpore's API. @@ -3084,41 +3041,39 @@ class MnistDataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集目录 self.dataset_dir = dataset_dir + # 初始化数据集使用类型 self.usage = replace_none(usage, "all") def parse(self, children=None): + # 解析子节点,返回MnistNode实例 return cde.MnistNode(self.dataset_dir, self.usage, self.sampler) - class OmniglotDataset(MappableDataset): """ A source dataset that reads and parses the Omniglot dataset. + 用于读取和解析Omniglot数据集。 The generated dataset has two columns :py:obj:`[image, label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`label` is a scalar of the uint32 type. + 生成的数据集具有两个列:[image, label]。 + image列的类型为uint8, + label列的类型为uint32的标量。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - background(bool, optional): Use the background dataset or the evaluation dataset - (default=None, will use the background dataset). - num_samples (int, optional): The number of images to be included in the dataset - (default=None, all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - decode (bool, optional): Decode the images after reading (default=False). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, 'num_samples' reflects - the max sample number of per shard. - shard_id (int, optional): The shard ID within num_shards (default=None). This - argument can only be specified when num_shards is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing - (default=None, which means no cache is used). + dataset_dir (str):数据集根目录的路径。 + background(bool, optional):使用背景数据集还是评估数据集(默认=None,使用背景数据集)。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示包含所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + decode (bool, optional):读取图像后是否解码(默认=False)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示每个 + 分片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):使用tensor caching服务加速数据集处理(默认=None,表示不使用缓存)。 Raises: RuntimeError: If `sampler` and `shuffle` are specified at the same time. @@ -3138,24 +3093,12 @@ class OmniglotDataset(MappableDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> omniglot_dataset_dir = "/path/to/omniglot_dataset_directory" @@ -3168,6 +3111,8 @@ class OmniglotDataset(MappableDataset): of hand-written characters with 1623 characters and 20 examples for each character. These characters are collected based upon 50 alphabets from different countries. It contains both images and strokes data. Stroke data are coordinates with time in milliseconds. + Omniglot是一个用于开发更具有人类特征的学习算法的巨大数据集。Omniglot包含1623个手写字符,每个字符有20个示例。这些字符是从不同 + 国家的50个字母收集的。它包含图像和笔划数据。笔划数据是坐标和时间(以毫秒为单位)。 You can unzip the original Omniglot dataset files into this directory structure and read by MindSpore's API. @@ -3212,48 +3157,49 @@ class OmniglotDataset(MappableDataset): decode=False, sampler=None, num_shards=None, shard_id=None, cache=None): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 设置数据集的目录路径 self.dataset_dir = dataset_dir + # 设置是否使用背景图片 self.background = replace_none(background, True) + # 设置是否对图像解码 self.decode = replace_none(decode, False) def parse(self, children=None): + # 解析函数,返回一个OmniglotNode对象 return cde.OmniglotNode(self.dataset_dir, self.background, self.decode, self.sampler) class PhotoTourDataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses the PhotoTour dataset. + 用于读取和解析PhotoTourDataset。 The generated dataset with different usage has different output columns. If train, the generated dataset has one column :py:obj:`[image]`, else three columns :py:obj:`[image1, image2, matches]`. + 如果用于训练,则生成的数据集只有一列[image], + 否则有三列[image1, image2, matches]。 The tensor of column :py:obj:`image`, :py:obj:`image1` and :py:obj:`image2` is of the uint8 type. The tensor of column :py:obj:`matches` is a scalar of the uint32 type. + image、image1和image2列的类型为uint8, + matches列的类型为uint32的标量。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - name (str): Name of the dataset to load, - should be one of 'notredame', 'yosemite', 'liberty', 'notredame_harris', - 'yosemite_harris' or 'liberty_harris'. - usage (str, optional): Usage of the dataset, can be 'train' or 'test' (Default=None, will be set to 'train'). - When usage is 'train', number of samples for each `name` is - {'notredame': 468159, 'yosemite': 633587, 'liberty': 450092, 'liberty_harris': 379587, - 'yosemite_harris': 450912, 'notredame_harris': 325295}. - When usage is 'test', will read 100,000 samples for testing. - num_samples (int, optional): The number of images to be included in the dataset - (default=None, will read all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, will use value set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str):数据集根目录的路径。 + name (str):数据集的名称,应该是 'notredame'、'yosemite'、'liberty'、'notredame_harris'、 + 'yosemite_harris' 或 'liberty_harris' 之一。 + usage (str, optional):数据集的用法,可以是 'train' 或 'test'(默认=None,将被设置为'train')。 + 当usage为'train'时,每个name的样本数分别为:{'notredame': 468159, 'yosemite': 633587, 'liberty': 450092, + 'liberty_harris': 379587, 'yosemite_harris': 450912, 'notredame_harris': 325295}。当usage为'test'时, + 将读取100,000个样本进行测试。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示读取所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示每 + 个分片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):使用tensor caching服务加速数据集处理(默认=None,表示不使用缓存)。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -3279,24 +3225,12 @@ class PhotoTourDataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> # Read 3 samples from PhotoTour dataset. @@ -3311,6 +3245,8 @@ class PhotoTourDataset(MappableDataset, VisionBaseDataset): The data is taken from Photo Tourism reconstructions from Trevi Fountain (Rome), Notre Dame (Paris) and Half Dome (Yosemite). Each dataset consists of a series of corresponding patches, which are obtained by projecting 3D points from Photo Tourism reconstructions back into the original images. + 数据集包括从罗马 Tribune Fountain、巴黎圣母院和优胜美地half dome的Photo Tourism重建中获取的系列对应块。这些块是通过 + 将3D点从Photo Tourism重建中投影回原始图像而获得的。 The dataset consists of 1024 x 1024 bitmap (.bmp) images, each containing a 16 x 16 array of image patches. Each patch is sampled as 64 x 64 grayscale, with a canonical scale and orientation. For details of how the scale @@ -3319,6 +3255,9 @@ class PhotoTourDataset(MappableDataset, VisionBaseDataset): top to bottom in each bitmap image. The first number on each row of info.txt is the 3D point ID from which that patch was sampled -- patches with the same 3D point ID are projected from the same 3D point (into different images). The second number in info.txt corresponds to the image from which the patch was sampled, and is not used at present. + 数据集由1024x1024像素的灰度位图(.bmp)图像组成,每个图像包含一个16x16大小的图像块。每个块被采样为64x64灰度,具有规范的缩放和 + 方向。关于如何建立缩放和方向的信息,请参阅论文。关联的元数据文件info.txt包含匹配信息。每行info.txt对应一个单独的块,从左到右 + 和从上到下在每张位图图像上顺序排列。info.txt中的第一数字是从该块采样3D点的ID。第二数字info.txt与当前未使用。 You can unzip the original PhotoTour dataset files into this directory structure and read by MindSpore's API. @@ -3366,42 +3305,44 @@ class PhotoTourDataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集路径 self.dataset_dir = dataset_dir + # 初始化数据集名称 self.name = name + # 初始化数据集使用情况 self.usage = replace_none(usage, "train") def parse(self, children=None): + 解析子节点,返回一个PhotoTourNode实例 return cde.PhotoTourNode(self.dataset_dir, self.name, self.usage, self.sampler) class Places365Dataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses the Places365 dataset. + 用于读取和解析Places365数据集。 The generated dataset has two columns :py:obj:`[image, label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`label` is a scalar of the uint32 type. + 生成的数据集具有两个列:[image, label]。 + image列的类型为uint8, + label列的类型为uint32的标量。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Usage of this dataset, can be 'train-standard', 'train-challenge' or 'val' - (default=None, will be set to 'train-standard'). - small (bool, optional): Use 256 * 256 images (True) or high resolution images (False) (default=False). - decode (bool, optional): Decode the images after reading (default=True). - num_samples (int, optional): The number of images to be included in the dataset - (default=None, will read all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, will use value set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str):数据集根目录的路径。 + usage (str, optional):数据集的用法,可以是'train-standard'、'train-challenge'或'val' + (默认=None,将被设置为'train-standard')。 + small (bool, optional):使用256 * 256像素的图像(True)还是高分辨率图像(False)(默认=False)。 + decode (bool, optional):读取图像后是否解码(默认=True)。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示读取所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示每 + 个分片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):使用tensor caching服务加速数据集处理(默认=None,表示不使用缓存)。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -3425,24 +3366,12 @@ class Places365Dataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> place365_dataset_dir = "/path/to/place365_dataset_directory" @@ -3463,6 +3392,10 @@ class Places365Dataset(MappableDataset, VisionBaseDataset): will add other kinds of annotation on the Places365-Standard in the future. Places365-Challenge is the competition set of Places2 Database, which has 6.2 million extra images compared to the Places365-Standard. The Places365-Challenge will be used for the Places Challenge 2016. + Convolutional neural networks(CNNs)在训练 Places2 数据库时可以用于场景识别和通用深度视觉特征的提取。作者发布了 + Places365-Standard 和 Places365-Challenge 的数据集,供公共使用。Places365-Standard 是 Places2 数据库的核心集, + 已被用于训练 Places365-CNNs。作者未来还将添加其他类型的注释到 Places365-Standard。Places365-Challenge 是 Places2 + 数据库的挑战集,比 Places365-Standard 多 6.2 亿张图像。Places365-Challenge 将用于 2016 年 Places 挑战赛。 You can unzip the original Places365 dataset files into this directory structure and read by MindSpore's API. @@ -3507,43 +3440,47 @@ class Places365Dataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 获取数据集的根目录 self.dataset_dir = os.path.abspath(dataset_dir) + # 替换usage为None的值为"train-standard" self.usage = replace_none(usage, "train-standard") + # small为布尔值,表示是否使用小数据集 self.small = small + # decode为布尔值,表示是否解码 self.decode = decode + # 定义parse函数,用于解析children参数 def parse(self, children=None): + # 返回cde.Places365Node对象,该对象接收self.dataset_dir, self.usage, self.small, self.decode, self.sampler参数 return cde.Places365Node(self.dataset_dir, self.usage, self.small, self.decode, self.sampler) class QMnistDataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses the QMNIST dataset. + 用于读取和解析QMnist数据集。 The generated dataset has two columns :py:obj:`[image, label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`label` is a scalar when `compat` is True else a tensor both of the uint32 type. + 生成的数据集具有两个列:[image, label]。 + image列的类型为uint8, + label列的类型为uint32的标量(当compat为True时)或张量(当compat为False时)。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Usage of this dataset, can be 'train', 'test', 'test10k', 'test50k', 'nist' - or 'all' (default=None, will read all samples). - compat (bool, optional): Whether the label for each example is class number (compat=True) or the full QMNIST - information (compat=False) (default=True). - num_samples (int, optional): The number of images to be included in the dataset - (default=None, will read all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, will use value set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str):数据集根目录的路径。 + usage (str, optional):数据集的用法,可以是'train'、'test'、'test10k'、'test50k'、'nist'或'all' + (默认=None,表示读取所有样本)。 + compat (bool, optional):标签对于每个示例是类别编号(compat=True)还是完整的QMNIST信息(compat=False) + (默认=True)。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示读取所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示每个 + 分片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):使用tensor caching服务加速数据集处理(默认=None,表示不使用缓存)。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -3565,24 +3502,12 @@ class QMnistDataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> qmnist_dataset_dir = "/path/to/qmnist_dataset_directory" @@ -3599,6 +3524,8 @@ class QMnistDataset(MappableDataset, VisionBaseDataset): Through an iterative process, researchers tried to generate an additional 50k images of MNIST-like data. They started with a reconstruction process given in the paper and used the Hungarian algorithm to find the best matches between the original MNIST samples and their reconstructed samples. + QMNIST数据集是从NIST特殊数据库19中原始数据中获得的,目的是尽可能地与MNIST预处理相接近。通过迭代过程,研究人员尝试生成5万 + 个与MNIST类似的数据。他们采用论文中的重建过程,并使用霍夫曼算法找到原始MNIST样本和重建样本之间的最佳匹配。 Here is the original QMNIST dataset structure. You can unzip the dataset files into this directory structure and read by MindSpore's API. @@ -3633,37 +3560,35 @@ class QMnistDataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集目录 self.dataset_dir = dataset_dir + # 初始化数据集使用类型 self.usage = replace_none(usage, "all") + # 初始化数据集兼容性 self.compat = compat def parse(self, children=None): + # 解析子节点,返回QMnistNode对象 return cde.QMnistNode(self.dataset_dir, self.usage, self.compat, self.sampler) class RandomDataset(SourceDataset, VisionBaseDataset): """ A source dataset that generates random data. + 该数据集用于生成随机数据。 Args: - total_rows (int, optional): Number of samples for the dataset to generate - (default=None, number of samples is random). - schema (Union[str, Schema], optional): Path to the JSON schema file or schema object (default=None). - If the schema is not provided, the random dataset generates a random schema. - columns_list (list[str], optional): List of columns to be read (default=None, read all columns) - num_samples (int, optional): The number of samples to be included in the dataset - (default=None, all samples). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, 'num_samples' reflects - the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. + total_rows (int, optional):数据集生成的总样本数(默认=None,随机生成样本数)。 + schema (Union[str, Schema], optional):JSON模式文件路径或模式对象(默认=None)。如果未提供模式, + 随机数据集将生成随机模式。 + columns_list (list[str], optional):要读取的列的列表(默认=None,读取所有列)。 + num_samples (int, optional):数据集中要包含的样本数(默认=None,包含所有样本)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + cache (DatasetCache, optional):使用tensor caching服务加速数据集处理(默认=None,表示不使用缓存)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示每 + 个分片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 """ @check_random_dataset @@ -3671,13 +3596,19 @@ class RandomDataset(SourceDataset, VisionBaseDataset): cache=None, shuffle=None, num_shards=None, shard_id=None): super().__init__(num_parallel_workers=num_parallel_workers, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 设置数据集中的总行数 self.total_rows = total_rows + # 如果数据集架构不为空 if schema is not None: + # 将其设置为Schema.get_num_rows(schema) self.total_rows = replace_none(total_rows, Schema.get_num_rows(schema)) + # 如果数据集架构为空,则设置为空 self.schema = schema + # 设置数据集的列列表 self.columns_list = replace_none(columns_list, []) def parse(self, children=None): + # 解析函数,用于生成随机节点 schema = self.schema.cpp_schema if isinstance(self.schema, Schema) else self.schema return cde.RandomNode(self.total_rows, schema, self.columns_list) @@ -3685,59 +3616,100 @@ class RandomDataset(SourceDataset, VisionBaseDataset): class _SBDataset: """ Dealing with the data file with .mat extension, and return one row in tuple (image, task) each time. + 处理带有.mat扩展名的数据文件,并返回每个行的元组(image, task)。 """ def __init__(self, dataset_dir, task, usage, decode): + # 初始化列列表 self.column_list = ['image', 'task'] + # 设置任务类型 self.task = task + # 设置图片路径 self.images_path = os.path.join(dataset_dir, 'img') + # 设置类别路径 self.cls_path = os.path.join(dataset_dir, 'cls') + # 加载mat文件 self._loadmat = loadmat + # 设置类别数 self.categories = 20 + # 设置是否解码图像 self.decode = replace_none(decode, False) + # 如果usage的值为"all" if usage == "all": + # 创建一个空列表image_names image_names = [] + # 遍历两个字符串"train"和"val",表示训练集和验证集 for item in ["train", "val"]: + # 对于每个字符串,代码将创建一个路径usage_path,将其连接到dataset_dir usage_path = os.path.join(dataset_dir, item + '.txt') + # 检查该路径文件是否存在 if not os.path.exists(usage_path): + # 抛出异常 raise FileNotFoundError("SBDataset: {0} not found".format(usage_path)) + # 打开该文件并读取所有行 with open(usage_path, 'r') as f: + # 将每行的内容(去掉行末尾的换行符)添加到image_names列表中 image_names += [x.strip() for x in f.readlines()] + + # 如果usage的值不是"all" else: + # 创建一个名为usage_path的路径,将其连接到dataset_dir usage_path = os.path.join(dataset_dir, usage + '.txt') + # 检查该路径文件是否存在 if not os.path.exists(usage_path): + # 抛出异常 raise FileNotFoundError("SBDataset: {0} not found".format(usage_path)) + # 打开该文件并读取所有行 with open(usage_path, 'r') as f: + # 将每行的内容(去掉行末尾的换行符)添加到image_names列表中 image_names = [x.strip() for x in f.readlines()] + # 存储图像路径 self.images = [os.path.join(self.images_path, i + ".jpg") for i in image_names] + # 存储类别路径 self.clss = [os.path.join(self.cls_path, i + ".mat") for i in image_names] + # 检查self.images和self.clss的长度是否相等 if len(self.images) != len(self.clss): + # 不相等则抛出异常 raise ValueError("SBDataset: images count not equal to cls count") + # 获取数据 self._get_data = self._get_boundaries_data if self.task == "Boundaries" else self._get_segmentation_data + # 处理数据 self._get_item = self._get_decode_item if self.decode else self._get_undecode_item + # 获取边界数据 def _get_boundaries_data(self, mat_path): + # 加载mat_path所指定的.mat文件,并将结果存储在mat_data中 mat_data = self._loadmat(mat_path) + ''' + 使用列表推导式遍历self.categories个类别,对于每个类别,使用np.expand_dims方法 + 将边界数据扩展为具有单维轴的数组,并使用np.concatenate方法将所有类别的边界数据 + 连接在一起,最后返回连接后的边界数据 + ''' return np.concatenate([np.expand_dims(mat_data['GTcls'][0][self.task][0][i][0].toarray(), axis=0) for i in range(self.categories)], axis=0) + # 加载mat文件,并将其转换为PIL图片 def _get_segmentation_data(self, mat_path): mat_data = self._loadmat(mat_path) return Image.fromarray(mat_data['GTcls'][0][self.task][0]) + # 根据索引idx获取图像和类别 def _get_decode_item(self, idx): return Image.open(self.images[idx]).convert('RGB'), self._get_data(self.clss[idx]) + # 从文件中读取图像,并将其转换为np.uint8类型 def _get_undecode_item(self, idx): return np.fromfile(self.images[idx], dtype=np.uint8), self._get_data(self.clss[idx]) - + + # 返回数据集中的图像数量 def __len__(self): return len(self.images) + # 获取数据集中的特定图像和类别 def __getitem__(self, idx): return self._get_item(idx) @@ -3745,30 +3717,28 @@ class _SBDataset: class SBDataset(GeneratorDataset): """ A source dataset that reads and parses Semantic Boundaries Dataset. + 用于读取和解析Semantic Boundaries数据集。 The generated dataset has two columns: :py:obj:`[image, task]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`task` contains 20 images of the uint8 type if `task` is 'Boundaries' otherwise contains 1 image of the uint8 type. + 该数据集有两个列:[image, task]。 + image列的类型为uint8。 + task列分为两种情况:当task为'Boundaries'时,包含20个uint8类型的图像;当task为其他值时,包含1个uint8类型的图像。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - task (str, optional): Acceptable tasks include 'Boundaries' or 'Segmentation' (default= 'Boundaries'). - usage (str, optional): Acceptable usages include 'train', 'val', 'train_noval' and 'all' (default= 'all'). - num_samples (int, optional): The number of images to be included in the dataset. - (default=None, all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - shuffle (bool, optional): Whether to perform shuffle on the dataset (default=None, expected - order behavior shown in the table). - decode (bool, optional): Decode the images after reading (default=None). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, `num_samples` reflects - the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. + dataset_dir (str):数据集根目录的路径。 + task (str, optional):可接受的任务包括'Boundaries'或'Segmentation'(默认='Boundaries')。 + usage (str, optional):可接受的用法包括'train'、'val'、'train_noval'和'all'(默认='all')。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示包含所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + decode (bool, optional):读取图像后是否解码(默认=None)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示 + 每个分片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 Raises: RuntimeError: If `dataset_dir` is not valid or does not contain data files. @@ -3793,24 +3763,12 @@ class SBDataset(GeneratorDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> sb_dataset_dir = "/path/to/sb_dataset_directory" @@ -3832,6 +3790,9 @@ class SBDataset(GeneratorDataset): 2857 images' name in the val.txt and 5623 images' name in the train_noval.txt. The category cls/ contains the Segmentation and Boundaries results of category-level, the category inst/ catains the Segmentation and Boundaries results of instance-level. + Semantic Boundaries数据集包含11355张彩色图像。在train.txt中,有8498张图像的名称;在val.txt中,有2857张图像的名 + 称;在train_noval.txt中,有5623张图像的名称。该数据集包含两个类别级别的结果:cls/包含分割和边界结果的类别级别, + category inst/包含分割和边界结果的实例级别。 You can unzip the dataset files into the following structure and read by MindSpore's API: @@ -3866,9 +3827,12 @@ class SBDataset(GeneratorDataset): """ @check_sb_dataset + # 初始化函数,用于初始化数据集 def __init__(self, dataset_dir, task='Boundaries', usage='all', num_samples=None, num_parallel_workers=1, shuffle=None, decode=None, sampler=None, num_shards=None, shard_id=None): + # 初始化数据集 dataset = _SBDataset(dataset_dir, task, usage, decode) + # 调用父类初始化函数 super().__init__(dataset, column_names=dataset.column_list, num_samples=num_samples, num_parallel_workers=num_parallel_workers, shuffle=shuffle, sampler=sampler, num_shards=num_shards, shard_id=shard_id) @@ -3877,28 +3841,26 @@ class SBDataset(GeneratorDataset): class SBUDataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses the SBU dataset. + 用于读取与解析SBU数据集。 The generated dataset has two columns :py:obj:`[image, caption]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`caption` is of the string type. + 生成的数据集具有两个列:[image, caption]。 + image列的类型为uint8, + caption列的类型为string。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - decode (bool, optional): Decode the images after reading (default=False). - num_samples (int, optional): The number of images to be included in the dataset - (default=None, will read all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, will use value set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str):数据集根目录的路径。 + decode (bool, optional):读取图像后是否解码(默认=False)。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示读取所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示每个 + 分片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):使用tensor caching服务加速数据集处理(默认=None,表示不使用缓存)。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -3920,24 +3882,12 @@ class SBUDataset(MappableDataset, VisionBaseDataset): * - Parameter 'sampler' - Parameter 'shuffle' - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> sbu_dataset_dir = "/path/to/sbu_dataset_directory" @@ -3948,6 +3898,7 @@ class SBUDataset(MappableDataset, VisionBaseDataset): SBU dataset is a large captioned photo collection. It contains one million images with associated visually relevant captions. + SBU数据集是一个包含一百万张图片和与之相关的视觉相关描述的数据集。 You should manually download the images using official download.m by replacing 'urls{i}(24, end)' with 'urls{i}(24:1:end)' and keep the directory as below. @@ -3981,38 +3932,39 @@ class SBUDataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集路径 self.dataset_dir = dataset_dir + # 初始化解码参数 self.decode = replace_none(decode, False) + # 定义parse函数,用于解析子节点 def parse(self, children=None): + # 返回一个SBUNode对象,其中dataset_dir为数据集路径,decode为解码器,sampler为采样器 return cde.SBUNode(self.dataset_dir, self.decode, self.sampler) class SemeionDataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses Semeion dataset. - + 用于读取与解析Semeion数据集。 + The generated dataset has two columns :py:obj:`[image, label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`label` is a scalar of the uint32 type. + 生成的数据集具有两个列:[image, label]。 + image列的类型为uint8, + label列的类型为uint32,并且是一个标量。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - num_samples (int, optional): The number of samples to be included in the dataset - (default=None, will read all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - shuffle (bool, optional): Whether to perform shuffle on the dataset (default=None, expected - order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, `num_samples` reflects - the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing - (default=None, which means no cache is used). + dataset_dir (str):数据集根目录的路径。 + num_samples (int, optional):数据集中样本的数量(默认=None,表示读取所有样本)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示每个 + 分片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):使用tensor caching服务加速数据集处理(默认=None,表示不使用缓存)。 Raises: ValueError: If `num_parallel_workers` exceeds the max thread numbers. @@ -4033,24 +3985,12 @@ class SemeionDataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> semeion_dataset_dir = "/path/to/semeion_dataset_directory" @@ -4071,6 +4011,8 @@ class SemeionDataset(MappableDataset, VisionBaseDataset): The dataset was created by Tactile Srl, Brescia, Italy (http://www.tattile.it) and donated in 1994 to Semeion Research Center of Sciences of Communication, Rome, Italy (http://www.semeion.it), for machine learning research. + Semeion数据集是由意大利米兰的Tactile Srl公司创建的,并于1994年捐赠给意大利罗马的科学 Communication 中心 + (Semeion Research Center of Sciences of Communication),用于机器学习研究。 This dataset consists of 1593 records (rows) and 256 attributes (columns). Each record represents a handwritten digit, originally scanned with a resolution of 256 grey scale. Each pixel of the each @@ -4078,6 +4020,9 @@ class SemeionDataset(MappableDataset, VisionBaseDataset): (setting to 0 every pixel whose value was under the value 127 of the grey scale (127 included) and setting to 1 each pixel whose original value in the grey scale was over 127). Finally, each binary image was scaled again into a 16x16 square box (the final 256 binary attributes). + 该数据集由1593行记录和256个属性组成。每行记录代表一个手写数字,原始扫描图像的分辨率是256灰度。每个原始扫描图像的 + 每个像素首先被拉伸,然后将其值缩放到0到1之间(将所有灰度值低于127的像素设置为0,将所有灰度值高于127的像素设置为1)。 + 最后,每个二进制图像将被缩放到16x16的方框(最终256个二进制属性)。 .. code-block:: @@ -4102,43 +4047,44 @@ class SemeionDataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集路径 self.dataset_dir = dataset_dir + # 定义parse函数,用于解析子节点 def parse(self, children=None): + # 返回一个SemeionNode对象 return cde.SemeionNode(self.dataset_dir, self.sampler) class STL10Dataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses STL10 dataset. + 用于读取与解析STL10数据集。 The generated dataset has two columns: :py:obj:`[image, label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`label` is of a scalar of int32 type. + 生成的数据集具有两个列:[image, label]。 + image列的类型为uint8, + label列的类型为int32,并且是一个标量。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Usage of this dataset, can be 'train', 'test', - 'unlabeled', 'train+unlabeled' or 'all' . 'train' will read from 5,000 - train samples, 'test' will read from 8,000 test samples, - 'unlabeled' will read from all 100,000 samples, and 'train+unlabeled' - will read from 105000 samples, 'all' will read all the samples - (default=None, all samples). - num_samples (int, optional): The number of images to be included in the dataset. - (default=None, all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - shuffle (bool, optional): Whether to perform shuffle on the dataset (default=None, expected - order behavior shown in the table). - sampler (Sampler, optional): Object used to choose samples from the - dataset (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, 'num_samples' reflects - the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str):数据集根目录的路径。 + usage (str, optional):数据集的用法,可以是'train'、'test'、'unlabeled'、'train+unlabeled'或 + 'all'(默认=None,表示读取所有样本)。 + 'train':从5,000个训练样本中读取。 + 'test':从8,000个测试样本中读取。 + 'unlabeled':从100,000个未标注样本中读取。 + 'train+unlabeled':从105,000个样本中读取(包括训练和未标注样本)。 + 'all':读取所有样本。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示读取所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示 + 每个分片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):使用tensor caching服务加速数据集处理(默认=None,表示不使用缓存)。 Raises: RuntimeError: If `dataset_dir` is not valid or does not exist or does not contain data files. @@ -4161,24 +4107,12 @@ class STL10Dataset(MappableDataset, VisionBaseDataset): * - Parameter 'sampler' - Parameter 'shuffle' - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> stl10_dataset_dir = "/path/to/stl10_dataset_directory" @@ -4199,6 +4133,11 @@ class STL10Dataset(MappableDataset, VisionBaseDataset): Images are 96x96 pixels, color. 500 training images, 800 test images per class and 100000 unlabeled images. Labels are 0-indexed, and unlabeled images have -1 as their labels. + STL10数据集包括10个类别:飞机、鸟、车、猫、鹿、狗、马、猴、船、卡车。 + STL10数据集受到了CIFAR-10数据集的启发。 + STL10数据集中的图片大小为96x96像素,为彩色。 + STL10数据集包括500张训练图片,每个类别800张测试图片,以及100,000张未标记的图片。 + 标签从0开始索引,未标记的图片的标签为-1。 Here is the original STL10 dataset structure. You can unzip the dataset files into this directory structure and read by MindSpore's API. @@ -4237,45 +4176,66 @@ class STL10Dataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集目录 self.dataset_dir = dataset_dir + # 初始化数据集使用类型 self.usage = replace_none(usage, "all") def parse(self, children=None): + # 解析STL10数据集,返回STL10Node对象 return cde.STL10Node(self.dataset_dir, self.usage, self.sampler) class _SVHNDataset: """ Mainly for loading SVHN Dataset, and return two rows each time. + 加载SVHN数据集,并每次返回两行。 """ def __init__(self, dataset_dir, usage): + # 获取数据集的路径 self.dataset_dir = os.path.realpath(dataset_dir) + # 获取使用模式 self.usage = usage + # 定义列名 self.column_names = ["image", "label"] + # 定义使用模式列表 self.usage_all = ["train", "test", "extra"] + # 定义数据和标签 self.data = np.array([], dtype=np.uint8) self.labels = np.array([], dtype=np.uint32) + # 如果使用模式为all,则遍历使用模式列表,加载数据和标签 if self.usage == "all": for _usage in self.usage_all: data, label = self._load_mat(_usage) + # 将加载的数据和标签拼接起来 self.data = np.concatenate((self.data, data)) if self.data.size else data self.labels = np.concatenate((self.labels, label)) if self.labels.size else label + # 如果使用模式不为all,则直接加载数据和标签 else: self.data, self.labels = self._load_mat(self.usage) + # 加载SVHN数据集中的mat文件,并将数据和标签转换为所需的格式 def _load_mat(self, mode): + # filename变量被分配为mode + "_32x32.mat",其中mode是数据集的类型(训练、验证或测试)。 filename = mode + "_32x32.mat" + # 使用loadmat函数加载mat文件 mat_data = loadmat(os.path.join(self.dataset_dir, filename)) + # data变量被分配为将mat文件中的数据转置后的结果,格式为[3, 0, 1, 2]。 data = np.transpose(mat_data['X'], [3, 0, 1, 2]) + # label变量被分配为mat文件中的标签,类型为np.uint32,并将其转换为标量。 label = mat_data['y'].astype(np.uint32).squeeze() + # 使用np.place函数将标签中的值为10的元素替换为0 np.place(label, label == 10, 0) + # 返回图像与标签 return data, label + # 用于获取数据集中的数据和标签 def __getitem__(self, index): return self.data[index], self.labels[index] + # 用于获取数据集中的数据数量 def __len__(self): return len(self.data) @@ -4283,26 +4243,32 @@ class _SVHNDataset: class SVHNDataset(GeneratorDataset): """ A source dataset that reads and parses SVHN dataset. + 用于读取与解析SVHN数据集 The generated dataset has two columns: :py:obj:`[image, label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`label` is of a scalar of uint32 type. + 生成的数据集具有两个列:[image, label]。 + image列的tensor类型为uint8。 + label列的tensor类型为标量uint32类型。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Specify the 'train', 'test', 'extra' or 'all' parts of dataset - (default=None, will read all samples). - num_samples (int, optional): The number of samples to be included in the dataset (default=None, all images). - num_parallel_workers (int, optional): Number of subprocesses used to fetch the dataset in parallel (default=1). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset. Random accessible input is required. - (default=None, expected order behavior shown in the table). - sampler (Union[Sampler, Iterable], optional): Object used to choose samples from the dataset. Random accessible - input is required (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - Random accessible input is required. When this argument is specified, 'num_samples' reflects the max - sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This argument must be specified only - when num_shards is also specified. Random accessible input is required. + dataset_dir (str):数据集根目录的路径。 + usage (str, optional):指定数据集的哪个部分,可以是'train'、'test'、'extra'或'all' + (默认=None,表示读取所有样本)。 + 'train':读取数据集的训练部分。 + 'test':读取数据集的测试部分。 + 'extra':读取数据集的附加部分。 + 'all':读取数据集的所有部分。 + num_samples (int, optional):数据集中样本的数量(默认=None,表示读取所有样本)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=1,使用一个线程)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。需要随机可访问的 + 输入。 + sampler (Union[Sampler, Iterable], optional):用于从数据集中选择样本的对象。需要随机可访问的输入 + (默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。需要随机可访问的输入。当此参数指 + 定时,num_samples表示每个分片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。需要随机可访问的输入。 Raises: RuntimeError: If `dataset_dir` is not valid or does not exist or does not contain data files. @@ -4325,24 +4291,12 @@ class SVHNDataset(GeneratorDataset): * - Parameter 'sampler' - Parameter 'shuffle' - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> svhn_dataset_dir = "/path/to/svhn_dataset_directory" @@ -4353,6 +4307,9 @@ class SVHNDataset(GeneratorDataset): SVHN dataset consists of 10 digit classes. SVHN is obtained from house numbers in Google Street View images. 73257 digits for training, 26032 digits for testing, and 531131 additional extra training data. + SVHN数据集包括10个数字类别。 + SVHN是从Google街景图片中的房屋数字获得的。 + 训练集有73257个数字,测试集有26032个数字,以及额外的531131个额外的训练数据。 Here is the original SVHN dataset structure. You can unzip the dataset files into this directory structure and read by MindSpore's API. @@ -4383,10 +4340,14 @@ class SVHNDataset(GeneratorDataset): @check_svhn_dataset def __init__(self, dataset_dir, usage=None, num_samples=None, num_parallel_workers=1, shuffle=None, sampler=None, num_shards=None, shard_id=None): + # 获取dataset_dir的绝对路径 self.dataset_dir = os.path.realpath(dataset_dir) + # 如果usage为空,则设置usage为all self.usage = replace_none(usage, "all") + # 创建_SVHNDataset实例 dataset = _SVHNDataset(self.dataset_dir, self.usage) + # 调用父类构造函数 super().__init__(dataset, column_names=dataset.column_names, num_samples=num_samples, num_parallel_workers=num_parallel_workers, shuffle=shuffle, sampler=sampler, num_shards=num_shards, shard_id=shard_id) @@ -4395,36 +4356,31 @@ class SVHNDataset(GeneratorDataset): class USPSDataset(SourceDataset, VisionBaseDataset): """ A source dataset that reads and parses the USPS dataset. + 用于读取与解析USPS数据集。 The generated dataset has two columns: :py:obj:`[image, label]`. The tensor of column :py:obj:`image` is of the uint8 type. The tensor of column :py:obj:`label` is of a scalar of uint32 type. + 生成的数据集具有两个列:[image, label]。 + image列的tensor类型为uint8。 + label列的tensor类型为标量uint32类型。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Usage of this dataset, can be 'train', 'test' or 'all'. 'train' will read from 7,291 - train samples, 'test' will read from 2,007 test samples, 'all' will read from all 9,298 samples. - (default=None, will read all samples) - num_samples (int, optional): The number of images to be included in the dataset - (default=None, will read all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, will use value set in the config). - shuffle (Union[bool, Shuffle level], optional): Perform reshuffling of the data every epoch - (default=Shuffle.GLOBAL). - If shuffle is False, no shuffling will be performed; - If shuffle is True, the behavior is the same as setting shuffle to be Shuffle.GLOBAL - Otherwise, there are two levels of shuffling: - - - Shuffle.GLOBAL: Shuffle both the files and samples. - - - Shuffle.FILES: Shuffle files only. - - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the max sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). + dataset_dir (str):数据集根目录的路径。 + usage (str, optional):数据集的用法,可以是'train'、'test'或'all'。'train'将读取7,291个训练样本, + 'test'将读取2,007个测试样本,'all'将读取所有9,298个样本(默认=None,表示读取所有样本)。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示读取所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (Union[bool, Shuffle level], optional):在每个epoch对数据进行重新洗牌(默认=Shuffle.GLOBAL)。 + 如果shuffle为False,则不会进行洗牌; + 如果shuffle为True,则行为与设置shuffle为Shuffle.GLOBAL相同; + 否则,有两种级别的洗牌: + Shuffle.GLOBAL:同时打乱文件和样本。 + Shuffle.FILES:仅打乱文件。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示每个 + 分片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):使用tensor caching服务加速数据集处理(默认=None,表示不使用缓存)。 Raises: RuntimeError: If `dataset_dir` is not valid or does not exist or does not contain data files. @@ -4449,6 +4405,8 @@ class USPSDataset(SourceDataset, VisionBaseDataset): USPS is a digit dataset automatically scanned from envelopes by the U.S. Postal Service containing a total of 9,298 16×16 pixel grayscale samples. The images are centered, normalized and show a broad range of font styles. + USPS是一个从信封自动扫描得到的digit数据集,共包含9298个16x16像素灰度图像样本。 + 图像被居中、标准化,展示了广泛的字体样式。 Here is the original USPS dataset structure. You can download and unzip the dataset files into this directory structure and read by MindSpore's API. @@ -4481,11 +4439,13 @@ class USPSDataset(SourceDataset, VisionBaseDataset): num_shards=None, shard_id=None, cache=None): super().__init__(num_parallel_workers=num_parallel_workers, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) - + # 设置数据集路径 self.dataset_dir = dataset_dir + # 设置数据集用法 self.usage = replace_none(usage, "all") def parse(self, children=None): + #解析函数,用于解析children return cde.USPSNode(self.dataset_dir, self.usage, self.num_samples, self.shuffle_flag, self.num_shards, self.shard_id) @@ -4493,42 +4453,44 @@ class USPSDataset(SourceDataset, VisionBaseDataset): class VOCDataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses VOC dataset. + 用于读取与解析VOC数据集。 The generated dataset with different task setting has different output columns: - task = :py:obj:`Detection`, output columns: :py:obj:`[image, dtype=uint8]`, :py:obj:`[bbox, dtype=float32]`, \ :py:obj:`[label, dtype=uint32]`, :py:obj:`[difficult, dtype=uint32]`, :py:obj:`[truncate, dtype=uint32]`. - task = :py:obj:`Segmentation`, output columns: :py:obj:`[image, dtype=uint8]`, :py:obj:`[target,dtype=uint8]`. + 当任务设置为Detection时,输出列包括: + image:uint8类型图像 + bbox:float32类型边界框 + label:uint32类型标签 + difficult:uint32类型难度 + truncate:uint32类型截断 + + 当任务设置为Segmentation时,输出列包括 + image:uint8类型图像 + target:uint8类型目标 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - task (str, optional): Set the task type of reading voc data, now only support 'Segmentation' or 'Detection' - (default= 'Segmentation'). - usage (str, optional): Set the task type of ImageSets(default= 'train'). If task is 'Segmentation', image and - annotation list will be loaded in ./ImageSets/Segmentation/usage + ".txt"; If task is 'Detection', image and - annotation list will be loaded in ./ImageSets/Main/usage + ".txt"; if task and usage are not set, image and - annotation list will be loaded in ./ImageSets/Segmentation/train.txt as default. - class_indexing (dict, optional): A str-to-int mapping from label name to index, only valid in - 'Detection' task (default=None, the folder names will be sorted alphabetically and each - class will be given a unique index starting from 0). - num_samples (int, optional): The number of images to be included in the dataset - (default=None, all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, number set in the config). - shuffle (bool, optional): Whether to perform shuffle on the dataset (default=None, expected - order behavior shown in the table). - decode (bool, optional): Decode the images after reading (default=False). - sampler (Sampler, optional): Object used to choose samples from the dataset - (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided - into (default=None). When this argument is specified, `num_samples` reflects - the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This - argument can only be specified when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing. - (default=None, which means no cache is used). - extra_metadata(bool, optional): Flag to add extra meta-data to row. If True, an additional column named - :py:obj:`[_meta-filename, dtype=string]` will be output at the end (default=False). + dataset_dir (str):数据集根目录的路径。 + task (str, optional):设置读取VOC数据的任务类型,目前仅支持'Segmentation'或'Detection' + (默认='Segmentation')。 + usage (str, optional):设置ImageSets的任务类型,如果任务是'Segmentation',则加载 + ./ImageSets/Segmentation/usage + ".txt";如果任务是'Detection',则加载./ImageSets/Main/usage + ".txt"; + 如果任务和用法未设置,则加载./ImageSets/Segmentation/train.txt作为默认值。 + class_indexing (dict, optional):用于将标签名称映射到索引的字典,仅在任务为'Detection'时有效 + (默认=None,标签名称将按字母顺序排序并分配唯一的索引从0开始)。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示读取所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + decode (bool, optional):读取图像后是否解码(默认=False)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示每个分 + 片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):使用tensor caching服务加速数据集处理(默认=None,表示不使用缓存)。 + extra_metadata(bool, optional):标志是否向行中添加额外的元数据。如果为True,则将在输出中添加一个名为 + [_meta-filename, dtype=string]的额外列(默认=False)。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -4558,24 +4520,12 @@ class VOCDataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> voc_dataset_dir = "/path/to/voc_dataset_directory" @@ -4603,6 +4553,8 @@ class VOCDataset(MappableDataset, VisionBaseDataset): object category recognition and detection, providing the vision and machine learning communities with a standard dataset of images and annotation, and standard evaluation procedures. + PASCAL Visual Object Classes (VOC)挑战赛,这是一个在视觉对象分类和检测领域的标 + 准数据集和评估标准。 You can unzip the original VOC-2012 dataset files into this directory structure and read by MindSpore's API. @@ -4656,14 +4608,22 @@ class VOCDataset(MappableDataset, VisionBaseDataset): cache=None, extra_metadata=False): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集路径 self.dataset_dir = dataset_dir + # 替换task为Segmentation self.task = replace_none(task, "Segmentation") + # 替换usage为train self.usage = replace_none(usage, "train") + # 替换class_indexing为空字典 self.class_indexing = replace_none(class_indexing, {}) + # 替换decode为False self.decode = replace_none(decode, False) + # 替换extra_metadata为False self.extra_metadata = extra_metadata + # 定义parse函数,用于解析VOCNode def parse(self, children=None): + # 返回VOCNode return cde.VOCNode(self.dataset_dir, self.task, self.usage, self.class_indexing, self.decode, self.sampler, self.extra_metadata) @@ -4695,6 +4655,7 @@ class VOCDataset(MappableDataset, VisionBaseDataset): class WIDERFaceDataset(MappableDataset, VisionBaseDataset): """ A source dataset that reads and parses WIDERFace dataset. + 用于读取与解析WIDERFace数据集 When usage is "train", "valid" or "all", the generated dataset has eight columns ["image", "bbox", "blur", "expression", "illumination", "occlusion", "pose", "invalid"]. When usage is "test", it only has one column @@ -4707,27 +4668,32 @@ class WIDERFaceDataset(MappableDataset, VisionBaseDataset): The tensor of column :py:obj:`occlusion` is a scalar of the uint32 type. The tensor of column :py:obj:`pose` is a scalar of the uint32 type. The tensor of column :py:obj:`invalid` is a scalar of the uint32 type. + 当使用"train"、"valid"或"all"时,生成的数据集具有八个列,分别是["image"、"bbox"、"blur"、 + "expression"、"illumination"、"occlusion"、"pose"、"invalid"]。当使用"test"时,只有["image"]这一列。 + + image列的类型为uint8, + bbox列的类型为uint32, + blur列的类型为uint32, + expression列的类型为uint32, + illumination列的类型为uint32, + occlusion列的类型为uint32, + pose列的类型为uint32, + invalid列的类型为uint32。 Args: - dataset_dir (str): Path to the root directory that contains the dataset. - usage (str, optional): Usage of this dataset, can be 'train', 'test', 'valid' or 'all'. 'train' will read - from 12,880 samples, 'test' will read from 16,097 samples, 'valid' will read from 3,226 test samples - and 'all' will read all 'train' and 'valid' samples (default=None, will be set to 'all'). - num_samples (int, optional): The number of images to be included in the dataset - (default=None, will read all images). - num_parallel_workers (int, optional): Number of workers to read the data - (default=None, will use value set in the config). - shuffle (bool, optional): Whether or not to perform shuffle on the dataset - (default=None, expected order behavior shown in the table). - decode (bool, optional): Decode the images after reading (default=False). - sampler (Sampler, optional): Object used to choose samples from the dataset - (default=None, expected order behavior shown in the table). - num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). - When this argument is specified, `num_samples` reflects the maximum sample number of per shard. - shard_id (int, optional): The shard ID within `num_shards` (default=None). This argument can only be specified - when `num_shards` is also specified. - cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing - (default=None, which means no cache is used). + dataset_dir (str):数据集根目录的路径。 + usage (str, optional):指定数据集的用法,可以是'train'、'test'、'valid'或'all'。'train'将读 + 取12,880个训练样本,'test'将读取16,097个测试样本,'valid'将读取3,226个验证样本,'all'将读取所有 + 'train'和'valid'样本(默认=None,表示读取所有样本)。 + num_samples (int, optional):数据集中图像的数量(默认=None,表示读取所有图像)。 + num_parallel_workers (int, optional):读取数据的工作线程数量(默认=None,根据配置文件设置)。 + shuffle (bool, optional):是否对数据集进行随机打乱(默认=None,根据配置文件设置)。 + decode (bool, optional):读取图像后是否解码(默认=False)。 + sampler (Sampler, optional):用于从数据集中选择样本的对象(默认=None,根据配置文件设置)。 + num_shards (int, optional):数据集将被分成多少个分片(默认=None)。当此参数指定时,num_samples表示 + 每个分片的最大样本数。 + shard_id (int, optional):分片ID(默认=None)。此参数只能在指定num_shards时使用。 + cache (DatasetCache, optional):使用tensor caching服务加速数据集处理(默认=None,表示不使用缓存)。 Raises: RuntimeError: If `dataset_dir` does not contain data files. @@ -4753,24 +4719,12 @@ class WIDERFaceDataset(MappableDataset, VisionBaseDataset): * - Parameter `sampler` - Parameter `shuffle` - Expected Order Behavior - * - None - - None - - random order - * - None - - True - - random order - * - None - - False - - sequential order - * - Sampler object - - None - - order defined by sampler - * - Sampler object - - True - - not allowed - * - Sampler object - - False - - not allowed + 当sampler为None时,shuffle为None时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为True时,数据集的顺序为随机顺序。 + 当sampler为None时,shuffle为False时,数据集的顺序为顺序顺序。 + 当sampler为对象时,shuffle为None时,数据集的顺序由sampler定义。 + 当sampler为对象时,shuffle为True时,不允许shuffle参数为True,因为sampler定义了数据的顺序。 + 当sampler为对象时,shuffle为False时,不允许shuffle参数为False,因为sampler定义了数据的顺序。 Examples: >>> wider_face_dir = "/path/to/wider_face_dataset" @@ -4783,7 +4737,10 @@ class WIDERFaceDataset(MappableDataset, VisionBaseDataset): The WIDERFace database of people faces has a training set of 12,880 samples, a testing set of 16,097 examples and a validating set of 3,226 examples. It is a subset of a larger set available from WIDER. The digits have been size-normalized and centered in a fixed-size image. - + WIDERFace数据集是一个包含人脸图像的子集,是从WIDER数据集中提取的。该数据集包括训练集12,880张图像,测试集16,097张图像和 + 验证集3,226张图像。 + 这些图像已经被标准化并中心化在固定大小的图像上。 + The following is the original WIDERFace dataset structure. You can unzip the dataset files into this directory structure and read by MindSpore's API. @@ -4836,9 +4793,14 @@ class WIDERFaceDataset(MappableDataset, VisionBaseDataset): super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + # 初始化数据集路径 self.dataset_dir = dataset_dir + # 初始化数据集使用类型 self.usage = replace_none(usage, "all") + # 初始化是否解码 self.decode = replace_none(decode, False) + # 定义一个parse函数,用于解析子节点 def parse(self, children=None): + # 返回一个WIDERFaceNode对象 return cde.WIDERFaceNode(self.dataset_dir, self.usage, self.decode, self.sampler) diff --git a/mindspore/python/mindspore/dataset/engine/graphdata.py b/mindspore/python/mindspore/dataset/engine/graphdata.py index d671c201e73..ec92eb1e2f2 100644 --- a/mindspore/python/mindspore/dataset/engine/graphdata.py +++ b/mindspore/python/mindspore/dataset/engine/graphdata.py @@ -16,22 +16,34 @@ graphdata.py supports loading graph dataset for GNN network training, and provides operations related to graph data. """ +# 导入atexit库,用于在程序退出时执行一些清理工作 import atexit +# 导入time库,用于处理时间相关的操作 import time +# 导入IntEnum类,用于定义整数枚举类型 from enum import IntEnum +# 导入numpy库,用于处理和计算数组和矩阵 import numpy as np +# 下面五个import用于处理图数据的部分 +# 导入mindspore._c_dataengine模块中的GraphDataClient from mindspore._c_dataengine import GraphDataClient +# 导入mindspore._c_dataengine模块中的GraphDataServer from mindspore._c_dataengine import GraphDataServer +# 导入mindspore._c_dataengine模块中的Tensor from mindspore._c_dataengine import Tensor +# 导入mindspore._c_dataengine模块中的SamplingStrategy(采样策略) from mindspore._c_dataengine import SamplingStrategy as Sampling +# 导入mindspore._c_dataengine模块中的OutputFormat(输出格式) from mindspore._c_dataengine import OutputFormat as Format - +# 导入一些验证函数,这些函数用于检查GNN数据处理过程中的参数是否合法 from .validators import check_gnn_graphdata, check_gnn_get_all_nodes, check_gnn_get_all_edges, \ check_gnn_get_nodes_from_edges, check_gnn_get_edges_from_nodes, check_gnn_get_all_neighbors, \ check_gnn_get_sampled_neighbors, check_gnn_get_neg_sampled_neighbors, check_gnn_get_node_feature, \ check_gnn_get_edge_feature, check_gnn_random_walk - +# 是一个整数枚举类型,用于定义图神经网络(GNN)中采样策略的类型 +# 在这个函数中有两个常量RANDOM和EDGE_WEIGHT +# 其中RANDOM:表示随机游走采样策略;EDGE_WEIGHT:表示基于边权重的采样策略。 class SamplingStrategy(IntEnum): """ Specifies the sampling strategy when execute `get_sampled_neighbors`. @@ -39,16 +51,21 @@ class SamplingStrategy(IntEnum): - RANDOM: Random sampling with replacement. - EDGE_WEIGHT: Sampling with edge weight as probability. """ + # 定义RANDOM为0 RANDOM = 0 + # 定义EDGE_WEIGHT为1 EDGE_WEIGHT = 1 - +# 定义一个字典DE_C_INTER_SAMPLING_STRATEGY,用于将SamplingStrategy枚举类型映射到Sampling枚举类型 DE_C_INTER_SAMPLING_STRATEGY = { + # 表示随机游走采样策略,将其映射到Sampling.DE_SAMPLING_RANDOM SamplingStrategy.RANDOM: Sampling.DE_SAMPLING_RANDOM, + # 表示基于边权重的采样策略,将其映射到Sampling.DE_SAMPLING_EDGE_WEIGHT SamplingStrategy.EDGE_WEIGHT: Sampling.DE_SAMPLING_EDGE_WEIGHT, } - +# 一个整数枚举类型,用于定义图神经网络(GNN)中输出数据的格式 +# NODE:表示输出节点特征;EDGE:表示输出边特征;GRAPH:表示输出图结构 class OutputFormat(IntEnum): """ Specifies the output storage format when execute `get_all_neighbors`. @@ -57,18 +74,25 @@ class OutputFormat(IntEnum): - COO: COO format. - CSR: CSR format. """ + # 定义NORMAL为0 NORMAL = 0 + # 定义COO为1 COO = 1 + # 定义CSR为2 CSR = 2 - +# 定义一个字典DE_C_INTER_OUTPUT_FORMAT,用于将OutputFormat枚举类型映射到Format枚举类型 DE_C_INTER_OUTPUT_FORMAT = { + # 表示输出格式为正常格式,将其映射到Format.DE_FORMAT_NORMAL OutputFormat.NORMAL: Format.DE_FORMAT_NORMAL, + # 表示输出格式为COO格式,将其映射到Format.DE_FORMAT_COO OutputFormat.COO: Format.DE_FORMAT_COO, + # 表示输出格式为CSR格式,将其映射到Format.DE_FORMAT_CSR OutputFormat.CSR: Format.DE_FORMAT_CSR, } - +# 用于表示图数据 +# GraphData类定义了图数据的属性,如节点数量、边数量、节点特征、边特征和邻接矩阵等,并提供了一些方法,用于获取和修改图数据的相关属性 class GraphData: """ Reads the graph dataset used for GNN training from the shared file and database. @@ -121,31 +145,50 @@ class GraphData: """ @check_gnn_graphdata + # 参数 + # dataset_file:表示图数据文件路径,用于指定图数据文件的位置,以便从文件中读取图数据 + # num_parallel_workers:表示并行工作线程数量,用于指定在读取图数据时使用的并行线程数量,以提高读取速度 + # working_mode:表示工作模式,用于指定图数据处理的工作模式,如本地模式、集群模式等 + # hostname:表示主机名,用于指定分布式模式下服务器的地址 + # port:表示服务器的端口号,用于指定分布式模式下服务器的端口号 + # num_client:表示客户端数量,用于指定分布式模式下客户端的数量 + # auto_shutdown:表示是否自动关闭,用于指定在完成图数据处理后是否自动关闭服务器 def __init__(self, dataset_file, num_parallel_workers=None, working_mode='local', hostname='127.0.0.1', port=50051, num_client=1, auto_shutdown=True): self._dataset_file = dataset_file self._working_mode = working_mode + # 如果num_parallel_workers参数为None if num_parallel_workers is None: + # 设置num_parallel_workers参数为1 num_parallel_workers = 1 def stop(): + # 调用self._graph_data.stop()在完成数据处理后关闭服务器 self._graph_data.stop() - + # 如果working_mode为'local'或'client' if working_mode in ['local', 'client']: + # 创建一个GraphDataClient对象,并将其赋值给self._graph_data self._graph_data = GraphDataClient(dataset_file, num_parallel_workers, working_mode, hostname, port) + # 用atexit模块注册一个stop函数,该函数在完成图数据处理后关闭服务器 atexit.register(stop) - + # 如果working_mode为'server' if working_mode == 'server': + # 创建一个GraphDataServer对象,并将其赋值给self._graph_data self._graph_data = GraphDataServer( dataset_file, num_parallel_workers, hostname, port, num_client, auto_shutdown) + # 用atexit模块注册一个stop函数,该函数在完成图数据处理后关闭服务器 atexit.register(stop) + # 使用try捕获异常 try: + # 循环检查图数据服务器是否已经停止 while self._graph_data.is_stopped() is not True: time.sleep(1) except KeyboardInterrupt: + # 抛出异常 raise Exception("Graph data server receives KeyboardInterrupt.") @check_gnn_get_all_nodes + # 用于获取图中所有指定类型的节点 def get_all_nodes(self, node_type): """ Get all nodes in the graph. @@ -162,11 +205,16 @@ class GraphData: Raises: TypeError: If `node_type` is not integer. """ + # 如果self._working_mode为'server' if self._working_mode == 'server': + # 抛出异常 raise Exception("This method is not supported when working mode is server.") + # 返回self._graph_data.get_all_nodes(node_type)获取图中所有指定类型的节点 + # 然后使用as_array()方法将节点转换为数组,并返回该数组 return self._graph_data.get_all_nodes(node_type).as_array() @check_gnn_get_all_edges + # 用于获取图中所有指定类型的边 def get_all_edges(self, edge_type): """ Get all edges in the graph. @@ -183,11 +231,16 @@ class GraphData: Raises: TypeError: If `edge_type` is not integer. """ + # 如果self._working_mode为'server' if self._working_mode == 'server': + # 抛出异常 raise Exception("This method is not supported when working mode is server.") + # 返回self._graph_data.get_all_nodes(node_type)获取图中所有指定类型的节点 + # 然后使用as_array()方法将节点转换为数组,并返回该数组 return self._graph_data.get_all_edges(edge_type).as_array() @check_gnn_get_nodes_from_edges + # 用于根据给定的边列表获取该边所连接的节点 def get_nodes_from_edges(self, edge_list): """ Get nodes from the edges. @@ -201,11 +254,16 @@ class GraphData: Raises: TypeError: If `edge_list` is not list or ndarray. """ + # 如果self._working_mode为'server' if self._working_mode == 'server': + # 抛出异常 raise Exception("This method is not supported when working mode is server.") + # 返回self._graph_data.get_all_nodes(node_type)获取图中所有指定类型的节点 + # 然后使用as_array()方法将节点转换为数组,并返回该数组 return self._graph_data.get_nodes_from_edges(edge_list).as_array() @check_gnn_get_edges_from_nodes + # 用于根据给定的节点列表获取与这些节点相连的所有边 def get_edges_from_nodes(self, node_list): """ Get edges from the nodes. @@ -222,11 +280,16 @@ class GraphData: Raises: TypeError: If `edge_list` is not list or ndarray. """ + # 如果self._working_mode为'server' if self._working_mode == 'server': + # 抛出异常 raise Exception("This method is not supported when working mode is server.") + # 返回self._graph_data.get_all_nodes(node_type)获取图中所有指定类型的节点 + # 然后使用as_array()方法将节点转换为数组,并返回该数组 return self._graph_data.get_edges_from_nodes(node_list).as_array() @check_gnn_get_all_neighbors + # 用于获取与给定节点列表中的每个节点相邻的指定类型的邻居节点 def get_all_neighbors(self, node_list, neighbor_type, output_format=OutputFormat.NORMAL): """ Get `neighbor_type` neighbors of the nodes in `node_list`. @@ -342,17 +405,26 @@ class GraphData: TypeError: If `node_list` is not list or ndarray. TypeError: If `neighbor_type` is not integer. """ + # 如果self._working_mode为'server' if self._working_mode == 'server': + # 抛出异常 raise Exception("This method is not supported when working mode is server.") + # 调用图数据对象的get_all_neighbors方法,传入节点列表node_list、邻居类型neighbor_type和输出格式output_format作为参数 result_list = self._graph_data.get_all_neighbors(node_list, neighbor_type, DE_C_INTER_OUTPUT_FORMAT[output_format]).as_array() + # 如果输出格式为OutputFormat.CSR if output_format == OutputFormat.CSR: + # 如果输出格式为OutputFormat.CSR,将结果列表的第一个部分(即偏移表)赋值给变量offset_table offset_table = result_list[:len(node_list)] + # 如果输出格式为OutputFormat.CSR,将结果列表的第二个部分(即邻居表)赋值给变量neighbor_table neighbor_table = result_list[len(node_list):] + # 返回offset_table和neighbor_table return offset_table, neighbor_table + # 返回result_list return result_list @check_gnn_get_sampled_neighbors + # 用于获取与给定节点列表中的每个节点相邻的指定类型的随机采样邻居节点 def get_sampled_neighbors(self, node_list, neighbor_nums, neighbor_types, strategy=SamplingStrategy.RANDOM): """ Get sampled neighbor information. @@ -386,14 +458,20 @@ class GraphData: TypeError: If `neighbor_nums` is not list or ndarray. TypeError: If `neighbor_types` is not list or ndarray. """ + # 如果输入的策略strategy不是SamplingStrategy枚举类型 if not isinstance(strategy, SamplingStrategy): + # 抛出异常 raise TypeError("Wrong input type for strategy, should be enum of 'SamplingStrategy'.") + # 如果self._working_mode为'server' if self._working_mode == 'server': + # 抛出异常 raise Exception("This method is not supported when working mode is server.") + # 返回self._graph_data.get_sampled_neighbors return self._graph_data.get_sampled_neighbors( node_list, neighbor_nums, neighbor_types, DE_C_INTER_SAMPLING_STRATEGY[strategy]).as_array() @check_gnn_get_neg_sampled_neighbors + # 用于获取与给定节点列表中的每个节点相邻的随机负采样邻居节点 def get_neg_sampled_neighbors(self, node_list, neg_neighbor_num, neg_neighbor_type): """ Get `neg_neighbor_type` negative sampled neighbors of the nodes in `node_list`. @@ -416,12 +494,16 @@ class GraphData: TypeError: If `neg_neighbor_num` is not integer. TypeError: If `neg_neighbor_type` is not integer. """ + # 如果self._working_mode为'server' if self._working_mode == 'server': + # 抛出异常 raise Exception("This method is not supported when working mode is server.") + # 返回self._graph_data.get_neg_sampled_neighbors return self._graph_data.get_neg_sampled_neighbors( node_list, neg_neighbor_num, neg_neighbor_type).as_array() @check_gnn_get_node_feature + # 用于获取给定节点列表的指定特征值 def get_node_feature(self, node_list, feature_types): """ Get `feature_types` feature of the nodes in `node_list`. @@ -441,16 +523,22 @@ class GraphData: TypeError: If `node_list` is not list or ndarray. TypeError: If `feature_types` is not list or ndarray. """ + # 如果self._working_mode为'server' if self._working_mode == 'server': + # 抛出异常 raise Exception("This method is not supported when working mode is server.") + # 如果node_list为列表类型 if isinstance(node_list, list): + # 将node_list转换为numpy数组类型,并指定数据类型为int32 node_list = np.array(node_list, dtype=np.int32) + # 返回用图数据对象的get_node_feature方法,传入节点特征类型列表feature_types和节点列表node_list作为参数 return [ t.as_array() for t in self._graph_data.get_node_feature( Tensor(node_list), feature_types)] @check_gnn_get_edge_feature + # 用于获取给定边列表的指定特征值 def get_edge_feature(self, edge_list, feature_types): """ Get `feature_types` feature of the edges in `edge_list`. @@ -470,15 +558,20 @@ class GraphData: TypeError: If `edge_list` is not list or ndarray. TypeError: If `feature_types` is not list or ndarray. """ + # 如果self._working_mode为'server' if self._working_mode == 'server': + # 抛出异常 raise Exception("This method is not supported when working mode is server.") + # 如果edge_list为列表类型 if isinstance(edge_list, list): + # 将edge_list转换为numpy数组类型,并指定数据类型为int32 edge_list = np.array(edge_list, dtype=np.int32) + # 返回用图数据对象的get_edge_feature方法,传入节点特征类型列表feature_types和节点列表edge_list作为参数 return [ t.as_array() for t in self._graph_data.get_edge_feature( Tensor(edge_list), feature_types)] - + # 用于获取图的基本信息,如节点数量、边数量、最大节点ID等 def graph_info(self): """ Get the meta information of the graph, including the number of nodes, the type of nodes, @@ -488,11 +581,15 @@ class GraphData: dict, meta information of the graph. The key is node_type, edge_type, node_num, edge_num, node_feature_type and edge_feature_type. """ + # 如果self._working_mode为'server' if self._working_mode == 'server': + # 抛出异常 raise Exception("This method is not supported when working mode is server.") + # 调用图数据对象的graph_info方法,获取图的基本信息,并返回这些信息 return self._graph_data.graph_info() @check_gnn_random_walk + # 用于执行随机游走算法,根据给定的目标节点、元路径和参数,生成目标节点的随机游走路径 def random_walk(self, target_nodes, meta_path, step_home_param=1.0, step_away_param=1.0, default_node=-1): """ Random walk in nodes. @@ -516,7 +613,10 @@ class GraphData: TypeError: If `target_nodes` is not list or ndarray. TypeError: If `meta_path` is not list or ndarray. """ + # 如果self._working_mode为'server' if self._working_mode == 'server': + # 抛出异常 raise Exception("This method is not supported when working mode is server.") + # 返回self._graph_data.random_walk return self._graph_data.random_walk(target_nodes, meta_path, step_home_param, step_away_param, default_node).as_array() diff --git a/mindspore/python/mindspore/dataset/engine/iterators.py b/mindspore/python/mindspore/dataset/engine/iterators.py index a0245f829b4..f7471dd71bd 100644 --- a/mindspore/python/mindspore/dataset/engine/iterators.py +++ b/mindspore/python/mindspore/dataset/engine/iterators.py @@ -31,28 +31,37 @@ _ITERATOR_CLEANUP = False def _set_iterator_cleanup(): global _ITERATOR_CLEANUP + # 将全局变量 _ITERATOR_CLEANUP 的值设置为 True + # 在后续的迭代中,会自动清理迭代器,避免资源泄漏。 _ITERATOR_CLEANUP = True def _unset_iterator_cleanup(): global _ITERATOR_CLEANUP + # 将全局变量 _ITERATOR_CLEANUP 的值设置为 False + # 在后续的迭代中,不会自动清理迭代器,可能会导致资源泄漏 _ITERATOR_CLEANUP = False - +# 检查 _ITERATOR_CLEANUP 的值 def check_iterator_cleanup(): global _ITERATOR_CLEANUP return _ITERATOR_CLEANUP - +# 定义一个全局便令ITERATORS_LIST,用于存储所有创建的迭代器,以便在后续的迭代中释放它们 ITERATORS_LIST = list() def _cleanup(): """Release all the Iterator.""" + # 将全局变量 _ITERATOR_CLEANUP 的值设置为 True _set_iterator_cleanup() + # 遍历全局变量 ITERATORS_LIST 的逆序,清空每个迭代器 for itr_ref in reversed(ITERATORS_LIST): + # 使用 itr_ref() 获取迭代器的值 itr = itr_ref() + # 检查它是否为 None if itr is not None: + # 释放资源 itr.release() @@ -67,7 +76,7 @@ class Iterator: def __init__(self, dataset, num_epochs=-1, output_numpy=False, do_copy=True): self._col_names = None - # create a copy of tree and work on it. + # 创建一个副本,并将其转换为 IR 树 self.__ori_dataset = dataset self.ir_tree, self.dataset = dataset.create_ir_tree() @@ -79,97 +88,143 @@ class Iterator: self._runtime_context.AssignConsumer(consumer) self._iterator = self._runtime_context.GetConsumer() + # 用于将张量转换为 NumPy 数组 self._transform_tensor = lambda t: t.as_array() + # 当 output_numpy 为 False 时 if not output_numpy: + # 对张量进行转换 + # do_copy 表示是否对张量进行复制 def _transform(t, do_copy): array = t.as_array() + # 将 dtype 转换为 np.str_ if array.dtype.type is np.bytes_: array = array.astype(np.str_) if do_copy: return Tensor(array) + # 直接返回张量的 NumPy 数组 return Tensor.from_numpy(array) self._transform_tensor = lambda t: _transform(t, do_copy) self.__index = 0 self.offload_model = None + # 获取一个 offload_model 对象,包含了识别到的计算图中需要 offloaded 操作的信息。 offload_model = offload.GetOffloadModel(consumer, self.__ori_dataset.get_col_names()) # See if GetOffloadModel identified any operations set to be offloaded. + # 检查 offload_model 的 transform_list 是否为空 if offload_model.transform_list != []: + # 如果不为空,说明有操作被识别为可以 offloaded + # 检查数据集中是否存在需要 concat 或 zip 的操作 offload.check_concat_zip_dataset(self.__ori_dataset) self.offload_model = offload_model + # 将当前对象的引用添加到 ITERATORS_LIST 中 ITERATORS_LIST.append(weakref.ref(self)) + # 调用 _unset_iterator_cleanup 函数取消迭代器的自动清理 _unset_iterator_cleanup() def __iter__(self): return self + # 定义stop函数,用于手动终止Python迭代器,而不是依靠外部销毁 def stop(self): """ Manually terminate Python iterator instead of relying on out of scope destruction. """ + # 判断是否有_runtime_context属性,且不为空 if hasattr(self, '_runtime_context') and self._runtime_context: + # 判断是否有_iterator属性,且不为空 if hasattr(self, '_iterator') and self._iterator: + # 终止运行时上下文 self._runtime_context.Terminate() + # 删除_iterator del self._iterator + # 删除_runtime_context del self._runtime_context + # 删除dataset del self.dataset # get weakref which is dead + # 创建dead_iterator列表 dead_iterator = [] + # 遍历ITERATORS_LIST for index, item in enumerate(ITERATORS_LIST): # item() == None indicate the object is dead # id(item()) == id(self) indicate del self if item() is None or id(item()) == id(self): + # 将dead_iterator中的索引添加到dead_iterator列表中 dead_iterator.append(index) # del dead weakref for index in reversed(dead_iterator): + # 从ITERATORS_LIST中删除dead_iterator中的索引 ITERATORS_LIST.pop(index) + # 调用 stop 方法停止迭代 def release(self): self.stop() + # 在对象被销毁时释放当前迭代器 def __del__(self): self.release() @abstractmethod + # 用于获取下一个元素 def _get_next(self): + # 抛出一个 RuntimeError 异常 + # 应该使用子类 DatasetIterator 的 get_next 方法来获取下一个元素 raise RuntimeError("Calling base class Iterator's get_next is invalid.") def __next__(self): + # 检查 self._runtime_context 是否为 None if not self._runtime_context: + # 如果为 None,说明当前迭代器没有正在运行的 C++ 管道,日志警告会被输出 + # 表明迭代器可能已经被停止或者 C++ 管道崩溃 logger.warning("Iterator does not have a running C++ pipeline." + "It might because Iterator stop() had been called, or C++ pipeline crashed silently.") raise RuntimeError("Iterator does not have a running C++ pipeline.") + # 从 self._get_next() 方法中获取下一个元素 data = self._get_next() + # 如果 data 为 None,说明已经到达了数据集的末尾 if not data: + # 如果这是第一次遍历,则日志警告可能会被输出,表明没有可用的记录 if self.__index == 0: logger.warning("No records available.") + # 如果数据集没有提供 dataset_size 属性,那么将其设置为当前索引 self.__index if self.__ori_dataset.dataset_size is None: self.__ori_dataset.dataset_size = self.__index + # 迭代结束 raise StopIteration + # 遍历次数 + 1 self.__index += 1 + # 检查 self.offload_model 是否为 None if self.offload_model is not None: + # 如果不为 None,说明存在需要 offloaded 操作 data = offload.apply_offload_iterators(data, self.offload_model) + # 返回处理后的数据集元素 return data def __deepcopy__(self, memo): return self + # 用于获取管道信息 def _getters(self): """ Get pipeline information. """ + # 初始化一个对象 getter = cde.TreeGetters() + # 初始化树 getter.Init(self.ir_tree) + # 将获取器分配给 self._runtime_context 的消费者 self._runtime_context.AssignConsumer(getter) + # 从获取器中获取列名并将其存储在 self._col_names 变量中 self._col_names = getter.GetColumnNames() + # 用于获取数据集的列名 def get_col_names(self): """ Get names of the columns in the dataset @@ -178,6 +233,7 @@ class Iterator: self._getters() return self._col_names + # 用于将迭代器重置到给定的步数 def _reset(self, step): """ Reset the iterator to the given step number. @@ -191,8 +247,10 @@ class Iterator: class DictIterator(Iterator): """ The derived class of Iterator with dict type. + 用于将数据集中的元素转换为字典类型 """ + # 定义一个函数_get_next,用于返回数据集中下一个记录作为字典 def _get_next(self): """ Returns the next record in the dataset as dictionary @@ -201,25 +259,30 @@ class DictIterator(Iterator): Dict, the next record in the dataset. """ try: + # 使用self._iterator.GetNextAsMap()获取下一个记录,并将其中的每个元素转换为字典 return {k: self._transform_tensor(t) for k, t in self._iterator.GetNextAsMap().items()} except RuntimeError as err: # maybe "Out of memory" / "MemoryError" error + # 捕获可能出现的“Out of memory” /“MemoryError”错误 err_info = str(err) if err_info.find("Out of memory") >= 0 or err_info.find("MemoryError") >= 0: logger.critical("Memory error occurred, process will exit.") os.kill(os.getpid(), signal.SIGKILL) raise err - + class TupleIterator(Iterator): """ The derived class of Iterator with list type. + 用于将数据集中的元素转换为元组类型 """ def __init__(self, dataset, columns=None, num_epochs=-1, output_numpy=False, do_copy=True): + # 如果columns不为空,则判断是否为list类型,不是则转换为list if columns is not None: if not isinstance(columns, list): columns = [columns] + # 调用父类构造函数,传入dataset和columns dataset = dataset.project(columns) super().__init__(dataset, num_epochs, output_numpy, do_copy) @@ -231,12 +294,15 @@ class TupleIterator(Iterator): List, the next record in the dataset. """ + # 调用父类的_get_next方法,返回list类型的数据 return [self._transform_tensor(t) for t in self._iterator.GetNextAsList()] class DummyIterator: """ A DummyIterator only work when env MS_ROLE="MS_PSERVER" or MS_ROLE="MS_SCHED" + 用于模拟数据集中的迭代器。 + 这个类主要用于在特定环境中工作,即 MS_ROLE="MS_PSERVER" 或 MS_ROLE="MS_SCHED"。 """ def __init__(self, dataset, mode): @@ -246,19 +312,30 @@ class DummyIterator: self.fetched_first = False def __get_tensor(self): + # 定义一个空列表,用于存放tensor tensor_row = [] + # 遍历shapes和types,将每个shape和type对应的数据转换为tensor for np_shape, np_type in zip(self.shapes, self.types): + # 创建一个指定shape和type的numpy数组 input_np = np.zeros(np_shape, np_type) + # 将numpy数组转换为tensor tensor = Tensor(input_np) + # 将tensor添加到tensor_row列表中 tensor_row.append(tensor) + # 返回tensor_row列表 return tensor_row def __iter__(self): return self def __next__(self): + # 当 mode 为 "tuple" 时 if self.mode == "tuple": + # 检查 fetched_first 是否为 True if not self.fetched_first: + # 调用 __get_tensor 方法获取一个包含多个张量元组的列表 + # 每个张量元组表示数据集中的一个记录,并将 fetched_first 设置为 True self.fetched_first = True return self.__get_tensor() + # 否则,抛出 StopIteration 异常 raise StopIteration() diff --git a/mindspore/python/mindspore/dataset/engine/offload.py b/mindspore/python/mindspore/dataset/engine/offload.py index a199b301bcd..34bce515ace 100644 --- a/mindspore/python/mindspore/dataset/engine/offload.py +++ b/mindspore/python/mindspore/dataset/engine/offload.py @@ -25,50 +25,71 @@ from mindspore.ops import operations as P from mindspore.ops.primitive import constexpr +# 定义一个函数,检查是否需要将数据集卸载,并应用转换 def check_add_offload_sink_mode(dataset, dataset_helper, network): """ Check if any map operations were removed to be offloaded and apply the transforms if so. + 检查是否需要将数据集卸载,并应用转换 """ if hasattr(dataset, '__no_send__'): # Dataset was not sent to device. Skip adding offload. + # 数据集未被发送到设备。跳过添加卸载 return network offload_model = dataset.__transfer_dataset__.get_offload_model() # See if the offload pass identified any operations to be offloaded + # 检查是否 identified any operations to be offloaded if offload_model.transform_list != []: + # 检查是否需要将zip和concat数据集卸载 check_concat_zip_dataset(dataset.__transfer_dataset__) + # 应用转换 network = ApplyPreTransform(offload_model, network) return network - +# 定义一个函数,检查传入的dataset是否是拼接或者压缩的 def check_concat_zip_dataset(dataset): """ Check if dataset is concatenated or zipped. + 检查传入的dataset是否是拼接或者压缩的 """ + # 遍历 dataset 的子节点,直到 dataset 为空为止 while dataset: + # 如果子节点的数量大于1 if len(dataset.children) > 1: + # 抛出一个 RuntimeError 异常,表示不支持拼接或压缩的dataset。 raise RuntimeError("Offload module currently does not support concatenated or zipped datasets.") if dataset.children: + # 将 dataset 设置为第一个子节点,并继续遍历 dataset = dataset.children[0] continue dataset = dataset.children +# 定义一个函数,用于获取输入列的索引 def get_col_idxs(node_cols, ds_cols): """ Get the index(es) of the input column(s) from the dataset + 用于获取输入列的索引 """ col_idxs = [] non_exist_cols = [] # temporary error if multiple node columns + # 处理多个节点列的错误 if len(node_cols) > 1: + # node_cols 的数量大于1时,它会抛出一个 RuntimeError 异常,表示不支持多个节点列的映射操作。 raise RuntimeError( "Offload hardware accelerator currently does not support map operations with multiple input columns") + # 遍历节点列 for node_col in node_cols: + # 如果节点列在数据集中 if node_col in ds_cols: + # 将节点列的索引添加到col_idxs中 col_idxs.append(ds_cols.index(node_col)) else: + # 否则将节点列添加到non_exist_cols中 non_exist_cols.append(node_col) + # 如果non_exist_cols不为空 if non_exist_cols: + # 抛出运行时错误 raise RuntimeError( ("The following input column(s) for an offloaded map operation " "do not exist: {}").format(non_exist_cols)) @@ -76,65 +97,90 @@ def get_col_idxs(node_cols, ds_cols): return col_idxs +# 定义一个函数,用于应用非sink模式管道中的offload def apply_offload_iterators(data, offload_model): """ Apply offload for non sink mode pipeline. + 用于应用非sink模式管道中的offload """ + # 创建一个空列表,用于存储非张量索引 non_tensor_idxs = [] + # 遍历data中的每一个元素 for i, _ in enumerate(data): + # 如果data中的元素不是张量,则将其转换为张量,并将索引添加到non_tensor_idxs中 if not isinstance(data[i], Tensor): data[i] = Tensor(data[i], dtype=mstype.float32) non_tensor_idxs.append(i) + # 将data传入offload_model中,并将返回值转换为列表 data = offload_model(data) data = list(data) + # 遍历non_tensor_idxs中的每一个索引 for idx in non_tensor_idxs: + # 将data中的元素转换为numpy,并将转换后的结果赋值给data data[idx] = data[idx].asnumpy() + # 返回data return data @constexpr +# 检查输入的维度是否符合操作的要求 def check_input_dims(x_shape, required_dim, offload_op_name): """ Check if input has the required number of dimensions for the operation. + 检查输入的维度是否符合操作的要求 """ input_dim = len(x_shape) + # 当输入数据的维度与所需维度不相同时 if input_dim is not required_dim: raise ValueError("For %s offload operation, the dimension of input should be %d, but got %d." % (offload_op_name, required_dim, input_dim)) +# 定义一个函数,用于调整输入参数的参数范围 def assign_min_max_params(in_params, center=1): """ Adjust input parameters for ops. + 用于调整输入参数的参数范围 """ + # 判断输入参数是否为列表或元组 if isinstance(in_params, (list, tuple)): + # 将输入参数的第一个元素赋值给min_param min_param = in_params[0] + # 将输入参数的第二个元素赋值给max_param max_param = in_params[1] else: + # 将输入参数的绝对值赋值给min_param min_param = max(0, center - in_params) + # 将输入参数加上中心参数赋值给max_param max_param = center + in_params + # 返回min_param和max_param return min_param, max_param class ApplyPreTransform(nn.Cell): """ Concatenates offload model with network. + 用于将预处理模型与网络连接起来 """ def __init__(self, transform, model): + # 调用父类构造函数 super(ApplyPreTransform, self).__init__(auto_prefix=False, flags=model.get_flags()) self.transform = transform self.model = model def construct(self, *x): data = [] + # 将输入数据分成多个数据列 for data_col in x: data.append(data_col) + # 将每个数据列传递给预处理模型 data = self.transform(data) + # 将预处理模型的输出传递给网络 data = self.model(*data) return data @@ -143,19 +189,22 @@ class ApplyPreTransform(nn.Cell): class IdentityCell(nn.Cell): """ Applies identity transform on given input tensors. + 用于应用恒等变换 """ def __init__(self): + # 调用父类构造 super(IdentityCell, self).__init__() self.identity = P.Identity() def construct(self, x): + # 用于组合恒等变换。它接收一个输入张量 x,并返回 x 本身 return self.identity(x) - class RandomHorizontalFlip(nn.Cell): """ Applies Random Horizontal Flip transform on given input tensors. + # 用于应用随机水平翻转变换 """ def __init__(self, prob): @@ -171,24 +220,34 @@ class RandomHorizontalFlip(nn.Cell): def construct(self, x): + # 将x转换为float32类型 x = self.cast(x, mstype.float32) + # 获取x的形状 x_shape = self.shape(x) + # 检查输入维度是否为4 check_input_dims(x_shape, 4, 'RandomHorizontalFlip') + # 获取x的bs,h,w,c bs, h, w, c = x_shape + # 生成一个bs*1的随机数,范围为[0, 1] flip_rand_factor = Tensor(np.random.uniform(size=(bs, 1)), dtype=mstype.float32) + # 将随机数转换为float32类型,并将其转换为bs*h*w*c的形状 flip_rand_factor = self.cast((self.prob > flip_rand_factor), mstype.float32) flip_rand_factor = self.reshape(C.repeat_elements(flip_rand_factor, rep=(h*w*c)), (bs, h, w, c)) + # 将x水平翻转 x_flip = self.h_flip(x) + # 将x_flip和flip_rand_factor相乘,再加上x和(1 - flip_rand_factor)相乘的结果 x = self.mul(x_flip, flip_rand_factor) + self.mul((1 - flip_rand_factor), x) + # 返回x return x class RandomVerticalFlip(nn.Cell): """ Applies Random Vertical Flip transform on given input tensors. + 用于应用随机垂直翻转变 """ def __init__(self, prob): @@ -204,24 +263,34 @@ class RandomVerticalFlip(nn.Cell): def construct(self, x): + # 将x转换为float32类型 x = self.cast(x, mstype.float32) + # 获取x的形状 x_shape = self.shape(x) + # 检查输入的维度是否为4 check_input_dims(x_shape, 4, 'RandomVerticalFlip') + # 获取x的bs, h, w, c bs, h, w, c = x_shape + # 生成一个bs*1的随机数组,范围为0-1 flip_rand_factor = Tensor(np.random.uniform(size=(bs, 1)), dtype=mstype.float32) + # 将随机数组转换为float32类型,并将其转换为bs*h*w*c的形状 flip_rand_factor = self.cast((self.prob > flip_rand_factor), mstype.float32) flip_rand_factor = self.reshape(C.repeat_elements(flip_rand_factor, rep=(h*w*c)), (bs, h, w, c)) + # 将x水平翻转 x_flip = self.h_flip(x) + # 将x_flip和flip_rand_factor相乘,再加上x和(1-flip_rand_factor)相乘的结果 x = self.mul(x_flip, flip_rand_factor) + self.mul((1 - flip_rand_factor), x) + # 返回x return x class GenerateRandBatch(nn.Cell): """ Generate batch with random values uniformly selected from [degree_min, degree_max]. + 用于生成具有随机值的批次。 """ def __init__(self): @@ -232,11 +301,17 @@ class GenerateRandBatch(nn.Cell): def construct(self, degree_min, degree_max, check_rand, shape): + # 获取输入的shape bs, h, w, c = shape + # 生成一个bs大小的随机数组 rand_factor = Tensor(np.random.uniform(size=(bs, 1)), dtype=mstype.float32) + # 将随机数组归一化到[degree_min, degree_max] rand_factor = degree_min + (degree_max - degree_min)*rand_factor + # 生成一个bs大小的全为degree_min的数组 degree_factor = degree_min * self.ones((bs, 1), mstype.float32) + # 将随机数组和全为degree_min的数组进行比较,取其中随机数组 rand_factor = (check_rand * degree_factor) + (~check_rand * rand_factor) + # 将随机数组扩展到(bs, h, w, c)的形状 rand_factor = self.reshape(C.repeat_elements(rand_factor, rep=(h*w*c)), (bs, h, w, c)) return rand_factor @@ -245,6 +320,7 @@ class GenerateRandBatch(nn.Cell): class RandomColorAdjust(nn.Cell): """ Applies Random Color Adjust transform on given input tensors. + 用于应用随机颜色调整变换。 """ def __init__(self, brightness, contrast, saturation, hue): @@ -255,11 +331,13 @@ class RandomColorAdjust(nn.Cell): self.sa_min, self.sa_max = assign_min_max_params(saturation) self.hue_min, self.hue_max = assign_min_max_params(hue) + # 检查随机亮度、对比度、饱和度和色调是否相等 self.check_rand_br = Tensor(self.br_min == self.br_max) self.check_rand_cont = Tensor(self.cont_min == self.cont_max) self.check_rand_sa = Tensor(self.sa_min == self.sa_max) self.check_rand_hue = Tensor(self.hue_min == self.hue_max) + # 定义操作 self.cast = P.Cast() self.shape = P.Shape() self.reshape = P.Reshape() @@ -267,6 +345,7 @@ class RandomColorAdjust(nn.Cell): self.expand_dims = P.ExpandDims() self.mul = P.Mul() + # 定义操作 self.mean = P.ReduceMean() self.argmaxvalue = P.ArgMaxWithValue(axis=3, keep_dims=False) self.argminvalue = P.ArgMinWithValue(axis=3, keep_dims=False) @@ -284,64 +363,94 @@ class RandomColorAdjust(nn.Cell): self.ones = P.Ones() self.reshape = P.Reshape() + # 生成随机batch self.generate_rand_batch = GenerateRandBatch() + # 用于组合随机颜色调整变换。 def construct(self, x): x = self.cast(x, mstype.float32) x_shape = self.shape(x) + # 检查维度是否与所需维度相等 check_input_dims(x_shape, 4, 'RandomColorAdjust') bs, h, w, c = x_shape + # 生成随机亮度 br_rand_factor = self.generate_rand_batch(self.br_min, self.br_max, self.check_rand_br, x_shape) + # 生成随机对比度 cont_rand_factor = self.generate_rand_batch(self.cont_min, self.cont_max, self.check_rand_cont, x_shape) + # 生成随机饱和度 sat_rand_factor = self.generate_rand_batch(self.sa_min, self.sa_max, self.check_rand_sa, x_shape) + # 将输入图像拆分成RGB三个通道 r, g, b = self.unstack(x) + # 将RGB转换成灰度图 x_gray = 0.2989 * r + 0.587 * g + 0.114 * b + # 计算灰度图的均值 x_gray_mean = self.expand_dims(self.mean(x_gray, (1, 2)) + 0.5, -1) + # 将灰度图的均值扩展到RGB三个通道 x_gray_mean = self.reshape(C.repeat_elements(x_gray_mean, rep=(h*w*c)), (bs, h, w, c)) + # 将输入图像扩展到RGB三个通道 x_gray = C.repeat_elements(self.expand_dims(x_gray, -1), rep=c, axis=-1) # Apply brightness + # 将输入张量 x 与亮度随机因子相乘 x = self.mul(x, br_rand_factor) + # 应用裁剪操作,以确保输出的张量值在 0 到 255 之间 x = C.clip_by_value(x, 0.0, 255.0) # Apply contrast + # 将输入张量 x 与对比度随机因子相乘 x = self.mul(x, cont_rand_factor) + self.mul((1 - cont_rand_factor), x_gray_mean) + # 应用裁剪操作,以确保输出的张量值在 0 到 255 之间 x = C.clip_by_value(x, 0.0, 255.0) # Apply saturation + # 将输入张量 x 与饱和度随机因子相乘 x = self.mul(x, sat_rand_factor) + self.mul((1 - sat_rand_factor), x_gray) + # 应用裁剪操作,以确保输出的张量值在 0 到 255 之间 x = C.clip_by_value(x, 0.0, 255.0) # Apply Hue Transform # Convert tensor from rgb to hsv + # 用于应用随机颜色调整变换中的颜色哈希变换 + # 将输入张量 x 从 RGB 格式转换为 HSV 格式 r, g, b = self.unstack(x) + # 计算最大值、最小值和 HSV 值的占比 max_c, max_v = self.argmaxvalue(x) _, min_v = self.argminvalue(x) hsv_denum = max_v - min_v + self.epsilon + # 根据这些值计算 HSV 空间的三个分量 h1 = self.fmod(((b - g) * 60 / hsv_denum), 360) h2 = (g - r) * 60 / hsv_denum + 120 h3 = (r - g) * 60 / hsv_denum + 240 + # 将这三个分量组合成一个新的张量 hue = self.squeeze(self.gatherd(self.stack((h1, h2, h3)), 0, self.expand_dims(max_c, 0))) s = self.cast((max_v > 0), mstype.float32) * (1 - min_v / (max_v + self.epsilon)) v = self.cast(max_v, mstype.float32) # Adjust hue + # 调整亮度 + # 用于根据随机生成的 hue_rand_factor 调整输入张量的颜色 + # 创建一个范围在 hue_min 和 hue_max 之间的随机张量 hue_rand_factor = Tensor(np.random.uniform(size=(bs, 1)), dtype=mstype.float32) hue_rand_factor = self.hue_min + (self.hue_max - self.hue_min)*hue_rand_factor degree_factor = self.hue_min * self.ones((bs, 1), mstype.float32) + # 将其与 degree_factor(全为 1 的张量)相加 hue_rand_factor = (self.check_rand_hue * degree_factor) + (~self.check_rand_hue * hue_rand_factor) hue_rand_factor = self.reshape(C.repeat_elements(hue_rand_factor, rep=(h*w)), (bs, h, w)) + # 最后将结果与原始的 hue 值相加 hue = hue + (hue_rand_factor * 360.0) # Convert tensor from hsv to rgb + # 用于将输入张量从 HSV 格式转换回 RGB 格式 + # 计算 HSV 空间的三个分量(色调、饱和度和明度) h_ = (hue - self.floor(hue / 360.0) * 360.0) / 60.0 c = self.mul(s, v) x_ = self.mul(c, (1 - self.abs(self.fmod(h_, 2) - 1))) zero_tensor = self.zeros_like(c) + # 根据这些值计算 RGB 空间的三个分量 y = self.stack((self.stack_axis_1((c, x_, zero_tensor)), self.stack_axis_1((x_, c, zero_tensor)), self.stack_axis_1((zero_tensor, c, x_)), self.stack_axis_1((zero_tensor, x_, c)), self.stack_axis_1((x_, zero_tensor, c)), self.stack_axis_1((c, zero_tensor, x_)), @@ -349,8 +458,10 @@ class RandomColorAdjust(nn.Cell): index = self.expand_dims(self.floor(h_), 1) index = self.expand_dims(C.repeat_elements(index, 3, 1), 0) index = self.cast(index, mstype.int32) + x = self.squeeze(self.gatherd(y, 0, index)) x = x + self.reshape(C.repeat_elements((v - c), rep=(3)), self.shape(x)) + # 将结果乘以 255 并裁剪到 0 到 255 之间,返回转换后的张量 x = self.transpose(x, (0, 2, 3, 1)) * 255.0 x = C.clip_by_value(x, 0.0, 255.0) @@ -360,6 +471,7 @@ class RandomColorAdjust(nn.Cell): class RandomSharpness(nn.Cell): """ Applies Random Sharpness transform on given input tensors. + 用于应用随机锐化变换。 """ def __init__(self, degrees): @@ -372,6 +484,7 @@ class RandomSharpness(nn.Cell): self.degree_min = max(0, 1 - degrees) self.degree_max = 1 + degrees + # 定义操作 self.cast = P.Cast() self.shape = P.Shape() self.ones = P.Ones() @@ -380,54 +493,75 @@ class RandomSharpness(nn.Cell): self.mul = P.Mul() self.transpose = P.Transpose() + # 检查输入的度数是否相等 self.check_rand = Tensor(self.degree_min == self.degree_max) + # 初始化权重 self.weight = np.array([[1, 1, 1], [1, 5, 1], [1, 1, 1]])/13.0 self.weight = np.repeat(self.weight[np.newaxis, :, :], 3, axis=0) self.weight = np.repeat(self.weight[np.newaxis, :, :], 3, axis=0) self.weight = Tensor(self.weight, mstype.float32) + # 初始化卷积层 self.filter = P.Conv2D(out_channel=3, kernel_size=(3, 3), pad_mode='pad', pad=1) def construct(self, x): + # 将输入x转换为float32类型 x = self.cast(x, mstype.float32) + # 获取输入x的形状 x_shape = self.shape(x) + # 检查输入x的形状是否符合要求 check_input_dims(x_shape, 4, 'RandomSharpness') + # 获取输入x的batch_size,h,w,c bs, h, w, c = x_shape + # 生成一个bs*1的随机数,范围在[0, 1] degree_rand_factor = Tensor(np.random.uniform(size=(bs, 1)), dtype=mstype.float32) + # 将degree_rand_factor的值设置为[self.degree_min, self.degree_max] degree_rand_factor = self.degree_min + (self.degree_max - self.degree_min)*degree_rand_factor + # 将degree_rand_factor的值设置为[self.degree_min, self.degree_min] degree_factor = self.degree_min * self.ones((bs, 1), mstype.float32) + # 将degree_rand_factor的值设置为[self.degree_min, self.degree_max] degree_rand_factor = (self.check_rand * degree_factor) + (~self.check_rand * degree_rand_factor) + # 将degree_rand_factor的值重复h*w*c次,并转换为bs*h*w*c的形状 degree_rand_factor = self.reshape(C.repeat_elements(degree_rand_factor, rep=(h*w*c)), (bs, h, w, c)) + # 将输入x转换为bs*c*h*w的形状,并使用self.weight对输入x进行卷积操作 x_sharp = self.filter(self.transpose(x, (0, 3, 1, 2)), self.weight) + # 将x_sharp转换为bs*h*w*c的形状 x_sharp = self.transpose(x_sharp, (0, 2, 3, 1)) + # 将输入x和x_sharp进行乘法操作,并使用degree_rand_factor和1-degree_rand_factor进行加法操作 x = self.mul(x, degree_rand_factor) + self.mul((1 - degree_rand_factor), x_sharp) + # 将x的值限制在[0, 255]之间 x = C.clip_by_value(x, 0.0, 255.0) + # 返回处理后的x return x class Rescale(nn.Cell): """ Applies Rescale transform on given input tensors. + 用于应用缩放变换。 """ def __init__(self, rescale, shift): super(Rescale, self).__init__() + # 将 rescale 和 shift 转换为张量 self.rescale = Tensor(rescale, dtype=mstype.float32) self.shift = Tensor(shift, dtype=mstype.float32) - + # 定义操作 self.cast = P.Cast() self.mul = P.Mul() def construct(self, x): + # 将x转换为float32类型 x = self.cast(x, mstype.float32) + # 将x乘以rescale,再加上shift x = x * self.rescale + self.shift return x @@ -436,47 +570,60 @@ class Rescale(nn.Cell): class HwcToChw(nn.Cell): """ Applies Channel Swap transform on given input tensors. + 用于交换输入张量的通道。 """ def __init__(self): super(HwcToChw, self).__init__() + # 定义操作 self.trans = P.Transpose() self.shape = P.Shape() - + def construct(self, x): + # 获取输入x的形状 x_shape = self.shape(x) + # 检查输入x的形状是否符合要求 check_input_dims(x_shape, 4, 'HwcToChw') + # 返回转置后的x return self.trans(x, (0, 3, 1, 2)) class Normalize(nn.Cell): """ Applies Normalize transform on given input tensors. + 用于应用归一化变换。 """ def __init__(self, mean, std): super(Normalize, self).__init__() + # 将 mean 和 std 转换为张量 self.mean = Tensor(mean, mstype.float32) self.std = Tensor(std, mstype.float32) + # 定义操作 self.sub = P.Sub() self.div = P.Div() self.cast = P.Cast() def construct(self, x): + # 将x转换为float32类型 x = self.cast(x, mstype.float32) + # 将x减去均值 x = self.sub(x, self.mean) + # 将x除以标准差 x = self.div(x, self.std) + # 返回处理后的x return x class TypeCast(nn.Cell): """ Applies TypeCast transform on given input tensors. + 用于应用类型转换。 """ def __init__(self, data_type_str): super(TypeCast, self).__init__() - + # 定义操作 self.cast = P.Cast() self.data_type = mstype.typing.str_to_type(data_type_str) @@ -485,13 +632,16 @@ class TypeCast(nn.Cell): return self.cast(x, self.data_type) +# 定义一个OffloadModel类 class OffloadModel(): + # 初始化函数,参数func为要调用的函数,args_names为函数参数的名称 def __init__(self, func, args_names=None): self.func = func self.args_names = args_names # Dictionary connecting operation name to model +# 定义了一个名为 op_to_model 的字典,用于将操作名称映射到对应的模型。 op_to_model = { "HWC2CHW": OffloadModel(HwcToChw), "HwcToChw": OffloadModel(HwcToChw), @@ -508,6 +658,7 @@ op_to_model = { class GetModelFromJson2Col(nn.Cell): """ Generates offload ME model from offload JSON file for a single map op. + 于从Offload JSON文件中生成Offload ME模型。 """ def __init__(self, json_offload, col_idxs): @@ -515,21 +666,27 @@ class GetModelFromJson2Col(nn.Cell): self.col_idxs = col_idxs self.me_ops = [] self.input_cols = [] + # 如果json_offload不为空,则从offload_hw_accelerator中获取操作 if json_offload is not None: offload_ops = json_offload["operations"] + # 遍历操作,获取操作模型 for op in offload_ops: name = op["tensor_op_name"] args = op["tensor_op_params"] op_model = op_to_model[name] op_model_inputs = [] + # 如果操作模型有参数,则获取参数 if op_model.args_names is not None: for arg_key in op_model.args_names: op_model_inputs.append(args[arg_key]) + # 获取操作模型 self.me_ops.append(op_model.func(*op_model_inputs)) else: + # 如果offload_hw_accelerator为空,则抛出异常 raise RuntimeError("Offload hardware accelarator cannot be applied for this pipeline.") + # 创建序列模型 self.cell = nn.SequentialCell(self.me_ops) def construct(self, x): @@ -543,20 +700,30 @@ class GetModelFromJson2Col(nn.Cell): class GetOffloadModel(nn.Cell): """ Generates offload ME model. + 用于生成Offload ME模型。 """ def __init__(self, dataset_consumer, ds_cols): super(GetOffloadModel, self).__init__() self.transform_list = [] + # 从dataset_consumer中获取offload信息 json_offload = json.loads(dataset_consumer.GetOffload()) + # 如果offload信息不为空 if json_offload is not None: + # 遍历offload信息中的每一个节点 for node in json_offload: + # 如果节点的操作类型为Map if node["op_type"] == 'Map': + # 获取输入列的索引 ds_col_idxs = get_col_idxs(node["input_columns"], ds_cols) + # 将节点和输入列索引添加到transform_list中 self.transform_list.append(GetModelFromJson2Col(node, ds_col_idxs)) + # 将transform_list反转 self.transform_list.reverse() def construct(self, x): + # 遍历transform_list中的每一个transform,对x进行转换 for transform in self.transform_list: x = transform(x) + # 返回转换后的x return x diff --git a/mindspore/python/mindspore/dataset/engine/queue.py b/mindspore/python/mindspore/dataset/engine/queue.py index 074414f41d6..f81b35f9c83 100644 --- a/mindspore/python/mindspore/dataset/engine/queue.py +++ b/mindspore/python/mindspore/dataset/engine/queue.py @@ -30,26 +30,30 @@ from ..transforms.py_transforms_util import ExceptionHandler class _SharedQueue(multiprocessing.queues.Queue): """ Class to implement a queue using shared memory for better performance. + 提高队列操作的性能,通过使用共享内存来减少进程之间的数据传输。 Args: - size: Number of elements in the queue. - copy_out: Flag to indidcate whether an extra copy should be done before returning. If data will immediately be - copied before returning, then this can be set to False. - max_rowsize: Maximum size of any element in the Queue in MB. + size: 队列中元素的个数。 + copy_out: 一个标志位,表示在返回数据之前是否需要进行一次额外的复制。如果数据立即被复制,可以设置为False。 + max_rowsize: 队列中任何元素的最大大小(以MB为单位)。 """ def __init__(self, size, copy_out=False, max_rowsize=6): + # 调用父类构造函数 super().__init__(size, ctx=multiprocessing.get_context()) - + # 将copy_out标志位设置为False self.copy_out = copy_out # change max_rowsize in MB into bytes + # 计算seg_size,将其设置为max_rowsize(以MB为单位)乘以1024*1024 self.seg_size = max_rowsize * 1024 * 1024 ##pipe can hold up to 65,636 bytes at a time + # 计算min_shared_mem,将其设置为10000字节 self.min_shared_mem = 10000 - self.shm_list = [] + self.shm_list = [] # 用于存储共享内存段 self.seg_pos = 0 # num_seg has to be 2 more than the queue size. We can have remote worker filling a buffer, main process # reading a buffer and also have a full queue of buffers in the meta-data queue + # num_seg需要设置为size加2。这样,我们可以有远程工作进程填充缓冲区,主进程读取缓冲区,以及一个完整的缓冲区队列在元数据队列中。 self.num_seg = size + 2 self.data_immediate = 0 self.data_shared = 1 @@ -57,9 +61,12 @@ class _SharedQueue(multiprocessing.queues.Queue): try: for _ in range(self.num_seg): + # 分配num_seg个元素,每个元素的大小为seg_size a = multiprocessing.Array("b", self.seg_size) + # 将分配的元素添加到shm_list中 self.shm_list.append(a) except Exception: + # 如果分配失败,抛出异常 raise RuntimeError( "_SharedQueue: Error allocating " + str(self.seg_size) @@ -68,41 +75,57 @@ class _SharedQueue(multiprocessing.queues.Queue): + " elements." + " This might be caused by insufficient shm, and the recommended shm size is at least 5 GB." ) - + def put(self, data, timeout=None): - if isinstance(data, ExceptionHandler): # pylint: disable=too-many-nested-blocks + # 检查data是否是一个ExceptionHandler对象 + if isinstance(data, ExceptionHandler): + # 如果是,则调用父类的put方法 super().put(data, timeout=timeout) else: + # 否则,将数据处理成一个新的列表name_list name_list = [] count = 0 start_bytes = 0 + # 如果data不是一个tuple类型也不是np.ndarray类型 if not isinstance(data, tuple) and not isinstance(data, np.ndarray): + # 抛出一个类型异常 raise TypeError("return value of user defined python function in GeneratorDataset or" " map should be numpy array or tuple of numpy array.") + # 如果data是一个np.ndarray类型 if isinstance(data, np.ndarray): + # 添加到name_list中,其中第一个元素是self.data_immediate,第二个元素是np.array(data) name_list.append((self.data_immediate, np.array(data))) else: + # 遍历data中的每个元素r for r in data: - # the map:pyfunc is a yield generator which can't be serialize + # 如果r是一个types.GeneratorType对象抛出一个类型错误,因为无法将生成器对象序列化。 if isinstance(r, types.GeneratorType): + # 抛出一个类型异常,因为无法将生成器对象序列化。 raise TypeError("Can not pickle {} object, please verify pyfunc return with numpy array" .format(type(r))) + # 检查r是否是一个np.ndarray类型,并且其大小是否大于min_shared_mem,并且start_bytes加上r.nbytes小于seg_size if (isinstance(r, np.ndarray) and r.size > self.min_shared_mem and start_bytes + r.nbytes < self.seg_size): # need to convert start_bytes to offset in array + # 将start_bytes转换为相对于数组偏移量 start_offset = start_bytes + # 创建一个新的np.ndarray对象dest dest = np.ndarray(r.shape, r.dtype, buffer=self.shm_list[self.seg_pos].get_obj(), offset=start_offset) + # 将r的值复制到dest中 np.copyto(dest, r) byte = r.nbytes byte = 8 * ((byte + 7) // 8) start_bytes += byte + # 将处理后的name_list添加到name_list中 name_list.append((self.data_shared, self.seg_pos, byte, r.dtype, r.shape)) count += 1 else: + # 如果r是一个np.ndarray类型,并且其大小大于min_shared_mem if isinstance(r, np.ndarray) and r.size >= self.min_shared_mem: # Only print out error the first time it happens if self.print_error: + # 打印一条警告信息,只在第一次发生时打印 logger.warning( "Using shared memory queue, but rowsize is larger than allocated memory " + "max_rowsize " @@ -110,47 +133,76 @@ class _SharedQueue(multiprocessing.queues.Queue): + " current rowsize " + str(start_bytes + r.nbytes) ) + # 将print_error设置为False self.print_error = False + # 将处理后的r添加到name_list中 name_list.append((self.data_immediate, r)) super().put(name_list, timeout=timeout) # note above could generate a queue full exception. It will be handled by teh caller # only increment seg_pos after successfully adding to metadata queue + # 在处理数据时,可能会遇到队列满的情况。在这种情况下,只有成功添加到metadata_queue后,才会增加seg_pos。 + # 如果start_bytes大于0 if start_bytes > 0: + # 将seg_pos加1,然后对num_seg取模 + # 确保seg_pos始终在0到num_seg-1之间 self.seg_pos = (self.seg_pos + 1) % self.num_seg + # 从队列中获取数据,并将获取到的数据转换为np.ndarray对象 def get(self, timeout=None): + # 调用父类queue.Queue的get方法,获取数据 result = super().get(timeout=timeout) + # 如果获取到的数据是一个ExceptionHandler对象 if isinstance(result, ExceptionHandler): + # 直接返回该对象 return result + # 创建一个空列表r,用于存储从队列中获取到的数据。 r = [] start_bytes = 0 + # 遍历result,处理每个数据 for x in result: + # 如果数据是以self.data_shared为前缀的,那么说明它是一个共享内存中的数据 if x[0] == self.data_shared: + # 获取数据 seg_pos = x[1] byte = x[2] dtype = x[3] shape = x[4] start_offset = start_bytes b = self.shm_list[seg_pos] + # 获取buffer对象,并将其转换为numpy数组 data = np.ndarray(shape, dtype, buffer=b.get_obj(), offset=start_offset) + # 更新字节数 start_bytes += byte + + # 判断是否需要复制输出 if self.copy_out: + # 复制输出 data2 = np.copy(data) + # 将复制后的数据添加到列表中 r.append(data2) else: + # 将原始数据添加到列表中 r.append(data) elif x[0] == self.data_immediate: + # 从result列表中获取数据,并将其添加到r列表中 r.append(x[1]) else: + # 抛出一个运行时异常 raise RuntimeError("SharedQueue, invalid entry in metadata.") + # 将r列表转换为元组,作为返回值 return tuple(r) def __del__(self): + # 获取shm_list的长度 shm_list_len = len(self.shm_list) + # 从后往前遍历shm_list,删除每一个元素 for idx in range(shm_list_len): del self.shm_list[shm_list_len - idx - 1] + # 删除shm_list del self.shm_list + # 关闭文件 self.close() + # 结束线程 self.join_thread() diff --git a/mindspore/python/mindspore/dataset/engine/samplers.py b/mindspore/python/mindspore/dataset/engine/samplers.py index 514eb25412a..aec2d4a4aa1 100644 --- a/mindspore/python/mindspore/dataset/engine/samplers.py +++ b/mindspore/python/mindspore/dataset/engine/samplers.py @@ -29,6 +29,7 @@ from ..core import validator_helpers as validator def select_sampler(num_samples, input_sampler, shuffle, num_shards, shard_id): """ Create sampler based on user input. + 根据用户输入创建一个采样器。 Args: num_samples (int): Number of samples. @@ -40,8 +41,16 @@ def select_sampler(num_samples, input_sampler, shuffle, num_shards, shard_id): Returns: Sampler, sampler selected based on user input. """ + # 根据输入的参数,函数会根据不同的情况选择不同的采样器类型。 if input_sampler is not None: + # 当用户输入给定的采样器时,我们不再选择采样器类型。 + # 意味着以下参数都为空: num_shards, shard_id, shuffle, num_samples + # 下面这个例子为: + # sampler = ds.DistributedSampler(num_shards=8, shard_id=3, shuffle=shuffle) + # data1 = ds.VOCDataset(voc_dir, decode=True, sampler=sampler, num_shards=4, shard_id=1) + # 在这种情况下,用户给出了与样本相关的不同参数,这些参数相互矛盾。 + # 为了防止这种情况,如果这些参数都是None,只允许用户手动指定采样器。 # If the user provided a sampler, then it doesn't matter what the other args are because # we are being asked specifically to use the given sampler. # That means the following arguments: num_shards, shard_id, shuffle, num_samples should all @@ -50,35 +59,56 @@ def select_sampler(num_samples, input_sampler, shuffle, num_shards, shard_id): # data1 = ds.VOCDataset(voc_dir, decode=True, sampler=sampler, num_shards=4, shard_id=1) # In this case, the user has given different sample-related arguments that contradict each other. # To prevent this, only allow the user to manually specify the sampler if those arguments are all None + # 检查input_sampler是否为内置的采样器类型,并且检查其他参数(如num_shards、shard_id和shuffle)是否为None。 if (isinstance(input_sampler, BuiltinSampler) and (any(arg is not None for arg in [num_shards, shard_id, shuffle, num_samples]))): + # 如果时内置采样器类型且参数不为None,抛出一个异常 raise ValueError( 'Conflicting arguments during sampler assignments. num_samples: {}, num_shards: {},' ' shard_id: {}, shuffle: {}.'.format(num_samples, num_shards, shard_id, shuffle)) + # 检查input_sampler是否为内置的采样器类型 if isinstance(input_sampler, BuiltinSampler): return input_sampler + # 如果input_sampler不是str类型,是np.ndarray,list或tuple类型 if not isinstance(input_sampler, str) and isinstance(input_sampler, (np.ndarray, list, tuple)): + # 创建一个SubsetSampler对象,用于实现从给定序列的索引中采样的元素的功能 return SubsetSampler(input_sampler, num_samples) if not isinstance(input_sampler, str) and validator.is_iterable(input_sampler): # in this case, the user passed in their own sampler object that's not of type BuiltinSampler + # 返回一个IterSampler对象。这种情况下用户提供的采样器是一个可迭代的对象,但不继承自Sampler类。 + # 这意味着这个对象可能不是我们期望的采样器类型,因此我们需要对其进行进一步处理。 return IterSampler(input_sampler, num_samples) + # 如果input_sampler是整型 if isinstance(input_sampler, int): + # 返回SubsetSampler对象 return SubsetSampler([input_sampler]) raise TypeError('Unsupported sampler object of type ({})'.format(type(input_sampler))) + # 检查shuffle是否为None。如果是,那么它会检查num_shards是否为None if shuffle is None: + # 如果num_shards不为None,那么说明用户已经指定了分片采样 if num_shards is not None: # If shuffle is not specified, sharding enabled, use distributed random sampler shuffle = True + # 函数会启用分片随机采样并返回一个DistributedSampler对象。 return DistributedSampler(num_shards, shard_id, shuffle=shuffle, num_samples=num_samples) # If shuffle is not specified, sharding disabled, use random sampler + # 检查num_samples是否不为None且不为0 if num_samples is not None and num_samples != 0: + # replacement设置为True。采样器会重复采样,直到采样完指定的数量 return RandomSampler(replacement=True, num_samples=num_samples) + # 返回一个只采样一次的RandomSampler对象 return RandomSampler(num_samples=num_samples) + + # 根据shuffle和num_shards的值来决定使用哪种采样器 + # 检查shuffle是否为True if shuffle is True: + # 检查num_shards是否为None if num_shards is not None: # If shuffle enabled, sharding enabled, use distributed random sampler + # 如果num_shards不为None,那么说明用户已经指定了分片采样,因此函数会启用分片随机采样并返回一个DistributedSampler对象 return DistributedSampler(num_shards, shard_id, shuffle=shuffle, num_samples=num_samples) # If shuffle enabled, sharding disabled, use random sampler + # 检查num_samples是否不为None且不为0。如果是,那么它返回一个RandomSampler对象 if num_samples is not None: return RandomSampler(replacement=True, num_samples=num_samples) return RandomSampler(num_samples=num_samples) @@ -86,14 +116,17 @@ def select_sampler(num_samples, input_sampler, shuffle, num_shards, shard_id): # If shuffle disabled, sharding enabled, use distributed sequential sampler return DistributedSampler(num_shards, shard_id, shuffle=shuffle, num_samples=num_samples) # If shuffle disabled, sharding disabled, use sequential sampler + # 返回一个只采样一次的SequentialSampler对象 return SequentialSampler(num_samples=num_samples) class BuiltinSampler: """ Base class for BuiltinSampler. + 用于处理内置的采样器。 User should not extend this class. + 用户不应该扩展这个类。 """ def __init__(self, num_samples=None): @@ -107,6 +140,9 @@ class BuiltinSampler: """ Add a sub-sampler for given sampler. The parent will receive all data from the output of sub-sampler sampler and apply its sample logic to return new samples. + 用于向现有的采样器添加一个子采样器。采样器的子采样器可以是一个内置的采样器。 + 允许一个采样器接收另一个采样器的结果,并将它的采样逻辑应用到新样本上。这样,用户可以创 + 建一个复杂的采样器网络,以便根据需要灵活地采样数据。 Args: sampler (Sampler): Object used to choose samples from the dataset. Only builtin @@ -118,13 +154,16 @@ class BuiltinSampler: >>> sampler.add_child(ds.RandomSampler(num_samples=4)) >>> dataset = ds.Cifar10Dataset(cifar10_dataset_dir, sampler=sampler) """ + # 如果self.child_sampler已经有一个子采样器 if self.child_sampler is not None: + # 抛出一个异常 raise RuntimeError("Cannot add child sampler, this sampler already has a child.") self.child_sampler = sampler def get_child(self): """ Get the child sampler of given sampler. + 用于从给定的采样器中获取子采样器。 Returns: Sampler, The child sampler of given sampler. @@ -137,23 +176,35 @@ class BuiltinSampler: return self.child_sampler def parse_child(self): - """ Parse the child sampler. """ + """ Parse the child sampler. + 解析子采样器。 + """ c_child_sampler = None + # 检查self.child_sampler是否为None if self.child_sampler is not None: + # 解析子采样器 c_child_sampler = self.child_sampler.parse() + # 返回解析后的子采样器 return c_child_sampler def parse_child_for_minddataset(self): - """ Parse the child sampler for MindRecord. """ + """ Parse the child sampler for MindRecord. + 与上面的parse_child方法类似,但是它主要用于处理MindDataset + """ c_child_sampler = None + # 检查self.child_sampler是否为None if self.child_sampler is not None: + # 解析子采样器 c_child_sampler = self.child_sampler.parse_for_minddataset() + # 返回解析后的子采样器 return c_child_sampler + # 用于检查采样器是否已经打乱数据,未实现的方法 def is_shuffled(self): """ Not implemented. """ raise NotImplementedError("Sampler must implement is_shuffled.") + # 用于检查采样器是否已经分片,未实现的方法 def is_sharded(self): """ Not implemented. """ raise NotImplementedError("Sampler must implement is_sharded.") @@ -212,17 +263,23 @@ class BuiltinSampler: if child_samples is not None: return min(self.num_samples, child_samples) + # 如果子采样器没有返回样本数,则返回父采样器设置的样本数 return self.num_samples + # 如果父采样器没有设置样本数,则返回子采样器返回的样本数 return child_samples + # 如果没有父采样器,则返回父采样器设置的样本数 return self.num_samples + class Sampler(BuiltinSampler): """ Base class for user defined sampler. + 通用的采样器类。 A user defined sampler can be used with any existing dataset with sampler support. + 用户可以继承这个类来创建自己的采样器。 A required _iter_() method should by overridden by the user for sample index generation. An optional reset() method can be overridden for per repeat reset, @@ -239,6 +296,7 @@ class Sampler(BuiltinSampler): """ def __init__(self, num_samples=None): + # 调用父类构造函数 super().__init__(num_samples) self.dataset_size = 0 self.child_sampler = None @@ -246,8 +304,11 @@ class Sampler(BuiltinSampler): def __iter__(self): """ + 抽象方法 User defined iterator, must be overridden. + 用户定义迭代器时,必须重写此方法。 _handshake is guaranteed to be called prior to iterator construction. + _handshake是一个私有方法,会在构建迭代器之前被调用 """ raise NotImplementedError @@ -257,63 +318,92 @@ class Sampler(BuiltinSampler): """ # Initialization handshake callback + # 初始化 handshake 回调 # Do not override this method! + # 不要重写这个方法! def _handshake(self, ds_size, num_samples): + # 设置数据集大小 self.dataset_size = ds_size + # 设置样本数量 self.num_samples = num_samples # Indices fetcher # Do not override this method! + # # 不要重写这个方法! # pylint: disable=missing-docstring + # 定义一个函数_get_indices,用于获取索引 def _get_indices(self): + # 创建一个迭代器 sampler_iter = iter(self) + # 创建一个空列表 ret = [] + # 循环num_samples次 for _ in range(self.num_samples): try: + # 获取下一个索引 idx = next(sampler_iter) + # 将索引添加到ret中 ret.append(idx) + # 如果迭代结束,抛出StopIteration异常 except StopIteration: break + # 将ret转换为numpy数组 indices = np.array(ret) + # 如果转换后的数组类型为object,抛出RuntimeError异常 if indices.dtype == object: raise RuntimeError("Fetched indices can not be converted to a valid ndarray.") + # 返回转换后的数组 return indices # Instance fetcher # Do not override this method! + # 定义一个函数parse,用于解析sampler def parse(self): """ Parse the sampler.""" + # 如果num_samples不为None,则将num_samples赋值给num_samples,否则赋值为0 num_samples = self.num_samples if self.num_samples is not None else 0 + # 创建一个PreBuiltSamplerObj对象,参数为num_samples和self c_sampler = cde.PreBuiltSamplerObj(num_samples, self) + # 解析子sampler c_child_sampler = self.parse_child() + # 将子sampler添加到c_sampler中 c_sampler.add_child(c_child_sampler) + # 返回c_sampler return c_sampler - + + # 用于将一个子采样器添加到当前采样器中 def add_child(self, sampler): self.child_sampler = sampler + # 用于实现多级放样(MCMC)算法中的辅助采样器 def get_child(self): return self.child_sampler + # 解析子采样器 def parse_child(self): c_child_sampler = None + # 如果子采样器不为空 if self.child_sampler is not None: + # 解析子采样器 c_child_sampler = self.child_sampler.parse() return c_child_sampler + # 用于检查采样器是否已经打乱数据 def is_shuffled(self): if self.child_sampler is None: return False return self.child_sampler.is_shuffled() + # 用于检查采样器是否已经分片 def is_sharded(self): if self.child_sampler is None: return False return self.child_sampler.is_sharded() + # 返回此数据集的样本数量 def get_num_samples(self): if self.num_samples is None: return None @@ -323,6 +413,7 @@ class Sampler(BuiltinSampler): class DistributedSampler(BuiltinSampler): """ A sampler that accesses a shard of the dataset, it helps divide dataset into multi-subset for distributed training. + 用于分布式训练的子采样器。 Args: num_shards (int): Number of shards to divide the dataset into. @@ -354,22 +445,29 @@ class DistributedSampler(BuiltinSampler): """ def __init__(self, num_shards, shard_id, shuffle=True, num_samples=None, offset=-1): + # 检查num_shards是否为整数 if not isinstance(num_shards, int): raise TypeError("num_shards must be integer but was: {}.".format(num_shards)) + # 检查shard_id是否为整数 if not isinstance(shard_id, int): raise TypeError("shard_id must be integer but was: {}.".format(shard_id)) + # 检查shuffle是否为布尔值 if not isinstance(shuffle, bool): raise TypeError("shuffle must be a boolean value but was: {}.".format(shuffle)) + # 检查num_samples是否为整数 if num_samples is not None: + # 判断num_samples是否为整数 if not isinstance(num_samples, int): raise TypeError("num_samples must be integer but was: {}.".format(num_samples)) + # 判断num_samples是否小于0或者大于INT64_MAX if num_samples < 0 or num_samples > validator.INT64_MAX: raise ValueError("num_samples exceeds the boundary between {} and {}(INT64_MAX)!" .format(0, validator.INT64_MAX)) + # 检查offset是否为整数 if not isinstance(offset, int): raise TypeError("offset must be integer but was: {}.".format(offset)) @@ -382,40 +480,61 @@ class DistributedSampler(BuiltinSampler): def parse(self): """ Parse the sampler.""" + # 获取num_samples,如果没有指定,则默认为0 num_samples = self.num_samples if self.num_samples is not None else 0 + # 获取shuffle,如果没有指定,则默认为True shuffle = self.shuffle if self.shuffle is not None else True + # 获取offset,如果没有指定,则默认为-1 offset = self.offset if self.offset is not None else -1 # each time user calls create_dict_iterator() (to do repeat) sampler would get a different seed to shuffle + # 每次调用create_dict_iterator()(重复)时,sampler会得到一个不同的种子来shuffle self.seed += 1 + # 创建一个DistributedSamplerObj实例,参数分别为num_shards,shard_id,shuffle,num_samples,seed,offset,True c_sampler = cde.DistributedSamplerObj(self.num_shards, self.shard_id, shuffle, num_samples, self.seed, offset, True) + # 解析子采样器 c_child_sampler = self.parse_child() + # 将子采样器添加到父采样器中 c_sampler.add_child(c_child_sampler) + # 返回父采样器 return c_sampler def parse_for_minddataset(self): - """ Parse the sampler for MindRecord.""" + """ Parse the sampler for MindRecord. + 用于为MindRecord数据集创建一个分布式采样器 + """ + # 从self对象中获取num_samples和shuffle属性 + # 如果num_samples为None,则将其设置为0 num_samples = self.num_samples if self.num_samples is not None else 0 + # 如果shuffle为None,则将其设置为True shuffle = self.shuffle if self.shuffle is not None else True + # 创建一个MindrecordDistributedSampler对象,参数为总分区数,当前分区ID,是否打乱数据,随机数种子,采样数量和采样偏移量 c_sampler = cde.MindrecordDistributedSampler(self.num_shards, self.shard_id, shuffle, self.seed, num_samples, self.offset) + # 解析子采样器 c_child_sampler = self.parse_child_for_minddataset() + # 将子采样器添加到分布式采样器的children列表中 c_sampler.add_child(c_child_sampler) + # 设置分布式采样器的num_samples属性 c_sampler.set_num_samples(num_samples) + # 返回创建的分布式采样器对象 return c_sampler + # 用于检查采样器是否已经打乱数据 def is_shuffled(self): if self.child_sampler is None: return self.shuffle return self.child_sampler.is_shuffled() + # 用于检查采样器是否已经分片 def is_sharded(self): if self.child_sampler is None: return self.num_shards > 1 return self.child_sampler.is_sharded() + # 用于设置分布式采样器的采样偏移量 def set_offset(self, offset): self.offset = offset return self @@ -423,7 +542,7 @@ class DistributedSampler(BuiltinSampler): class PKSampler(BuiltinSampler): """ - Samples K elements for each P class in the dataset. + 用于从MindRecord数据集中对于每个P类,采样K个元素。 Args: num_val (int): Number of elements to sample for each class. @@ -451,21 +570,28 @@ class PKSampler(BuiltinSampler): """ def __init__(self, num_val, num_class=None, shuffle=False, class_column='label', num_samples=None): + # 检查num_val的类型是否为int型 if not isinstance(num_val, int): raise TypeError("num_val must be integer but was: {}.".format(num_val)) - + + # 检查num_class是否为None if num_class is not None: raise NotImplementedError("Not supported to specify num_class for PKSampler.") + # 检查shuffle的类型是否为bool型 if not isinstance(shuffle, bool): raise TypeError("shuffle must be a boolean value but was: {}.".format(shuffle)) + # 检查class_column的类型是否为str型 if not isinstance(class_column, str): raise TypeError("class_column must be a str value but was: {}.".format(class_column)) + # 检查num_samples是否为None if num_samples is not None: + # 检查num_samples的类型是否为int型 if not isinstance(num_samples, int): raise TypeError("num_samples must be integer but was: {}.".format(num_samples)) + # 检查num_samples是否在有效范围内 if num_samples < 0 or num_samples > validator.INT64_MAX: raise ValueError("num_samples exceeds the boundary between {} and {}(INT64_MAX)!" .format(0, validator.INT64_MAX)) @@ -477,19 +603,27 @@ class PKSampler(BuiltinSampler): def parse(self): """ Parse the sampler.""" + # 如果num_samples不为空,则取num_samples,否则取0 num_samples = self.num_samples if self.num_samples is not None else 0 + # 如果shuffle不为空,则取shuffle,否则取False shuffle = self.shuffle if self.shuffle is not None else False + # 创建一个PKSamplerObj对象 c_sampler = cde.PKSamplerObj(self.num_val, shuffle, num_samples) + # 解析子采样器 c_child_sampler = self.parse_child() + # 将子采样器添加到PKSamplerObj对象中 c_sampler.add_child(c_child_sampler) + # 返回PKSamplerObj对象 return c_sampler + # 用于检查采样器是否已经打乱数据 def is_shuffled(self): if self.child_sampler is None: return self.shuffle return self.child_sampler.is_shuffled() + # 用于检查采样器是否已经分片 def is_sharded(self): if self.child_sampler is None: return False @@ -497,21 +631,33 @@ class PKSampler(BuiltinSampler): return self.child_sampler.is_sharded() def parse_for_minddataset(self): - """Parse the sampler for MindRecord.""" + """Parse the sampler for MindRecord. + 用于为MindRecord数据集创建一个解析器 + """ + # 检查class_column是否为空字符串或非字符串类型 if not self.class_column or not isinstance(self.class_column, str): + # 如果是,则抛出一个异常 raise ValueError("class_column should be a not empty string value, \ but got class_column: {}.".format(self.class_column)) + # 从self对象中获取num_samples属性 + # 如果num_samples为None,则将其设置为0 num_samples = self.num_samples if self.num_samples is not None else 0 + # 创建一个MindrecordPkSampler对象,参数为验证集数量,类别列名,是否打乱数据,采样数量 c_sampler = cde.MindrecordPkSampler(self.num_val, self.class_column, self.shuffle, num_samples) + # 解析子解析器 c_child_sampler = self.parse_child_for_minddataset() + # 将子解析器添加到解析器的children列表中 c_sampler.add_child(c_child_sampler) + # 设置解析器的num_samples属性 c_sampler.set_num_samples(num_samples) + # 返回创建的解析器对象 return c_sampler class RandomSampler(BuiltinSampler): """ Samples the elements randomly. + 用于从数据集中随机采样元素。 Args: replacement (bool, optional): If True, put the sample ID back for the next draw (default=False). @@ -531,12 +677,17 @@ class RandomSampler(BuiltinSampler): """ def __init__(self, replacement=False, num_samples=None): + # 检查replacement的类型是否为bool型 if not isinstance(replacement, bool): raise TypeError("replacement must be a boolean value but was: {}.".format(replacement)) + # 检查num_samples是否为None + if num_samples is not None: + # 检查num_samples的类型是否为int型 if not isinstance(num_samples, int): raise TypeError("num_samples must be integer but was: {}.".format(num_samples)) + # 检查num_samples是否在有效范围内 if num_samples < 0 or num_samples > validator.INT64_MAX: raise ValueError("num_samples exceeds the boundary between {} and {}(INT64_MAX)!" .format(0, validator.INT64_MAX)) @@ -548,25 +699,39 @@ class RandomSampler(BuiltinSampler): def parse(self): """ Parse the sampler.""" + # 如果num_samples不为空,则取num_samples,否则取0 num_samples = self.num_samples if self.num_samples is not None else 0 + # 如果replacement不为空,则取replacement,否则取False replacement = self.replacement if self.replacement is not None else False + # 创建一个RandomSamplerObj对象 c_sampler = cde.RandomSamplerObj(replacement, num_samples, self.reshuffle_each_epoch) + # 解析子采样器 c_child_sampler = self.parse_child() + # 将子采样器添加到PKSamplerObj对象中 c_sampler.add_child(c_child_sampler) + # 返回RandomSamplerObj对象 return c_sampler def parse_for_minddataset(self): """Parse the sampler for MindRecord.""" + # 如果num_samples不为空,则取num_samples,否则取0 num_samples = self.num_samples if self.num_samples is not None else 0 + # 创建一个MindrecordRandomSampler对象 c_sampler = cde.MindrecordRandomSampler(num_samples, self.replacement, self.reshuffle_each_epoch) + # 解析子解析器 c_child_sampler = self.parse_child_for_minddataset() + # 将子解析器添加到解析器的children列表中 c_sampler.add_child(c_child_sampler) + # 设置解析器的num_samples属性 c_sampler.set_num_samples(num_samples) + # 返回MindrecordRandomSampler对象 return c_sampler + # 用于检查采样器是否已经打乱数据 def is_shuffled(self): return True + # 用于检查采样器是否已经分片 def is_sharded(self): if self.child_sampler is None: return False @@ -577,6 +742,7 @@ class RandomSampler(BuiltinSampler): class SequentialSampler(BuiltinSampler): """ Samples the dataset elements sequentially that is equivalent to not using a sampler. + 用于从数据集中按顺序采样元素。 Args: start_index (int, optional): Index to start sampling at. (default=None, start at first ID) @@ -597,6 +763,7 @@ class SequentialSampler(BuiltinSampler): """ def __init__(self, start_index=None, num_samples=None): + # 检查参数类型 if start_index is not None and not isinstance(start_index, int): raise TypeError("start_index must be integer but was: {}.".format(start_index)) @@ -612,29 +779,44 @@ class SequentialSampler(BuiltinSampler): def parse(self): """ Parse the sampler.""" + # 如果start_index不为空,则取start_index,否则取False start_index = self.start_index if self.start_index is not None else 0 + # 如果num_samples不为空,则取num_samples,否则取0 num_samples = self.num_samples if self.num_samples is not None else 0 + # 创建一个SequentialSamplerObj对象 c_sampler = cde.SequentialSamplerObj(start_index, num_samples) + # 解析子采样器 c_child_sampler = self.parse_child() + # 将子解析器添加到解析器的children列表中 c_sampler.add_child(c_child_sampler) + # 返回SequentiaSamplerObj对象 return c_sampler def parse_for_minddataset(self): """Parse the sampler for MindRecord.""" + # 如果start_index不为空,则取start_index,否则取False start_index = self.start_index if self.start_index is not None else 0 + # 如果num_samples不为空,则取num_samples,否则取0 num_samples = self.num_samples if self.num_samples is not None else 0 + # 创建一个MindrecordSequentialSampler对象 c_sampler = cde.MindrecordSequentialSampler(num_samples, start_index) + # 解析子解析器 c_child_sampler = self.parse_child_for_minddataset() + # # 将子解析器添加到解析器的children列表中 c_sampler.add_child(c_child_sampler) + # 设置解析器的num_samples属性 c_sampler.set_num_samples(num_samples) + # 返回MindrecordSequentialSampler对象 return c_sampler + # 用于检查采样器是否已经打乱数据 def is_shuffled(self): if self.child_sampler is None: return False return self.child_sampler.is_shuffled() + # 用于检查采样器是否已经分片 def is_sharded(self): if self.child_sampler is None: return False @@ -698,16 +880,20 @@ class SubsetSampler(BuiltinSampler): super().__init__(num_samples) def parse(self): - """ Parse the sampler.""" + """ Parse the sampler. + 代码含义与之前相似 + """ num_samples = self.num_samples if self.num_samples is not None else 0 c_sampler = cde.SubsetSamplerObj(self.indices, num_samples) c_child_sampler = self.parse_child() c_sampler.add_child(c_child_sampler) return c_sampler + # 用于检查采样器是否已经打乱数据 def is_shuffled(self): return False + # 用于检查采样器是否已经分片 def is_sharded(self): if self.child_sampler is None: return False @@ -715,7 +901,9 @@ class SubsetSampler(BuiltinSampler): return self.child_sampler.is_sharded() def parse_for_minddataset(self): - """Parse the sampler for MindRecord.""" + """Parse the sampler for MindRecord. + 代码含义与之前相似 + """ c_sampler = cde.MindrecordSubsetSampler(self.indices) c_child_sampler = self.parse_child_for_minddataset() c_sampler.add_child(c_child_sampler) @@ -723,6 +911,9 @@ class SubsetSampler(BuiltinSampler): return c_sampler def get_num_samples(self): + ''' + 返回此数据集的样本数 + ''' num_samples = super().get_num_samples() if num_samples is None: return len(self.indices) @@ -752,18 +943,23 @@ class SubsetRandomSampler(SubsetSampler): """ def parse(self): - """ Parse the sampler.""" + """ Parse the sampler. + 代码含义与之前相似 + """ num_samples = self.num_samples if self.num_samples is not None else 0 c_sampler = cde.SubsetRandomSamplerObj(self.indices, num_samples) c_child_sampler = self.parse_child() c_sampler.add_child(c_child_sampler) return c_sampler + # 用于检查采样器是否已经打乱数据 def is_shuffled(self): return True def parse_for_minddataset(self): - """Parse the sampler for MindRecord.""" + """Parse the sampler for MindRecord. + 代码含义与之前相似 + """ c_sampler = cde.MindrecordSubsetSampler(self.indices, ds.config.get_seed()) c_child_sampler = self.parse_child_for_minddataset() c_sampler.add_child(c_child_sampler) @@ -774,6 +970,7 @@ class SubsetRandomSampler(SubsetSampler): class IterSampler(Sampler): """ User provided an iterable object without inheriting from our Sampler class. + 允许用户提供一个没有继承自我们的Sampler类的迭代对象。 Note: This class exists to allow handshake logic between dataset operators and user defined samplers. @@ -856,7 +1053,9 @@ class WeightedRandomSampler(BuiltinSampler): super().__init__(num_samples) def parse(self): - """ Parse the sampler.""" + """ Parse the sampler. + 代码含义与之前相似 + """ num_samples = self.num_samples if self.num_samples is not None else 0 replacement = self.replacement if self.replacement is not None else True c_sampler = cde.WeightedRandomSamplerObj(self.weights, num_samples, replacement) @@ -864,9 +1063,11 @@ class WeightedRandomSampler(BuiltinSampler): c_sampler.add_child(c_child_sampler) return c_sampler + # 用于检查采样器是否已经打乱数据 def is_shuffled(self): return True + # 用于检查采样器是否已经分片 def is_sharded(self): if self.child_sampler is None: return False diff --git a/mindspore/python/mindspore/dataset/engine/serializer_deserializer.py b/mindspore/python/mindspore/dataset/engine/serializer_deserializer.py index 2789d3ef8fe..5b848d51662 100644 --- a/mindspore/python/mindspore/dataset/engine/serializer_deserializer.py +++ b/mindspore/python/mindspore/dataset/engine/serializer_deserializer.py @@ -25,6 +25,7 @@ from . import datasets as de def serialize(dataset, json_filepath=""): """ Serialize dataset pipeline into a JSON file. + 用于将数据集管道序列化为一个JSON文件。 Note: Currently some Python objects are not supported to be serialized. @@ -48,12 +49,14 @@ def serialize(dataset, json_filepath=""): >>> # serialize it to JSON file >>> serialized_data = ds.serialize(dataset, json_filepath="/path/to/mnist_dataset_pipeline.json") """ + # 返回一个字典,其中包含序列化后的数据集图。 return dataset.to_json(json_filepath) def deserialize(input_dict=None, json_filepath=None): """ Construct dataset pipeline from a JSON file produced by de.serialize(). + 用于从JSON文件中构造数据集管道。 Note: Currently Python function deserialization of map operator are not supported. @@ -82,17 +85,24 @@ def deserialize(input_dict=None, json_filepath=None): """ data = None + # 判断input_dict是否为空 if input_dict: + # 将input_dict转换为de.Dataset对象 data = de.DeserializedDataset(input_dict) + # 如果json_filepath不为空 if json_filepath: + # 将json_filepath转换为de.Dataset对象 data = de.DeserializedDataset(json_filepath) return data +# 定义一个函数expand_path,用于将相对路径转换为绝对路径 def expand_path(node_repr, key, val): - """Convert relative to absolute path.""" + """Convert relative to absolute path.用于将相对路径转换为绝对路径。""" + # 如果val是一个列表 if isinstance(val, list): + # 那么将其中的每个文件路径转换为绝对路径,并添加到node_repr字典中 node_repr[key] = [os.path.abspath(file) for file in val] else: node_repr[key] = os.path.abspath(val) @@ -101,6 +111,7 @@ def expand_path(node_repr, key, val): def show(dataset, indentation=2): """ Write the dataset pipeline graph to logger.info file. + 用于将数据集管道图形写入logger.info文件。 Args: dataset (Dataset): The starting node. @@ -114,14 +125,16 @@ def show(dataset, indentation=2): >>> dataset = dataset.batch(batch_size=10, drop_remainder=True) >>> ds.show(dataset) """ - + # 使用json.dumps函数将其转换为格式化的字符串 pipeline = dataset.to_json() + # 写入logger.info文件 logger.info(json.dumps(pipeline, indent=indentation)) def compare(pipeline1, pipeline2): """ Compare if two dataset pipelines are the same. + 用于比较两个数据集管道的是否相同。 Args: pipeline1 (Dataset): a dataset pipeline. @@ -135,5 +148,5 @@ def compare(pipeline1, pipeline2): >>> pipeline2 = ds.Cifar10Dataset(cifar10_dataset_dir, num_samples=100) >>> res = ds.compare(pipeline1, pipeline2) """ - + # 返回一个bool值 return pipeline1.to_json() == pipeline2.to_json() diff --git a/mindspore/python/mindspore/dataset/text/__init__.py b/mindspore/python/mindspore/dataset/text/__init__.py index 489bef9a38f..ff512cbed33 100644 --- a/mindspore/python/mindspore/dataset/text/__init__.py +++ b/mindspore/python/mindspore/dataset/text/__init__.py @@ -11,6 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +# mindspore.dataset.text。它提供了两个部分:transforms和utils。transforms是一个高性能的自然语言处理(NLP)文本处理模块,它使用ICU4C和cppjieba进行开发。utils提供了用于NLP文本处理的通用方法 """ This module is to support text processing for NLP. It includes two parts: transforms and utils. transforms is a high performance @@ -29,6 +31,34 @@ Descriptions of common data processing terms are as follows: - TensorOperation, the base class of all data processing operations implemented in C++. - TextTensorOperation, the base class of all text processing operations. It is a derived class of TensorOperation. """ +""" +该模块中包含了以下内容: + + Lookup:用于查找字典中的单词。 + JiebaTokenizer:使用结巴分词库进行分词的类。 + UnicodeCharTokenizer:用于将Unicode字符切分为字符的类。 + Ngram:用于生成n-gram的类。 + WordpieceTokenizer:用于对单词进行分块的类。 + TruncateSequencePair:用于截断序列对的类。 + ToNumber:用于将文本转换为数字的类。 + SlidingWindow:用于生成滑动窗口的类。 + SentencePieceTokenizer:使用SentencePiece进行分词的类。 + PythonTokenizer:使用Python进行分词的类。 + ToVectors:用于将文本向量化的类。 + to_str:将字节转换为字符串的函数。 + to_bytes:将字符串转换为字节的函数。 + JiebaMode:结巴分词的模式。 + Vocab:词汇表类。 + NormalizeForm:文本正则化模式。 + SentencePieceVocab:SentencePiece词汇表类。 + SentencePieceModel:SentencePiece模型类。 + SPieceTokenizerOutType:SentencePiece分词器的输出类型。 + SPieceTokenizerLoadType:SentencePiece分词器的加载类型。 + Vectors:向量类。 + FastText:FastText向量类。 + GloVe:GloVe向量类。 + CharNGram:CharNGram向量类 +""" import platform from .transforms import Lookup, JiebaTokenizer, UnicodeCharTokenizer, Ngram, WordpieceTokenizer, \ TruncateSequencePair, ToNumber, SlidingWindow, SentencePieceTokenizer, PythonTokenizer, ToVectors @@ -43,9 +73,23 @@ __all__ = [ "GloVe", "CharNGram" ] +# 这段代码是用于判断当前操作系统是否为Windows +# 如果不是Windows,则从mindspore.dataset.text.transforms模块中导入相应的数据处理操作类 +# 其中,platform.system()函数用于获取当前操作系统的名称,并将其转换为小写 if platform.system().lower() != 'windows': from .transforms import UnicodeScriptTokenizer, WhitespaceTokenizer, CaseFold, NormalizeUTF8, \ RegexReplace, RegexTokenizer, BasicTokenizer, BertTokenizer - + # 如果当前操作系统不是Windows(即platform.system().lower() != 'windows'),则从transforms模块中导入以下数据处理操作类: + """ + UnicodeScriptTokenizer:用于将Unicode脚本切分为单词的类。 + WhitespaceTokenizer:用于将空格分隔的文本切分为单词的类。 + CaseFold:用于将文本转换为小写的类。 + NormalizeUTF8:用于对UTF-8编码的文本进行正则化处理的类。 + RegexReplace:用于使用正则表达式替换文本的类。 + RegexTokenizer:使用正则表达式切分文本的类。 + BasicTokenizer:用于对文本进行基本切分的类。 + BertTokenizer:用于对BERT模型进行分词的类。 + """ + # 同时,将导入的类添加到__all__列表中,以便在其他模块中使用这些类 __all__.extend(["UnicodeScriptTokenizer", "WhitespaceTokenizer", "CaseFold", "NormalizeUTF8", "RegexReplace", "RegexTokenizer", "BasicTokenizer", "BertTokenizer"]) diff --git a/mindspore/python/mindspore/dataset/text/transforms.py b/mindspore/python/mindspore/dataset/text/transforms.py index 65bec0bfcfe..f2b59f33d97 100644 --- a/mindspore/python/mindspore/dataset/text/transforms.py +++ b/mindspore/python/mindspore/dataset/text/transforms.py @@ -85,51 +85,16 @@ DE_C_INTER_SENTENCEPIECE_OUTTYPE = { class JiebaTokenizer(TextTensorOperation): - """ - Tokenize Chinese string into words based on dictionary. - - Note: - The integrity of the HMMSEgment algorithm and MPSegment algorithm files must be confirmed. - - Args: - hmm_path (str): Dictionary file is used by HMMSegment algorithm. - The dictionary can be obtained on the official website of cppjieba. - mp_path (str): Dictionary file is used by MPSegment algorithm. - The dictionary can be obtained on the official website of cppjieba. - mode (JiebaMode, optional): Valid values can be any of [JiebaMode.MP, JiebaMode.HMM, - JiebaMode.MIX](default=JiebaMode.MIX). - - - JiebaMode.MP, tokenize with MPSegment algorithm. - - JiebaMode.HMM, tokenize with Hidden Markov Model Segment algorithm. - - JiebaMode.MIX, tokenize with a mix of MPSegment and HMMSegment algorithm. - with_offsets (bool, optional): Whether or not output offsets of tokens (default=False). - - Raises: - ValueError: If path of HMMSegment dict is not provided. - ValueError: If path of MPSegment dict is not provided. - TypeError: If `hmm_path` or `mp_path` is not of type string. - TypeError: If `with_offsets` is not of type bool. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.text import JiebaMode - >>> # If with_offsets=False, default output one column {["text", dtype=str]} - >>> jieba_hmm_file = "/path/to/jieba/hmm/file" - >>> jieba_mp_file = "/path/to/jieba/mp/file" - >>> tokenizer_op = text.JiebaTokenizer(jieba_hmm_file, jieba_mp_file, mode=JiebaMode.MP, with_offsets=False) - >>> text_file_dataset = text_file_dataset.map(operations=tokenizer_op) - >>> # If with_offsets=False, then output three columns {["token", dtype=str], ["offsets_start", dtype=uint32], - >>> # ["offsets_limit", dtype=uint32]} - >>> tokenizer_op = text.JiebaTokenizer(jieba_hmm_file, jieba_mp_file, mode=JiebaMode.MP, with_offsets=True) - >>> text_file_dataset_1 = text_file_dataset_1.map(operations=tokenizer_op, input_columns=["text"], - ... output_columns=["token", "offsets_start", "offsets_limit"], - ... column_order=["token", "offsets_start", "offsets_limit"]) - """ - @check_jieba_init def __init__(self, hmm_path, mp_path, mode=JiebaMode.MIX, with_offsets=False): + ''' + 初始化函数,用于初始化HMM模型和MP模型 + :param hmm_path: HMM模型路径 + :param mp_path: MP模型路径 + :param mode: 模式 + :param with_offsets: 是否包含词偏移量 + ''' + # 检查模式是否为Jiebamode if not isinstance(mode, JiebaMode): raise TypeError("Wrong input type for mode, should be JiebaMode.") @@ -142,204 +107,156 @@ class JiebaTokenizer(TextTensorOperation): self.words = [] def parse(self): + ''' + 分词 + :return: 分词操作 + ''' + # 初始化分词操作 jieba_tokenizer = cde.JiebaTokenizerOperation(self.hmm_path, self.mp_path, DE_C_INTER_JIEBA_MODE[self.mode], self.with_offsets) + # 遍历单词 for word in self.words: + # 添加单词 jieba_tokenizer.add_word(word[0], word[1]) + # 返回分词操作 return jieba_tokenizer @check_jieba_add_word + # 将用户定义的词添加到 JiebaTokenizer 的字典中。 def add_word(self, word, freq=None): - """ - Add a user defined word to JiebaTokenizer's dictionary. - - Args: - word (str): The word to be added to the JiebaTokenizer instance. - The added word will not be written into the built-in dictionary on disk. - freq (int, optional): The frequency of the word to be added. The higher the frequency, - the better chance the word will be tokenized (default=None, use default frequency). - - Examples: - >>> from mindspore.dataset.text import JiebaMode - >>> jieba_hmm_file = "/path/to/jieba/hmm/file" - >>> jieba_mp_file = "/path/to/jieba/mp/file" - >>> jieba_op = text.JiebaTokenizer(jieba_hmm_file, jieba_mp_file, mode=JiebaMode.MP) - >>> sentence_piece_vocab_file = "/path/to/sentence/piece/vocab/file" - >>> with open(sentence_piece_vocab_file, 'r') as f: - ... for line in f: - ... word = line.split(',')[0] - ... jieba_op.add_word(word) - >>> text_file_dataset = text_file_dataset.map(operations=jieba_op, input_columns=["text"]) - """ - + # 如果freq为None,则将word添加到words列表中 if freq is None: self.words.append((word, 0)) + # 否则,将word和freq添加到words列表 else: self.words.append((word, freq)) @check_jieba_add_dict + # 将用户定义的词添加到 JiebaTokenizer 的字典中。 def add_dict(self, user_dict): - """ - Add a user defined word to JiebaTokenizer's dictionary. - - Args: - user_dict (Union[str, dict]): One of the two loading methods is file path(str) loading - (according to the Jieba dictionary format) and the other is Python dictionary(dict) loading, - Python Dict format: {word1:freq1, word2:freq2,...}. - Jieba dictionary format : word(required), freq(optional), such as: - - .. code-block:: - - word1 freq1 - word2 None - word3 freq3 - - Only valid word-freq pairs in user provided file will be added into the dictionary. - Rows containing invalid input will be ignored. No error nor warning Status is returned. - - Examples: - >>> from mindspore.dataset.text import JiebaMode - >>> jieba_hmm_file = "/path/to/jieba/hmm/file" - >>> jieba_mp_file = "/path/to/jieba/mp/file" - >>> user_dict = {"男默女泪": 10} - >>> jieba_op = text.JiebaTokenizer(jieba_hmm_file, jieba_mp_file, mode=JiebaMode.MP) - >>> jieba_op.add_dict(user_dict) - >>> text_file_dataset = text_file_dataset.map(operations=jieba_op, input_columns=["text"]) - """ - + ''' + 添加字典 + :param user_dict: 字典 + :return: + ''' + # 如果user_dict是字符串类型,则将其转换为字典 if isinstance(user_dict, str): self.__add_dict_py_file(user_dict) + # 如果user_dict是字典类型,则遍历字典中的每一项 elif isinstance(user_dict, dict): + # 将每一项添加到word_dict中 for k, v in user_dict.items(): self.add_word(k, v) + # 如果user_dict不是字符串或字典类型,则抛出异常 else: raise TypeError("The type of user_dict must str or dict.") + # 将用户定义的词通过文档添加到 JiebaTokenizer 的字典中。 def __add_dict_py_file(self, file_path): - """Add user defined word by file""" + ''' + 添加字典文件 + :param file_path: 字典文件路径 + :return: + ''' words_list = self.__parser_file(file_path) + # 遍历words_list for data in words_list: + # 如果data[1]为None,则freq为0 if data[1] is None: freq = 0 + # 否则,freq为data[1] else: freq = int(data[1]) - self.add_word(data[0], freq) + # 将data[0]和freq添加到self.words中 + self.add_word(data[0], freq) + # 解析用户通过文件定义的字典 def __parser_file(self, file_path): - """parser user defined word by file""" + ''' + 解析文件 + :param file_path: 文件路径 + :return: 返回词语列表 + ''' + # 检查路径是否存在 if not os.path.exists(file_path): raise ValueError( "user dict file {} is not exist.".format(file_path)) + # 获取文件路径 real_file_path = os.path.realpath(file_path) + # 将文件路径转换为真实路径 file_dict = open(real_file_path) + # 打开文件 data_re = re.compile('^\\s*([^\\s*]+?)\\s*([0-9]+)?\\s*$', re.U) + # 正则表达式,用于匹配字符串 words_list = [] for item in file_dict: + # 去除字符串两端的空格 data = item.strip() + # 如果字符串不是字符串类型,则调用__decode函数解码 if not isinstance(data, str): data = self.__decode(data) + # 使用正则表达式匹配字符串 tmp = data_re.match(data) + # 如果匹配不到,则跳过 if not tmp: continue + # 将匹配到的结果赋值给words words = tmp.groups() + # 将words追加到words_list中 words_list.append(words) + # 关闭文件 file_dict.close() + # 返回words_list return words_list + # 把字符串解码为UTF-8格式 def __decode(self, data): - """decode the dict file to utf8""" try: data = data.decode('utf-8') except UnicodeDecodeError: raise ValueError("user dict file must be utf8 format.") return data.lstrip('\ufeff') + # 检查模型路径是否存在 def __check_path__(self, model_path): - """check model path""" if not os.path.exists(os.path.realpath(model_path)): raise ValueError( " jieba mode file {} is not exist.".format(model_path)) +# 根据词表,将分词标记(token)映射到其索引值(id) class Lookup(TextTensorOperation): - """ - Look up a word into an id according to the input vocabulary table. - - Args: - vocab (Vocab): A vocabulary object. - unknown_token (str, optional): Word is used for lookup. In case of the word is out of vocabulary (OOV), - the result of lookup will be replaced with unknown_token. If the unknown_token is not specified or - it is OOV, runtime error will be thrown (default=None, means no unknown_token is specified). - data_type (mindspore.dtype, optional): The data type that lookup operation maps - string to(default=mindspore.int32). - - Raises: - TypeError: If `vocab` is not of type text.Vocab. - TypeError: If `unknown_token` is not of type string. - TypeError: If `data_type` is not of type mindspore.dtype. - - Supported Platforms: - ``CPU`` - - Examples: - >>> # Load vocabulary from list - >>> vocab = text.Vocab.from_list(['深', '圳', '欢', '迎', '您']) - >>> # Use Lookup operator to map tokens to ids - >>> lookup = text.Lookup(vocab) - >>> text_file_dataset = text_file_dataset.map(operations=[lookup]) - """ - @check_lookup + # 初始化Lookup类,参数vocab, unknown_token, data_type def __init__(self, vocab, unknown_token=None, data_type=mstype.int32): + ''' + 初始化函数 + :param vocab: 词汇表 + :param unknown_token: 未知词 + :param data_type: 数据类型 + ''' self.vocab = vocab self.unknown_token = unknown_token self.data_type = data_type def parse(self): + ''' + 解析操作 + ''' return cde.LookupOperation(self.vocab.c_vocab, self.unknown_token, str(mstype_to_detype(self.data_type))) - +# 从1-D的字符串生成N-gram class Ngram(TextTensorOperation): - """ - Generate n-gram from a 1-D string Tensor. - - Refer to https://en.wikipedia.org/wiki/N-gram#Examples for an overview of what n-gram is and how it works. - + ''' + 计算指定维度的ngram + Args: - n (list[int]): n in n-gram, which is a list of positive integers. For example, if n=[4, 3], then the result - would be a 4-gram followed by a 3-gram in the same tensor. If the number of words is not enough to make up - for a n-gram, an empty string will be returned. For example, 3 grams on ["mindspore", "best"] will result in - an empty string produced. - left_pad (tuple, optional): Padding performed on left side of the sequence shaped like ("pad_token", pad_width). - `pad_width` will be capped at n-1. For example, specifying left_pad=("_", 2) would pad left side of the - sequence with "__" (default=("", 0)). - right_pad (tuple, optional): Padding performed on right side of the sequence shaped like - ("pad_token", pad_width). `pad_width` will be capped at n-1. For example, specifying right_pad=("_", 2) - would pad right side of the sequence with "__" (default=("", 0)). - separator (str, optional): Symbol used to join strings together. For example, if 2-gram is - ["mindspore", "amazing"] with separator="-", the result would be ["mindspore-amazing"] - (default=" ", which will use whitespace as separator). - - Raises: - TypeError: If values of `n` not positive is not of type int. - ValueError: If values of `n` not positive. - ValueError: If `left_pad` is not a tuple of length 2. - ValueError: If `right_pad` is not a tuple of length 2. - TypeError: If `separator` is not of type string. - - Supported Platforms: - ``CPU`` - - Examples: - >>> ngram_op = text.Ngram(3, separator="-") - >>> output = ngram_op(["WildRose Country", "Canada's Ocean Playground", "Land of Living Skies"]) - >>> # output - >>> # ["WildRose Country-Canada's Ocean Playground-Land of Living Skies"] - >>> # same ngram_op called through map - >>> text_file_dataset = text_file_dataset.map(operations=ngram_op) - """ - + n (int): ngram的维度 + left_pad (tuple): 左边转换的填充 + right_pad (tuple): 右边转换的填充 + separator (str): 分隔符 + ''' @check_ngram def __init__(self, n, left_pad=("", 0), right_pad=("", 0), separator=" "): self.ngrams = n @@ -350,280 +267,143 @@ class Ngram(TextTensorOperation): def parse(self): return cde.NgramOperation(self.ngrams, self.left_pad, self.right_pad, self.separator) - +# 使用SentencePiece分词器对字符串进行分词 class SentencePieceTokenizer(TextTensorOperation): - """ - Tokenize scalar token or 1-D tokens to tokens by sentencepiece. - - Args: - mode (Union[str, SentencePieceVocab]): SentencePiece model. - If the input parameter is a file, it represents the path of SentencePiece mode to be loaded. - If the input parameter is a SentencePieceVocab object, it should be constructed in advanced. - out_type (SPieceTokenizerOutType): The type of output, it can be any of [SPieceTokenizerOutType.STRING, - SPieceTokenizerOutType.INT]. - - - SPieceTokenizerOutType.STRING, means output type of SentencePice Tokenizer is string. - - SPieceTokenizerOutType.INT, means output type of SentencePice Tokenizer is int. - - Raises: - TypeError: If `mode` is not of type string or SentencePieceVocab. - TypeError: If `out_type` is not of type SPieceTokenizerOutType. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.text import SentencePieceModel, SPieceTokenizerOutType - >>> sentence_piece_vocab_file = "/path/to/sentence/piece/vocab/file" - >>> vocab = text.SentencePieceVocab.from_file([sentence_piece_vocab_file], 5000, 0.9995, - ... SentencePieceModel.UNIGRAM, {}) - >>> tokenizer = text.SentencePieceTokenizer(vocab, out_type=SPieceTokenizerOutType.STRING) - >>> text_file_dataset = text_file_dataset.map(operations=tokenizer) - """ - @check_sentence_piece_tokenizer def __init__(self, mode, out_type): + ''' + 初始化SentencePieceTokenizer + :param mode: 可以是SentencePieceVocab或者SentencePieceVocab的实例 + :param out_type: 输出类型 + ''' self.mode = mode self.out_type = out_type def parse(self): + ''' + 解析SentencePieceTokenizer + :return:解析后的SentencePieveTokenizer + ''' self.mode = self.mode.c_sentence_piece_vocab if isinstance(self.mode, SentencePieceVocab) else self.mode return cde.SentencePieceTokenizerOperation(self.mode, DE_C_INTER_SENTENCEPIECE_OUTTYPE[self.out_type]) - +# 在输入数据的某个维度上进行滑窗切分处理,当前仅支持处理1-D的Tensor class SlidingWindow(TextTensorOperation): - """ - Construct a tensor from given data (only support 1-D for now), where each element in the dimension axis - is a slice of data starting at the corresponding position, with a specified width. - - Args: - width (int): The width of the window. It must be an integer and greater than zero. - axis (int, optional): The axis along which the sliding window is computed (default=0). - - Raises: - TypeError: If `width` is not of type int. - ValueError: If value of `width` is not positive. - TypeError: If `axis` is not of type int. - - Supported Platforms: - ``CPU`` - - Examples: - >>> dataset = ds.NumpySlicesDataset(data=[[1, 2, 3, 4, 5]], column_names="col1") - >>> # Data before - >>> # | col1 | - >>> # +--------------+ - >>> # | [[1, 2, 3, 4, 5]] | - >>> # +--------------+ - >>> dataset = dataset.map(operations=text.SlidingWindow(3, 0)) - >>> # Data after - >>> # | col1 | - >>> # +--------------+ - >>> # | [[1, 2, 3], | - >>> # | [2, 3, 4], | - >>> # | [3, 4, 5]] | - >>> # +--------------+ - """ - + ''' + 滑动窗口操作 + ''' @check_slidingwindow def __init__(self, width, axis=0): + ''' + 构造滑动窗口操作 + :param width: 滑动窗口的宽度 + :param axis: 滑动窗口的轴 + ''' self.width = width self.axis = axis def parse(self): + ''' + 解析滑动窗口操作 + :return: 滑动窗口操作 + ''' return cde.SlidingWindowOperation(self.width, self.axis) - +# 将字符串的每个元素转换为数字 class ToNumber(TextTensorOperation): - """ - Tensor operation to convert every element of a string tensor to a number. - - Strings are cast according to the rules specified in the following links, except that any strings which represent - negative numbers cannot be cast to an unsigned integer type, rules links are as follows: - https://en.cppreference.com/w/cpp/string/basic_string/stof, - https://en.cppreference.com/w/cpp/string/basic_string/stoul, - - Args: - data_type (mindspore.dtype): Type to be cast to. Must be a numeric type in mindspore.dtype. - - Raises: - TypeError: If `data_type` is not of type mindspore.dtype. - RuntimeError: If strings are invalid to cast, or are out of range after being cast. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore import dtype as mstype - >>> data = [["1", "2", "3"]] - >>> dataset = ds.NumpySlicesDataset(data) - >>> to_number_op = text.ToNumber(mstype.int8) - >>> dataset = dataset.map(operations=to_number_op) - """ - + ''' + 将数据类型转换为数字 + ''' @check_to_number def __init__(self, data_type): + ''' + :param data_type: 数据类型 + ''' data_type = mstype_to_detype(data_type) self.data_type = str(data_type) def parse(self): + ''' + 返回ToNumberOperation对象 + ''' return cde.ToNumberOperation(self.data_type) class ToVectors(TextTensorOperation): - """ - Look up a token into vectors according to the input vector table. - - Args: - vectors (Vectors): A vectors object. - unk_init (sequence, optional): Sequence used to initialize out-of-vectors (OOV) token - (default=None, initialize with zero vectors). - lower_case_backup (bool, optional): Whether to look up the token in the lower case. If False, each token in the - original case will be looked up; if True, each token in the original case will be looked up first, if not - found in the keys of the property stoi, the token in the lower case will be looked up (default=False). - - Raises: - TypeError: If `unk_init` is not of type sequence. - TypeError: If elements of `unk_init` is not of type float or int. - TypeError: If `lower_case_backup` is not of type bool. - - Supported Platforms: - ``CPU`` - - Examples: - >>> # Load vectors from file - >>> vectors = text.Vectors.from_file("/path/to/vectors/file") - >>> # Use ToVectors operator to map tokens to vectors - >>> to_vectors = text.ToVectors(vectors) - >>> text_file_dataset = text_file_dataset.map(operations=[to_vectors]) - """ - + ''' + 将文本转换为向量 + ''' @check_to_vectors def __init__(self, vectors, unk_init=None, lower_case_backup=False): + ''' + :param vectors: 向量 + :param unk_init: 文本未知的初始值 + :param lower_case_backup: 是否将文本转换为小写 + ''' self.vectors = vectors self.unk_init = unk_init if unk_init is not None else [] self.lower_case_backup = lower_case_backup def parse(self): + ''' + 解析函数,将文本转换为向量 + ''' return cde.ToVectorsOperation(self.vectors, self.unk_init, self.lower_case_backup) - +# 截断一对 1-D 字符串的内容,使其总长度小于给定长度 class TruncateSequencePair(TextTensorOperation): - """ - Truncate a pair of rank-1 tensors such that the total length is less than max_length. - - This operation takes two input tensors and returns two output Tensors. - - Args: - max_length (int): Maximum length required. - - Raises: - TypeError: If `max_length` is not of type int. - - Supported Platforms: - ``CPU`` - - Examples: - >>> dataset = ds.NumpySlicesDataset(data={"col1": [[1, 2, 3]], "col2": [[4, 5]]}) - >>> # Data before - >>> # | col1 | col2 | - >>> # +-----------+-----------| - >>> # | [1, 2, 3] | [4, 5] | - >>> # +-----------+-----------+ - >>> truncate_sequence_pair_op = text.TruncateSequencePair(max_length=4) - >>> dataset = dataset.map(operations=truncate_sequence_pair_op) - >>> # Data after - >>> # | col1 | col2 | - >>> # +-----------+-----------+ - >>> # | [1, 2] | [4, 5] | - >>> # +-----------+-----------+ - """ - + ''' + 对于一对文本序列,我们可以指定截断长度,以便我们可以在每个序列的末尾截断。 + ''' @check_pair_truncate def __init__(self, max_length): + ''' + :param max_length: 截断长度 + ''' self.max_length = max_length def parse(self): + ''' + 返回一个TruncateSequencePairOperation对象,它接受一个max_length参数 + ''' return cde.TruncateSequencePairOperation(self.max_length) - +# 使用Unicode分词器将字符串分词为Unicode字符 class UnicodeCharTokenizer(TextTensorOperation): - """ - Tokenize a scalar tensor of UTF-8 string to Unicode characters. - - Args: - with_offsets (bool, optional): Whether or not output offsets of tokens (default=False). - - Raises: - TypeError: If `with_offsets` is not of type bool. - - Supported Platforms: - ``CPU`` - - Examples: - >>> # If with_offsets=False, default output one column {["text", dtype=str]} - >>> tokenizer_op = text.UnicodeCharTokenizer(with_offsets=False) - >>> text_file_dataset = text_file_dataset.map(operations=tokenizer_op) - >>> # If with_offsets=True, then output three columns {["token", dtype=str], ["offsets_start", dtype=uint32], - >>> # ["offsets_limit", dtype=uint32]} - >>> tokenizer_op = text.UnicodeCharTokenizer(with_offsets=True) - >>> text_file_dataset = text_file_dataset.map(operations=tokenizer_op, input_columns=["text"], - ... output_columns=["token", "offsets_start", "offsets_limit"], - ... column_order=["token", "offsets_start", "offsets_limit"]) - """ - + ''' + 分词,将文本转换为Unicode字符 + ''' @check_with_offsets def __init__(self, with_offsets=False): + ''' + 初始化分词器 + :param with_offsets: 是否输出偏移量 + ''' self.with_offsets = with_offsets def parse(self): + ''' + 解析函数 + :return: 分词器 + ''' return cde.UnicodeCharTokenizerOperation(self.with_offsets) - +# 将输入的字符串切分为子词 class WordpieceTokenizer(TextTensorOperation): - """ - Tokenize the input text to subword tokens. - - Args: - vocab (Vocab): Vocabulary used to look up words. - suffix_indicator (str, optional): Prefix flags used to indicate subword suffixes. Default: '##'. - max_bytes_per_token (int, optional): The maximum length of tokenization, words exceeding this length will - not be split. Default: 100. - unknown_token (str, optional): The output for unknown words. When set to an empty string, the corresponding - unknown word will be directly returned as the output. Otherwise, the set string will be returned as the - output. Default: '[UNK]'. - with_offsets (bool, optional): Whether to return the offsets of tokens. Default: False. - - Raises: - TypeError: If `vocab` is not of type :class:`mindspore.dataset.text.Vocab`. - TypeError: If `suffix_indicator` is not of type str. - TypeError: If `max_bytes_per_token` is not of type int. - TypeError: If `unknown_token` is not of type str. - TypeError: If `with_offsets` is not of type bool. - ValueError: If `max_bytes_per_token` is negative. - - Supported Platforms: - ``CPU`` - - Examples: - >>> vocab_list = ["book", "cholera", "era", "favor", "##ite", "my", "is", "love", "dur", "##ing", "the"] - >>> vocab = text.Vocab.from_list(vocab_list) - >>> # If with_offsets=False, default output one column {["text", dtype=str]} - >>> tokenizer_op = text.WordpieceTokenizer(vocab=vocab, unknown_token='[UNK]', - ... max_bytes_per_token=100, with_offsets=False) - >>> text_file_dataset = text_file_dataset.map(operations=tokenizer_op) - >>> # If with_offsets=True, then output three columns {["token", dtype=str], ["offsets_start", dtype=uint32], - >>> # ["offsets_limit", dtype=uint32]} - >>> tokenizer_op = text.WordpieceTokenizer(vocab=vocab, unknown_token='[UNK]', - ... max_bytes_per_token=100, with_offsets=True) - >>> text_file_dataset = text_file_dataset.map(operations=tokenizer_op, input_columns=["text"], - ... output_columns=["token", "offsets_start", "offsets_limit"], - ... column_order=["token", "offsets_start", "offsets_limit"]) - """ - + ''' + 创建一个WordpieceTokenizer对象,用于将文本转换为Tensor,使用该对象可以将文本转换为Tensor,并且可以指定最大字节数,未知词表,最大字节数,未知词表,布尔值,布尔值 + ''' @check_wordpiece_tokenizer def __init__(self, vocab, suffix_indicator='##', max_bytes_per_token=100, unknown_token='[UNK]', with_offsets=False): + ''' + 初始化WordpieceTokenizer对象,用于将文本转换为Tensor,使用该对象可以将文本转换为Tensor,并且可以指定最大字节数,未知词表,最大字节数,未知词表,布尔值,布尔值 + :param vocab: 字典,包含所有的词 + :param suffix_indicator: 前缀索引,默认为## + :param max_bytes_per_token: 最大字节数,默认为100 + :param unknown_token: 未知词表,默认为[UNK] + :param with_offsets:布尔值,布尔值,默认为False + ''' self.vocab = vocab self.suffix_indicator = suffix_indicator self.max_bytes_per_token = max_bytes_per_token @@ -631,36 +411,32 @@ class WordpieceTokenizer(TextTensorOperation): self.with_offsets = with_offsets def parse(self): + ''' + 创建一个WordpieceTokenizer对象,用于将文本转换为Tensor,使用该对象可以将文本转换为Tensor,并且可以指定最大字节数,未知词表,最大字节数,未知词表,布尔值,布尔值 + :return: WordpieceTokenizer对象 + ''' return cde.WordpieceTokenizerOperation(self.vocab.c_vocab, self.suffix_indicator, self.max_bytes_per_token, self.unknown_token, self.with_offsets) - +# 使用用户自定义的分词器对输入字符串进行分词 class PythonTokenizer: - """ - Class that applies user-defined string tokenizer into input string. - - Args: - tokenizer (Callable): Python function that takes a `str` and returns a list of `str` as tokens. - - Raises: - TypeError: If `tokenizer` is not a callable Python function. - - Supported Platforms: - ``CPU`` - - Examples: - >>> def my_tokenizer(line): - ... return line.split() - >>> text_file_dataset = text_file_dataset.map(operations=text.PythonTokenizer(my_tokenizer)) - """ - + ''' + PythonTokenizer类用于将Python语言的tokenizer转换为NumPy数组 + ''' @check_python_tokenizer def __init__(self, tokenizer): + ''' + 初始化PythonTokenizer类 + :tokenizer:Python可调用对象 + ''' self.pyfunc = tokenizer self.tokenizer = np.vectorize(lambda x: np.array(tokenizer(x), dtype='U'), signature='()->(n)') self.random = False def __call__(self, in_array): + ''' + 使用PythonTokenizer类的tokenizer函数,将in_array转换为NumPy数组 + ''' if not isinstance(in_array, np.ndarray): raise TypeError("input should be a NumPy array. Got {}.".format(type(in_array))) if in_array.dtype.type is np.bytes_: @@ -673,6 +449,7 @@ class PythonTokenizer: if platform.system().lower() != 'windows': + # 如果系统不是windows,则将DE_C_INTER_NORMALIZE_FORM设置为DE_C_INTER_NORMALIZE_FORM中的值 DE_C_INTER_NORMALIZE_FORM = { NormalizeForm.NONE: cde.NormalizeForm.DE_NORMALIZE_NONE, NormalizeForm.NFC: cde.NormalizeForm.DE_NORMALIZE_NFC, @@ -681,73 +458,22 @@ if platform.system().lower() != 'windows': NormalizeForm.NFKD: cde.NormalizeForm.DE_NORMALIZE_NFKD } - + # 按照指定规则对输入的UTF-8编码字符串进行分词 class BasicTokenizer(TextTensorOperation): - """ - Tokenize the input UTF-8 encoded string by specific rules. - - Note: - `BasicTokenizer` is not supported on Windows platform yet. - - Args: - lower_case (bool, optional): Whether to perform lowercase processing on the text. If True, will fold the - text to lower case and strip accented characters. If False, will only perform normalization on the - text, with mode specified by `normalization_form`. Default: False. - keep_whitespace (bool, optional): If True, the whitespace will be kept in the output. Default: False. - normalization_form (NormalizeForm, optional): - `Unicode normalization forms `_, only valid when `lower_case` - is False, can be NormalizeForm.NONE, NormalizeForm.NFC, NormalizeForm.NFKC, NormalizeForm.NFD or - NormalizeForm.NFKD. Default: NormalizeForm.NONE. - - - NormalizeForm.NONE, no normalization. - - NormalizeForm.NFC, Canonical Decomposition, followed by Canonical Composition. - - NormalizeForm.NFKC, Compatibility Decomposition, followed by Canonical Composition. - - NormalizeForm.NFD, Canonical Decomposition. - - NormalizeForm.NFKD, Compatibility Decomposition. - - preserve_unused_token (bool, optional): Whether to preserve special tokens. If True, will not split special - tokens like '[CLS]', '[SEP]', '[UNK]', '[PAD]', '[MASK]'. Default: True. - with_offsets (bool, optional): Whether to return the offsets of tokens. Default: False. - - Raises: - TypeError: If `lower_case` is not of type bool. - TypeError: If `keep_whitespace` is not of type bool. - TypeError: If `normalization_form` is not of type :class:`mindspore.dataset.text.NormalizeForm`. - TypeError: If `preserve_unused_token` is not of type bool. - TypeError: If `with_offsets` is not of type bool. - RuntimeError: If dtype of input Tensor is not str. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.text import NormalizeForm - >>> - >>> # If with_offsets=False, default output one column {["text", dtype=str]} - >>> tokenizer_op = text.BasicTokenizer(lower_case=False, - ... keep_whitespace=False, - ... normalization_form=NormalizeForm.NONE, - ... preserve_unused_token=True, - ... with_offsets=False) - >>> text_file_dataset = text_file_dataset.map(operations=tokenizer_op) - >>> # If with_offsets=True, then output three columns {["token", dtype=str], - >>> # ["offsets_start", dtype=uint32], - >>> # ["offsets_limit", dtype=uint32]} - >>> tokenizer_op = text.BasicTokenizer(lower_case=False, - ... keep_whitespace=False, - ... normalization_form=NormalizeForm.NONE, - ... preserve_unused_token=True, - ... with_offsets=True) - >>> text_file_dataset_1 = text_file_dataset_1.map(operations=tokenizer_op, input_columns=["text"], - ... output_columns=["token", "offsets_start", - ... "offsets_limit"], - ... column_order=["token", "offsets_start", - ... "offsets_limit"]) - """ - + ''' + 分词器,用于将文本转换为词汇表,并将词汇表转换为文本表示。 + ''' @check_basic_tokenizer def __init__(self, lower_case=False, keep_whitespace=False, normalization_form=NormalizeForm.NONE, preserve_unused_token=True, with_offsets=False): + ''' + 初始化分词器,参数包括: + lower_case:是否将输入的文本转换为小写,默认为False + keep_whitespace:是否保留空白字符,默认为False + normalization_form:当前分词器的形式,默认为NormalizeForm.NONE + preserve_unused_token:是否保留未使用的词汇,默认为True + with_offsets:是否添加词汇和词汇偏移量,默认为False + ''' if not isinstance(normalization_form, NormalizeForm): raise TypeError("Wrong input type for normalization_form, should be enum of 'NormalizeForm'.") @@ -758,92 +484,32 @@ if platform.system().lower() != 'windows': self.with_offsets = with_offsets def parse(self): + ''' + 解析操作,返回一个BasicTokenizerOperation对象 + ''' return cde.BasicTokenizerOperation(self.lower_case, self.keep_whitespace, self.normalization_form, self.preserve_unused_token, self.with_offsets) - + # 使用Bert分词器对字符串进行分词 class BertTokenizer(TextTensorOperation): - """ - Tokenizer used for Bert text process. - - Note: - `BertTokenizer` is not supported on Windows platform yet. - - Args: - vocab (Vocab): Vocabulary used to look up words. - suffix_indicator (str, optional): Prefix flags used to indicate subword suffixes. Default: '##'. - max_bytes_per_token (int, optional): The maximum length of tokenization, words exceeding this length will - not be split. Default: 100. - unknown_token (str, optional): The output for unknown words. When set to an empty string, the corresponding - unknown word will be directly returned as the output. Otherwise, the set string will be returned as the - output. Default: '[UNK]'. - lower_case (bool, optional): Whether to perform lowercase processing on the text. If True, will fold the - text to lower case and strip accented characters. If False, will only perform normalization on the - text, with mode specified by `normalization_form`. Default: False. - keep_whitespace (bool, optional): If True, the whitespace will be kept in the output. Default: False. - normalization_form (NormalizeForm, optional): - `Unicode normalization forms `_, only valid when `lower_case` - is False, can be NormalizeForm.NONE, NormalizeForm.NFC, NormalizeForm.NFKC, NormalizeForm.NFD or - NormalizeForm.NFKD. Default: NormalizeForm.NONE. - - - NormalizeForm.NONE, no normalization. - - NormalizeForm.NFC, Canonical Decomposition, followed by Canonical Composition. - - NormalizeForm.NFKC, Compatibility Decomposition, followed by Canonical Composition. - - NormalizeForm.NFD, Canonical Decomposition. - - NormalizeForm.NFKD, Compatibility Decomposition. - - preserve_unused_token (bool, optional): Whether to preserve special tokens. If True, will not split special - tokens like '[CLS]', '[SEP]', '[UNK]', '[PAD]', '[MASK]'. Default: True. - with_offsets (bool, optional): Whether to return the offsets of tokens. Default: False. - - Raises: - TypeError: If `vocab` is not of type :class:`mindspore.dataset.text.Vocab`. - TypeError: If `suffix_indicator` is not of type str. - TypeError: If `max_bytes_per_token` is not of type int. - ValueError: If `max_bytes_per_token` is negative. - TypeError: If `unknown_token` is not of type str. - TypeError: If `lower_case` is not of type bool. - TypeError: If `keep_whitespace` is not of type bool. - TypeError: If `normalization_form` is not of type :class:`mindspore.dataset.text.NormalizeForm`. - TypeError: If `preserve_unused_token` is not of type bool. - TypeError: If `with_offsets` is not of type bool. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.text import NormalizeForm - >>> - >>> # If with_offsets=False, default output one column {["text", dtype=str]} - >>> vocab_list = ["床", "前", "明", "月", "光", "疑", "是", "地", "上", "霜", "举", "头", "望", "低", - ... "思", "故", "乡","繁", "體", "字", "嘿", "哈", "大", "笑", "嘻", "i", "am", "mak", - ... "make", "small", "mistake", "##s", "during", "work", "##ing", "hour", "😀", "😃", - ... "😄", "😁", "+", "/", "-", "=", "12", "28", "40", "16", " ", "I", "[CLS]", "[SEP]", - ... "[UNK]", "[PAD]", "[MASK]", "[unused1]", "[unused10]"] - >>> vocab = text.Vocab.from_list(vocab_list) - >>> tokenizer_op = text.BertTokenizer(vocab=vocab, suffix_indicator='##', max_bytes_per_token=100, - ... unknown_token='[UNK]', lower_case=False, keep_whitespace=False, - ... normalization_form=NormalizeForm.NONE, preserve_unused_token=True, - ... with_offsets=False) - >>> text_file_dataset = text_file_dataset.map(operations=tokenizer_op) - >>> # If with_offsets=True, then output three columns {["token", dtype=str], - >>> # ["offsets_start", dtype=uint32], - >>> # ["offsets_limit", dtype=uint32]} - >>> tokenizer_op = text.BertTokenizer(vocab=vocab, suffix_indicator='##', max_bytes_per_token=100, - ... unknown_token='[UNK]', lower_case=False, keep_whitespace=False, - ... normalization_form=NormalizeForm.NONE, preserve_unused_token=True, - ... with_offsets=True) - >>> text_file_dataset_1 = text_file_dataset_1.map(operations=tokenizer_op, input_columns=["text"], - ... output_columns=["token", "offsets_start", - ... "offsets_limit"], - ... column_order=["token", "offsets_start", - ... "offsets_limit"]) - """ - @check_bert_tokenizer def __init__(self, vocab, suffix_indicator='##', max_bytes_per_token=100, unknown_token='[UNK]', lower_case=False, keep_whitespace=False, normalization_form=NormalizeForm.NONE, preserve_unused_token=True, with_offsets=False): + ''' + 创建一个BertTokenizer对象,用于将vocab中的每个单词映射到一个id + + 参数: + vocab:Bert的词汇表 + suffix_indicator:用于拼接字符串的符号,默认为## + max_bytes_per_token:最大字符数,默认为100 + unknown_token:未知词,默认为[UNK] + lower_case:是否将字符串转换为小写,默认为False + keep_whitespace:是否保留空格,默认为False + normalization_form:标准化表示,默认为NONE + preserve_unused_token:是否保留未使用的单词,默认为True + with_offsets:是否添加词典的起始和结束位置,默认为False + ''' if not isinstance(normalization_form, NormalizeForm): raise TypeError("Wrong input type for normalization_form, should be enum of 'NormalizeForm'.") @@ -858,86 +524,38 @@ if platform.system().lower() != 'windows': self.with_offsets = with_offsets def parse(self): + ''' + 解析参数 + ''' return cde.BertTokenizerOperation(self.vocab.c_vocab, self.suffix_indicator, self.max_bytes_per_token, self.unknown_token, self.lower_case, self.keep_whitespace, self.normalization_form, self.preserve_unused_token, self.with_offsets) - + # 将UTF-8编码字符串中的字符规范化为小写,相比 str.lower 支持更多字符 class CaseFold(TextTensorOperation): - """ - Apply case fold operation on UTF-8 string tensor, which is aggressive that can convert more characters into - lower case. Supported normalization forms please refer to - `ICU_Normalizer2 `_ . - - Note: - CaseFold is not supported on Windows platform yet. - - Supported Platforms: - ``CPU`` - - Examples: - >>> case_op = text.CaseFold() - >>> text_file_dataset = text_file_dataset.map(operations=case_op) - """ - + ''' + 折叠文本 + ''' def parse(self): return cde.CaseFoldOperation() - + # 将Wikipedia XML格式转储过滤为仅由小写字母(a-z,从A-Z转换而来)和空格(从不连续)组成的“干净”文本 class FilterWikipediaXML(TextTensorOperation): - """ - Filter Wikipedia XML dumps to "clean" text consisting only of lowercase letters (a-z, converted from A-Z), - and spaces (never consecutive). - - Note: - FilterWikipediaXML is not supported on Windows platform yet. - - Supported Platforms: - ``CPU`` - - Examples: - >>> import mindspore.dataset.text.transforms as text - >>> - >>> replace_op = text.FilterWikipediaXML() - >>> text_file_dataset = text_file_dataset.map(operations=replace_op) - """ - + ''' + 过滤 Wikipedia XML 文件中的数据 + ''' def parse(self): return cde.FilterWikipediaXMLOperation() - + # 对UTF-8编码的字符串进行规范化处理 class NormalizeUTF8(TextTensorOperation): - """ - Apply normalize operation on UTF-8 string tensor. - - Note: - NormalizeUTF8 is not supported on Windows platform yet. - - Args: - normalize_form (NormalizeForm, optional): Valid values can be [NormalizeForm.NONE, NormalizeForm.NFC, - NormalizeForm.NFKC, NormalizeForm.NFD, NormalizeForm.NFKD] any of the four unicode - normalized forms(default=NormalizeForm.NFKC). - See http://unicode.org/reports/tr15/ for details. - - - NormalizeForm.NONE, do nothing for input string tensor. - - NormalizeForm.NFC, normalize with Normalization Form C. - - NormalizeForm.NFKC, normalize with Normalization Form KC. - - NormalizeForm.NFD, normalize with Normalization Form D. - - NormalizeForm.NFKD, normalize with Normalization Form KD. - - Raises: - TypeError: If `normalize_form` is not of type NormalizeForm. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.text import NormalizeForm - >>> normalize_op = text.NormalizeUTF8(normalize_form=NormalizeForm.NFC) - >>> text_file_dataset = text_file_dataset.map(operations=normalize_op) - """ - + ''' + 普通的UTF-8标准化,用于将文本转换为UTF-8标准 + ''' def __init__(self, normalize_form=NormalizeForm.NFKC): + ''' + normalize_form: 标准化表达式 + ''' if not isinstance(normalize_form, NormalizeForm): raise TypeError("Wrong input type for normalization_form, should be enum of 'NormalizeForm'.") @@ -945,132 +563,69 @@ if platform.system().lower() != 'windows': self.normalize_form = DE_C_INTER_NORMALIZE_FORM[normalize_form] def parse(self): + ''' + 返回一个新的NormalizeUTF8Operation对象 + ''' return cde.NormalizeUTF8Operation(self.normalize_form) - + # 根据正则表达式对UTF-8编码格式的字符串内容进行正则替换 class RegexReplace(TextTensorOperation): - """ - Replace a part of UTF-8 string tensor with given text according to regular expressions. - - See https://unicode-org.github.io/icu/userguide/strings/regexp.html for supported regex pattern. - - Note: - RegexReplace is not supported on Windows platform yet. - - Args: - pattern (str): the regex expression patterns. - replace (str): the string to replace matched element. - replace_all (bool, optional): If False, only replace first matched element; - if True, replace all matched elements (default=True). - - Raises: - TypeError: If `pattern` is not of type string. - TypeError: If `replace` is not of type string. - TypeError: If `replace_all` is not of type bool. - - Supported Platforms: - ``CPU`` - - Examples: - >>> pattern = 'Canada' - >>> replace = 'China' - >>> replace_op = text.RegexReplace(pattern, replace) - >>> text_file_dataset = text_file_dataset.map(operations=replace_op) - """ - + ''' + 正则表达式替换 + ''' @check_regex_replace def __init__(self, pattern, replace, replace_all=True): + ''' + 指定正则表达式替换 + + 参数: + pattern:正则表达式 + replace:替换内容 + replace_all:是否替换所有 + ''' self.pattern = pattern self.replace = replace self.replace_all = replace_all def parse(self): + ''' + 解析指定的正则表达式替换 + ''' return cde.RegexReplaceOperation(self.pattern, self.replace, self.replace_all) - + # 根据正则表达式对字符串进行分词 class RegexTokenizer(TextTensorOperation): - """ - Tokenize a scalar tensor of UTF-8 string by regex expression pattern. - - See https://unicode-org.github.io/icu/userguide/strings/regexp.html for supported regex pattern. - - Note: - RegexTokenizer is not supported on Windows platform yet. - - Args: - delim_pattern (str): The pattern of regex delimiters. - The original string will be split by matched elements. - keep_delim_pattern (str, optional): The string matched by 'delim_pattern' can be kept as a token - if it can be matched by 'keep_delim_pattern'. The default value is an empty str - which means that delimiters will not be kept as an output token (default=''). - with_offsets (bool, optional): Whether or not output offsets of tokens(default=False). - - Raises: - TypeError: If `delim_pattern` is not of type string. - TypeError: If `keep_delim_pattern` is not of type string. - TypeError: If `with_offsets` is not of type bool. - - Supported Platforms: - ``CPU`` - - Examples: - >>> # If with_offsets=False, default output is one column {["text", dtype=str]} - >>> delim_pattern = r"[ |,]" - >>> tokenizer_op = text.RegexTokenizer(delim_pattern, with_offsets=False) - >>> text_file_dataset = text_file_dataset.map(operations=tokenizer_op) - >>> # If with_offsets=True, then output three columns {["token", dtype=str], - >>> # ["offsets_start", dtype=uint32], - >>> # ["offsets_limit", dtype=uint32]} - >>> tokenizer_op = text.RegexTokenizer(delim_pattern, with_offsets=True) - >>> text_file_dataset_1 = text_file_dataset_1.map(operations=tokenizer_op, input_columns=["text"], - ... output_columns=["token", "offsets_start", - ... "offsets_limit"], - ... column_order=["token", "offsets_start", - ... "offsets_limit"]) - """ - + ''' + 使用正则表达式分词类型的文本操作 + ''' @check_regex_tokenizer def __init__(self, delim_pattern, keep_delim_pattern='', with_offsets=False): + ''' + 初始化正则表达式分词类型的文本操作 + + 参数: + delim_pattern:分隔符模式 + keep_delim_pattern:保留分隔符模式 + with_offsets:是否包含偏移量 + ''' self.delim_pattern = delim_pattern self.keep_delim_pattern = keep_delim_pattern self.with_offsets = with_offsets def parse(self): + ''' + 解析操作 + ''' return cde.RegexTokenizerOperation(self.delim_pattern, self.keep_delim_pattern, self.with_offsets) - + # 使用UnicodeScript分词器对UTF-8编码的字符串进行分词 class UnicodeScriptTokenizer(TextTensorOperation): - """ - Tokenize a scalar tensor of UTF-8 string based on Unicode script boundaries. - - Note: - UnicodeScriptTokenizer is not supported on Windows platform yet. - - Args: - keep_whitespace (bool, optional): Whether or not emit whitespace tokens (default=False). - with_offsets (bool, optional): Whether or not output offsets of tokens (default=False). - - Raises: - TypeError: If `keep_whitespace` is not of type bool. - TypeError: If `with_offsets` is not of type bool. - - Supported Platforms: - ``CPU`` - - Examples: - >>> # If with_offsets=False, default output one column {["text", dtype=str]} - >>> tokenizer_op = text.UnicodeScriptTokenizer(keep_whitespace=True, with_offsets=False) - >>> text_file_dataset = text_file_dataset.map(operations=tokenizer_op) - >>> # If with_offsets=True, then output three columns {["token", dtype=str], - >>> # ["offsets_start", dtype=uint32], - >>> # ["offsets_limit", dtype=uint32]} - >>> tokenizer_op = text.UnicodeScriptTokenizer(keep_whitespace=True, with_offsets=True) - >>> text_file_dataset = text_file_dataset.map(operations=tokenizer_op, input_columns=["text"], - ... output_columns=["token", "offsets_start", "offsets_limit"], - ... column_order=["token", "offsets_start", "offsets_limit"]) - - """ - + ''' + 创建一个UnicodeScriptTokenizer对象,用于将文本转换为tensor, + 参数: + keep_whitespace:是否保留空格,默认为False + with_offsets:是否包含文本的起始位置,默认为False + ''' @check_unicode_script_tokenizer def __init__(self, keep_whitespace=False, with_offsets=False): keep_whitespace = replace_none(keep_whitespace, False) @@ -1081,39 +636,20 @@ if platform.system().lower() != 'windows': def parse(self): return cde.UnicodeScriptTokenizerOperation(self.keep_whitespace, self.with_offsets) - + # 基于ICU4C定义的空白字符(' ', '\\t', '\\r', '\\n')对输入的UTF-8字符串进行分词 class WhitespaceTokenizer(TextTensorOperation): - """ - Tokenize a scalar tensor of UTF-8 string on ICU4C defined whitespaces, such as: ' ', '\\\\t', '\\\\r', '\\\\n'. - - Note: - WhitespaceTokenizer is not supported on Windows platform yet. - - Args: - with_offsets (bool, optional): Whether or not output offsets of tokens (default=False). - - Raises: - TypeError: If `with_offsets` is not of type bool. - - Supported Platforms: - ``CPU`` - - Examples: - >>> # If with_offsets=False, default output one column {["text", dtype=str]} - >>> tokenizer_op = text.WhitespaceTokenizer(with_offsets=False) - >>> text_file_dataset = text_file_dataset.map(operations=tokenizer_op) - >>> # If with_offsets=True, then output three columns {["token", dtype=str], - >>> # ["offsets_start", dtype=uint32], - >>> # ["offsets_limit", dtype=uint32]} - >>> tokenizer_op = text.WhitespaceTokenizer(with_offsets=True) - >>> text_file_dataset = text_file_dataset.map(operations=tokenizer_op, input_columns=["text"], - ... output_columns=["token", "offsets_start", "offsets_limit"], - ... column_order=["token", "offsets_start", "offsets_limit"]) - """ - + ''' + 分词,将文本按空格分割成单词,并将每个单词的开始和结束位置设置为-1。 + ''' @check_with_offsets def __init__(self, with_offsets=False): + ''' + 初始化分词器,默认为不包含开始和结束位置。 + ''' self.with_offsets = with_offsets def parse(self): + ''' + 返回分词器。 + ''' return cde.WhitespaceTokenizerOperation(self.with_offsets) diff --git a/mindspore/python/mindspore/dataset/text/utils.py b/mindspore/python/mindspore/dataset/text/utils.py index 06b0b5211cf..eb021950b4b 100644 --- a/mindspore/python/mindspore/dataset/text/utils.py +++ b/mindspore/python/mindspore/dataset/text/utils.py @@ -30,7 +30,7 @@ __all__ = [ "Vocab", "SentencePieceVocab", "to_str", "to_bytes", "Vectors", "FastText", "GloVe", "CharNGram" ] - +# 用于查找单词的Vocab对象 class Vocab: """ Vocab object that is used to save pairs of words and ids. @@ -39,6 +39,7 @@ class Vocab: """ def __init__(self): + # 初始化词汇表 self.c_vocab = None def vocab(self): @@ -52,7 +53,9 @@ class Vocab: >>> vocab = text.Vocab.from_list(["word_1", "word_2", "word_3", "word_4"]) >>> vocabory_dict = vocab.vocab() """ + # 检查c_vocab是否有效 check_vocab(self.c_vocab) + # 返回c_vocab的vocab return self.c_vocab.vocab() @check_tokens_to_ids @@ -71,11 +74,16 @@ class Vocab: >>> vocab = text.Vocab.from_list(["w1", "w2", "w3"], special_tokens=[""], special_first=True) >>> ids = vocab.tokens_to_ids(["w1", "w3"]) """ + + # 检查c_vocab中是否存在tokens check_vocab(self.c_vocab) + # 如果tokens是ndarray类型,则将其转换为列表 if isinstance(tokens, np.ndarray): tokens = tokens.tolist() + # 如果tokens是字符串类型,则将其转换为列表 if isinstance(tokens, str): tokens = [tokens] + # 返回c_vocab中tokens转换为id的结果 return self.c_vocab.tokens_to_ids(tokens) @check_ids_to_tokens @@ -94,6 +102,8 @@ class Vocab: >>> vocab = text.Vocab.from_list(["w1", "w2", "w3"], special_tokens=[""], special_first=True) >>> token = vocab.ids_to_tokens(0) """ + + # 检查c_vocab中是否存在ids check_vocab(self.c_vocab) if isinstance(ids, np.ndarray): ids = ids.tolist() @@ -141,7 +151,18 @@ class Vocab: >>> dataset = dataset.map(operations=text.Lookup(vocab, ""), input_columns=["text"]) """ + ''' + 创建一个词汇表,用于将数据集中的所有列转换为词汇表 + :param dataset: 数据集 + :param columns: 列名,默认为None + :param freq_range: 频率范围,默认为None + :param top_k: 根据频率范围设置的最大词汇数,默认为None + :param special_tokens: 特殊标记,默认为None + :param special_first: 是否将特殊标记放在第一个词汇表的第一个位置,默认为True + :return: 词汇表 + ''' vocab = cls() + # 创建一个vocab实例 vocab.c_vocab = dataset.build_vocab(columns, freq_range, top_k, special_tokens, special_first) return vocab @@ -166,9 +187,13 @@ class Vocab: """ if special_tokens is None: + # 如果special_tokens为空,则将其设置为空列表 special_tokens = [] + # 创建一个Vocab对象 vocab = Vocab() + # 将word_list中的元素添加到vocab中,并将special_tokens设置为special_first vocab.c_vocab = cde.Vocab.from_list(word_list, special_tokens, special_first) + # 返回vocab return vocab @classmethod @@ -207,9 +232,20 @@ class Vocab: >>> vocabulary = vocab.vocab() """ + ''' + 从文件中读取词汇表 + :param file_path: 包含词汇表的文件路径 + :param delimiter: 分隔符 + :param vocab_size: 从文件路径读出的词汇表大小 + :param special_tokens: 以逗号分隔的特殊标记 + :param special_first: 是否按照特殊标记的顺序排序 + :return: 词汇表 + ''' + # 如果词汇表大小为空,则把大小设置为-1 if vocab_size is None: vocab_size = -1 if special_tokens is None: + # 如果special_tokens为空,则将其设置为空列表 special_tokens = [] vocab = cls() vocab.c_vocab = cde.Vocab.from_file(file_path, delimiter, vocab_size, special_tokens, special_first) @@ -217,6 +253,7 @@ class Vocab: @classmethod @check_from_dict + # 从字典中创建词汇表 def from_dict(cls, word_dict): """ Build a vocab object from a dict. @@ -236,7 +273,7 @@ class Vocab: vocab.c_vocab = cde.Vocab.from_dict(word_dict) return vocab - +# 用于执行分词的SentencePiece对象 class SentencePieceVocab: """ SentencePiece object that is used to do words segmentation. @@ -281,10 +318,13 @@ class SentencePieceVocab: ... SentencePieceModel.UNIGRAM, {}) """ + # 创建句子段落词汇表 sentence_piece_vocab = cls() + # 使用数据集构建句子段落词汇表 sentence_piece_vocab.c_sentence_piece_vocab = dataset.build_sentencepiece_vocab(col_names, vocab_size, character_coverage, model_type, params) + # 返回句子段落词汇表 return sentence_piece_vocab @classmethod @@ -322,7 +362,17 @@ class SentencePieceVocab: ... SentencePieceModel.UNIGRAM, {}) """ + ''' + 从文件中创建SentencePieceVocab对象 + :param file_path: 文件路径 + :param vocab_size: 词汇表大小 + :param character_coverage: 字符覆盖率 + :param model_type: 模型类型 + :param params: 参数 + :return: SentencePieceVocab对象 + ''' sentence_piece_vocab = cls() + # 从文件中加载SentencePieceVocab sentence_piece_vocab.c_sentence_piece_vocab = \ cde.SentencePieceVocab.from_file(file_path, vocab_size, character_coverage, DE_C_INTER_SENTENCEPIECE_MODE[model_type], params) @@ -330,6 +380,7 @@ class SentencePieceVocab: @classmethod @check_save_model + # 将模型保存至给定路径 def save_model(cls, vocab, path, filename): """ Save model into given filepath. @@ -348,7 +399,7 @@ class SentencePieceVocab: cde.SentencePieceVocab.save_model(vocab.c_sentence_piece_vocab, path, filename) - +# 将数组转换为字符串 def to_str(array, encoding='utf8'): """ Convert NumPy array of `bytes` to array of `str` by decoding each element based on charset `encoding`. @@ -372,7 +423,7 @@ def to_str(array, encoding='utf8'): return np.char.decode(array, encoding) - +# 将数组转化成bytes def to_bytes(array, encoding='utf8'): """ Convert NumPy array of `str` to array of `bytes` by encoding each element based on charset `encoding`. @@ -397,35 +448,34 @@ def to_bytes(array, encoding='utf8'): return np.char.encode(array, encoding) - +# mindspore.dataset.text.JiebaTokenizer 的枚举值,可能的值为JiebaMode.MIX, JiebaMode.MP, JiebaMode.HMM class JiebaMode(IntEnum): """ An enumeration for JiebaTokenizer. Possible enumeration values are: JiebaMode.MIX, JiebaMode.MP, JiebaMode.HMM. - - JiebaMode.MIX: tokenize with a mix of MPSegment and HMMSegment algorithm. - - JiebaMode.MP: tokenize with MPSegment algorithm. - - JiebaMode.HMM: tokenize with Hidden Markov Model Segment algorithm. + JiebaMode.MIX - 使用最大概率法和隐马尔可夫模型算法混合进行分词。 + JiebaMode.MP - 使用最大概率法算法进行分词。 + JiebaMode.HMM - 使用隐马尔可夫模型算法进行分词。 """ MIX = 0 MP = 1 HMM = 2 - +# Unicode规范化模式 枚举类,可能的值为NormalizeForm.NONE, NormalizeForm.NFC, NormalizeForm.NFKC, NormalizeForm.NFD, NormalizeForm.NFKD class NormalizeForm(IntEnum): """ Enumeration class for `Unicode normalization forms `_ . - Possible enumeration values are: NormalizeForm.NONE, NormalizeForm.NFC, NormalizeForm.NFKC, NormalizeForm.NFD - and NormalizeForm.NFKD. + Possible enumeration values are: . - - NormalizeForm.NONE: no normalization. - - NormalizeForm.NFC: Canonical Decomposition, followed by Canonical Composition. - - NormalizeForm.NFKC: Compatibility Decomposition, followed by Canonical Composition. - - NormalizeForm.NFD: Canonical Decomposition. - - NormalizeForm.NFKD: Compatibility Decomposition. + NormalizeForm.NONE - 不进行规范化处理。 + NormalizeForm.NFC - 先以标准等价方式分解,再以标准等价方式重组。 + NormalizeForm.NFKC - 先以兼容等价方式分解,再以标准等价方式重组。 + NormalizeForm.NFD - 以标准等价方式分解。 + NormalizeForm.NFKD - 以兼容等价方式分解。 """ NONE = 0 @@ -434,7 +484,7 @@ class NormalizeForm(IntEnum): NFD = 3 NFKD = 4 - +# SentencePiece分词方法的枚举类,可能的值为SentencePieceModel.UNIGRAM, SentencePieceModel.BPE, SentencePieceModel.CHAR, SentencePieceModel.WORD class SentencePieceModel(IntEnum): """ An enumeration for SentencePieceModel. @@ -442,12 +492,10 @@ class SentencePieceModel(IntEnum): Possible enumeration values are: SentencePieceModel.UNIGRAM, SentencePieceModel.BPE, SentencePieceModel.CHAR, SentencePieceModel.WORD. - - SentencePieceModel.UNIGRAM: Unigram Language Model means the next word in the sentence is assumed to be - independent of the previous words generated by the model. - - SentencePieceModel.BPE: refers to byte pair encoding algorithm, which replaces the most frequent pair of bytes in - a sentence with a single, unused byte. - - SentencePieceModel.CHAR: refers to char based sentencePiece Model type. - - SentencePieceModel.WORD: refers to word based sentencePiece Model type. + SentencePieceModel.UNIGRAM - Unigram语言模型意味着句子中的下一个单词被假定为独立于模型生成的前一个单词。 + SentencePieceModel.BPE - 指字节对编码算法,它取代了最频繁的句子对中的字节数,其中包含一个未使用的字节。 + SentencePieceModel.CHAR - 引用基于字符的SentencePiece模型类型。 + SentencePieceModel.WORD - 引用基于单词的SentencePiece模型类型。 """ UNIGRAM = 0 @@ -463,35 +511,35 @@ DE_C_INTER_SENTENCEPIECE_MODE = { SentencePieceModel.WORD: cde.SentencePieceModel.DE_SENTENCE_PIECE_WORD } - +# mindspore.dataset.text.SentencePieceTokenizer 输出类型的枚举值,可能的值为SPieceTokenizerOutType.STRING, SPieceTokenizerOutType.INT class SPieceTokenizerOutType(IntEnum): """ An enumeration for SPieceTokenizerOutType. Possible enumeration values are: SPieceTokenizerOutType.STRING, SPieceTokenizerOutType.INT. - - SPieceTokenizerOutType.STRING: means output type of SentencePiece Tokenizer is string. - - SPieceTokenizerOutType.INT: means output type of SentencePiece Tokenizer is int. + SPieceTokenizerOutType.STRING - 表示SentencePiece分词器的输出类型为string。 + SPieceTokenizerOutType.INT - 表示SentencePiece分词器的输出类型为int。 """ STRING = 0 INT = 1 - +# mindspore.dataset.text.SentencePieceTokenizer 加载类型的枚举值,可能的值为SPieceTokenizerLoadType.FILE, SPieceTokenizerLoadType.MODEL class SPieceTokenizerLoadType(IntEnum): """ An enumeration for loading type of SentencePieceTokenizer. Possible enumeration values are: SPieceTokenizerLoadType.FILE, SPieceTokenizerLoadType.MODEL. - - SPieceTokenizerLoadType.FILE: Load SentencePiece tokenizer from a Vocab file. - - SPieceTokenizerLoadType.MODEL: Load SentencePiece tokenizer from a SentencePieceVocab object. + SPieceTokenizerLoadType.FILE - 从词典文件中加载SentencePiece分词器。 + SPieceTokenizerLoadType.MODEL - 从 mindspore.dataset.text.SentencePieceVocab 对象中加载SentencePiece分词器。 """ FILE = 0 MODEL = 1 - +# 用于将tokens映射到向量的Vectors对象 class Vectors(cde.Vectors): """ Vectors object that is used to map tokens into vectors. @@ -514,14 +562,17 @@ class Vectors(cde.Vectors): >>> vector = text.Vectors.from_file("/path/to/vectors/file", max_vectors=None) """ + ''' + 从文件中加载词向量 + :param file_path: 文件路径 + :param max_vectors: 最大词向量数量,默认为0 + :return: 词向量 + ''' max_vectors = max_vectors if max_vectors is not None else 0 return super().from_file(file_path, max_vectors) - +# 用于将tokens映射到向量的FastText对象 class FastText(cde.FastText): - """ - FastText object that is used to map tokens into vectors. - """ @classmethod @check_from_file_vectors @@ -541,14 +592,13 @@ class FastText(cde.FastText): >>> fast_text = text.FastText.from_file("/path/to/fast_text/file", max_vectors=None) """ + '''从文件中读取数据''' + max_vectors = max_vectors if max_vectors is not None else 0 return super().from_file(file_path, max_vectors) - +# 用于将tokens映射到向量的GloVe对象 class GloVe(cde.GloVe): - """ - GloVe object that is used to map tokens into vectors. - """ @classmethod @check_from_file_vectors @@ -571,11 +621,9 @@ class GloVe(cde.GloVe): max_vectors = max_vectors if max_vectors is not None else 0 return super().from_file(file_path, max_vectors) - +# CharNGram对象,用于将 tokens 映射到预训练的向量中 class CharNGram(cde.CharNGram): - """ - CharNGram object that is used to map tokens into pre-trained vectors. - """ + @classmethod @check_from_file_vectors diff --git a/mindspore/python/mindspore/dataset/transforms/__init__.py b/mindspore/python/mindspore/dataset/transforms/__init__.py index 8e119b4dfd0..a3ee15e63f4 100644 --- a/mindspore/python/mindspore/dataset/transforms/__init__.py +++ b/mindspore/python/mindspore/dataset/transforms/__init__.py @@ -11,6 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +# 这段代码是Python中的一个模块,名为mindspore.dataset.transforms。它提供了两个部分:c_transforms和py_transforms。c_transforms是一个高性能的数据增强模块,它使用C++进行开发。py_transforms提供了Python和NumPy实现的可选实现 """ This module is to support common augmentations. C_transforms is a high performance augmentation module which is developed by C++. Py_transforms provides an optional @@ -31,6 +33,9 @@ Descriptions of common data processing terms are as follows: - TensorOperation, the base class of all data processing operations implemented in C++. - PyTensorOperation, the base class of all data processing operations implemented in Python. """ +# 包含与图像处理相关的函数 from .. import vision +# 包含C++实现的数据增强操作 from . import c_transforms +# 包含Python和NumPy实现的可选数据增强操作 from . import py_transforms diff --git a/mindspore/python/mindspore/dataset/transforms/py_transforms.py b/mindspore/python/mindspore/dataset/transforms/py_transforms.py index 85f42e6e8b7..5df78e2e0d0 100644 --- a/mindspore/python/mindspore/dataset/transforms/py_transforms.py +++ b/mindspore/python/mindspore/dataset/transforms/py_transforms.py @@ -87,297 +87,104 @@ class PyTensorOperation: class OneHotOp(PyTensorOperation): - """ - Apply one hot encoding transformation to the input label, make label be more smoothing and continuous. - - Args: - num_classes (int): Number of classes of objects in dataset. - It should be larger than the largest label number in the dataset. - smoothing_rate (float, optional): Adjustable hyperparameter for label smoothing level. - (Default=0.0 means no smoothing is applied.) - - Raises: - TypeError: `num_classes` is not of type int. - TypeError: `smoothing_rate` is not of type float. - ValueError: `smoothing_rate` is not in range [0.0, 1.0]. - - Supported Platforms: - ``CPU`` - - Examples: - >>> # Assume that dataset has 10 classes, thus the label ranges from 0 to 9 - >>> transforms_list = [py_transforms.OneHotOp(num_classes=10, smoothing_rate=0.1)] - >>> transform = py_transforms.Compose(transforms_list) - >>> mnist_dataset = mnist_dataset.map(input_columns=["label"], operations=transform) - """ - + # 定义OneHotOp类,参数num_classes, smoothing_rate @check_one_hot_op def __init__(self, num_classes, smoothing_rate=0.0): + # 初始化num_classes和smoothing_rate self.num_classes = num_classes self.smoothing_rate = smoothing_rate self.random = False def __call__(self, label): - """ - Call method. - - Args: - label (numpy.ndarray): label to be applied label smoothing. - - Returns: - label (numpy.ndarray), label after being Smoothed. - """ + # 返回one_hot_encoding函数的结果 return util.one_hot_encoding(label, self.num_classes, self.smoothing_rate) - +# 将多个数据增强操作组合使用 class Compose(PyTensorOperation): - """ - Compose a list of transforms. - - .. Note:: - Compose takes a list of transformations either provided in py_transforms or from user-defined implementation; - each can be an initialized transformation class or a lambda function, as long as the output from the last - transformation is a single tensor of type numpy.ndarray. See below for an example of how to use Compose - with py_transforms classes and check out FiveCrop or TenCrop for the use of them in conjunction with lambda - functions. - - Args: - transforms (list): List of transformations to be applied. - - Raises: - TypeError: If `transforms` is not of type list. - ValueError: If `transforms` is empty. - TypeError: If transformations in `transforms` are not Python callable objects. - - Supported Platforms: - ``CPU`` - - Examples: - >>> image_folder_dataset_dir = "/path/to/image_folder_dataset_directory" - >>> # create a dataset that reads all files in dataset_dir with 8 threads - >>> image_folder_dataset = ds.ImageFolderDataset(image_folder_dataset_dir, num_parallel_workers=8) - >>> # create a list of transformations to be applied to the image data - >>> transform = py_transforms.Compose([py_vision.Decode(), - ... py_vision.RandomHorizontalFlip(0.5), - ... py_vision.ToTensor(), - ... py_vision.Normalize((0.491, 0.482, 0.447), (0.247, 0.243, 0.262)), - ... py_vision.RandomErasing()]) - >>> # apply the transform to the dataset through dataset.map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transform, input_columns=["image"]) - >>> - >>> # Compose is also be invoked implicitly, by just passing in a list of ops - >>> # the above example then becomes: - >>> transforms_list = [py_vision.Decode(), - ... py_vision.RandomHorizontalFlip(0.5), - ... py_vision.ToTensor(), - ... py_vision.Normalize((0.491, 0.482, 0.447), (0.247, 0.243, 0.262)), - ... py_vision.RandomErasing()] - >>> - >>> # apply the transform to the dataset through dataset.map() - >>> image_folder_dataset_1 = image_folder_dataset_1.map(operations=transforms_list, input_columns=["image"]) - >>> - >>> # Certain C++ and Python ops can be combined, but not all of them - >>> # An example of combined operations - >>> arr = [0, 1] - >>> dataset = ds.NumpySlicesDataset(arr, column_names=["cols"], shuffle=False) - >>> transformed_list = [py_transforms.OneHotOp(2), c_transforms.Mask(c_transforms.Relational.EQ, 1)] - >>> dataset = dataset.map(operations=transformed_list, input_columns=["cols"]) - >>> - >>> # Here is an example of mixing vision ops - >>> import numpy as np - >>> op_list=[c_vision.Decode(), - ... c_vision.Resize((224, 244)), - ... py_vision.ToPIL(), - ... np.array, # need to convert PIL image to a NumPy array to pass it to C++ operation - ... c_vision.Resize((24, 24))] - >>> image_folder_dataset = image_folder_dataset.map(operations=op_list, input_columns=["image"]) - """ - @check_compose_list def __init__(self, transforms): + ''' + 参数: + transforms:PyTensorOperation对象列表 + ''' self.transforms = transforms if all(hasattr(transform, "random") and not transform.random for transform in self.transforms): self.random = False @check_compose_call def __call__(self, *args): - """ - Call method. - - Returns: - lambda function, Lambda function that takes in an args to apply transformations on. - """ + # 返回:组合后的PyTensorOperation对象 return util.compose(self.transforms, *args) - + @staticmethod def reduce(operations): - """ - Wraps adjacent Python operations in a Compose to allow mixing of Python and C++ operations. - - Args: - operations (list): list of tensor operations. - - Returns: - list, the reduced list of operations. - """ # import nn and ops locally for type check from mindspore import nn, ops + # 检查输入的运算是否包含像mindspore.nn或mindspore.ops的运算 for item in operations: if isinstance(item, (nn.Cell, ops.Primitive)): raise ValueError("Input operations should not contain network computing operator like in " "mindspore.nn or mindspore.ops, got operation:", str(item)) + # 检查输入的运算是否为空 if len(operations) == 1: + # 检查输入的运算是否为张量运算,如果是返回运算 if str(operations).find("c_transform") >= 0 or isinstance(operations[0], TensorOperation): return operations + # 如果不是,返回util.FuncWrapper的运算结果 return [util.FuncWrapper(operations[0])] + # 重新设置数值 new_ops, start_ind, end_ind = [], 0, 0 + # 循环遍历运算 for i, op in enumerate(operations): + # 检查当前运算为 Compose if str(op).find("c_transform") >= 0: - # reset counts - if start_ind != end_ind: + # 为new_ops添加新的数值 + if start_ind!= end_ind: new_ops.append(Compose(operations[start_ind:end_ind])) + # 添加当前运算符 new_ops.append(op) start_ind, end_ind = i + 1, i + 1 + # 检查当前运算为 Compose else: + # 让end_ind 加一 end_ind += 1 - # do additional check in case the last operation is a Python operation - if start_ind != end_ind: + # 额外检查以防最后一个运算为Python运算 + if start_ind!= end_ind: new_ops.append(Compose(operations[start_ind:end_ind])) return new_ops - +# 指定一组数据增强处理及其被应用的概率,在运算时按概率随机应用其中的增强处理 class RandomApply(PyTensorOperation): - """ - Randomly perform a series of transforms with a given probability. - - Args: - transforms (list): List of transformations to apply. - prob (float, optional): The probability to apply the transformation list (default=0.5). - - Raises: - TypeError: If `transforms` is not of type list. - ValueError: If `transforms` is empty. - TypeError: If elements of `transforms` are neither Python callable objects nor data - processing operations in py_transforms. - TypeError: If `prob` is not of type float. - ValueError: If `prob` is not in range [0.0, 1.0]. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> transforms_list = [py_vision.RandomHorizontalFlip(0.5), - ... py_vision.Normalize((0.491, 0.482, 0.447), (0.247, 0.243, 0.262)), - ... py_vision.RandomErasing()] - >>> transforms = Compose([py_vision.Decode(), - ... py_transforms.RandomApply(transforms_list, prob=0.6), - ... py_vision.ToTensor()]) - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms, input_columns=["image"]) - """ - + # 定义RandomApply类,参数transforms和prob:概率 @check_random_apply def __init__(self, transforms, prob=0.5): self.prob = prob self.transforms = transforms def __call__(self, img): - """ - Call method. - - Args: - img (PIL image): Image to be randomly applied a list transformations. - - Returns: - img (PIL image), Transformed image. - """ + # 返回随机应用于img的结果 return util.random_apply(img, self.transforms, self.prob) - +# 在一组数据增强中随机选择部分增强处理进行应用 class RandomChoice(PyTensorOperation): - """ - Randomly select one transform from a series of transforms and applies that on the image. - - Args: - transforms (list): List of transformations to be chosen from to apply. - - Raises: - TypeError: If `transforms` is not of type list. - TypeError: If elements of `transforms` are neither Python callable objects nor data - processing operations in py_transforms. - ValueError: If `transforms` is empty. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> transforms_list = [py_vision.RandomHorizontalFlip(0.5), - ... py_vision.Normalize((0.491, 0.482, 0.447), (0.247, 0.243, 0.262)), - ... py_vision.RandomErasing()] - >>> transforms = Compose([py_vision.Decode(), - ... py_transforms.RandomChoice(transforms_list), - ... py_vision.ToTensor()]) - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms, input_columns=["image"]) - """ - + # 定义RandomChoice类,继承PyTensorOperation类 @check_transforms_list def __init__(self, transforms): + # 初始化transforms参数 self.transforms = transforms def __call__(self, img): - """ - Call method. - - Args: - img (PIL image): Image to be applied transformation. - - Returns: - img (PIL image), Transformed image. - """ + # 调用PyTensorOperation类的__call__方法,传入img参数 return util.random_choice(img, self.transforms) - +# 给一个数据增强的列表,随机打乱数据增强处理的顺序 class RandomOrder(PyTensorOperation): - """ - Perform a series of transforms to the input PIL image in a random order. - - Args: - transforms (list): List of the transformations to apply. - - Raises: - TypeError: If `transforms` is not of type list. - TypeError: If elements of `transforms` are neither Python callable objects nor data - processing operations in py_transforms. - ValueError: If `transforms` is empty. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> transforms_list = [py_vision.RandomHorizontalFlip(0.5), - ... py_vision.Normalize((0.491, 0.482, 0.447), (0.247, 0.243, 0.262)), - ... py_vision.RandomErasing()] - >>> transforms = Compose([py_vision.Decode(), - ... py_transforms.RandomOrder(transforms_list), - ... py_vision.ToTensor()]) - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms, input_columns=["image"]) - """ - + # 定义一个RandomOrder类,用于接收一个可调用的transforms列表 @check_transforms_list def __init__(self, transforms): self.transforms = transforms def __call__(self, img): - """ - Call method. - - Args: - img (PIL image): Image to apply transformations in a random order. - - Returns: - img (PIL image), Transformed image. - """ + # 调用PyTensorOperation的__call__方法,传入img参数,并将返回值赋值给img return util.random_order(img, self.transforms) diff --git a/mindspore/python/mindspore/dataset/transforms/py_transforms_util.py b/mindspore/python/mindspore/dataset/transforms/py_transforms_util.py index 25c86d4aeff..67aceb26f42 100644 --- a/mindspore/python/mindspore/dataset/transforms/py_transforms_util.py +++ b/mindspore/python/mindspore/dataset/transforms/py_transforms_util.py @@ -24,7 +24,11 @@ from ..core.py_util_helpers import is_numpy, ExceptionHandler def all_numpy(args): - """ for multi-input lambdas""" + ''' + 判断传入的参数是否都是numpy的 + :param args: 参数 + :return: 是否都是numpy的 + ''' if isinstance(args, tuple): for value in args: if not is_numpy(value): @@ -34,22 +38,21 @@ def all_numpy(args): def compose(transforms, *args): - """ - Compose a list of transforms and apply on the image. - - Args: - img (numpy.ndarray): An image in NumPy ndarray. - transforms (list): A list of transform Class objects to be composed. - - Returns: - img (numpy.ndarray), An augmented image in NumPy ndarray. - """ + ''' + 将多个参数组合在一起 + :param transforms: 可变参数列表 + :param args: 可变参数列表 + :return: 参数列表 + ''' for transform in transforms: try: + # 使用transform函数对args参数进行处理 args = transform(*args) except Exception: + # 如果发生异常,则抛出异常处理结果 result = ExceptionHandler(where="in map(or batch) worker and execute Python function") result.reraise() + # 如果args参数不是元组,则将其转换为元组 args = (args,) if not isinstance(args, tuple) else args if all_numpy(args): @@ -58,50 +61,51 @@ def compose(transforms, *args): def one_hot_encoding(label, num_classes, epsilon): - """ - Apply label smoothing transformation to the input label, and make label be more smoothing and continuous. - - Args: - label (numpy.ndarray): label to be applied label smoothing. - num_classes (int): Num class of object in dataset, value should over 0. - epsilon (float): The adjustable Hyper parameter. Default is 0.0. - - Returns: - img (numpy.ndarray), label after being one hot encoded and done label smoothed. - - Examples: - >>> # assume num_classes = 5 - >>> # 1) input np.array(3) output [0, 0, 0, 1, 0] - >>> # 2) input np.array([4, 2, 0]) output [[0, 0, 0, 0, 1], [0, 0, 1, 0, 0], [1, 0, 0, 0, 0]] - >>> # 3) input np.array([[4], [2], [0]]) output [[[0, 0, 0, 0, 1]], [[0, 0, 1, 0, 0][, [[1, 0, 0, 0, 0]]] - """ + ''' + 将label转换为one-hot编码 + :param label: 数据 + :param num_classes: 类别数 + :param epsilon: 正则化阈值 + :return: one-hot编码 + ''' + # 如果numpy不是()或(1,)或(n, 1),抛出数值错误异常 if isinstance(label, np.ndarray): # the numpy should be () or (1, ) or shape: (n, 1) if label.dtype not in [np.int8, np.int16, np.int32, np.int64, np.uint8, np.uint16, np.uint32, np.uint64]: - raise ValueError('the input numpy type should be int, but the input is: ' + str(label.dtype)) - + raise ValueError('the input numpy type should be int, but the input is:'+ str(label.dtype)) + + # 如果label的维度为0,则初始化一个num_classes维的one_hot_label if label.ndim == 0: + # 如果label大于num_classes,抛出数值错误异常 if label >= num_classes: raise ValueError('the num_classes is smaller than the category number.') - + # 初始化一个num_classes维的one_hot_label one_hot_label = np.zeros((num_classes), dtype=int) + # 将label中的每一个元素都设置为1 one_hot_label[label] = 1 else: + # 将label转换为num_classes维 label_flatten = label.flatten() + # 对label_flatten进行遍历,如果元素大于num_classes,则抛出异常 for item in label_flatten: if item >= num_classes: raise ValueError('the num_classes:' + str(num_classes) + ' is smaller than the category number:' + str(item)) + # 计算label_flatten的长度 num_elements = label_flatten.size + # 初始化一个num_elements*num_classes维的one_hot_label one_hot_label = np.zeros((num_elements, num_classes), dtype=int) + # 对label_flatten进行遍历,将每一个元素都设置为1 for index in range(num_elements): one_hot_label[index][label_flatten[index]] = 1 + # 将label的形状转换为num_classes维 new_shape = [] for dim in label.shape: new_shape.append(dim) new_shape.append(num_classes) + # 将one_hot_label转换为num_classes维 one_hot_label = one_hot_label.reshape(new_shape) else: raise ValueError('the input is invalid, it should be numpy.ndarray.') @@ -110,88 +114,80 @@ def one_hot_encoding(label, num_classes, epsilon): def random_order(img, transforms): - """ - Applies a list of transforms in a random order. - - Args: - img: Image to be applied transformations in a random order. - transforms (list): List of the transformations to be applied. - - Returns: - img, Transformed image. - """ + ''' + 随机打乱转换组 + + 参数: + img:图片 + transforms:转换组 + + 返回: + 转换后的图片 + ''' + # 随机打乱转换组 random.shuffle(transforms) + # 遍历转换组,将图片转换为参数 for transform in transforms: img = transform(img) + # 返回转换后的图片 return img - def random_apply(img, transforms, prob): - """ - Apply a list of transformation, randomly with a given probability. - - Args: - img: Image to be randomly applied a list transformations. - transforms (list): List of transformations to be applied. - prob (float): The probability to apply the transformation list. - - Returns: - img, Transformed image. - """ + # 如果概率小于随机数,则直接返回图片 if prob < random.random(): return img + # 遍历可选参数 for transform in transforms: + # 调用可选参数的transform函数 img = transform(img) + # 返回调用可选参数的transform函数的结果 return img - def random_choice(img, transforms): - """ - Random selects one transform from a list of transforms and applies that on the image. - - Args: - img: Image to be applied transformation. - transforms (list): List of transformations to be chosen from to apply. - - Returns: - img, Transformed image. - """ + ''' + 随机选择一个转换函数 + :param img: 图像 + :param transforms: 转换函数列表 + :return: 返回转换后的图像 + ''' return random.choice(transforms)(img) class FuncWrapper: - """ - Wrap function with try except logic, mainly for warping python function. - - Args: - transform: Callable python function. - - Returns: - result, data after apply transformation. - """ + # 初始化函数包装器 def __init__(self, transform): + # 如果输入操作不是可调用的python函数,抛出异常 if not callable(transform): raise ValueError("Input operations should be callable python function, but got: " + str(transform)) + # 将输入操作赋值给self.transform self.transform = transform + # 尝试获取self.transform的random属性,如果没有,将self.random设置为True try: if hasattr(self.transform, "random") and not self.transform.random: self.random = False except KeyError: self.random = True + # 实现__call__方法 def __call__(self, *args): + # 定义一个空的结果变量 result = None + # 尝试调用self.transform函数,如果出现异常,将异常赋值给result try: result = self.transform(*args) except Exception: result = ExceptionHandler(where="in map(or batch) worker and execute python function") result.reraise() + # 返回result return result + # 实现to_json方法 def to_json(self): + # 如果self.transform是FunctionType类型,则将其转换为json格式 if isinstance(self.transform, FunctionType): json_obj = {} json_obj["tensor_op_name"] = self.transform.__name__ json_obj["python_module"] = self.__class__.__module__ return json.dumps(json_obj) + # 否则,返回self.transform的to_json方法 return self.transform.to_json() diff --git a/mindspore/python/mindspore/dataset/utils/__init__.py b/mindspore/python/mindspore/dataset/utils/__init__.py index 69a9477e500..fb11d929f4e 100644 --- a/mindspore/python/mindspore/dataset/utils/__init__.py +++ b/mindspore/python/mindspore/dataset/utils/__init__.py @@ -12,7 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== + +# mindspore.dataset.util。它提供了imshow_det_bbox函数,用于在图像上显示检测结果 """init file for MindData utils""" from .browse_dataset import imshow_det_bbox +# __all__是一个包含模块中可导出对象的列表。在这里,只有imshow_det_bbox函数被导出 __all__ = ["imshow_det_bbox"] diff --git a/mindspore/python/mindspore/dataset/utils/browse_dataset.py b/mindspore/python/mindspore/dataset/utils/browse_dataset.py index ed0601537c9..4b94ed6be25 100644 --- a/mindspore/python/mindspore/dataset/utils/browse_dataset.py +++ b/mindspore/python/mindspore/dataset/utils/browse_dataset.py @@ -96,39 +96,63 @@ def imshow_det_bbox(image, bboxes, labels, segm=None, class_names=None, score_th """ try: + # 导入cv2模块 cv2 = importlib.import_module("cv2") except ModuleNotFoundError: + # 如果导入失败,抛出异常 raise ImportError("Importing cv2 failed, try to install it by running `pip install opencv-python`.") # validation assert isinstance(image, np.ndarray) and image.ndim == 3 and (image.shape[0] == 3 or image.shape[2] == 3), \ "image must be a ndarray in (H, W, C) or (C, H, W) format." if bboxes is not None: + # 断言函数,在条件不满足时直接返回错误 + # 判断bboxes是否为ndarray类型,且ndim等于2,且shape[1]等于4或者shape[1]等于5 assert isinstance(bboxes, np.ndarray) and bboxes.ndim == 2 and (bboxes.shape[1] == 4 or bboxes.shape[1] == 5), \ "bboxes must be a ndarray in (N, 4) or (N, 5) format." + # 判断labels是否为ndarray类型,且ndim等于2,且shape[1]等于1,且shape[0]等于bboxes的行数 assert isinstance(labels, np.ndarray) and labels.ndim == 2 and labels.shape[1] == 1 and \ labels.shape[0] == bboxes.shape[ 0], "labels must be a ndarray in (N, 1) format and has same N with bboxes." if segm is not None: + # 断言函数,在条件不满足时直接返回错误 + # 判断segm是否为ndarray,且ndim等于3 assert isinstance(segm, np.ndarray) and segm.ndim == 3, "segm must be a ndarray in (M, H, W) format." + # 获取图像的高度和宽度 H, W = (image.shape[0], image.shape[1]) if image.shape[2] == 3 else (image.shape[1], image.shape[2]) + # 判断segm的高度和宽度是否相等 assert H == segm.shape[1] and W == segm.shape[2], "segm must has same height and width with image." + # 判断bboxes是否为None if bboxes is not None: + # 判断bboxes的长度是否小于segm的长度 assert bboxes.shape[0] <= segm.shape[0], "number of segm masks must not be less than the number of bboxes." + + # 断言,判断类名是否为tuple, list或dict格式 assert isinstance(class_names, (tuple, list, dict)), "class_names must be a list, tuple or dict." + # 断言,判断bbox线的颜色是否为(BGR)格式 assert isinstance(bbox_color, tuple) and len(bbox_color) == 3, \ "bbox_color must be a three tuple, formatted (B, G, R)." + # 断言,判断文本的颜色是否为(BGR)格式 assert isinstance(text_color, tuple) and len(text_color) == 3, \ "text_color must be a three tuple, formatted (B, G, R)." + # 断言,判断蒙层颜色是否为(BGR)格式 assert isinstance(mask_color, tuple) and len(mask_color) == 3, \ "mask_color must be a three tuple, formatted (B, G, R)." + # 断言,判断线宽度是否为整型 assert isinstance(thickness, int), "thickness must be an int." + # 断言,判断线宽度是否大于等于0 assert thickness >= 0, "thickness must be larger than or equal to zero." + # 断言,判断字体大小是否为整型或浮点型 assert isinstance(font_size, (int, float)), "font_size must be an int or float." + # 断言,判断字体大小是否大于等于0 assert font_size >= 0, "font_size must be larger than or equal to zero." + # 断言,判断展示图片是否为bool类型 assert isinstance(show, bool), "show must be a bool." + # 断言,判断窗口名是否为字符串 assert isinstance(win_name, str), "win_name must be a str." + # 断言,判断等待时间是否为整型 assert isinstance(wait_time, int), "wait_time must be an int." + # 断言,判断等待时间是否大于等于0 assert wait_time >= 0, "wait_time must be larger than or equal to zero." if out_file is not None: assert isinstance(out_file, str), "out_file must be a str." @@ -140,43 +164,70 @@ def imshow_det_bbox(image, bboxes, labels, segm=None, class_names=None, score_th # image if image.shape[0] == 3: + # 将图片转换为RGB格式 image = image.transpose((1, 2, 0)) + # 将图片转换为BGR格式 draw_image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) if bboxes is not None: + # 获取bbox数量 bbox_num = bboxes.shape[0] + # 遍历bbox数组 for i in range(bbox_num): + # 获取bbox draw_bbox = bboxes[i] + # 判断bbox数组是否有4个元素 if len(draw_bbox) > 4: + # 判断bbox的置信度是否小于阈值 if draw_bbox[4] < score_threshold: continue - # bbox + # bbox,给图片边框赋值 x1, y1 = int(draw_bbox[0]), int(draw_bbox[1]) x2, y2 = int(draw_bbox[0] + draw_bbox[2]), int(draw_bbox[1] + draw_bbox[3]) + # 画框 cv2.rectangle(draw_image, (x1, y1), (x2, y2), bbox_color, thickness) # label try: draw_label = str(class_names[labels[i][0]]) if class_names is not None else f'class {labels[i][0]}' except (IndexError, KeyError): draw_label = f'class {labels[i][0]}' + # 判断bbox数组是否有4个元素 if len(draw_bbox) > 4: + # 判断bbox的置信度是否小于阈值 draw_label += f'|{draw_bbox[-1]:.02f}' + # 画标签 cv2.putText(draw_image, draw_label, (x1, y2), cv2.FONT_HERSHEY_SIMPLEX, font_size, text_color, thickness) + # 判断segm数组是否有值 if segm is not None: + # 获取segm数组 mask = segm[i].astype(bool) + # 画segm draw_image[mask] = draw_image[mask] * 0.5 + np.array(mask_color) * 0.5 else: + # 判断segm数组是否有值 if segm is not None: + # 获取segm数组数量 segm_num = segm.shape[0] + # 遍历segm数组 for i in range(segm_num): + # 获取segm数组 mask = segm[i].astype(bool) + # 画segm draw_image[mask] = draw_image[mask] * 0.5 + np.array(mask_color) * 0.5 + # 是否显示 if show: + # 显示图片 cv2.imshow(win_name, draw_image) + # 等待按键 if cv2.waitKey(wait_time) == 27: sys.exit() + # 是否保存 if out_file: + # 输出图片文件名 logger.info("Saving image file with name: " + out_file + "...") + # 保存图片 cv2.imwrite(out_file, draw_image) + # 更改文件权限 os.chmod(out_file, 0o600) + # 返回图片 return draw_image diff --git a/mindspore/python/mindspore/dataset/vision/__init__.py b/mindspore/python/mindspore/dataset/vision/__init__.py index c604ad6f9de..c1232ae6840 100644 --- a/mindspore/python/mindspore/dataset/vision/__init__.py +++ b/mindspore/python/mindspore/dataset/vision/__init__.py @@ -11,6 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +# mindspore.dataset.vision。它提供了两个部分:c_transforms和py_transforms。c_transforms是一个高性能的图像增强模块,它使用C++的OpenCV进行开发。py_transforms提供了更多种类的图像增强,它们使用Python的PIL进行开发 """ This module is to support vision augmentations. It includes two parts: c_transforms and py_transforms. C_transforms is a high performance @@ -31,6 +33,9 @@ Descriptions of common data processing terms are as follows: - PyTensorOperation, the base class of all data processing operations implemented in Python. - ImageTensorOperation, the base class of all image processing operations. It is a derived class of TensorOperation. """ +# 包含C++实现的图像增强操作 from . import c_transforms +# 包含Python实现的图像增强操作 from . import py_transforms +# 包含一些通用方法,用于图像处理 from .utils import Inter, Border, ConvertMode, ImageBatchFormat, SliceMode, AutoAugmentPolicy diff --git a/mindspore/python/mindspore/dataset/vision/c_transforms.py b/mindspore/python/mindspore/dataset/vision/c_transforms.py index f26bab580e8..669d1eda792 100644 --- a/mindspore/python/mindspore/dataset/vision/c_transforms.py +++ b/mindspore/python/mindspore/dataset/vision/c_transforms.py @@ -47,39 +47,34 @@ import numpy as np from PIL import Image import mindspore._c_dataengine as cde -from .utils import Inter, Border, ImageBatchFormat, ConvertMode, SliceMode, AutoAugmentPolicy -from .validators import check_prob, check_crop, check_center_crop, check_resize_interpolation, \ +from .utils import Inter, Border, ImageBatchFormat +from .validators import check_prob, check_crop, check_resize_interpolation, check_random_resize_crop, \ check_mix_up_batch_c, check_normalize_c, check_normalizepad_c, check_random_crop, check_random_color_adjust, \ - check_random_rotation, check_range, check_resize, check_rescale, check_pad, check_cutout, check_alpha, \ - check_uniform_augment_cpp, check_convert_color, check_random_resize_crop, check_random_auto_contrast, \ - check_random_adjust_sharpness, check_auto_augment, \ + check_random_rotation, check_range, check_resize, check_rescale, check_pad, check_cutout, \ + check_uniform_augment_cpp, \ check_bounding_box_augment_cpp, check_random_select_subpolicy_op, check_auto_contrast, check_random_affine, \ check_random_solarize, check_soft_dvpp_decode_random_crop_resize_jpeg, check_positive_degrees, FLOAT_MAX_INTEGER, \ - check_cut_mix_batch_c, check_posterize, check_gaussian_blur, check_rotate, check_slice_patches, check_adjust_gamma + check_cut_mix_batch_c, check_posterize from ..transforms.c_transforms import TensorOperation class ImageTensorOperation(TensorOperation): - """ - Base class of Image Tensor Ops - """ - + # 定义__call__方法,用于调用父类的__call__方法 def __call__(self, *input_tensor_list): + # 遍历input_tensor_list for tensor in input_tensor_list: + # 如果input_tensor_list中的元素不是NumPy或PIL图片,则抛出TypeError异常 if not isinstance(tensor, (np.ndarray, Image.Image)): - raise TypeError( - "Input should be NumPy or PIL image, got {}.".format(type(tensor))) + raise TypeError("Input should be NumPy or PIL image, got {}.".format(type(tensor))) + # 调用父类的__call__方法 return super().__call__(*input_tensor_list) + # 定义parse方法,用于解析输入 def parse(self): - raise NotImplementedError( - "ImageTensorOperation has to implement parse() method.") + # 抛出NotImplementedError异常 + raise NotImplementedError("ImageTensorOperation has to implement parse() method.") -DE_C_AUTO_AUGMENT_POLICY = {AutoAugmentPolicy.IMAGENET: cde.AutoAugmentPolicy.DE_AUTO_AUGMENT_POLICY_IMAGENET, - AutoAugmentPolicy.CIFAR10: cde.AutoAugmentPolicy.DE_AUTO_AUGMENT_POLICY_CIFAR10, - AutoAugmentPolicy.SVHN: cde.AutoAugmentPolicy.DE_AUTO_AUGMENT_POLICY_SVHN} - DE_C_BORDER_TYPE = {Border.CONSTANT: cde.BorderType.DE_BORDER_CONSTANT, Border.EDGE: cde.BorderType.DE_BORDER_EDGE, Border.REFLECT: cde.BorderType.DE_BORDER_REFLECT, @@ -94,181 +89,39 @@ DE_C_INTER_MODE = {Inter.NEAREST: cde.InterpolationMode.DE_INTER_NEAREST_NEIGHBO Inter.AREA: cde.InterpolationMode.DE_INTER_AREA, Inter.PILCUBIC: cde.InterpolationMode.DE_INTER_PILCUBIC} -DE_C_SLICE_MODE = {SliceMode.PAD: cde.SliceMode.DE_SLICE_PAD, - SliceMode.DROP: cde.SliceMode.DE_SLICE_DROP} - -DE_C_CONVERT_COLOR_MODE = {ConvertMode.COLOR_BGR2BGRA: cde.ConvertMode.DE_COLOR_BGR2BGRA, - ConvertMode.COLOR_RGB2RGBA: cde.ConvertMode.DE_COLOR_RGB2RGBA, - ConvertMode.COLOR_BGRA2BGR: cde.ConvertMode.DE_COLOR_BGRA2BGR, - ConvertMode.COLOR_RGBA2RGB: cde.ConvertMode.DE_COLOR_RGBA2RGB, - ConvertMode.COLOR_BGR2RGBA: cde.ConvertMode.DE_COLOR_BGR2RGBA, - ConvertMode.COLOR_RGB2BGRA: cde.ConvertMode.DE_COLOR_RGB2BGRA, - ConvertMode.COLOR_RGBA2BGR: cde.ConvertMode.DE_COLOR_RGBA2BGR, - ConvertMode.COLOR_BGRA2RGB: cde.ConvertMode.DE_COLOR_BGRA2RGB, - ConvertMode.COLOR_BGR2RGB: cde.ConvertMode.DE_COLOR_BGR2RGB, - ConvertMode.COLOR_RGB2BGR: cde.ConvertMode.DE_COLOR_RGB2BGR, - ConvertMode.COLOR_BGRA2RGBA: cde.ConvertMode.DE_COLOR_BGRA2RGBA, - ConvertMode.COLOR_RGBA2BGRA: cde.ConvertMode.DE_COLOR_RGBA2BGRA, - ConvertMode.COLOR_BGR2GRAY: cde.ConvertMode.DE_COLOR_BGR2GRAY, - ConvertMode.COLOR_RGB2GRAY: cde.ConvertMode.DE_COLOR_RGB2GRAY, - ConvertMode.COLOR_GRAY2BGR: cde.ConvertMode.DE_COLOR_GRAY2BGR, - ConvertMode.COLOR_GRAY2RGB: cde.ConvertMode.DE_COLOR_GRAY2RGB, - ConvertMode.COLOR_GRAY2BGRA: cde.ConvertMode.DE_COLOR_GRAY2BGRA, - ConvertMode.COLOR_GRAY2RGBA: cde.ConvertMode.DE_COLOR_GRAY2RGBA, - ConvertMode.COLOR_BGRA2GRAY: cde.ConvertMode.DE_COLOR_BGRA2GRAY, - ConvertMode.COLOR_RGBA2GRAY: cde.ConvertMode.DE_COLOR_RGBA2GRAY, - } - def parse_padding(padding): - """ Parses and prepares the padding tuple""" - + ''' + 解析padding + :param padding: padding的格式为[left, top, right, bottom]或者[left, top, right, bottom,] + :return: padding的格式为(left, top, right, bottom) + ''' if isinstance(padding, numbers.Number): + # 如果padding是数字,则设置padding的长度为4 padding = [padding] * 4 + # 如果padding是列表,则设置padding的长度为2 if len(padding) == 2: left = top = padding[0] right = bottom = padding[1] padding = (left, top, right, bottom,) + # 如果padding是元组,则设置padding if isinstance(padding, list): padding = tuple(padding) + # 返回padding return padding - -class AdjustGamma(ImageTensorOperation): - r""" - Apply gamma correction on input image. Input image is expected to be in [..., H, W, C] or [H, W] format. - .. math:: - I_{\text{out}} = 255 \times \text{gain} \times \left(\frac{I_{\text{in}}}{255}\right)^{\gamma} - - See `Gamma Correction`_ for more details. - - .. _Gamma Correction: https://en.wikipedia.org/wiki/Gamma_correction - - Args: - gamma (float): Non negative real number. - The output image pixel value is exponentially related to the input image pixel value. - gamma larger than 1 make the shadows darker, - while gamma smaller than 1 make dark regions lighter. - gain (float, optional): The constant multiplier (default=1). - - Raises: - TypeError: If `gain` is not of type float. - TypeError: If `gamma` is not of type float. - ValueError: If `gamma` is less than 0. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> transforms_list = [c_vision.Decode(), c_vision.AdjustGamma(gamma=10.0, gain=1.0)] - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns=["image"]) - """ - - @check_adjust_gamma - def __init__(self, gamma, gain=1): - self.gamma = gamma - self.gain = gain - - def parse(self): - return cde.AdjustGammaOperation(self.gamma, self.gain) - - -class AutoAugment(ImageTensorOperation): - """ - Apply AutoAugment data augmentation method based on - `AutoAugment: Learning Augmentation Strategies from Data `_. - This operation works only with 3-channel RGB images. - - Args: - policy (AutoAugmentPolicy, optional): AutoAugment policies learned on different datasets - (default=AutoAugmentPolicy.IMAGENET). - It can be any of [AutoAugmentPolicy.IMAGENET, AutoAugmentPolicy.CIFAR10, AutoAugmentPolicy.SVHN]. - Randomly apply 2 operations from a candidate set. See auto augmentation details in AutoAugmentPolicy. - - - AutoAugmentPolicy.IMAGENET, means to apply AutoAugment learned on ImageNet dataset. - - - AutoAugmentPolicy.CIFAR10, means to apply AutoAugment learned on Cifar10 dataset. - - - AutoAugmentPolicy.SVHN, means to apply AutoAugment learned on SVHN dataset. - - interpolation (Inter, optional): Image interpolation mode for Resize operator (default=Inter.NEAREST). - It can be any of [Inter.NEAREST, Inter.BILINEAR, Inter.BICUBIC, Inter.AREA]. - - - Inter.NEAREST: means interpolation method is nearest-neighbor interpolation. - - - Inter.BILINEAR: means interpolation method is bilinear interpolation. - - - Inter.BICUBIC: means the interpolation method is bicubic interpolation. - - - Inter.AREA: means the interpolation method is area interpolation. - - fill_value (Union[int, tuple], optional): Pixel fill value for the area outside the transformed image. - It can be an int or a 3-tuple. If it is a 3-tuple, it is used to fill R, G, B channels respectively. - If it is an integer, it is used for all RGB channels. The fill_value values must be in range [0, 255] - (default=0). - - Raises: - TypeError: If `policy` is not of type AutoAugmentPolicy. - TypeError: If `interpolation` is not of type Inter. - TypeError: If `fill_value` is not an integer or a tuple of length 3. - RuntimeError: If given tensor shape is not . - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.vision import AutoAugmentPolicy, Inter - >>> transforms_list = [c_vision.Decode(), c_vision.AutoAugment(policy=AutoAugmentPolicy.IMAGENET, - ... interpolation=Inter.NEAREST, - ... fill_value=0)] - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns=["image"]) - """ - - @check_auto_augment - def __init__(self, policy=AutoAugmentPolicy.IMAGENET, interpolation=Inter.NEAREST, fill_value=0): - self.policy = policy - self.interpolation = interpolation - if isinstance(fill_value, int): - fill_value = tuple([fill_value] * 3) - self.fill_value = fill_value - - def parse(self): - return cde.AutoAugmentOperation(DE_C_AUTO_AUGMENT_POLICY[self.policy], DE_C_INTER_MODE[self.interpolation], - self.fill_value) - - +# 在输入图像上应用自动对比度 class AutoContrast(ImageTensorOperation): - """ - Apply automatic contrast on input image. This operator calculates histogram of image, reassign cutoff percent - of the lightest pixels from histogram to 255, and reassign cutoff percent of the darkest pixels from histogram to 0. - - Args: - cutoff (float, optional): Percent of lightest and darkest pixels to cut off from - the histogram of input image. The value must be in the range [0.0, 50.0) (default=0.0). - ignore (Union[int, sequence], optional): The background pixel values to ignore, - The ignore values must be in range [0, 255] (default=None). - - Raises: - TypeError: If `cutoff` is not of type float. - TypeError: If `ignore` is not of type int or sequence. - ValueError: If `cutoff` is not in range [0, 50.0). - ValueError: If `ignore` is not in range [0, 255]. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> transforms_list = [c_vision.Decode(), c_vision.AutoContrast(cutoff=10.0, ignore=[10, 20])] - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns=["image"]) - """ - + ''' + 自动对比度操作,可以设置对比度的阈值和忽略的像素点 + ''' @check_auto_contrast def __init__(self, cutoff=0.0, ignore=None): + ''' + 参数: + cutoff:对比度的阈值,默认为0.0 + ignore:忽略的像素点,可以是一个整数或者一个列表,默认为空列表 + ''' if ignore is None: ignore = [] if isinstance(ignore, int): @@ -279,37 +132,15 @@ class AutoContrast(ImageTensorOperation): def parse(self): return cde.AutoContrastOperation(self.cutoff, self.ignore) - +# 对图像的随机标注边界框区域,应用给定的图像变换处理 class BoundingBoxAugment(ImageTensorOperation): - """ - Apply a given image processing operation on a random selection of bounding box regions of a given image. - - Args: - transform (TensorOperation): C++ transformation operation to be applied on random selection - of bounding box regions of a given image. - ratio (float, optional): Ratio of bounding boxes to apply augmentation on. - Range: [0.0, 1.0] (default=0.3). - - Raises: - TypeError: If `transform` is not an image processing operation - in :class:`mindspore.dataset.vision.c_transforms`. - TypeError: If `ratio` is not of type float. - ValueError: If `ratio` is not in range [0.0, 1.0]. - RuntimeError: If given bounding box is invalid. - - Supported Platforms: - ``CPU`` - - Examples: - >>> # set bounding box operation with ratio of 1 to apply rotation on all bounding boxes - >>> bbox_aug_op = c_vision.BoundingBoxAugment(c_vision.RandomRotation(90), 1) - >>> # map to apply ops - >>> image_folder_dataset = image_folder_dataset.map(operations=[bbox_aug_op], - ... input_columns=["image", "bbox"], - ... output_columns=["image", "bbox"], - ... column_order=["image", "bbox"]) - """ - + ''' + 对图像进行边界框增强。 + + 参数: + transform:要增强的变换。 + ratio:比例,默认为0.3。 + ''' @check_bounding_box_augment_cpp def __init__(self, transform, ratio=0.3): self.ratio = ratio @@ -322,183 +153,37 @@ class BoundingBoxAugment(ImageTensorOperation): transform = self.transform return cde.BoundingBoxAugmentOperation(transform, self.ratio) - +# 对输入图像应用中心区域裁剪 class CenterCrop(ImageTensorOperation): - """ - Crop the input image at the center to the given size. If input image size is smaller than output size, - input image will be padded with 0 before cropping. + ''' + 对图像进行中心裁剪 - Args: - size (Union[int, sequence]): The output size of the cropped image. - If size is an integer, a square crop of size (size, size) is returned. - If size is a sequence of length 2, an image of size (height, width) will be cropped. - The size value(s) must be larger than 0. - - Raises: - TypeError: If `size` is not of type int or sequence. - ValueError: If `size` is less than or equal to 0. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> # crop image to a square - >>> transforms_list1 = [c_vision.Decode(), c_vision.CenterCrop(50)] - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list1, - ... input_columns=["image"]) - >>> # crop image to portrait style - >>> transforms_list2 = [c_vision.Decode(), c_vision.CenterCrop((60, 40))] - >>> image_folder_dataset_1 = image_folder_dataset_1.map(operations=transforms_list2, - ... input_columns=["image"]) - """ - - @check_center_crop + 参数: + size:图像的大小 + ''' + @check_crop def __init__(self, size): if isinstance(size, int): size = (size, size) self.size = size def parse(self): + ''' + 返回中心裁剪的操作 + ''' return cde.CenterCropOperation(self.size) - -class ConvertColor(ImageTensorOperation): - """ - Change the color space of the image. - - Args: - convert_mode (ConvertMode): The mode of image channel conversion. - - - ConvertMode.COLOR_BGR2BGRA, Convert BGR image to BGRA image. - - - ConvertMode.COLOR_RGB2RGBA, Convert RGB image to RGBA image. - - - ConvertMode.COLOR_BGRA2BGR, Convert BGRA image to BGR image. - - - ConvertMode.COLOR_RGBA2RGB, Convert RGBA image to RGB image. - - - ConvertMode.COLOR_BGR2RGBA, Convert BGR image to RGBA image. - - - ConvertMode.COLOR_RGB2BGRA, Convert RGB image to BGRA image. - - - ConvertMode.COLOR_RGBA2BGR, Convert RGBA image to BGR image. - - - ConvertMode.COLOR_BGRA2RGB, Convert BGRA image to RGB image. - - - ConvertMode.COLOR_BGR2RGB, Convert BGR image to RGB image. - - - ConvertMode.COLOR_RGB2BGR, Convert RGB image to BGR image. - - - ConvertMode.COLOR_BGRA2RGBA, Convert BGRA image to RGBA image. - - - ConvertMode.COLOR_RGBA2BGRA, Convert RGBA image to BGRA image. - - - ConvertMode.COLOR_BGR2GRAY, Convert BGR image to GRAY image. - - - ConvertMode.COLOR_RGB2GRAY, Convert RGB image to GRAY image. - - - ConvertMode.COLOR_GRAY2BGR, Convert GRAY image to BGR image. - - - ConvertMode.COLOR_GRAY2RGB, Convert GRAY image to RGB image. - - - ConvertMode.COLOR_GRAY2BGRA, Convert GRAY image to BGRA image. - - - ConvertMode.COLOR_GRAY2RGBA, Convert GRAY image to RGBA image. - - - ConvertMode.COLOR_BGRA2GRAY, Convert BGRA image to GRAY image. - - - ConvertMode.COLOR_RGBA2GRAY, Convert RGBA image to GRAY image. - - Raises: - TypeError: If `convert_mode` is not of type :class:`mindspore.dataset.vision.c_transforms.ConvertMode`. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> import mindspore.dataset.vision.utils as mode - >>> # Convert RGB images to GRAY images - >>> convert_op = c_vision.ConvertColor(mode.ConvertMode.COLOR_RGB2GRAY) - >>> image_folder_dataset = image_folder_dataset.map(operations=convert_op, - ... input_columns=["image"]) - >>> # Convert RGB images to BGR images - >>> convert_op = c_vision.ConvertColor(mode.ConvertMode.COLOR_RGB2BGR) - >>> image_folder_dataset_1 = image_folder_dataset_1.map(operations=convert_op, - ... input_columns=["image"]) - """ - - @check_convert_color - def __init__(self, convert_mode): - self.convert_mode = convert_mode - - def parse(self): - return cde.ConvertColorOperation(DE_C_CONVERT_COLOR_MODE[self.convert_mode]) - - -class Crop(ImageTensorOperation): - """ - Crop the input image at a specific location. - - Args: - coordinates(sequence): Coordinates of the upper left corner of the cropping image. Must be a sequence of two - values, in the form of (top, left). - size (Union[int, sequence]): The output size of the cropped image. - If size is an integer, a square crop of size (size, size) is returned. - If size is a sequence of length 2, an image of size (height, width) will be cropped. - The size value(s) must be larger than 0. - - Raises: - TypeError: If `coordinates` is not of type sequence. - TypeError: If `size` is not of type int or sequence. - ValueError: If `coordinates` is less than 0. - ValueError: If `size` is less than or equal to 0. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> decode_op = c_vision.Decode() - >>> crop_op = c_vision.Crop((0, 0), 32) - >>> transforms_list = [decode_op, crop_op] - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns=["image"]) - """ - - @check_crop - def __init__(self, coordinates, size): - if isinstance(size, int): - size = (size, size) - self.coordinates = coordinates - self.size = size - - def parse(self): - return cde.CropOperation(self.coordinates, self.size) - - +# 对输入批次的图像和标注应用剪切混合转换 class CutMixBatch(ImageTensorOperation): """ Apply CutMix transformation on input batch of images and labels. - Note that you need to make labels into one-hot format and batched before calling this operator. + Note that you need to make labels into one-hot format and batch before calling this function. Args: - image_batch_format (ImageBatchFormat): The method of padding. Can be any of - [ImageBatchFormat.NHWC, ImageBatchFormat.NCHW]. - alpha (float, optional): Hyperparameter of beta distribution, must be larger than 0 (default = 1.0). - prob (float, optional): The probability by which CutMix is applied to each image, range: [0, 1] (default = 1.0). - - Raises: - TypeError: If `image_batch_format` is not of type :class:`mindspore.dataset.vision.ImageBatchFormat`. - TypeError: If `alpha` is not of type float. - TypeError: If `prob` is not of type float. - ValueError: If `alpha` is less than or equal 0. - ValueError: If `prob` is not in range [0, 1]. - RuntimeError: If given tensor shape is not . - - Supported Platforms: - ``CPU`` + image_batch_format (Image Batch Format): The method of padding. Can be any of + [ImageBatchFormat.NHWC, ImageBatchFormat.NCHW] + alpha (float, optional): hyperparameter of beta distribution (default = 1.0). + prob (float, optional): The probability by which CutMix is applied to each image (default = 1.0). Examples: >>> from mindspore.dataset.vision import ImageBatchFormat @@ -511,8 +196,18 @@ class CutMixBatch(ImageTensorOperation): ... input_columns=["image", "label"]) """ + + ''' + 对图像批次进行裁剪混合 + ''' @check_cut_mix_batch_c def __init__(self, image_batch_format, alpha=1.0, prob=1.0): + ''' + 参数: + image_batch_format: 图像批次格式 + alpha: 混合概率 + prob: 概率 + ''' self.image_batch_format = image_batch_format.value self.alpha = alpha self.prob = prob @@ -520,24 +215,14 @@ class CutMixBatch(ImageTensorOperation): def parse(self): return cde.CutMixBatchOperation(DE_C_IMAGE_BATCH_FORMAT[self.image_batch_format], self.alpha, self.prob) - +# 从输入图像数组中随机裁剪出给定数量的正方形区域。 class CutOut(ImageTensorOperation): """ - Randomly cut (mask) out a given number of square patches from the input image array. + Randomly cut (mask) out a given number of square patches from the input NumPy image array. Args: - length (int): The side length of each square patch, must be larger than 0. - num_patches (int, optional): Number of patches to be cut out of an image, must be larger than 0. (default=1). - - Raises: - TypeError: If `length` is not of type int. - TypeError: If `num_patches` is not of type int. - ValueError: If `length` is less than or equal 0. - ValueError: If `num_patches` is less than or equal 0. - RuntimeError: If given tensor shape is not . - - Supported Platforms: - ``CPU`` + length (int): The side length of each square patch. + num_patches (int, optional): Number of patches to be cut out of an image (default=1). Examples: >>> transforms_list = [c_vision.Decode(), c_vision.CutOut(80, num_patches=10)] @@ -547,27 +232,25 @@ class CutOut(ImageTensorOperation): @check_cutout def __init__(self, length, num_patches=1): + ''' + 参数: + length:每个正方形区域的边长 + num_patches:要从图像中切出的正方形区域数 + ''' self.length = length self.num_patches = num_patches def parse(self): return cde.CutOutOperation(self.length, self.num_patches) - + # 将输入的压缩图像解码为RGB格式 class Decode(ImageTensorOperation): """ - Decode the input image. + Decode the input image in RGB mode. Args: rgb (bool, optional): Mode of decoding input image (default=True). - If True means format of decoded image is RGB else BGR (deprecated). - - Raises: - RuntimeError: If `rgb` is False, since this option is deprecated. - RuntimeError: If given tensor is not a 1D sequence. - - Supported Platforms: - ``CPU`` + If True means format of decoded image is RGB else BGR(deprecated). Examples: >>> transforms_list = [c_vision.Decode(), c_vision.RandomHorizontalFlip()] @@ -588,27 +271,18 @@ class Decode(ImageTensorOperation): Returns: img (NumPy), Decoded image. """ - if isinstance(img, bytes): - img = np.frombuffer(img, np.uint8) - elif not isinstance(img, np.ndarray) or img.ndim != 1 or img.dtype.type is np.str_: - raise TypeError( - "Input should be an encoded image in 1-D NumPy format, got {}.".format(type(img))) + if not isinstance(img, np.ndarray) or img.ndim != 1 or img.dtype.type is np.str_: + raise TypeError("Input should be an encoded image in 1-D NumPy format, got {}.".format(type(img))) return super().__call__(img) def parse(self): return cde.DecodeOperation(self.rgb) - +# 对输入图像进行直方图均衡化 class Equalize(ImageTensorOperation): """ Apply histogram equalization on input image. - Raises: - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - Examples: >>> transforms_list = [c_vision.Decode(), c_vision.Equalize()] >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, @@ -616,85 +290,13 @@ class Equalize(ImageTensorOperation): """ def parse(self): + # 返回一个EqualizeOperation对象 return cde.EqualizeOperation() - -class GaussianBlur(ImageTensorOperation): - """ - Blur input image with the specified Gaussian kernel. - - Args: - kernel_size (Union[int, Sequence[int]]): Size of the Gaussian kernel to use. The value must be positive and odd. - If only an integer is provided, the kernel size will be (kernel_size, kernel_size). If a sequence of integer - is provided, it must be a sequence of 2 values which represents (width, height). - sigma (Union[float, Sequence[float]], optional): Standard deviation of the Gaussian kernel to use - (default=None). The value must be positive. If only a float is provided, the sigma will be (sigma, sigma). - If a sequence of float is provided, it must be a sequence of 2 values which represents (width, height). - If None is provided, the sigma will be calculated as ((kernel_size - 1) * 0.5 - 1) * 0.3 + 0.8. - - Raises: - TypeError: If `kernel_size` is not of type int or Sequence[int]. - TypeError: If `sigma` is not of type float or Sequence[float]. - ValueError: If `kernel_size` is not positive and odd. - ValueError: If `sigma` is not positive. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> transforms_list = [c_vision.Decode(), c_vision.GaussianBlur(3, 3)] - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns=["image"]) - """ - - @check_gaussian_blur - def __init__(self, kernel_size, sigma=None): - if isinstance(kernel_size, int): - kernel_size = (kernel_size,) - if sigma is None: - sigma = (0,) - elif isinstance(sigma, (int, float)): - sigma = (float(sigma),) - self.kernel_size = kernel_size - self.sigma = sigma - - def parse(self): - return cde.GaussianBlurOperation(self.kernel_size, self.sigma) - - -class HorizontalFlip(ImageTensorOperation): - """ - Flip the input image horizontally. - - Raises: - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> transforms_list = [c_vision.Decode(), c_vision.HorizontalFlip()] - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns=["image"]) - """ - - def parse(self): - return cde.HorizontalFlipOperation() - - +# 将输入图像的shape从 转换为 class HWC2CHW(ImageTensorOperation): """ - Transpose the input image from shape to shape . The input image should be 3 channels image. - - Note: - This operation supports running on Ascend or GPU platforms by Offload. - - Raises: - RuntimeError: If given tensor shape is not . - - Supported Platforms: - ``CPU`` ``Ascend`` ``GPU`` + Transpose the input image; shape (H, W, C) to shape (C, H, W). Examples: >>> transforms_list = [c_vision.Decode(), @@ -708,16 +310,10 @@ class HWC2CHW(ImageTensorOperation): def parse(self): return cde.HwcToChwOperation() - +# 在 RGB 模式下对输入图像应用像素反转 class Invert(ImageTensorOperation): """ - Apply invert on input image in RGB mode. This operator will reassign every pixel to (255 - pixel). - - Raises: - RuntimeError: If given tensor shape is not . - - Supported Platforms: - ``CPU`` + Apply invert on input image in RGB mode. Examples: >>> transforms_list = [c_vision.Decode(), c_vision.Invert()] @@ -728,28 +324,16 @@ class Invert(ImageTensorOperation): def parse(self): return cde.InvertOperation() - +# 对输入批次的图像和标注应用混合转换 class MixUpBatch(ImageTensorOperation): """ - Apply MixUp transformation on input batch of images and labels. Each image is - multiplied by a random weight (lambda) and then added to a randomly selected image from the batch - multiplied by (1 - lambda). The same formula is also applied to the one-hot labels. - - The lambda is generated based on the specified alpha value. Two coefficients x1, x2 are randomly generated - in the range [alpha, 1], and lambda = (x1 / (x1 + x2)). - - Note that you need to make labels into one-hot format and batched before calling this operator. + Apply MixUp transformation on input batch of images and labels. Each image is multiplied by a random weight (lambda) + and then added to a randomly selected image from the batch multiplied by (1 - lambda). The same formula is also + applied to the one-hot labels. + Note that you need to make labels into one-hot format and batch before calling this function. Args: - alpha (float, optional): Hyperparameter of beta distribution. The value must be positive (default = 1.0). - - Raises: - TypeError: If `alpha` is not of type float. - ValueError: If `alpha` is not positive. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` + alpha (float, optional): Hyperparameter of beta distribution (default = 1.0). Examples: >>> onehot_op = c_transforms.OneHot(num_classes=10) @@ -761,21 +345,24 @@ class MixUpBatch(ImageTensorOperation): ... input_columns=["image", "label"]) """ + ''' + MixUpBatch:MixUpBatch类,用于混合混合概率 + ''' + @check_mix_up_batch_c def __init__(self, alpha=1.0): + ''' + alpha:混合概率 + ''' self.alpha = alpha def parse(self): return cde.MixUpBatchOperation(self.alpha) - +# 根据均值和标准差对输入图像进行归一化 class Normalize(ImageTensorOperation): """ - Normalize the input image with respect to mean and standard deviation. This operator will normalize - the input image with: output[channel] = (input[channel] - mean[channel]) / std[channel], where channel >= 1. - - Note: - This operation supports running on Ascend or GPU platforms by Offload. + Normalize the input image with respect to mean and standard deviation. Args: mean (sequence): List or tuple of mean values for each channel, with respect to channel order. @@ -783,16 +370,6 @@ class Normalize(ImageTensorOperation): std (sequence): List or tuple of standard deviations for each channel, with respect to channel order. The standard deviation values must be in range (0.0, 255.0]. - Raises: - TypeError: If `mean` is not of type sequence. - TypeError: If `std` is not of type sequence. - ValueError: If `mean` is not in range [0.0, 255.0]. - ValueError: If `mean` is not in range (0.0, 255.0]. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` ``Ascend`` ``GPU`` - Examples: >>> decode_op = c_vision.Decode() >>> normalize_op = c_vision.Normalize(mean=[121.0, 115.0, 100.0], std=[70.0, 68.0, 71.0]) @@ -803,13 +380,25 @@ class Normalize(ImageTensorOperation): @check_normalize_c def __init__(self, mean, std): + ''' + 参数: + mean:图像每个通道的均值组成的列表或元组 + std:图像每个通道的标准差组成的列表或元组 + ''' + # 如果mean的长度为1,则将mean转换为3个元素的列表 + if len(mean) == 1: + mean = [mean[0]] * 3 + # 如果std的长度为1,则将std转换为3个元素的列表 + if len(std) == 1: + std = [std[0]] * 3 + # 将mean和std赋值给self.mean和self.std self.mean = mean self.std = std def parse(self): return cde.NormalizeOperation(self.mean, self.std) - +# 根据均值和标准差对输入图像进行归一化,然后填充一个全零的额外通道 class NormalizePad(ImageTensorOperation): """ Normalize the input image with respect to mean and standard deviation then pad an extra channel with value zero. @@ -819,18 +408,7 @@ class NormalizePad(ImageTensorOperation): The mean values must be in range (0.0, 255.0]. std (sequence): List or tuple of standard deviations for each channel, with respect to channel order. The standard deviation values must be in range (0.0, 255.0]. - dtype (str, optional): Set the dtype of the output image (default is "float32"). - - Raises: - TypeError: If `mean` is not of type sequence. - TypeError: If `std` is not of type sequence. - TypeError: If `dtype` is not of type str. - ValueError: If `mean` is not in range [0.0, 255.0]. - ValueError: If `std` is not in range (0.0, 255.0]. - RuntimeError: If given tensor shape is not . - - Supported Platforms: - ``CPU`` + dtype (str): Set the output data type of normalized image (default is "float32"). Examples: >>> decode_op = c_vision.Decode() @@ -842,8 +420,17 @@ class NormalizePad(ImageTensorOperation): ... input_columns=["image"]) """ + ''' + 对图像进行标准化和填充 + ''' + @check_normalizepad_c def __init__(self, mean, std, dtype="float32"): + ''' + :param mean: 图像均值 + :param std: 图像方差 + :param dtype: 数据类型 + ''' self.mean = mean self.std = std self.dtype = dtype @@ -854,20 +441,20 @@ class NormalizePad(ImageTensorOperation): class Pad(ImageTensorOperation): """ - Pad the image. + Pad the image according to padding parameters. Args: - padding (Union[int, Sequence[tuple]]): The number of pixels to pad each border of the image. + padding (Union[int, sequence]): The number of pixels to pad the image. If a single number is provided, it pads all borders with this value. - If a tuple or lists of 2 values are provided, it pads the (left and top) + If a tuple or list of 2 values are provided, it pads the (left and top) with the first value and (right and bottom) with the second value. - If 4 values are provided as a list or tuple, it pads the left, top, right and bottom respectively. - The pad values must be non-negative. - fill_value (Union[int, tuple[int]], optional): The pixel intensity of the borders, only valid for + If 4 values are provided as a list or tuple, + it pads the left, top, right and bottom respectively. + fill_value (Union[int, tuple], optional): The pixel intensity of the borders, only valid for padding_mode Border.CONSTANT. If it is a 3-tuple, it is used to fill R, G, B channels respectively. If it is an integer, it is used for all RGB channels. The fill_value values must be in range [0, 255] (default=0). - padding_mode (Border, optional): The method of padding (default=Border.CONSTANT). Can be any of + padding_mode (Border mode, optional): The method of padding (default=Border.CONSTANT). Can be any of [Border.CONSTANT, Border.EDGE, Border.REFLECT, Border.SYMMETRIC]. - Border.CONSTANT, means it fills the border with constant values. @@ -880,26 +467,23 @@ class Pad(ImageTensorOperation): - Border.SYMMETRIC, means it reflects the values on the edge repeating the last value of edge. - Raises: - TypeError: If `padding` is not of type int or Sequence[int]. - TypeError: If `fill_value` is not of type int or tuple[int]. - TypeError: If `padding_mode` is not of type :class:`mindspore.dataset.vision.Border`. - ValueError: If `padding` is negative. - ValueError: If `fill_value` is not in range [0, 255]. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - Examples: + >>> from mindspore.dataset.vision import Border >>> transforms_list = [c_vision.Decode(), c_vision.Pad([100, 100, 100, 100])] >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, ... input_columns=["image"]) """ + ''' + 参数: + padding:要填充的像素 + fill_value:填充值,可以是int或者tuple + padding_mode:填充模式,可以是Border.CONSTANT或者Border.REFLECT或者Border.REPLICATE + ''' @check_pad def __init__(self, padding, fill_value=0, padding_mode=Border.CONSTANT): padding = parse_padding(padding) + # 如果fill_value是整数,则将其转换为元组 if isinstance(fill_value, int): fill_value = tuple([fill_value] * 3) self.padding = padding @@ -909,71 +493,35 @@ class Pad(ImageTensorOperation): def parse(self): return cde.PadOperation(self.padding, self.fill_value, DE_C_BORDER_TYPE[self.padding_mode]) - -class RandomAdjustSharpness(ImageTensorOperation): - """ - Randomly adjust the sharpness of the input image with a given probability. - - Args: - degree (float): Sharpness adjustment degree, which must be non negative. - Degree of 0.0 gives a blurred image, degree of 1.0 gives the original image, - and degree of 2.0 increases the sharpness by a factor of 2. - prob (float, optional): Probability of the image being sharpness adjusted, which - must be in range of [0, 1] (default=0.5). - - Raises: - TypeError: If `degree` is not of type float. - TypeError: If `prob` is not of type float. - ValueError: If `degree` is negative. - ValueError: If `prob` is not in range [0, 1]. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> transforms_list = [c_vision.Decode(), c_vision.RandomAdjustSharpness(2.0, 0.5)] - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns=["image"]) - """ - - @check_random_adjust_sharpness - def __init__(self, degree, prob=0.5): - self.prob = prob - self.degree = degree - - def parse(self): - return cde.RandomAdjustSharpnessOperation(self.degree, self.prob) - - +# 对输入图像应用随机仿射变换 class RandomAffine(ImageTensorOperation): """ Apply Random affine transformation to the input image. Args: - degrees (Union[int, float, sequence]): Range of the rotation degrees. - If `degrees` is a number, the range will be (-degrees, degrees). - If `degrees` is a sequence, it should be (min, max). + degrees (int or float or sequence): Range of the rotation degrees. + If degrees is a number, the range will be (-degrees, degrees). + If degrees is a sequence, it should be (min, max). translate (sequence, optional): Sequence (tx_min, tx_max, ty_min, ty_max) of minimum/maximum translation in - x(horizontal) and y(vertical) directions, range [-1.0, 1.0] (default=None). + x(horizontal) and y(vertical) directions (default=None). The horizontal and vertical shift is selected randomly from the range: (tx_min*width, tx_max*width) and (ty_min*height, ty_max*height), respectively. If a tuple or list of size 2, then a translate parallel to the X axis in the range of (translate[0], translate[1]) is applied. - If a tuple or list of size 4, then a translate parallel to the X axis in the range of + If a tuple of list of size 4, then a translate parallel to the X axis in the range of (translate[0], translate[1]) and a translate parallel to the Y axis in the range of (translate[2], translate[3]) are applied. If None, no translation is applied. - scale (sequence, optional): Scaling factor interval, which must be non negative - (default=None, original scale is used). - shear (Union[int, float, sequence], optional): Range of shear factor, which must be positive (default=None). + scale (sequence, optional): Scaling factor interval (default=None, original scale is used). + shear (int or float or sequence, optional): Range of shear factor (default=None). If a number, then a shear parallel to the X axis in the range of (-shear, +shear) is applied. If a tuple or list of size 2, then a shear parallel to the X axis in the range of (shear[0], shear[1]) is applied. - If a tuple or list of size 4, then a shear parallel to X axis in the range of (shear[0], shear[1]) + If a tuple of list of size 4, then a shear parallel to X axis in the range of (shear[0], shear[1]) and a shear parallel to Y axis in the range of (shear[2], shear[3]) is applied. If None, no shear is applied. - resample (Inter, optional): An optional resampling filter (default=Inter.NEAREST). + resample (Inter mode, optional): An optional resampling filter (default=Inter.NEAREST). + If omitted, or if the image has mode "1" or "P", it is set to be Inter.NEAREST. It can be any of [Inter.BILINEAR, Inter.NEAREST, Inter.BICUBIC]. - Inter.BILINEAR, means resample method is bilinear interpolation. @@ -982,25 +530,21 @@ class RandomAffine(ImageTensorOperation): - Inter.BICUBIC, means resample method is bicubic interpolation. - fill_value (Union[int, tuple[int]], optional): Optional fill_value to fill the area outside the transform + fill_value (tuple or int, optional): Optional fill_value to fill the area outside the transform in the output image. There must be three elements in tuple and the value of single element is [0, 255]. - (default=0, filling is performed). + Used only in Pillow versions > 5.0.0 (default=0, filling is performed). Raises: - TypeError: If `degrees` is not of type int, float or sequence. - TypeError: If `translate` is not of type sequence. - TypeError: If `scale` is not of type sequence. - TypeError: If `shear` is not of type int, float or sequence. - TypeError: If `resample` is not of type :class:`mindspore.dataset.vision.Inter`. - TypeError: If `fill_value` is not of type int or tuple[int]. - ValueError: If `degrees` is negative. - ValueError: If `translate` is not in range [-1.0, 1.0]. - ValueError: If `scale` is negative. - ValueError: If `shear` is not positive. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` + ValueError: If degrees is negative. + ValueError: If translation value is not between -1 and 1. + ValueError: If scale is not positive. + ValueError: If shear is a number but is not positive. + TypeError: If degrees is not a number or a list or a tuple. + If degrees is a list or tuple, its length is not 2. + TypeError: If translate is specified but is not list or a tuple of length 2 or 4. + TypeError: If scale is not a list or tuple of length 2.'' + TypeError: If shear is not a list or tuple of length 2 or 4. + TypeError: If fill_value is not a single integer or a 3-tuple. Examples: >>> from mindspore.dataset.vision import Inter @@ -1014,19 +558,32 @@ class RandomAffine(ImageTensorOperation): ... input_columns=["image"]) """ + ''' + 参数: + degrees:旋转度数的范围 + translate:(tx_min, tx_max, ty_min, ty_max)用于表示水平(tx)方向和垂直(ty)方向的最小/最大平移范围 + scale:图像的比例因子的随机范围,必须为非负数 + shear:图像的剪切因子的随机范围,必须为正数 + resample:图像插值方式 + fill_value:用于填充输出图像中变换之外的区域 + ''' + @check_random_affine def __init__(self, degrees, translate=None, scale=None, shear=None, resample=Inter.NEAREST, fill_value=0): # Parameter checking if shear is not None: + # 如果shear参数是数字,则转换为元组 if isinstance(shear, numbers.Number): shear = (-1 * shear, shear, 0., 0.) else: + # 如果shear参数是元组,则判断元组长度 if len(shear) == 2: shear = [shear[0], shear[1], 0., 0.] elif len(shear) == 4: shear = [s for s in shear] if isinstance(degrees, numbers.Number): + # 如果degrees参数是数字,则转换为元组 degrees = (-1 * degrees, degrees) if isinstance(fill_value, numbers.Number): @@ -1034,14 +591,17 @@ class RandomAffine(ImageTensorOperation): # translation if translate is None: + # 如果translate参数为空,则设置为0.0 translate = (0.0, 0.0, 0.0, 0.0) # scale if scale is None: + # 如果scale参数为空,则设置为1.0 scale = (1.0, 1.0) # shear if shear is None: + # 如果shear参数为空,则设置为0.0 shear = (0.0, 0.0, 0.0, 0.0) self.degrees = degrees @@ -1055,75 +615,27 @@ class RandomAffine(ImageTensorOperation): return cde.RandomAffineOperation(self.degrees, self.translate, self.scale_, self.shear, self.resample, self.fill_value) - -class RandomAutoContrast(ImageTensorOperation): - """ - Automatically adjust the contrast of the image with a given probability. - - Args: - cutoff (float, optional): Percent of the lightest and darkest pixels to be cut off from - the histogram of the input image. The value must be in range of [0.0, 50.0) (default=0.0). - ignore (Union[int, sequence], optional): The background pixel values to be ignored, each of - which must be in range of [0, 255] (default=None). - prob (float, optional): Probability of the image being automatically contrasted, which - must be in range of [0, 1] (default=0.5). - - Raises: - TypeError: If `cutoff` is not of type float. - TypeError: If `ignore` is not of type int or sequence of int. - TypeError: If `prob` is not of type float. - ValueError: If `cutoff` is not in range [0.0, 50.0). - ValueError: If `ignore` is not in range [0, 255]. - ValueError: If `prob` is not in range [0, 1]. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> transforms_list = [c_vision.Decode(), c_vision.RandomAutoContrast(cutoff=0.0, ignore=None, prob=0.5)] - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns=["image"]) - """ - - @check_random_auto_contrast - def __init__(self, cutoff=0.0, ignore=None, prob=0.5): - if ignore is None: - ignore = [] - if isinstance(ignore, int): - ignore = [ignore] - self.cutoff = cutoff - self.ignore = ignore - self.prob = prob - - def parse(self): - return cde.RandomAutoContrastOperation(self.cutoff, self.ignore, self.prob) - - +# 随机调整输入图像的颜色 class RandomColor(ImageTensorOperation): """ Adjust the color of the input image by a fixed or random degree. - This operation works only with 3-channel RGB images. + This operation works only with 3-channel color images. Args: - degrees (Sequence[float], optional): Range of random color adjustment degrees, which must be non-negative. + degrees (sequence, optional): Range of random color adjustment degrees. It should be in (min, max) format. If min=max, then it is a single fixed magnitude operation (default=(0.1, 1.9)). - Raises: - TypeError: If `degrees` is not of type Sequence[float]. - ValueError: If `degrees` is negative. - RuntimeError: If given tensor shape is not . - - Supported Platforms: - ``CPU`` - Examples: >>> transforms_list = [c_vision.Decode(), c_vision.RandomColor((0.5, 2.0))] >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, ... input_columns=["image"]) """ + ''' + 参数: + degrees:色彩调节系数的范围,必须为非负数 + ''' @check_positive_degrees def __init__(self, degrees=(0.1, 1.9)): self.degrees = degrees @@ -1131,45 +643,28 @@ class RandomColor(ImageTensorOperation): def parse(self): return cde.RandomColorOperation(*self.degrees) - +# 随机调整输入图像的亮度、对比度、饱和度和色调 class RandomColorAdjust(ImageTensorOperation): """ Randomly adjust the brightness, contrast, saturation, and hue of the input image. - Note: - This operation supports running on Ascend or GPU platforms by Offload. - Args: - brightness (Union[float, Sequence[float]], optional): Brightness adjustment factor (default=(1, 1)). + brightness (Union[float, list, tuple], optional): Brightness adjustment factor (default=(1, 1)). Cannot be negative. If it is a float, the factor is uniformly chosen from the range [max(0, 1-brightness), 1+brightness]. If it is a sequence, it should be [min, max] for the range. - contrast (Union[float, Sequence[float]], optional): Contrast adjustment factor (default=(1, 1)). + contrast (Union[float, list, tuple], optional): Contrast adjustment factor (default=(1, 1)). Cannot be negative. If it is a float, the factor is uniformly chosen from the range [max(0, 1-contrast), 1+contrast]. If it is a sequence, it should be [min, max] for the range. - saturation (Union[float, Sequence[float]], optional): Saturation adjustment factor (default=(1, 1)). + saturation (Union[float, list, tuple], optional): Saturation adjustment factor (default=(1, 1)). Cannot be negative. If it is a float, the factor is uniformly chosen from the range [max(0, 1-saturation), 1+saturation]. If it is a sequence, it should be [min, max] for the range. - hue (Union[float, Sequence[float]], optional): Hue adjustment factor (default=(0, 0)). + hue (Union[float, list, tuple], optional): Hue adjustment factor (default=(0, 0)). If it is a float, the range will be [-hue, hue]. Value should be 0 <= hue <= 0.5. If it is a sequence, it should be [min, max] where -0.5 <= min <= max <= 0.5. - Raises: - TypeError: If `brightness` is not of type float or Sequence[float]. - TypeError: If `contrast` is not of type float or Sequence[float]. - TypeError: If `saturation` is not of type float or Sequence[float]. - TypeError: If `hue` is not of type float or Sequence[float]. - ValueError: If `brightness` is negative. - ValueError: If `contrast` is negative. - ValueError: If `saturation` is negative. - ValueError: If `hue` is not in range [-0.5, 0.5]. - RuntimeError: If given tensor shape is not . - - Supported Platforms: - ``CPU`` ``Ascend`` ``GPU`` - Examples: >>> decode_op = c_vision.Decode() >>> transform_op = c_vision.RandomColorAdjust(brightness=(0.5, 1), @@ -1180,59 +675,71 @@ class RandomColorAdjust(ImageTensorOperation): ... input_columns=["image"]) """ + ''' + 参数: + brightness:亮度调整因子 + contrast:对比度调整因子 + saturation:饱和度调整因子 + hue:色调调整因子 + ''' @check_random_color_adjust def __init__(self, brightness=(1, 1), contrast=(1, 1), saturation=(1, 1), hue=(0, 0)): - brightness = self.__expand_values(brightness) - contrast = self.__expand_values(contrast) - saturation = self.__expand_values(saturation) - hue = self.__expand_values( - hue, center=0, bound=(-0.5, 0.5), non_negative=False) + # 调用expand_values,将下列值填充到指定范围内 + brightness = self.expand_values(brightness) + contrast = self.expand_values(contrast) + saturation = self.expand_values(saturation) + hue = self.expand_values(hue, center=0, bound=(-0.5, 0.5), non_negative=False) self.brightness = brightness self.contrast = contrast self.saturation = saturation self.hue = hue - def __expand_values(self, value, center=1, bound=(0, FLOAT_MAX_INTEGER), non_negative=True): - """Expand input value for vision adjustment factor.""" + def expand_values(self, value, center=1, bound=(0, FLOAT_MAX_INTEGER), non_negative=True): + ''' + 将值填充到指定范围内 + :param center: 填充范围的中心 + :param bound: 填充范围 + :param non_negative: 是否填充负值 + ''' if isinstance(value, numbers.Number): + # 如果value是数字,则将其转换为[center - value, center + value] value = [center - value, center + value] + # 如果non_negative为True,则将[0, center]转换为[0, center] if non_negative: value[0] = max(0, value[0]) + # 检查value的范围 check_range(value, bound) + # 返回value的范围 return (value[0], value[1]) def parse(self): return cde.RandomColorAdjustOperation(self.brightness, self.contrast, self.saturation, self.hue) - +# 对输入图像进行随机区域的裁剪 class RandomCrop(ImageTensorOperation): """ - Crop the input image at a random location. If input image size is smaller than output size, - input image will be padded before cropping. + Crop the input image at a random location. - Note: - If the input image is more than one, then make sure that the image size is the same. Args: - size (Union[int, Sequence[int]]): The output size of the cropped image. The size value(s) must be positive. + size (Union[int, sequence]): The output size of the cropped image. If size is an integer, a square crop of size (size, size) is returned. - If size is a sequence of length 2, an image of size (height, width) will be cropped. - padding (Union[int, Sequence[int]], optional): The number of pixels to pad each border of the image. - The padding value(s) must be non-nagetive (default=None). - If padding is not None, pad image first with padding values. + If size is a sequence of length 2, it should be (height, width). + padding (Union[int, sequence], optional): The number of pixels to pad the image (default=None). + If padding is not None, pad image firstly with padding values. If a single number is provided, pad all borders with this value. - If a tuple or lists of 2 values are provided, pad the (left and top) + If a tuple or list of 2 values are provided, pad the (left and top) with the first value and (right and bottom) with the second value. If 4 values are provided as a list or tuple, pad the left, top, right and bottom respectively. pad_if_needed (bool, optional): Pad the image if either side is smaller than the given output size (default=False). - fill_value (Union[int, tuple[int]], optional): The pixel intensity of the borders, only valid for + fill_value (Union[int, tuple], optional): The pixel intensity of the borders, only valid for padding_mode Border.CONSTANT. If it is a 3-tuple, it is used to fill R, G, B channels respectively. If it is an integer, it is used for all RGB channels. The fill_value values must be in range [0, 255] (default=0). - padding_mode (Border, optional): The method of padding (default=Border.CONSTANT). It can be any of + padding_mode (Border mode, optional): The method of padding (default=Border.CONSTANT). It can be any of [Border.CONSTANT, Border.EDGE, Border.REFLECT, Border.SYMMETRIC]. - Border.CONSTANT, means it fills the border with constant values. @@ -1245,20 +752,6 @@ class RandomCrop(ImageTensorOperation): - Border.SYMMETRIC, means it reflects the values on the edge repeating the last value of edge. - Raises: - TypeError: If `size` is not of type int or Sequence[int]. - TypeError: If `padding` is not of type int or Sequence[int]. - TypeError: If `pad_if_needed` is not of type boolean. - TypeError: If `fill_value` is not of type int or tuple[int]. - TypeError: If `padding_mode` is not of type :class:`mindspore.dataset.vision.Border`. - ValueError: If `size` is not positive. - ValueError: If `padding` is negative. - ValueError: If `fill_value` is not in range [0, 255]. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - Examples: >>> from mindspore.dataset.vision import Border >>> decode_op = c_vision.Decode() @@ -1268,15 +761,28 @@ class RandomCrop(ImageTensorOperation): ... input_columns=["image"]) """ + ''' + 参数: + size:裁剪图像的输出尺寸大小 + padding:图像各边填充的像素数 + pad_if_needed:如果输入图像高度或者宽度小于 size 指定的输出图像尺寸大小,是否进行填充 + fill_value:边框的像素强度,仅当 padding_mode 为 Border.CONSTANT 时有效 + padding_mode:边界填充方式 + ''' @check_random_crop def __init__(self, size, padding=None, pad_if_needed=False, fill_value=0, padding_mode=Border.CONSTANT): + # 初始化RandomCrop类,调用ImageTensorOperation类的__init__方法 if isinstance(size, int): + # 如果size为int,则将size转换为元组 size = (size, size) if padding is None: + # 如果padding为None,则padding设置为(0, 0, 0, 0) padding = (0, 0, 0, 0) else: + # 否则,将padding转换为元组 padding = parse_padding(padding) if isinstance(fill_value, int): + # 如果fill_value为int,则将fill_value转换为元组 fill_value = tuple([fill_value] * 3) self.size = size @@ -1287,24 +793,24 @@ class RandomCrop(ImageTensorOperation): def parse(self): border_type = DE_C_BORDER_TYPE[self.padding_mode] + # 获取padding_mode的值 return cde.RandomCropOperation(self.size, self.padding, self.pad_if_needed, self.fill_value, border_type) - +# "裁剪"、"解码"和"调整尺寸大小"的组合处理 class RandomCropDecodeResize(ImageTensorOperation): """ - A combination of `Crop`, `Decode` and `Resize`. It will get better performance for JPEG images. This operator - will crop the input image at a random location, decode the cropped image in RGB mode, and resize the decoded image. + A combination of `Crop`, `Decode` and `Resize`. It will get better performance for JPEG images. Args: - size (Union[int, Sequence[int]]): The output size of the resized image. The size value(s) must be positive. + size (Union[int, sequence]): The size of the output image. If size is an integer, a square crop of size (size, size) is returned. - If size is a sequence of length 2, an image of size (height, width) will be cropped. - scale (Union[list, tuple], optional): Range [min, max) of respective size of the - original size to be cropped, which must be non-negative (default=(0.08, 1.0)). - ratio (Union[list, tuple], optional): Range [min, max) of aspect ratio to be - cropped, which must be non-negative (default=(3. / 4., 4. / 3.)). - interpolation (Inter, optional): Image interpolation mode for resize operator(default=Inter.BILINEAR). - It can be any of [Inter.BILINEAR, Inter.NEAREST, Inter.BICUBIC, Inter.AREA, Inter.PILCUBIC]. + If size is a sequence of length 2, it should be (height, width). + scale (tuple, optional): Range [min, max) of respective size of the + original size to be cropped (default=(0.08, 1.0)). + ratio (tuple, optional): Range [min, max) of aspect ratio to be + cropped (default=(3. / 4., 4. / 3.)). + interpolation (Inter mode, optional): Image interpolation mode (default=Inter.BILINEAR). + It can be any of [Inter.BILINEAR, Inter.NEAREST, Inter.BICUBIC]. - Inter.BILINEAR, means interpolation method is bilinear interpolation. @@ -1312,28 +818,8 @@ class RandomCropDecodeResize(ImageTensorOperation): - Inter.BICUBIC, means interpolation method is bicubic interpolation. - - Inter.AREA, means interpolation method is pixel area interpolation. - - - Inter.PILCUBIC, means interpolation method is bicubic interpolation like implemented in pillow, input - should be in 3 channels format. - max_attempts (int, optional): The maximum number of attempts to propose a valid crop_area (default=10). - If exceeded, fall back to use center_crop instead. The max_attempts value must be positive. - - Raises: - TypeError: If `size` is not of type int or Sequence[int]. - TypeError: If `scale` is not of type tuple or list. - TypeError: If `ratio` is not of type tuple or list. - TypeError: If `interpolation` is not of type :class:`mindspore.dataset.vision.Inter`. - TypeError: If `max_attempts` is not of type int. - ValueError: If `size` is not positive. - ValueError: If `scale` is negative. - ValueError: If `ratio` is negative. - ValueError: If `max_attempts` is not positive. - RuntimeError: If given tensor is not a 1D sequence. - - Supported Platforms: - ``CPU`` + If exceeded, fall back to use center_crop instead. Examples: >>> from mindspore.dataset.vision import Inter @@ -1346,9 +832,20 @@ class RandomCropDecodeResize(ImageTensorOperation): ... input_columns=["image"]) """ + ''' + 随机裁剪并在输入图像上进行缩放,并使用随机裁剪和缩放操作对图像进行随机化。 + ''' + @check_random_resize_crop def __init__(self, size, scale=(0.08, 1.0), ratio=(3. / 4., 4. / 3.), interpolation=Inter.BILINEAR, max_attempts=10): + ''' + :param size: 图像的大小,以像素为单位 + :param scale: 缩放比例,默认为0.08 + :param ratio: 缩放比例,默认为3. / 4. + :param interpolation: 缩放操作的插值方法,默认为BILINEAR + :param max_attempts: 最多尝试操作的次数,默认为10 + ''' if isinstance(size, int): size = (size, size) self.size = size @@ -1363,37 +860,40 @@ class RandomCropDecodeResize(ImageTensorOperation): self.max_attempts) def __call__(self, img): + ''' + 返回随机裁剪并缩放的图像 + :param img: 图像 + :return: 缩放后的图像 + ''' if not isinstance(img, np.ndarray): - raise TypeError( - "Input should be an encoded image in 1-D NumPy format, got {}.".format(type(img))) - if img.ndim != 1 or img.dtype.type is not np.uint8: + raise TypeError("Input should be an encoded image in 1-D NumPy format, got {}.".format(type(img))) + if img.ndim!= 1 or img.dtype.type is not np.uint8: raise TypeError("Input should be an encoded image with uint8 type in 1-D NumPy format, " + "got format:{}, dtype:{}.".format(type(img), img.dtype.type)) return super().__call__(img) - +# 在输入图像的随机位置进行裁剪并相应地调整边界框 class RandomCropWithBBox(ImageTensorOperation): """ Crop the input image at a random location and adjust bounding boxes accordingly. Args: - size (Union[int, Sequence[int]]): The output size of the cropped image. The size value(s) must be positive. + size (Union[int, sequence]): The output size of the cropped image. If size is an integer, a square crop of size (size, size) is returned. - If size is a sequence of length 2, an image of size (height, width) will be cropped. - padding (Union[int, Sequence[int]], optional): The number of pixels to pad the image - The padding value(s) must be non-nagetive (default=None). + If size is a sequence of length 2, it should be (height, width). + padding (Union[int, sequence], optional): The number of pixels to pad the image (default=None). If padding is not None, first pad image with padding values. If a single number is provided, pad all borders with this value. - If a tuple or lists of 2 values are provided, pad the (left and top) + If a tuple or list of 2 values are provided, pad the (left and top) with the first value and (right and bottom) with the second value. If 4 values are provided as a list or tuple, pad the left, top, right and bottom respectively. pad_if_needed (bool, optional): Pad the image if either side is smaller than the given output size (default=False). - fill_value (Union[int, tuple[int]], optional): The pixel intensity of the borders, only valid for + fill_value (Union[int, tuple], optional): The pixel intensity of the borders, only valid for padding_mode Border.CONSTANT. If it is a 3-tuple, it is used to fill R, G, B channels respectively. If it is an integer, it is used for all RGB channels. The fill_value values must be in range [0, 255] (default=0). - padding_mode (Border, optional): The method of padding (default=Border.CONSTANT). It can be any of + padding_mode (Border mode, optional): The method of padding (default=Border.CONSTANT). It can be any of [Border.CONSTANT, Border.EDGE, Border.REFLECT, Border.SYMMETRIC]. - Border.CONSTANT, means it fills the border with constant values. @@ -1406,20 +906,6 @@ class RandomCropWithBBox(ImageTensorOperation): - Border.SYMMETRIC, means it reflects the values on the edge repeating the last value of edge. - Raises: - TypeError: If `size` is not of type int or Sequence[int]. - TypeError: If `padding` is not of type int or Sequence[int]. - TypeError: If `pad_if_needed` is not of type boolean. - TypeError: If `fill_value` is not of type int or tuple[int]. - TypeError: If `padding_mode` is not of type :class:`mindspore.dataset.vision.Border`. - ValueError: If `size` is not positive. - ValueError: If `padding` is negative. - ValueError: If `fill_value` is not in range [0, 255]. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - Examples: >>> decode_op = c_vision.Decode() >>> random_crop_with_bbox_op = c_vision.RandomCropWithBBox([512, 512], [200, 200, 200, 200]) @@ -1428,16 +914,33 @@ class RandomCropWithBBox(ImageTensorOperation): ... input_columns=["image"]) """ + ''' + 随机裁剪并缩放图像,同时保持bbox不变 + ''' + + def __init__(self, size, scale=None, ratio=None, interpolation=Border.CONSTANT, max_attempts=10): + ''' + 参数: + size:裁剪的大小,int类型 + padding:裁剪的边界,list或者tuple类型,默认为None + pad_if_needed:是否裁剪,bool类型,默认为False + fill_value:填充值,list或者tuple类型,默认为0 + padding_mode:填充模式,Border类型,默认为Border.CONSTANT + ''' @check_random_crop def __init__(self, size, padding=None, pad_if_needed=False, fill_value=0, padding_mode=Border.CONSTANT): if isinstance(size, int): + # 如果size是int类型,则将其转换为元组 size = (size, size) if padding is None: + # 如果padding为None,则将其设置为(0, 0, 0, 0) padding = (0, 0, 0, 0) else: + # 否则,将padding解析为元组 padding = parse_padding(padding) if isinstance(fill_value, int): + # 如果fill_value是int类型,则将其转换为元组 fill_value = tuple([fill_value] * 3) self.size = size @@ -1447,65 +950,28 @@ class RandomCropWithBBox(ImageTensorOperation): self.padding_mode = padding_mode.value def parse(self): + # 解析RandomCropWithBBox类 + # 返回RandomCropWithBBoxOperation类 border_type = DE_C_BORDER_TYPE[self.padding_mode] return cde.RandomCropWithBBoxOperation(self.size, self.padding, self.pad_if_needed, self.fill_value, border_type) - - -class RandomEqualize(ImageTensorOperation): - """ - Apply histogram equalization on the input image with a given probability. - - Args: - prob (float, optional): Probability of the image being equalized, which - must be in range of [0, 1] (default=0.5). - - Raises: - TypeError: If `prob` is not of type float. - ValueError: If `prob` is not in range [0, 1]. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> transforms_list = [c_vision.Decode(), c_vision.RandomEqualize(0.5)] - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns=["image"]) - """ - - @check_prob - def __init__(self, prob=0.5): - self.prob = prob - - def parse(self): - return cde.RandomEqualizeOperation(self.prob) - - +# 对输入图像按给定的概率进行水平随机翻转 class RandomHorizontalFlip(ImageTensorOperation): """ Randomly flip the input image horizontally with a given probability. - Note: - This operation supports running on Ascend or GPU platforms by Offload. - Args: - prob (float, optional): Probability of the image being flipped, which must be in range of [0, 1] (default=0.5). - - Raises: - TypeError: If `prob` is not of type float. - ValueError: If `prob` is not in range [0, 1]. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` ``Ascend`` ``GPU`` + prob (float, optional): Probability of the image being flipped (default=0.5). Examples: >>> transforms_list = [c_vision.Decode(), c_vision.RandomHorizontalFlip(0.75)] >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, ... input_columns=["image"]) """ - + ''' + 参数: + prob:翻转的概率 + ''' @check_prob def __init__(self, prob=0.5): self.prob = prob @@ -1513,21 +979,13 @@ class RandomHorizontalFlip(ImageTensorOperation): def parse(self): return cde.RandomHorizontalFlipOperation(self.prob) - +# 对输入图像按给定的概率进行水平随机翻转并相应地调整边界框 class RandomHorizontalFlipWithBBox(ImageTensorOperation): """ - Flip the input image horizontally randomly with a given probability and adjust bounding boxes accordingly. + Flip the input image horizontally, randomly with a given probability and adjust bounding boxes accordingly. Args: - prob (float, optional): Probability of the image being flipped, which must be in range of [0, 1] (default=0.5). - - Raises: - TypeError: If `prob` is not of type float. - ValueError: If `prob` is not in range [0, 1]. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` + prob (float, optional): Probability of the image being flipped (default=0.5). Examples: >>> transforms_list = [c_vision.Decode(), c_vision.RandomHorizontalFlipWithBBox(0.70)] @@ -1535,6 +993,10 @@ class RandomHorizontalFlipWithBBox(ImageTensorOperation): ... input_columns=["image"]) """ + ''' + 参数: + prob:翻转的概率 + ''' @check_prob def __init__(self, prob=0.5): self.prob = prob @@ -1542,119 +1004,54 @@ class RandomHorizontalFlipWithBBox(ImageTensorOperation): def parse(self): return cde.RandomHorizontalFlipWithBBoxOperation(self.prob) - -class RandomInvert(ImageTensorOperation): - """ - Randomly invert the colors of image with a given probability. - - Args: - prob (float, optional): Probability of the image being inverted, which must be in range of [0, 1] (default=0.5). - - Raises: - TypeError: If `prob` is not of type float. - ValueError: If `prob` is not in range [0, 1]. - RuntimeError: If given tensor shape is not . - - Supported Platforms: - ``CPU`` - - Examples: - >>> transforms_list = [c_vision.Decode(), c_vision.RandomInvert(0.5)] - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns=["image"]) - """ - - @check_prob - def __init__(self, prob=0.5): - self.prob = prob - - def parse(self): - return cde.RandomInvertOperation(self.prob) - - -class RandomLighting(ImageTensorOperation): - """ - Add AlexNet-style PCA-based noise to an image. The eigenvalue and eigenvectors for Alexnet's PCA noise is - calculated from the imagenet dataset. - - Args: - alpha (float, optional): Intensity of the image, which must be non-negative (default=0.05). - - Raises: - TypeError: If `alpha` is not of type float. - ValueError: If `alpha` is negative. - RuntimeError: If given tensor shape is not . - - Supported Platforms: - ``CPU`` - - Examples: - >>> transforms_list = [c_vision.Decode(), c_vision.RandomLighting(0.1)] - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns=["image"]) - """ - - @check_alpha - def __init__(self, alpha=0.05): - self.alpha = alpha - - def parse(self): - return cde.RandomLightingOperation(self.alpha) - - +# 随机减少图像的颜色通道的比特位数,使图像变得高对比度和颜色鲜艳 class RandomPosterize(ImageTensorOperation): """ - Reduce the number of bits for each color channel to posterize the input image randomly with a given probability. + Reduce the number of bits for each color channel. Args: - bits (Union[int, Sequence[int]], optional): Range of random posterize to compress image. + bits (sequence or int, optional): Range of random posterize to compress image. Bits values must be in range of [1,8], and include at least one integer value in the given range. It must be in (min, max) or integer format. If min=max, then it is a single fixed magnitude operation (default=(8, 8)). - Raises: - TypeError: If `bits` is not of type int or sequence of int. - ValueError: If `bits` is not in range [1, 8]. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - Examples: >>> transforms_list = [c_vision.Decode(), c_vision.RandomPosterize((6, 8))] >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, ... input_columns=["image"]) """ + ''' + 参数: + bits:随机位数压缩的范围 + ''' @check_posterize def __init__(self, bits=(8, 8)): self.bits = bits def parse(self): bits = self.bits + # 如果bits参数是int类型,则将其转换为元组 if isinstance(bits, int): bits = (bits, bits) return cde.RandomPosterizeOperation(bits) - +# 对输入图像进行随机裁剪,并使用指定的 mindspore.dataset.vision.Inter 插值方式去调整为指定的尺寸大小 class RandomResizedCrop(ImageTensorOperation): """ - This operator will crop the input image randomly, and resize the cropped image using a selected interpolation mode. - - Note: - If the input image is more than one, then make sure that the image size is the same. + Crop the input image to a random size and aspect ratio. Args: - size (Union[int, Sequence[int]]): The output size of the resized image. The size value(s) must be positive. - If size is an integer, a square of size (size, size) will be cropped with this value. - If size is a sequence of length 2, an image of size (height, width) will be cropped. - scale (Union[list, tuple], optional): Range [min, max) of respective size of the original - size to be cropped, which must be non-negative (default=(0.08, 1.0)). - ratio (Union[list, tuple], optional): Range [min, max) of aspect ratio to be - cropped, which must be non-negative (default=(3. / 4., 4. / 3.)). - interpolation (Inter, optional): Method of interpolation (default=Inter.BILINEAR). - It can be any of [Inter.BILINEAR, Inter.NEAREST, Inter.BICUBIC, Inter.AREA, Inter.PILCUBIC]. + size (Union[int, sequence]): The size of the output image. + If size is an integer, a square crop of size (size, size) is returned. + If size is a sequence of length 2, it should be (height, width). + scale (tuple, optional): Range [min, max) of respective size of the original + size to be cropped (default=(0.08, 1.0)). + ratio (tuple, optional): Range [min, max) of aspect ratio to be cropped + (default=(3. / 4., 4. / 3.)). + interpolation (Inter mode, optional): Image interpolation mode (default=Inter.BILINEAR). + It can be any of [Inter.BILINEAR, Inter.NEAREST, Inter.BICUBIC]. - Inter.BILINEAR, means interpolation method is bilinear interpolation. @@ -1664,27 +1061,11 @@ class RandomResizedCrop(ImageTensorOperation): - Inter.AREA, means interpolation method is pixel area interpolation. - - Inter.PILCUBIC, means interpolation method is bicubic interpolation like implemented in pillow, input - should be in 3 channels format. + - Inter.PILCUBIC, means interpolation method is bicubic interpolation like implemented in pillow. max_attempts (int, optional): The maximum number of attempts to propose a valid crop_area (default=10). If exceeded, fall back to use center_crop instead. - Raises: - TypeError: If `size` is not of type int or Sequence[int]. - TypeError: If `scale` is not of type tuple or list. - TypeError: If `ratio` is not of type tuple or list. - TypeError: If `interpolation` is not of type :class:`mindspore.dataset.vision.Inter`. - TypeError: If `max_attempts` is not of type int. - ValueError: If `size` is not positive. - ValueError: If `scale` is negative. - ValueError: If `ratio` is negative. - ValueError: If `max_attempts` is not positive. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - Examples: >>> from mindspore.dataset.vision import Inter >>> decode_op = c_vision.Decode() @@ -1695,9 +1076,24 @@ class RandomResizedCrop(ImageTensorOperation): ... input_columns=["image"]) """ + ''' + 参数: + size:图像的输出尺寸大小 + scale:裁剪子图的尺寸大小相对原图比例的随机选取范围 + ratio:裁剪子图的宽高比的随机选取范围 + interpolation:插值方式 + Inter.BILINEAR,双线性插值。 + Inter.NEAREST,最近邻插值。 + Inter.BICUBIC,双三次插值。 + Inter.AREA,像素区域插值。 + Inter.PILCUBIC,Pillow库中实现的双三次插值,输入应为3通道格式。 + max_attempts:生成随机裁剪位置的最大尝试次数,超过该次数时将使用中心裁剪 + ''' + @check_random_resize_crop def __init__(self, size, scale=(0.08, 1.0), ratio=(3. / 4., 4. / 3.), interpolation=Inter.BILINEAR, max_attempts=10): + # 如果size是整型,则转化为元组 if isinstance(size, int): size = (size, size) self.size = size @@ -1710,21 +1106,21 @@ class RandomResizedCrop(ImageTensorOperation): return cde.RandomResizedCropOperation(self.size, self.scale, self.ratio, DE_C_INTER_MODE[self.interpolation], self.max_attempts) - +# 对输入图像进行随机裁剪且随机调整纵横比,并将处理后的图像调整为指定的尺寸大小,并相应地调整边界框 class RandomResizedCropWithBBox(ImageTensorOperation): """ Crop the input image to a random size and aspect ratio and adjust bounding boxes accordingly. Args: - size (Union[int, Sequence[int]]): The size of the output image. The size value(s) must be positive. - If size is an integer, a square of size (size, size) will be cropped with this value. - If size is a sequence of length 2, an image of size (height, width) will be cropped. - scale (Union[list, tuple], optional): Range (min, max) of respective size of the original - size to be cropped, which must be non-negative (default=(0.08, 1.0)). - ratio (Union[list, tuple], optional): Range (min, max) of aspect ratio to be - cropped, which must be non-negative (default=(3. / 4., 4. / 3.)). - interpolation (Inter mode, optional): Method of interpolation (default=Inter.BILINEAR). - It can be any of [Inter.BILINEAR, Inter.NEAREST, Inter.BICUBIC] . + size (Union[int, sequence]): The size of the output image. + If size is an integer, a square crop of size (size, size) is returned. + If size is a sequence of length 2, it should be (height, width). + scale (tuple, optional): Range (min, max) of respective size of the original + size to be cropped (default=(0.08, 1.0)). + ratio (tuple, optional): Range (min, max) of aspect ratio to be cropped + (default=(3. / 4., 4. / 3.)). + interpolation (Inter mode, optional): Image interpolation mode (default=Inter.BILINEAR). + It can be any of [Inter.BILINEAR, Inter.NEAREST, Inter.BICUBIC]. - Inter.BILINEAR, means interpolation method is bilinear interpolation. @@ -1735,21 +1131,6 @@ class RandomResizedCropWithBBox(ImageTensorOperation): max_attempts (int, optional): The maximum number of attempts to propose a valid crop area (default=10). If exceeded, fall back to use center crop instead. - Raises: - TypeError: If `size` is not of type int or Sequence[int]. - TypeError: If `scale` is not of type tuple or list. - TypeError: If `ratio` is not of type tuple or list. - TypeError: If `interpolation` is not of type :class:`mindspore.dataset.vision.Inter`. - TypeError: If `max_attempts` is not of type int. - ValueError: If `size` is not positive. - ValueError: If `scale` is negative. - ValueError: If `ratio` is negative. - ValueError: If `max_attempts` is not positive. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - Examples: >>> from mindspore.dataset.vision import Inter >>> decode_op = c_vision.Decode() @@ -1759,9 +1140,21 @@ class RandomResizedCropWithBBox(ImageTensorOperation): ... input_columns=["image"]) """ + + ''' + 随机裁剪图像,以指定的尺寸和比例进行裁剪 + ''' + @check_random_resize_crop def __init__(self, size, scale=(0.08, 1.0), ratio=(3. / 4., 4. / 3.), interpolation=Inter.BILINEAR, max_attempts=10): + ''' + :param size: 裁剪的图像的尺寸 + :param scale: 裁剪的图像的大小比例 + :param ratio: 裁剪的图像的比例 + :param interpolation: 裁剪的图像的缩放模式 + :param max_attempts: 最大尝试次数 + ''' if isinstance(size, int): size = (size, size) self.size = size @@ -1774,23 +1167,16 @@ class RandomResizedCropWithBBox(ImageTensorOperation): return cde.RandomResizedCropWithBBoxOperation(self.size, self.scale, self.ratio, DE_C_INTER_MODE[self.interpolation], self.max_attempts) - +# 对输入图像使用随机选择的 mindspore.dataset.vision.Inter 插值方式去调整它的尺寸大小 class RandomResize(ImageTensorOperation): """ - Resize the input image using a randomly selected interpolation mode. + Tensor operation to resize the input image using a randomly selected interpolation mode. Args: - size (Union[int, Sequence[int]]): The output size of the resized image. The size value(s) must be positive. - If size is an integer, a square of size (size, size) will be cropped with this value. - If size is a sequence of length 2, an image of size (height, width) will be cropped. - - Raises: - TypeError: If `size` is not of type int or Sequence[int]. - ValueError: If `size` is not positive. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` + size (Union[int, sequence]): The output size of the resized image. + If size is an integer, smaller edge of the image will be resized to this value with + the same image aspect ratio. + If size is a sequence of length 2, it should be (height, width). Examples: >>> # randomly resize image, keeping aspect ratio @@ -1804,33 +1190,32 @@ class RandomResize(ImageTensorOperation): """ @check_resize + def __init__(self, size): + ''' + 参数: + size:重新设置图片的尺寸 + ''' self.size = size def parse(self): size = self.size + # 如果size是整型,则转化成元组 if isinstance(size, int): size = (size,) return cde.RandomResizeOperation(size) - +# 对输入图像使用随机选择的 mindspore.dataset.vision.Inter 插值方式去调整它的尺寸大小,并相应地调整边界框的尺寸大小 class RandomResizeWithBBox(ImageTensorOperation): """ Tensor operation to resize the input image using a randomly selected interpolation mode and adjust bounding boxes accordingly. Args: - size (Union[int, Sequence[int]]): The output size of the resized image. The size value(s) must be positive. - If size is an integer, a square of size (size, size) will be cropped with this value. - If size is a sequence of length 2, an image of size (height, width) will be cropped. - - Raises: - TypeError: If `size` is not of type int or Sequence[int]. - ValueError: If `size` is not positive. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` + size (Union[int, sequence]): The output size of the resized image. + If size is an integer, smaller edge of the image will be resized to this value with + the same image aspect ratio. + If size is a sequence of length 2, it should be (height, width). Examples: >>> # randomly resize image with bounding boxes, keeping aspect ratio @@ -1845,24 +1230,30 @@ class RandomResizeWithBBox(ImageTensorOperation): @check_resize def __init__(self, size): + ''' + 参数: + size:重新设置图片的尺寸 + ''' self.size = size def parse(self): size = self.size + # 如果size为整型,则将size转换为元组 if isinstance(size, int): size = (size,) return cde.RandomResizeWithBBoxOperation(size) - +# 在指定的角度范围内,随机旋转输入图像 class RandomRotation(ImageTensorOperation): """ - Rotate the input image randomly within a specified range of degrees. + Rotate the input image by a random angle. Args: - degrees (Union[int, float, sequence]): Range of random rotation degrees. - If `degrees` is a number, the range will be converted to (-degrees, degrees). - If `degrees` is a sequence, it should be (min, max). - resample (Inter, optional): An optional resampling filter (default=Inter.NEAREST). + degrees (Union[int, float, sequence): Range of random rotation degrees. + If degrees is a number, the range will be converted to (-degrees, degrees). + If degrees is a sequence, it should be (min, max). + resample (Inter mode, optional): An optional resampling filter (default=Inter.NEAREST). + If omitted, or if the image has mode "1" or "P", it is set to be Inter.NEAREST. It can be any of [Inter.BILINEAR, Inter.NEAREST, Inter.BICUBIC]. - Inter.BILINEAR, means resample method is bilinear interpolation. @@ -1877,23 +1268,11 @@ class RandomRotation(ImageTensorOperation): Note that the expand flag assumes rotation around the center and no translation. center (tuple, optional): Optional center of rotation (a 2-tuple) (default=None). Origin is the top left corner. None sets to the center of the image. - fill_value (Union[int, tuple[int]], optional): Optional fill color for the area outside the rotated image. + fill_value (Union[int, tuple], optional): Optional fill color for the area outside the rotated image. If it is a 3-tuple, it is used to fill R, G, B channels respectively. If it is an integer, it is used for all RGB channels. The fill_value values must be in range [0, 255] (default=0). - Raises: - TypeError: If `degrees` is not of type int, float or sequence. - TypeError: If `resample` is not of type :class:`mindspore.dataset.vision.Inter`. - TypeError: If `expand` is not of type boolean. - TypeError: If `center` is not of type tuple. - TypeError: If `fill_value` is not of type int or tuple[int]. - ValueError: If `fill_value` is not in range [0, 255]. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - Examples: >>> from mindspore.dataset.vision import Inter >>> transforms_list = [c_vision.Decode(), @@ -1903,23 +1282,25 @@ class RandomRotation(ImageTensorOperation): >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, ... input_columns=["image"]) """ - + @check_random_rotation def __init__(self, degrees, resample=Inter.NEAREST, expand=False, center=None, fill_value=0): - if isinstance(degrees, (int, float)): + if isinstance(degrees, numbers.Number): + # 如果degrees是数字,则将其转换为360度 degrees = degrees % 360 - degrees = [-degrees, degrees] - elif isinstance(degrees, (list, tuple)): - if degrees[1] - degrees[0] >= 360: - degrees = [-180, 180] - else: - degrees = [degrees[0] % 360, degrees[1] % 360] - if degrees[0] > degrees[1]: - degrees[1] += 360 - if center is None: - center = () - if isinstance(fill_value, int): - fill_value = tuple([fill_value] * 3) + if isinstance(degrees, (list, tuple)): + # 如果degrees是列表,则将其转换为[0, 360) + degrees = [degrees[0] % 360, degrees[1] % 360] + # 如果degrees[0]大于degrees[1],则将degrees[1]加上360 + if degrees[0] > degrees[1]: + degrees[1] += 360 + ''' + 参数:degrees:旋转角度,可以是数字或者列表,默认为0 + resample:重采样模式,可选的为Inter.NEAREST,Inter.BILINEAR,Inter.BICUBIC,Inter.LANCZOS,Inter.ANTIALIAS,默认为Inter.NEAREST + expand:是否增强图像,默认为False + center:坐标中心,默认为None + fill_value:填充值,默认为0 + ''' self.degrees = degrees self.resample = resample self.expand = expand @@ -1927,74 +1308,62 @@ class RandomRotation(ImageTensorOperation): self.fill_value = fill_value def parse(self): - return cde.RandomRotationOperation(self.degrees, DE_C_INTER_MODE[self.resample], self.expand, self.center, - self.fill_value) - + # 解析RandomRotation类 + # pylint false positive + # pylint: disable=E1130 + degrees = (-self.degrees, self.degrees) if isinstance(self.degrees, numbers.Number) else self.degrees + interpolation = DE_C_INTER_MODE[self.resample] + expand = self.expand + center = (-1, -1) if self.center is None else self.center + fill_value = tuple([self.fill_value] * 3) if isinstance(self.fill_value, int) else self.fill_value + return cde.RandomRotationOperation(degrees, interpolation, expand, center, fill_value) +# 从策略列表中随机选择一个子策略以应用于输入图像 class RandomSelectSubpolicy(ImageTensorOperation): - """ - Choose a random sub-policy from a policy list to be applied on the input image. - Args: - policy (list[list[tuple[TensorOperation, float]]]): List of sub-policies to choose from. - A sub-policy is a list of tuple[operation, prob], where operation is a data processing operation and prob - is the probability that this operation will be applied, and the prob values must be in range [0, 1]. - Once a sub-policy is selected, each operation within the sub-policy with be applied in sequence according - to its probability. - - Raises: - TypeError: If `policy` contains invalid data processing operations. - - Supported Platforms: - ``CPU`` - - Examples: - >>> policy = [[(c_vision.RandomRotation((45, 45)), 0.5), - ... (c_vision.RandomVerticalFlip(), 1), - ... (c_vision.RandomColorAdjust(), 0.8)], - ... [(c_vision.RandomRotation((90, 90)), 1), - ... (c_vision.RandomColorAdjust(), 0.2)]] - >>> image_folder_dataset = image_folder_dataset.map(operations=c_vision.RandomSelectSubpolicy(policy), - ... input_columns=["image"]) - """ @check_random_select_subpolicy_op def __init__(self, policy): + ''' + 参数: + police:可供选择的子策略列表 + ''' self.policy = policy def parse(self): + """ + Return a C++ representation of the operator for execution + """ policy = [] for list_one in self.policy: policy_one = [] for list_two in list_one: + # 如果list_two中有两个元素,且list_two[0]有parse属性 if list_two[0] and getattr(list_two[0], 'parse', None): + # 将list_two[0].parse()和list_two[1]添加到policy_one中 policy_one.append((list_two[0].parse(), list_two[1])) else: + # 否则,将list_two[0]和list_two[1]添加到policy_one中 policy_one.append((list_two[0], list_two[1])) + # 将policy_one添加到policy中 policy.append(policy_one) + # 返回RandomSelectSubpolicyOperation对象 return cde.RandomSelectSubpolicyOperation(policy) - +# 在固定或随机的范围调整输入图像的锐度 class RandomSharpness(ImageTensorOperation): """ Adjust the sharpness of the input image by a fixed or random degree. Degree of 0.0 gives a blurred image, degree of 1.0 gives the original image, and degree of 2.0 gives a sharpened image. - Note: - This operation supports running on Ascend or GPU platforms by Offload. - Args: - degrees (Union[list, tuple], optional): Range of random sharpness adjustment degrees, - which must be non-negative. It should be in (min, max) format. If min=max, then - it is a single fixed magnitude operation (default = (0.1, 1.9)). + degrees (Union[list, tuple], optional): Range of random sharpness adjustment degrees. It should be in + (min, max) format. If min=max, then it is a single fixed magnitude operation (default = (0.1, 1.9)). Raises: - TypeError : If `degrees` is not of type list or tuple. - ValueError: If `degrees` is negative. - ValueError: If `degrees` is in (max, min) format instead of (min, max). - - Supported Platforms: - ``CPU`` ``Ascend`` ``GPU`` + TypeError : If degrees is not a list or tuple. + ValueError: If degrees is negative. + ValueError: If degrees is in (max, min) format instead of (min, max). Examples: >>> transforms_list = [c_vision.Decode(), c_vision.RandomSharpness(degrees=(0.2, 1.9))] @@ -2002,6 +1371,14 @@ class RandomSharpness(ImageTensorOperation): ... input_columns=["image"]) """ + + ''' + 对图像进行锐化操作 + + 参数: + degrees:锐化角度,默认为0.1-1.9之间的随机数 + ''' + @check_positive_degrees def __init__(self, degrees=(0.1, 1.9)): self.degrees = degrees @@ -2009,57 +1386,43 @@ class RandomSharpness(ImageTensorOperation): def parse(self): return cde.RandomSharpnessOperation(self.degrees) - +# 从给定阈值范围内随机选择一个子范围,对位于给定子范围内的像素,将其像素值设置为(255 - 原本像素值) class RandomSolarize(ImageTensorOperation): """ - Randomly selects a subrange within the specified threshold range and sets the pixel value within - the subrange to (255 - pixel). + Randomly invert the pixel values of input image within given range. Args: threshold (tuple, optional): Range of random solarize threshold (default=(0, 255)). Threshold values should always be in (min, max) format, - where min and max are integers in the range [0, 255], and min <= max. + where min <= max, min and max are integers in the range (0, 255). If min=max, then invert all pixel values above min(max). - Raises: - TypeError : If `threshold` is not of type tuple. - ValueError: If `threshold` is not in range of [0, 255]. - - Supported Platforms: - ``CPU`` - Examples: >>> transforms_list = [c_vision.Decode(), c_vision.RandomSolarize(threshold=(10,100))] >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, ... input_columns=["image"]) """ + @check_random_solarize def __init__(self, threshold=(0, 255)): + ''' + 参数: + threshold:阈值,可以是一个区间,也可以是一个数字 + ''' self.threshold = threshold def parse(self): return cde.RandomSolarizeOperation(self.threshold) - +# 以给定的概率对输入图像在垂直方向进行随机翻转 class RandomVerticalFlip(ImageTensorOperation): """ Randomly flip the input image vertically with a given probability. - Note: - This operation supports running on Ascend or GPU platforms by Offload. - Args: prob (float, optional): Probability of the image being flipped (default=0.5). - Raises: - TypeError: If `prob` is not of type float. - ValueError: If `prob` is not in range [0, 1]. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` ``Ascend`` ``GPU`` - Examples: >>> transforms_list = [c_vision.Decode(), c_vision.RandomVerticalFlip(0.25)] >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, @@ -2068,85 +1431,85 @@ class RandomVerticalFlip(ImageTensorOperation): @check_prob def __init__(self, prob=0.5): + ''' + 参数: + prob:对图片翻转的概率 + ''' self.prob = prob def parse(self): return cde.RandomVerticalFlipOperation(self.prob) - +# 以给定的概率对输入图像和边界框在垂直方向进行随机翻转 class RandomVerticalFlipWithBBox(ImageTensorOperation): """ - Flip the input image vertically, randomly with a given probability and adjust bounding boxes accordingly. + Randomly flip the input image vertically with a given probability. Args: prob (float, optional): Probability of the image being flipped (default=0.5). - Raises: - TypeError: If `prob` is not of type float. - ValueError: If `prob` is not in range [0, 1]. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - Examples: - >>> transforms_list = [c_vision.Decode(), c_vision.RandomVerticalFlipWithBBox(0.20)] + >>> transforms_list = [c_vision.Decode(), c_vision.RandomVerticalFlipWithBBox(0.25)] >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, ... input_columns=["image"]) """ + @check_prob def __init__(self, prob=0.5): + ''' + 参数: + prob:对图片翻转的概率 + ''' self.prob = prob def parse(self): return cde.RandomVerticalFlipWithBBoxOperation(self.prob) +# 基于给定的缩放和平移因子调整图像的像素大小 class Rescale(ImageTensorOperation): """ - Rescale the input image with the given rescale and shift. This operator will rescale the input image - with: output = image * rescale + shift. - - Note: - This operation supports running on Ascend or GPU platforms by Offload. + Tensor operation to rescale the input image. Args: rescale (float): Rescale factor. shift (float): Shift factor. - Raises: - TypeError: If `rescale` is not of type float. - TypeError: If `shift` is not of type float. - - Supported Platforms: - ``CPU`` ``Ascend`` ``GPU`` - Examples: >>> transforms_list = [c_vision.Decode(), c_vision.Rescale(1.0 / 255.0, -1.0)] >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, ... input_columns=["image"]) """ + ''' + 重新缩放图像 + ''' @check_rescale def __init__(self, rescale, shift): + ''' + 重新缩放图像 + :param rescale: 重新缩放因子 + :param shift: 偏移量 + ''' self.rescale = rescale self.shift = shift def parse(self): return cde.RescaleOperation(self.rescale, self.shift) - +# 对输入图像使用给定的 mindspore.dataset.vision.Inter 插值方式去调整为给定的尺寸大小 class Resize(ImageTensorOperation): """ - Resize the input image to the given size with a given interpolation mode. + Resize the input image to the given size. Args: - size (Union[int, Sequence[int]]): The output size of the resized image. The size value(s) must be positive. - If size is an integer, a square of size (size, size) will be cropped with this value. - If size is a sequence of length 2, an image of size (height, width) will be cropped. - interpolation (Inter, optional): Image interpolation mode (default=Inter.LINEAR). - It can be any of [Inter.LINEAR, Inter.NEAREST, Inter.BICUBIC, Inter.AREA, Inter.PILCUBIC]. + size (Union[int, sequence]): The output size of the resized image. + If size is an integer, the smaller edge of the image will be resized to this value with + the same image aspect ratio. + If size is a sequence of length 2, it should be (height, width). + interpolation (Inter mode, optional): Image interpolation mode (default=Inter.LINEAR). + It can be any of [Inter.LINEAR, Inter.NEAREST, Inter.BICUBIC]. - Inter.LINEAR, means interpolation method is bilinear interpolation. @@ -2156,17 +1519,7 @@ class Resize(ImageTensorOperation): - Inter.AREA, means interpolation method is pixel area interpolation. - - Inter.PILCUBIC, means interpolation method is bicubic interpolation like implemented in pillow, input - should be in 3 channels format. - - Raises: - TypeError: If `size` is not of type int or Sequence[int]. - TypeError: If `interpolation` is not of type :class:`mindspore.dataset.vision.Inter`. - ValueError: If `size` is not positive. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` + - Inter.PILCUBIC, means interpolation method is bicubic interpolation like implemented in pillow. Examples: >>> from mindspore.dataset.vision import Inter @@ -2179,7 +1532,13 @@ class Resize(ImageTensorOperation): @check_resize_interpolation def __init__(self, size, interpolation=Inter.LINEAR): + ''' + 参数: + size:要调整的尺寸,可以是int或者tuple + interpolation:调整的插值方式,可以是Inter.LINEAR或者Inter.NEAREST + ''' if isinstance(size, int): + # 如果size是整型,则转换为元组 size = (size,) self.size = size self.interpolation = interpolation @@ -2187,17 +1546,17 @@ class Resize(ImageTensorOperation): def parse(self): return cde.ResizeOperation(self.size, DE_C_INTER_MODE[self.interpolation]) - +# 将输入图像调整为给定的尺寸大小并相应地调整边界框的大小 class ResizeWithBBox(ImageTensorOperation): """ Resize the input image to the given size and adjust bounding boxes accordingly. Args: - size (Union[int, Sequence[int]]): The output size of the resized image. + size (Union[int, sequence]): The output size of the resized image. If size is an integer, smaller edge of the image will be resized to this value with the same image aspect ratio. If size is a sequence of length 2, it should be (height, width). - interpolation (Inter, optional): Image interpolation mode (default=Inter.LINEAR). + interpolation (Inter mode, optional): Image interpolation mode (default=Inter.LINEAR). It can be any of [Inter.LINEAR, Inter.NEAREST, Inter.BICUBIC]. - Inter.LINEAR, means interpolation method is bilinear interpolation. @@ -2206,15 +1565,6 @@ class ResizeWithBBox(ImageTensorOperation): - Inter.BICUBIC, means interpolation method is bicubic interpolation. - Raises: - TypeError: If `size` is not of type int or Sequence[int]. - TypeError: If `interpolation` is not of type :class:`mindspore.dataset.vision.Inter`. - ValueError: If `size` is not positive. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - Examples: >>> from mindspore.dataset.vision import Inter >>> decode_op = c_vision.Decode() @@ -2224,8 +1574,17 @@ class ResizeWithBBox(ImageTensorOperation): ... input_columns=["image"]) """ + + ''' + 对图像进行缩放,使其具有指定的尺寸和插值方法 + ''' + @check_resize_interpolation def __init__(self, size, interpolation=Inter.LINEAR): + ''' + :param size: 缩放后的图像的尺寸 + :param interpolation: 缩放插值方法 + ''' self.size = size self.interpolation = interpolation @@ -2236,153 +1595,10 @@ class ResizeWithBBox(ImageTensorOperation): return cde.ResizeWithBBoxOperation(size, DE_C_INTER_MODE[self.interpolation]) -class RgbToBgr(ImageTensorOperation): - """ - Convert RGB image to BGR. - - Raises: - RuntimeError: If given tensor shape is not . - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.vision import Inter - >>> - >>> decode_op = c_vision.Decode() - >>> rgb2bgr_op = c_vision.RgbToBgr() - >>> transforms_list = [decode_op, rgb2bgr_op] - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns=["image"]) - """ - - def parse(self): - return cde.RgbToBgrOperation() - - -class Rotate(ImageTensorOperation): - """ - Rotate the input image by specified degrees. - - Args: - degrees (Union[int, float]): Rotation degrees. - - resample (Inter, optional): An optional resampling filter (default=Inter.NEAREST). - It can be any of [Inter.BILINEAR, Inter.NEAREST, Inter.BICUBIC]. - - - Inter.BILINEAR, means resample method is bilinear interpolation. - - Inter.NEAREST, means resample method is nearest-neighbor interpolation. - - Inter.BICUBIC, means resample method is bicubic interpolation. - - expand (bool, optional): Optional expansion flag (default=False). If set to True, expand the output - image to make it large enough to hold the entire rotated image. - If set to False or omitted, make the output image the same size as the input. - Note that the expand flag assumes rotation around the center and no translation. - center (tuple, optional): Optional center of rotation (a 2-tuple) (default=None). - Origin is the top left corner. None sets to the center of the image. - fill_value (Union[int, tuple[int]], optional): Optional fill color for the area outside the rotated image. - If it is a 3-tuple, it is used to fill R, G, B channels respectively. - If it is an integer, it is used for all RGB channels. - The fill_value values must be in range [0, 255] (default=0). - - Raises: - TypeError: If `degrees` is not of type int or float. - TypeError: If `resample` is not of type :class:`mindspore.dataset.vision.Inter`. - TypeError: If `expand` is not of type bool. - TypeError: If `center` is not of type tuple. - TypeError: If `fill_value` is not of type int or tuple[int]. - ValueError: If `fill_value` is not in range [0, 255]. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.vision import Inter - >>> transforms_list = [c_vision.Decode(), - ... c_vision.Rotate(degrees=30.0, - ... resample=Inter.NEAREST, - ... expand=True)] - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns=["image"]) - """ - - @check_rotate - def __init__(self, degrees, resample=Inter.NEAREST, expand=False, center=None, fill_value=0): - if isinstance(degrees, (int, float)): - degrees = degrees % 360 - if center is None: - center = () - if isinstance(fill_value, int): - fill_value = tuple([fill_value] * 3) - self.degrees = degrees - self.resample = resample - self.expand = expand - self.center = center - self.fill_value = fill_value - - def parse(self): - return cde.RotateOperation(self.degrees, DE_C_INTER_MODE[self.resample], self.expand, self.center, - self.fill_value) - - -class SlicePatches(ImageTensorOperation): - """ - Slice Tensor to multiple patches in horizontal and vertical directions. - - The usage scenario is suitable to large height and width Tensor. The Tensor - will keep the same if set both num_height and num_width to 1. And the - number of output tensors is equal to num_height*num_width. - - Args: - num_height (int, optional): The number of patches in vertical direction, which must be positive (default=1). - num_width (int, optional): The number of patches in horizontal direction, which must be positive (default=1). - slice_mode (Inter, optional): A mode represents pad or drop (default=SliceMode.PAD). - It can be any of [SliceMode.PAD, SliceMode.DROP]. - fill_value (int, optional): The border width in number of pixels in - right and bottom direction if slice_mode is set to be SliceMode.PAD. - The fill_value must be in range [0, 255] (default=0). - - Raises: - TypeError: If `num_height` is not of type int. - TypeError: If `num_width` is not of type int. - TypeError: If `slice_mode` is not of type :class:`mindspore.dataset.vision.Inter`. - TypeError: If `fill_value` is not of type int. - ValueError: If `num_height` is not positive. - ValueError: If `num_width` is not positive. - ValueError: If `fill_value` is not in range [0, 255]. - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> # default padding mode - >>> decode_op = c_vision.Decode() - >>> num_h, num_w = (1, 4) - >>> slice_patches_op = c_vision.SlicePatches(num_h, num_w) - >>> transforms_list = [decode_op, slice_patches_op] - >>> cols = ['img' + str(x) for x in range(num_h*num_w)] - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns=["image"], - ... output_columns=cols, column_order=cols) - """ - - @check_slice_patches - def __init__(self, num_height=1, num_width=1, slice_mode=SliceMode.PAD, fill_value=0): - self.num_height = num_height - self.num_width = num_width - self.slice_mode = slice_mode - self.fill_value = fill_value - - def parse(self): - return cde.SlicePatchesOperation(self.num_height, self.num_width, - DE_C_SLICE_MODE[self.slice_mode], self.fill_value) - - class SoftDvppDecodeRandomCropResizeJpeg(ImageTensorOperation): """ - A combination of `Crop`, `Decode` and `Resize` using the simulation algorithm of Ascend series chip DVPP module. + Tensor operation to decode, random crop and resize JPEG image using the simulation algorithm of + Ascend series chip DVPP module. The usage scenario is consistent with SoftDvppDecodeResizeJpeg. The input image size should be in range [32*32, 8192*8192]. @@ -2390,57 +1606,55 @@ class SoftDvppDecodeRandomCropResizeJpeg(ImageTensorOperation): Only images with an even resolution can be output. The output of odd resolution is not supported. Args: - size (Union[int, Sequence[int]]): The size of the output image. The size value(s) must be positive. + size (Union[int, sequence]): The size of the output image. If size is an integer, a square crop of size (size, size) is returned. - If size is a sequence of length 2, an image of size (height, width) will be cropped. - scale (Union[list, tuple], optional): Range [min, max) of respective size of the - original size to be cropped, which must be non-negative (default=(0.08, 1.0)). - ratio (Union[list, tuple], optional): Range [min, max) of aspect ratio to be - cropped, which must be non-negative (default=(3. / 4., 4. / 3.)). + If size is a sequence of length 2, it should be (height, width). + scale (tuple, optional): Range [min, max) of respective size of the + original size to be cropped (default=(0.08, 1.0)). + ratio (tuple, optional): Range [min, max) of aspect ratio to be + cropped (default=(3. / 4., 4. / 3.)). max_attempts (int, optional): The maximum number of attempts to propose a valid crop_area (default=10). - If exceeded, fall back to use center_crop instead. The max_attempts value must be positive. - - Raises: - TypeError: If `size` is not of type int or Sequence[int]. - TypeError: If `scale` is not of type tuple or list. - TypeError: If `ratio` is not of type tuple or list. - TypeError: If `max_attempts` is not of type int. - ValueError: If `size` is not positive. - ValueError: If `scale` is negative. - ValueError: If `ratio` is negative. - ValueError: If `max_attempts` is not positive. - RuntimeError: If given tensor is not a 1D sequence. - - Supported Platforms: - ``CPU`` + If exceeded, fall back to use center_crop instead. Examples: >>> # decode, randomly crop and resize image, keeping aspect ratio - >>> transforms_list1 = [c_vision.SoftDvppDecodeRandomCropResizeJpeg(90)] + >>> transforms_list1 = [c_vision.Decode(), c_vision.SoftDvppDecodeRandomCropResizeJpeg(90)] >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list1, ... input_columns=["image"]) >>> # decode, randomly crop and resize to landscape style - >>> transforms_list2 = [c_vision.SoftDvppDecodeRandomCropResizeJpeg((80, 100))] + >>> transforms_list2 = [c_vision.Decode(), c_vision.SoftDvppDecodeRandomCropResizeJpeg((80, 100))] >>> image_folder_dataset_1 = image_folder_dataset_1.map(operations=transforms_list2, ... input_columns=["image"]) """ + # 定义一个类SoftDvppDecodeRandomCropResizeJpeg,用于解码随机裁剪和缩放JPEG图像 + + @check_soft_dvpp_decode_random_crop_resize_jpeg def __init__(self, size, scale=(0.08, 1.0), ratio=(3. / 4., 4. / 3.), max_attempts=10): if isinstance(size, int): size = (size, size) + ''' + 参数: + size:图像尺寸,可以是int或者元组(int,int) + scale:裁剪缩放比例,可以是一个浮点数或者元组(float,float) + ratio:裁剪比例,可以是一个浮点数或者元组(float,float) + max_attempts:最大尝试次数 + ''' self.size = size self.scale = scale self.ratio = ratio self.max_attempts = max_attempts def parse(self): + # 解析类,返回一个SoftDvppDecodeRandomCropResizeJpegOperation对象 return cde.SoftDvppDecodeRandomCropResizeJpegOperation(self.size, self.scale, self.ratio, self.max_attempts) class SoftDvppDecodeResizeJpeg(ImageTensorOperation): """ - Decode and resize JPEG image using the simulation algorithm of Ascend series chip DVPP module. + Tensor operation to decode and resize JPEG image using the simulation algorithm of + Ascend series chip DVPP module. It is recommended to use this algorithm in the following scenarios: When training, the DVPP of the Ascend chip is not used, @@ -2451,26 +1665,18 @@ class SoftDvppDecodeResizeJpeg(ImageTensorOperation): Only images with an even resolution can be output. The output of odd resolution is not supported. Args: - size (Union[int, Sequence[int]]): The output size of the resized image. The size value(s) must be positive. + size (Union[int, sequence]): The output size of the resized image. If size is an integer, smaller edge of the image will be resized to this value with the same image aspect ratio. - If size is a sequence of length 2, an image of size (height, width) will be cropped. - - Raises: - TypeError: If `size` is not of type int or Sequence[int]. - ValueError: If `size` is not positive. - RuntimeError: If given tensor is not a 1D sequence. - - Supported Platforms: - ``CPU`` + If size is a sequence of length 2, it should be (height, width). Examples: >>> # decode and resize image, keeping aspect ratio - >>> transforms_list1 = [c_vision.SoftDvppDecodeResizeJpeg(70)] + >>> transforms_list1 = [c_vision.Decode(), c_vision.SoftDvppDecodeResizeJpeg(70)] >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list1, ... input_columns=["image"]) >>> # decode and resize to portrait style - >>> transforms_list2 = [c_vision.SoftDvppDecodeResizeJpeg((80, 60))] + >>> transforms_list2 = [c_vision.Decode(), c_vision.SoftDvppDecodeResizeJpeg((80, 60))] >>> image_folder_dataset_1 = image_folder_dataset_1.map(operations=transforms_list2, ... input_columns=["image"]) """ @@ -2484,24 +1690,14 @@ class SoftDvppDecodeResizeJpeg(ImageTensorOperation): def parse(self): return cde.SoftDvppDecodeResizeJpegOperation(self.size) - +# 从指定序列中均匀采样一批数据处理操作,并按顺序随机执行,即采样出的操作也可能不被执行 class UniformAugment(ImageTensorOperation): """ - Perform randomly selected augmentation on input image. + Tensor operation to perform randomly selected augmentation. Args: - transforms (TensorOperation): C++ transformation operation to be applied on random selection - of bounding box regions of a given image (Python operations are not accepted). - num_ops (int, optional): Number of operations to be selected and applied, which must be positive (default=2). - - Raises: - TypeError: If `transform` is not an image processing operation - in :class:`mindspore.dataset.vision.c_transforms`. - TypeError: If `num_ops` is not of type int. - ValueError: If `num_ops` is not positive. - - Supported Platforms: - ``CPU`` + transforms: List of C++ operations (Python operations are not accepted). + num_ops (int, optional): Number of operations to be selected and applied (default=2). Examples: >>> import mindspore.dataset.vision.py_transforms as py_vision @@ -2511,14 +1707,20 @@ class UniformAugment(ImageTensorOperation): ... c_vision.RandomRotation(degrees=45)] >>> uni_aug_op = c_vision.UniformAugment(transforms=transforms_list, num_ops=2) >>> transforms_all = [c_vision.Decode(), c_vision.Resize(size=[224, 224]), - ... uni_aug_op] + ... uni_aug_op, py_vision.ToTensor()] >>> image_folder_dataset_1 = image_folder_dataset.map(operations=transforms_all, ... input_columns="image", ... num_parallel_workers=1) """ + @check_uniform_augment_cpp def __init__(self, transforms, num_ops=2): + ''' + 参数: + transforms:要添加的变换 + num_ops:要添加的变换数量 + ''' self.transforms = transforms self.num_ops = num_ops @@ -2530,23 +1732,3 @@ class UniformAugment(ImageTensorOperation): else: transforms.append(op) return cde.UniformAugOperation(transforms, self.num_ops) - - -class VerticalFlip(ImageTensorOperation): - """ - Flip the input image vertically. - - Raises: - RuntimeError: If given tensor shape is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> transforms_list = [c_vision.Decode(), c_vision.VerticalFlip()] - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns=["image"]) - """ - - def parse(self): - return cde.VerticalFlipOperation() diff --git a/mindspore/python/mindspore/dataset/vision/py_transforms.py b/mindspore/python/mindspore/dataset/vision/py_transforms.py index f6a43bd7822..473b715017b 100644 --- a/mindspore/python/mindspore/dataset/vision/py_transforms.py +++ b/mindspore/python/mindspore/dataset/vision/py_transforms.py @@ -49,539 +49,162 @@ DE_PY_INTER_MODE = {Inter.NEAREST: Image.NEAREST, class AdjustGamma(py_transforms.PyTensorOperation): - """ - Perform gamma correction on the input PIL Image. - - Args: - gamma (float): The gamma parameter in correction equation, must be non negative. - gain (float, optional): The constant multiplier. Default: 1.0. - - Raises: - TypeError: If `gain` is not of type float. - TypeError: If `gamma` is not of type float. - ValueError: If `gamma` is less than 0. - RuntimeError: If shape of the input image is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.AdjustGamma(gamma=10.0), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义AdjustGamma类,用于调整图像的gamma值 @check_adjust_gamma def __init__(self, gamma, gain=1.0): + # 初始化AdjustGamma类,参数为gamma值和gain值 self.gamma = gamma self.gain = gain self.random = False def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be gamma adjusted. - - Returns: - PIL.Image.Image, gamma adjusted image. - """ - + # 调用adjust_gamma函数,输入图像和gamma值和gain值 return util.adjust_gamma(img, self.gamma, self.gain) class AutoContrast(py_transforms.PyTensorOperation): - """ - Maximize (normalize) contrast of the input PIL Image. - - It will first calculate a histogram of the input image, remove `cutoff` percent of the - lightest and darkest pixels from the histogram, then remap the pixel value to [0, 255], - making the darkest pixel black and the lightest pixel white. - - Args: - cutoff (float, optional): Percent to cut off from the histogram on the low and - high ends, must be in range of [0.0, 50.0). Default: 0.0. - ignore (Union[int, Sequence[int]], optional): Background pixel value, which will be - directly remapped to white. Default: None, means no background. - - Raises: - TypeError: If `cutoff` is not of type float. - TypeError: If `ignore` is not of type int or sequence. - ValueError: If `cutoff` is not in range [0, 50.0). - ValueError: If `ignore` is not in range [0, 255]. - RuntimeError: If shape of the input image is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.AutoContrast(), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义AutoContrast类,实现__init__方法,参数cutoff为阈值,ignore为忽略的像素 @check_auto_contrast def __init__(self, cutoff=0.0, ignore=None): self.cutoff = cutoff self.ignore = ignore self.random = False + # 定义__call__方法,参数img为图像 def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be automatically contrasted. - - Returns: - PIL.Image.Image, automatically contrasted image. - """ - + # 调用util.auto_contrast函数,参数img,cutoff,ignore return util.auto_contrast(img, self.cutoff, self.ignore) class CenterCrop(py_transforms.PyTensorOperation): - """ - Crop the central region of the input PIL Image with the given size. - - Args: - size (Union[int, Sequence[int, int]]): The size of the cropped image. - If int is provided, a square of size (`size`, `size`) will be cropped with this value. - If Sequence[int, int] is provided, its two elements will be taken as the cropped height and width. - - Raises: - TypeError: If `size` is not of type int or Sequence[int, int]. - ValueError: If `size` is not positive. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.CenterCrop(64), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 初始化函数,设置裁剪的大小 @check_center_crop def __init__(self, size): self.size = size self.random = False + # 调用util.center_crop函数,参数img, size def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be center cropped. - - Returns: - PIL.Image.Image, cropped image. - """ return util.center_crop(img, self.size) - +# 从输入图像数组中随机裁剪出给定数量的正方形区域。 class Cutout(py_transforms.PyTensorOperation): - """ - Randomly cut out a certain number of square patches on the input numpy.ndarray image, - setting the pixel values in the patch to zero. - - See `Improved Regularization of Convolutional Neural Networks with Cutout `_. - - Args: - length (int): The side length of square patches to be cut out. - num_patches (int, optional): The number of patches to be cut out. Default: 1. - - Raises: - TypeError: If `length` is not of type int. - TypeError: If `num_patches` is not of type int. - ValueError: If `length` is less than or equal 0. - ValueError: If `num_patches` is less than or equal 0. - RuntimeError: If shape of the input image is not . - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.ToTensor(), - ... py_vision.Cutout(80)]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 初始化Cutout类,参数为length, num_patches @check_cutout def __init__(self, length, num_patches=1): self.length = length self.num_patches = num_patches self.random = False + # 定义__call__方法,用于调用Cutout类 def __call__(self, np_img): - """ - Call method. - - Args: - np_img (numpy.ndarray): Image in shape of (C, H, W) to be cut out. - - Returns: - numpy.ndarray, image cut out. - """ + # 如果np_img不是NumPy数组,抛出类型错误异常 if not isinstance(np_img, np.ndarray): raise TypeError( "img should be NumPy array. Got {}.".format(type(np_img))) + # 如果np_img的维度不是3,抛出类型错误异常 if np_img.ndim != 3: raise TypeError( 'img dimension should be 3. Got {}.'.format(np_img.ndim)) + # 获取np_img的高度和宽度 _, image_h, image_w = np_img.shape + # 计算图片裁剪规模 scale = (self.length * self.length) / (image_h * image_w) + # 将bounded赋值为False bounded = False + # 遍历num_patches次 for _ in range(self.num_patches): + # 获取i,j,erase_h(擦除区域高度),erase_w(擦除区域宽度),erase_value(擦除区域的像素填充值) i, j, erase_h, erase_w, erase_value = util.get_erase_params(np_img, (scale, scale), (1, 1), 0, bounded, 1) + # 使用util.erase函数,将np_img中的像素值替换为erase_value np_img = util.erase(np_img, i, j, erase_h, erase_w, erase_value) + # 返回np_img return np_img - +# 将输入的压缩图像解码为RGB格式 class Decode(py_transforms.PyTensorOperation): - """ - Decode the input raw image bytes to PIL Image format in RGB mode. - - Raises: - ValueError: If the input is not raw image bytes. - ValueError: If the input image is already decoded. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.RandomHorizontalFlip(0.5), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义Decode类 def __init__(self): self.random = False def __call__(self, img): - """ - Call method. - - Args: - img (Bytes-like Object): Raw image data to be decoded. - - Returns: - PIL.Image.Image, decoded PIL Image in RGB mode. - """ + # 调用util.decode函数,输入img return util.decode(img) - +# 对输入图像进行直方图均衡化。 class Equalize(py_transforms.PyTensorOperation): - """ - Equalize the histogram of the input PIL Image. - - By applying a non-linear mapping to the input image, it creates a uniform - distribution of grayscale values in the output. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.Equalize(), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义Decode类 def __init__(self): self.random = False def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be equalized. - - Returns: - PIL.Image.Image, equalized image. - """ - + # 调用util.equalize函数,输入img,返回比较图像 return util.equalize(img) - +# 定义一个类FiveCrop,用来对图像进行五分类 class FiveCrop(py_transforms.PyTensorOperation): - """ - Crop the given image into one central crop and four corners. - - Args: - size (Union[int, Sequence[int, int]]): The size of the cropped image. - If int is provided, a square of size (`size`, `size`) will be cropped with this value. - If Sequence[int, int] is provided, its two elements will be taken as the cropped height and width. - - Raises: - TypeError: If `size` is not of type int or Sequence[int, int]. - ValueError: If `size` is not positive. - - Supported Platforms: - ``CPU`` - - Examples: - >>> import numpy - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.FiveCrop(size=200), - ... # 4D stack of 5 images - ... lambda *images: numpy.stack([py_vision.ToTensor()(image) for image in images])]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - @check_five_crop def __init__(self, size): + # 初始化类FiveCrop,参数为size self.size = size self.random = False def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be cropped. - - Returns: - tuple[PIL.Image.Image], five cropped images in order of top_left, top_right, bottom_left, - bottom_right and center. - """ + # 返回五分类图像 return util.five_crop(img, self.size) - +# 将输入PIL图像转换为灰度图 class Grayscale(py_transforms.PyTensorOperation): - """ - Convert the input PIL Image to grayscale. - - Args: - num_output_channels (int): The number of channels desired for the output image, must be 1 or 3. - If 3 is provided, the returned image will have 3 identical RGB channels. Default: 1. - - Raises: - TypeError: If `num_output_channels` is not of type int. - ValueError: If `num_output_channels` is not 1 or 3. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.Grayscale(3), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义Grayscale类 @check_num_channels def __init__(self, num_output_channels=1): + # 初始化类,参数num_output_channels表示输出通道数 self.num_output_channels = num_output_channels self.random = False def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be converted to grayscale. - - Returns: - PIL.Image.Image, converted grayscale image. - """ + # 返回一个黑白图像 return util.grayscale(img, num_output_channels=self.num_output_channels) - +# 将输入的HSV格式numpy.ndarray图像转换为RGB格式。 class HsvToRgb(py_transforms.PyTensorOperation): - """ - Convert the input numpy.ndarray images from HSV to RGB. - - Args: - is_hwc (bool): If True, means the input image is in shape of (H, W, C) or (N, H, W, C). - Otherwise, it is in shape of (C, H, W) or (N, C, H, W). Default: False. - - Raises: - TypeError: If `is_hwc` is not of type bool. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.CenterCrop(20), - ... py_vision.ToTensor(), - ... py_vision.HsvToRgb()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义HsvToRgb类,参数为is_hwc,默认为False @check_hsv_to_rgb def __init__(self, is_hwc=False): + # 初始化HsvToRgb类,参数为is_hwc self.is_hwc = is_hwc + # 设置随机数为False self.random = False def __call__(self, hsv_imgs): - """ - Call method. - - Args: - hsv_imgs (numpy.ndarray): HSV images to be converted. - - Returns: - numpy.ndarray, converted RGB images. - """ + # 调用util.hsv_to_rgbs函数,传入hsv_imgs,is_hwc参数 return util.hsv_to_rgbs(hsv_imgs, self.is_hwc) - +# 将输入图像的shape从 转换为 class HWC2CHW(py_transforms.PyTensorOperation): - """ - Transpose the input numpy.ndarray image of shape (H, W, C) to (C, H, W). - - Raises: - TypeError: If the input image is not of type :class:`numpy.ndarray`. - TypeError: If dimension of the input image is not 3. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.HWC2CHW()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ def __init__(self): self.random = False def __call__(self, img): - """ - Call method. - - Args: - img (numpy.ndarray): numpy.ndarray of shape (H, W, C) to be transposed. - - Returns: - numpy.ndarray, transposed numpy.ndarray of shape (C, H, W). - """ return util.hwc_to_chw(img) - +# 在 RGB 模式下对输入图像应用像素反转。 class Invert(py_transforms.PyTensorOperation): - """ - Invert the colors of the input PIL Image. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.Invert(), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义Invert类,初始化random变量 def __init__(self): self.random = False + # 返回util.invert_color函数的调用结果 def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be color inverted. - - Returns: - PIL.Image.Image, color inverted image. - """ - return util.invert_color(img) - +# 使用指定的变换方阵和均值向量对输入numpy.ndarray图像进行线性变换。 class LinearTransformation(py_transforms.PyTensorOperation): - r""" - Linearly transform the input numpy.ndarray image with a square transformation matrix and a mean vector. - - It will first flatten the input image and subtract the mean vector from it, then compute the dot - product with the transformation matrix, finally reshape it back to its original shape. - - Args: - transformation_matrix (numpy.ndarray): A square transformation matrix in shape of (D, D), where - :math:`D = C \times H \times W`. - mean_vector (numpy.ndarray): A mean vector in shape of (D,), where :math:`D = C \times H \times W`. - - Raises: - TypeError: If `transformation_matrix` is not of type :class:`numpy.ndarray`. - TypeError: If `mean_vector` is not of type :class:`numpy.ndarray`. - - Supported Platforms: - ``CPU`` - - Examples: - >>> import numpy as np - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> height, width = 32, 32 - >>> dim = 3 * height * width - >>> transformation_matrix = np.ones([dim, dim]) - >>> mean_vector = np.zeros(dim) - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.Resize((height,width)), - ... py_vision.ToTensor(), - ... py_vision.LinearTransformation(transformation_matrix, mean_vector)]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义LinearTransformation类,参数transformation_matrix, mean_vector @check_linear_transform def __init__(self, transformation_matrix, mean_vector): self.transformation_matrix = transformation_matrix @@ -589,53 +212,15 @@ class LinearTransformation(py_transforms.PyTensorOperation): self.random = False def __call__(self, np_img): - """ - Call method. - - Args: - np_img (numpy.ndarray): Image in shape of (C, H, W) to be linearly transformed. - - Returns: - numpy.ndarray, linearly transformed image. - """ + # 返回线性变换后的图像 return util.linear_transform(np_img, self.transformation_matrix, self.mean_vector) - +# 随机混合一批输入的numpy.ndarray图像及其标签。 class MixUp(py_transforms.PyTensorOperation): - """ - Randomly mix up a batch of images together with its labels. - - Each image will be multiplied by a random weight :math:`lambda` generated from the Beta distribution and then added - to another image multiplied by :math:`1 - lambda`. The same transformation will be applied to their labels with the - same value of :math:`lambda`. Make sure that the labels are one-hot encoded in advance. - - Args: - batch_size (int): The number of images in a batch. - alpha (float): The alpha and beta parameter for the Beta distribution. - is_single (bool, optional): If True, it will randomly mix up [img0, ..., img(n-1), img(n)] with - [img1, ..., img(n), img0] in each batch. Otherwise, it will randomly mix up images with the - output of the previous batch. Default: True. - - Raises: - TypeError: If `batch_size` is not of type int. - TypeError: If `alpha` is not of type float. - TypeError: If `is_single` is not of type bool. - ValueError: If `batch_size` is not positive. - ValueError: If `alpha` is not positive. - - Supported Platforms: - ``CPU`` - - Examples: - >>> # Setup multi-batch mixup transformation - >>> transform = [py_vision.MixUp(batch_size=16, alpha=0.2, is_single=False)] - >>> # Apply the transform to the dataset through dataset.map() - >>> image_folder_dataset = image_folder_dataset.map(input_columns="image", - ... operations=transform) - """ - + # 定义MixUp类,参数image, label, batch_size, alpha, is_single @check_mix_up def __init__(self, batch_size, alpha, is_single=True): + # 初始化image和label self.image = 0 self.label = 0 self.is_first = True @@ -644,135 +229,36 @@ class MixUp(py_transforms.PyTensorOperation): self.is_single = is_single self.random = False + # 返回mix_up_single函数 def __call__(self, image, label): - """ - Call method. - - Args: - image (numpy.ndarray): Images to be mixed up. - label (numpy.ndarray): Labels to be mixed up. - - Returns: - numpy.ndarray, images after mixing up. - numpy.ndarray, labels after mixing up. - """ + # 如果is_single为True,则返回mix_up_single函数 if self.is_single: return util.mix_up_single(self.batch_size, image, label, self.alpha) + # 否则返回mix_up_muti函数 return util.mix_up_muti(self, self.batch_size, image, label, self.alpha) - +# 根据均值和标准差对输入图像进行归一化 class Normalize(py_transforms.PyTensorOperation): - r""" - Normalize the input numpy.ndarray image of shape (C, H, W) with the specified mean and standard deviation. - - .. math:: - - output_{c} = \frac{input_{c} - mean_{c}}{std_{c}} - - Note: - The pixel values of the input image need to be in range of [0.0, 1.0]. - If not so, please call :class:`mindspore.dataset.vision.py_transforms.ToTensor` first. - - Args: - mean (Union[float, Sequence[float]]): Mean pixel values for each channel, - must be in range of [0.0, 1.0]. - If float is provided, it will be applied to each channel. - If Sequence[float] is provided, it should have the same length with channel - and be arranged in channel order. - std (Union[float, Sequence[float]]): Standard deviation values for each channel, must be in range of (0.0, 1.0]. - If float is provided, it will be applied to each channel. - If Sequence[float] is provided, it should have the same length with channel - and be arranged in channel order. - - Raises: - TypeError: If the input image is not of type :class:`numpy.ndarray`. - TypeError: If dimension of the input image is not 3. - NotImplementedError: If dtype of the input image is int. - ValueError: If lengths of `mean` and `std` are not equal. - ValueError: If length of `mean` or `std` is neither equal to 1 nor equal to the length of channel. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.RandomHorizontalFlip(0.5), - ... py_vision.ToTensor(), - ... py_vision.Normalize((0.491, 0.482, 0.447), (0.247, 0.243, 0.262))]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - @check_normalize_py def __init__(self, mean, std): + ''' + 初始化函数,用于初始化模型参数 + :param mean: 均值 + :param std: 标准差 + ''' + #设置均值 self.mean = mean + #设置标准差 self.std = std self.random = False def __call__(self, img): - """ - Call method. - - Args: - img (numpy.ndarray): numpy.ndarray to be normalized. - - Returns: - numpy.ndarray, normalized numpy.ndarray. - """ + #返回一个normalize对象 return util.normalize(img, self.mean, self.std) - +# 根据均值和标准差对输入图像进行归一化,然后填充一个全零的额外通道 class NormalizePad(py_transforms.PyTensorOperation): - r""" - Normalize the input numpy.ndarray image of shape (C, H, W) with the specified mean and standard deviation, - then pad an extra channel filled with zeros. - - .. math:: - output_{c} = \begin{cases} - \frac{input_{c} - mean_{c}}{std_{c}}, & \text{if} \quad 0 \le c < 3 \text{;}\\ - 0, & \text{if} \quad c = 3 \text{.} - \end{cases} - - Note: - The pixel values of the input image need to be in range of [0.0, 1.0]. - If not so, please call :class:`mindspore.dataset.vision.py_transforms.ToTensor` first. - - Args: - mean (Union[float, Sequence[float]]): Mean pixel values for each channel, must be in range of [0.0, 1.0]. - If float is provided, it will be applied to each channel. - If Sequence[float] is provided, it should have the same length with channel - and be arranged in channel order. - std (Union[float, Sequence[float]]): Standard deviation values for each channel, must be in range of (0.0, 1.0]. - If float is provided, it will be applied to each channel. - If Sequence[float] is provided, it should have the same length with channel - and be arranged in channel order. - dtype (str): The dtype of the output image. Only "float32" and "float16" are supported. Default: "float32". - - Raises: - TypeError: If the input image is not of type :class:`numpy.ndarray`. - TypeError: If dimension of the input image is not 3. - NotImplementedError: If dtype of the input image is int. - ValueError: If lengths of `mean` and `std` are not equal. - ValueError: If length of `mean` or `std` is neither equal to 1 nor equal to the length of channel. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.RandomHorizontalFlip(0.5), - ... py_vision.ToTensor(), - ... py_vision.NormalizePad((0.491, 0.482, 0.447), (0.247, 0.243, 0.262), "float32")]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 初始化NormalizePad类,参数mean, std, dtype @check_normalizepad_py def __init__(self, mean, std, dtype="float32"): self.mean = mean @@ -780,67 +266,17 @@ class NormalizePad(py_transforms.PyTensorOperation): self.dtype = dtype self.random = False + # 定义__call__函数,用于调用normalize函数 def __call__(self, img): - """ - Call method. - - Args: - img (numpy.ndarray): numpy.ndarray to be normalized and padded. - - Returns: - numpy.ndarray, normalized and padded numpy.ndarray. - """ + # 调用util.normalize函数,将img参数转换为输入的格式,并且设置pad_channel为True,dtype为self.dtype return util.normalize(img, self.mean, self.std, pad_channel=True, dtype=self.dtype) - +# 填充图像 class Pad(py_transforms.PyTensorOperation): - """ - Pad the input PIL Image on all sides. - - Args: - padding (Union[int, Sequence[int, int], Sequence[int, int, int, int]]): The number of pixels to pad - on each border. - If int is provided, pad all borders with this value. - If Sequence[int, int] is provided, pad the left and top borders with the - first value and the right and bottom borders with the second value. - If Sequence[int, int, int, int] is provided, pad the left, top, right and bottom borders respectively. - fill_value (Union[int, tuple[int, int, int]], optional): Pixel value used to pad the borders, - only valid when `padding_mode` is Border.CONSTANT. - If int is provided, it will be used for all RGB channels. - If tuple[int, int, int] is provided, it will be used for R, G, B channels respectively. Default: 0. - padding_mode (Border, optional): Method of padding. It can be Border.CONSTANT, Border.EDGE, Border.REFLECT - or Border.SYMMETRIC. Default: Border.CONSTANT. Default: Border.CONSTANT. - - - Border.CONSTANT, pads with a constant value. - - Border.EDGE, pads with the last value at the edge of the image. - - Border.REFLECT, pads with reflection of the image omitting the last value on the edge. - - Border.SYMMETRIC, pads with reflection of the image repeating the last value on the edge. - - Raises: - TypeError: If `padding` is not of type int or Sequence[int, int]. - TypeError: If `fill_value` is not of type int or tuple[int, int, int]. - TypeError: If `padding_mode` is not of type :class:`mindspore.dataset.vision.Border`. - ValueError: If `padding` is negative. - ValueError: If `fill_value` is not in range of [0, 255]. - RuntimeError: If shape of the input image is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... # adds 10 pixels (default black) to each border of the image - ... py_vision.Pad(padding=10), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - @check_pad def __init__(self, padding, fill_value=0, padding_mode=Border.CONSTANT): + # 初始化NormalizePad类,参数padding, fill_value, padding_mode + # 解析padding parse_padding(padding) self.padding = padding @@ -848,84 +284,17 @@ class Pad(py_transforms.PyTensorOperation): self.padding_mode = DE_PY_BORDER_TYPE[padding_mode] self.random = False + # 定义__call__方法,用于处理图像 def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be padded. - - Returns: - PIL.Image.Image, padded image. - """ + # 调用util.pad函数,处理图像 return util.pad(img, self.padding, self.fill_value, self.padding_mode) - +# 对输入图像应用随机仿射变换 class RandomAffine(py_transforms.PyTensorOperation): - """ - Apply random affine transformation to the input PIL Image. - - Args: - degrees (Union[float, Sequence[float, float]]): Range of degrees to select from. - If float is provided, the degree will be randomly selected from (-`degrees`, `degrees`). - If Sequence[float, float] is provided, it needs to be arranged in order of (min, max). - translate (Sequence[float, float], optional): Maximum absolute fraction sequence in shape of (tx, ty) - for horizontal and vertical translations. The horizontal and vertical shifts are randomly - selected from (-tx * width, tx * width) and (-ty * height, ty * height) respectively. - Default: None, means no translation. - scale (Sequence[float, float], optional): Range of scaling factor to select from. - Default: None, means to keep the original scale. - shear (Union[float, Sequence[float, float], Sequence[float, float, float, float]], optional): - Range of shear factor to select from. - If float is provided, a shearing parallel to X axis with a factor selected from - (- `shear` , `shear` ) will be applied. - If Sequence[float, float] is provided, a shearing parallel to X axis with a factor selected - from ( `shear` [0], `shear` [1]) will be applied. - If Sequence[float, float, float, float] is provided, a shearing parallel to X axis with a factor selected - from ( `shear` [0], `shear` [1]) and a shearing parallel to Y axis with a factor selected from - ( `shear` [2], `shear` [3]) will be applied. Default: None, means no shearing. - resample (Inter, optional): Method of interpolation. It can be Inter.BILINEAR, Inter.NEAREST - or Inter.BICUBIC. If the input PIL Image is in mode of "1" or "P", Inter.NEAREST will be - used directly. Default: Inter.NEAREST. - - - Inter.BILINEAR, bilinear interpolation. - - Inter.NEAREST, nearest-neighbor interpolation. - - Inter.BICUBIC, bicubic interpolation. - - fill_value (Union[int, tuple[int, int, int]], optional): Pixel value for areas outside the transform image. - If int is provided, it will be used for all RGB channels. - If tuple[int, int, int] is provided, it will be used for R, G, B channels respectively. - Only supported with Pillow 5.0.0 and above. Default: 0. - - Raises: - TypeError: If `degrees` is not of type float or Sequence[float, float]. - TypeError: If `translate` is not of type Sequence[float, float]. - TypeError: If `scale` is not of type Sequence[float, float]. - TypeError: If `shear` is not of type float or Sequence[float, float]. - TypeError: If `resample` is not of type :class:`mindspore.dataset.vision.Inter`. - TypeError: If `fill_value` is not of type int or tuple[int, int, int]. - ValueError: If `degrees` is negative. - ValueError: If `translate` is not in range of [-1.0, 1.0]. - ValueError: If `scale` is negative. - ValueError: If `shear` is not positive. - RuntimeError: If shape of the input image is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.RandomAffine(degrees=15, translate=(0.1, 0.1), scale=(0.9, 1.1)), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 根据参数degrees,translate,scale,shear,resample,fill_value初始化RandomAffine类 @check_random_affine def __init__(self, degrees, translate=None, scale=None, shear=None, resample=Inter.NEAREST, fill_value=0): + # 如果shear不为空,则将shear转换为数组 if shear is not None: if isinstance(shear, numbers.Number): shear = (-1 * shear, shear) @@ -935,6 +304,7 @@ class RandomAffine(py_transforms.PyTensorOperation): elif len(shear) == 4: shear = [s for s in shear] + # 如果degrees不为空,则将degrees转换为数组 if isinstance(degrees, numbers.Number): degrees = (-degrees, degrees) @@ -945,17 +315,9 @@ class RandomAffine(py_transforms.PyTensorOperation): self.resample = DE_PY_INTER_MODE[resample] self.fill_value = fill_value + # 返回一个随机变换后的图像 def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be randomly affine transformed. - - Returns: - PIL.Image.Image, randomly affine transformed image. - """ - + # 调用util.random_affine函数,参数img,self.degrees,self.translate,self.scale_ranges,self.shear,self.resample,self.fill_value return util.random_affine(img, self.degrees, self.translate, @@ -964,103 +326,20 @@ class RandomAffine(py_transforms.PyTensorOperation): self.resample, self.fill_value) - +# 随机调整输入图像的颜色 class RandomColor(py_transforms.PyTensorOperation): - """ - Adjust the color balance of the input PIL Image by a random degree. - - Args: - degrees (Sequence[float, float]): Range of color adjustment degree to select from, - must be a Sequence of length 2, arranged in order of (min, max). - A degree of 1.0 gives the original image, a degree of 0.0 gives a black and white image - and higher degrees mean more brightness, contrast, etc. Default: (0.1, 1.9). - - Raises: - TypeError: If `degrees` is not of type Sequence[float, float]. - ValueError: If `degrees` is negative. - RuntimeError: If shape of the input image is not . - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.RandomColor((0.5, 2.0)), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义一个RandomColor类,参数为degrees,用于控制随机颜色的范围 @check_positive_degrees def __init__(self, degrees=(0.1, 1.9)): self.degrees = degrees def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be color adjusted. - - Returns: - PIL.Image.Image, color adjusted image. - """ - + # 调用py_transforms.PyTensorOperation的__call__方法,传入img参数 return util.random_color(img, self.degrees) class RandomColorAdjust(py_transforms.PyTensorOperation): - """ - Randomly adjust the brightness, contrast, saturation, and hue of the input PIL Image. - - Args: - brightness (Union[float, Sequence[float, float]], optional): Range of brightness adjustment factor - to select from, must be non negative. - If float is provided, the factor will be uniformly selected from - [max(0, 1 - `brightness`), 1 + `brightness`). - If Sequence[float, float] is provided, it should be arranged in order of (min, max). Default: (1, 1). - contrast (Union[float, Sequence[float, float]], optional): Range of contrast adjustment factor - to select from, must be non negative. - If float is provided, the factor will be uniformly selected from [max(0, 1 - `contrast`), 1 + `contrast`). - If Sequence[float, float] is provided, it should be arranged in order of (min, max). Default: (1, 1). - saturation (Union[float, Sequence[float, float]], optional): Range of saturation adjustment factor - to select from, must be non negative. - If float is provided, the factor will be uniformly selected from - [max(0, 1 - `saturation`), 1 + `saturation`). - If Sequence[float, float] is provided, it should be arranged in order of (min, max). Default: (1, 1). - hue (Union[float, Sequence[float, float]], optional): Range of hue adjustment factor to select from. - If float is provided, it must be in range of [0, 0.5], and the factor will be uniformly - selected from [-`hue`, `hue`). - If Sequence[float, float] is provided, the elements must be in range of [-0.5, 0.5] and arranged in - order of (min, max). Default: (0, 0). - - Raises: - TypeError: If `brightness` is not of type float or Sequence[float, float]. - TypeError: If `contrast` is not of type float or Sequence[float, float]. - TypeError: If `saturation` is not of type float or Sequence[float, float]. - TypeError: If `hue` is not of type float or Sequence[float, float]. - ValueError: If `brightness` is negative. - ValueError: If `contrast` is negative. - ValueError: If `saturation` is negative. - ValueError: If `hue` is not in range of [-0.5, 0.5]. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.RandomColorAdjust(0.4, 0.4, 0.4, 0.1), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义一个RandomColorAdjust类,接收三个参数:brightness、contrast、saturation和hue @check_random_color_adjust def __init__(self, brightness=(1, 1), contrast=(1, 1), saturation=(1, 1), hue=(0, 0)): self.brightness = brightness @@ -1069,146 +348,35 @@ class RandomColorAdjust(py_transforms.PyTensorOperation): self.hue = hue def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be randomly color adjusted. - - Returns: - PIL.Image.Image, randomly color adjusted image. - """ + # 调用PyTensorOperation类的__call__方法,传入img参数,返回一个改变图像的图像 return util.random_color_adjust(img, self.brightness, self.contrast, self.saturation, self.hue) - +# 对输入图像进行随机区域的裁剪 class RandomCrop(py_transforms.PyTensorOperation): - """ - Crop the input PIL Image at a random location with the specified size. - - Args: - size (Union[int, Sequence[int, int]]): The size of the cropped image. - If int is provided, a square of size (`size`, `size`) will be cropped with this value. - If Sequence[int, int] is provided, its two elements will be taken as the cropped height and width. - padding (Union[int, Sequence[int, int], Sequence[int, int, int, int]], optional): The number of pixels to pad - on each border. When specified, it will pad the image before random cropping. - If int is provided, pad all borders with this value. - If Sequence[int, int] is provided, pad the left and top borders with the - first value and the right and bottom borders with the second value. - If Sequence[int, int, int, int] is provided, pad the left, top, right and bottom borders respectively. - Default: None, means not to pad. - pad_if_needed (bool, optional): Whether to pad the image if either side is shorter than - the given cropping size. Default: False, means not to pad. - fill_value (Union[int, tuple[int, int, int]], optional): Pixel value used to pad the borders, - only valid when `padding_mode` is Border.CONSTANT. - If int is provided, it will be used for all RGB channels. - If tuple[int, int, int] is provided, it will be used for R, G, B channels respectively. Default: 0. - padding_mode (Border, optional): Method of padding. It can be Border.CONSTANT, Border.EDGE, Border.REFLECT - or Border.SYMMETRIC. Default: Border.CONSTANT. - - - Border.CONSTANT, pads with a constant value. - - Border.EDGE, pads with the last value at the edge of the image. - - Border.REFLECT, pads with reflection of the image omitting the last value on the edge. - - Border.SYMMETRIC, pads with reflection of the image repeating the last value on the edge. - - Raises: - TypeError: If `size` is not of type int or Sequence[int, int]. - TypeError: If `padding` is not of type int, Sequence[int, int] or Sequence[int, int, int, int]. - TypeError: If `pad_if_needed` is not of type bool. - TypeError: If `fill_value` is not of type int or tuple[int, int, int]. - TypeError: If `padding_mode` is not of type :class:`mindspore.dataset.vision.Border`. - ValueError: If `size` is not positive. - ValueError: If `padding` is negative. - ValueError: If `fill_value` is not in range of [0, 255]. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.RandomCrop(224), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 初始化RandomCrop类,参数size为裁剪大小,padding为边界,pad_if_needed为是否需要裁剪,fill_value为填充值,padding_mode为填充模式 @check_random_crop def __init__(self, size, padding=None, pad_if_needed=False, fill_value=0, padding_mode=Border.CONSTANT): + # 如果padding为空,则padding为(0, 0, 0, 0) if padding is None: padding = (0, 0, 0, 0) else: + # 将padding转换为元组 padding = parse_padding(padding) self.size = size self.padding = padding - self.pad_if_needed = pad_if_needed + self.pad_if_needed = pad_if_neededm self.fill_value = fill_value self.padding_mode = DE_PY_BORDER_TYPE[padding_mode] + # 定义__call__方法,用于调用RandomCrop类的构造函数 def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be randomly cropped. - - Returns: - PIL.Image.Image, cropped image. - """ + # 调用util.random_crop函数,输入img,self.size,self.padding,self.pad_if_needed,self.fill_value,self.padding_mode return util.random_crop(img, self.size, self.padding, self.pad_if_needed, self.fill_value, self.padding_mode) - +# 按照指定的概率擦除输入numpy.ndarray图像上随机矩形区域内的像素 class RandomErasing(py_transforms.PyTensorOperation): - """ - Randomly erase pixels within a random selected rectangle erea on the input numpy.ndarray image. - - See `Random Erasing Data Augmentation `_. - - Args: - prob (float, optional): Probability of performing erasing. Default: 0.5. - scale (Sequence[float, float], optional): Range of area scale of the erased area relative - to the original image to select from, arranged in order of (min, max). - Default: (0.02, 0.33). - ratio (Sequence[float, float], optional): Range of aspect ratio of the erased area to select - from, arraged in order of (min, max). Default: (0.3, 3.3). - value (Union[int, str, Sequence[int, int, int]]): Pixel value used to pad the erased area. - If int is provided, it will be used for all RGB channels. - If Sequence[int, int, int] is provided, it will be used for R, G, B channels respectively. - If a string of 'random' is provided, each pixel will be erased with a random value obtained - from a standard normal distribution. Default: 0. - inplace (bool, optional): Whether to apply erasing inplace. Default: False. - max_attempts (int, optional): The maximum number of attempts to propose a valid - erased area, beyond which the original image will be returned. Default: 10. - - Raises: - TypeError: If `prob` is not of type float. - TypeError: If `scale` is not of type Sequence[float, float]. - TypeError: If `ratio` is not of type Sequence[float, float]. - TypeError: If `value` is not of type int, str, or Sequence[int, int, int]. - TypeError: If `inplace` is not of type bool. - TypeError: If `max_attempts` is not of type int. - ValueError: If `prob` is not in range of [0, 1]. - ValueError: If `scale` is negative. - ValueError: If `ratio` is negative. - ValueError: If `value` is not in range of [0, 255]. - ValueError: If `max_attempts` is not positive. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.ToTensor(), - ... py_vision.RandomErasing(value='random')]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义一个RandomErasing类,参数prob, scale, ratio, value, inplace, max_attempts @check_random_erasing def __init__(self, prob=0.5, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=0, inplace=False, max_attempts=10): self.prob = prob @@ -1219,273 +387,93 @@ class RandomErasing(py_transforms.PyTensorOperation): self.max_attempts = max_attempts def __call__(self, np_img): - """ - Call method. - - Args: - np_img (numpy.ndarray): image in shape of (C, H, W) to be randomly erased. - - Returns: - numpy.ndarray, erased image. - """ + # 调用父类的__call__函数 bounded = True + # 如果概率大于随机的,则进行擦除 if self.prob > random.random(): + # 获取擦除参数 i, j, erase_h, erase_w, erase_value = util.get_erase_params(np_img, self.scale, self.ratio, self.value, bounded, self.max_attempts) + # 进行擦除 return util.erase(np_img, i, j, erase_h, erase_w, erase_value, self.inplace) return np_img - +# 按照指定的概率将输入PIL图像转换为灰度图 class RandomGrayscale(py_transforms.PyTensorOperation): - """ - Randomly convert the input PIL Image to grayscale. - - Args: - prob (float, optional): Probability of performing grayscale conversion. Default: 0.1. - - Raises: - TypeError: If `prob` is not of type float. - ValueError: If `prob` is not in range of [0, 1]. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.RandomGrayscale(0.3), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义RandomGrayscale类参数prob @check_prob def __init__(self, prob=0.1): self.prob = prob + # 定义操作 def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be randomly converted to grayscale. - - Returns: - PIL.Image.Image, randomly converted grayscale image, which has the same number of channels - as the input image. - If input image has 1 channel, the output grayscale image will have 1 channel. - If input image has 3 channels, the output grayscale image will have 3 identical channels. - """ + # 如果图像类型为L,则输出通道数为1 if img.mode == 'L': num_output_channels = 1 else: + # 否则输出通道数为3 num_output_channels = 3 + # 如果概率大于随机数,则进行灰度变换 if self.prob > random.random(): return util.grayscale(img, num_output_channels=num_output_channels) + # 否则返回原图 return img - +# 对输入图像按给定的概率进行水平随机翻转 class RandomHorizontalFlip(py_transforms.PyTensorOperation): - """ - Randomly flip the input PIL Image horizontally with a given probability. - - Args: - prob (float, optional): Probability of performing horizontally flip. Default: 0.5. - - Raises: - TypeError: If `prob` is not of type float. - ValueError: If `prob` is not in range of [0, 1]. - RuntimeError: If shape of the input image is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.RandomHorizontalFlip(0.5), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 初始化函数,接收一个prob参数,默认为0.5 @check_prob def __init__(self, prob=0.5): self.prob = prob + # 定义函数,返回一个随机水平翻转的图像 def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be horizontally flipped. - - Returns: - PIL.Image.Image, randomly horizontally flipped image. - """ + + # 返回一个随机水平翻转的图像,并将prob参数赋值给img return util.random_horizontal_flip(img, self.prob) - +# 将AlexNet PCA的噪声添加到图像中 class RandomLighting(py_transforms.PyTensorOperation): - """ - Add AlexNet-style PCA-based noise to the input PIL Image. - - Args: - alpha (float, optional): Intensity of the noise. Default: 0.05. - - Raises: - TypeError: If `alpha` is not of type float. - ValueError: If `alpha` is negative. - RuntimeError: If shape of input image is not . - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.RandomLighting(0.1), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义一个RandomLighting类 @check_alpha def __init__(self, alpha=0.05): self.alpha = alpha def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be added AlexNet-style PCA-based noise. - - Returns: - PIL.Image.Image, image with noise added. - """ - + # 调用随机亮度函数 return util.random_lighting(img, self.alpha) - +# 按照指定的概率对输入PIL图像进行透视变换 class RandomPerspective(py_transforms.PyTensorOperation): - """ - Randomly apply perspective transformation to the input PIL Image with a given probability. - - Args: - distortion_scale (float, optional): Scale of distortion, in range of [0, 1]. Default: 0.5. - prob (float, optional): Probability of performing perspective transformation. Default: 0.5. - interpolation (Inter, optional): Method of interpolation. It can be Inter.BILINEAR, - Inter.NEAREST or Inter.BICUBIC. Default: Inter.BICUBIC. - - - Inter.BILINEAR, bilinear interpolation. - - Inter.NEAREST, nearest-neighbor interpolation. - - Inter.BICUBIC, bicubic interpolation. - - Raises: - TypeError: If `distortion_scale` is not of type float. - TypeError: If `prob` is not of type float. - TypeError: If `interpolation` is not of type :class:`mindspore.dataset.vision.Inter`. - ValueError: If `distortion_scale` is not in range of [0, 1]. - ValueError: If `prob` is not in range of [0, 1]. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.RandomPerspective(prob=0.1), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义RandomPerspective类,接收一个参数distortion_scale,用于控制随机投影的概率,prob,用于控制随机投影的插值方式 @check_random_perspective def __init__(self, distortion_scale=0.5, prob=0.5, interpolation=Inter.BICUBIC): self.distortion_scale = distortion_scale self.prob = prob + # 将interpolation转换为插值方式 self.interpolation = DE_PY_INTER_MODE[interpolation] def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be applied randomly perspective transformation. - - Returns: - PIL.Image.Image, image applied randomly perspective transformation. - """ + # 定义__call__方法,接收一个参数img,用于处理图像 if not is_pil(img): raise ValueError("Input image should be a Pillow image.") + # 如果prob大于随机生成的值,则获取随机投影的起点和终点 if self.prob > random.random(): start_points, end_points = util.get_perspective_params( img, self.distortion_scale) + # 返回投影后的图像 return util.perspective(img, start_points, end_points, self.interpolation) + # 如果prob小于随机生成的值,则返回原图 return img - +# 对输入图像进行随机裁剪,并使用指定的 mindspore.dataset.vision.Inter 插值方式去调整为指定的尺寸大小 class RandomResizedCrop(py_transforms.PyTensorOperation): - """ - Randomly crop the input PIL Image and resize it to a given size. - - Args: - size (Union[int, Sequence[int, int]]): The size of the cropped image. - If int is provided, a square of size (`size`, `size`) will be cropped with this value. - If Sequence[int, int] is provided, its two elements will be taken as the cropped height and width. - scale (Sequence[float, float], optional): Range of area scale of the cropped area relative - to the original image to select from, arraged in order or (min, max). Default: (0.08, 1.0). - ratio (Sequence[float, float], optional): Range of aspect ratio of the cropped area to select - from, arraged in order of (min, max). Default: (3./4., 4./3.). - interpolation (Inter, optional): Method of interpolation. It can be Inter.NEAREST, - Inter.ANTIALIAS, Inter.BILINEAR or Inter.BICUBIC. Default: Inter.BILINEAR. - - - Inter.NEAREST, nearest-neighbor interpolation. - - Inter.ANTIALIAS, antialias interpolation. - - Inter.BILINEAR, bilinear interpolation. - - Inter.BICUBIC, bicubic interpolation. - - max_attempts (int, optional): The maximum number of attempts to propose a valid - crop area, beyond which it will fall back to use center crop instead. Default: 10. - - Raises: - TypeError: If `size` is not of type int or Sequence[int, int]. - TypeError: If `scale` is not of type Sequence[float, float]. - TypeError: If `ratio` is not of type Sequence[float, float]. - TypeError: If `interpolation` is not of type :class:`mindspore.dataset.vision.Inter`. - TypeError: If `max_attempts` is not of type int. - ValueError: If `size` is not positive. - ValueError: If `scale` is negative. - ValueError: If `ratio` is negative. - ValueError: If `max_attempts` is not positive. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.RandomResizedCrop(224), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义RandomResizedCrop类,用来对图像进行随机裁剪 @check_random_resize_crop def __init__(self, size, scale=(0.08, 1.0), ratio=(3. / 4., 4. / 3.), interpolation=Inter.BILINEAR, max_attempts=10): + # 初始化RandomResizedCrop类,设置裁剪尺寸,裁剪比例,裁剪方式,最大尝试次数 self.size = size self.scale = scale self.ratio = ratio @@ -1493,70 +481,14 @@ class RandomResizedCrop(py_transforms.PyTensorOperation): self.max_attempts = max_attempts def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be randomly cropped and resized. - - Returns: - PIL.Image.Image, randomly cropped and resized image. - """ + # 返回随机裁剪后的图像 return util.random_resize_crop(img, self.size, self.scale, self.ratio, self.interpolation, self.max_attempts) - +# 在指定的角度范围内,随机旋转输入图像 class RandomRotation(py_transforms.PyTensorOperation): - """ - Rotate the input PIL Image by a random angle. - - Args: - degrees (Union[float, Sequence[float, float]]): Range of rotation degree to select from. - If int is provided, the rotation degree will be randomly selected from (-`degrees`, `degrees`). - If Sequence[float, float] is provided, it should be arranged in order of (min, max). - resample (Inter, optional): Method of interpolation. It can be Inter.NEAREST, Inter.ANTIALIAS, - Inter.BILINEAR or Inter.BICUBIC. If the input PIL Image is in mode of "1" or "P", - Inter.NEAREST will be used directly. Default: Inter.NEAREST. - - - Inter.NEAREST, nearest-neighbor interpolation. - - Inter.ANTIALIAS, antialias interpolation. - - Inter.BILINEAR, bilinear interpolation. - - Inter.BICUBIC, bicubic interpolation. - - expand (bool, optional): If True, it will expand the image to make it large enough to hold the entire - rotated image. If False, keep the image the same size as the input. Please note that the expansion - assumes rotation around the center and no translation. Default: False. - center (Sequence[int, int], optional): The position of the rotation center, taking the upper left corner - as the origin. It should be arranged in order of (width, height). Default: None, means to set the - center of the image. - fill_value (Union[int, tuple[int, int, int]], optional): Pixel value for areas outside the rotated image. - If int is provided, it will be used for all RGB channels. - If tuple[int, int, int] is provided, it will be used for R, G, B channels respectively. Default: 0. - - Raises: - TypeError: If `degrees` is not of type float or Sequence[float, float]. - TypeError: If `resample` is not of type :class:`mindspore.dataset.vision.Inter`. - TypeError: If `expand` is not of type bool. - TypeError: If `center` is not of type Sequence[int, int]. - TypeError: If `fill_value` is not of type int or tuple[int, int, int]. - ValueError: If `fill_value` is not in range of [0, 255]. - RuntimeError: If shape of the input image is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.RandomRotation(30), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - @check_random_rotation + # 定义RandomRotation类,参数degrees, resample, expand, center, fill_value def __init__(self, degrees, resample=Inter.NEAREST, expand=False, center=None, fill_value=0): self.degrees = degrees self.resample = DE_PY_INTER_MODE[resample] @@ -1565,144 +497,35 @@ class RandomRotation(py_transforms.PyTensorOperation): self.fill_value = fill_value def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be randomly rotated. - - Returns: - PIL.Image.Image, randomly rotated image. - """ + # 返回随机旋转图像 return util.random_rotation(img, self.degrees, self.resample, self.expand, self.center, self.fill_value) - +# 在固定或随机的范围调整输入图像的锐度 class RandomSharpness(py_transforms.PyTensorOperation): - """ - Adjust the sharpness of the input PIL Image by a random degree. - - Args: - degrees (Sequence[float, float]): Range of sharpness adjustment degree to select from, arranged - in order of (min, max). A degree of 0.0 gives a blurred image, a degree of 1.0 - gives the original image and a degree of 2.0 gives a sharpened image. - Default: (0.1, 1.9). - - Raises: - TypeError : If `degrees` is not of type Sequence[float, float]. - ValueError: If `degrees` is negative. - ValueError: If `degrees` is not in order of (min, max). - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.RandomSharpness((0.5, 1.5)), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义RandomSharpness类,参数为degrees,表示角度 @check_positive_degrees def __init__(self, degrees=(0.1, 1.9)): self.degrees = degrees + # 定义__call__函数 def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be sharpness adjusted. - - Returns: - PIL.Image.Image, sharpness adjusted image. - """ - + # 调用util.random_sharpness函数,参数为img和self.degrees return util.random_sharpness(img, self.degrees) class RandomVerticalFlip(py_transforms.PyTensorOperation): - """ - Randomly flip the input PIL Image vertically with a given probability. - - Args: - prob (float, optional): Probability of performing vertically flip. Default: 0.5. - - Raises: - TypeError: If `prob` is not of type float. - ValueError: If `prob` is not in range of [0, 1]. - RuntimeError: If shape of input image is not or . - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.RandomVerticalFlip(0.5), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义RandomVerticalFlip类,参数为prob @check_prob def __init__(self, prob=0.5): self.prob = prob def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be vertically flipped. - - Returns: - PIL.Image.Image, randomly vertically flipped image. - """ + # 返回随机翻转图像 return util.random_vertical_flip(img, self.prob) - +# 对输入图像使用给定的 mindspore.dataset.vision.Inter 插值方式去调整为给定的尺寸大小 class Resize(py_transforms.PyTensorOperation): - """ - Resize the input PIL Image to the given size. - - Args: - size (Union[int, Sequence[int, int]]): The size of the resized image. - If int is provided, resize the smaller edge of the image to this - value, keeping the image aspect ratio the same. - If Sequence[int, int] is provided, its two elements will be taken as the resized height and width. - interpolation (Inter, optional): Method of interpolation. It can be Inter.NEAREST, - Inter.ANTIALIAS, Inter.BILINEAR or Inter.BICUBIC. Default: Inter.BILINEAR. - - - Inter.NEAREST, nearest-neighbor interpolation. - - Inter.ANTIALIAS, antialias interpolation. - - Inter.BILINEAR, bilinear interpolation. - - Inter.BICUBIC, bicubic interpolation. - - Raises: - TypeError: If `size` is not of type int or Sequence[int, int]. - TypeError: If `interpolation` is not of type :class:`mindspore.dataset.vision.Inter`. - ValueError: If `size` is not positive. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.Resize(256), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义Resize类,接收size和interpolation参数 @check_resize_interpolation def __init__(self, size, interpolation=Inter.BILINEAR): self.size = size @@ -1710,344 +533,101 @@ class Resize(py_transforms.PyTensorOperation): self.random = False def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be resized. - - Returns: - PIL.Image.Image, resized image. - """ + # 返回resize函数,传入图像和size和interpolation参数 return util.resize(img, self.size, self.interpolation) - +# 将输入的RGB格式numpy.ndarray图像转换为BGR格式 class RgbToBgr(py_transforms.PyTensorOperation): - """ - Convert the input numpy.ndarray images from RGB to BGR. - - Args: - is_hwc (bool): If True, means the input image is in shape of (H, W, C) or (N, H, W, C). - Otherwise, it is in shape of (C, H, W) or (N, C, H, W). Default: False. - - Raises: - TypeError: If `is_hwc` is not of type bool. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.CenterCrop(20), - ... py_vision.ToTensor(), - ... py_vision.RgbToBgr()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - @check_rgb_to_bgr def __init__(self, is_hwc=False): self.is_hwc = is_hwc self.random = False def __call__(self, rgb_imgs): - """ - Call method. - - Args: - rgb_imgs (numpy.ndarray): RGB images to be converted. - - Returns: - numpy.ndarray, converted BGR images. - """ return util.rgb_to_bgrs(rgb_imgs, self.is_hwc) - +# 将输入的RGB格式numpy.ndarray图像转换为HSV格式 class RgbToHsv(py_transforms.PyTensorOperation): - """ - Convert the input numpy.ndarray images from RGB to HSV. - - Args: - is_hwc (bool): If True, means the input image is in shape of (H, W, C) or (N, H, W, C). - Otherwise, it is in shape of (C, H, W) or (N, C, H, W). Default: False. - - Raises: - TypeError: If `is_hwc` is not of type bool. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.CenterCrop(20), - ... py_vision.ToTensor(), - ... py_vision.RgbToHsv()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - @check_rgb_to_hsv def __init__(self, is_hwc=False): self.is_hwc = is_hwc self.random = False def __call__(self, rgb_imgs): - """ - Call method. - - Args: - rgb_imgs (numpy.ndarray): RGB images to be converted. - - Returns: - numpy.ndarray, converted HSV images. - """ return util.rgb_to_hsvs(rgb_imgs, self.is_hwc) - +# 在输入PIL图像的中心与四个角处分别裁剪指定尺寸大小的子图,并将其翻转图一并返回 class TenCrop(py_transforms.PyTensorOperation): - """ - Crop the given image into one central crop and four corners with the flipped version of these. - - Args: - size (Union[int, Sequence[int, int]]): The size of the cropped image. - If int is provided, a square of size (`size`, `size`) will be cropped with this value. - If Sequence[int, int] is provided, its two elements will be taken as the cropped height and width. - use_vertical_flip (bool, optional): If True, flip the images vertically. Otherwise, flip them - horizontally. Default: False. - - Raises: - TypeError: If `size` is not of type int or Sequence[int, int]. - TypeError: If `use_vertical_flip` is not of type bool. - ValueError: If `size` is not positive. - - Supported Platforms: - ``CPU`` - - Examples: - >>> import numpy - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.TenCrop(size=200), - ... # 4D stack of 10 images - ... lambda *images: numpy.stack([py_vision.ToTensor()(image) for image in images])]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义TenCrop类,接收一个size参数,use_vertical_flip参数 @check_ten_crop def __init__(self, size, use_vertical_flip=False): + # 如果size参数是int类型,则将size转换为元组,否则直接赋值 if isinstance(size, int): size = (size, size) self.size = size self.use_vertical_flip = use_vertical_flip + # 将random设置为False self.random = False + # 定义__call__方法,接收一个img参数 def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be cropped. - - Returns: - tuple, 10 cropped PIL.Image.Image, in order of top_left, top_right, bottom_left, bottom_right, center - of the original image and top_left, top_right, bottom_left, bottom_right, center of the flipped image. - """ + # 调用util.ten_crop函数,传入img和self.size,self.use_vertical_flip return util.ten_crop(img, self.size, self.use_vertical_flip) - +# 将已解码的numpy.ndarray图像转换为PIL图像 class ToPIL(py_transforms.PyTensorOperation): - """ - Convert the input decoded numpy.ndarray image to PIL Image. - - Note: - The conversion mode will be determined by the data type using :class:`PIL.Image.fromarray`. - - Raises: - TypeError: If the input image is not of type :class:`numpy.ndarray` or :class:`PIL.Image.Image`. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> # data is already decoded, but not in PIL Image format - >>> transforms_list = Compose([py_vision.ToPIL(), - ... py_vision.RandomHorizontalFlip(0.5), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - def __init__(self): self.random = False def __call__(self, img): - """ - Call method. - - Args: - img (numpy.ndarray): Decoded numpy.ndarray image to be converted to PIL.Image.Image. - - Returns: - PIL.Image.Image, converted PIL Image. - """ return util.to_pil(img) - +# 将输入PIL图像或numpy.ndarray图像转换为指定类型的numpy.ndarray图像, +# 图像的像素值范围将从[0, 255]放缩为[0.0, 1.0],shape将从调整为 class ToTensor(py_transforms.PyTensorOperation): - """ - Convert the input PIL Image or numpy.ndarray to numpy.ndarray of the desired dtype. At the same time, - the range of pixel value will be changed from [0, 255] to [0.0, 1.0] and the shape will be changed - from (H, W, C) to (C, H, W). - - Args: - output_type (numpy.dtype, optional): The desired dtype of the output image. Default: :class:`numpy.float32`. - - Raises: - TypeError: If the input image is not of type :class:`PIL.Image.Image` or :class:`numpy.ndarray`. - TypeError: If dimension of the input image is not 2 or 3. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> # create a list of transformations to be applied to the "image" column of each data row - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.RandomHorizontalFlip(0.5), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 初始化ToTensor类,输入参数output_type为np.float32 def __init__(self, output_type=np.float32): self.output_type = output_type self.random = False + # 定义__call__方法,输入参数img,返回util.to_tensor函数的输出 def __call__(self, img): - """ - Call method. - - Args: - img (Union[PIL.Image.Image, numpy.ndarray]): PIL.Image.Image or numpy.ndarray to be type converted. - - Returns: - numpy.ndarray, converted numpy.ndarray with desired type. - """ return util.to_tensor(img, self.output_type) - +# 将输入转换为指定的MindSpore数据类型或NumPy数据类型 class ToType(py_transforms.PyTensorOperation): - """ - Convert the input numpy.ndarray image to the desired dtype. - - Args: - output_type (numpy.dtype): The desired dtype of the output image, e.g. :class:`numpy.float32`. - - Raises: - TypeError: If the input image is not of type :class:`numpy.ndarray`. - - Supported Platforms: - ``CPU`` - - Examples: - >>> import numpy as np - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms_list =Compose([py_vision.Decode(), - ... py_vision.RandomHorizontalFlip(0.5), - ... py_vision.ToTensor(), - ... py_vision.ToType(np.float32)]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + # 定义ToType类,设置output_type参数 def __init__(self, output_type): self.output_type = output_type + # 定义random变量,默认为False self.random = False + # 定义__call__方法,将img参数转换为output_type类型 def __call__(self, img): - """ - Call method. - - Args: - img (numpy.ndarray): numpy.ndarray to be dtype converted. - - Returns: - numpy.ndarray, converted numpy.ndarray with desired dtype. - """ return util.to_type(img, self.output_type) - +# 从指定序列中均匀采样一批数据处理操作,并按顺序随机执行,即采样出的操作也可能不被执行 class UniformAugment(py_transforms.PyTensorOperation): - """ - Uniformly select a number of transformations from a sequence and apply them - sequentially and randomly, which means that there is a chance that a chosen - transformation will not be applied. - - All transformations in the sequence require the output type to be the same as - the input. Thus, the latter one can deal with the output of the previous one. - - Args: - transforms (Sequence): Sequence of transformations to select from. - num_ops (int, optional): Number of transformations to be sequentially and randomly applied. Default: 2. - - Raises: - TypeError: If `transforms` is not a sequence of data processing operations. - TypeError: If `num_ops` is not of type int. - ValueError: If `num_ops` is not positive. - - Supported Platforms: - ``CPU`` - - Examples: - >>> from mindspore.dataset.transforms.py_transforms import Compose - >>> - >>> transforms = [py_vision.CenterCrop(64), - ... py_vision.RandomColor(), - ... py_vision.RandomSharpness(), - ... py_vision.RandomRotation(30)] - >>> transforms_list = Compose([py_vision.Decode(), - ... py_vision.UniformAugment(transforms), - ... py_vision.ToTensor()]) - >>> # apply the transform to dataset through map function - >>> image_folder_dataset = image_folder_dataset.map(operations=transforms_list, - ... input_columns="image") - """ - + ''' + 使用随机操作增强图像 + ''' @check_uniform_augment_py def __init__(self, transforms, num_ops=2): + ''' + :param transforms: 增强函数列表 + :param num_ops: 增强次数 + ''' self.transforms = transforms self.num_ops = num_ops self.random = False def __call__(self, img): - """ - Call method. - - Args: - img (PIL.Image.Image): Image to be transformed. - - Returns: - PIL.Image.Image, transformed image. - """ + ''' + :param img: 图像 + :return: 增强后的图像 + ''' return util.uniform_augment(img, self.transforms.copy(), self.num_ops) - def not_random(func): """ Specify the function as "not random", i.e., it produces deterministic result. diff --git a/mindspore/python/mindspore/dataset/vision/py_transforms_util.py b/mindspore/python/mindspore/dataset/vision/py_transforms_util.py index ab95e2d8c37..aca54b031ac 100644 --- a/mindspore/python/mindspore/dataset/vision/py_transforms_util.py +++ b/mindspore/python/mindspore/dataset/vision/py_transforms_util.py @@ -27,100 +27,88 @@ from ..core.py_util_helpers import is_numpy augment_error_message = "img should be PIL image. Got {}. Use Decode() for encoded data or ToPIL() for decoded data." - +#检测输入的图像是否为PIL格式 def is_pil(img): - """ - Check if the input image is PIL format. - - Args: - img: Image to be checked. - - Returns: - bool, True if input is PIL.Image.Image. - """ + #返回一个bool值:输入的图像是否为Image.Image类型 return isinstance(img, Image.Image) def normalize(img, mean, std, pad_channel=False, dtype="float32"): - """ - Normalize the image between [0, 1] with respect to mean and standard deviation. - - Args: - img (numpy.ndarray): Image array of shape CHW to be normalized. - mean (list): List of mean values for each channel, w.r.t channel order. - std (list): List of standard deviations for each channel, w.r.t. channel order. - pad_channel (bool): Whether to pad a extra channel with value zero. - dtype (str): Output datatype of normalize, only worked when pad_channel is True. (default is "float32") - - Returns: - img (numpy.ndarray), Normalized image. - """ + # 如果参数img不是数组图像,抛出类型错误异常 if not is_numpy(img): raise TypeError("img should be NumPy image. Got {}.".format(type(img))) - + # 如果参数img不是彩色图像,抛出类型错误异常 if img.ndim != 3: raise TypeError('img dimension should be 3. Got {}.'.format(img.ndim)) - + # 如果img的数据类型是整型,则抛出未成功实施某种函数异常 if np.issubdtype(img.dtype, np.integer): raise NotImplementedError("Unsupported image datatype: [{}], pls execute [ToTensor] before [Normalize]." .format(img.dtype)) - + + # 将输入图的通道数num_channels赋值为图像的高度 num_channels = img.shape[0] # shape is (C, H, W) - + # 如果图像每个通道的均值组成列表的长度不等于其标准差组成列表的长度,抛出数值错误异常 if len(mean) != len(std): raise ValueError("Length of mean and std must be equal.") - # if length equal to 1, adjust the mean and std arrays to have the correct - # number of channels (replicate the values) + # 如果长度等于1,调整mean和std数组赋值为正确的输入图通道数num_channels值(复制数值) if len(mean) == 1: + # 将mean赋值为输入图的通道数num_channels的值 mean = [mean[0]] * num_channels + # 将std赋值为输入图的通道数num_channels的值 std = [std[0]] * num_channels + # 如果mean的长度与图像的通道数不等,则抛出数值错误异常 elif len(mean) != num_channels: raise ValueError("Length of mean and std must both be 1 or equal to the number of channels({0})." .format(num_channels)) + + # 将均值转换为numpy数组 mean = np.array(mean, dtype=img.dtype) + # 将标准差转换为numpy数组 std = np.array(std, dtype=img.dtype) + # 将图像减去均值和标准差 image = (img - mean[:, None, None]) / std[:, None, None] if pad_channel: + # 如果pad_channel为True,则将image的第一个维度拼接到image的最后一个维度上 zeros = np.zeros([1, image.shape[1], image.shape[2]], dtype=np.float32) image = np.concatenate((image, zeros), axis=0) + # 如果dtype为float16,则将image的第一个维度转换为float32 if dtype == "float16": + # 将image转换为float16类型 image = image.astype(np.float16) + # 如果pad_channel为True,则将image的第一个维度值为0,并将image和zeros合并 + zeros = np.zeros([1, image.shape[1], image.shape[2]], dtype=np.float32) + image = np.concatenate((image, zeros), axis=0) + # 如果dtype为float16,则将image转换为float16类型 + if dtype == "float16": + image = image.astype(np.float16) + # 返回image return image def decode(img): - """ - Decode the input image to PIL Image format in RGB mode. - - Args: - img: Image to be decoded. - - Returns: - img (PIL.Image.Image), Decoded image in RGB mode. - """ - + ''' + 将图片解码为RGB格式 + :param img: 图片 + :return: 解码后的图片 + ''' try: data = io.BytesIO(img) + # 将图片转换为RGB格式 img = Image.open(data) + # 返回RGB格式的解码图片 return img.convert('RGB') + # 如果打开失败,抛出数值错误异常 except IOError as e: raise ValueError("{0}\n: Failed to decode given image.".format(e)) + + # 如果图片已经被解码,抛出数值错误异常 except AttributeError as e: raise ValueError("{0}\n: Failed to decode, Image might already be decoded.".format(e)) - def hwc_to_chw(img): - """ - Transpose the input image; shape (H, W, C) to shape (C, H, W). - - Args: - img (numpy.ndarray): Image to be converted. - - Returns: - img (numpy.ndarray), Converted image. - """ + # 将输入图像的shape从 转换为 if not is_numpy(img): raise TypeError('img should be NumPy array. Got {}.'.format(type(img))) if img.ndim != 3: @@ -129,42 +117,44 @@ def hwc_to_chw(img): def to_tensor(img, output_type): - """ - Change the input image (PIL.Image.Image or numpy.ndarray) to numpy.ndarray format. - - Args: - img (Union[PIL.Image.Image, numpy.ndarray]): Image to be converted. - output_type: The datatype of the NumPy output. e.g. np.float32 - - Returns: - img (numpy.ndarray), Converted image. - """ + '''将图像转换为tensor格式 + + 参数: + img:图像,可以是PIL图像或NumPy数组 + output_type:输出类型 + 返回: + 转换后的tensor格式 + ''' if not (is_pil(img) or is_numpy(img)): raise TypeError("img should be PIL image or NumPy array. Got {}.".format(type(img))) + # 如果图像是PIL图像,则将其转换为NumPy数组 img = np.asarray(img) + # 如果图像的维度不是2或3,则抛出异常 if img.ndim not in (2, 3): raise TypeError("img dimension should be 2 or 3. Got {}.".format(img.ndim)) + # 如果图像的维度是2,则将其转换为3维 if img.ndim == 2: img = img[:, :, None] + # 将图像转换为CHW格式 img = hwc_to_chw(img) + # 将图像转换为输出类型 img = img / 255. return to_type(img, output_type) def to_pil(img): - """ - Convert the input image to PIL format. - - Args: - img: Image to be converted. - - Returns: - img (PIL.Image.Image), Converted image. - """ + '''将图片转换为PIL格式 + + 参数: + img : 需要转化为PIL格式的图片 + + 返回: + PIL Image: PIL格式的图片. + ''' if not is_pil(img): if not isinstance(img, np.ndarray): raise TypeError("The input of ToPIL should be ndarray. Got {}".format(type(img))) @@ -173,15 +163,11 @@ def to_pil(img): def horizontal_flip(img): - """ - Flip the input image horizontally. - - Args: - img (PIL.Image.Image): Image to be flipped horizontally. - - Returns: - PIL.Image.Image, Horizontally flipped image. - """ + ''' + 水平翻转图像 + :param img: PIL图像 + :return: 水平翻转后的图像 + ''' if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) @@ -189,15 +175,11 @@ def horizontal_flip(img): def vertical_flip(img): - """ - Flip the input image vertically. - - Args: - img (PIL.Image.Image): Image to be flipped vertically. - - Returns: - PIL.Image.Image, Vertically flipped image. - """ + ''' + 对图像进行垂直翻转 + :param img: PIL图像 + :return: 垂直翻转后的图像 + ''' if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) @@ -205,17 +187,13 @@ def vertical_flip(img): def random_horizontal_flip(img, prob): - """ - Randomly flip the input image horizontally. - - Args: - img (PIL.Image.Image): Image to be flipped. - If the given probability is above the random probability, then the image is flipped. - prob (float): Probability of the image being flipped. - - Returns: - PIL.Image.Image, Converted image. - """ + ''' + 随机水平翻转图片 + :param img: PIL图片 + :param prob: 概率 + :return: 水平翻转后的图片 + ''' + if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) @@ -225,17 +203,13 @@ def random_horizontal_flip(img, prob): def random_vertical_flip(img, prob): - """ - Randomly flip the input image vertically. - - Args: - img (PIL.Image.Image): Image to be flipped. - If the given probability is above the random probability, then the image is flipped. - prob (float): Probability of the image being flipped. - - Returns: - PIL.Image.Image, Converted image. - """ + ''' + 参数: + img:图像 + prob:随机翻转概率 + 返回: + img:随机翻转后的图像 + ''' if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) @@ -266,85 +240,72 @@ def crop(img, top, left, height, width): def resize(img, size, interpolation=Inter.BILINEAR): - """ - Resize the input PIL Image to desired size. - - Args: - img (PIL.Image.Image): Image to be resized. - size (Union[int, sequence]): The output size of the resized image. - If size is an integer, smaller edge of the image will be resized to this value with - the same image aspect ratio. - If size is a sequence of (height, width), this will be the desired output size. - interpolation (interpolation mode): Image interpolation mode. Default is Inter.BILINEAR = 2. - - Returns: - PIL.Image.Image, resized image. - """ + '''对输入图像调整为给定的尺寸大小 + 参数: + img : 被调整的图像 + size : 期望输出大小. + interpolation : 期望的插值 + 返回: + img:调整后的图像 + ''' + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) + # 判断size是否是int或者list/tuple if not (isinstance(size, int) or (isinstance(size, (list, tuple)) and len(size) == 2)): raise TypeError('Size should be a single number or a list/tuple (h, w) of length 2.' 'Got {}.'.format(size)) + # size是int if isinstance(size, int): + # 获取图片的宽度和高度 img_width, img_height = img.size + # 获取图片的宽高比 aspect_ratio = img_width / img_height # maintain the aspect ratio + # 判断图片的宽度是否小于高度 if (img_width <= img_height and img_width == size) or \ (img_height <= img_width and img_height == size): return img + # 判断图片的宽度是否小于高度的比例 if img_width < img_height: + # 设置输出宽度和高度 out_width = size out_height = int(size / aspect_ratio) + # 返回缩放后的图片 return img.resize((out_width, out_height), interpolation) + # 设置输出高度和宽度 out_height = size out_width = int(size * aspect_ratio) + # 返回缩放后的图片 return img.resize((out_width, out_height), interpolation) + # 返回缩放后的图片 return img.resize(size[::-1], interpolation) + +# 定义一个函数,用于对图像进行中心裁剪 def center_crop(img, size): - """ - Crop the input PIL Image at the center to the given size. - - Args: - img (PIL.Image.Image): Image to be cropped. - size (Union[int, tuple]): The size of the crop box. - If size is an integer, a square crop of size (size, size) is returned. - If size is a sequence of length 2, it should be (height, width). - - Returns: - PIL.Image.Image, cropped image. - """ + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) + # 如果输入的size是一个整数,则将其转换为元组 if isinstance(size, int): size = (size, size) + # 获取图像的宽度和高度 img_width, img_height = img.size + # 计算裁剪的高度和宽度 crop_height, crop_width = size + # 计算裁剪的上边距 crop_top = int(round((img_height - crop_height) / 2.)) + # 计算裁剪的左边距 crop_left = int(round((img_width - crop_width) / 2.)) + # 返回裁剪后的图像 return crop(img, crop_top, crop_left, crop_height, crop_width) def random_resize_crop(img, size, scale, ratio, interpolation=Inter.BILINEAR, max_attempts=10): - """ - Crop the input PIL Image to a random size and aspect ratio. - - Args: - img (PIL.Image.Image): Image to be randomly cropped and resized. - size (Union[int, sequence]): The size of the output image. - If size is an integer, a square crop of size (size, size) is returned. - If size is a sequence of length 2, it should be (height, width). - scale (tuple): Range (min, max) of respective size of the original size to be cropped. - ratio (tuple): Range (min, max) of aspect ratio to be cropped. - interpolation (interpolation mode): Image interpolation mode. Default is Inter.BILINEAR = 2. - max_attempts (int): The maximum number of attempts to propose a valid crop_area. Default 10. - If exceeded, fall back to use center_crop instead. - - Returns: - PIL.Image.Image, randomly cropped and resized image. - """ + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) if isinstance(size, int): @@ -353,10 +314,11 @@ def random_resize_crop(img, size, scale, ratio, interpolation=Inter.BILINEAR, ma size = size else: raise TypeError("Size should be a single integer or a list/tuple (h, w) of length 2.") - + # 如果缩放范围大于等于放大范围,或者缩放比例大于放大比例 if scale[0] > scale[1] or ratio[0] > ratio[1]: raise ValueError("Range should be in the order of (min, max).") - + + # 输入转因子函数 def _input_to_factor(img, scale, ratio): img_width, img_height = img.size img_area = img_width * img_height @@ -378,90 +340,99 @@ def random_resize_crop(img, size, scale, ratio, interpolation=Inter.BILINEAR, ma # exceeding max_attempts, use center crop img_ratio = img_width / img_height + # 如果图片宽高比小于指定的比例,则宽度等于图片宽度,高度等于图片宽度的指定比例乘以宽度 if img_ratio < ratio[0]: width = img_width height = int(round(width / ratio[0])) + # 如果图片宽高比大于指定的比例,则高度等于图片高度,宽度等于图片高度的指定比例乘以高度 elif img_ratio > ratio[1]: height = img_height width = int(round(height * ratio[1])) + # 如果图片宽高比相等,则宽度等于图片宽度,高度等于图片高度 else: width = img_width height = img_height + # 计算图片的上下左右边界 top = int(round((img_height - height) / 2.)) left = int(round((img_width - width) / 2.)) + # 返回图片的上下左右边界 return top, left, height, width top, left, height, width = _input_to_factor(img, scale, ratio) + # 将图像边界框裁剪 img = crop(img, top, left, height, width) + # 将图像缩放 img = resize(img, size, interpolation) + # 返回缩放后的图像 return img - def random_crop(img, size, padding, pad_if_needed, fill_value, padding_mode): - """ - Crop the input PIL Image at a random location. - - Args: - img (PIL.Image.Image): Image to be randomly cropped. - size (Union[int, sequence]): The output size of the cropped image. - If size is an integer, a square crop of size (size, size) is returned. - If size is a sequence of length 2, it should be (height, width). - padding (Union[int, sequence], optional): The number of pixels to pad the image. - If a single number is provided, it pads all borders with this value. - If a tuple or lists of 2 values are provided, it pads the (left and top) - with the first value and (right and bottom) with the second value. - If 4 values are provided as a list or tuple, - it pads the left, top, right and bottom respectively. - Default is None. - pad_if_needed (bool): Pad the image if either side is smaller than - the given output size. Default is False. - fill_value (Union[int, tuple]): The pixel intensity of the borders if - the padding_mode is 'constant'. If it is a 3-tuple, it is used to - fill R, G, B channels respectively. - padding_mode (str): The method of padding. Can be any of ['constant', 'edge', 'reflect', 'symmetric']. - - - 'constant', means it fills the border with constant values - - 'edge', means it pads with the last value on the edge - - 'reflect', means it reflects the values on the edge omitting the last - value of edge - - 'symmetric', means it reflects the values on the edge repeating the last - value of edge - - Returns: - PIL.Image.Image, cropped image. - """ + ''' + 随机裁剪图片 + :param img: PIL图片 + :param size: 裁剪大小 + :param padding: 填充 + :param pad_if_needed: 是否填充 + :param fill_value: 填充值 + :param padding_mode: 填充模式 + :return: 裁剪后的图片 + ''' + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) + # 如果size是整形,转化为列表 if isinstance(size, int): size = (size, size) + # 如果size是列表,直接使用 elif isinstance(size, (tuple, list)) and len(size) == 2: size = size + # 如果都不是,抛出类型错误异常 else: raise TypeError("Size should be a single integer or a list/tuple (h, w) of length 2.") def _input_to_factor(img, size): + ''' + 将图片输入到因子中 + :param img: 图片 + :param size: 因子大小 + :return: top, left, height, width + ''' + # 获取图片的长宽 img_width, img_height = img.size + # 把宽高赋值为size,此处宽高为裁剪后宽高 height, width = size + # 如果裁剪后宽高大于图片宽高,抛出数值错误异常 if height > img_height or width > img_width: raise ValueError("Crop size {} is larger than input image size {}.".format(size, (img_height, img_width))) - + # 如果裁剪后宽高等于于图片宽高,返回top, left, height, width if width == img_width and height == img_height: return 0, 0, img_height, img_width - + + # 设置随机生成的top坐标 top = random.randint(0, img_height - height) + # 设置随机生成的left坐标 left = random.randint(0, img_width - width) + # 设置随机生成的height值 + height = random.randint(1, 5) + # 设置随机生成的width值 + width = random.randint(1, 5) return top, left, height, width - + + # 如果padding不为空,则使用padding函数对图片进行填充 if padding is not None: img = pad(img, padding, fill_value, padding_mode) # pad width when needed, img.size (width, height), crop size (height, width) + # 如果pad_if_needed为True,则使用pad函数对图片进行填充,并且计算图片的高度和宽度 if pad_if_needed and img.size[0] < size[1]: img = pad(img, (size[1] - img.size[0], 0), fill_value, padding_mode) # pad height when needed + # 如果pad_if_needed为True,则使用pad函数对图片进行填充,并且计算图片的高度和宽度 if pad_if_needed and img.size[1] < size[0]: img = pad(img, (0, size[0] - img.size[1]), fill_value, padding_mode) + # 计算图片的top, left, height, width top, left, height, width = _input_to_factor(img, size) + # 使用crop函数对图片进行裁剪,并返回裁剪后的图片 return crop(img, top, left, height, width) @@ -486,22 +457,20 @@ def adjust_brightness(img, brightness_factor): def adjust_contrast(img, contrast_factor): - """ - Adjust contrast of an image. - - Args: - img (PIL.Image.Image): PIL Image to be adjusted. - contrast_factor (float): A non negative number indicated the factor by which - the contrast is adjusted. 0 gives a solid gray image, 1 gives the original. - - Returns: - PIL.Image.Image, contrast adjusted image. - """ + ''' + 调整图像的对比度 + :param img: PIL Image + :param contrast_factor: float + :return: PIL Image + ''' + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) + # 使用enhancer对图像进行对比度增强 enhancer = ImageEnhance.Contrast(img) img = enhancer.enhance(contrast_factor) + # 返回img PIL格式 return img @@ -566,16 +535,7 @@ def adjust_hue(img, hue_factor): def to_type(img, output_type): - """ - Convert the NumPy image array to desired NumPy dtype. - - Args: - img (numpy): NumPy image to cast to desired NumPy dtype. - output_type (Numpy datatype): NumPy dtype to cast to. - - Returns: - img (numpy.ndarray), Converted image. - """ + '''将img转换为output_type类型''' if not is_numpy(img): raise TypeError("img should be NumPy image. Got {}.".format(type(img))) @@ -619,207 +579,199 @@ def rotate(img, angle, resample, expand, center, fill_value): def random_color_adjust(img, brightness, contrast, saturation, hue): - """ - Randomly adjust the brightness, contrast, saturation, and hue of an image. - - Args: - img (PIL.Image.Image): Image to have its color adjusted randomly. - brightness (Union[float, tuple]): Brightness adjustment factor. Cannot be negative. - If it is a float, the factor is uniformly chosen from the range [max(0, 1-brightness), 1+brightness]. - If it is a sequence, it should be [min, max] for the range. - contrast (Union[float, tuple]): Contrast adjustment factor. Cannot be negative. - If it is a float, the factor is uniformly chosen from the range [max(0, 1-contrast), 1+contrast]. - If it is a sequence, it should be [min, max] for the range. - saturation (Union[float, tuple]): Saturation adjustment factor. Cannot be negative. - If it is a float, the factor is uniformly chosen from the range [max(0, 1-saturation), 1+saturation]. - If it is a sequence, it should be [min, max] for the range. - hue (Union[float, tuple]): Hue adjustment factor. - If it is a float, the range will be [-hue, hue]. Value should be 0 <= hue <= 0.5. - If it is a sequence, it should be [min, max] where -0.5 <= min <= max <= 0.5. - - Returns: - PIL.Image.Image, image after random adjustment of its color. - """ + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) def _input_to_factor(value, input_name, center=1, bound=(0, float('inf')), non_negative=True): + ''' + 将输入值转换为因子 + :param value: 输入值 + :param input_name: 输入名称 + :param center: 因子中心 + :param bound: 因子边界 + :param non_negative: 是否为非负数 + :return: 因子 + ''' if isinstance(value, numbers.Number): + # 如果value是数字,则检查value是否小于0 if value < 0: raise ValueError("The input value of {} cannot be negative.".format(input_name)) # convert value into a range + # 将value转换为范围 value = [center - value, center + value] + # 如果non_negative为True,则将value的最小值设置为0 if non_negative: value[0] = max(0, value[0]) elif isinstance(value, (list, tuple)) and len(value) == 2: + # 如果value是一个列表或元组,且长度为2,则检查value是否在bound范围内 if not bound[0] <= value[0] <= value[1] <= bound[1]: raise ValueError("Please check your value range of {} is valid and " "within the bound {}.".format(input_name, bound)) else: + # 如果value不是数字,则抛出TypeError raise TypeError("Input of {} should be either a single value, or a list/tuple of " "length 2.".format(input_name)) + # 产生一个随机数 factor = random.uniform(value[0], value[1]) + # 返回factor return factor - + + # 将输入的值转换为因子 brightness_factor = _input_to_factor(brightness, 'brightness') contrast_factor = _input_to_factor(contrast, 'contrast') - saturation_factor = _input_to_factor(saturation, 'saturation') + saturation_factor = _input_to_factor(saturation,'saturation') hue_factor = _input_to_factor(hue, 'hue', center=0, bound=(-0.5, 0.5), non_negative=False) + # 创建一个空列表 transforms = [] + # 将brightness_factor添加到transforms列表中 transforms.append(lambda img: adjust_brightness(img, brightness_factor)) + # 将contrast_factor添加到transforms列表中 transforms.append(lambda img: adjust_contrast(img, contrast_factor)) + # 将saturation_factor添加到transforms列表中 transforms.append(lambda img: adjust_saturation(img, saturation_factor)) + # 将hue_factor添加到transforms列表中 transforms.append(lambda img: adjust_hue(img, hue_factor)) # apply color adjustments in a random order + # 随机洗牌 + # 从transforms列表中随机洗牌 random.shuffle(transforms) + # 遍历transforms列表中的每一个元素 for transform in transforms: + # 将transform函数的输出值赋值给img img = transform(img) + # 返回img return img def random_lighting(img, alpha): - """ - Add AlexNet-style PCA-based noise to an image. - - Args: - img (PIL.Image.Image): Image to be added AlexNet-style PCA-based noise. - alpha (float, optional): Intensity of the image. - - Returns: - PIL.Image.Image, image with noise added. - """ + ''' + 随机添加亮度、饱和度、对比度和颜色的效果 + :param img: PIL Image + :param alpha: 添加的亮度 + :return: PIL Image + ''' if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) - if img.mode != 'RGB': + if img.mode!= 'RGB': img = img.convert("RGB") + # 随机生成一个均匀分布的值 alpha_r = np.random.normal(loc=0.0, scale=alpha) alpha_g = np.random.normal(loc=0.0, scale=alpha) alpha_b = np.random.normal(loc=0.0, scale=alpha) + # 将alpha_r, alpha_g, alpha_b分别转换为[0, 1]之间的数 table = np.array([ [55.46 * -0.5675, 4.794 * 0.7192, 1.148 * 0.4009], [55.46 * -0.5808, 4.794 * -0.0045, 1.148 * -0.8140], [55.46 * -0.5836, 4.794 * -0.6948, 1.148 * 0.4203] ]) + # 计算pca_r, pca_g, pca_b pca_r = table[0][0] * alpha_r + table[0][1] * alpha_g + table[0][2] * alpha_b pca_g = table[1][0] * alpha_r + table[1][1] * alpha_g + table[1][2] * alpha_b pca_b = table[2][0] * alpha_r + table[2][1] * alpha_g + table[2][2] * alpha_b + # 将pca_r, pca_g, pca_b转换为PIL Image img_arr = np.array(img).astype(np.float64) img_arr[:, :, 0] += pca_r img_arr[:, :, 1] += pca_g img_arr[:, :, 2] += pca_b + # 将img_arr中的值小于0或者大于255的值赋值为0 img_arr = np.uint8(np.minimum(np.maximum(img_arr, 0), 255)) + # 将img_arr转换为PIL Image img = Image.fromarray(img_arr) return img def random_rotation(img, degrees, resample, expand, center, fill_value): - """ - Rotate the input PIL Image by a random angle. - - See . - - Args: - img (PIL.Image.Image): Image to be rotated. - degrees (Union[int, float, sequence]): Range of random rotation degrees. - If `degrees` is a number, the range will be converted to (-degrees, degrees). - If `degrees` is a sequence, it should be (min, max). - resample (Union[Inter.NEAREST, Inter.BILINEAR, Inter.BICUBIC], optional): An optional resampling filter. - If omitted, or if the image has mode "1" or "P", it is set to be Inter.NEAREST. - expand (bool, optional): Optional expansion flag. If set to True, expand the output - image to make it large enough to hold the entire rotated image. - If set to False or omitted, make the output image the same size as the input. - Note that the expand flag assumes rotation around the center and no translation. - center (tuple, optional): Optional center of rotation (a 2-tuple). - Origin is the top left corner. - fill_value (Union[int, tuple]): Optional fill color for the area outside the rotated image. - If it is a 3-tuple, it is used for R, G, B channels respectively. - If it is an integer, it is used for all RGB channels. - - Returns: - PIL.Image.Image, Rotated image. - """ + ''' + 随机旋转图像 + :param img: 图像 + :param degrees: 旋转角度 + :param resample: 是否重采样 + :param expand: 是否展开 + :param center: 坐标 + :param fill_value: 填充值 + :return: 旋转后的图像 + ''' + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) - + #检测degree格式是否符合标准,转化成可用的列表 if isinstance(degrees, numbers.Number): if degrees < 0: raise ValueError("If degrees is a single number, it cannot be negative.") degrees = (-degrees, degrees) elif isinstance(degrees, (list, tuple)): - if len(degrees) != 2: + if len(degrees)!= 2: raise ValueError("If degrees is a sequence, the length must be 2.") else: raise TypeError("Degrees must be a single non-negative number or a sequence.") - + #随机旋转角度 angle = random.uniform(degrees[0], degrees[1]) return rotate(img, angle, resample, expand, center, fill_value) def five_crop(img, size): - """ - Generate 5 cropped images (one central and four corners). - - Args: - img (PIL.Image.Image): PIL Image to be cropped. - size (Union[int, sequence]): The output size of the crop. - If size is an integer, a square crop of size (size, size) is returned. - If size is a sequence of length 2, it should be (height, width). - - Returns: - img_tuple (tuple), a tuple of 5 PIL Image - (top_left, top_right, bottom_left, bottom_right, center). - """ + ''' + 裁剪图像,截取图像的五个像素 + :param img: 原始图像 + :param size: 图像的大小 + :return: 图像的五个像素 + ''' + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) + # 如果size是整数,则将size转换为元组 if isinstance(size, int): size = (size, size) + # 如果size是元组或列表并且size的长度大于2,size不变 elif isinstance(size, (tuple, list)) and len(size) == 2: size = size + #除了以上两种情况,抛出类型错误异常 else: raise TypeError("Size should be a single number or a list/tuple (h, w) of length 2.") # PIL.Image.Image.size returns in (width, height) order img_width, img_height = img.size + # 获取图像的宽度和高度 crop_height, crop_width = size + # 计算裁剪图像的高度和宽度 if crop_height > img_height or crop_width > img_width: + # 如果裁剪图像的高度和宽度大于图像的高度和宽度,抛出数值错误异常 raise ValueError("Crop size {} is larger than input image size {}.".format(size, (img_height, img_width))) + # 计算中心裁剪图像 center = center_crop(img, (crop_height, crop_width)) + # 计算左上角裁剪图像 top_left = img.crop((0, 0, crop_width, crop_height)) + # 计算右上角裁剪图像 top_right = img.crop((img_width - crop_width, 0, img_width, crop_height)) + # 计算左下角裁剪图像 bottom_left = img.crop((0, img_height - crop_height, crop_width, img_height)) + # 计算右下角裁剪图像 bottom_right = img.crop((img_width - crop_width, img_height - crop_height, img_width, img_height)) + # 返回左上角,右上角,左下角,右下角,中心裁剪图像 return top_left, top_right, bottom_left, bottom_right, center - def ten_crop(img, size, use_vertical_flip=False): - """ - Generate 10 cropped images (first 5 from FiveCrop, second 5 from their flipped version). - - The default is horizontal flipping, use_vertical_flip=False. - - Args: - img (PIL.Image.Image): PIL Image to be cropped. - size (Union[int, sequence]): The output size of the crop. - If size is an integer, a square crop of size (size, size) is returned. - If size is a sequence of length 2, it should be (height, width). - use_vertical_flip (bool): Flip the image vertically instead of horizontally if set to True. - - Returns: - tuple[PIL.Image.Image], a tuple of 10 PIL Image - (top_left, top_right, bottom_left, bottom_right, center) of original image + - (top_left, top_right, bottom_left, bottom_right, center) of flipped image. - """ + ''' + 对图片进行10抽取,抽取的每一张图片都是一个5*5的矩形,每一个矩形都是一个图片 + :param img: PIL图片 + :param size: 抽取的图片大小 + :param use_vertical_flip: 是否使用垂直翻转 + :return: 一个包含10张图片的列表 + ''' + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) - + # 如果size是整数,则将size转换为元组 if isinstance(size, int): size = (size, size) + # 如果size是元组或列表并且size的长度大于2,size不变 elif isinstance(size, (tuple, list)) and len(size) == 2: size = size else: @@ -827,123 +779,126 @@ def ten_crop(img, size, use_vertical_flip=False): first_five_crop = five_crop(img, size) + # 如果使用垂直翻转,则将图片变换为长宽比例不变的图片 if use_vertical_flip: - img = vertical_flip(img) + img = vertical_flip(img) + # 否则,将图片变换为水平翻转的图片 else: img = horizontal_flip(img) + # 将图片进行五折裁剪,并将五折裁剪的结果添加到第一次五折裁剪的结果中 second_five_crop = five_crop(img, size) + # 返回第一次五折裁剪的结果和第二次五折裁剪的结果 return first_five_crop + second_five_crop def grayscale(img, num_output_channels): - """ - Convert the input PIL Image to grayscale image. - - Args: - img (PIL.Image.Image): PIL Image to be converted to grayscale. - num_output_channels (int): Number of channels of the output grayscale image (1 or 3). - - Returns: - PIL.Image.Image, grayscaled image. - """ + ''' + 将图像转换为灰度图像 + :param img: 图像 + :param num_output_channels: 灰度图像的通道数 + :return: 灰度图像 + ''' + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) + # 如果灰度图像的通道数为1,将图像转换为灰度图像 if num_output_channels == 1: img = img.convert('L') + # 如果灰度图像的通道数为3,将图像转换为灰度图像,每个通道都是相同的灰度图像 elif num_output_channels == 3: - # each channel is the same grayscale layer img = img.convert('L') - np_gray = np.array(img, dtype=np.uint8) - np_img = np.dstack([np_gray, np_gray, np_gray]) - img = Image.fromarray(np_img, 'RGB') + np_gray = np.array(img, dtype=np.uint8)# 将图片转化为8位像素灰度图 + np_img = np.dstack([np_gray, np_gray, np_gray]) # 将图像转换为灰度图 + img = Image.fromarray(np_img, 'RGB')# 将灰度图像转换为RGB图像 + # 如果灰度图像的通道数不为1或3,抛出数值错误异常 else: raise ValueError('num_output_channels should be either 1 or 3. Got {}.'.format(num_output_channels)) - + # 返回转化完的灰度图 return img def pad(img, padding, fill_value, padding_mode): - """ - Pad the image according to padding parameters. - - Args: - img (PIL.Image.Image): Image to be padded. - padding (Union[int, sequence], optional): The number of pixels to pad the image. - If a single number is provided, it pads all borders with this value. - If a tuple or lists of 2 values are provided, it pads the (left and top) - with the first value and (right and bottom) with the second value. - If 4 values are provided as a list or tuple, - it pads the left, top, right and bottom respectively. - Default is None. - fill_value (Union[int, tuple]): The pixel intensity of the borders if - the padding_mode is "constant". If it is a 3-tuple, it is used to - fill R, G, B channels respectively. - padding_mode (str): The method of padding. Can be any of ['constant', 'edge', 'reflect', 'symmetric']. - - - 'constant', means it fills the border with constant values - - 'edge', means it pads with the last value on the edge - - 'reflect', means it reflects the values on the edge omitting the last - value of edge - - 'symmetric', means it reflects the values on the edge repeating the last - value of edge - - Returns: - PIL.Image.Image, padded image. - """ + ''' + 对图像进行补全处理 + :param img: 图像 + :param padding: 填充长度 + :param fill_value: 填充值 + :param padding_mode: 填充模式 + :return: 填充后的图像 + ''' + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) - + # 如果填充长度为一个数字,则图片填充的顶部底部左边右边全为此padding值 if isinstance(padding, numbers.Number): top = bottom = left = right = padding - + # 如果填充长度为一个列表 elif isinstance(padding, (tuple, list)): + # 列表长度为2,顶部底部填充长度为第一个值,左边右边填充长度为第二个值 if len(padding) == 2: left = top = padding[0] right = bottom = padding[1] + # 列表长度为4,则四个值分别为左边,顶部,右边,底部 elif len(padding) == 4: left = padding[0] top = padding[1] right = padding[2] bottom = padding[3] + # 其他情况,抛出数值错误异常 else: raise ValueError("The size of the padding list or tuple should be 2 or 4.") else: raise TypeError("Padding can be any of: a number, a tuple or list of size 2 or 4.") - + + #判断填充值是否正确,填充值是否是单个数值或列表 if not isinstance(fill_value, (numbers.Number, str, tuple)): raise TypeError("fill_value can be any of: an integer, a string or a tuple.") - + # 判断填充模式是否正确,填充模式是否为恒定、边缘、映射、对称 if padding_mode not in ['constant', 'edge', 'reflect', 'symmetric']: raise ValueError("Padding mode should be 'constant', 'edge', 'reflect', or 'symmetric'.") + # 如果填充模式为'constant' if padding_mode == 'constant': + + # 如果图像模式为P,则获取图像的调色板 if img.mode == 'P': palette = img.getpalette() + # 将图像填充为左上角,右下角,填充值 image = ImageOps.expand(img, border=(left, top, right, bottom), fill=fill_value) + # 将调色板替换图像 image.putpalette(palette) return image + # 否则,将图像填充为左上角,右下角,填充值,直接返回图像 return ImageOps.expand(img, border=(left, top, right, bottom), fill=fill_value) if img.mode == 'P': + # 获取图像的调色板 palette = img.getpalette() + # 将图像转换为numpy数组 img = np.asarray(img) + # 将图像填充到指定的位置 img = np.pad(img, ((top, bottom), (left, right)), padding_mode) + # 将图像转换为PIL图像 img = Image.fromarray(img) + # 将调色板替换图像的调色板 img.putpalette(palette) + # 返回替换后的图像 return img img = np.asarray(img) + # 如果img的维度为3,则在图像的左上角填充0 if len(img.shape) == 3: img = np.pad(img, ((top, bottom), (left, right), (0, 0)), padding_mode) + # 如果img的维度为2,则在图像的左上角填充0 if len(img.shape) == 2: img = np.pad(img, ((top, bottom), (left, right)), padding_mode) + # 将图像转换为Image对象 return Image.fromarray(img) - def get_perspective_params(img, distortion_scale): """Helper function to get parameters for RandomPerspective. """ @@ -965,19 +920,14 @@ def get_perspective_params(img, distortion_scale): def perspective(img, start_points, end_points, interpolation=Inter.BICUBIC): - """ - Apply perspective transformation to the input PIL Image. - - Args: - img (PIL.Image.Image): PIL Image to be applied perspective transformation. - start_points (list): List of [top_left, top_right, bottom_right, bottom_left] of the original image. - end_points: List of [top_left, top_right, bottom_right, bottom_left] of the transformed image. - interpolation (interpolation mode): Image interpolation mode, Default is Inter.BICUBIC = 3. - - Returns: - PIL.Image.Image, image after being perspectively transformed. - """ - + ''' + 使用插值投影将图像转换为投影矩阵 + :param img: PIL Image + :param start_points: 图像起始点 + :param end_points: 图像结束点 + :param interpolation: 插值方法 + :return: PIL Image + ''' def _input_to_coeffs(original_points, transformed_points): # Get the coefficients (a, b, c, d, e, f, g, h) for the perspective transforms. # According to "Using Projective Geometry to Correct a Camera" from AMS. @@ -985,12 +935,17 @@ def perspective(img, start_points, end_points, interpolation=Inter.BICUBIC): # https://github.com/python-pillow/Pillow/blob/master/src/libImaging/Geometry.c#L377 matrix = [] + # 将转换后的点和原始点拼接起来 for pt1, pt2 in zip(transformed_points, original_points): matrix.append([pt1[0], pt1[1], 1, 0, 0, 0, -pt2[0] * pt1[0], -pt2[0] * pt1[1]]) matrix.append([0, 0, 0, pt1[0], pt1[1], 1, -pt2[1] * pt1[0], -pt2[1] * pt1[1]]) + # 将拼接后的矩阵转换为数组 matrix_a = np.array(matrix, dtype=np.float) + # 将原始点按照8个点拼接起来 matrix_b = np.array(original_points, dtype=np.float).reshape(8) + # 使用numpy的linalg.lstsq函数求解矩阵 res = np.linalg.lstsq(matrix_a, matrix_b, rcond=None)[0] + # 返回矩阵的拟合结果 return res.tolist() if not is_pil(img): @@ -999,10 +954,8 @@ def perspective(img, start_points, end_points, interpolation=Inter.BICUBIC): coeffs = _input_to_coeffs(start_points, end_points) return img.transform(img.size, Image.PERSPECTIVE, coeffs, interpolation) - +# 获取擦除参数,关于系数的计算 def get_erase_params(np_img, scale, ratio, value, bounded, max_attempts): - """Helper function to get parameters for RandomErasing/Cutout. - """ if not is_numpy(np_img): raise TypeError('img should be NumPy array. Got {}.'.format(type(np_img))) @@ -1051,46 +1004,38 @@ def get_erase_params(np_img, scale, ratio, value, bounded, max_attempts): def erase(np_img, i, j, height, width, erase_value, inplace=False): - """ - Erase the pixels, within a selected rectangle region, to the given value. Applied on the input NumPy image array. - Args: - np_img (numpy.ndarray): NumPy image array of shape (C, H, W) to be erased. - i (int): The height component of the top left corner (height, width). - j (int): The width component of the top left corner (height, width). - height (int): Height of the erased region. - width (int): Width of the erased region. - erase_value: Erase value return from helper function get_erase_params(). - inplace (bool, optional): Apply this transform inplace. Default is False. - - Returns: - np_img (numpy.ndarray), Erased NumPy image array. - """ + ''' + 擦除图像中指定位置的像素 + :param np_img: NumPy array + :param i: 行号 + :param j: 列号 + :param height: 高度 + :param width: 宽度 + :param erase_value: 擦除像素的值 + :param inplace: 是否擦除原图像 + :return: 擦除后的图像 + ''' + # 如果参数img不是数组图像,抛出类型错误异常 if not is_numpy(np_img): raise TypeError('img should be NumPy array. Got {}.'.format(type(np_img))) + # 如果np_img不是擦除原图像,复制np_img if not inplace: np_img = np_img.copy() - # (i, j) here are the coordinates of axes (height, width) as in CHW + # 将np_img中第i到i+height行,第j到j+width列的值替换为erase_value np_img[:, i:i + height, j:j + width] = erase_value + # 返回新的np_img PIL格式 return np_img - def linear_transform(np_img, transformation_matrix, mean_vector): - """ - Apply linear transformation to the input NumPy image array, given a square transformation matrix and a mean_vector. - - The transformation first flattens the input array and subtract mean_vector from it, then computes the - dot product with the transformation matrix, and reshapes it back to its original shape. - - Args: - np_img (numpy.ndarray): NumPy image array of shape (C, H, W) to be linear transformed. - transformation_matrix (numpy.ndarray): a square transformation matrix of shape (D, D), D = C x H x W. - mean_vector (numpy.ndarray): a NumPy ndarray of shape (D,) where D = C x H x W. - - Returns: - np_img (numpy.ndarray), Linear transformed image. - """ + ''' + 线性变换 + :param np_img: NumPy数组 + :param transformation_matrix: 矩阵 + :param mean_vector: 均值向量 + :return: 线性变换后的图像 + ''' if not is_numpy(np_img): raise TypeError('img should be NumPy array. Got {}'.format(type(np_img))) if transformation_matrix.shape[0] != transformation_matrix.shape[1]: @@ -1102,87 +1047,87 @@ def linear_transform(np_img, transformation_matrix, mean_vector): if mean_vector.shape[0] != transformation_matrix.shape[0]: raise ValueError("mean_vector length {0} should match either one dimension of the square " "transformation_matrix {1}.".format(mean_vector.shape[0], transformation_matrix.shape)) + # 将图像均值向量和矩阵拼接 zero_centered_img = np_img.reshape(1, -1) - mean_vector + # 将图像均值向量和矩阵拼接后的结果乘以矩阵 transformed_img = np.dot(zero_centered_img, transformation_matrix) + # 如果结果的长度不等于原图像长度,则抛出数值错误异常 if transformed_img.size != np_img.size: raise ValueError("Linear transform failed, input shape should match with transformation_matrix.") + # 将结果reshape成原图像的形状 transformed_img = transformed_img.reshape(np_img.shape) return transformed_img def random_affine(img, angle, translations, scale, shear, resample, fill_value=0): - """ - Applies a random Affine transformation on the input PIL Image. - - Args: - img (PIL.Image.Image): Image to be applied affine transformation. - angle (Union[int, float]): Rotation angle in degrees, clockwise. - translations (sequence): Translations in horizontal and vertical axis. - scale (float): Scale parameter, a single number. - shear (Union[float, sequence]): Shear amount parallel to X axis and Y axis. - resample (Union[Inter.NEAREST, Inter.BILINEAR, Inter.BICUBIC], optional): An optional resampling filter. - fill_value (Union[tuple int], optional): Optional fill_value to fill the area outside the transform - in the output image. Used only in Pillow versions > 5.0.0. - If None, no filling is performed. - - Returns: - PIL.Image.Image, randomly affine transformed image. - - """ + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise ValueError("Input image should be a Pillow image.") - # rotation + # angle angle = random.uniform(angle[0], angle[1]) # translation if translations is not None: + # 计算最大转换倍数 max_dx = translations[0] * img.size[0] max_dy = translations[1] * img.size[1] + # 计算新的转换倍数 translations = (np.round(random.uniform(-max_dx, max_dx)), - np.round(random.uniform(-max_dy, max_dy))) + np.round(random.uniform(-max_dy, max_dy))) else: translations = (0, 0) # scale if scale is not None: + # 计算新的缩放比例 scale = random.uniform(scale[0], scale[1]) else: scale = 1.0 # shear if shear is not None: + # 计算新的旋转角度 if len(shear) == 2: shear = [random.uniform(shear[0], shear[1]), 0.] elif len(shear) == 4: shear = [random.uniform(shear[0], shear[1]), - random.uniform(shear[2], shear[3])] + random.uniform(shear[2], shear[3])] else: shear = 0.0 - output_size = img.size - center = (img.size[0] * 0.5 + 0.5, img.size[1] * 0.5 + 0.5) + output_size = img.size + # 计算图片的中心点 + center = (img.size[0] * 0.5 + 0.5, img.size[1] * 0.5 + 0.5) + # 将角度转换为弧度 angle = math.radians(angle) + # 如果shear是元组或列表,且元素个数为2 if isinstance(shear, (tuple, list)) and len(shear) == 2: shear = [math.radians(s) for s in shear] + # 如果shear是数字 elif isinstance(shear, numbers.Number): + # 将shear转换为弧度 shear = math.radians(shear) shear = [shear, 0] + # 如果shear不是元组或列表,且元素个数不为2 else: raise ValueError( "Shear should be a single value or a tuple/list containing " + "two values. Got {}.".format(shear)) +# 将scale转换为浮点数 scale = 1.0 / scale # Inverted rotation matrix with scale and shear d = math.cos(angle + shear[0]) * math.cos(angle + shear[1]) + \ math.sin(angle + shear[0]) * math.sin(angle + shear[1]) + # 计算矩阵 matrix = [ math.cos(angle + shear[0]), math.sin(angle + shear[0]), 0, -math.sin(angle + shear[1]), math.cos(angle + shear[1]), 0 ] + # 计算缩放比例 matrix = [scale / d * m for m in matrix] # Apply inverse of translation and of center translation: RSS^-1 * C^-1 * T^-1 @@ -1193,6 +1138,7 @@ def random_affine(img, angle, translations, scale, shear, resample, fill_value=0 matrix[2] += center[0] matrix[5] += center[1] + # Apply center translation: C * RSS^-1 * C^-1 * T^-1 if __version__ >= '5': kwargs = {"fillcolor": fill_value} else: @@ -1201,97 +1147,79 @@ def random_affine(img, angle, translations, scale, shear, resample, fill_value=0 def mix_up_single(batch_size, img, label, alpha=0.2): - """ - Apply mix up transformation to image and label in single batch internal, One hot encoding should done before this. - - Args: - batch_size (int): The batch size of dataset. - img (numpy.ndarray): NumPy image to be applied mix up transformation. - label (numpy.ndarray): NumPy label to be applied mix up transformation. - alpha (float): The mix up rate. - - Returns: - mix_img (numpy.ndarray): NumPy image after being applied mix up transformation. - mix_label (numpy.ndarray): NumPy label after being applied mix up transformation. - """ - + ''' + 混合掉一个图像和标签 + :param batch_size: 批次大小 + :param img: 图像 + :param label: 标签 + :param alpha: 权重 + :return: 混合后的图像和标签 + ''' def cir_shift(data): + ''' + 对图像进行编码 + :param data: 图像 + :return: 编码后的图像 + ''' index = list(range(1, batch_size)) + [0] data = data[index, ...] + data = data[index,...] return data + # 创建一个batch_size大小的随机系数,其中alpha为概率的参数 lam = np.random.beta(alpha, alpha, batch_size) + # 将lam的值转换为batch_size维的数组 lam_img = lam.reshape((batch_size, 1, 1, 1)) + # 将lam的值转换为batch_size维的数组,并将其reshape为(batch_size, 1, 1, 1) mix_img = lam_img * img + (1 - lam_img) * cir_shift(img) - + # 将lam的值转换为batch_size维的数组,并将其reshape为(batch_size, 1) lam_label = lam.reshape((batch_size, 1)) + # 将lam的值乘以img和cir_shift(img)的值,并将结果赋值给mix_img + mix_label = lam_label * label + (1 - lam_label) * cir_shift(label) return mix_img, mix_label def mix_up_muti(tmp, batch_size, img, label, alpha=0.2): - """ - Apply mix up transformation to image and label in continuous batch, one hot encoding should done before this. - - Args: - tmp (class object): mainly for saving the tmp parameter. - batch_size (int): the batch size of dataset. - img (numpy.ndarray): NumPy image to be applied mix up transformation. - label (numpy.ndarray): NumPy label to be applied mix up transformation. - alpha (float): refer to the mix up rate. - - Returns: - mix_img (numpy.ndarray): NumPy image after being applied mix up transformation. - mix_label (numpy.ndarray): NumPy label after being applied mix up transformation. - """ + ''' + 混合混合概率 + :param tmp: 原始数据 + :param batch_size: 批量大小 + :param img: 图片 + :param label: 标签 + :param alpha: 混合概率 + :return: 混合后的图片和标签 + ''' + # 创建一个batch_size大小的随机系数,其中alpha为概率的参数 lam = np.random.beta(alpha, alpha, batch_size) + # 如果tmp.is_first为True,则将lam的值设置为1 if tmp.is_first: lam = np.ones(batch_size) tmp.is_first = False - + # 将lam的值转换为batch_size*1*1*1的形式 lam_img = lam.reshape((batch_size, 1, 1, 1)) mix_img = lam_img * img + (1 - lam_img) * tmp.image - + # 将lam的值转换为batch_size*1的形式 lam_label = lam.reshape(batch_size, 1) mix_label = lam_label * label + (1 - lam_label) * tmp.label + # 将mix_img和mix_label混合到tmp.image和tmp.label中 tmp.image = mix_img tmp.label = mix_label return mix_img, mix_label - +# RGB格式转换为BGR格式 def rgb_to_bgr(np_rgb_img, is_hwc): - """ - Convert RGB img to BGR img. - - Args: - np_rgb_img (numpy.ndarray): NumPy RGB image array of shape (H, W, C) or (C, H, W) to be converted. - is_hwc (Bool): If True, the shape of np_hsv_img is (H, W, C), otherwise must be (C, H, W). - - Returns: - np_bgr_img (numpy.ndarray), NumPy BGR image with same type of np_rgb_img. - """ + if is_hwc: np_bgr_img = np_rgb_img[:, :, ::-1] else: np_bgr_img = np_rgb_img[::-1, :, :] return np_bgr_img - +# RGB格式转换为BGR格式 def rgb_to_bgrs(np_rgb_imgs, is_hwc): - """ - Convert RGB imgs to BGR imgs. - - Args: - np_rgb_imgs (numpy.ndarray): NumPy RGB images array of shape (H, W, C) or (N, H, W, C), - or (C, H, W) or (N, C, H, W) to be converted. - is_hwc (Bool): If True, the shape of np_rgb_imgs is (H, W, C) or (N, H, W, C); - If False, the shape of np_rgb_imgs is (C, H, W) or (N, C, H, W). - - Returns: - np_bgr_imgs (numpy.ndarray), NumPy BGR images with same type of np_rgb_imgs. - """ if not is_numpy(np_rgb_imgs): raise TypeError("img should be NumPy image. Got {}".format(type(np_rgb_imgs))) @@ -1323,18 +1251,8 @@ def rgb_to_bgrs(np_rgb_imgs, is_hwc): return rgb_to_bgr(np_rgb_imgs, is_hwc) return np.array([rgb_to_bgr(img, is_hwc) for img in np_rgb_imgs]) - +# RGB格式转化为HSV格式 def rgb_to_hsv(np_rgb_img, is_hwc): - """ - Convert RGB img to HSV img. - - Args: - np_rgb_img (numpy.ndarray): NumPy RGB image array of shape (H, W, C) or (C, H, W) to be converted. - is_hwc (Bool): If True, the shape of np_hsv_img is (H, W, C), otherwise must be (C, H, W). - - Returns: - np_hsv_img (numpy.ndarray), NumPy HSV image with same type of np_rgb_img. - """ if is_hwc: r, g, b = np_rgb_img[:, :, 0], np_rgb_img[:, :, 1], np_rgb_img[:, :, 2] else: @@ -1348,20 +1266,8 @@ def rgb_to_hsv(np_rgb_img, is_hwc): np_hsv_img = np.stack((h, s, v), axis=axis) return np_hsv_img - +# RGB格式转化为HSV格式 def rgb_to_hsvs(np_rgb_imgs, is_hwc): - """ - Convert RGB imgs to HSV imgs. - - Args: - np_rgb_imgs (numpy.ndarray): NumPy RGB images array of shape (H, W, C) or (N, H, W, C), - or (C, H, W) or (N, C, H, W) to be converted. - is_hwc (Bool): If True, the shape of np_rgb_imgs is (H, W, C) or (N, H, W, C); - If False, the shape of np_rgb_imgs is (C, H, W) or (N, C, H, W). - - Returns: - np_hsv_imgs (numpy.ndarray), NumPy HSV images with same type of np_rgb_imgs. - """ if not is_numpy(np_rgb_imgs): raise TypeError("img should be NumPy image. Got {}".format(type(np_rgb_imgs))) @@ -1393,18 +1299,8 @@ def rgb_to_hsvs(np_rgb_imgs, is_hwc): return rgb_to_hsv(np_rgb_imgs, is_hwc) return np.array([rgb_to_hsv(img, is_hwc) for img in np_rgb_imgs]) - +# HSV转化为RGB格式 def hsv_to_rgb(np_hsv_img, is_hwc): - """ - Convert HSV img to RGB img. - - Args: - np_hsv_img (numpy.ndarray): NumPy HSV image array of shape (H, W, C) or (C, H, W) to be converted. - is_hwc (Bool): If True, the shape of np_hsv_img is (H, W, C), otherwise must be (C, H, W). - - Returns: - np_rgb_img (numpy.ndarray), NumPy HSV image with same shape of np_hsv_img. - """ if is_hwc: h, s, v = np_hsv_img[:, :, 0], np_hsv_img[:, :, 1], np_hsv_img[:, :, 2] else: @@ -1421,36 +1317,36 @@ def hsv_to_rgb(np_hsv_img, is_hwc): def hsv_to_rgbs(np_hsv_imgs, is_hwc): - """ - Convert HSV imgs to RGB imgs. - - Args: - np_hsv_imgs (numpy.ndarray): NumPy HSV images array of shape (H, W, C) or (N, H, W, C), - or (C, H, W) or (N, C, H, W) to be converted. - is_hwc (Bool): If True, the shape of np_hsv_imgs is (H, W, C) or (N, H, W, C); - If False, the shape of np_hsv_imgs is (C, H, W) or (N, C, H, W). - - Returns: - np_rgb_imgs (numpy.ndarray), NumPy RGB images with same type of np_hsv_imgs. - """ + ''' + 将HSV图像转换为RGB图像 + :param np_hsv_imgs: 原始的HSV图像 + :param is_hwc: 是否为HWC格式 + :return: 转换后的RGB图像 + ''' + # 如果参数img不是数组图像,抛出类型错误异常 if not is_numpy(np_hsv_imgs): raise TypeError("img should be NumPy image. Got {}.".format(type(np_hsv_imgs))) - + # 如果is_hwc不是bool 值,抛出类型错误异常 if not isinstance(is_hwc, bool): raise TypeError("is_hwc should be bool type. Got {}.".format(type(is_hwc))) + # 把shape_size赋值为np_hsv_imgs的形状长度信息 shape_size = len(np_hsv_imgs.shape) - + # 如果shape_size的值不在3,4之间,抛出类型错误异常 if not shape_size in (3, 4): raise TypeError("img shape should be (H, W, C)/(N, H, W, C)/(C, H, W)/(N, C, H, W). " "Got {}.".format(np_hsv_imgs.shape)) - + + # 如果图像的形状为3,则batch_size为0 if shape_size == 3: batch_size = 0 + # 如果图像是HWC格式,则num_channels为图像的通道数 if is_hwc: num_channels = np_hsv_imgs.shape[2] + # 否则num_channels为图像的行数 else: num_channels = np_hsv_imgs.shape[0] + # 如果图像形状不为3,则batch_size赋值为np_hsv_imgs矩阵第一维度 else: batch_size = np_hsv_imgs.shape[0] if is_hwc: @@ -1458,79 +1354,67 @@ def hsv_to_rgbs(np_hsv_imgs, is_hwc): else: num_channels = np_hsv_imgs.shape[1] + # 如果num_channels不是3,则抛出类型错误异常 if num_channels != 3: raise TypeError("img should be 3 channels RGB img. Got {} channels.".format(num_channels)) + # 如果batch_size为0,则返回hsv_to_rgb函数的结果 if batch_size == 0: return hsv_to_rgb(np_hsv_imgs, is_hwc) + # 否则,返回一个batch_size大小的数组,每个元素为hsv_to_rgb函数的结果 return np.array([hsv_to_rgb(img, is_hwc) for img in np_hsv_imgs]) def random_color(img, degrees): """ - Adjust the color of the input PIL Image by a random degree. - - Args: - img (PIL.Image.Image): Image to be color adjusted. - degrees (sequence): Range of random color adjustment degrees. - It should be in (min, max) format (default=(0.1,1.9)). - - Returns: - PIL.Image.Image, color adjusted image. + 随机颜色增强 + :param img: 输入图像 + :param degrees: 随机颜色增强的参数 + :return: 增强后的图像 """ - + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) + # 计算随机颜色增强的参数 v = (degrees[1] - degrees[0]) * random.random() + degrees[0] + # 返回增强后的图像 return ImageEnhance.Color(img).enhance(v) def random_sharpness(img, degrees): - """ - Adjust the sharpness of the input PIL Image by a random degree. - - Args: - img (PIL.Image.Image): Image to be sharpness adjusted. - degrees (sequence): Range of random sharpness adjustment degrees. - It should be in (min, max) format (default=(0.1,1.9)). - - Returns: - PIL.Image.Image, sharpness adjusted image. - """ - + ''' + 随机着色调 + :param img: PIL格式的图像 + :param degrees: 图像的角度 + :return: 图像的着色调增强 + ''' + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) + # 计算角度的随机值 v = (degrees[1] - degrees[0]) * random.random() + degrees[0] + # 返回图像的着色调增强 return ImageEnhance.Sharpness(img).enhance(v) - +# 调整图像的gamma值,并返回调整后的图像 def adjust_gamma(img, gamma, gain): - """ - Adjust gamma of the input PIL Image. - - Args: - img (PIL.Image.Image): Image to be augmented with AdjustGamma. - gamma (float): Non negative real number, same as gamma in the equation. - gain (float, optional): The constant multiplier. - - Returns: - PIL.Image.Image, augmented image. - - """ - + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError("img should be PIL image. Got {}.".format(type(img))) + # 计算gamma值的表 gamma_table = [(255 + 1 - 1e-3) * gain * pow(x / 255., gamma) for x in range(256)] + # 如果图像是三通道,则将gamma值乘以3再进行图像映射 if len(img.split()) == 3: gamma_table = gamma_table * 3 img = img.point(gamma_table) + # 如果图像是一通道,则以gamma值进行图像映射 elif len(img.split()) == 1: img = img.point(gamma_table) + # 返回img PIL格式 return img - def auto_contrast(img, cutoff, ignore): """ Automatically maximize the contrast of the input PIL Image. @@ -1549,19 +1433,8 @@ def auto_contrast(img, cutoff, ignore): return ImageOps.autocontrast(img, cutoff, ignore) - +# 在 RGB 模式下对输入图像应用像素反转。 def invert_color(img): - """ - Invert colors of input PIL Image. - - Args: - img (PIL.Image.Image): Image to be color inverted. - - Returns: - PIL.Image.Image, color inverted image. - - """ - if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) @@ -1569,44 +1442,22 @@ def invert_color(img): def equalize(img): - """ - Equalize the histogram of input PIL Image. - - Args: - img (PIL.Image.Image): Image to be equalized - - Returns: - PIL.Image.Image, equalized image. - - """ - + # 如果输入的图像不是PIL格式,抛出类型错误异常 if not is_pil(img): raise TypeError(augment_error_message.format(type(img))) + # 返回图像均衡图像直方图 return ImageOps.equalize(img) def uniform_augment(img, transforms, num_ops): - """ - Uniformly select and apply a number of transforms sequentially from - a list of transforms. Randomly assigns a probability to each transform for - each image to decide whether apply it or not. - All the transforms in transform list must have the same input/output data type. - - Args: - img: Image to be applied transformation. - transforms (list): List of transformations to be chosen from to apply. - num_ops (int): number of transforms to sequentially aaply. - - Returns: - img, Transformed image. - - """ - + # 从transforms中随机选择num_ops个运算,并将其作为参数传入AugmentOp op_idx = np.random.choice(len(transforms), size=num_ops, replace=False) for idx in op_idx: AugmentOp = transforms[idx] + # 从random.random()中随机选择一个小于等于pr的数,并将其赋值给pr pr = random.random() + # 如果random.random()小于pr,则执行AugmentOp if random.random() < pr: img = AugmentOp(img.copy()) diff --git a/mindspore/python/mindspore/nn/__init__.py b/mindspore/python/mindspore/nn/__init__.py index 2c11a7ed275..aa8e9e6878c 100644 --- a/mindspore/python/mindspore/nn/__init__.py +++ b/mindspore/python/mindspore/nn/__init__.py @@ -17,34 +17,49 @@ Neural Networks Cells. Pre-defined building blocks or computing units to construct neural networks. """ -from . import layer, loss, optim, metrics, wrap, grad, probability, sparse, dynamic_lr,\ - reinforcement -from .learning_rate_schedule import * -from .dynamic_lr import * -from .cell import Cell, GraphCell -from .layer import * -from .loss import * -from .optim import * -from .metrics import * -from .wrap import * -from .grad import Jvp, Vjp -from .sparse import * -from .reinforcement import * -from .transformer import AttentionMask, VocabEmbedding, MultiHeadAttention, FeedForward, TransformerEncoder, \ - TransformerDecoder, TransformerEncoderLayer, TransformerDecoderLayer, Transformer, TransformerOpParallelConfig, \ - EmbeddingOpParallelConfig, TransformerRecomputeConfig, MoEConfig, OpParallelConfig +# 定义构建单元或计算单元,用于构建神经网络 +from. import layer, loss, optim, metrics, wrap, probability, sparse, dynamic_lr +# 导入layer、loss、optim、metrics、wrap、probability、sparse、dynamic_lr模块 +from.learning_rate_schedule import * +# 导入learning_rate_schedule模块 +from.dynamic_lr import * +# 导入dynamic_lr模块 +from.cell import Cell, GraphKernel, GraphCell +# 导入Cell、GraphKernel、GraphCell模块 +from.layer import * +# 导入layer模块 +from.loss import * +# 导入loss模块 +from.optim import * +# 导入optim模块 +from.metrics import * +# 导入metrics模块 +from.wrap import * +# 导入wrap模块 +from.sparse import * -__all__ = ["Cell", "GraphCell"] + +# 导入sparse模块 + + +__all__ = ["Cell", "GraphKernel", "GraphCell"] +# 定义构建单元或计算单元,用于构建神经网络 __all__.extend(layer.__all__) +# 向__all__中添加layer模块 __all__.extend(loss.__all__) +# 向__all__中添加loss模块 __all__.extend(optim.__all__) +# 向__all__中添加optim模块 __all__.extend(metrics.__all__) +# 向__all__中添加metrics模块 __all__.extend(wrap.__all__) -__all__.extend(grad.__all__) +# 向__all__中添加wrap模块 __all__.extend(sparse.__all__) +# 向__all__中添加sparse模块 __all__.extend(learning_rate_schedule.__all__) +# 向__all__中添加learning_rate_schedule模块 __all__.extend(dynamic_lr.__all__) -__all__.extend(reinforcement.__all__) -__all__.extend(transformer.__all__) + +# 向__all__中添加dynamic_lr模块 __all__.sort() diff --git a/mindspore/python/mindspore/nn/cell.py b/mindspore/python/mindspore/nn/cell.py index c102bb75f92..53a424a7c4e 100755 --- a/mindspore/python/mindspore/nn/cell.py +++ b/mindspore/python/mindspore/nn/cell.py @@ -12,32 +12,60 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ +# 用于定义一个名为cell的神经网络基本单位。模块中包含了用于训练和推理神经网络的各种功能,所有神经网络层,优化器,损失函数均需以此类为父类继承构建。 +# 此文件有博客介绍,以下为链接: +# https://forum.gitlink.org.cn/forums/9904/detail """cell""" +# 用于垃圾回收 import gc +# 用于获取函数和类的详细信息 import inspect +# 导入os模块,用于处理文件和目录 import os +# 用于处理时间 import time +# 从collections模块中导入OrderedDict类,用于创建有序字典 from collections import OrderedDict +# 从types模块中导入FunctionType和MethodType类,用于定义函数和类 from types import FunctionType, MethodType +# 用于处理数值计算 import numpy +# 用于检查函数参数类型 from mindspore._checkparam import args_type_check +# 导入log对象,用于输出日志 from mindspore import log as logger +# 用于表示默认的参数名 from mindspore.common.parameter import PARAMETER_NAME_DEFAULT +# 用于处理钩子 from mindspore.common.hook_handle import HookHandle +# 用于处理并行模式 from mindspore.context import ParallelMode +# 用于处理分片操作 from mindspore.ops.composite import Shard +# 用于获取当前上下文 from .. import context +# 从.._c_expression模块中导入多个类,用于处理C++表达式和函数图 from .._c_expression import init_pipeline, update_func_graph_hyper_params, Cell_, FuncGraph, MixedPrecisionType +# 用于验证参数 from .._checkparam import Validator +# 用于处理数据类型 from ..common import dtype as mstype +# 从..common.api模块中导入多个函数,用于处理单元格图执行、Python原生执行、检查所有张量是否正确和缓存单元格图 from ..common.api import _cell_graph_executor, _pynative_executor, _check_all_tensor, cells_compile_cache +# 用于处理参数 from ..common.parameter import Parameter, ParameterTuple +# 用于处理变量 from ..common.variable import Variable +# 从..common.tensor模块中导入多个类,用于处理张量、CSR张量和COO张量 from ..common.tensor import Tensor, CSRTensor, COOTensor +# 导入Cast类,用于转换张量的数据类型 from ..ops.operations import Cast +# 用于定义原始操作 from ..ops.primitive import Primitive +# 用于处理内部操作 from ..ops.operations import _inner_ops as inner +# 用于根据布局加载张量 from ..parallel._tensor import _load_tensor_by_layout @@ -83,18 +111,22 @@ class Cell(Cell_): """ class _CellGuard: + # 用于检测是否正在使用with语句中的Cell。当在单元格中使用with语句时,这个类会记录单元格是否是顶级Cell """Detecting whether the cell is a top-level cell with the 'with statement'.""" - def __enter__(self): + def __enter__(self):#输入cell并增加递归深度计数 """Enter cell and increase recursion depth count.""" _pynative_executor.set_lazy_build(True) _pynative_executor.enter_cell() - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__(self, exc_type, exc_val, exc_tb):#退出cell并减少递归深度计数 """Exit cell and decrease recursion depth count.""" _pynative_executor.exit_cell() + # 如果是顶级Cell单元 if _pynative_executor.is_top_cell(): + # 这样,在后续的构建过程中,将不会执行懒构建 _pynative_executor.set_lazy_build(False) + # 用于在序列化和反序列化Cell时,排除不需要序列化的属性 IGNORE_LIST = ['_scope', '_cell_init_args', '_auto_prefix', '_cells', '_params', '_construct_inputs_names', '_construct_inputs_num', '_create_time', '_mindspore_flags', '_parallel_inputs_run', '_parameter_layout_dict', '_params_list', '_tensor_list', '_phase', '_auto_parallel_mode', @@ -103,68 +135,117 @@ class Cell(Cell_): '_attr_synced', 'pynative', 'requires_grad', '_auto_parallel_compile_and_run', 'cell_type'] def __init__(self, auto_prefix=True, flags=None): + # 初始化Cell类 Cell_.__init__(self, self._cell_tag) + # 初始化参数字典 self._params = OrderedDict() + # 初始化单元字典 self._cells = OrderedDict() + # 初始化参数列表 self._params_list = OrderedDict() + # 初始化矢量列表 self._tensor_list = OrderedDict() + # 初始化操作字典 self._primitives = OrderedDict() + # 设置是否自动添加前缀 self.training = False + # 设置是否需要梯度 self.requires_grad = False + # 设置是否使用pynative self.pynative = False + # 设置参数前缀 self._attr_synced = False + # 设置参数前缀 self._param_prefix = '' + # 设置是否自动添加前缀 self._auto_prefix = auto_prefix + # 设置作用域 self._scope = None + # 设置训练阶段 self._phase = 'train' + # 设置参数排序字典 self._parameter_layout_dict = {} + # 设置并行参数名称列表 self._parallel_parameter_name_list = () + # 设置并行参数合并网络字典 self._parallel_parameter_merge_net_dict = {} + # 创建时间 self._create_time = int(time.time() * 1e9) + # 初始化参数 self.arguments_key = "" + # 初始化编译缓存 self.compile_cache = set() + # 初始化单元编译缓存 cells_compile_cache[id(self)] = self.compile_cache + # 设置参数广播是否完成 self.parameter_broadcast_done = False self._id = 1 + # 两个名为exist_names和exist_objs的集合,用于存储Cell中已存在的名称和对象。这样,在序列化和反序列化Cell时,可以排除掉这些不需要序列化的名称和对象 self.exist_names = set("") self.exist_objs = set() init_pipeline() # call gc to release GE session resources used by non-used cell objects + # 检查GC_COLLECT_IN_CELL环境变量是否为1。 + # 如果是,则调用gc.collect()函数来释放非使用的GE会话资源。这样可以确保在处理Cell时,没有不必要的内存泄漏 if os.getenv('GC_COLLECT_IN_CELL') == '1': gc.collect() self._construct_inputs_num = 0 self._construct_inputs_names = [] + # 设置是否使用自动平行模式 self._auto_parallel_mode = False + # 设置并行参数运行 self._parallel_inputs_run = None + # 初始化前向调用钩子 if flags: self.add_flags(**flags) + # 设置是否启用前向调用钩子 self._bprop_debug = False + # 初始化前向前置钩子 self._forward_pre_hook = OrderedDict() + # 初始化前向钩子 self._forward_hook = OrderedDict() + # 初始化是否启用前向前置钩子 self._enable_forward_pre_hook = False + # 初始化是否启用前向钩子 self._enable_forward_hook = False + # 初始化是否启用后向钩子 self._enable_backward_hook = False + # 初始化单元后向钩子 self._cell_backward_hook = None + # 设置单元类型 self.cell_type = None + # 设置自动平行编译和运行 self._auto_parallel_compile_and_run = False + # 初始化参数 self.cast = Cast() + # 初始化是否有配置重新计算 self._has_config_recompute = False + # 初始化用户参数 self._user_parameters = [] self._dynamic_shape_inputs = None self.saved_dynamic_shape = None def __getstate__(self): + # 获取Cell类的状态 base = Cell_.__getstate__(self) + # 返回base和self.__dict__ return base, self.__dict__ def __setstate__(self, state): + # 获取state base, dict_ = state + # 设置Cell类的状态 Cell_.__setstate__(self, base) + # 将self.__dict__设置为dict_ self.__dict__ = dict_ + # 将_attr_synced设置为False self._attr_synced = False - + """ + 以下定义了三个属性:_cell_tag、create_time和cell_init_args。它们分别用于获取Cell的标签、创建时间和cell的初始化参数 + param_prefix属性,用于获取当前cell直系子参数的前缀 + """ @property def _cell_tag(self): # `` to `xxxxxxx` @@ -186,14 +267,14 @@ class Cell(Cell_): return self._param_prefix @property - def bprop_debug(self): + def bprop_debug(self):#获取是否启用了cell自定义bprop调试 """ Get whether cell custom bprop debug is enabled. """ return self._bprop_debug @bprop_debug.setter - def bprop_debug(self, value): + def bprop_debug(self, value):#设置是否启用cell自定义bprop调试 """ Set whether to enable cell custom bprop debug. @@ -209,7 +290,7 @@ class Cell(Cell_): raise TypeError(f"For 'Cell', the property 'bprop_debug' must be bool type, but got type {type(value)}.") self._bprop_debug = value - def update_cell_prefix(self): + def update_cell_prefix(self):#更新所有child cells' self.param_prefix. """ Update the all child cells' self.param_prefix. @@ -220,7 +301,7 @@ class Cell(Cell_): for cell_name, cell in cells_name: cell._param_prefix = cell_name - def update_cell_type(self, cell_type): + def update_cell_type(self, cell_type):#更新 cell 格式为 'cell_type' """ The current cell type is updated when a quantization aware training network is encountered. @@ -233,6 +314,9 @@ class Cell(Cell_): @cell_init_args.setter def cell_init_args(self, value): + ''' + 设置cell的初始化参数 + ''' if not isinstance(value, str): raise TypeError(f"For 'Cell', the property 'cell_init_args' must be string type, " f"but got type {type(value)}.") @@ -240,10 +324,12 @@ class Cell(Cell_): @property def phase(self): + # 获取Cell的阶段 return self._phase @phase.setter def phase(self, value): + # 设置Cell的阶段 if not isinstance(value, str): raise TypeError(f"For 'Cell', the property 'phase' must be string type, but got type {type(value)}.") self._phase = value @@ -254,14 +340,17 @@ class Cell(Cell_): `parameter_layout_dict` represents the tensor layout of a parameter, which is inferred by shard strategy and distributed operator information. """ + # 获取Cell的参数布局字典 return self._parameter_layout_dict @property def cls_name(self): + # 获取Cell的类名 return self.__class__.__name__ @parameter_layout_dict.setter def parameter_layout_dict(self, value): + # 设置Cell的参数布局字典 if not isinstance(value, dict): raise TypeError(f"For 'Cell', the property 'parameter_layout_dict' must be dict type, " f"but got type {type(value)}.") @@ -269,10 +358,14 @@ class Cell(Cell_): @property def parallel_parameter_name_list(self): + # 获取Cell的并行参数名称列表 return self._parallel_parameter_name_list @parallel_parameter_name_list.setter def parallel_parameter_name_list(self, value): + ''' + 设置参数名称列表 + ''' if not isinstance(value, list): raise TypeError(f"For 'Cell', the property 'parallel_parameter_name_list' must be list type, " f"but got type {type(value)}.") @@ -284,6 +377,11 @@ class Cell(Cell_): @pipeline_stage.setter def pipeline_stage(self, value): + ''' + 设置pipeline_stage属性 + :param value: int类型,表示pipeline_stage的值 + :return: None + ''' if not isinstance(value, int) or isinstance(value, bool): raise TypeError("For 'Cell', the property 'pipeline_stage' " "must be int type, but got type : {}".format(type(value))) @@ -297,10 +395,19 @@ class Cell(Cell_): @property def parallel_parameter_merge_net_dict(self): + ''' + 获取parallel_parameter_merge_net_dict属性 + :return: dict类型 + ''' return self._parallel_parameter_merge_net_dict @parallel_parameter_merge_net_dict.setter def parallel_parameter_merge_net_dict(self, value): + ''' + 设置parallel_parameter_merge_net_dict属性 + :param value: dict类型 + :return: None + ''' if not isinstance(value, dict): raise TypeError(f"For 'Cell', the property 'parallel_parameter_merge_net_dict' must be dict type, " f"but got type {type(value)}.") @@ -308,10 +415,17 @@ class Cell(Cell_): def get_func_graph_proto(self): """Return graph binary proto.""" + ''' + 返回graph binary proto + :return: graph binary proto + ''' exec_id = ".".join([self.phase, str(self.create_time), str(id(self))]) return _cell_graph_executor._get_func_graph_proto(self, exec_id, "anf_ir", True) def __getattr__(self, name): + ''' + 获取属性 + ''' if '_params' in self.__dict__: params = self.__dict__['_params'] if name in params: @@ -338,6 +452,9 @@ class Cell(Cell_): raise AttributeError("The '{}' object has no attribute '{}'.".format(type(self).__name__, name)) def __del__(self): + ''' + 析构函数 + ''' if context.get_context is not None and context._get_mode() == context.PYNATIVE_MODE: _pynative_executor.del_cell(str(id(self))) @@ -348,6 +465,9 @@ class Cell(Cell_): _cell_graph_executor.del_net_res(self.compile_cache) def __delattr__(self, name): + ''' + 删除属性 + ''' if name in self._params: del self._params[name] elif name in self._cells: @@ -364,14 +484,23 @@ class Cell(Cell_): """Cast input for mixed precision""" res = list() for item in inputs: + # 如果item是元组类型 if isinstance(item, tuple): + # 将元组中的元素转换为指定类型 res.append(self._cast_mixed_precision_inputs(item, dst_type)) + # 如果item是浮点数 elif isinstance(item, float): + # 将元组中的元素转换为指定类型 res.append(self.cast(item, dst_type)) + # 如果item有dtype属性,且dtype属性的值在{mstype.float16, mstype.float32, mstype.float64}中 elif hasattr(item, "dtype") and item.dtype in {mstype.float16, mstype.float32, mstype.float64}: + # 将元组中的元素转换为指定类型 res.append(self.cast(item, dst_type)) + # 否则 else: + # 将元组中的元素转换为指定类型 res.append(item) + # 返回转换后的元组 return tuple(res) def cast_inputs(self, inputs, dst_type): @@ -387,19 +516,28 @@ class Cell(Cell_): """ res = list() for item in inputs: + # 如果item是元组类型 if isinstance(item, tuple): + # 调用cast_inputs函数,将元组转换为dst_type类型 res.append(self.cast_inputs(item, dst_type)) + # 否则 else: + # 调用cast函数,将item转换为dst_type类型 res.append(self.cast(item, dst_type)) + # 返回转换后的结果 return tuple(res) def _do_parameter_broadcast(self): + # 用于执行参数广播 if context.get_auto_parallel_context("parallel_mode") == ParallelMode.DATA_PARALLEL: + # 在MindSpore的自动并行模式为数据并行时,如果未广播参数,则调用_pynative_executor.parameter_broadcast()函数执行参数广播, + # 并将parameter_broadcast_done属性设置为True if not self.parameter_broadcast_done: _pynative_executor.parameter_broadcast(self, self.phase, self._auto_parallel_mode) self.parameter_broadcast_done = True def run_construct(self, cast_inputs, kwargs): + # 用于运行Cell的构造函数,即将删除 """ Run the construct function. @@ -419,40 +557,61 @@ class Cell(Cell_): return output def _run_construct(self, cast_inputs, kwargs): + # 用于运行Cell的构造函数 """Run the construct function""" if self._enable_forward_pre_hook: + # 调用_run_forward_pre_hook函数 cast_inputs = self._run_forward_pre_hook(cast_inputs) + # 如果_enable_backward_hook为True,则调用_backward_hook_construct函数 if self._enable_backward_hook: output = self._backward_hook_construct(*cast_inputs) + # 如果_enable_backward_hook为False,且_shard_fn存在,则调用_shard_fn函数 elif hasattr(self, "_shard_fn"): output = self._shard_fn(*cast_inputs, **kwargs) + # 否则,调用self.construct函数 else: output = self.construct(*cast_inputs, **kwargs) + # 如果_enable_forward_hook为True,则调用_run_forward_hook函数 if self._enable_forward_hook: output = self._run_forward_hook(cast_inputs, output) + # 返回输出 return output def _check_construct_args(self, *inputs, **kwargs): + # 用于检查Cell的构造函数中即将传入的参数 """Check the args needed by the function construct""" if kwargs: raise ValueError(f"For 'Cell', expect no kwargs here, " "maybe you pass wrong arguments, args: {inputs}, kwargs: {kwargs}") + # 初始化positional_args和default_args positional_args = 0 default_args = 0 + # 遍历构造函数的参数 for value in inspect.signature(self.construct).parameters.values(): + # 如果参数是可变参数或者可变关键字参数 if value.kind is inspect.Parameter.VAR_POSITIONAL or value.kind is inspect.Parameter.VAR_KEYWORD: return + # 跳过 + continue + # 如果参数是正常参数 if value.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD: + # 如果参数默认值为空 if value.default is inspect.Parameter.empty: + # 将positional_args加1 positional_args += 1 else: + # 将default_args加1 default_args += 1 + # 如果输入的参数小于positional_args if len(inputs) < positional_args: + # 抛出TypeError raise TypeError(f"For 'Cell', the function construct need {positional_args} positional argument, " f"but got {len(inputs)}.") + # 如果输入的参数大于positional_args + default_args if len(inputs) > positional_args + default_args: + # 抛出TypeError raise TypeError(f"For 'Cell', the function construct need {positional_args} positional argument and " f"{default_args} default argument, total {positional_args + default_args}, " f"but got {len(inputs)}.") @@ -460,23 +619,27 @@ class Cell(Cell_): def _hook_fn_registered(self): if self._enable_forward_pre_hook or self._enable_forward_hook or self._enable_backward_hook: return True + # 遍历cells for cell in self.cells(): + # 如果cell没有注册hook_fn if cell._hook_fn_registered(): return True return False def _get_prims_recursively(self): all_prims = list() + # 遍历_primitives字典,将每一个值转换为元组,并将元组添加到all_prims列表中 for _, value in self._primitives.items(): if value: all_prims.append(value) + # 遍历cells,将每一个cell的_get_prims_recursively()方法的返回值添加到all_prims列表中 for cell in self.cells(): all_prims.extend(cell._get_prims_recursively()) return all_prims - def set_data_parallel(self): + def set_data_parallel(self):#并行化所有ops算子 """ For all primitive ops in this cell(including ops of cells that wrapped by this cell), if parallel strategy is not specified, then instead of auto-searching, data parallel @@ -498,6 +661,7 @@ class Cell(Cell_): prim.add_prim_attr("strategy_gen_mode", "data_parallel") def shard(self, in_strategy, out_strategy, device="Ascend", level=0): + # 用于设置输入和输出布局以及并行策略。in_strategy 和 out_strategy 定义了输入和输出布局 """ Defining the input and output layouts of this cell and the parallel strategies of remaining ops will be generated by sharding propagation. in_strategy and out_strategy define the input and output layout respectively. @@ -544,8 +708,11 @@ class Cell(Cell_): ... return x """ shard_fn = Shard() + # 创建一个Shard函数,用于分割输入和输出 fn = shard_fn(self, in_strategy, out_strategy, device, level) + # 将分割函数赋值给object.__setattr__(self, "_shard_fn", fn) object.__setattr__(self, "_shard_fn", fn) + # 返回self return self def auto_cast_inputs(self, inputs): @@ -559,9 +726,12 @@ class Cell(Cell_): Tuple, the inputs after data type cast. """ cast_inputs = inputs + # 获取当前类型的调整精度类型 mixed_type = self.get_mixed_precision_type() + # 如果当前类型的调整精度类型为FP16,则将输入转换为FP16类型 if mixed_type == MixedPrecisionType.FP16: cast_inputs = self._cast_mixed_precision_inputs(inputs, mstype.float16) + # 如果当前类型的调整精度类型为FP32,则将输入转换为FP32类型 if mixed_type == MixedPrecisionType.FP32: cast_inputs = self._cast_mixed_precision_inputs(inputs, mstype.float32) @@ -569,8 +739,10 @@ class Cell(Cell_): def __call__(self, *args, **kwargs): if self.__class__.construct is Cell.construct: + # 如果类的构造函数没有被重写,则警告日志 logger.warning(f"The '{self.__class__}' does not override the method 'construct', " f"it will call the super class(Cell) 'construct'.") + # 如果有参数,则使用inspect模块的signature函数绑定参数 if kwargs: bound_arguments = inspect.signature(self.construct).bind(*args, **kwargs) bound_arguments.apply_defaults() @@ -579,138 +751,213 @@ class Cell(Cell_): # Run in Graph mode. if context._get_mode() == context.GRAPH_MODE: + # 检查构造参数 self._check_construct_args(*args, **kwargs) + # 检查hook函数是否已经注册 if self._hook_fn_registered(): logger.warning(f"For 'Cell', it's not support hook function in graph mode. If you want to use hook " f"function, please use context.set_context to set pynative mode.") + # 编译和运行 out = self.compile_and_run(*args) return out # Run in PyNative mode. if _pynative_executor.is_top_cell(): + # 如果是最顶层的cell,则设置lazy_build为True,并设置optimizer为None _pynative_executor.set_lazy_build(True) _pynative_executor._optimizer = getattr(self, "optimizer", None) _pynative_executor._top_cell = self # There many Casts in parameter_broadcast. Enable lazy_build and build faster. + # 设置参数broadcast的状态 self._do_parameter_broadcast() + # 将参数转换为numpy数组 for item in args: if isinstance(item, Tensor) and item.has_init: item.init_data() elif isinstance(item, numpy.ndarray): raise TypeError("For 'Cell', inputs should not be numpy array.") + # 如果需要梯度,则设置梯度标志为True if self.requires_grad: _pynative_executor.set_grad_flag(True) + # 创建新图 _pynative_executor.new_graph(self, *args, **kwargs) + # 获取自动转换的输入 cast_inputs = self.auto_cast_inputs(args) with self._CellGuard(): try: + # 运行构造函数 output = self._run_construct(cast_inputs, kwargs) except Exception as err: + # 清除资源 _pynative_executor.clear_res() raise err if _pynative_executor.is_top_cell(): + # 如果是最顶层的单元,则执行lazy_task _pynative_executor.execute_lazy_task() + # 如果output是Parameter类型,则将output转换为data if isinstance(output, Parameter): output = output.data + # 结束图,并将output传入_pynative_executor.end_graph _pynative_executor.end_graph(self, output, *args, **kwargs) + # 返回output return output - def _add_attr(self, name, value): - if name and name[:2] != '__' and name not in Cell.IGNORE_LIST: + ''' + 添加属性 + :param name: 属性名 + :param value: 属性值 + :return: None + ''' + if name and name[:2]!= '__' and name not in Cell.IGNORE_LIST: super(Cell, self)._add_attr(name, value) def _sync_attr_for_compile(self): + # 用于同步属性到C++对象 """Sync the attr to c++ object.""" if self._attr_synced: return + # 获取cells cells = self.__dict__.get('_cells') + # 遍历cells for key in cells: cell = cells[key] + # 调用cell的_sync_attr_for_compile()方法 cell._sync_attr_for_compile() + # 添加attr self._add_attr(key, cell) + # 获取params params = self.__dict__.get('_params') + # 遍历params for key in params: if '.' in key: continue param = params[key] + # 添加attr self._add_attr(key, param) + # 获取params_list params_list = self.__dict__.get('_params_list') + # 遍历params_list for key in params_list: params_list_item = params_list[key] + # 添加attr self._add_attr(key, params_list_item) + # 遍历self.__dict__ for key in self.__dict__: value = self.__dict__[key] + # 添加attr self._add_attr(key, value) + # 将attr同步状态设置为True self._attr_synced = True + """ + 以下为数种不同的set attr方法,用于适配传入的不同参数,具体介绍请移步至setsttr博客查看 + https://forum.gitlink.org.cn/forums/9958/detail + """ def _set_attr_for_parameter(self, name, value): + # 用于设置参数的属性 """Set attr for parameter.""" + # 从self.__dict__中获取_cells和_params属性 cells = self.__dict__.get('_cells') params = self.__dict__.get('_params') + # 如果_params为None if params is None: + # 则抛出一个AttributeError异常 raise AttributeError("For 'Cell', can not assign params before Cell.__init__() is called.") + # 如果name在self.__dict__中,并且其值不为None if name in self.__dict__: if self.__dict__[name] is not None: + # 抛出一个TypeError异常 raise TypeError(f"For 'Cell', the {name} should not be Parameter.") del self.__dict__[name] + # 如果cells存在并且name在cells中,则抛出一个TypeError异常 if cells and name in cells: raise TypeError(f"For 'Cell', the {name} should be Cell, but got Parameter.") + # 最后,调用内部方法将参数插入到cells中 self.insert_param_to_cell(name, value) def _set_attr_for_parameter_tuple(self, name, value): + # 设置参数元组中的参数 """Set attr for parameter in ParameterTuple.""" + # 获取Cell的参数 params = self.__dict__.get('_params') + # 获取Cell的参数列表 params_list = self.__dict__.get('_params_list') + # 如果没有参数,则抛出异常 if params is None: raise AttributeError("For 'Cell', can not assign params before Cell.__init__() is called.") + # 初始化已存在的名称 exist_names = set("") exist_objs = set() + # 遍历value for item in value: + # 如果有多个相同的对象,它们的名称只检查一次 if item in exist_objs: # If there are multiple identical objects, their names only check once. continue + # 将item添加到已存在的名称中 exist_objs.add(item) + # 如果item的名称为默认名称,则警告 if item.name == PARAMETER_NAME_DEFAULT: logger.warning("For 'Cell', the parameter definition is deprecated.\n" "Please set a unique name for the parameter in ParameterTuple '{}'.".format(value)) + # 将item的名称添加到Cell的参数列表中 item.name = item.name + "$" + str(self._id) self._id += 1 + # 添加参数到Cell的参数列表 self.insert_param_to_cell(item.name, item, check_name_contain_dot=False) + # 如果已存在的名称中包含exist_names,则抛出异常 if item.name in exist_names: - raise ValueError("The value {} , its name '{}' already exists. " + raise ValueError("The value {}, its name '{}' already exists. " "Please set a unique name for the parameter.".format(value, item.name)) + # 将已存在的名称添加到已存在的名称中 exist_names.add(item.name) if context._get_mode() == context.PYNATIVE_MODE: + # 如果当前模式为PYNATIVE_MODE if name in self.__dict__: + # 如果name在self.__dict__中 del self.__dict__[name] + # 删除self.__dict__中name if name in params: + # 如果name在params中 del params[name] + # 删除params中name params_list[name] = value + # 将value添加到params_list中 else: + # 如果当前模式为NATIVE_MODE object.__setattr__(self, name, value) def _set_attr_for_parameter_in_list_or_tuple(self, name, value): + # 给定一个参数列表(或元组)和一个属性值,为列表中的每个参数设置一个特定的属性值 """Set attr for parameter in list or tuple.""" for item in value: + # 如果value中存在item,则跳过本次循环 if item in self.exist_objs: # If there are multiple identical objects, their names only check once. + # 如果value中存在重复的对象,则仅检查一次 continue self.exist_objs.add(item) + # 将item添加到exist_objs中 if item.name == PARAMETER_NAME_DEFAULT: + # 如果item的name为默认值,则将item的name添加到item的name中 item.name = item.name + "$" + str(self._id) self._id += 1 + # 如果item的name已经存在于exist_names中,则抛出异常 if item.name in self.exist_names: - raise ValueError("The value {} , its name '{}' already exists. " + raise ValueError("The value {}, its name '{}' already exists. " "Please set a unique name for the parameter.".format(value, item.name)) + # 如果item的name已经存在于exist_names中,则抛出异常 self.exist_names.add(item.name) + # 将item的name添加到exist_names中 object.__setattr__(self, name, value) def _set_attr_for_cell(self, name, value): + # 针对cell元素开发的set attr 方法 """Set attr for cell.""" cells = self.__dict__.get('_cells') params = self.__dict__.get('_params') @@ -721,57 +968,94 @@ class Cell(Cell_): if params and name in params: raise TypeError(f"For 'Cell', the {name} should be Parameter, but got Cell.") if self._auto_prefix: + # 更新参数名称 value.update_parameters_name(name + '.') + # 将参数添加到cells字典中 cells[name] = value if hasattr(self, '_cell_init_args'): + # 如果存在_cell_init_args属性 self.cell_init_args += str({name: value}) def _set_attr_for_params(self, name, value): if isinstance(value, Tensor) and self._params[name] is not None: + # 如果value是Tensor类型,且name对应的参数不为空 self._params[name].set_data(value) elif value is not None: + # 如果value不为空 raise TypeError(f"For 'Cell', the type of {name} should be Parameter or ParameterTuple, " f"but got {type(value).__name__}.") else: + # 如果value为空 self.insert_param_to_cell(name, None) def _set_attr_for_tensor(self, name, value): if context._get_mode() == context.PYNATIVE_MODE: + # 如果当前模式为PYNATIVE_MODE,则将tensor_list设置为self.__dict__.get('_tensor_list') tensor_list = self.__dict__.get('_tensor_list') + # 获取self.__dict__中的name if name in self.__dict__: + # 如果name存在,则删除self.__dict__中的name del self.__dict__[name] + # 将value添加到tensor_list中 tensor_list[name] = value else: + # 如果当前模式为NATIVE_MODE,则将self.__dict__[name]设置为value object.__setattr__(self, name, value) def __setattr__(self, name, value): + # 获取cells属性 cells = self.__dict__.get('_cells') + # 获取params属性 params = self.__dict__.get('_params') + # 如果value是Parameter类型 if isinstance(value, Parameter): + # 调用_set_attr_for_parameter方法 self._set_attr_for_parameter(name, value) + # 如果value是ParameterTuple类型 elif isinstance(value, ParameterTuple): + # 调用_set_attr_for_parameter_tuple方法 self._set_attr_for_parameter_tuple(name, value) + # 如果value是list或tuple类型,且value为可检查的参数列表或元组 elif isinstance(value, (list, tuple)) and value and _check_param_list_tuple(value): + # 调用_set_attr_for_parameter_in_list_or_tuple方法 self._set_attr_for_parameter_in_list_or_tuple(name, value) + # 如果value是Cell类型 elif isinstance(value, Cell): + # 调用_set_attr_for_cell方法 self._set_attr_for_cell(name, value) + # 如果params属性为真 elif params and name in params: + # 调用_set_attr_for_params方法 self._set_attr_for_params(name, value) + # 如果cells属性为真 elif cells and name in cells: + # 如果value不为空 if value is not None: + # 抛出TypeError异常 raise TypeError(f"For 'Cell', the type of {name} should be cell, but got {type(value).__name__}.") + # 将name和None设置为cells[name] self._cells[name] = None + # 如果value是Tensor类型 elif isinstance(value, Tensor): + # 调用_set_attr_for_tensor方法 self._set_attr_for_tensor(name, value) + # 其他情况 else: + # 如果value是Primitive类型 if isinstance(value, Primitive): + # 调用set_prim_instance_name方法 value.set_prim_instance_name(name) + # 将name和value设置为primitives[name] self._primitives[name] = value + # 将name和value设置为object.__setattr__(self, name, value) object.__setattr__(self, name, value) + # 如果name不在Cell.IGNORE_LIST中 if name not in Cell.IGNORE_LIST: + # 将_attr_synced设置为False self._attr_synced = False def extend_repr(self): + # 函数用于扩展单元格的描述 """ Expand the description of Cell. @@ -783,6 +1067,9 @@ class Cell(Cell_): return self.__repr__() def __repr__(self): + ''' + 返回一个字符串,该字符串表示当前对象的信息 + ''' extra_str = self.extend_repr() info_str = self.__class__.__name__ + '<' if self._cells: @@ -797,7 +1084,7 @@ class Cell(Cell_): info_str += extra_str + '>' return info_str - def load_parameter_slice(self, params): + def load_parameter_slice(self, params):#利用并行策略将parameters替代为分割的张量 """ Replace parameters with sliced tensors by parallel strategies. @@ -809,22 +1096,31 @@ class Cell(Cell_): if params is None: params = self.parameters_dict() if isinstance(params, OrderedDict): + # 遍历params中的每一个key for key in params: + # 获取key对应的tensor tensor = params[key].data + # 如果key不在parameter_layout_dict中,则输出警告信息 if key not in self.parameter_layout_dict: logger.info("The layout dict does not contain the key %s.", key) continue + # 如果key已经被sliced,则输出警告信息 if params[key].sliced: logger.debug("The param %s is already sliced.", key) continue + # 如果key不在parameter_layout_dict中,则输出警告信息 layout = self.parameter_layout_dict[key] + # 使用layout对tensor进行加载 new_tensor = _load_tensor_by_layout(tensor, layout) + # 设置key对应的tensor params[key].set_data(new_tensor, True) else: + # 如果params的类型不是OrderedDict,则抛出异常 raise TypeError("For 'load_parameter_slice', the argument 'params' should be OrderedDict type, " "but got {}.".format(type(params))) def _load_inputs(self, *inputs): + # 用于根据并行策略切片输入张量 """ Slice inputs tensors by parallel strategies. @@ -835,13 +1131,16 @@ class Cell(Cell_): # judge if *args exists in input if self.argspec[1] is not None: prefix = self.argspec[1] + # 如果inputs中有*args,则将其赋值给prefix for i in range(len(inputs)): key = prefix + str(i) self._construct_inputs_names = self._construct_inputs_names + (key,) self._construct_inputs_num = self._construct_inputs_num + 1 + # 对inputs中的每一个tensor,根据layout进行加载 for i, tensor in enumerate(inputs): key = self._construct_inputs_names[i] # if input is not used, self.parameter_layout_dict may not contain the key + # 如果layout存在,则根据layout加载tensor if key not in self.parameter_layout_dict: logger.warning("Layout dict does not contain the key %s.", key) parallel_inputs_run.append(tensor) @@ -849,38 +1148,51 @@ class Cell(Cell_): layout = self.parameter_layout_dict[key] new_tensor = _load_tensor_by_layout(tensor, layout) parallel_inputs_run.append(new_tensor) + # 返回一个字典,其中键是输入张量的索引,值是切片后的张量。这个字典的键通常是从 0 开始的整数,对应于输入张量的顺序 return tuple(parallel_inputs_run) def set_parallel_input_with_inputs(self, *inputs): + # 用于根据并行策略切片输入张量。函数接收一个可变参数列表 *inputs,表示输入张量 """ Slice inputs tensors by parallel strategies. Args: inputs (tuple): inputs of construct method. """ + # 调用上一个 _load_inputs 函数根据并行策略加载输入数据,并将结果存储在 _parallel_inputs_run 属性中 self._parallel_inputs_run = self._load_inputs(*inputs) def _get_construct_inputs_number_and_name(self): """Compute self._construct_inputs_names and self._construct_inputs_num""" + # 导入了一个名为 get_parse_method_of_class 的函数。这个函数用于获取一个类的解析方法 from mindspore._extends.parse.parser import get_parse_method_of_class fn = get_parse_method_of_class(self) + # 获取类的parse方法 self.argspec = inspect.getfullargspec(fn) + # 获取类的parse方法的参数信息 self._construct_inputs_num = fn.__code__.co_argcount + # 获取类的parse方法的参数数量 self._construct_inputs_names = fn.__code__.co_varnames if self._construct_inputs_num <= 0: - raise ValueError(f"For 'set_auto_parallel', the number of inputs must be greater than 0," + # 如果构造输入的数量小于等于0,抛出异常 + raise ValueError(f"For'set_auto_parallel', the number of inputs must be greater than 0," f"but got {self._construct_inputs_num}.") - if self._construct_inputs_names[0] != 'self': + # 如果构造输入的第一个元素不是self,抛出异常 + if self._construct_inputs_names[0]!='self': raise ValueError(f"First member of fn function must be self, but got {self._construct_inputs_names[0]}") + # 如果构造输入的数量减1大于fn函数成员的数量,抛出异常 if self._construct_inputs_num - 1 > len(self._construct_inputs_names): raise ValueError(f"Num of inputs must be greater than num of fn function members, num of inputs is \ {self._construct_inputs_names - 1}, num of fn function members is {len(self._construct_inputs_names)}") + # 将构造输入的第二个元素从fn函数成员中移除 self._construct_inputs_names = self._construct_inputs_names[1:self._construct_inputs_num] + # 将构造输入的数量减1 self._construct_inputs_num = self._construct_inputs_num - 1 def set_inputs(self, *inputs): + # 用于设置Cell的输入。函数接收一个可变参数列表 *inputs,表示输入张量 """ Save set inputs for computation graph. @@ -917,6 +1229,7 @@ class Cell(Cell_): self._dynamic_shape_inputs = inputs def get_inputs(self): + # 获取cell中的输入 """ Returns the dynamic_inputs of a cell object in one network. @@ -935,19 +1248,25 @@ class Cell(Cell_): Args: inputs (tuple): Inputs of the Cell object. """ + # 如果_dynamic_shape_inputs为None或者_dynamic_shape_inputs[0]为None,则调用_cell_graph_executor.compile函数 if self._dynamic_shape_inputs is None or self._dynamic_shape_inputs[0] is None: _cell_graph_executor.compile(self, *inputs, phase=self.phase, auto_parallel_mode=self._auto_parallel_mode) + # 如果_dynamic_shape_inputs不为None,但是_dynamic_shape_inputs[0]不为None,则检查_dynamic_shape_inputs else: self._check_compile_dynamic_shape(*inputs) + # 如果saved_dynamic_shape不为空,且saved_dynamic_shape[i].shape不等于_dynamic_shape_inputs[i].shape和_dynamic_shape_inputs[i].shape,则终止 if self.saved_dynamic_shape: for i in range(len(self.saved_dynamic_shape)): - if self.saved_dynamic_shape[i].shape != self._dynamic_shape_inputs[i].shape \ - and self.saved_dynamic_shape[i].shape != self._dynamic_shape_inputs[i].shape: + if self.saved_dynamic_shape[i].shape!= self._dynamic_shape_inputs[i].shape \ + and self.saved_dynamic_shape[i].shape!= self._dynamic_shape_inputs[i].shape: break return + # 否则,将_dynamic_shape_inputs赋值给saved_dynamic_shape self.saved_dynamic_shape = self._dynamic_shape_inputs + # 调用_cell_graph_executor.compile函数,并设置phase为self.phase,auto_parallel_mode为self._auto_parallel_mode _cell_graph_executor.compile(self, *self._dynamic_shape_inputs, phase=self.phase, auto_parallel_mode=self._auto_parallel_mode) + # 打印日志 logger.debug("Compiled Graph with dynamic shape") def compile_and_run(self, *inputs): @@ -962,35 +1281,58 @@ class Cell(Cell_): Returns: Object, the result of executing. """ + # 设置自动并行编译和运行 self._auto_parallel_compile_and_run = True + # 编译和运行 self.compile(*inputs) + # 创建新的输入 new_inputs = [] for i in inputs: + # 如果输入是Tensor类型 if isinstance(i, Tensor): + # 如果输入有初始化 if i.has_init: + # 初始化输入 i.init_data() + # 将输入添加到新的输入列表中 new_inputs.append(i) + # 如果输入是COOTensor或CSRTensor类型 elif isinstance(i, (COOTensor, CSRTensor)): + # 将输入添加到新的输入列表中 new_inputs.append(i) + # 如果输入是Variable类型 elif isinstance(i, Variable): + # 将输入添加到新的输入列表中 new_inputs.append(i.value) + # 如果输入是context.get_context("grad_for_scalar")为True的时候且输入是int或float类型 elif context.get_context("grad_for_scalar") and isinstance(i, (int, float)): + # 将输入添加到新的输入列表中 new_inputs.append(i) + # 如果自动并行模式为True elif hasattr(self, "enable_tuple_broaden") and self.enable_tuple_broaden and isinstance(i, tuple) and \ _check_all_tensor(i): + # 将输入添加到新的输入列表中 new_inputs.append(i) + # 如果自动并行模式为True if self._auto_parallel_mode: + # 如果新的输入是Tensor类型,且输入的虚拟标志为True if new_inputs and isinstance(new_inputs[0], Tensor) and inputs[0].virtual_flag: # get parallel inputs in sink mode, parallel inputs set in _cell_graph_executor.compile + # 获取并行输入 parallel_inputs_run = self._parallel_inputs_run + # 否则 else: + # 将新的输入添加到并行输入列表中 parallel_inputs_run = new_inputs + # 运行_cell_graph_executor return _cell_graph_executor(self, *parallel_inputs_run, phase=self.phase) + # 否则 return _cell_graph_executor(self, *new_inputs, phase=self.phase) def auto_parallel_compile_and_run(self): + # 检查是否在 'AUTO_PARALLEL' 或 'SEMI_AUTO_PARALLEL' 模式下执行编译和运行 """ Whether or not to execute compile and run in 'AUTO_PARALLEL' or 'SEMI_AUTO_PARALLEL' mode. @@ -1001,9 +1343,10 @@ class Cell(Cell_): def exec_checkpoint_graph(self): """Executes saving checkpoint graph operation.""" + # 调用_cell_graph_executor函数,传入参数phase='save' _cell_graph_executor(self, phase='save') - def insert_param_to_cell(self, param_name, param, check_name_contain_dot=True): + def insert_param_to_cell(self, param_name, param, check_name_contain_dot=True):#给当前cell添加parameter """ Adds a parameter to the current cell. @@ -1036,6 +1379,8 @@ class Cell(Cell_): self._params[param_name] = param def cast_param(self, param): + # 用于根据自动混合精度级别在 pynative 模式下将参数转换。这个接口在 auto mix precision 情况下使用,通常不需要显式调用。 + # 函数接收一个 Parameter 类型的参数 param,并返回一个转换后的 Parameter 对象 """ Cast parameter according to auto mix precision level in pynative mode. @@ -1048,17 +1393,19 @@ class Cell(Cell_): Parameter, the input parameter with type automatically cast. """ mixed_type = self.get_mixed_precision_type() - if mixed_type != MixedPrecisionType.NOTSET: + # 如果mixed_type不为NOTSET,则设置param的cast_dtype + if mixed_type!= MixedPrecisionType.NOTSET: if mixed_type == MixedPrecisionType.FP32: param.set_cast_dtype(mstype.float32) elif mixed_type == MixedPrecisionType.FP16: param.set_cast_dtype(mstype.float16) + # 如果param有set_cast_dtype方法,则重新设置param的cast_dtype elif hasattr(param, "set_cast_dtype"): # retest dtype param.set_cast_dtype() return param - def insert_child_to_cell(self, child_name, child_cell): + def insert_child_to_cell(self, child_name, child_cell):#用特定名称给当前cell添加子cell """ Adds a child cell to the current cell with a given name. @@ -1096,33 +1443,45 @@ class Cell(Cell_): """ return None - def remove_redundant_parameters(self): + def remove_redundant_parameters(self):#移除多余的parameters """ Remove the redundant parameters. This interface usually needs not to be used explicitly. """ cells = self.cells_and_names() + # 遍历cells中的每一个cell for _, cell in cells: + # 获取cell的参数 params = cell._params.items() + # 遍历cell的参数 for param_name, param in list(params): + # 如果参数名在parallel_parameter_name_list中,则从参数中移除 if param.name not in self.parallel_parameter_name_list: cell._params.pop(param_name) logger.info("remove the redundant parameter: %s", param.name) continue + # 获取cell的字典 cell_dict = cell.__dict__ + # 遍历cell的字典 for key in cell_dict: + # 如果字典中的值是ParameterTuple类型,则获取其中的参数 if isinstance(cell_dict[key], ParameterTuple): param_tuple = cell_dict[key] + # 创建新的参数列表 new_param_tuple = [] + # 遍历参数列表 for param in param_tuple: + # 如果参数名在parallel_parameter_name_list中,则从参数列表中移除 if param.name not in self.parallel_parameter_name_list: logger.info("remove the redundant parameter: %s in ParameterTuple", param.name) continue + # 将参数添加到新的参数列表中 new_param_tuple.append(param) + # 将新的参数列表赋值给cell的字典中的值 cell.__dict__[key] = ParameterTuple(new_param_tuple) - def init_parameters_data(self, auto_parallel_mode=False): + def init_parameters_data(self, auto_parallel_mode=False):#初始化_parameters_data """ Initialize all parameters and replace the original saved parameters in cell. @@ -1138,48 +1497,79 @@ class Cell(Cell_): """ replace = dict() + # 定义一个函数_updata,用于更新参数 def _updata(param): + # 如果param在replace中,则返回replace中的值 if param in replace: return replace.get(param) + # 定义一个变量layout,用于存储更新后的参数 layout = None + # 定义一个变量set_sliced,用于标记是否设置sliced set_sliced = False + # 如果auto_parallel_mode为True,则设置set_sliced为True if auto_parallel_mode: set_sliced = True + # 如果param的name不在self.parameter_layout_dict中,则输出警告信息 if param.name not in self.parameter_layout_dict: logger.debug("Layout dict does not contain the key %s.", param.name) + # 否则,获取layout else: layout = self.parameter_layout_dict[param.name] + # 将param的初始数据更新到layout中 new_p = param.init_data(layout, set_sliced=set_sliced) + # 将param更新到replace中 replace[param] = new_p + # 返回更新后的参数 return new_p # replace all original usage. + # 更新所有原有的usage cells = self.cells_and_names() for _, cell in cells: + # 获取cell的参数 params = cell._params.items() + # 遍历参数 for param_name, param in params: + # 如果auto_parallel_mode为False,则跳过 if not auto_parallel_mode: + # 将参数更新到cell的参数中 cell._params[param_name] = _updata(param) continue + # 如果param的name在parallel_parameter_name_list中,则跳过 if param.name in self.parallel_parameter_name_list: + # 将参数更新到cell的参数中 cell._params[param_name] = _updata(param) + continue + # 否则,将参数更新到cell的参数中 + cell._params[param_name] = _updata(param) + # 获取cell的__dict__ cell_dict = cell.__dict__ + # 遍历cell的__dict__ for key in cell_dict: + # 如果cell的__dict__中是ParameterTuple类型 if isinstance(cell_dict[key], ParameterTuple): + # 将ParameterTuple类型的参数更新到cell的__dict__中 param_tuple = cell_dict[key] new_param_tuple = [] + # 遍历ParameterTuple的参数 for param in param_tuple: + # 如果auto_parallel_mode为False,则跳过 if not auto_parallel_mode: + # 将参数更新到new_param_tuple中 new_param_tuple.append(_updata(param)) continue + # 如果param的name在parallel_parameter_name_list中,则跳过 if param.name in self.parallel_parameter_name_list: + # 将参数更新到new_param_tuple中 new_param_tuple.append(_updata(param)) + # 否则,将参数更新到new_param_tuple中 else: new_param_tuple.append(param) cell.__dict__[key] = ParameterTuple(new_param_tuple) return replace def parameters_dict(self, recurse=True): + # 用于获取Cell的参数字典。它接收一个可选参数 recurse(是否包含子Cell的参数),默认为 True。函数内部实现了以下功能 """ Gets parameters dictionary. @@ -1192,11 +1582,15 @@ class Cell(Cell_): OrderedDict, return parameters dictionary. """ param_dict = OrderedDict() + # 遍历获取的参数 for param in self.get_parameters(expand=recurse): + # 将参数名和参数值存入字典中 param_dict[param.name] = param + # 返回字典 return param_dict def parameters_broadcast_dict(self, recurse=True): + # 用于获取Cell的参数字典用于广播。它接收一个可选参数 recurse(是否包含子Cell的参数),默认为 True """ Gets the parameters broadcast dictionary of this cell. @@ -1207,14 +1601,19 @@ class Cell(Cell_): OrderedDict, return parameters broadcast dictionary. """ param_dict = OrderedDict() + # 遍历获取的参数 for param in self.get_parameters(expand=recurse): + # 如果参数不是层级并行,则将其存入字典中 if param.layerwise_parallel is False: param_dict[param.name] = param + # 如果字典为空,则返回None if not param_dict: return None + # 否则返回字典 return param_dict def update_parameters_name(self, prefix='', recurse=True): + # 根据给定的字符串更新参数名称 """ Updates the names of parameters with given prefix string. @@ -1227,11 +1626,14 @@ class Cell(Cell_): Validator.check_str_by_regular(prefix) for name, param in self.parameters_and_names(expand=recurse): - if prefix != '': + # 如果prefix不为空,则将param的is_init设置为False + if prefix!= '': param.is_init = False + # 将prefix加上param的name赋值给param的name param.name = prefix + name def _update_local_parameters_name(self, prefix='', recurse=True): + # 用于更新本地参数的名称。它接收两个参数:prefix(可选的预定义前缀字符串)和 recurse(是否包含子Cell的参数) """ Updates the names of local parameters with given prefix string. @@ -1246,13 +1648,16 @@ class Cell(Cell_): Validator.check_str_by_regular(prefix) for name, param in self.parameters_and_names(expand=recurse): + # 如果name在_user_parameters中,则跳过 if name in self._user_parameters: continue - if prefix != '': + # 如果prefix不为空,则将param设置为is_init为False + if prefix!= '': param.is_init = False + # 将prefix加上name赋值给param.name param.name = prefix + name - def trainable_params(self, recurse=True): + def trainable_params(self, recurse=True):#返回全部的trainable_params """ Returns all trainable parameters. @@ -1266,7 +1671,7 @@ class Cell(Cell_): """ return list(filter(lambda x: x.requires_grad, self.get_parameters(expand=recurse))) - def untrainable_params(self, recurse=True): + def untrainable_params(self, recurse=True):#返回全部的untrainable_params """ Returns all untrainable parameters. @@ -1280,7 +1685,7 @@ class Cell(Cell_): """ return list(filter(lambda x: not x.requires_grad, self.get_parameters(expand=recurse))) - def get_parameters(self, expand=True): + def get_parameters(self, expand=True):#返回cell上的迭代器 """ Returns an iterator over cell parameters. @@ -1308,13 +1713,16 @@ class Cell(Cell_): Check the names of cell parameters. """ names = set("") + # 创建一个空集合,用于存储参数名称 for value, param in self.parameters_and_names(): + # 遍历参数和参数名称 if param.name in names: + # 如果参数名称已存在,抛出异常 raise ValueError("The value of {} is {}, its name '{}' already exists. " "Please set a unique name for the parameter.".format(value, param, param.name)) names.add(param.name) - def parameters_and_names(self, name_prefix='', expand=True): + def parameters_and_names(self, name_prefix='', expand=True):#返回cell上的迭代器包括名称 """ Returns an iterator over cell parameters. @@ -1337,26 +1745,34 @@ class Cell(Cell_): ... names.append(m[0]) """ cells = [] + # 如果expand为True,则cells为self.cells_and_names(name_prefix=name_prefix) if expand: cells = self.cells_and_names(name_prefix=name_prefix) + # 否则cells为[(name_prefix, self)] else: cells.append((name_prefix, self)) params_set = set() + # 遍历cells,获取每一个cell的参数 for cell_name, cell in cells: params = cell._params.items() + # 遍历参数 for par_name, par in params: + # 如果参数未初始化,则获取参数 if par.inited_param is not None: par = par.inited_param + # 如果参数不为None,且id不在params_set中,则将参数添加到params_set中 if par is not None and id(par) not in params_set: params_set.add(id(par)) par_new_name = par_name + # 如果cell_name为空,则将参数的名称添加到par_new_name中 if cell_name: par_new_name = cell_name + '.' + par_new_name + # 返回参数的新名称和参数 yield par_new_name, par - def cells_and_names(self, cells=None, name_prefix=''): + def cells_and_names(self, cells=None, name_prefix=''):#返回网络中cell上的迭代器和名字 """ Returns an iterator over all cells in the network. @@ -1399,7 +1815,7 @@ class Cell(Cell_): for ele in cell.cells_and_names(t_cells, cells_name_prefix): yield ele - def cells(self): + def cells(self):# 返回一个迭代器,遍历Cell中的直接子Cell """ Returns an iterator over immediate cells. @@ -1408,14 +1824,14 @@ class Cell(Cell_): """ return self.name_cells().values() - def _set_scope(self, name): + def _set_scope(self, name):# 首次设置名称 """Sets the name on the first time.""" if self._scope is None: self._scope = name elif self._scope == 'recompute_': self._scope = self._scope + name - def _children_scope_recursive(self, parent_prefix='Default'): + def _children_scope_recursive(self, parent_prefix='Default'):# 递归地生成网络的每一层 """Generates the scope of each layer of the network recursively.""" reserve_class_name_in_scope = context.get_context("reserve_class_name_in_scope") @@ -1429,7 +1845,7 @@ class Cell(Cell_): if reserve_class_name_in_scope else "")): yield key, value - def get_scope(self): + def get_scope(self):# 返回一个网络中的cell对象的scope """ Returns the scope of a cell object in one network. @@ -1438,7 +1854,7 @@ class Cell(Cell_): """ return self._scope - def generate_scope(self): + def generate_scope(self):#为网络中的每个cell对象生成scope """Generate the scope for each cell object in the network.""" for name, cell in self._children_scope_recursive(): cell._set_scope(name) @@ -1454,24 +1870,35 @@ class Cell(Cell_): """ value_set = set() cells = OrderedDict() + # 遍历cells字典,获取每一个cell的值 for name, cell in self._cells.items(): + # 如果cell不为空,且不在value_set中,则将cell添加到value_set中 if cell is not None and cell not in value_set: value_set.add(cell) cells[name] = cell + # 返回cells字典 return cells def _add_mixed_precision_flag(self, **flags): """Add mixed precision flag to current cell""" + # 如果flags中存在fp16,且flags中的fp16属性为True if "fp16" in flags and flags.get("fp16", False): + # 设置当前cell的mixed precision类型为FP16 Cell_.set_mixed_precision_type(self, MixedPrecisionType.FP16) + # 如果flags中存在fp32,且flags中的fp32属性为True if "fp32" in flags and flags.get("fp32", False): + # 设置当前cell的mixed precision类型为FP32 Cell_.set_mixed_precision_type(self, MixedPrecisionType.FP32) def _add_mixed_precision_flag_recursive(self, **flags): """Add mixed precision flag to each cell""" + # 如果flags中存在fp16,且flags中的fp16属性为True if "fp16" in flags and flags.get("fp16", False): + # 调用_set_mixed_precision_type_recursive函数,设置当前cell的mixed precision类型为FP16 self._set_mixed_precision_type_recursive(MixedPrecisionType.FP16) + # 如果flags中存在fp32,且flags中的fp32属性为True if "fp32" in flags and flags.get("fp32", False): + # 调用_set_mixed_precision_type_recursive函数,设置当前cell的mixed precision类型为FP32 self._set_mixed_precision_type_recursive(MixedPrecisionType.FP32) def add_flags(self, **flags): @@ -1485,10 +1912,15 @@ class Cell(Cell_): dataset. Users can also customize network attributes by this parameter. Default: None. """ if not hasattr(self, "_mindspore_flags"): + # 如果_mindspore_flags不存在,则初始化为空字典 self._mindspore_flags = {} + # 将flags添加到_mindspore_flags中 self._mindspore_flags.update({**flags}) + # 将flags的属性添加到self中 self.__dict__.update({**flags}) + # 将flags的属性添加到self中,并将其设置为self self._add_mixed_precision_flag(**flags) + # 返回self return self def add_flags_recursive(self, **flags): @@ -1500,16 +1932,24 @@ class Cell(Cell_): dataset. Users can also customize network attributes by this parameter. Default: None. """ self.add_flags(**flags) + # 添加flags self._add_mixed_precision_flag_recursive(**flags) + # 递归添加mixed precision flag for cell in self.cells(): + # 遍历cells cell.add_flags_recursive(**flags) + # 递归添加flags return self + # 用于将传入的 keyword 参数添加到 _cell_init_args 属性中 def _add_init_args(self, **args): + # 首先检查 _cell_init_args 属性是否存在 if hasattr(self, '_cell_init_args'): + # 如果存在,则将其与传入的参数进行合并并返回。这里使用 str() 函数将合并后的参数转换为字符串,以便在日志中记录 self._cell_init_args += str({**args}) def get_flags(self): + # 用于获取Cell的自定义属性,这些属性可以通过 add_flags 方法添加 """ Get the self_defined attributes of the cell, which can be added by `add_flags` method. """ @@ -1520,10 +1960,15 @@ class Cell(Cell_): def _set_mixed_precision_type_recursive(self, mixed_type): """Set mixed precision type to each cell""" Cell_.set_mixed_precision_type(self, mixed_type) + # 遍历每一个Cell for cell in self.cells(): + # 递归调用_set_mixed_precision_type_recursive函数 cell._set_mixed_precision_type_recursive(mixed_type) def to_float(self, dst_type): + # 用于将Cell及其子Cell的输入转换为特定浮点类型。 + # 当 dst_type 为 mindspore.dtype.float16 时,所有Cell的输入(包括输入、参数和张量)都将转换为 float16 类型。 + # 请注意,多次调用此函数将覆盖先前的设置 """ Add cast on all inputs of cell and child cells to run with certain float type. @@ -1554,14 +1999,20 @@ class Cell(Cell_): raise ValueError("For 'to_float', the argument 'dst_type' should be float32 or float16, " "but got {}.".format(dst_type)) if dst_type == mstype.float16: + # 设置为FP16类型 self._set_mixed_precision_type_recursive(MixedPrecisionType.FP16) else: + # 设置为FP32类型 self._set_mixed_precision_type_recursive(MixedPrecisionType.FP32) + # 将flags字典中的值设置为dst_type的类型 flags = {'fp16': dst_type == mstype.float16, 'fp32': dst_type == mstype.float32} + # 添加初始化参数 self._add_init_args(**flags) return self def set_boost(self, boost_type): + # 用于配置网络以启用自动加速算法 + # 注:启用自动加速算法可能会影响网络的准确性,请谨慎选择 """ In order to improve the network performance, configure the network auto enable to accelerate the algorithm in the algorithm library. @@ -1585,10 +2036,12 @@ class Cell(Cell_): raise ValueError("For 'set_boost', the argument 'boost_type' should be 'less_bn', " "but got {}.".format(boost_type)) flags = {"less_bn": boost_type == "less_bn"} + # 调用add_flags_recursive函数,传入参数flags self.add_flags_recursive(**flags) return self def set_grad(self, requires_grad=True): + # 设置梯度,在 PyNative 模式下,此参数指定是否需要计算梯度。如果为 True,则在执行forward 网络时需要生成后向网络 """ Sets the cell flag for gradient. In pynative mode, this parameter specifies whether the network requires gradients. If true, the backward network needed to compute the gradients will be generated when the forward @@ -1605,6 +2058,8 @@ class Cell(Cell_): return self def set_train(self, mode=True): + # 用于设置单元格的训练模式。它将单元格本身和所有子单元格的训练模式设置为给定的 mode 值。 + # 对于具有不同构造的层(如 BatchNorm),通过此属性区分训练和预测模式。如果设置为 True,则执行训练分支,否则执行另一个分支 """ Sets the cell to training mode. @@ -1622,6 +2077,7 @@ class Cell(Cell_): self._phase = 'predict' else: self._phase = 'train' + # 将cell的training属性设置为mode self.add_flags_recursive(training=mode) return self @@ -1632,10 +2088,12 @@ class Cell(Cell_): Args: mode (bool): Specifies whether the mode is parameter broadcast. Default: True. """ + # 调用add_flags_recursive函数,传入参数broadcast_flag,并将mode设置为True self.add_flags_recursive(broadcast_flag=mode) + # 返回self return self - def set_auto_parallel(self): + def set_auto_parallel(self):#设置自动并行 """ Set the cell to auto parallel mode. @@ -1648,11 +2106,16 @@ class Cell(Cell_): self._get_construct_inputs_number_and_name() def flatten_weights(self): + # 用于重置权重参数的数据,以便它们使用连续的内存块按数据类型分组 """ Reset data for weight parameters so that they are using contiguous memory chunks grouped by data type. """ Tensor._flatten_tensors(self.trainable_params()) # pylint: disable=W0212 + """ + 以下函数用于实现hook功能,具体原理和介绍请转移至博客查看 + https://forum.gitlink.org.cn/forums/9960/detail + """ def _run_forward_pre_hook(self, inputs): """ Running forward pre hook function registered on Cell object. @@ -1666,14 +2129,19 @@ class Cell(Cell_): Supported Platforms: ``Ascend`` ``GPU`` ``CPU`` """ + # 获取Cell对象的类名 cell_id = self.cls_name + "(" + str(id(self)) + ")" + # 遍历forward_pre_hook字典 for fn in self._forward_pre_hook.values(): + # 调用fn函数 ret = fn(cell_id, inputs) + # 如果返回值不为空,则将其转换为tuple if ret is not None: if not isinstance(ret, tuple): inputs = (ret,) else: inputs = ret + # 返回新的输入对象 return inputs def register_forward_pre_hook(self, hook_fn): @@ -1742,17 +2210,23 @@ class Cell(Cell_): return HookHandle() if not isinstance(hook_fn, (FunctionType, MethodType)): - raise TypeError(f"When using 'register_forward_pre_hook(hook_fn)', the type of 'hook_fn' should be python " + raise TypeError(f"When using'register_forward_pre_hook(hook_fn)', the type of 'hook_fn' should be python " f"function, but got {type(hook_fn)}.") if hook_fn.__code__.co_name == "staging_specialize": raise TypeError(f"Decorating hook function {hook_fn.__name__} with '@ms_function' is not supported.") + # 将hook_fn的名称设置为enable_forward_pre_hook self._enable_forward_pre_hook = True + # 设置hook_fn的变更 _pynative_executor.set_hook_changed(self) if not hasattr(self, '_forward_pre_hook_key'): + # 如果没有_forward_pre_hook_key属性,则设置_forward_pre_hook_key为-1 self._forward_pre_hook_key = -1 + # 将_forward_pre_hook_key加1 self._forward_pre_hook_key += 1 + # 将hook_fn添加到_forward_pre_hook中 self._forward_pre_hook[self._forward_pre_hook_key] = hook_fn + # 返回HookHandle handle = HookHandle(self, self._forward_pre_hook_key, "_forward_pre_hook") return handle @@ -1771,6 +2245,7 @@ class Cell(Cell_): ``Ascend`` ``GPU`` ``CPU`` """ cell_id = self.cls_name + "(" + str(id(self)) + ")" + # 遍历_forward_hook字典,调用fn函数,返回输出 for fn in self._forward_hook.values(): ret = fn(cell_id, inputs, output) if ret is not None: @@ -1845,11 +2320,12 @@ class Cell(Cell_): return HookHandle() if not isinstance(hook_fn, (FunctionType, MethodType)): - raise TypeError(f"When using 'register_forward_hook(hook_fn)', the type of 'hook_fn' should be python " + raise TypeError(f"When using'register_forward_hook(hook_fn)', the type of 'hook_fn' should be python " f"function, but got {type(hook_fn)}.") if hook_fn.__code__.co_name == "staging_specialize": raise TypeError(f"Decorating hook function {hook_fn.__name__} with '@ms_function' is not supported.") + # 注册前向调用的hook函数 self._enable_forward_hook = True _pynative_executor.set_hook_changed(self) if not hasattr(self, '_forward_hook_key'): @@ -1872,15 +2348,21 @@ class Cell(Cell_): Supported Platforms: ``Ascend`` ``GPU`` ``CPU`` """ + # 如果输入的数量大于1,则将输入转换为Cell的输出 if len(inputs) > 1: inputs = self._cell_backward_hook(inputs) + # 如果输入的数量小于1,则将输入转换为Cell的输出,并将输入转换为tuple else: inputs = self._cell_backward_hook(*inputs) + # 如果输入的类型是tuple,则调用construct方法,将输入转换为tuple if isinstance(inputs, tuple): outputs = self.construct(*inputs) + # 如果输入的类型不是tuple,则调用construct方法,将输入转换为tuple else: outputs = self.construct(inputs) + # 将输出转换为Cell的输出 outputs = self._cell_backward_hook(outputs) + # 返回输出 return outputs def register_backward_hook(self, hook_fn): @@ -1949,19 +2431,27 @@ class Cell(Cell_): return HookHandle() if not isinstance(hook_fn, (FunctionType, MethodType)): - raise TypeError(f"When using 'register_backward_hook(hook_fn)', the type of 'hook_fn' should be python " + raise TypeError(f"When using'register_backward_hook(hook_fn)', the type of 'hook_fn' should be python " f"function, but got {type(hook_fn)}.") + # 判断hook_fn是否为函数 if self._cell_backward_hook is None: + # 如果没有设置backward hook,则设置backward hook self._enable_backward_hook = True + # 设置backward hook为True self._cell_backward_hook = inner.CellBackwardHook(self.cls_name + "(" + str(id(self)) + ")") + # 创建cell_backward_hook backward_hook_key = self._cell_backward_hook.register_backward_hook(hook_fn) + # 注册backward hook handle = HookHandle(self, backward_hook_key, "_cell_backward_hook") else: + # 如果设置了backward hook,则注册backward hook backward_hook_key = self._cell_backward_hook.register_backward_hook(hook_fn) + # 注册backward hook handle = HookHandle(self, backward_hook_key, "_cell_backward_hook") + # 返回handle return handle - def set_param_ps(self, recurse=True, init_in_server=False): + def set_param_ps(self, recurse=True, init_in_server=False):#设置可训练的参数是否由参数服务器进行更新,以及是否由可训练的参数在服务器上进行初始化 """ Set whether the trainable parameters are updated by parameter server and whether the trainable parameters are initialized on server. @@ -1978,7 +2468,7 @@ class Cell(Cell_): for param in params: param.set_param_ps(init_in_server) - def set_param_fl(self, push_to_server=False, pull_from_server=False, requires_aggr=True): + def set_param_fl(self, push_to_server=False, pull_from_server=False, requires_aggr=True):#设置参数和服务器交互的方式 """ Set the way of parameter and server interaction. @@ -1991,7 +2481,7 @@ class Cell(Cell_): for param in params: param[1].set_param_fl(push_to_server, pull_from_server, requires_aggr) - def set_comm_fusion(self, fusion_type, recurse=True): + def set_comm_fusion(self, fusion_type, recurse=True):#为cell中每个参数设置 `comm_fusion` """ Set `comm_fusion` for all the parameters in this cell. Please refer to the description of :class:`mindspore.Parameter.comm_fusion`. @@ -2010,11 +2500,14 @@ class Cell(Cell_): def _set_recompute_scope(self, mode): prefix = 'recompute_' + # 如果模式存在,则设置_scope为prefix if mode: if self._scope is None: self._scope = prefix + # 如果_scope不以prefix开头,则更新_scope elif not self._scope.startswith(prefix): self._scope = prefix + self._scope + # 如果_scope以prefix开头,则从_scope中删除prefix elif self._scope is not None and self._scope.startswith(prefix): self._scope = self._scope[len(prefix):] @@ -2022,10 +2515,15 @@ class Cell(Cell_): """ Set the model parallel communication in cell recomputed. """ + # 遍历每一个primitive for _, value in self._primitives.items(): + # 如果primitive存在 if value: + # 将recompute_comm_op设置为mp_comm_recompute value.add_prim_attr("recompute_comm_op", mp_comm_recompute) + # 遍历每一个cell for cell in self.cells(): + # 调用cell的_mp_comm_recompute函数 cell._mp_comm_recompute(mp_comm_recompute) def _parallel_optimizer_comm_recompute(self, parallel_optimizer_comm_recompute=False): @@ -2039,10 +2537,15 @@ class Cell(Cell_): """ Slice the cell output which would remains in memory. """ + # 遍历每一个primitive for _, value in self._primitives.items(): + # 如果primitive存在 if value: + # 将slice_activation设置为True value.add_prim_attr("slice_activation", slice_activation) + # 遍历每一个cell for cell in self.cells(): + # 调用cell的_recompute_slice_activation函数 cell._recompute_slice_activation(slice_activation) def _recompute(self, mode=True, output_recompute=False): @@ -2050,23 +2553,31 @@ class Cell(Cell_): Set the cell recomputed. """ if context._get_mode() == context.PYNATIVE_MODE: + # 如果当前模式为PYNATIVE_MODE,则抛出异常 raise TypeError("Recompute is not supported in pynative mode currently, you can use " "'context.set_context(mode=context.GRAPH_MODE)' to set graph mode.") + # 检查mode是否为布尔值 Validator.check_bool(mode) + # 检查output_recompute是否为布尔值 Validator.check_bool(output_recompute) + # 如果_has_config_recompute为False,则设置_has_config_recompute为True if not self._has_config_recompute: self._has_config_recompute = True + # 否则抛出异常 else: raise RuntimeError("The recompute interface can be configured only once." " When the parent cell is configured, the child cell should not be configured") + # 设置_recompute_scope self._set_recompute_scope(mode) + # 如果mode为True,且output_recompute为False,则添加output_no_recompute标志 if mode and not output_recompute: self.add_flags(output_no_recompute=True) + # 遍历所有的cell for cell in self.cells(): + # 调用cell的_recompute函数 cell._recompute(mode, True) - @args_type_check(mp_comm_recompute=bool, parallel_optimizer_comm_recompute=bool) - def recompute(self, **kwargs): + def recompute(self, **kwargs):#cell中recompute的设置 """ Set the cell recomputed. All the primitive in the cell except the outputs will be set recomputed. If a primitive set recomputed feeds into some backward nodes for computing gradient, rather than @@ -2097,8 +2608,10 @@ class Cell(Cell_): Default: False. """ self._recompute() - if 'mp_comm_recompute' in kwargs.keys(): + # 如果kwargs中有mp_comm_recompute,则调用_mp_comm_recompute函数 + if'mp_comm_recompute' in kwargs.keys(): self._mp_comm_recompute(kwargs.get('mp_comm_recompute', False)) + # 如果kwargs中有parallel_optimizer_comm_recompute,则调用_parallel_optimizer_comm_recompute函数 if 'parallel_optimizer_comm_recompute' in kwargs.keys(): if (kwargs.get('parallel_optimizer_comm_recompute', False) and context.get_auto_parallel_context("pipeline_stages") > 1): @@ -2106,16 +2619,18 @@ class Cell(Cell_): "are not support recomputation in pipeline parallel.") elif context.get_auto_parallel_context("pipeline_stages") == 1: self._parallel_optimizer_comm_recompute(kwargs.get('parallel_optimizer_comm_recompute', False)) - if 'recompute_slice_activation' in kwargs.keys(): + # 如果kwargs中有recompute_slice_activation,则调用_recompute_slice_activation函数 + if'recompute_slice_activation' in kwargs.keys(): self._recompute_slice_activation(kwargs.get('recompute_slice_activation', False)) + # 遍历kwargs中的key,如果key不在'recompute'中,则抛出ValueError异常 for key, _ in kwargs.items(): if key not in ('mp_comm_recompute', 'parallel_optimizer_comm_recompute', 'recompute_slice_activation'): raise ValueError("For 'recompute', keyword '%s' is not recognized! " "the key kwargs must be 'mp_comm_recompute', " "'parallel_optimizer_comm_recompute', 'recompute_slice_activation'" % key) - def infer_param_pipeline_stage(self): + def infer_param_pipeline_stage(self):#静态分析pipeline_stages中的数据 """ Infer pipeline stages of all parameters in the cell. @@ -2132,24 +2647,35 @@ class Cell(Cell_): Raises: RuntimeError: If there is a parameter does not belong to any stage. """ + # 从 mindspore.parallel._utils 模块中导入了一个名为 _get_global_rank 和一个名为 _get_device_num 的函数。这两个函数是用于获取全局排名和设备数量的 from mindspore.parallel._utils import _get_global_rank, _get_device_num logger.warning(f"This interface may be deleted in the future.") + # 获取自动并行上下文中的pipeline_stages stage_num = context.get_auto_parallel_context("pipeline_stages") + # 获取设备数量 device_num = _get_device_num() + # 获取当前程序的计算节点 rank_id = _get_global_rank() + # 计算每个stage的设备数量 per_stage_devices = device_num // stage_num + # 获取当前stage的索引 current_stage = rank_id // per_stage_devices + # 初始化参数列表 params = [] + # 遍历训练参数 for param in self.trainable_params(): + # 如果参数不在任何stage中,抛出异常 if not param._pipeline_stage_list: raise RuntimeError("For 'infer_param_pipeline_stage', the parameter {} does not belong to any stage, " "please check whether the cell where the param locates has been set " "'pipeline_stage'. Otherwise, the parameter should use 'add_pipeline_stage' " "to add its stage information".format(param.name)) + # 如果当前stage在参数的pipeline_stage列表中,则将参数添加到参数列表中 if current_stage in param._pipeline_stage_list: params.append(param) return params + #检查输入的是否与动态形状输入的一致 def _check_compile_dynamic_shape(self, *inputs): """ Check if graph has been compiled with dynamic shape. @@ -2157,38 +2683,60 @@ class Cell(Cell_): Args: inputs (tuple): Inputs of the Cell object. """ + # 获取输入的长度 len_inputs = len(inputs) + # 获取动态形状输入的长度 len_dynamic_shape_inputs = len(self._dynamic_shape_inputs) - if len_dynamic_shape_inputs != len_inputs: + # 检查输入的长度是否与动态形状输入的长度一致 + if len_dynamic_shape_inputs!= len_inputs: raise ValueError( f"For 'set_inputs', the Length of Tensor should be {len_inputs}, but got {len_dynamic_shape_inputs}." + f"For'set_inputs', the Length of Tensor should be {len_inputs}, but got {len_dynamic_shape_inputs}." ) + # 遍历动态形状输入 for tensor_index in range(len_dynamic_shape_inputs): + # 获取动态形状输入 i_dynamic_shape_inputs = self._dynamic_shape_inputs[tensor_index] + # 获取输入 i_inputs = inputs[tensor_index] + # 检查输入的数据类型是否与动态形状输入的数据类型一致 if i_dynamic_shape_inputs.dtype is not i_inputs.dtype: raise TypeError( f"For 'set_inputs', the DataType of Tensor should be {i_inputs.dtype}, but got " + f"For'set_inputs', the DataType of Tensor should be {i_inputs.dtype}, but got " f"{i_dynamic_shape_inputs.dtype}." ) + # 获取输入的形状 set_inputs_shape = list(i_dynamic_shape_inputs.shape) inputs_shape = list(i_inputs.shape) - if len(inputs_shape) != len(set_inputs_shape): + # 检查输入的形状是否与动态形状输入的形状一致 + if len(inputs_shape)!= len(set_inputs_shape): raise ValueError( f"For 'set_inputs' the Dimension of Tensor shape must be {len(inputs_shape)}, but got " + f"For'set_inputs' the Dimension of Tensor shape must be {len(inputs_shape)}, but got " f"{len(set_inputs_shape)}." ) + # 遍历输入的形状 for shape_index in i_dynamic_shape_inputs.shape: - if shape_index != -1: + # 检查形状是否为-1 + if shape_index!= -1: + # 获取动态形状输入的形状索引 dynamic_index = i_dynamic_shape_inputs.shape.index(shape_index) - if set_inputs_shape[dynamic_index] != inputs_shape[dynamic_index]: + # 检查输入的形状是否与动态形状输入的形状一致 + if set_inputs_shape[dynamic_index]!= inputs_shape[dynamic_index]: raise ValueError( f"For 'Length of Tensor shape', the value must be the same with that of inputs, but" f" got {i_dynamic_shape_inputs.shape}." ) -class GraphCell(Cell): +class GraphCell(Cell):# GraphCell类 + # 用于运行从 MindIR 加载的图。 + # 这个功能仍然在开发中。当前 GraphCell 不支持修改图的结构,只能使用与输入相同形状和类型的数据进行导出。 + + # 参数 graph 是一个编译后的图,从 MindIR 加载。 + + # 参数 params_init 是一个字典,用于初始化图中的参数。键是参数名称,值是类型为 Tensor 或 Parameter 的对象。如果参数在图中有相应的名称,更新其值。如果没有,忽略它。默认值为 None """ Base class for running the graph loaded from a MindIR. @@ -2227,31 +2775,42 @@ class GraphCell(Cell): [4. 6. 4.]]]] """ def __init__(self, graph, params_init=None): + # 初始化GraphCell类 super(GraphCell, self).__init__(auto_prefix=True) + # 判断传入的graph是否为FuncGraph类型 if not isinstance(graph, FuncGraph): raise TypeError(f"For 'GraphCell', the argument 'graph' must be a FuncGraph loaded from MindIR, " f"but got type {type(graph)}.") self.graph = graph + # 初始化params_init参数 params_init = {} if params_init is None else params_init + # 判断params_init参数是否为字典类型 if not isinstance(params_init, dict): raise TypeError(f"For 'GraphCell', the argument 'params_init' must be a dict, but got {type(params_init)}.") + # 遍历params_init参数,将其转换为Tensor类型 for name, value in params_init.items(): if not isinstance(name, str) or not isinstance(value, Tensor): raise TypeError("For 'GraphCell', the key of the 'params_init' must be str, " "and the value must be Tensor or Parameter, " f"but got the key type: {type(name)}, and the value type: {type(value)}") + # 更新graph的hyper_params params_dict = update_func_graph_hyper_params(self.graph, params_init) + # 遍历params_dict,将其赋值给self._params for name, param in params_dict.items(): self._params[name] = param def construct(self, *inputs): + # 返回graph函数调用 return self.graph(*inputs) def __call__(self, *inputs): + # 设置graph_load_from_mindir参数 self.phase = "graph_load_from_mindir" + # 添加graph_load_from_mindir参数 self._add_attr("graph_load_from_mindir", self.graph) + # 返回compile_and_run函数调用 return self.compile_and_run(*inputs) @@ -2262,6 +2821,8 @@ def _check_param_list_tuple(value): :return: The types of all inputs are parameter. """ for item in value: + # 如果item不是Parameter类型,返回False if not isinstance(item, Parameter): return False + # 如果所有的item都是Parameter类型,返回True return True diff --git a/mindspore/python/mindspore/nn/dynamic_lr.py b/mindspore/python/mindspore/nn/dynamic_lr.py index 716fdb722a1..cc933e32144 100644 --- a/mindspore/python/mindspore/nn/dynamic_lr.py +++ b/mindspore/python/mindspore/nn/dynamic_lr.py @@ -13,12 +13,15 @@ # limitations under the License. # ============================================================================ """Dynamic Learning Rate""" +# 本文件为动态学习率定义,与LearningRateSchedule类相比可自定义参数更多 +# 导入数学模块 import math - +# 导入检查模块 from mindspore._checkparam import Validator as validator def piecewise_constant_lr(milestone, learning_rates): + # 获取分段常量学习率 r""" Get piecewise constant learning rate. The learning rate for each step will be stored in a list. @@ -55,41 +58,60 @@ def piecewise_constant_lr(milestone, learning_rates): >>> print(output) [0.1, 0.1, 0.05, 0.05, 0.05, 0.01, 0.01, 0.01, 0.01, 0.01] """ + # 检查milestone和learning_rates的值类型是否正确 validator.check_value_type('milestone', milestone, (tuple, list)) validator.check_value_type('learning_rates', learning_rates, (tuple, list)) - if len(milestone) != len(learning_rates): + # 检查milestone和learning_rates的长度是否相同 + if len(milestone)!= len(learning_rates): raise ValueError("For 'piecewise_constant_lr', " - "the size of 'milestone' must be same with the size of 'learning_rates', " - "but got 'milestone' size: {}, 'learning_rates' size: {}." + "the size of'milestone' must be same with the size of 'learning_rates', " + "but got'milestone' size: {}, 'learning_rates' size: {}." .format(len(milestone), len(learning_rates))) + # 初始化lr lr = [] + # 设置last_item为0 last_item = 0 for i, item in enumerate(milestone): + # 检查milestone[i]是否为正整数 validator.check_positive_int(item, f'milestone[{i}]') + # 检查learning_rates[i]是否为浮点数 validator.check_is_float(learning_rates[i], f'learning_rates[{i}]') + # 如果milestone[i]小于last_item,抛出ValueError异常 if item < last_item: raise ValueError(f"For 'piecewise_constant_lr', " f"the value of milestone[{i}] must be greater than milestone[{i - 1}], " f"but got milestone[{i}]: {milestone[i]}, " f"milestone[{i - 1}]: {milestone[i - 1]}.") + # 将learning_rates[i]和milestone[i]拼接到lr中 lr += [learning_rates[i]] * (item - last_item) + # 记录last_item last_item = item return lr def _check_inputs(learning_rate, decay_rate, total_step, step_per_epoch, decay_epoch, is_stair): + '''检查输入参数是否合法''' validator.check_positive_int(total_step, 'total_step') - validator.check_positive_int(step_per_epoch, 'step_per_epoch') + # 检查total_step是否大于0 + validator.check_positive_int(step_per_epoch,'step_per_epoch') + # 检查step_per_epoch是否大于0 validator.check_positive_int(decay_epoch, 'decay_epoch') + # 检查decay_epoch是否大于0 validator.check_positive_float(learning_rate, 'learning_rate') + # 检查learning_rate是否大于0 validator.check_is_float(learning_rate, 'learning_rate') + # 检查learning_rate是否为浮点数 validator.check_positive_float(decay_rate, 'decay_rate') + # 检查decay_rate是否大于0 validator.check_is_float(decay_rate, 'decay_rate') + # 检查decay_rate是否为浮点数 validator.check_value_type('is_stair', is_stair, [bool]) + # 检查is_stair是否为bool类型 def exponential_decay_lr(learning_rate, decay_rate, total_step, step_per_epoch, decay_epoch, is_stair=False): + # 基于指数衰减函数计算学习率。 r""" Calculates learning rate base on exponential decay function. The learning rate for each step will be stored in a list. @@ -133,18 +155,27 @@ def exponential_decay_lr(learning_rate, decay_rate, total_step, step_per_epoch, >>> print(output) [0.1, 0.1, 0.09000000000000001, 0.09000000000000001, 0.08100000000000002, 0.08100000000000002] """ + # 检查输入参数 _check_inputs(learning_rate, decay_rate, total_step, step_per_epoch, decay_epoch, is_stair) + # 定义一个函数,用于计算学习率,参数分别为学习率,衰减率,总步数,步数每次衰减的结束步数,衰减的结束第几次,是否为偶数 + # 参数:learning_rate:学习率;decay_rate:衰减率;total_step:总步数;step_per_epoch:步数每次衰减的结束步数;decay_epoch:衰减的结束第几次;is_stair:是否为偶数 + # 返回:学习率列表 lr = [] + # 遍历总步数,计算学习率 for i in range(total_step): + # 如果is_stair为TRUE,则使用两次指数衰减函数计算学习率 if is_stair: lr.append(learning_rate * decay_rate ** math.floor(math.floor(i / step_per_epoch) / decay_epoch)) + # 否则,使用一次指数衰减函数计算学习率 else: lr.append(learning_rate * decay_rate ** (math.floor(i / step_per_epoch) / decay_epoch)) + # 返回学习率列表 return lr def natural_exp_decay_lr(learning_rate, decay_rate, total_step, step_per_epoch, decay_epoch, is_stair=False): + # 基于自然指数衰减函数计算学习率。 r""" Calculates learning rate base on natural exponential decay function. The learning rate for each step will be stored in a list. @@ -188,19 +219,26 @@ def natural_exp_decay_lr(learning_rate, decay_rate, total_step, step_per_epoch, >>> print(output) [0.1, 0.1, 0.1, 0.1, 0.016529888822158657, 0.016529888822158657] """ + # 检查输入参数,并返回学习率 _check_inputs(learning_rate, decay_rate, total_step, step_per_epoch, decay_epoch, is_stair) function = lambda x, y: x + # 如果为True,则学习率每 decay_epoch 次衰减一次。 if is_stair: + # 如果是,则使用指数衰减函数 function = lambda x, y: math.floor(x / y) * y + # 初始化学习率 lr = [] + # 循环计算学习率 for i in range(total_step): + # 使用自然指数衰减函数计算学习率 lr.append(learning_rate * math.e ** (-decay_rate * function(math.floor(i / step_per_epoch), decay_epoch))) + # 返回学习率 return lr - def inverse_decay_lr(learning_rate, decay_rate, total_step, step_per_epoch, decay_epoch, is_stair=False): + # 基于逆时衰减函数计算学习率。 r""" Calculates learning rate base on inverse-time decay function. The learning rate for each step will be stored in a list. @@ -246,16 +284,34 @@ def inverse_decay_lr(learning_rate, decay_rate, total_step, step_per_epoch, deca """ _check_inputs(learning_rate, decay_rate, total_step, step_per_epoch, decay_epoch, is_stair) +# 定义一个函数,用于计算学习率 +# learning_rate:学习率 +# decay_rate:衰减率 +# total_step:总步数 +# step_per_epoch:每个epoch的步数 +# decay_epoch:衰减的epoch +# is_stair:是否是偶数 +# 返回:学习率 +def _lr_calc(learning_rate, decay_rate, total_step, step_per_epoch, decay_epoch, is_stair): + # 检查输入参数 + _check_inputs(learning_rate, decay_rate, total_step, step_per_epoch, decay_epoch, is_stair) + + # 初始化学习率 lr = [] + # 循环计算学习率 for i in range(total_step): + # 如果为TRUE,则用逆时间衰减函数每 decay_epoch 次衰减一次。 if is_stair: lr.append(learning_rate / (1 + decay_rate * math.floor(math.floor(i / step_per_epoch) / decay_epoch))) + # 否则计算学习率 else: lr.append(learning_rate / (1 + decay_rate * math.floor(i / step_per_epoch) / decay_epoch)) + # 返回学习率 return lr def cosine_decay_lr(min_lr, max_lr, total_step, step_per_epoch, decay_epoch): + # 基于余弦衰减函数计算学习率。 r""" Calculates learning rate base on cosine decay function. The learning rate for each step will be stored in a list. @@ -300,27 +356,41 @@ def cosine_decay_lr(min_lr, max_lr, total_step, step_per_epoch, decay_epoch): [0.1, 0.1, 0.05500000000000001, 0.05500000000000001, 0.01, 0.01] """ if not isinstance(min_lr, float): - raise TypeError("For 'cosine_decay_lr', the argument 'min_lr' must be type of float, " - "but got 'min_lr' type: {}.".format(type(min_lr))) + raise TypeError("For 'cosine_decay_lr', the argument'min_lr' must be type of float, " + "but got'min_lr' type: {}.".format(type(min_lr))) + # 检查min_lr的类型是否为float validator.check_non_negative_float(min_lr, "min_lr", None) - validator.check_positive_float(max_lr, 'max_lr') - validator.check_is_float(max_lr, 'max_lr') + # 检查min_lr是否大于0 + validator.check_positive_float(max_lr,'max_lr') + # 检查max_lr是否大于min_lr + validator.check_is_float(max_lr,'max_lr') + # 检查total_step是否大于0 validator.check_positive_int(total_step, 'total_step') - validator.check_positive_int(step_per_epoch, 'step_per_epoch') + # 检查step_per_epoch是否大于0 + validator.check_positive_int(step_per_epoch,'step_per_epoch') + # 检查decay_epoch是否大于0 validator.check_positive_int(decay_epoch, 'decay_epoch') + # 检查min_lr是否小于max_lr if min_lr >= max_lr: - raise ValueError("For 'cosine_decay_lr', the 'max_lr' should be greater than the 'min_lr', " - "but got 'max_lr' value: {}, 'min_lr' value: {}.".format(max_lr, min_lr)) + raise ValueError("For 'cosine_decay_lr', the'max_lr' should be greater than the'min_lr', " + "but got'max_lr' value: {},'min_lr' value: {}.".format(max_lr, min_lr)) + # 定义一个delta值,用于计算cosine的角度 delta = 0.5 * (max_lr - min_lr) + # 定义一个lr列表,用于存放cosine的角度 lr = [] + # 遍历total_step for i in range(total_step): + # 计算tmp_epoch tmp_epoch = min(math.floor(i / step_per_epoch), decay_epoch) + # 将cosine的角度添加到lr列表中 lr.append(min_lr + delta * (1 + math.cos(math.pi * tmp_epoch / decay_epoch))) + # 返回lr列表 return lr def polynomial_decay_lr(learning_rate, end_learning_rate, total_step, step_per_epoch, decay_epoch, power, update_decay_epoch=False): + # 基于多项式衰减函数计算学习率。 r""" Calculates learning rate base on polynomial decay function. The learning rate for each step will be stored in a list. @@ -382,33 +452,48 @@ def polynomial_decay_lr(learning_rate, end_learning_rate, total_step, step_per_e [0.1, 0.1, 0.07363961030678928, 0.07363961030678928, 0.01, 0.01] """ validator.check_positive_float(learning_rate, 'learning_rate') + # 检查learning_rate是否为正数 validator.check_is_float(learning_rate, 'learning_rate') + # 检查end_learning_rate的取值是否是浮点数 if not isinstance(end_learning_rate, float): raise TypeError("For 'polynomial_decay_lr', the argument 'end_learning_rate' must be type of float, " "but got 'end_learning_rate' type: {}.".format(type(end_learning_rate))) validator.check_non_negative_float(end_learning_rate, "end_learning_rate", None) + # 检查end_learning_rate的取值是否大于0 validator.check_positive_float(power, 'power') + # 检查power的取值是否大于0 validator.check_is_float(power, 'power') + # 检查power的取值是否为float validator.check_positive_int(total_step, 'total_step') - validator.check_positive_int(step_per_epoch, 'step_per_epoch') + # 检查total_step的取值是否大于0 + validator.check_positive_int(step_per_epoch,'step_per_epoch') + # 检查step_per_epoch的取值是否大于0 validator.check_positive_int(decay_epoch, 'decay_epoch') + # 检查decay_epoch的取值是否大于0 validator.check_value_type('update_decay_epoch', update_decay_epoch, [bool]) origin_decay_epoch = decay_epoch + # 初始化函数 function = lambda x, y: (x, min(x, y)) + # 如果更新梯度衰减周期 if update_decay_epoch: + # 用多项式衰减函数计算值 function = lambda x, y: (origin_decay_epoch * max(math.ceil(y / origin_decay_epoch), 1), y) lr = [] + # 计算函数的值 delta = learning_rate - end_learning_rate for i in range(total_step): current_epoch = math.floor(i / step_per_epoch) + # 计算学习路周期 decay_epoch, tmp_epoch = function(decay_epoch, current_epoch) + # 计算学习率 lr.append(delta * (1 - tmp_epoch / decay_epoch) ** power + end_learning_rate) return lr def warmup_lr(learning_rate, total_step, step_per_epoch, warmup_epoch): + # 预热学习率方法。 r""" Gets learning rate warming up. The learning rate for each step will be stored in a list. @@ -447,21 +532,31 @@ def warmup_lr(learning_rate, total_step, step_per_epoch, warmup_epoch): >>> print(output) [0.0, 0.0, 0.05, 0.05, 0.1, 0.1] """ + # 检查learning_rate是否为浮点数 if not isinstance(learning_rate, float): raise TypeError("For 'warmup_lr', the argument 'learning_rate' must be type of float, " - "but got 'learning_rate' type: {}.".format(type(learning_rate))) validator.check_non_negative_float(learning_rate, "learning_rate", None) + # 检查learning_rate是否为正数 validator.check_positive_int(warmup_epoch, 'warmup_epoch') + # 检查warmup_epoch是否为正数 validator.check_positive_int(total_step, 'total_step') - validator.check_positive_int(step_per_epoch, 'step_per_epoch') + # 检查total_step是否为正数 + validator.check_positive_int(step_per_epoch,'step_per_epoch') + # 定义一个预热学习率函数,用于计算函数的值 function = lambda x, y: (x, min(x, y)) + # 定义一个空列表,用于存放计算函数的值 lr = [] + # 遍历total_step次,每次计算函数的值 for i in range(total_step): + # 计算当前epoch current_epoch = math.floor(i / step_per_epoch) + # 计算warmup_epoch和当前epoch的最小值 warmup_epoch, tmp_epoch = function(warmup_epoch, current_epoch) + # 用函数计算学习率的值 lr.append(learning_rate * tmp_epoch / warmup_epoch) + # 返回计算后学习率的值 return lr diff --git a/mindspore/python/mindspore/nn/grad/__init__.py b/mindspore/python/mindspore/nn/grad/__init__.py index 218e1461f02..7c6ae5bf0bd 100644 --- a/mindspore/python/mindspore/nn/grad/__init__.py +++ b/mindspore/python/mindspore/nn/grad/__init__.py @@ -17,7 +17,7 @@ Grad Cells of grad function. Calculate the gradient of input network or function. """ - +# 本文件为神经网络梯度的雅各比矩阵向量乘积计算操作的构建 from .cell_grad import Jvp, Vjp diff --git a/mindspore/python/mindspore/nn/grad/cell_grad.py b/mindspore/python/mindspore/nn/grad/cell_grad.py index 54b10059edc..803ac07fc49 100644 --- a/mindspore/python/mindspore/nn/grad/cell_grad.py +++ b/mindspore/python/mindspore/nn/grad/cell_grad.py @@ -13,54 +13,80 @@ # limitations under the License. # ============================================================================ """cell grad""" -from ..cell import Cell -from ...ops import composite as C -from ...ops import operations as P -from ...ops.primitive import Primitive -from ...common import dtype as mstype -from ...common.api import ms_function +# Cell神经网络基本单元的梯度计算 +from..cell import Cell +from...ops import composite as C +from...ops import operations as P +from...ops.primitive import Primitive +from...common import dtype as mstype +from...common.api import ms_function class _FirstGrad(Cell): + # 初始化梯度类 def __init__(self, fn): + # 初始化类 super(_FirstGrad, self).__init__() + # 初始化first_grad_op变量 self.first_grad_op = C.GradOperation(sens_param=True, get_all=True) + # 将fn变量赋值给self.fn self.fn = fn + # 构建梯度 def construct(self, u, first_grad_input): + # 返回first_grad_op函数的输出 return self.first_grad_op(self.fn)(*first_grad_input, u) class _JvpFirstGrad(Cell): + # 初始化雅可比矩阵与向量乘积的计算类 def __init__(self): super(_JvpFirstGrad, self).__init__() + # 定义一个变量,用来存储梯度 self.first_grad_op = C.GradOperation(sens_param=True, get_all=True) + # 构建梯度 def construct(self, u, fn, first_grad_input): + # 返回梯度 return self.first_grad_op(fn)(*first_grad_input, u) class _FirstGradSingleValue(Cell): + ''' + 计算第一次梯度单元 + ''' def __init__(self, fn): + ''' + 初始化FirstGradSingleValue类 + :param fn: 函数 + :return: None + ''' super(_FirstGradSingleValue, self).__init__() self.first_grad_single_value_op = C.GradOperation(sens_param=True) self.fn = fn def construct(self, u, first_grad_single_value_input): + ''' + 计算第一次梯度单元 + ''' return self.first_grad_single_value_op(self.fn)(*first_grad_single_value_input, u) - class _JvpFirstGradSingleValue(Cell): + # 初始化雅可比矩阵与向量乘积的第一次梯度单值操作计算类 def __init__(self): super(_JvpFirstGradSingleValue, self).__init__() + # 初始化第一次梯度单值操作 self.first_grad_single_value_op = C.GradOperation(sens_param=True) + # 构建梯度 def construct(self, u, fn, first_grad_single_value_input): + # 返回第一次梯度单值操作的梯度 return self.first_grad_single_value_op(fn)(*first_grad_single_value_input, u) class Jvp(Cell): + # 实现雅可比矩阵与向量乘积的操作类 """ Compute the jacobian-vector-product of the given fn. Jvp is equivalent to forward mode autodiff. @@ -99,81 +125,125 @@ class Jvp(Cell): """ def __init__(self, fn): super(Jvp, self).__init__() + # 初始化函数 self.fn = fn + # 初始化OnesLike函数 self.oneslike = P.OnesLike() + # 初始化FirstGrad函数 self.first_grad = _FirstGrad(fn) + # 添加flags self.first_grad.add_flags(enable_tuple_grad=True) self.first_grad_single_value = _FirstGradSingleValue(fn) self.first_grad_single_value.add_flags(enable_tuple_grad=True) + # 初始化C.GradOperation函数 self.second_grad_op = C.GradOperation(sens_param=True) + # 初始化IsSubClass函数 self.issubclass_ = P.IsSubClass() + # 初始化Primitive函数 self.typeof = Primitive('typeof') + # 初始化MakeTuple函数 self.make_tuple = Primitive('MakeTuple') + # 初始化tuple_len函数 self.tuple_len = Primitive("tuple_len") @ms_function def construct(self, *args): + # 获取输入参数 jvp_input = args[0:-1] + # 获取输出参数 v = args[-1] + # 调用函数 output = self.fn(*jvp_input) + # 判断输出类型 if self.issubclass_(self.typeof(output), mstype.tuple_): + # 初始化u u = self.make_tuple() + # 遍历输出参数 for i in range(self.tuple_len(output)): + # 将OnesLike函数赋值给u u = u + self.make_tuple(self.oneslike(output[i])) else: + # 将OnesLike函数赋值给u u = self.oneslike(output) + # 判断输入参数的长度 if self.tuple_len(jvp_input) == 1: + # 初始化second_gradient_net second_gradient_net = self.second_grad_op(self.first_grad_single_value) + # 调用second_gradient_net函数 gradient_output = second_gradient_net(u, jvp_input, v) else: + # 初始化second_gradient_net second_gradient_net = self.second_grad_op(self.first_grad) + # 调用second_gradient_net函数 gradient_output = second_gradient_net(u, jvp_input, v) + # 返回输出参数和梯度输出参数 return output, gradient_output class _JvpInner(Cell): + # 实现雅可比矩阵与向量乘积的内部过程类 """ Compute the jacobian-vector-product of the given network. Jvp is equivalent to forward mode autodiff. This class implements the inner process of function jvp. """ def __init__(self): super(_JvpInner, self).__init__() + # 初始化一个OnesLike函数 self.oneslike = P.OnesLike() + # 初始化一个_JvpFirstGrad函数 self.first_grad = _JvpFirstGrad() + # 添加_JvpFirstGrad函数的标志位enable_tuple_grad=True self.first_grad.add_flags(enable_tuple_grad=True) + # 初始化一个_JvpFirstGradSingleValue函数 self.first_grad_single_value = _JvpFirstGradSingleValue() + # 添加_JvpFirstGradSingleValue函数的标志位enable_tuple_grad=True self.first_grad_single_value.add_flags(enable_tuple_grad=True) + # 初始化一个C.GradOperation函数的参数sens_param=True self.second_grad_op = C.GradOperation(sens_param=True) + # 初始化一个P.IsSubClass函数 self.issubclass_ = P.IsSubClass() + # 初始化一个Primitive函数 self.typeof = Primitive('typeof') + # 初始化一个Primitive函数 self.make_tuple = Primitive('MakeTuple') + # 初始化一个Primitive函数 self.tuple_len = Primitive("tuple_len") + # 定义一个函数,用于构建JVP def construct(self, *args): + # 获取参数 fn = args[0] v = args[1] jvp_input = args[2:] + # 调用函数 output = fn(*jvp_input) + # 如果输出是一个元组,则调用_JvpFirstGradSingleValue函数 if self.issubclass_(self.typeof(output), mstype.tuple_): u = self.make_tuple() for i in range(self.tuple_len(output)): + # 将output[i]的值设置为oneslike函数的返回值 u = u + self.make_tuple(self.oneslike(output[i])) + # 否则调用_JvpFirstGrad函数 else: u = self.oneslike(output) + # 如果输入是一个元组,则调用_JvpFirstGrad函数 if self.tuple_len(jvp_input) == 1: second_gradient_net = self.second_grad_op(self.first_grad_single_value) gradient_output = second_gradient_net(u, fn, jvp_input, v) + # 否则调用_JvpFirstGrad函数 else: second_gradient_net = self.second_grad_op(self.first_grad) gradient_output = second_gradient_net(u, fn, jvp_input, v) + # 返回输出和梯度输出 return output, gradient_output class Vjp(Cell): + # 计算向量`v`与给定fn的雅可比矩阵之间的点积。 """ Computes the dot product between a vector `v` and the Jacobian of the given fn at the point given by the inputs. @@ -216,26 +286,39 @@ class Vjp(Cell): """ def __init__(self, fn): + # 初始化Vjp类 super(Vjp, self).__init__() + # 将fn赋值给self.fn self.fn = fn + # 创建一个GradOperation对象,获取所有参数和sens_param self.grad = C.GradOperation(get_all=True, sens_param=True) + # 创建一个GradOperation对象,获取sens_param self.grad_single_value = C.GradOperation(sens_param=True) + # 创建一个IsSubClass对象 self.issubclass_ = P.IsSubClass() + # 创建一个typeof和tuple_len Primitive self.typeof = Primitive('typeof') self.tuple_len = Primitive("tuple_len") + # 定义构造函数 @ms_function def construct(self, *args): + # 获取前一个参数 front_input = args[0:-1] + # 调用fn函数 output = self.fn(*front_input) + # 如果前一个参数为tuple,则调用grad_single_value函数 if self.tuple_len(front_input) == 1: gradient_output = self.grad_single_value(self.fn)(*args) + # 否则调用grad函数 else: gradient_output = self.grad(self.fn)(*args) + # 返回输出和梯度输出 return output, gradient_output class _VjpInner(Cell): + # 计算向量`v`与给定网络在输入给出的点处的雅可比矩阵之间的点积(内部过程) """ Computes the dot product between a vector `v` and the Jacobian of the given network at the point given by the inputs. This class implements the inner process of function vjp. @@ -243,17 +326,27 @@ class _VjpInner(Cell): def __init__(self): super(_VjpInner, self).__init__() + # 创建一个梯度操作,获取每个输入的参数的梯度 self.grad = C.GradOperation(get_all=True, sens_param=True) + # 创建一个梯度操作,获取单个输入的梯度 self.grad_single_value = C.GradOperation(sens_param=True) + # 创建一个操作,获取输入的tuple长度 self.tuple_len = Primitive("tuple_len") def construct(self, *args): + # 获取输入的函数 fn = args[0] + # 获取输入的前一个输入 front_input = args[1:-1] + # 获取输入的元组 input_with_v = args[1:] + # 计算输出和梯度 output = fn(*front_input) if self.tuple_len(front_input) == 1: + # 若输入的tuple长度为1,则计算单个输入的梯度 gradient_output = self.grad_single_value(fn)(*input_with_v) else: + # 若输入的tuple长度不为1,则计算多个输入的梯度 gradient_output = self.grad(fn)(*input_with_v) + # 返回输出和梯度 return output, gradient_output diff --git a/mindspore/python/mindspore/nn/layer/__init__.py b/mindspore/python/mindspore/nn/layer/__init__.py index 5a0836fe035..1dba26587a7 100644 --- a/mindspore/python/mindspore/nn/layer/__init__.py +++ b/mindspore/python/mindspore/nn/layer/__init__.py @@ -11,7 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# ============================================================================ +# ============================================================================】 +# 用于初始化layer """ Layer. @@ -19,22 +20,39 @@ The high-level components(Cells) used to construct the neural network. """ from . import activation, normalization, container, conv, basic, embedding, pooling, image, quant, math, \ combined, timedistributed, thor_layer, rnns, rnn_cells +# 包含激活函数(如ReLU、sigmoid等) from .activation import * +# 包含数据归一化方法(如Batch Normalization、Layer Normalization等) from .normalization import * +# 包含神经网络层容器(如Sequential、TimeDistributed等) from .container import * +# 包含卷积层(如Conv1D、Conv2D等) from .conv import * +# 包含所有RNN层(如SimpleRNN、GRU、LSTM等) from .rnns import * +# 包含RNN单元(如SimpleRNNCell、GRUCell、LSTMCell等) from .rnn_cells import * +# 包含基本层(如Dense、Flatten等) from .basic import * +# 包含嵌入层(将文本数据转换为向量表示) from .embedding import * +# 包含池化层(如MaxPooling1D、MaxPooling2D等) from .pooling import * +# 包含图像层(如ResizeMethod、RandomCrop等) from .image import * +# 包含量化层(将浮点数转换为定点数) from .quant import * +# 包含数学运算层(如Add、Multiply等) from .math import * +# 包含组合层(将多个层组合为一个层) from .combined import * +# 包含时间序列层(如Bidirectional、TimeDistributed等) from .timedistributed import * +# 包含Thor层(用于Thor框架) from .thor_layer import DenseThor, Conv2dThor, EmbeddingThor, EmbeddingLookupThor +# 定义一个名为__all__的列表,并将多个模块中的类添加到该列表中 +# 下面extend中和上面的import中包含的基本一致 __all__ = [] __all__.extend(activation.__all__) __all__.extend(normalization.__all__) diff --git a/mindspore/python/mindspore/nn/layer/activation.py b/mindspore/python/mindspore/nn/layer/activation.py index 14cb3a766f7..1501b0f3ea5 100644 --- a/mindspore/python/mindspore/nn/layer/activation.py +++ b/mindspore/python/mindspore/nn/layer/activation.py @@ -14,13 +14,17 @@ # ============================================================================ """activation""" import numpy as np - +# 从mindspore._checkparam库中导入Validator类,用于参数验证 from mindspore._checkparam import Validator as validator +# 从mindspore._extends库中导入cell_attr_register函数,用于注册Cell类的属性 from mindspore._extends import cell_attr_register +# 从mindspore.common库中导入dtype和Parameter类,用于处理数据类型和参数 from mindspore.common import dtype as mstype +# 从mindspore.common.tensor库中导入Tensor类,用于处理张量数据 from mindspore.common.parameter import Parameter from mindspore.common.tensor import Tensor from mindspore.ops import functional as F +# 从mindspore.ops库中导入functional和operations模块,用于定义一些基本的操作 from mindspore.ops import operations as P from ..cell import Cell @@ -44,7 +48,8 @@ __all__ = ['Softmax', 'CELU', ] - +#解决神经网络中梯度消失问题的激活函数 +#优点是其没有梯度消失且附近所有平均激活为零,可以帮助我们加速并以其他方法改善学习 class CELU(Cell): r""" Continuously differentiable exponential linear units activation function. @@ -89,12 +94,14 @@ class CELU(Cell): def __init__(self, alpha=1.0): """Initialize CELU.""" super(CELU, self).__init__() + #初始化CELU类 self.celu = P.CeLU(alpha=alpha) def construct(self, x): + #定义构造函数,用于构造神经网络 return self.celu(x) - +#输出值转化为概率分布 class Softmax(Cell): r""" Softmax activation function. It is a two-category function :class:`mindspore.nn.Sigmoid` in the promotion of @@ -142,12 +149,15 @@ class Softmax(Cell): def __init__(self, axis=-1): """Initialize Softmax.""" super(Softmax, self).__init__() + #初始化Softmax类 self.softmax = P.Softmax(axis) def construct(self, x): + #返回x return self.softmax(x) - +#计算log的Softmax函数 +#计算概率分布的对数概率的归一化操作,其常用于计算神经网络的输出概率,确保概率和为1,有助于计算损失函数并且优化网络参数 class LogSoftmax(Cell): r""" LogSoftmax activation function. @@ -193,12 +203,15 @@ class LogSoftmax(Cell): def __init__(self, axis=-1): """Initialize LogSoftmax.""" super(LogSoftmax, self).__init__() + #初始化LogSoftmax激活函数 self.log_softmax = P.LogSoftmax(axis) def construct(self, x): + #返回x return self.log_softmax(x) - +#与前面的CELU函数类似 +#用于替代传统的ReLU函数,在负数区域有着很好的表现,有利于帮助我们处理神经网络中的负值 class ELU(Cell): r""" Exponential Linear Unit activation function. @@ -246,12 +259,14 @@ class ELU(Cell): def __init__(self, alpha=1.0): """Initialize ELU.""" super(ELU, self).__init__() + #初始化ELU激活函数 self.elu = P.Elu(alpha) def construct(self, x): + #返回x值 return self.elu(x) - +#一种用于替代传统sigmoid函数和tanh函数的激活函数,计算速度快但是收负数区限制 class ReLU(Cell): r""" Rectified Linear Unit activation function. @@ -291,12 +306,14 @@ class ReLU(Cell): def __init__(self): """Initialize ReLU.""" super(ReLU, self).__init__() + #初始化ReLU激活函数 self.relu = P.ReLU() def construct(self, x): + #返回x return self.relu(x) - +#为前面ReLU函数的变体 class ReLU6(Cell): r""" Compute ReLU6 activation function. @@ -335,12 +352,14 @@ class ReLU6(Cell): def __init__(self): """Initialize ReLU6.""" super(ReLU6, self).__init__() + #初始化函数 self.relu6 = P.ReLU6() def construct(self, x): + #返回x return self.relu6(x) - +#LeakyReLU函数不同于传统的ReLU函数,其优点是在负数区域也有着很好的表现,有利于处理负值 class LeakyReLU(Cell): r""" Leaky ReLU activation function. @@ -382,17 +401,24 @@ class LeakyReLU(Cell): def __init__(self, alpha=0.2): """Initialize LeakyReLU.""" super(LeakyReLU, self).__init__() + #检查alpha的类型是否为float或int validator.check_value_type('alpha', alpha, [float, int], self.cls_name) + #初始化greater_equal和mul self.greater_equal = P.GreaterEqual() self.mul = P.Mul() + #将alpha赋值给self.alpha self.alpha = alpha + #初始化select_op self.select_op = P.Maximum() + #如果alpha大于1,则将select_op赋值为Minimum if self.alpha > 1: self.select_op = P.Minimum() def construct(self, x): alpha_array = P.Cast()(F.scalar_to_array(self.alpha), P.DType()(x)) + #将alpha_array转换为x的数据类型 out = self.select_op(alpha_array * x, x) + #返回select_op函数的输出 return out @@ -433,12 +459,15 @@ class Tanh(Cell): def __init__(self): """Initialize Tanh.""" super(Tanh, self).__init__() + # 初始化Tanh函数 self.tanh = P.Tanh() def construct(self, x): + #返回x return self.tanh(x) - +#GELU是一种可以一定程度上代替传统ReLU函数的函数 +#在负数区域有很好表现,计算速度也更快 class GELU(Cell): r""" Gaussian error linear unit activation function. @@ -499,10 +528,14 @@ class GELU(Cell): def __init__(self, approximate=True): """Initialize GELU.""" super(GELU, self).__init__() + #检查approximate参数 validator.check_bool(approximate, 'approximate', self.cls_name) + #将approximate参数设置为True self.approximate = approximate + #如果approximate参数为True,则将gelu设置为P.GeLU() if self.approximate: self.gelu = P.GeLU() + #否则,将erf设置为P.Erf(),并将sqrt设置为P.Sqrt(),并将const0设置为Tensor(0.5, mstype.float32),const1设置为Tensor(1.0, mstype.float32),const2设置为Tensor(2.0, mstype.float32) else: self.erf = P.Erf() self.sqrt = P.Sqrt() @@ -512,11 +545,13 @@ class GELU(Cell): def construct(self, x): if self.approximate: + #如果approximate为True,则返回gelu函数的输出 return self.gelu(x) + #否则,返回x的const0和const1的求和结果,并将const2的值转换为x的dtype return x * F.cast(self.const0, x.dtype) * (F.cast(self.const1, x.dtype) + \ self.erf(x / self.sqrt(F.cast(self.const2, x.dtype)))) - +#为GELU活函数的变体 class FastGelu(Cell): r""" Fast Gaussian error linear unit activation function. @@ -559,12 +594,14 @@ class FastGelu(Cell): def __init__(self): """Initialize FastGelu.""" super(FastGelu, self).__init__() + #初始化FastGelu类 self.fast_gelu = P.FastGeLU() def construct(self, x): return self.fast_gelu(x) - +#Sigmoid 激活函数用于将神经网络的输出映射到0到1之间的实数 +#0到1之间的实数有利于进行概率估计 class Sigmoid(Cell): r""" Sigmoid activation function. @@ -605,12 +642,13 @@ class Sigmoid(Cell): def __init__(self): """Initialize Sigmoid.""" super(Sigmoid, self).__init__() + # 初始化Sigmoid类 self.sigmoid = P.Sigmoid() def construct(self, x): return self.sigmoid(x) - +#增强版的ReLU函数,在负数区域有着更好的表现,计算速度更快 class PReLU(Cell): r""" PReLU activation function. @@ -668,45 +706,60 @@ class PReLU(Cell): def __init__(self, channel=1, w=0.25): """Initialize PReLU.""" super(PReLU, self).__init__() + #检查channel是否为正整数 validator.check_positive_int(channel, 'channel', self.cls_name) + #如果w是一个float类型,则创建一个新的float32类型的数组 if isinstance(w, (float, np.float32)): tmp = np.empty((channel,), dtype=np.float32) + #将w的值赋值给tmp tmp.fill(w) + #将tmp转换为Tensor类型 w = Tensor(tmp, dtype=mstype.float32) + #如果w是一个list类型,则检查list的长度是否等于channel elif isinstance(w, list): - if len(w) != channel: + if len(w)!= channel: + #如果长度不等于channel,则抛出ValueError异常 raise ValueError(f"For '{self.cls_name}', the length of 'w' should be equal to the 'channel' when " f"the 'w' is a list, but got the length of 'w': {len(w)}, the 'channel': {channel}.") - for i in w: + #如果w不是float类型,抛出异常 if not isinstance(i, (float, np.float32)): raise ValueError(f"For '{self.cls_name}', all elements in 'w' should be " f"float when the 'w' is a list, but got {i}.") + #将w转换为float32类型 w = Tensor(w, dtype=mstype.float32) elif isinstance(w, Tensor): + #如果w是Tensor类型,且dtype不是float16或float32,抛出异常 if w.dtype not in (mstype.float16, mstype.float32): raise ValueError(f"For '{self.cls_name}', the dtype of 'w' should be float16 or " f"float32 when the 'w' is a tensor, but got {w.dtype}.") - if len(w.shape) != 1 or w.shape[0] != channel: + #如果w的维度不是1,且w的元素个数不等于channel,抛出异常 + if len(w.shape)!= 1 or w.shape[0]!= channel: raise ValueError(f"For '{self.cls_name}', the dimension of 'w' should be 1, and the elements number " f"should be equal to the 'channel' when the 'w' is a tensor, " f"but got 'w' shape {w.shape}, the 'channel' {channel}.") else: + #如果w不是float,list或tensor,抛出异常 raise TypeError(f"For '{self.cls_name}', the 'w' only supported float, list and tensor, " f"but got {type(w).__name__}.") self.w = Parameter(w, name='a') + #创建一个PReLU激活函数 self.prelu = P.PReLU() + #创建一个ReLU激活函数 self.relu = P.ReLU() + #创建一个Assign操作 self.assign = P.Assign() def construct(self, x): u = self.relu(self.w) v = self.prelu(x, F.cast(u, x.dtype)) + #如果训练,将w赋值给u if self.training: self.assign(self.w, u) + #返回v return v - +#一种可以替代ReUL的新激活函数,可以在 class HSwish(Cell): r""" Hard swish activation function. @@ -744,9 +797,11 @@ class HSwish(Cell): def __init__(self): """Initialize HSwish.""" super(HSwish, self).__init__() + #初始化HSwish函数 self.hswish = P.HSwish() def construct(self, x): + #返回x值 return self.hswish(x) @@ -784,12 +839,14 @@ class HSigmoid(Cell): def __init__(self): """Initialize HSigmoid.""" super(HSigmoid, self).__init__() + #初始化HSigmoid类 self.hsigmoid = P.HSigmoid() - def construct(self, input_x): + #返回input_x return self.hsigmoid(input_x) - +#可以用于替代传统Sigmoid函数 +#输出范围是[-inf, 0],有助于提高神经网络的响应能力 class LogSigmoid(Cell): r""" Logsigmoid activation function. @@ -827,21 +884,29 @@ class LogSigmoid(Cell): def __init__(self): """Initialize LogSigmoid.""" super(LogSigmoid, self).__init__() + #初始化mul和exp self.mul = P.Mul() self.exp = P.Exp() + #初始化add和rec和log self.add = P.Add() self.rec = P.Reciprocal() self.log = P.Log() def construct(self, input_x): neg_input = self.mul(input_x, -1) + #计算负号的指数 exp_neg_input = self.exp(neg_input) + #计算负号的指数的平方 exp_neg_input_1 = self.add(exp_neg_input, 1) + #计算负号的指数的平方的1+ rec_exp_neg_input_1 = self.rec(exp_neg_input_1) + #计算负号的指数的平方的1+的逆 ret = self.log(rec_exp_neg_input_1) + #返回负号的指数的平方的1+的逆 return ret - +#可以用于替代ReLU函数 +#输出范围是 [-alpha, alpha],有助于提高神经网络的响应能力 class SoftShrink(Cell): r""" Applies the SoftShrink function element-wise. @@ -884,13 +949,16 @@ class SoftShrink(Cell): def __init__(self, lambd=0.5): super(SoftShrink, self).__init__() + #初始化softshrink函数 self.softshrink = P.SoftShrink(lambd) def construct(self, input_x): output = self.softshrink(input_x) + #返回output return output - +#可以用于替代ReLU函数 +#输出范围是 [-alpha, alpha],有助于提高神经网络的响应能力 class HShrink(Cell): r""" Hard Shrink activation function. Calculates the output according to the input elements. @@ -935,6 +1003,7 @@ class HShrink(Cell): def __init__(self, lambd=0.5): super(HShrink, self).__init__() + # 初始化HShrink类的实例 self.hshrink = P.HShrink(lambd) def construct(self, input_x): @@ -981,9 +1050,12 @@ def get_activation(name, prim_name=None): Sigmoid<> """ msg_prefix = f"For '{prim_name}', the" if prim_name else "The" + #如果name为None,则返回None if name is None: return None + #如果name不在_activation中,则抛出KeyError异常 if name not in _activation: raise KeyError(f"{msg_prefix} 'name' should be in {list(_activation.keys())}, but got {name}.") + #返回_activation中name对应的函数 return _activation[name]() diff --git a/mindspore/python/mindspore/nn/layer/basic.py b/mindspore/python/mindspore/nn/layer/basic.py index b57f2e084d2..449e31a5d24 100644 --- a/mindspore/python/mindspore/nn/layer/basic.py +++ b/mindspore/python/mindspore/nn/layer/basic.py @@ -14,22 +14,37 @@ # ============================================================================ """basic""" +# 导入Python的math库,提供了一些数学函数,如 sin、cos、sqrt 等 import math +# 导入NumPy库 import numpy as np +# 从mindspore.common.dtype模块中导入DType类,用于表示数据类型 import mindspore.common.dtype as mstype +# 从mindspore.ops.composite.multitype_ops模块中导入_constexpr_utils模块,用于提供一些常量计算工具 from mindspore.ops.composite.multitype_ops import _constexpr_utils as const_utils +# 从mindspore.common.seed模块中导入_get_graph_seed函数,用于获取图的随机种子 from mindspore.common.seed import _get_graph_seed +# 从mindspore.common.tensor模块中导入Tensor类,用于表示张量(多维数组) from mindspore.common.tensor import Tensor +# 从mindspore.common.initializer模块中导入initializer函数,用于创建初始化器(用于初始化张量的值) from mindspore.common.initializer import initializer +# 从mindspore.ops模块中导入operations子模块,用于提供一些基本的操作(如矩阵乘法、加法等) from mindspore.ops import operations as P +# 从mindspore.ops模块中导入functional子模块,用于提供一些基本的函数(如relu、sigmoid等) from mindspore.ops import functional as F +# 从mindspore.ops.functional模块中导入identity函数,用于返回输入的副本 from mindspore.ops.functional import identity +# 从mindspore.ops.operations模块中导入_inner_ops子模块,用于提供一些内部操作(如逐元素操作等) from mindspore.ops.operations import _inner_ops as inner +# 从mindspore.ops.primitive模块中导入constexpr和Primitive类,用于定义常量表达式和基本操作 from mindspore.ops.primitive import constexpr, Primitive from mindspore.common.parameter import Parameter +# 从mindspore._extends模块中导入cell_attr_register函数,用于注册Cell类的属性 from mindspore._extends import cell_attr_register from mindspore._checkparam import Rel, Validator +# 从当前模块(..cell)中导入Cell类,用于创建自定义Cell from ..cell import Cell +# 从当前模块(.activation)中导入get_activation函数,用于获取激活函数 from .activation import get_activation __all__ = ['Dropout', 'Flatten', 'Dense', 'ClipByNorm', 'Norm', 'OneHot', 'Pad', 'Unfold', 'Tril', 'Triu', @@ -81,18 +96,26 @@ class L1Regularizer(Cell): def __init__(self, scale): """Initialize L1Regularizer.""" super(L1Regularizer, self).__init__() + #检查scale的类型是否为整数或浮点数 Validator.check_value_type("scale", scale, [int, float], self.cls_name) + #如果scale小于等于0,抛出异常 if scale <= 0: - raise ValueError(f"For '{self.cls_name}', the 'scale' should be greater than 0, but got {scale}.") + raise ValueError(f"For '{self.cls_name}', the'scale' should be greater than 0, but got {scale}.") + #如果scale是INF或NAN,抛出异常 if math.isinf(scale) or math.isnan(scale): - raise ValueError(f"For '{self.cls_name}', the 'scale' can not be INF or NAN, but got {scale}.") + raise ValueError(f"For '{self.cls_name}', the'scale' can not be INF or NAN, but got {scale}.") + #初始化绝对值 self.abs = P.Abs() + #初始化平方和 self.reduce_sum = P.ReduceSum() + #将scale转换为Tensor类型 self.scale = Tensor(scale, dtype=mstype.float32) def construct(self, weights): const_utils.check_type_valid(F.dtype(weights), mstype.number_type, 'weights') + #计算weights的绝对值 l1_regularization = self.scale * self.reduce_sum(self.abs(weights)) + #返回l1_regularization return l1_regularization @@ -150,28 +173,40 @@ class Dropout(Cell): def __init__(self, keep_prob=0.5, dtype=mstype.float32): """Initialize Dropout.""" super(Dropout, self).__init__() + #检查keep_prob的类型 Validator.check_value_type('keep_prob', keep_prob, [float], self.cls_name) + #检查keep_prob的值在0和1之间 if keep_prob <= 0 or keep_prob > 1: raise ValueError(f"For '{self.cls_name}', the 'keep_prob' should be a number in range (0, 1], " f"but got {keep_prob}.") + #检查dtype的类型 Validator.check_subclass("dtype", dtype, mstype.number_type, self.cls_name) + #将keep_prob赋值给self.keep_prob self.keep_prob = keep_prob + #获取图的seed0和seed1 seed0, seed1 = _get_graph_seed(0, "dropout") + #将seed0赋值给self.seed0 self.seed0 = seed0 + #将seed1赋值给self.seed1 self.seed1 = seed1 + #将Dropout函数赋值给self.dropout self.dropout = P.Dropout(keep_prob, seed0, seed1) def construct(self, x): + #如果训练模式为False,则直接返回x if not self.training: return x + #如果keep_prob为1,则直接返回x if self.keep_prob == 1: return x + #否则,使用dropout操作,并返回结果 out, _ = self.dropout(x) return out def extend_repr(self): + #返回keep_prob的字符串表示 return 'keep_prob={}'.format(self.keep_prob) @@ -213,17 +248,20 @@ class Flatten(Cell): super(Flatten, self).__init__() def construct(self, x): + #将x的形状转换为(batch_size, -1) return F.reshape(x, (F.shape(x)[0], -1)) - @constexpr def check_dense_input_shape(x, prim_name=None): + #检查输入是否为稠密的形状 msg_prefix = f"For '{prim_name}', the" if prim_name else "The" if len(x) < 2: + #如果x的维度小于2,抛出ValueError异常 raise ValueError(f"{msg_prefix} dimension of 'x' should not be less than 2, but got {len(x)}.") class Dense(Cell): + #全链接层 r""" The dense connected layer. @@ -285,87 +323,122 @@ class Dense(Cell): activation=None): """Initialize Dense.""" super(Dense, self).__init__() + #获取输入通道数 self.in_channels = Validator.check_positive_int(in_channels, "in_channels", self.cls_name) + #获取输出通道数 self.out_channels = Validator.check_positive_int(out_channels, "out_channels", self.cls_name) + #是否有偏差 self.has_bias = Validator.check_bool(has_bias, "has_bias", self.cls_name) + #初始化reshape self.reshape = P.Reshape() + #获取shape self.shape_op = P.Shape() if isinstance(weight_init, Tensor): - if weight_init.ndim != 2 or weight_init.shape[0] != out_channels or \ - weight_init.shape[1] != in_channels: + #判断weight_init的维度是否正确,且第一个维度和第二个维度是否相等 + if weight_init.ndim!= 2 or weight_init.shape[0]!= out_channels or \ + weight_init.shape[1]!= in_channels: + #如果不正确,抛出异常 raise ValueError(f"For '{self.cls_name}', weight init shape error. The ndim of 'weight_init' should " f"be equal to 2, and the first dim should be equal to 'out_channels', and the " f"second dim should be equal to 'in_channels'. But got 'weight_init': {weight_init}, " f"'out_channels': {out_channels}, 'in_channels': {in_channels}.") + #创建一个参数,初始化为weight_init,并设置name为"weight" self.weight = Parameter(initializer(weight_init, [out_channels, in_channels]), name="weight") self.bias = None if self.has_bias: + #如果有bias,则初始化bias if isinstance(bias_init, Tensor): - if bias_init.ndim != 1 or bias_init.shape[0] != out_channels: + if bias_init.ndim!= 1 or bias_init.shape[0]!= out_channels: raise ValueError(f"For '{self.cls_name}', bias init shape error. The ndim of 'bias_init' should " f"be equal to 1, and the first dim should be equal to 'out_channels'. But got " f"'bias_init': {bias_init}, 'out_channels': {out_channels}.") + #初始化bias self.bias = Parameter(initializer(bias_init, [out_channels]), name="bias") + #初始化bias_add self.bias_add = P.BiasAdd() + #初始化matmul self.matmul = P.MatMul(transpose_b=True) + #如果activation不为空,且不是Cell或Primitive,则抛出异常 self.activation = get_activation(activation) if isinstance(activation, str) else activation if activation is not None and not isinstance(self.activation, (Cell, Primitive)): raise TypeError(f"For '{self.cls_name}', the 'activation' must be str or Cell or Primitive, but got " f"{type(activation).__name__}.") + #如果activation不为空,且不是Cell或Primitive,则将activation赋值给activation_flag self.activation_flag = self.activation is not None def construct(self, x): + #获取x的形状 x_shape = self.shape_op(x) + #检查x的形状是否符合要求 check_dense_input_shape(x_shape, self.cls_name) - if len(x_shape) != 2: + #如果x的形状不是2维,则将x转换为2维 + if len(x_shape)!= 2: x = self.reshape(x, (-1, x_shape[-1])) + #将x与weight相乘,并将结果添加到x中 x = self.matmul(x, self.weight) + #如果有偏置,则将x与偏置相加,并将结果添加到x中 if self.has_bias: x = self.bias_add(x, self.bias) + #如果有激活函数,则执行激活函数,并将结果添加到x中 if self.activation_flag: x = self.activation(x) - if len(x_shape) != 2: + #如果x的形状不是2维,则将x转换为2维,并将结果添加到x中 + if len(x_shape)!= 2: out_shape = x_shape[:-1] + (-1,) x = self.reshape(x, out_shape) + #返回x return x def extend_repr(self): + #扩展字符串 s = 'input_channels={}, output_channels={}'.format(self.in_channels, self.out_channels) + #如果有bias,则添加bias if self.has_bias: s += ', has_bias={}'.format(self.has_bias) + #如果有激活函数,则添加激活函数 if self.activation_flag: s += ', activation={}'.format(self.activation) + #返回扩展字符串 return s @constexpr def _is_equal_one(x): + #如果x为None,返回False if x is None: return False + #返回布尔型的x return bool(x.asnumpy().mean() == 1.0) @constexpr +#检查x_dtype def _dtype_check(x_dtype, prim_name=None): msg_prefix = f"For '{prim_name}', the" if prim_name else "The" + #果x_dtype不是float32或float16,抛出异常 if x_dtype not in [mstype.float32, mstype.float16]: raise TypeError(f"{msg_prefix} x_dtype must be float32 or float16, but got {x_dtype}.") @constexpr +#判断是否为浮点型 def _is_float_dtype(dtype): if dtype in [mstype.float32, mstype.float16]: + #如果dtype类型为float32或float16,返回True return True + #否则,返回False return False - @constexpr +#判断是否需要执行_reduce_all def _need_reduce_all(axis): if axis == (): + # 如果axis为空,则返回True return True + #否则,返回False return False @@ -413,47 +486,72 @@ class ClipByNorm(Cell): def __init__(self, axis=None): """Initialize ClipByNorm.""" super(ClipByNorm, self).__init__() + #如果axis为空,则设置为元组类型 if axis is None: axis = () + #如果axis为元组类型,则遍历元组中的每一个元素,检查元素类型是否为int,若不是则抛出异常 if isinstance(axis, tuple): for idx, item in enumerate(axis): Validator.check_value_type("axis[%d]" % idx, item, [int], self.cls_name) + #将axis转换为int类型 self.axis = Validator.check_value_type('axis', axis, [int, tuple], self.cls_name) + #实例化ReduceSum类,keep_dims为True self.reduce_sum = P.ReduceSum(keep_dims=True) + #将keep_dims设置为True self.select_ = P.Select() + #将Select()函数设置为一个选择函数 self.greater_ = P.Greater() + #将Greater()函数设置为一个比较函数 self.cast = P.Cast() + #将Cast()函数设置为一个转换函数 self.sqrt = P.Sqrt() + #将Sqrt()函数设置为一个平方根函数 self.max_op = P.Maximum() + #将Maximum()函数设置为一个最大函数 self.shape = P.Shape() + #将Shape()函数设置为一个维度函数 self.reshape = P.Reshape() + #将Reshape()函数设置为一个变形函数 self.fill = P.Fill() + #将Fill()函数设置为一个填充函数 self.expand_dims = P.ExpandDims() + #将ExpandDims()函数设置为一个增加维度函数 self.dtype = P.DType() def construct(self, x, clip_norm): + #计算x的平方 mul_x = F.square(x) + #计算x的平方和 l2sum = self.cast(self.reduce_sum(mul_x, self.axis), mstype.float32) + #如果l2sum大于0,则将l2sum赋值给cond,否则将l2sum赋值为ones_ cond = self.greater_(l2sum, 0) ones_ = self.fill(self.dtype(cond), self.shape(cond), 1.0) + #将l2sum赋值给l2sum_safe,如果cond为True,则将l2sum赋值给l2sum_safe,否则将l2sum赋值为ones_ l2sum_safe = self.select_(cond, l2sum, self.cast(ones_, self.dtype(l2sum))) + #将l2sum_safe赋值给l2norm,如果cond为True,则将l2sum_safe赋值给l2norm,否则将l2sum赋值为l2sum l2norm = self.select_(cond, self.sqrt(l2sum_safe), l2sum) _dtype_check(self.dtype(x), self.cls_name) + #判断x的数据类型是否符合要求 if _is_equal_one(clip_norm): + #如果clip_norm为1,则直接返回x intermediate = x else: + #否则,将x乘以clip_norm intermediate = x * clip_norm - + #计算max_norm max_norm = self.max_op(l2norm, clip_norm) + #如果clip_norm为1,则将max_norm转换为float32 if _need_reduce_all(self.axis): max_norm = self.expand_dims(max_norm, -1) + #计算values_clip values_clip = self.cast(intermediate, mstype.float32) / max_norm + #将values_clip转换为x的数据类型 values_clip = self.reshape(values_clip, self.shape(x)) + #返回values_clip values_clip = identity(values_clip) return values_clip - class Norm(Cell): r""" Computes the norm of vectors, currently including Euclidean norm, i.e., :math:`L_2`-norm. @@ -515,21 +613,29 @@ class Norm(Cell): def __init__(self, axis=(), keep_dims=False): """Initialize Norm.""" super(Norm, self).__init__() + #检查keep_dims的类型是否为布尔型 Validator.check_value_type("keep_dims", keep_dims, [bool], self.cls_name) + #将axis赋值给self.axis self.axis = axis + #将keep_dims赋值给self.keep_dims self.keep_dims = keep_dims + #初始化ReduceSum函数 self.reduce_sum = P.ReduceSum(True) + #初始化Sqrt函数 self.sqrt = P.Sqrt() + #初始化Squeeze函数 self.squeeze = P.Squeeze(self.axis) def construct(self, x): x = self.sqrt(self.reduce_sum(F.square(x), self.axis)) + #如果keep_dims为False,则将x转置 if not self.keep_dims: x = self.squeeze(x) return x - + #添加一个字符串,用于表示当前类的扩展信息 def extend_repr(self): + return 'axis={}, keep_dims={}'.format(self.axis, self.keep_dims) @@ -651,12 +757,17 @@ class OneHot(Cell): def __init__(self, axis=-1, depth=1, on_value=1.0, off_value=0.0, dtype=mstype.float32): """Initialize OneHot.""" super(OneHot, self).__init__() + #初始化OneHot类 self.onehot = P.OneHot(axis) + #初始化axis参数 self.depth = depth + #初始化depth参数 self.dtype = dtype + #初始化dtype参数 self.on_value = on_value + #初始化on_value参数 self.off_value = off_value - + #构建onehot编码 def construct(self, indices): return self.onehot(indices, self.depth, F.cast(self.on_value, self.dtype), F.cast(self.off_value, self.dtype)) @@ -799,26 +910,37 @@ class Pad(Cell): def __init__(self, paddings, mode="CONSTANT"): """Initialize Pad.""" super(Pad, self).__init__() + #设置模式 self.mode = mode + #检查模式是否为CONSTANT、REFLECT、SYMMETRIC + Validator.check_string(self.mode, ["CONSTANT", "REFLECT", "SYMMETRIC"],'mode', self.cls_name) + #设置填充 self.paddings = paddings Validator.check_string(self.mode, ["CONSTANT", "REFLECT", "SYMMETRIC"], 'mode', self.cls_name) + #检查mode是否为字符串,是否为CONSTANT、REFLECT或SYMMETRIC if not isinstance(paddings, tuple): raise TypeError(f"For '{self.cls_name}', the type of 'paddings' must be tuple, " f"but got {type(paddings).__name__}.") for item in paddings: - if len(item) != 2: + #如果item的长度不等于2,抛出异常 + if len(item)!= 2: raise ValueError(f"For '{self.cls_name}', the dimension of 'paddings' must be (n, 2), " f"but got {paddings}.") + #如果paddings的长度大于4,抛出异常 if len(paddings) > 4: raise ValueError(f"For '{self.cls_name}', only 'paddings' up to 4 dims is supported, but got " f"{len(paddings)}.") + #如果mode的值为CONSTANT,则将paddings转换为Tensor类型 if mode == "CONSTANT": self.pad = P.Pad(self.paddings) else: + #否则将paddings转换为numpy数组类型,并赋值给self.paddings self.paddings = Tensor(np.array(self.paddings), dtype=mstype.int64) + #将mode设置为MirrorPad模式 self.pad = P.MirrorPad(mode=mode) - + #构建模式为CONSTANT的卷积层 def construct(self, x): + if self.mode == "CONSTANT": x = self.pad(x) else: @@ -830,21 +952,29 @@ class Pad(Cell): def bilinear(shape, size, scale, align_corners, prim_name=None): """Check input and calculate shape""" msg_prefix = f"For '{prim_name}', the" if prim_name else "The" + #检查align_corners的类型是否为布尔值 if not isinstance(align_corners, bool): raise TypeError(f"{msg_prefix} type of 'align_corners' should be boolean, " f"but got {type(align_corners).__name__}.") + #检查size和scale的值是否都不为空 if size is None and scale is None: - raise ValueError(f"{msg_prefix} 'size' and 'scale' both none.") + raise ValueError(f"{msg_prefix}'size' and'scale' both none.") + #检查size和scale的值是否都不为空 if size is not None and scale is not None: - raise ValueError(f"{msg_prefix} 'size' and 'scale' both not none.") + raise ValueError(f"{msg_prefix}'size' and'scale' both not none.") if size is not None: + #如果size不是元组或列表,则抛出错误 if not isinstance(size, (tuple, list)): - raise ValueError(f"{msg_prefix} 'size' must be tuple or list or None, but got {type(size).__name__}.") + raise ValueError(f"{msg_prefix}'size' must be tuple or list or None, but got {type(size).__name__}.") + #检查size的长度是否为2,是否小于等于2,是否大于等于1 Validator.check_int(len(size), 2, Rel.EQ, "size", "bilinear") Validator.check_int(size[0], 1, Rel.GE, "size[0]", "bilinear") Validator.check_int(size[1], 1, Rel.GE, "size[1]", "bilinear") + #返回size return size + #检查scale是否大于等于1 Validator.check_int(scale, 1, Rel.GE, "scale factor", "bilinear") + #返回scale乘以shape的第2列和第3列 ret = (scale * shape[2], scale * shape[3]) return ret @@ -915,11 +1045,13 @@ class ResizeBilinear(Cell): self.half_pixel_centers = half_pixel_centers def construct(self, x, size=None, scale_factor=None, align_corners=False): + #调用bilinear函数,计算shape shape = bilinear(x.shape, size, scale_factor, align_corners, self.cls_name) + #调用ResizeBilinear函数,计算resize_bilinear resize_bilinear = P.ResizeBilinear(shape, align_corners, self.half_pixel_centers) + #返回resize_bilinear函数的结果 return resize_bilinear(x) - class Unfold(Cell): r""" Extracts patches from images. @@ -982,10 +1114,13 @@ class Unfold(Cell): super(Unfold, self).__init__() def _check_tuple_or_list(arg_name, arg_val, prim_name): + #检查参数arg_name的类型是否为tuple或list Validator.check_value_type(f"{arg_name}s", ksizes, [tuple, list], self.cls_name) - if len(arg_val) != 4 or arg_val[0] != 1 or arg_val[3] != 1: + #检查参数arg_val的长度是否为4,且参数arg_val的第一个元素是否为1,第四个元素是否为1 + if len(arg_val)!= 4 or arg_val[0]!= 1 or arg_val[3]!= 1: raise ValueError(f"For '{prim_name}' the format of '{arg_name}s' should be [1, {arg_name}_row, " f"{arg_name}_col, 1], but got {arg_val}.") + #检查参数arg_val的第二个元素和第三个元素是否为正整数 if not isinstance(arg_val[1], int) or not isinstance(arg_val[2], int) or arg_val[1] < 1 or arg_val[2] < 1: raise ValueError(f"For '{prim_name}' the {arg_name}_row and {arg_name}_col in '{arg_name}s' should be " f"an positive integer number, but got {arg_name}_row is {arg_val[1]}, " @@ -994,16 +1129,22 @@ class Unfold(Cell): _check_tuple_or_list("ksize", ksizes, self.cls_name) _check_tuple_or_list("stride", strides, self.cls_name) _check_tuple_or_list("rate", rates, self.cls_name) + # 将ksizes转换为元组,并将元组转换为列表 ksizes = ksizes[0], ksizes[3], ksizes[1], ksizes[2] + # 将strides转换为元组,并将元组转换为列表 strides = strides[0], strides[3], strides[1], strides[2] + # 将rates转换为元组,并将元组转换为列表 rates = rates[0], rates[3], rates[1], rates[2] + # 调用inner.ExtractImagePatches函数,传入ksizes、strides、rates、padding参数 self.extract_image_patches = inner.ExtractImagePatches(ksizes, strides, rates, padding) + #定义一个构造函数,用于提取图像特征 def construct(self, input_x): + #使用extract_image_patches函数提取图像特征 result = self.extract_image_patches(input_x) + #返回提取的特征 return result - @constexpr def tril(x_shape, x_dtype, k): Validator.check_int(len(x_shape), 1, Rel.GE, "x rank", "tril") @@ -1096,13 +1237,18 @@ class Tril(Cell): def __init__(self): """Initialize Tril.""" super(Tril, self).__init__() + #初始化dtype self.dtype = P.DType() + #初始化mul self.mul = P.Mul() + #初始化cast self.cast = P.Cast() def construct(self, x, k=0): assist = tril(x.shape, self.dtype(x), k) + #将x的行数和列数转换为float32类型 result = self.mul(self.cast(x, mstype.float32), self.cast(assist, mstype.float32)) + #将result的类型转换为x的类型 return self.cast(result, self.dtype(x)) @@ -1110,7 +1256,9 @@ class Tril(Cell): def triu(x_shape, x_dtype, k): Validator.check_int(len(x_shape), 1, Rel.GE, "x rank", "triu") Validator.check_is_int(k, "k value", "triu") + # 创建一个mask,其中元素值为1,表示元素在对角线上,其余为0 mask = np.triu(np.ones(x_shape), k) + # 返回一个Tensor,其中元素值为mask,其类型为x_dtype return Tensor(mask, x_dtype) @@ -1185,33 +1333,46 @@ class Triu(Cell): [ 0 11 12 13] [ 0 0 16 17]] """ - + #构造 def __init__(self): """Initialize Triu.""" super(Triu, self).__init__() + #初始化dtype self.dtype = P.DType() + #初始化mul self.mul = P.Mul() + #初始化cast self.cast = P.Cast() - + #构建或计算类的逻辑 def construct(self, x, k=0): assist = triu(x.shape, self.dtype(x), k) + #将x的行和列都设置为0,并将其转换为float32类型 result = self.mul(self.cast(x, mstype.float32), self.cast(assist, mstype.float32)) + #将result转换为x的类型 return self.cast(result, self.dtype(x)) @constexpr +#获取一个张量对角线上的值 def _get_matrix_diag_assist(x_shape, x_dtype): Validator.check_int(len(x_shape), 1, Rel.GE, "x rank", "_get_matrix_diag_assist") + #创建一个eye矩阵,维度为x_shape[-1],x_shape[-1] base_eye = np.eye(x_shape[-1], x_shape[-1]).reshape(-1) + #将eye矩阵填充到x_shape中,每个元素都是base_eye assist = np.tile(base_eye, x_shape[:-1]).reshape(x_shape + (x_shape[-1],)) + #将eye矩阵和x_shape中的元素进行tile,每个元素都是base_eye return Tensor(assist, x_dtype) @constexpr +#获取一个张量对角线上的值 def _get_matrix_diag_part_assist(x_shape, x_dtype): Validator.check_int(len(x_shape), 2, Rel.GE, "x rank", "_get_matrix_diag_part_assist") + #创建一个eye矩阵,输入维度为x_shape[-2]和x_shape[-1],输出维度为x_shape base_eye = np.eye(x_shape[-2], x_shape[-1]).reshape(-1) + #将eye矩阵填充到x_shape中,输出维度为x_shape assist = np.tile(base_eye, x_shape[:-2]).reshape(x_shape) + #将eye矩阵填充到Tensor中,输出维度为x_shape return Tensor(assist, x_dtype) @@ -1279,17 +1440,21 @@ class MatrixDiag(Cell): def __init__(self): """Initialize MatrixDiag.""" super(MatrixDiag, self).__init__() + #初始化矩阵对角线 self.matrix_diag = inner.MatrixDiag() + #获取数据类型 self.dtype = P.DType() def construct(self, input_x): x_shape = F.shape(input_x) x_dtype = self.dtype(input_x) + #获取矩阵对角线元素的辅助信息 assist = _get_matrix_diag_assist(x_shape, x_dtype) + #调用矩阵对角线元素的函数 out_matrix_diag = self.matrix_diag(input_x, assist) + #返回矩阵对角线元素 return out_matrix_diag - class MatrixDiagPart(Cell): r""" Returns the batched diagonal part of a batched tensor. @@ -1336,14 +1501,19 @@ class MatrixDiagPart(Cell): def __init__(self): """Initialize MatrixDiagPart.""" super(MatrixDiagPart, self).__init__() + #初始化矩阵对角线部分 self.matrix_diag_part = inner.MatrixDiagPart() + #获取数据类型 self.dtype = P.DType() def construct(self, input_x): x_shape = F.shape(input_x) x_dtype = self.dtype(input_x) + #获取输入x的对角线部分 assist = _get_matrix_diag_part_assist(x_shape, x_dtype) + #调用matrix_diag_part函数,获取输入x的对角线部分 out_matrix_diag_part = self.matrix_diag_part(input_x, assist) + #返回输出x的对角线部分 return out_matrix_diag_part @@ -1395,18 +1565,23 @@ class MatrixSetDiag(Cell): def __init__(self): """Initialize MatrixSetDiag.""" super(MatrixSetDiag, self).__init__() + #初始化矩阵集合对角线 self.matrix_set_diag = inner.MatrixSetDiag() + #获取数据类型 self.dtype = P.DType() def construct(self, input_x, diagonal): x_shape = F.shape(input_x) x_dtype = self.dtype(input_x) + #获取输入x的矩阵对角线部分 assist = _get_matrix_diag_part_assist(x_shape, x_dtype) + #调用matrix_set_diag函数,计算输入x的对角线元素 out_matrix_set_diag = self.matrix_set_diag(input_x, diagonal, assist) return out_matrix_set_diag @constexpr +#检查输入的维度是否在指定的范围内 def _check_input_dim(axis, dim, cls_name): Validator.check_int_range(axis, -dim, dim, Rel.INC_LEFT, 'axis', cls_name) @@ -1459,35 +1634,53 @@ class Roll(Cell): def __init__(self, shift, axis): """Initialize Roll""" super(Roll, self).__init__() + #检查shift的类型是否为int, tuple, list Validator.check_value_type("shift", shift, [int, tuple, list], self.cls_name) + #检查axis的类型是否为int, tuple, list Validator.check_value_type("axis", axis, [int, tuple, list], self.cls_name) + #创建shape操作对象 self.shape_op = P.Shape() + #将shift赋值给self.shift self.shift = shift + #将axis赋值给self.axis self.axis = axis + #创建op_list列表 self.op_list = [] if not isinstance(self.axis, (list, tuple)): + #如果axis不是list或tuple类型,则添加一个Roll类型的op_list self.op_list.append((inner.Roll(shift=self.shift, axis=0), self.axis)) else: - if len(self.shift) != len(self.axis): - raise ValueError(f"For '{self.cls_name}', the shape of 'shift' and the shape of 'axis' must be " - f"the same, but got the length of 'shift' {len(self.shift)} and the length of 'axis'" + #如果axis是list或tuple类型,则检查shift和axis的长度是否相等 + if len(self.shift)!= len(self.axis): + raise ValueError(f"For '{self.cls_name}', the shape of'shift' and the shape of 'axis' must be " + f"the same, but got the length of'shift' {len(self.shift)} and the length of 'axis'" f" {len(self.axis)}.") + #遍历axis,添加一个Roll类型的op_list for idx, _ in enumerate(self.axis): self.op_list.append((inner.Roll(shift=self.shift[idx], axis=0), self.axis[idx])) - def construct(self, input_x): dim = len(self.shape_op(input_x)) + #遍历op_list,检查输入的维度是否正确 for single_op_roll, single_axis in self.op_list: + #检查输入的维度是否正确 _check_input_dim(single_axis, dim, self.cls_name) + #如果输入的维度小于0,则将输入的维度翻转 if single_axis < 0: single_axis += dim + #初始化transpose_perm transpose_perm = [] + #遍历维度 for i in range(dim): + #将维度添加到transpose_perm中 transpose_perm.append(i) + #将输入的维度翻转 transpose_perm[0], transpose_perm[single_axis] = single_axis, 0 input_x = input_x.transpose(transpose_perm) + #将输入x的维度进行转置 input_x = single_op_roll(input_x) + #对输入x进行单操作滑动 input_x = input_x.transpose(transpose_perm) + #返回转置后的输入x return input_x diff --git a/mindspore/python/mindspore/nn/layer/combined.py b/mindspore/python/mindspore/nn/layer/combined.py index 19d8f30319b..10d1ab7ee8c 100644 --- a/mindspore/python/mindspore/nn/layer/combined.py +++ b/mindspore/python/mindspore/nn/layer/combined.py @@ -12,13 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ +#combined操作函数主要完成让我们简化模型。减少代码重复,提高代码可读性和可维护性 """Combined cells.""" - +# 从mindspore库中导入nn模块,用于提供神经网络相关的功能,如模型、损失函数、优化器等 from mindspore import nn +# 从mindspore.ops.primitive模块中导入Primitive类,用于定义基本操作(如矩阵乘法、加法等) from mindspore.ops.primitive import Primitive +# 从mindspore._checkparam模块中导入Validator类,用于验证参数的范围和类型 from mindspore._checkparam import Validator +# 从当前模块(.normalization)中导入BatchNorm2d和BatchNorm1d类,用于实现批量归一化层 from .normalization import BatchNorm2d, BatchNorm1d +# 从当前模块(.activation)中导入get_activation和LeakyReLU函数,用于获取激活函数和LeakyReLU激活函数 from .activation import get_activation, LeakyReLU +# 从当前模块(..cell)中导入Cell类,用于创建自定义Cell from ..cell import Cell @@ -27,6 +33,8 @@ __all__ = [ 'DenseBnAct' ] +#用于实现卷积神经网络 +#结合了卷积层、批量归一化层和激活函数(如ReLU、Swish或Mish等),通常用于构建深度学习模型 class Conv2dBnAct(Cell): r""" @@ -127,28 +135,38 @@ class Conv2dBnAct(Cell): has_bias=has_bias, weight_init=weight_init, bias_init=bias_init) + #判断是否有BN(Batch Normalization)层 self.has_bn = Validator.check_bool(has_bn, "has_bn", self.cls_name) + #判断是否有激活函数 self.has_act = activation is not None + #判断是否在fake前 self.after_fake = Validator.check_bool(after_fake, "after_fake", self.cls_name) if has_bn: + #如果有batchnorm,则创建一个batchnorm self.batchnorm = BatchNorm2d(out_channels, eps, momentum) if activation == "leakyrelu": + #如果activation为leakyrelu,则创建一个leakyrelu self.activation = LeakyReLU(alpha) else: + #如果activation不为leakyrelu,则创建一个激活函数 self.activation = get_activation(activation) if isinstance(activation, str) else activation + #如果activation不为空,且不是Cell或Primitive,则抛出错误 if activation is not None and not isinstance(self.activation, (Cell, Primitive)): raise TypeError(f"For '{self.cls_name}', the 'activation' must be str or Cell or Primitive, " f"but got {type(activation).__name__}.") def construct(self, x): x = self.conv(x) + #如果有bn层,则添加bn层 if self.has_bn: x = self.batchnorm(x) + #如果有激活函数,则添加激活函数 if self.has_act: x = self.activation(x) return x - +#用于实现全连接神经网络 +#结合了全连接层、归一化层和激活函数 class DenseBnAct(Cell): r""" A combination of Dense, Batchnorm, and the activation layer. @@ -218,23 +236,34 @@ class DenseBnAct(Cell): weight_init, bias_init, has_bias) + #定义激活函数 self.has_bn = Validator.check_bool(has_bn, "has_bn", self.cls_name) + #判断是否有BN层 self.has_act = activation is not None + #判断是否有激活函数 self.after_fake = Validator.check_bool(after_fake, "after_fake", self.cls_name) if has_bn: + #如果有BN层 self.batchnorm = BatchNorm1d(out_channels, eps, momentum) if activation == "leakyrelu": + #如果激活函数为leakyrelu self.activation = LeakyReLU(alpha) else: + #如果激活函数不为leakyrelu self.activation = get_activation(activation) if isinstance(activation, str) else activation + #如果激活函数不为空且不是Cell或Primitive if activation is not None and not isinstance(self.activation, (Cell, Primitive)): + #如果激活函数不是Cell或Primitive,抛出异常 raise TypeError(f"For '{self.cls_name}', the 'activation' must be str or Cell or Primitive, " f"but got {type(activation).__name__}.") - def construct(self, x): + # 将x转换为维度为[B, C, H, W]的矩阵 x = self.dense(x) + # 如果有bn层,则对矩阵进行操作 if self.has_bn: x = self.batchnorm(x) + # 如果有act层,则对矩阵进行操作 if self.has_act: x = self.activation(x) - return x + # 返回矩阵 + return x \ No newline at end of file diff --git a/mindspore/python/mindspore/nn/layer/container.py b/mindspore/python/mindspore/nn/layer/container.py index 0f558fb0853..c61366f65d4 100644 --- a/mindspore/python/mindspore/nn/layer/container.py +++ b/mindspore/python/mindspore/nn/layer/container.py @@ -13,66 +13,92 @@ # limitations under the License. # ============================================================================ """container""" +# 从collections库中导入OrderedDict类,用于创建一个有序字典,用于存储网络层的参数 from collections import OrderedDict +# 从abc库中导入abstractmethod类,用于定义一个抽象方法,要求子类覆盖这个方法 from abc import abstractmethod +# 从当前模块(..cell)中导入Cell类,用于创建自定义Cell from ..cell import Cell __all__ = ['SequentialCell', 'CellList'] - +#用于检测搜索的值和类型是否有效,从而避免在神经网络中出现错误 def _valid_index(cell_num, index, op_name=None): """Internal function, used to detect the value and type of index.""" msg_prefix = f"For '{op_name}', the" if op_name else "The" + #检查索引类型是否为int if not isinstance(index, int): + # 如果不是,抛出类型错误 raise TypeError(f"{msg_prefix} type of 'index' should be int, but got {type(index).__name__}.") + #检查索引是否在cell_num范围内 if not -cell_num <= index < cell_num: + #如果不在,抛出索引错误 raise IndexError(f"{msg_prefix} value of 'index' should be a number in range [{-cell_num}, {cell_num}), " f"but got {index}.") + #返回索引对应的整数 return index % cell_num - +#用于检查输入的Cell是否为Cell子类,来处理卷积神经网络中Cell合并和连接 def _valid_cell(cell, op_name=None): """Internal function, used to check whether the input cell is a subclass of Cell.""" + #判断cell是否是Cell的子类 if issubclass(cell.__class__, Cell): + # 如果是,返回True return True + #如果不是,拼接提示信息 msg_prefix = f"For '{op_name}'," if op_name else "" + #抛出异常 raise TypeError(f'{msg_prefix} each cell should be subclass of Cell, but got {type(cell).__name__}.') - +#获得字符串前缀和索引 def _get_prefix_and_index(cells): """get prefix and index of parameter name in sequential cell or cell list.""" + #给prefix和index赋值 prefix = "" index = 0 + #如果cells为空,则返回prefix和index if not cells: return prefix, index + #将cells字典转换为列表 cell_list = list(cells.items()) + #定义变量first_param,first_key,second_param,second_key first_param, first_key = None, None second_param, second_key = None, None + #遍历cells字典 for key, cell in cell_list: try: + #获取cell中的参数和名称 _, param = next(cell.parameters_and_names()) except StopIteration: + #如果遍历完cells字典,则跳出循环 continue + #如果first_param为空,则将参数和名称赋值给first_param和first_key if first_param is None: first_param = param first_key = key continue + #如果second_param为空,则将参数和名称赋值给second_param和second_key second_param = param second_key = key + #跳出循环 break - + #如果first_param为空,返回prefix和index if first_param is None: return prefix, index split_names = first_param.name.split(".") + #遍历split_names,从第一个元素开始,拆分出每一个元素 for idx, name in enumerate(split_names): + #如果拆分出的元素与first_key相同,则将prefix设置为拆分出的元素 if name == first_key: prefix = ".".join(split_names[:idx]) prefix = prefix + "." if prefix else prefix index = idx + #如果second_param不为空,且拆分出的元素与second_key相同,则结束循环 if second_param is not None and second_param.name.split(".")[idx] == second_key: break + #返回prefix和index return prefix, index @@ -93,16 +119,18 @@ class _CellListBase: @abstractmethod def __len__(self): + #返回自身的长度 pass - @abstractmethod def __getitem__(self, index): + #返回索引index对应的元素 pass def construct(self): raise NotImplementedError - +#用于表示一个顺序存储的单元格列表,构造Cell顺序容器 +#其继承了_CellListBase来实现相关方法 class SequentialCell(Cell): """ Sequential Cell container. For more details about Cell, please refer to @@ -162,82 +190,122 @@ class SequentialCell(Cell): def __init__(self, *args): """Initialize SequentialCell.""" super(SequentialCell, self).__init__() + #初始化参数 self._is_dynamic_name = [] + #初始化变量 if len(args) == 1: cells = args[0] + #如果参数是列表,则将其转换为字典 if isinstance(cells, list): for index, cell in enumerate(cells): + #将子类插入到cell中 self.insert_child_to_cell(str(index), cell) + #将cell的参数名更新为index+"." cell.update_parameters_name(str(index) + ".") + #将变量添加到变量列表中 self._is_dynamic_name.append(True) + #如果参数是字典,则将其转换为列表 elif isinstance(cells, OrderedDict): for name, cell in cells.items(): + #将子类插入到cell中 self.insert_child_to_cell(name, cell) + #将cell的参数名更新为name+"." cell.update_parameters_name(name + ".") + #将变量添加到变量列表中 self._is_dynamic_name.append(False) else: + #返回错误 raise TypeError(f"For '{self.__class__.__name__}', the 'args[0]' must be list or orderedDict, " f"but got {type(cells).__name__}") else: for index, cell in enumerate(args): + #将cell插入到cell_list中 self.insert_child_to_cell(str(index), cell) + #将cell的参数名添加到_is_dynamic_name中 cell.update_parameters_name(str(index) + ".") self._is_dynamic_name.append(True) + #将cells转换为列表 self.cell_list = list(self._cells.values()) - + #在这里我们来解释一下slice类,其用于表示切片来用于从序列之中获取子序列的抽象概念 + #用于实现索引访问单元列表操作 def __getitem__(self, index): if isinstance(index, slice): + #如果index是一个slice,则返回一个新的OrderedDict,其中包含self._cells中的指定位置的元素 return self.__class__( OrderedDict(list(self._cells.items())[index])) + #如果index是一个整数,则检查index是否在self._cells中,若在则返回self._cells中的指定位置的元素,否则抛出异常 index = _valid_index(len(self), index, self.__class__.__name__) + #返回list return list(self._cells.values())[index] - + #用于实现索引访问单元列表操作 def __setitem__(self, index, cell): cls_name = self.__class__.__name__ + #检查cell是否符合要求 if _valid_cell(cell, cls_name): + #获取cell的前缀和索引 prefix, _ = _get_prefix_and_index(self._cells) + #检查索引是否合法 index = _valid_index(len(self), index, cls_name) + #获取cell的键 key = list(self._cells.keys())[index] + #将cell添加到self._cells中 self._cells[key] = cell + #更新cell的参数名 cell.update_parameters_name(prefix + key + ".") + #更新self.cell_list self.cell_list = list(self._cells.values()) - + #用于实现删除索引单元列表操作 def __delitem__(self, index): cls_name = self.__class__.__name__ if isinstance(index, int): + #如果index是int类型,则将index转换为_valid_index函数的返回值 index = _valid_index(len(self), index, cls_name) + #获取key key = list(self._cells.keys())[index] + #删除key del self._cells[key] + #删除is_dynamic_name中index del self._is_dynamic_name[index] elif isinstance(index, slice): + #如果index是slice类型,则获取keys keys = list(self._cells.keys())[index] + #遍历keys,删除key for key in keys: del self._cells[key] + #删除is_dynamic_name中index del self._is_dynamic_name[index] else: + #如果index不是int类型或者slice类型,则抛出TypeError异常 raise TypeError(f"For '{cls_name}', the type of index should be int type or slice type, " f"but got {type(index).__name__}") + #获取prefix和key_index prefix, key_index = _get_prefix_and_index(self._cells) + #创建一个临时字典 temp_dict = OrderedDict() + #遍历cells,将key和cell添加到temp_dict中 for idx, key in enumerate(self._cells.keys()): cell = self._cells[key] + #如果is_dynamic_name中idx为True,则将cell添加到temp_dict中 if self._is_dynamic_name[idx]: for _, param in cell.parameters_and_names(): param.name = prefix + str(idx) + "." + ".".join(param.name.split(".")[key_index+1:]) temp_dict[str(idx)] = cell else: temp_dict[key] = cell + #将temp_dict中的值赋值给cells self._cells = temp_dict + #将cells中的值赋值给self.cell_list self.cell_list = list(self._cells.values()) - + #获取长度 def __len__(self): + #返回长度 return len(self._cells) - + #设置单元表格梯度 def set_grad(self, flag=True): self.requires_grad = flag + #检查是否设置梯度 for cell in self._cells.values(): cell.set_grad(flag) - def append(self, cell): """ Appends a given Cell to the end of the list. @@ -264,16 +332,23 @@ class SequentialCell(Cell): [[26.999863 26.999863] [26.999863 26.999863]]]] """ + #_valid_cell函数用于检查给定的单元是否有效 if _valid_cell(cell, self.__class__.__name__): prefix, _ = _get_prefix_and_index(self._cells) + #将当前cell的名称添加到prefix中 cell.update_parameters_name(prefix + str(len(self)) + ".") + #将当前cell添加到self._cells中 self._is_dynamic_name.append(True) + #将当前cell添加到self._is_dynamic_name中 self._cells[str(len(self))] = cell + #将self._cells中的值赋值给self.cell_list self.cell_list = list(self._cells.values()) - + #创建表格 def construct(self, input_data): for cell in self.cell_list: + #调用cell函数,传入input_data参数 input_data = cell(input_data) + #返回input_data参数值 return input_data @@ -301,63 +376,84 @@ class CellList(_CellListBase, Cell): >>> cell_ls.append(relu) >>> cell_ls.extend([relu, relu]) """ + #初始化 def __init__(self, *args, **kwargs): """Initialize CellList.""" auto_prefix = kwargs["auto_prefix"] if "auto_prefix" in kwargs.keys() else True + #初始化CellListBase类 _CellListBase.__init__(self) + #初始化Cell类 Cell.__init__(self, auto_prefix) + #如果只有一个参数,则将其追加到CellListBase中 if len(args) == 1: self.extend(args[0]) - + #调用 def __getitem__(self, index): cls_name = self.__class__.__name__ + #如果index是slice类型,则返回一个新的list,其中包含self._cells中的值 if isinstance(index, slice): return self.__class__(list(self._cells.values())[index]) + #如果index是int类型,则根据index的长度,获取对应的index,并返回self._cells中对应的值 if isinstance(index, int): index = _valid_index(len(self), index, cls_name) return self._cells[str(index)] + #如果index的类型不是int或slice,则抛出TypeError异常 raise TypeError(f"For '{cls_name}', the type of 'index' should be int or slice, " f"but got {type(index).__name__}.") - + #修改对象数值 def __setitem__(self, index, cell): cls_name = self.__class__.__name__ + #判断索引是否为整数,并且cell是否为有效cell if not isinstance(index, int) and _valid_cell(cell, cls_name): raise TypeError(f"For '{cls_name}', the type of 'index' should be int, " f"but got {type(index).__name__}.") + #如果自动前缀为True,则获取前缀和索引 index = _valid_index(len(self), index, cls_name) + #如果自动前缀为True,则更新参数名 if self._auto_prefix: prefix, _ = _get_prefix_and_index(self._cells) cell.update_parameters_name(prefix + str(index) + ".") + #将索引和cell添加到self._cells中 self._cells[str(index)] = cell - + #用于实现删除索引单元列表操作 def __delitem__(self, index): cls_name = self.__class__.__name__ if isinstance(index, int): + #如果index是int类型,则将index转换为_valid_index函数的返回值 index = _valid_index(len(self), index, cls_name) + #将_valid_index函数的返回值赋值给index del self._cells[str(index)] elif isinstance(index, slice): + #如果index是slice类型,则遍历self._cells字典,将每一项删除 keys = list(self._cells.keys())[index] for key in keys: del self._cells[key] else: + #如果index不是int类型或slice类型,则抛出TypeError异常 raise TypeError(f"For '{cls_name}', the type of 'index' should be int or slice, " f"but got {type(index).__name__}.") # adjust orderedDict prefix, key_index = _get_prefix_and_index(self._cells) + #创建一个空,用于存储cell temp_dict = OrderedDict() + #遍历cells中的每一个cell for idx, cell in enumerate(self._cells.values()): + #如果自动前缀,则把cell中的参数名称添加到前缀中 if self._auto_prefix: for _, param in cell.parameters_and_names(): param.name = prefix + str(idx) + "." + ".".join(param.name.split(".")[key_index+1:]) + #添加cell temp_dict[str(idx)] = cell + #更新cell self._cells = temp_dict - + #获取长度 def __len__(self): + #返回长度 return len(self._cells) - + #返回迭代器 def __iter__(self): return iter(self._cells.values()) - + #实现对象间加法 def __iadd__(self, cells): self.extend(cells) return self @@ -385,7 +481,7 @@ class CellList(_CellListBase, Cell): self._cells[str(idx)] = cell if self._auto_prefix: cell.update_parameters_name(prefix + str(idx) + ".") - + #迭代器,将 cells 中的每个元素添加到 MyContainer 对象的末尾 def extend(self, cells): """ Appends Cells from a Python iterable to the end of the list. @@ -397,15 +493,23 @@ class CellList(_CellListBase, Cell): TypeError: If the argument cells are not a list of Cells. """ cls_name = self.__class__.__name__ + #判断cells是否为list类型 if not isinstance(cells, list): + # 如果不是,抛出错误 raise TypeError(f"For '{cls_name}', the new cells wanted to append " f"should be instance of list, but got {type(cells).__name__}.") + #获取cells的前缀和索引 prefix, _ = _get_prefix_and_index(self._cells) + #遍历cells for cell in cells: + #判断cell是否有效 if _valid_cell(cell, cls_name): + #如果自动前缀,更新cell的参数名 if self._auto_prefix: cell.update_parameters_name(prefix + str(len(self)) + ".") + #将cell添加到self._cells中 self._cells[str(len(self))] = cell + #返回self return self def append(self, cell): @@ -416,15 +520,21 @@ class CellList(_CellListBase, Cell): cell(Cell): The subcell to be appended. """ if _valid_cell(cell, self.__class__.__name__): + #如果cell是有效的cell,则更新cell的参数名 if self._auto_prefix: + #获取cell的前缀和索引 prefix, _ = _get_prefix_and_index(self._cells) + #更新cell的参数名 cell.update_parameters_name(prefix + str(len(self)) + ".") + #将cell添加到self._cells中 self._cells[str(len(self))] = cell - + #设置单元表格梯度 def set_grad(self, flag=True): self.requires_grad = flag + #检查是否设置梯度 for cell in self._cells.values(): cell.set_grad(flag) - + #创建表格 def construct(self, *inputs): raise NotImplementedError + diff --git a/mindspore/python/mindspore/nn/layer/conv.py b/mindspore/python/mindspore/nn/layer/conv.py index e9a73c8c162..ad16afb3488 100644 --- a/mindspore/python/mindspore/nn/layer/conv.py +++ b/mindspore/python/mindspore/nn/layer/conv.py @@ -13,21 +13,36 @@ # limitations under the License. # ============================================================================ """conv""" +# 导入numpy库,用于处理数值计算 import numpy as np +# 从mindspore库中导入log模块,用于提供日志记录功能 from mindspore import log as logger +# 从mindspore库中导入context模块,用于获取和设置运行环境 from mindspore import context +# 从mindspore.ops模块中导入operations模块,用于提供基本操作(如矩阵乘法、加法等) from mindspore.ops import operations as P +# 从mindspore.ops.primitive模块中导入constexpr类,用于定义一个常量 from mindspore.ops.primitive import constexpr +# 从mindspore.common.parameter模块中导入Parameter类,用于创建一个参数 from mindspore.common.parameter import Parameter +# 从mindspore.common.initializer模块中导入initializer函数,用于初始化参数 from mindspore.common.initializer import initializer +# 从mindspore.common.tensor模块中导入Tensor类,用于表示张量 from mindspore.common.tensor import Tensor +# 从mindspore._checkparam模块中导入Validator、Rel、twice和_check_3d_int_or_tuple函数,用于验证参数的范围和类型 from mindspore._checkparam import Validator, Rel, twice, _check_3d_int_or_tuple +# 从mindspore._extends模块中导入cell_attr_register函数,用于注册Cell类的属性 from mindspore._extends import cell_attr_register +# 从当前模块(..cell)中导入Cell类,用于创建自定义Cell from ..cell import Cell - +# P:用于定义MindSpore操作的模块(操作是可以在MindSpore设备上执行的函数) +# constexpr:装饰器,用于标记一个函数为常量表达式,将在编译时执行 +# Parameter:用于定义模型参数的类 +# initializer:用于定义初始化参数的模块 +# Validator:用于验证参数的类 __all__ = ['Conv2d', 'Conv2dTranspose', 'Conv1d', 'Conv1dTranspose', 'Conv3d', 'Conv3dTranspose'] - +# 用于实现卷积层 class _Conv(Cell): """ Applies a N-D convolution over an input signal composed of several input planes. @@ -51,61 +66,80 @@ class _Conv(Cell): super(_Conv, self).__init__() self.in_channels = Validator.check_positive_int(in_channels, 'in_channels', self.cls_name) self.out_channels = Validator.check_positive_int(out_channels, 'out_channels', self.cls_name) + # 定义卷积核的大小 self.kernel_size = kernel_size + # 定义步长 self.stride = stride + # 定义填充模式 self.pad_mode = pad_mode + # 定义权重初始化方式 self.weight_init = weight_init + # 定义偏置初始化方式 self.bias_init = bias_init + # 检查数据格式 self.format = Validator.check_string(data_format, ['NCHW', 'NHWC', 'NCDHW'], 'format', self.cls_name) if context.get_context("device_target") != "GPU" and self.format == "NHWC": + # 检查设备目标是否为GPU,并且格式是否为NHWC raise ValueError(f"For '{self.cls_name}', the \"NHWC\" format only support in GPU target, " f"but got the 'format' is {self.format} and " f"the platform is {context.get_context('device_target')}.") if isinstance(padding, int): + # 检查padding是否为非负整数 Validator.check_non_negative_int(padding, 'padding', self.cls_name) self.padding = padding elif isinstance(padding, tuple): + # 检查padding是否为元组 for pad in padding: Validator.check_non_negative_int(pad, 'padding item', self.cls_name) self.padding = padding else: + # 抛出类型错误 raise TypeError(f"For '{self.cls_name}', the type of 'padding' must be int or tuple(int), " f"but got {type(padding).__name__}.") - self.dilation = dilation self.group = Validator.check_positive_int(group) self.has_bias = has_bias + # 检查kernel_size是否为正整数 for kernel_size_elem in kernel_size: Validator.check_positive_int(kernel_size_elem, 'kernel_size item', self.cls_name) + # 检查stride是否为正整数 for stride_elem in stride: - Validator.check_positive_int(stride_elem, 'stride item', self.cls_name) + Validator.check_positive_int(stride_elem,'stride item', self.cls_name) + # 检查dilation是否为正整数 for dilation_elem in dilation: Validator.check_positive_int(dilation_elem, 'dilation item', self.cls_name) - if in_channels % group != 0: + # 检查in_channels是否为偶数 + if in_channels % group!= 0: raise ValueError(f"For '{self.cls_name}', the attr 'in_channels' must be divisible by attr 'group', " f"but got 'in_channels': {in_channels} and 'group': {group}.") - if out_channels % group != 0: + # 检查out_channels是否为偶数 + if out_channels % group!= 0: raise ValueError(f"For '{self.cls_name}', the 'out_channels' must be divisible by attr 'group', " f"but got 'out_channels': {out_channels} and 'group': {group}.") if transposed: + # 如果transposed为True,则设置weight的shape为[in_channels, out_channels//group, *kernel_size] shape = [in_channels, out_channels // group, *kernel_size] else: + # 如果transposed为False,则设置weight的shape为[out_channels, *kernel_size, in_channels//group] shape = [out_channels, *kernel_size, in_channels // group] if self.format == "NHWC" else \ [out_channels, in_channels // group, *kernel_size] + # 将weight初始化为输入的weight_init self.weight = Parameter(initializer(self.weight_init, shape), name='weight') + # 如果has_bias为True,则设置bias的shape为[out_channels] if Validator.check_bool(has_bias, "has_bias", self.cls_name): self.bias = Parameter(initializer(self.bias_init, [out_channels]), name='bias') + # 如果has_bias为False,则设置bias的shape为[out_channels],并将bias的初始值设置为0 else: - if self.bias_init != 'zeros': + if self.bias_init!='zeros': logger.warning("Value of 'has_bias' is False, value of 'bias_init' will be ignored.") self.bias = None - + # 如果没有子类覆盖这个方法则抛出一个未实现异常 def construct(self, *inputs): """Must be overridden by all subclasses.""" raise NotImplementedError - +# 用于实现二维卷积操作 class Conv2d(_Conv): r""" 2D convolution layer. @@ -240,17 +274,29 @@ class Conv2d(_Conv): @cell_attr_register def __init__(self, + # 输入通道数 in_channels, + # 输出通道数 out_channels, + # 卷积核大小 kernel_size, + # 步长为1 stride=1, + # 填充模式为'same' pad_mode='same', + # 填充数量为0 padding=0, + # 空洞大小为1 dilation=1, + # 分组数为1 group=1, + # 权重初始化方式为False has_bias=False, + # 权重初始化方式'normal' weight_init='normal', + # 权重初始化方式'zeros' bias_init='zeros', + # 数据格式为'NCHW' data_format='NCHW'): """Initialize Conv2d.""" kernel_size = twice(kernel_size) @@ -258,17 +304,29 @@ class Conv2d(_Conv): self._dilation = dilation dilation = twice(dilation) super(Conv2d, self).__init__( + # 输入通道数 in_channels, + # 输出通道数 out_channels, + # 卷积核大小 kernel_size, + # 步长 stride, + # 填充模式 pad_mode, + # 填充数量 padding, + # 空洞大小 dilation, + # 分组数 group, + # 是否包含偏置 has_bias, + # 权重初始化方式 weight_init, + # 偏置初始化方式 bias_init, + # 数据格式 data_format) self.conv2d = P.Conv2D(out_channel=self.out_channels, kernel_size=self.kernel_size, @@ -279,14 +337,16 @@ class Conv2d(_Conv): dilation=self.dilation, group=self.group, data_format=self.format) + # 初始化卷积层 self.bias_add = P.BiasAdd(data_format=self.format) - def construct(self, x): output = self.conv2d(x, self.weight) + # 如果有偏置,则在输出的后面添加偏置 if self.has_bias: output = self.bias_add(output, self.bias) + # 返回输出 return output - + # 扩展输出repr,添加卷积操作的额外信息 def extend_repr(self): s = 'input_channels={}, output_channels={}, kernel_size={}, ' \ 'stride={}, pad_mode={}, padding={}, dilation={}, ' \ @@ -309,10 +369,12 @@ class Conv2d(_Conv): @constexpr def _check_input_3d(input_shape, op_name): + # 如果长度不是3 if len(input_shape) != 3: + #抛出异常 raise ValueError(f"For '{op_name}', the dimension of input should be 3d, but got {len(input_shape)}.") - +# 用于实现一维卷积 class Conv1d(_Conv): r""" 1D convolution layer. @@ -413,37 +475,60 @@ class Conv1d(_Conv): @cell_attr_register def __init__(self, + # 输入通道数 in_channels, + # 输出通道数 out_channels, + # 卷积核大小 kernel_size, + # 步长为1 stride=1, + # 填充模式为'same' pad_mode='same', + # 填充数量为0 padding=0, + # 空洞大小为1 dilation=1, + # 分组数为1 group=1, + # 权重初始化方式为False has_bias=False, + # 权重初始化方式'normal' weight_init='normal', - bias_init='zeros'): + # 权重初始化方式'zeros' + bias_init='zeros',): """Initialize Conv1d.""" Validator.check_value_type("kernel_size", kernel_size, [int], self.cls_name) Validator.check_value_type("stride", stride, [int], self.cls_name) Validator.check_value_type("padding", padding, [int], self.cls_name) Validator.check_value_type("dilation", dilation, [int], self.cls_name) Validator.check_int(kernel_size, 1, Rel.GE, 'kernel_size', self.cls_name) - Validator.check_int(stride, 1, Rel.GE, 'stride', self.cls_name) + Validator.check_int(stride, 1, Rel.GE,'stride', self.cls_name) Validator.check_non_negative_int(padding, 'padding', self.cls_name) Validator.check_int(dilation, 1, Rel.GE, 'dilation', self.cls_name) + # 判断kernel_size是否为整数 kernel_size = (1, kernel_size) + # 判断stride是否为整数 stride = (1, stride) + # 判断dilation是否为整数 dilation = (1, dilation) + # 获取形状 get_shape = P.Shape() + # 获取数据类型 get_dtype = P.DType() + # 判断weight_init是否为Tensor类型 if isinstance(weight_init, Tensor): + # 获取weight_init的形状 weight_init_shape = get_shape(weight_init) + # 判断weight_init的形状是否为3维 Validator.check_equal_int(len(weight_init_shape), 3, 'weight_init_shape', self.cls_name) + # 获取weight_init的数据类型 weight_init_dtype = get_dtype(weight_init) + # 获取weight_init的值 weight_init_value = weight_init.asnumpy() + # 将weight_init的值按照2维拉直 weight_init_value = np.expand_dims(weight_init_value, 2) + # 将weight_init转换为Tensor类型 weight_init = Tensor(weight_init_value, weight_init_dtype) super(Conv1d, self).__init__( @@ -458,8 +543,10 @@ class Conv1d(_Conv): has_bias, weight_init, bias_init) + # 定义卷积层参数 self.padding = (0, 0, padding, padding) - Validator.check_string(pad_mode, ['valid', 'same', 'pad'], 'pad_mode', self.cls_name) + Validator.check_string(pad_mode, ['valid','same', 'pad'], 'pad_mode', self.cls_name) + # 定义卷积层 self.conv2d = P.Conv2D(out_channel=self.out_channels, kernel_size=self.kernel_size, mode=1, @@ -468,19 +555,28 @@ class Conv1d(_Conv): stride=self.stride, dilation=self.dilation, group=self.group) + # 定义BiasAdd层 self.bias_add = P.BiasAdd() + # 定义ExpandDims层 self.expand_dims = P.ExpandDims() + # 定义Squeeze层 self.squeeze = P.Squeeze(2) + # 定义Shape层 self.shape = P.Shape() def construct(self, x): x_shape = self.shape(x) + # 检查输入的形状是否为3D _check_input_3d(x_shape, self.cls_name) + # 将输入x展开为3D x = self.expand_dims(x, 2) + # 将x和weight拼接起来 output = self.conv2d(x, self.weight) + # 如果有偏置,则在输出的维度上添加偏置 if self.has_bias: output = self.bias_add(output, self.bias) + # 将输出梯度平滑 output = self.squeeze(output) return output @@ -508,7 +604,8 @@ def _check_input_5dims(input_shape, op_name): if len(input_shape) != 5: raise ValueError(f"For '{op_name}', the dimension of input should be 5d, but got {len(input_shape)}.") - +# 用于实现三维卷积 +# 其中参数与Conv1d和Conv2d中基本一样 class Conv3d(_Conv): r""" 3D convolution layer. @@ -663,10 +760,15 @@ class Conv3d(_Conv): bias_init='zeros', data_format='NCDHW'): """Initialize Conv3d.""" + # 检查kernel_size参数是否为整数或元组并且进行类型转换 kernel_size = _check_3d_int_or_tuple("kernel_size", kernel_size, self.cls_name) + # 检查stride参数是否为整数或元组并且进行类型转换 stride = _check_3d_int_or_tuple("stride", stride, self.cls_name) + # 检查dilation参数是否为整数或元组并且进行类型转换 dilation = _check_3d_int_or_tuple("dilation", dilation, self.cls_name) + # 检查padding参数是否为整数或元组 Validator.check_value_type('padding', padding, (int, tuple), self.cls_name) + # 如果padding的类型是元组,检查padding的长度是否为6 if isinstance(padding, tuple): Validator.check_equal_int(len(padding), 6, 'padding size', self.cls_name) super(Conv3d, self).__init__( @@ -696,16 +798,19 @@ class Conv3d(_Conv): def construct(self, x): x_shape = self.shape(x) + # 检查输入的形状是否为5维 _check_input_5dims(x_shape, self.cls_name) + # 调用conv3d函数,将x转换为3维 output = self.conv3d(x, self.weight) + # 如果有偏置,调用bias_add函数,将偏置和输出添加到输出上 if self.has_bias: output = self.bias_add(output, self.bias) + # 返回输出 return output - def extend_repr(self): s = 'input_channels={}, output_channels={}, kernel_size={}, ' \ - 'stride={}, pad_mode={}, padding={}, dilation={}, ' \ - 'group={}, has_bias={}, ' \ + 'stride={}, pad_mode={}, padding={}, dilation={},'\ + 'group={}, has_bias={},'\ 'weight_init={}, bias_init={}, format={}'.format( self.in_channels, self.out_channels, @@ -721,7 +826,8 @@ class Conv3d(_Conv): self.format) return s - +# 实现三维卷积转置操作 +# 其中参数也和前面一致 class Conv3dTranspose(_Conv): r""" 3D transposed convolution layer. @@ -875,9 +981,12 @@ class Conv3dTranspose(_Conv): kernel_size = _check_3d_int_or_tuple("kernel_size", kernel_size, self.cls_name) stride = _check_3d_int_or_tuple("stride", stride, self.cls_name) dilation = _check_3d_int_or_tuple("dilation", dilation, self.cls_name) + # 检查padding的类型是否为整数或元组 Validator.check_value_type('padding', padding, (int, tuple), self.cls_name) + # 如果padding是元组,检查padding的长度是否等于6 if isinstance(padding, tuple): Validator.check_equal_int(len(padding), 6, 'padding size', self.cls_name) + # 检查output_padding的类型是否为整数或元组,且大于0 self.output_padding = _check_3d_int_or_tuple("output_padding", output_padding, self.cls_name, greater_zero=False) super(Conv3dTranspose, self).__init__( @@ -905,13 +1014,17 @@ class Conv3dTranspose(_Conv): group=self.group, output_padding=self.output_padding, data_format=self.format) + # 定义卷积转换层的变换操作 self.bias_add = P.BiasAdd(data_format=self.format) + # 定义偏置和形状 self.shape = P.Shape() def construct(self, x): x_shape = self.shape(x) _check_input_5dims(x_shape, self.cls_name) + # 调用conv3d_transpose函数,将x转换为3D维度 output = self.conv3d_transpose(x, self.weight) + # 如果有偏置,调用bias_add函数,将转换后的结果加到output上 if self.has_bias: output = self.bias_add(output, self.bias) return output @@ -937,20 +1050,31 @@ class Conv3dTranspose(_Conv): def _deconv_output_length(is_valid, is_same, is_pad, input_length, filter_size, stride_size, dilation_size, padding): """Calculate the width and height of output.""" length = 0 + # 计算滤波器的长度 filter_size = filter_size + (filter_size - 1) * (dilation_size - 1) + # 如果是有效的 if is_valid: + # 如果滤波器的长度大于等于输入长度的步长 if filter_size - stride_size > 0: + # 计算输入长度的步长 length = input_length * stride_size + filter_size - stride_size + # 如果滤波器的长度小于等于输入长度的步长 else: + # 计算输入长度的步长 length = input_length * stride_size + # 如果是填充的 elif is_same: + # 计算输入长度的步长 length = input_length * stride_size + # 如果是填充的 elif is_pad: + # 计算输入长度的步长 length = input_length * stride_size - padding + filter_size - stride_size return length - +# 实现三维卷积转置操作 +# 其中参数也和前面一致 class Conv2dTranspose(_Conv): r""" 2D transposed convolution layer. @@ -1083,7 +1207,9 @@ class Conv2dTranspose(_Conv): kernel_size = twice(kernel_size) stride = twice(stride) dilation = twice(dilation) + # 检查padding的类型是否为int或tuple Validator.check_value_type('padding', padding, (int, tuple), self.cls_name) + # 如果padding的类型是tuple,检查padding的长度是否为4 if isinstance(padding, tuple): Validator.check_equal_int(len(padding), 4, 'padding size', self.cls_name) # out_channels and in_channels swap. @@ -1106,11 +1232,14 @@ class Conv2dTranspose(_Conv): self.in_channels = in_channels self.out_channels = out_channels self.shape = P.Shape() - Validator.check_string(pad_mode, ['valid', 'same', 'pad'], 'pad_mode', self.cls_name) + Validator.check_string(pad_mode, ['valid','same', 'pad'], 'pad_mode', self.cls_name) + # 判断pad_mode是否为valid,same,pad self.is_valid = self.pad_mode == 'valid' - self.is_same = self.pad_mode == 'same' + self.is_same = self.pad_mode =='same' self.is_pad = self.pad_mode == 'pad' + # 判断has_bias是否为布尔值 if Validator.check_bool(has_bias, "has_bias", self.cls_name): + # 初始化偏置参数 self.bias = Parameter(initializer(bias_init, [out_channels]), name='bias') # cause Conv2DTranspose's out_channel refers to Conv2D's out_channel. @@ -1123,6 +1252,7 @@ class Conv2dTranspose(_Conv): dilation=dilation, group=group) self.bias_add = P.BiasAdd() + # 如果padding的类型是int,则将padding设置为padding的值,否则设置为4倍padding值 if isinstance(self.padding, int): self.padding_top, self.padding_bottom, self.padding_left, self.padding_right = (self.padding,) * 4 else: @@ -1134,10 +1264,12 @@ class Conv2dTranspose(_Conv): def construct(self, x): n, _, h, w = self.shape(x) + # 计算输出长度 h_out = _deconv_output_length(self.is_valid, self.is_same, self.is_pad, h, self.kernel_size[0], self.stride[0], self.dilation[0], self.padding_top + self.padding_bottom) w_out = _deconv_output_length(self.is_valid, self.is_same, self.is_pad, w, self.kernel_size[1], self.stride[1], self.dilation[1], self.padding_left + self.padding_right) + # 如果有偏置,则在输出上添加偏置 if self.has_bias: return self.bias_add(self.conv2d_transpose(x, self.weight, (n, self.out_channels, h_out, w_out)), self.bias) @@ -1145,8 +1277,8 @@ class Conv2dTranspose(_Conv): def extend_repr(self): s = 'input_channels={}, output_channels={}, kernel_size={}, ' \ - 'stride={}, pad_mode={}, padding={}, dilation={}, ' \ - 'group={}, has_bias={}, ' \ + 'stride={}, pad_mode={}, padding={}, dilation={},'\ + 'group={}, has_bias={},'\ 'weight_init={}, bias_init={}'.format(self.in_channels, self.out_channels, self.kernel_size, @@ -1160,7 +1292,9 @@ class Conv2dTranspose(_Conv): self.bias_init) return s - +# 实现一维卷积转置操作 +# 其中参数和前面一致 +# 函数中逻辑与过程也一致 class Conv1dTranspose(_Conv): r""" 1D transposed convolution layer. @@ -1264,25 +1398,39 @@ class Conv1dTranspose(_Conv): weight_init='normal', bias_init='zeros'): """Initialize Conv1dTranspose.""" + # 检查kernel_size的类型是否为int型 Validator.check_value_type("kernel_size", kernel_size, [int], self.cls_name) + # 检查stride的类型是否为int型 Validator.check_value_type("stride", stride, [int], self.cls_name) + # 检查padding的类型是否为int型 Validator.check_value_type("padding", padding, [int], self.cls_name) + # 检查dilation的类型是否为int型 Validator.check_value_type("dilation", dilation, [int], self.cls_name) + # 验证kernel_size参数是否为正整数,且大于等于 Validator.check_int(kernel_size, 1, Rel.GE, 'kernel_size', self.cls_name) - Validator.check_int(stride, 1, Rel.GE, 'stride', self.cls_name) + # 验证stride参数是否为正整数,且大于等于 + Validator.check_int(stride, 1, Rel.GE,'stride', self.cls_name) + # 验证padding参数是否为非负整数 Validator.check_non_negative_int(padding, 'padding', self.cls_name) + # 验证dilation参数是否为正整数,且大于等于1 Validator.check_int(dilation, 1, Rel.GE, 'dilation', self.cls_name) kernel_size = (1, kernel_size) stride = (1, stride) dilation = (1, dilation) + # 获取形状和数据类型 get_shape = P.Shape() get_dtype = P.DType() + # 判断权重初始化是否为Tensor if isinstance(weight_init, Tensor): + # 获取权重初始化的形状和数据类型 weight_init_shape = get_shape(weight_init) Validator.check_equal_int(len(weight_init_shape), 3, 'weight_init_shape', self.cls_name) weight_init_dtype = get_dtype(weight_init) + # 获取权重初始化的值 weight_init_value = weight_init.asnumpy() + # 将值按照维度增加2 weight_init_value = np.expand_dims(weight_init_value, 2) + # 将值转换为Tensor weight_init = Tensor(weight_init_value, weight_init_dtype) # out_channels and in_channels swap. # cause Conv2DBackpropInput's out_channel refers to Conv2D's out_channel, @@ -1301,13 +1449,15 @@ class Conv1dTranspose(_Conv): bias_init, transposed=True) self.padding = (0, 0, padding, padding) + # 定义padding,可以是任意数量的值,如果是valid,则padding为0,same,则padding为padding,pad,则padding为(padding, padding) self.in_channels = in_channels self.out_channels = out_channels self.shape = P.Shape() - Validator.check_string(pad_mode, ['valid', 'same', 'pad'], 'pad_mode', self.cls_name) + Validator.check_string(pad_mode, ['valid','same', 'pad'], 'pad_mode', self.cls_name) self.is_valid = self.pad_mode == 'valid' - self.is_same = self.pad_mode == 'same' + self.is_same = self.pad_mode =='same' self.is_pad = self.pad_mode == 'pad' + # 判断has_bias是否为布尔值,如果是,则初始化bias,并设置name if Validator.check_bool(has_bias, "has_bias", self.cls_name): self.bias = Parameter(initializer(bias_init, [out_channels]), name='bias') @@ -1320,8 +1470,11 @@ class Conv1dTranspose(_Conv): stride=stride, dilation=dilation, group=group) + # 定义BiasAdd层 self.bias_add = P.BiasAdd() + # 定义ExpandDims层 self.expand_dims = P.ExpandDims() + # 定义Squeeze层 self.squeeze = P.Squeeze(2) def shard(self, strategy): @@ -1331,21 +1484,26 @@ class Conv1dTranspose(_Conv): def construct(self, x): x_shape = self.shape(x) _check_input_3d(x_shape, self.cls_name) + # 将x的形状和kernel_size和stride和dilation属性转换成一个元组 x = self.expand_dims(x, 2) + # 获取x的形状 n, _, h, w = self.shape(x) + # 计算h_out和w_out h_out = _deconv_output_length(self.is_valid, self.is_same, self.is_pad, h, self.kernel_size[0], self.stride[0], self.dilation[0], self.padding[0] + self.padding[1]) w_out = _deconv_output_length(self.is_valid, self.is_same, self.is_pad, w, self.kernel_size[1], self.stride[1], self.dilation[1], self.padding[2] + self.padding[3]) + # 将x和weight进行卷积转换 output = self.conv2d_transpose(x, self.weight, (n, self.out_channels, h_out, w_out)) + # 如果有偏置,则在转换后的结果中加上偏置 if self.has_bias: output = self.bias_add(output, self.bias) + # 将转换后的结果转换成一个元组 output = self.squeeze(output) return output - def extend_repr(self): s = 'input_channels={}, output_channels={}, kernel_size={}, ' \ 'stride={}, pad_mode={}, padding={}, dilation={}, ' \ diff --git a/mindspore/python/mindspore/nn/layer/embedding.py b/mindspore/python/mindspore/nn/layer/embedding.py index 2750aade333..f367e3574d6 100755 --- a/mindspore/python/mindspore/nn/layer/embedding.py +++ b/mindspore/python/mindspore/nn/layer/embedding.py @@ -13,41 +13,67 @@ # limitations under the License. # ============================================================================ """embedding""" +# 从mindspore.common.dtype模块中导入mstype类,用于表示数据类型 import mindspore.common.dtype as mstype +# 从mindspore库中导入log模块,用于提供日志记录功能 from mindspore import log as logger +# 从mindspore.common.tensor模块中导入Tensor类,用于表示张量 from mindspore.common.tensor import Tensor +# 从mindspore.ops模块中导入operations模块,用于提供基本操作(如矩阵乘法、加法等) from mindspore.ops import operations as P +# 从mindspore.ops.functional模块中导入functional类,用于提供一些基本功能,如条件判断、循环等 from mindspore.ops import functional as F +# 从mindspore.common.parameter模块中导入Parameter类,用于创建一个参数 from mindspore.common.parameter import Parameter +# 从mindspore.common.initializer模块中导入initializer函数,用于初始化参数 from mindspore.common.initializer import initializer +# mindspore.communication.management模块中导入get_group_size和get_rank函数,用于获取集群中组的大小和当前节点的排名 from mindspore.communication.management import get_group_size, get_rank +# 从mindspore.context模块中导入ParallelMode类,用于表示并行模式 from mindspore.context import ParallelMode +# 从mindspore.parallel._utils模块中导入_get_parallel_mode和_get_full_batch函数,用于获取并行模式和全批量大小 from mindspore.parallel._utils import _get_parallel_mode, _get_full_batch +# 从mindspore.parallel._ps_context模块中导入_is_role_worker和_get_ps_context函数 +# 用于获取当前节点是否为工作节点和获取PS上下文 from mindspore.parallel._ps_context import _is_role_worker, _get_ps_context +# 从mindspore.parallel._ps_context模块中导入_insert_hash_table_size、_set_cache_enable和_set_rank_id函数 +# 用于设置哈希表大小、缓存启用和节点排名 from mindspore.parallel._ps_context import _insert_hash_table_size, _set_cache_enable, _set_rank_id +# 从mindspore库中导入context模块,用于获取和设置运行环境 from mindspore import context +# 从mindspore._checkparam模块中导入Rel类,用于表示相对关系 from mindspore._checkparam import Rel +# 从mindspore._checkparam模块中导入Validator类,用于验证参数的范围和类型 from mindspore._checkparam import Validator as validator +# 从mindspore.ops.primitive模块中导入constexpr类,用于定义一个常量 from mindspore.ops.primitive import constexpr +# 从当前模块(..basic)中导入ClipByNorm类,用于裁剪张量范数 from .basic import ClipByNorm +# 从当前模块(..math)中导入Range类,用于创建一个范围 from .math import Range +# 从当前模块(..cell)中导入Cell类,用于创建自定义Cell from ..cell import Cell __all__ = ['Embedding', 'EmbeddingLookup', 'MultiFieldEmbeddingLookup'] @constexpr +# 检查模型输入参数是否符合预期 def _check_input_2d(input_shape, param_name, func_name): + # 如果输入形状不是二维 if len(input_shape) != 2: + # 则抛出ValueError异常 raise ValueError(f"For '{func_name}', the dimension of '{param_name}' should be 2d, but got {len(input_shape)}") return True - @constexpr +# 检查输入数据类型是否在允许的类型列表中 def _check_input_dtype(input_dtype, param_name, allow_dtypes, cls_name): validator.check_type_name(param_name, input_dtype, allow_dtypes, cls_name) - +# Embedding嵌入层 +# 用于将连续输入数据转换为嵌入向量 +# 将输入数据转换为一个固定大小的嵌入向量,其中每个嵌入向量表示输入数据中的一个特定特征 class Embedding(Cell): r""" A simple lookup table that stores embeddings of a fixed dictionary and size. @@ -97,6 +123,15 @@ class Embedding(Cell): def __init__(self, vocab_size, embedding_size, use_one_hot=False, embedding_table='normal', dtype=mstype.float32, padding_idx=None): + # 主要参数 + # vocab_size (int): 嵌入向量的数量 + # embedding_size (int): 嵌入向量的维度 + # use_one_hot (bool): 是否使用独热编码,默认为False + # embedding_table (str, optional): 嵌入向量的初始化方式,默认为'normal' + # dtype (:class:`mindspore.dtype`, optional): 数据类型,默认为`mstype.float32 + # padding_idx (int, optional):在输入数据中填充的索引,默认为None。 + + """Initialize Embedding.""" super(Embedding, self).__init__() self.vocab_size = validator.check_value_type('vocab_size', vocab_size, [int], self.cls_name) @@ -105,54 +140,81 @@ class Embedding(Cell): validator.check_subclass("dtype", dtype, mstype.number_type, self.cls_name) self.use_one_hot = use_one_hot self.dtype = dtype + # 初始化embedding_table self.init_tensor = initializer(embedding_table, [vocab_size, embedding_size]) + # 如果padding_idx不为空,则将padding_idx转换为整数 self.padding_idx = padding_idx if padding_idx is not None: self.padding_idx = validator.check_int_range(padding_idx, 0, vocab_size, Rel.INC_BOTH, "padding_idx", self.cls_name) + # 如果init_tensor是Tensor类型,则将其转换为numpy数组 if isinstance(self.init_tensor, Tensor) and self.init_tensor.init is not None: self.init_tensor = self.init_tensor.init_data() + # 将padding_idx转换为numpy数组 self.init_tensor = self.init_tensor.asnumpy() + # 将padding_idx中的元素设置为0 self.init_tensor[self.padding_idx] = 0 + # 将转换后的numpy数组转换为Tensor类型 self.init_tensor = Tensor(self.init_tensor) self.embedding_table = Parameter(self.init_tensor, name='embedding_table') + # 初始化embedding_table参数 self.expand = P.ExpandDims() + # 将embedding_table参数放大一倍 self.reshape_flat = P.Reshape() + # 将embedding_table参数变形为一维 self.shp_flat = (-1,) + # 获取embedding_table参数的形状 self.gather = P.Gather() + # 从embedding_table参数中抽取元素 self.one_hot = P.OneHot() + # 将embedding_table参数转换为one_hot self.on_value = Tensor(1.0, self.dtype) + # 将embedding_table参数的值设置为1.0 self.off_value = Tensor(0.0, self.dtype) + # 将embedding_table参数的值设置为0.0 self.array_mul = P.MatMul() + # 使用矩阵乘法 self.reshape = P.Reshape() + # 将embedding_table参数变形为一维 self.get_shp = P.Shape() def construct(self, ids): extended_ids = self.expand(ids, -1) out_shape = self.get_shp(ids) + (self.embedding_size,) + # 将extended_ids转换为矩阵 flat_ids = self.reshape_flat(extended_ids, self.shp_flat) + # 如果使用one_hot if self.use_one_hot: + # 将flat_ids转换为one_hot矩阵 one_hot_ids = self.one_hot(flat_ids, self.vocab_size, self.on_value, self.off_value) + # 将one_hot矩阵乘以embedding_table output_for_reshape = self.array_mul(one_hot_ids, self.embedding_table) else: + # 将embedding_table中的flat_ids按照索引获取 output_for_reshape = self.gather(self.embedding_table, flat_ids, 0) + # 将reshape后的output_for_reshape转换为矩阵 output = self.reshape(output_for_reshape, out_shape) return output def extend_repr(self): + # 返回一个字符串,用于表示给定的参数 s = 'vocab_size={}, embedding_size={}, use_one_hot={}, embedding_table={}, dtype={}, padding_idx={}'.format( self.vocab_size, self.embedding_size, self.use_one_hot, self.embedding_table, self.dtype, self.padding_idx) return s @constexpr + +# 生成一个从start到end的范围 def _make_axis_range(start, end): axis = tuple(range(start, end)) return axis - +# 嵌入查找层 +# 参数与Embedding相同 +# 除了Embedding中的一部分功能,在EmbeddingLookup中还可以用于将图像数据的整数张量转换为嵌入向量,以便于图像分类 class EmbeddingLookup(Cell): r""" Returns a slice of the input tensor based on the specified indices. @@ -230,152 +292,243 @@ class EmbeddingLookup(Cell): self.vocab_cache_size = validator.check_non_negative_int(vocab_cache_size, 'vocab_cache_size') self.target = target self.sparse = sparse + # 检查target参数是否为CPU或DEVICE self.cache_enable = self.vocab_cache_size > 0 + # 检查vocab_cache_size参数是否大于0 + validator.check_string(target, ['CPU', 'DEVICE'], 'target', self.cls_name) + # 检查target参数是否为CPU或DEVICE self.forward_unique = False + # 如果vocab_cache_size大于0,则target参数为CPU或DEVICE,否则target参数为DEVICE validator.check_string(target, ['CPU', 'DEVICE'], 'target', self.cls_name) if not sparse and target == 'CPU': - raise ValueError(f"For '{self.cls_name}', 'sparse' must be True when 'target' is \"CPU\", " - f"but got 'sparse': {sparse} and 'target': {target}") + raise ValueError(f"For '{self.cls_name}','sparse' must be True when 'target' is \"CPU\", " + f"but got'sparse': {sparse} and 'target': {target}") if sparse: + # 如果sparse为True,则使用SparseGatherV2 self.gatherv2 = P.SparseGatherV2() else: + # 否则使用Gather self.gatherv2 = P.Gather() + # 将EmbeddingLookup添加primitive_target属性,值为CPU self.embeddinglookup = P.EmbeddingLookup().add_prim_attr('primitive_target', 'CPU') + # 获取enable_ps的上下文 enable_ps = _get_ps_context("enable_ps") if enable_ps: + # 如果enable_ps为True,则处理词汇表缓存 self._process_vocab_cache(slice_mode) self.embedding_size = validator.check_positive_int(embedding_size, 'embedding_size', self.cls_name) + # 创建embedding_table参数,初始化为初始化参数初始值,名字为embedding_table self.embedding_table = Parameter(initializer(param_init, [self.vocab_size, self.embedding_size]), name='embedding_table') + # 获取并行模式 parallel_mode = _get_parallel_mode() + # 是否自动平行 is_auto_parallel = parallel_mode in (ParallelMode.SEMI_AUTO_PARALLEL, ParallelMode.AUTO_PARALLEL) + # 获取gather_revert函数 self.gather_revert = P.Gather() + # 获取reshape函数 self.reshape_first = P.Reshape() + # 获取reshape函数 self.reshape = P.Reshape() + # 获取unique函数 self.unique = P.Unique() + # 获取shape函数 self.shape = P.Shape() + # 如果自动平行,则设置unique函数的参数cache_enable为True if is_auto_parallel: self.unique = P.Unique().shard(((1,),)) + # 如果缓存启用,则设置_set_voacb_cache_enable_for_ps函数 if self.cache_enable and enable_ps: self._set_voacb_cache_enable_for_ps(vocab_cache_size, embedding_size, vocab_size) + # 如果自动平行,则设置unique函数的参数cache_enable为True if is_auto_parallel: self.unique.add_prim_attr('cache_enable', True) + # 维度大小为2 indices_shape_size = 2 + # 检查'manual_shapes'是否不为None,当"field_mode"为"field_slice" if slice_mode == "field_slice" and is_auto_parallel: if not manual_shapes: + # 抛出一个异常 raise ValueError(f"For '{self.cls_name}', the 'manual_shapes' should not be none " f"when the 'slice_mode' is \"filed_slice\", but got {manual_shapes}.") + # 检查 'manual_shapes' 是否是一个元组且每个维度都是一个正整数 if not isinstance(manual_shapes, tuple): + # 抛出一个异常 raise TypeError(f"For '{self.cls_name}', the type of 'manual_shapes' must be tuple(int), " f"but got {type(manual_shapes).__name__}!") + # 遍历 'manual_shapes' 并检查每个维度是否是一个正整数 for dim in manual_shapes: validator.check_positive_int(dim, 'manual shape dim', self.cls_name) self.gatherv2.add_prim_attr("manual_split", manual_shapes) self.embeddinglookup.add_prim_attr("manual_split", manual_shapes) + # 将manual_shapes添加到gatherv2和embeddinglookup中 self.gatherv2.shard(((get_group_size(), 1), (1, get_group_size()))) self.embeddinglookup.shard(((get_group_size(), 1), (1, get_group_size()))) elif slice_mode == "table_row_slice" and is_auto_parallel: + # 获取全部批次 full_batch = _get_full_batch() + # 如果target为DEVICE,且不是全部批次,或者cache_enable为True,且sparse为True if (target == 'DEVICE' and not full_batch) or (self.cache_enable and enable_ps and sparse): + # 设置indices_shape_size为1 indices_shape_size = 1 + # 调用gather_revert.shard方法 self.gather_revert.shard(((1, 1), (get_group_size(),))) + # 设置forward_unique为True self.forward_unique = True + # 设置indices_strategy为(1,)*indices_shape_size indices_strategy = (1,)*indices_shape_size + # 调用gatherv2.shard方法 self.gatherv2.shard(((get_group_size(), 1), indices_strategy)) + # 调用embeddinglookup.shard方法 self.embeddinglookup.shard(((get_group_size(), 1), indices_strategy)) elif slice_mode == "table_column_slice" and is_auto_parallel: + # 如果target为DEVICE,则获取indices_shape_size if target == 'DEVICE': indices_shape_size = 1 + # 将shard操作设置为True self.gather_revert.shard(((1, get_group_size()), (1,))) + # 将forward_unique设置为True self.forward_unique = True indices_strategy = (1,)*indices_shape_size + # 分配给gatherv2的维度 self.gatherv2.shard(((1, get_group_size()), indices_strategy)) + # 分配给embeddinglookup的维度 self.embeddinglookup.shard(((1, get_group_size()), indices_strategy)) + # 如果slice_mode为batch_slice,且is_auto_parallel为True elif slice_mode == "batch_slice" and is_auto_parallel: + # 获取每个维度的分区大小 indices_strategy = [get_group_size()] indices_strategy.extend([1]*(indices_shape_size - 1)) indices_strategy = tuple(indices_strategy) + # 将gatherv2分区大小设置为(1,1),以及每个维度的维度大小 self.gatherv2.shard(((1, 1), indices_strategy)) + # 将embeddinglookup分区大小设置为(1,1),以及每个维度的维度大小 self.embeddinglookup.shard(((1, 1), indices_strategy)) else: + # 如果是自动并行,则支持模式为['field_slice', 'table_row_slice', 'table_column_slice', 'batch_slice'] if is_auto_parallel: support_mode = ["field_slice", "table_row_slice", "table_column_slice", "batch_slice"] - raise ValueError("For '{}', the 'slice_mode' must be in {}, " + # 抛出异常 + raise ValueError("For '{}', the'slice_mode' must be in {}, " "but got \"{}\".".format(self.cls_name, support_mode, slice_mode)) if self.cache_enable and not enable_ps: + # 如果缓存模式为True,并且不支持ps模式 raise ValueError(f"For '{self.cls_name}', haven't supported cache enable for not ps mode.") self.embedding_table.unique = self.forward_unique + # 设置最大范数 self.max_norm = max_norm + # 如果最大范数不为None,则检查最大范数是否为正数 if self.max_norm is not None: - self.max_norm = validator.check_positive_float(self.max_norm, 'max_norm', self.cls_name) + # 检查最大范数是否为正数 + self.max_norm = validator.check_positive_float(self.max_norm,'max_norm', self.cls_name) + # 将最大范数转换为Tensor类型 self.max_norm = Tensor(self.max_norm, dtype=mstype.float32) - + # 用于处理词汇缓存 def _process_vocab_cache(self, slice_mode): """PS embeddingLookup cache check and process.""" self.cache_enable = False + # 如果self.vocab_cache_size > 0 if self.vocab_cache_size > 0: + # 如果目标为CPU if self.target == 'CPU': + # 则发出警告,设置logger的warning信息 logger.warning("The configuration of 'vocab_cache_size' is valid only in 'DEVICE' target, " "current target is CPU, so it will be ignored.") + # 如果vocab_cache_size大于0,则设置enable_ps为False + enable_ps = _get_ps_context("enable_ps") + if not enable_ps: + # 如果enable_ps为False,则设置logger的warning信息 + logger.warning("The configuration of 'vocab_cache_size' is valid only in parameter server training " + "mode, current mode is not parameter server trainning mode, so it will be ignored.") return enable_ps = _get_ps_context("enable_ps") + # 如果enable_ps为False,则跳过 if not enable_ps: logger.warning("The configuration of 'vocab_cache_size' is valid only in parameter server training " "mode, current mode is not parameter server trainning mode, so it will be ignored.") return + # 获取并行模式 parallel_mode = _get_parallel_mode() + # 判断并行模式是否为自动平行 is_auto_parallel = parallel_mode in (ParallelMode.SEMI_AUTO_PARALLEL, ParallelMode.AUTO_PARALLEL) if is_auto_parallel: + # 获取组的大小 rank_size = get_group_size() + # 获取当前进程的编号 rank_id = get_rank() + # 获取全部的批次 full_batch = _get_full_batch() + # 如果组的大小大于1且不是全部批次且slice_mode为行切片 if rank_size > 1 and not (full_batch and slice_mode == "table_row_slice"): + # 抛出错误 raise ValueError(f"For '{self.cls_name}', the cache of parameter server parallel should only be " f"used in \"full_batch\" and the value of \"full_batch\" should be True. " - f"Meanwhile, the value of 'slice_mode' should be \"table_row_slice\"." - f"But got full_batch: {full_batch} and 'slice_mode': \"{slice_mode}\".") + f"Meanwhile, the value of'slice_mode' should be \"table_row_slice\"." + f"But got full_batch: {full_batch} and'slice_mode': \"{slice_mode}\".") + # 设置编号 self.vocab_cache_size = self.vocab_cache_size * rank_size + # 设置rank_id _set_rank_id(rank_id) + # 设置缓存开关 self.cache_enable = True if _is_role_worker(): + # 如果是工作节点,则将vocab_size设置为vocab_cache_size self.vocab_size = self.vocab_cache_size - if context.get_context("enable_sparse") != self.sparse: - raise ValueError(f"For '{self.cls_name}', the value of parameter 'sparse' must be same for all " + # 如果context中的enable_sparse不等于self.sparse + if context.get_context("enable_sparse")!= self.sparse: + # 抛出异常 + raise ValueError(f"For '{self.cls_name}', the value of parameter'sparse' must be same for all " f"kernels and equal the value of 'enable_sparse' in context setting in " - f"parameter server cache mode, but got value of parameter 'sparse': {self.sparse}" + f"parameter server cache mode, but got value of parameter'sparse': {self.sparse}" f" and the 'enable_sparse' in context setting: " f"{context.get_context('enable_sparse')}.") - + # 在PS下设置词汇缓存的相关配置 def _set_voacb_cache_enable_for_ps(self, vocab_cache_size, embedding_size, vocab_size): """PS embeddingLookup cache enable set.""" + # 设置self.embedding_table.cache_enable为True self.embedding_table.cache_enable = True + # 设置self.embedding_table.is_param_ps为True self.embedding_table.is_param_ps = True _set_cache_enable(True) if self.sparse: + # 如果sparse为True,则设置forward_unique为True self.forward_unique = True + # 如果_is_role_worker()返回True if _is_role_worker(): + # 插入hash表大小 _insert_hash_table_size(self.embedding_table.name, vocab_cache_size, embedding_size, vocab_size) - + # 根据给定的索引张量构建输出张量 def construct(self, indices): if self.target == "CPU": + # 如果target为CPU,则将indices转换为embedding_table的维度,并计算embedding_lookup out = self.embeddinglookup(self.embedding_table, indices, 0) else: + # 如果target为GPU,则将indices转换为embedding_table的维度,并计算embedding_lookup if self.forward_unique: + # 如果forward_unique为True,则将indices转换为shape+embedding_size的维度,并计算unique shp = self.shape(indices) + (self.embedding_size,) indices_flatten = self.reshape_first(indices, (-1,)) unique_id, unique_idx = self.unique(indices_flatten) + # 将unique_id和unique_idx转换为embedding_table的维度,并计算weight_unique weight_unique = self.gatherv2(self.embedding_table, unique_id, 0) + # 将weight_unique和unique_idx转换为shape的维度,并计算weight_flatten weight_flatten = self.gather_revert(weight_unique, unique_idx, 0) + # 将weight_flatten和shp转换为out的维度 out = self.reshape(weight_flatten, shp) else: + # 如果forward_unique为False,则将indices转换为embedding_table的维度,并计算out out = self.gatherv2(self.embedding_table, indices, 0) + # 如果max_norm不为None,则将out的维度转换为axis if self.max_norm is not None: axis = _make_axis_range(F.rank(indices), F.rank(out)) + # 将out的维度转换为clip_by_norm的维度 clip_by_norm = ClipByNorm(axis) + # 将out转换为clip_by_norm的维度,并计算out out = clip_by_norm(out, self.max_norm) + # 返回out return out - +# 根据指定的索引和字段ID,返回输入Tensor的切片 class MultiFieldEmbeddingLookup(EmbeddingLookup): r""" Returns a slice of input tensor based on the specified indices and the field ids. This operation @@ -453,6 +606,17 @@ class MultiFieldEmbeddingLookup(EmbeddingLookup): def __init__(self, vocab_size, embedding_size, field_size, param_init='normal', target='CPU', slice_mode='batch_slice', feature_num_list=None, max_norm=None, sparse=True, operator='SUM'): + # 参数 + # vocab_size(int): 词汇表大小 + # embedding_size(int): 嵌入维度 + # field_size(int): 字段数量。 + # param_init(str): 参数初始化方法,默认为 'normal'。 + # target(str): 计算目标,默认为 'CPU'。 + # slice_mode(str): 切片模式,默认为 'batch_slice'。 + # feature_num_list(list): 字段中特征的数量列表,用于指定每个字段的特征数量。 + # max_norm(float): 最大归一化值,用于对输出张量进行归一化。 + # sparse(bool): 是否为稀疏张量,默认为 True。 + # operator(str): 聚合操作,默认为 'SUM'。 """Initialize MultiFieldEmbeddingLookup.""" super(MultiFieldEmbeddingLookup, self).__init__(vocab_size, embedding_size, param_init, target, slice_mode, feature_num_list, max_norm, sparse) @@ -475,111 +639,177 @@ class MultiFieldEmbeddingLookup(EmbeddingLookup): self.max_no_equal = P.NotEqual() validator.check_string(operator, ['SUM', 'MAX', 'MEAN'], 'operator', self.cls_name) + # 如果operator的值为SUM,则将merge_op设置为UnsortedSegmentSum() if operator == MultiFieldEmbeddingLookup.OPERATOR_SUM: self.merge_op = P.UnsortedSegmentSum() + # 如果operator的值为MAX,则将merge_op设置为UnsortedSegmentMax() elif operator == MultiFieldEmbeddingLookup.OPERATOR_MAX: self.merge_op = P.UnsortedSegmentMax() + # 否则,将merge_op设置为UnsortedSegmentSum() else: self.merge_op = P.UnsortedSegmentSum() - - parallel_mode = _get_parallel_mode() + # 判断是否为自动并行 is_auto_parallel = parallel_mode in (ParallelMode.SEMI_AUTO_PARALLEL, ParallelMode.AUTO_PARALLEL) + # 判断是否为行切片模式 if slice_mode in ["table_row_slice", "batch_slice"] and is_auto_parallel: + # 将shard操作添加到merge_op中 self.merge_op.shard(((get_group_size(), 1, 1), (get_group_size(), 1))) + # 将shard操作添加到expand中 self.expand.shard(((get_group_size(),),)) self.bias_add.shard(((1, 1), (1, 1))) + # 将bias_add按照分区大小分割 self.mul.shard(((get_group_size(), 1, 1), (get_group_size(), 1, 1))) + # 将mul按照分区大小分割 self.count_op.shard(((get_group_size(), 1), (get_group_size(), 1))) + # 将count_op按照分区大小分割 self.add.shard(((get_group_size(),), (get_group_size(),))) + # 将add按照分区大小分割 self.div_no_nan.shard(((get_group_size(), 1), (get_group_size(), 1))) + # 将div_no_nan按照分区大小分割 self.max_mask_mul.shard(((get_group_size(), 1), (get_group_size(), 1))) + # 将max_mask_mul按照分区大小分割 self.max_no_equal.shard(((1,), ())) + # 如果操作为MultiFieldEmbeddingLookup.OPERATOR_MAX if operator == MultiFieldEmbeddingLookup.OPERATOR_MAX: + # 将shard操作添加到equal和inf_mask_mul中 self.equal.shard(((get_group_size(), 1, 1), ())) self.inf_mask_mul.shard(((get_group_size(), 1, 1), ())) + # 将shard操作添加到merge_op中 self.merge_op.shard(((get_group_size(), 1), (get_group_size(),))) + # 将shard操作添加到count_op中 self.count_op.shard(((get_group_size(),), (get_group_size(),))) + # 将shard操作添加到inf_add中 self.inf_add.shard(((get_group_size(), 1, 1), (get_group_size(), 1, 1))) elif slice_mode == "table_column_slice" and is_auto_parallel: self.merge_op.shard(((1, 1, get_group_size()), (1, 1))) + # 将div_no_nan的shard参数设置为(1, get_group_size()) self.div_no_nan.shard(((1, get_group_size()), (1, 1))) + # 将bias_add的shard参数设置为(1, 1) self.bias_add.shard(((1, 1), (1, 1))) + # 将mul的shard参数设置为(1, 1, get_group_size()) self.mul.shard(((1, 1, 1), (1, 1, get_group_size()))) + # 将count_op的shard参数设置为(1, 1) self.count_op.shard(((1, 1), (1, 1))) + # 将add的shard参数设置为(1,) self.add.shard(((1,), (1,))) + # 将max_mask_mul的shard参数设置为(1, get_group_size()) self.max_mask_mul.shard(((1, get_group_size()), (1, 1))) + # 将expand的shard参数设置为(1,) self.expand.shard(((1,),)) + # 将max_no_equal的shard参数设置为(1,) self.max_no_equal.shard(((1,), ())) if operator == MultiFieldEmbeddingLookup.OPERATOR_MAX: + # 将equal的shard参数设置为(1, 1, 1) self.equal.shard(((1, 1, 1), ())) + # 将inf_mask_mul的shard参数设置为(1, 1, 1) self.inf_mask_mul.shard(((1, 1, 1), ())) + # 将merge_op的shard参数设置为(1, get_group_size()) self.merge_op.shard(((1, get_group_size()), (1,))) + # 将count_op的shard参数设置为(1,) self.count_op.shard(((1,), (1,))) + # 将inf_add的shard参数设置为(1, 1, get_group_size()) self.inf_add.shard(((1, 1, get_group_size()), (1, 1, 1))) else: if is_auto_parallel: + # 抛出异常 raise ValueError("For '{}', the 'slice_mode' should be in ['table_row_slice', 'batch_slice' and \ 'table_column_slice'], but got {}".format(self.cls_name, str(slice_mode))) # Min value for fp32 self.negative_inf_value = -3.402823466E+38 - + # 构建输出张量 def construct(self, input_indices, input_values, field_ids): - + # 检查输入input_indices是否为2d _check_input_2d(F.shape(input_indices), "input_indices", self.cls_name) + # 检查输入input_values是否为2d _check_input_2d(F.shape(input_values), "input_values", self.cls_name) + # 检查输入field_ids是否为2d _check_input_2d(F.shape(field_ids), "field_ids", self.cls_name) + # 检查输入input_indices是否符合允许类型 _check_input_dtype(F.dtype(input_indices), "input_indices", [mstype.int32, mstype.int64], self.cls_name) + # 检查输入input_values是否符合允许类型 _check_input_dtype(F.dtype(input_values), "input_values", [mstype.float32], self.cls_name) + # 检查输入field_ids是否符合允许类型 _check_input_dtype(F.dtype(field_ids), "field_ids", [mstype.int32], self.cls_name) + # 定义一个函数,计算输入的field_ids和bias batch_size = self.shape(input_indices)[0] + # 计算输入索引的数量 num_segments = batch_size * self.field_size + # 计算bias bias = Range(0, num_segments, self.field_size)() + # 将bias变换为batch_size*num_segments的形状 bias = self.reshape(bias, (batch_size, -1)) + # 将field_ids和bias合并 field_ids = self.bias_add(field_ids, bias) - + # 如果目标是CPU if self.target == "CPU": + # 调用embeddinglookup函数 out = self.embeddinglookup(self.embedding_table, input_indices, 0) else: + # 如果目标是GPU,则调用forward_unique参数为True时,调用unique函数,获取unique_id和unique_idx if self.forward_unique: + # 计算输入索引形状,并扩展为包含嵌入维数的形状 shp = self.shape(input_indices) + (self.embedding_size,) + # 将输入索引展开成一位数组 indices_flatten = self.reshape(input_indices, (-1,)) + # 调用unique函数,获取unique_id和unique_idx unique_id, unique_idx = self.unique(indices_flatten) + # 调用gatherv2函数,获取embedding_table中unique_id对应的embedding weight_unique = self.gatherv2(self.embedding_table, unique_id, 0) + # 调用gather函数,获取embedding_table中unique_idx对应的embedding weight_flatten = self.gather_revert(weight_unique, unique_idx, 0) out = self.reshape(weight_flatten, shp) else: + # 否则,调用gatherv2函数 out = self.gatherv2(self.embedding_table, input_indices, 0) + # 如果max_norm不为None,则调用ClipByNorm函数进行裁剪,设置axis和clip_by_norm参数 if self.max_norm is not None: + # 计算裁剪轴的范围 axis = _make_axis_range(F.rank(input_indices), F.rank(out)) + # 创建用于输出裁剪的对象 clip_by_norm = ClipByNorm(axis) + # 对输出裁剪 out = clip_by_norm(out, self.max_norm) - + # 将输入值reshape成一个(input_indices)(索引形状)[1], 1)的矩阵 weights = self.reshape(input_values, (batch_size, self.shape(input_indices)[1], 1)) + # 将输入值与输出值相乘 embedding = self.mul(weights, out) - + # 检查self.operator是否为'MAX',如果为'MAX' if self.operator == 'MAX': # Fill the padding value to -inf, so the padded value will not influence the results + # 创建一个码,在权重为0的位置填充-inf negative_inf_mask = self.cast(self.equal(weights, 0), mstype.float32) + #用上行创建的码与-inf相乘,得到一个新的码 inf_mask = self.inf_mask_mul(negative_inf_mask, self.negative_inf_value) + # 相加 embedding = self.inf_add(embedding, inf_mask) + # reshape为一个(-1, self.embedding_size)的矩阵 embedding = self.reshape(embedding, (-1, self.embedding_size)) + # 将field_ids(字段)reshape field_ids = self.reshape(field_ids, (-1,)) merged_vectors = self.merge_op(embedding, field_ids, num_segments) - + # 如果operator为'MAX' if self.operator == 'MAX': + # 使用count_op函数计算字段ID中每个值的计数 value_count = self.count_op(self.abs(self.reshape(input_values, (-1,))), field_ids, num_segments) + # 创建一个元素值全为0的数组 value_zeros = self.cast(self.max_no_equal(value_count, 0.0), mstype.float32) + # 用extend进行全零数组扩充,成为与出入向量形状相同的数组 count = self.expand(value_zeros, -1) + # 进行乘法运算 merged_vectors = self.max_mask_mul(merged_vectors, count) - + + # 如果operator为'MEAN' if self.operator == 'MEAN': + # 使用count_op函数计算字段ID中每个值的计数 value_count = self.count_op(self.abs(input_values), field_ids, num_segments) + # 用extend进行扩充,成为与出入向量形状相同的数组 value_count = self.expand(value_count, -1) + # 使用div_no_nan进行运算 merged_vectors = self.div_no_nan(merged_vectors, value_count) - + # 将函数reshape为(batch_size, self.field_size, -1)的矩阵 merged_vectors = self.reshape(merged_vectors, (batch_size, self.field_size, -1)) return merged_vectors diff --git a/mindspore/python/mindspore/nn/layer/image.py b/mindspore/python/mindspore/nn/layer/image.py index 8b39c044212..33547f6c690 100644 --- a/mindspore/python/mindspore/nn/layer/image.py +++ b/mindspore/python/mindspore/nn/layer/image.py @@ -13,23 +13,36 @@ # limitations under the License. # ============================================================================ """image""" +# 导入numbers库,用于处理整数和浮点数 import numbers +# 导入numpy库,用于处理数值计算 import numpy as np +# 从mindspore.common.dtype模块中导入mstype类,用于表示数据类型 import mindspore.common.dtype as mstype +# 从mindspore.common.tensor模块中导入Tensor类,用于表示张量 from mindspore.common.tensor import Tensor +# 从mindspore.ops模块中导入operations模块,用于提供基本操作(如矩阵乘法、加法等) from mindspore.ops import operations as P +# 从mindspore.ops.functional模块中导入functional类,用于提供一些基本功能,如条件判断、循环等 from mindspore.ops import functional as F +# 从mindspore.ops.primitive模块中导入constexpr类,用于定义一个常量 from mindspore.ops.primitive import constexpr +# 从mindspore._checkparam模块中导入Rel类(用于表示相对关系)和Validator类(用于验证参数的范围和类型) from mindspore._checkparam import Rel, Validator as validator +# 从当前模块(..conv)中导入Conv2d类,用于创建卷积层 from .conv import Conv2d +# 从当前模块(..container)中导入CellList类,用于创建Cell列表 from .container import CellList +# 从当前模块(..pooling)中导入AvgPool2d类,用于创建平均池化层 from .pooling import AvgPool2d +# 从当前模块(..activation)中导入ReLU类,用于创建ReLU激活函数 from .activation import ReLU +# 从当前模块(..cell)中导入Cell类,用于创建自定义Cell from ..cell import Cell __all__ = ['ImageGradients', 'SSIM', 'MSSSIM', 'PSNR', 'CentralCrop'] - +# 用于计算图像梯度 class ImageGradients(Cell): r""" Returns two tensors, the first is along the height dimension and the second is along the width dimension. @@ -68,137 +81,203 @@ class ImageGradients(Cell): [[[[1, 0], [1, 0]]]])) """ + # 计算图像梯度 def __init__(self): super(ImageGradients, self).__init__() def construct(self, images): + # 检查如数张量形状是否为四维 check = _check_input_4d(F.shape(images), "images", self.cls_name) + # 用depend函数将检查结果添加到计算图中 images = F.depend(images, check) + # 获取images的形状 batch_size, depth, height, width = P.Shape()(images) + # 如果images的高度为1,则将dy设置为0 if height == 1: dy = P.Fill()(P.DType()(images), (batch_size, depth, 1, width), 0) + # 否则,将dy的第二个维度设置为images的第一个维度的值 else: dy = images[:, :, 1:, :] - images[:, :, :height - 1, :] dy_last = P.Fill()(P.DType()(images), (batch_size, depth, 1, width), 0) dy = P.Concat(2)((dy, dy_last)) + # 如果images的宽度为1,则将dx设置为0 if width == 1: dx = P.Fill()(P.DType()(images), (batch_size, depth, height, 1), 0) + # 否则,将dx的第三个维度设置为images的第一个维度的值 else: dx = images[:, :, :, 1:] - images[:, :, :, :width - 1] dx_last = P.Fill()(P.DType()(images), (batch_size, depth, height, 1), 0) dx = P.Concat(3)((dx, dx_last)) + # 返回dy和dx return dy, dx - +# 将图像类型转换为float32,并且将最大值归一化到[0, 1]范围 def _convert_img_dtype_to_float32(img, max_val): """convert img dtype to float32""" # Usually max_val is 1.0 or 255, we will do the scaling if max_val > 1. # We will scale img pixel value if max_val > 1. and just cast otherwise. + # 将图像类型转换为float32 ret = F.cast(img, mstype.float32) + # 计算最大值 max_val = F.scalar_cast(max_val, mstype.float32) + # 如果大于1则 if max_val > 1.: + # 计算比例 scale = 1. / max_val ret = ret * scale return ret @constexpr +# 获取数据中最大值 def _get_dtype_max(dtype): """get max of the dtype""" + # 将数据类型转换为numpy数据类型 np_type = mstype.dtype_to_nptype(dtype) + # 检查数据类型是否为整数类型,如果是整数类型 if issubclass(np_type, numbers.Integral): + # 获取最大值 dtype_max = np.float64(np.iinfo(np_type).max) + # 如果不是整型 else: + # 把最大值赋为1.0 dtype_max = 1.0 return dtype_max @constexpr +# 检查输入的图像是否为四维 def _check_input_4d(input_shape, param_name, func_name): + # 如果输入的图像不是四维 if len(input_shape) != 4: + # 抛出异常 raise ValueError(f"For '{func_name}', the dimension of '{param_name}' should be 4d, " f"but got {len(input_shape)}.") return True @constexpr +# 检查输入的图像尺寸、名称,滤波器尺寸和函数名称 def _check_input_filter_size(input_shape, param_name, filter_size, func_name): + # 检查输入的图像是否为四维 _check_input_4d(input_shape, param_name, func_name) + # 检查参数shape,size validator.check(param_name + " shape[2]", input_shape[2], "filter_size", filter_size, Rel.GE, func_name) + # 检查参数shape,size validator.check(param_name + " shape[3]", input_shape[3], "filter_size", filter_size, Rel.GE, func_name) @constexpr +# 检查输入数据类型 def _check_input_dtype(input_dtype, param_name, allow_dtypes, cls_name): validator.check_type_name(param_name, input_dtype, allow_dtypes, cls_name) - +# 用于实现两个特征图片的二维卷积操作 +# 输入输出通道、卷积核尺寸、权重、填充大小和填充模式 def _conv2d(in_channels, out_channels, kernel_size, weight, stride=1, padding=0): + # 返回处理完的图像 return Conv2d(in_channels, out_channels, kernel_size=kernel_size, stride=stride, weight_init=weight, padding=padding, pad_mode="valid") - +# 创建用于输入参数的高斯窗口 def _create_window(size, sigma): x_data, y_data = np.mgrid[-size // 2 + 1:size // 2 + 1, -size // 2 + 1:size // 2 + 1] + # 将x_data和y_data拉直,并且添加一个维度 x_data = np.expand_dims(x_data, axis=-1).astype(np.float32) x_data = np.expand_dims(x_data, axis=-1) ** 2 y_data = np.expand_dims(y_data, axis=-1).astype(np.float32) y_data = np.expand_dims(y_data, axis=-1) ** 2 + # 计算sigma的值 sigma = 2 * sigma ** 2 + # 计算g的值 g = np.exp(-(x_data + y_data) / sigma) + # 返回g的值,并且排序,以便于计算 return np.transpose(g / np.sum(g), (2, 3, 0, 1)) - +# 将输入图像按给定分块尺寸进行分块 def _split_img(x): + # 获取输入张量的通道数 _, c, _, _ = F.shape(x) + # 创建P.Split操作符 img_split = P.Split(1, c) + # 输入x output = img_split(x) + # 返回output和c return output, c - +# 用卷积神经网络计算输入的两张图片img1和img2之间的损失 def _compute_per_channel_loss(c1, c2, img1, img2, conv): """computes ssim index between img1 and img2 per single channel""" dot_img = img1 * img2 + # 计算图像1和图像2的乘积 mu1 = conv(img1) + # 计算图像1的均值 mu2 = conv(img2) + # 计算图像1和图像2的均值的平方 mu1_sq = mu1 * mu1 + # 计算图像1和图像2的均值的平方 mu2_sq = mu2 * mu2 + # 计算图像1和图像2的均值的平方和 mu1_mu2 = mu1 * mu2 + # 计算图像1和图像2的方差 sigma1_tmp = conv(img1 * img1) + # 计算图像1和图像2的方差的平方 sigma1_sq = sigma1_tmp - mu1_sq + # 计算图像2和图像1的方差 sigma2_tmp = conv(img2 * img2) + # 计算图像2和图像1的方差的平方 sigma2_sq = sigma2_tmp - mu2_sq + # 计算图像1和图像2的点乘 sigma12_tmp = conv(dot_img) + # 计算图像1和图像2的点乘的平方 sigma12 = sigma12_tmp - mu1_mu2 + # 计算图像1和图像2的平方根 a = (2 * mu1_mu2 + c1) + # 计算图像1和图像2的平方根的平方 b = (mu1_sq + mu2_sq + c1) + # 计算图像1和图像2的二次方差 v1 = 2 * sigma12 + c2 + # 计算图像1和图像2的二次方差的平方 v2 = sigma1_sq + sigma2_sq + c2 + # 计算图像1和图像2的平方根的二次方差 ssim = (a * v1) / (b * v2) + # 计算图像1和图像2的二次方差的平方根 cs = v1 / v2 + # 返回ssim和cs return ssim, cs - +# 计算两个图像张量的损失 def _compute_multi_channel_loss(c1, c2, img1, img2, conv, concat, mean): """computes ssim index between img1 and img2 per color channel""" + # 初始化img1 split_img1, c = _split_img(img1) + # 初始化img2 split_img2, _ = _split_img(img2) + # 初始化每个通道的损失 multi_ssim = () multi_cs = () + # 遍历每个通道 for i in range(c): + # 计算每个通道的损失 ssim_per_channel, cs_per_channel = _compute_per_channel_loss(c1, c2, split_img1[i], split_img2[i], conv) + # 将每个通道的损失添加到multi_ssim中 multi_ssim += (ssim_per_channel,) + # 将每个通道的损失添加到multi_cs中 multi_cs += (cs_per_channel,) + # 将multi_ssim和multi_cs合并 multi_ssim = concat(multi_ssim) multi_cs = concat(multi_cs) + # 计算平均损失 ssim = mean(multi_ssim, (2, 3)) cs = mean(multi_cs, (2, 3)) + # 返回平均损失 return ssim, cs - +# 用于检测图像之间的结构相似性指数 +# 通过比较图像的局部特征来衡量图像的相似程度,输出值通常在1到-1之间,越接近1越相似 class SSIM(Cell): r""" Returns SSIM index between two images. @@ -256,46 +335,64 @@ class SSIM(Cell): >>> print(output) [1.] """ + # 参数 + # max_val:最大值,用于归一化SSIM值。默认值为1.0 + # filter_size:滤波器大小,用于计算SSIM的卷积核大小。默认值为11 + # filter_sigma:滤波器sigma值,用于计算SSIM的卷积核sigma大小。默认值为1.5 + # k1:权重参数k1,用于计算SSIM的公式。默认值为0.01 + # k2:权重参数k2,用于计算SSIM的公式。默认值为0.03 def __init__(self, max_val=1.0, filter_size=11, filter_sigma=1.5, k1=0.01, k2=0.03): super(SSIM, self).__init__() + # 检查max_val类型是否为int或float validator.check_value_type('max_val', max_val, [int, float], self.cls_name) + # 检查max_val是否大于0.0 validator.check_number('max_val', max_val, 0.0, Rel.GT, self.cls_name) self.max_val = max_val + # 检查filter_size是否大于等于1 self.filter_size = validator.check_int(filter_size, 1, Rel.GE, 'filter_size', self.cls_name) + # 检查filter_sigma是否大于等于0 self.filter_sigma = validator.check_positive_float(filter_sigma, 'filter_sigma', self.cls_name) + # 赋值k1、k2 self.k1 = validator.check_value_type('k1', k1, [float], self.cls_name) self.k2 = validator.check_value_type('k2', k2, [float], self.cls_name) + # 创建计算滤波器窗口 window = _create_window(filter_size, filter_sigma) + # 创建一个二维卷积层 self.conv = _conv2d(1, 1, filter_size, Tensor(window)) self.conv.weight.requires_grad = False self.reduce_mean = P.ReduceMean() self.concat = P.Concat(axis=1) - + # 将img1、img2这两个输入的图像张量传入SSIM进行比较 def construct(self, img1, img2): + # 检查img1的类型是否为float32或float16 _check_input_dtype(F.dtype(img1), "img1", [mstype.float32, mstype.float16], self.cls_name) + # 检查filter_size是否等于图像尺寸 _check_input_filter_size(F.shape(img1), "img1", self.filter_size, self.cls_name) + # 用SameTypeShape保证img1和img2形状相同 P.SameTypeShape()(img1, img2) + # 获取张量最大值 dtype_max_val = _get_dtype_max(F.dtype(img1)) max_val = F.scalar_cast(self.max_val, F.dtype(img1)) max_val = _convert_img_dtype_to_float32(max_val, dtype_max_val) + # 把img1和img2转化为float32数据类型 img1 = _convert_img_dtype_to_float32(img1, dtype_max_val) img2 = _convert_img_dtype_to_float32(img2, dtype_max_val) c1 = (self.k1 * max_val) ** 2 c2 = (self.k2 * max_val) ** 2 - + # 计算img1与img2之间的ssim损失 ssim_ave_channel, _ = _compute_multi_channel_loss(c1, c2, img1, img2, self.conv, self.concat, self.reduce_mean) loss = self.reduce_mean(ssim_ave_channel, -1) - + # 返回损失值 return loss - +# 向下取样img1、img2 def _downsample(img1, img2, op): a = op(img1) b = op(img2) return a, b - +# MSSIM相比于SSIM对于不同分辨率都可以保持性能稳定性 class MSSSIM(Cell): r""" Returns MS-SSIM index between two images. @@ -351,43 +448,78 @@ class MSSSIM(Cell): >>> print(output) [1.] """ + # 参数 + # max_val:最大值,用于归一化SSIM值。默认值为1.0 + # filter_size:滤波器大小,用于计算SSIM的卷积核大小。默认值为11 + # filter_sigma:滤波器sigma值,用于计算SSIM的卷积核sigma大小。默认值为1.5 + # k1:权重参数k1,用于计算SSIM的公式。默认值为0.01 + # k2:权重参数k2,用于计算SSIM的公式。默认值为0.03 def __init__(self, max_val=1.0, power_factors=(0.0448, 0.2856, 0.3001, 0.2363, 0.1333), filter_size=11, filter_sigma=1.5, k1=0.01, k2=0.03): super(MSSSIM, self).__init__() + # 检查max_val类型是否为int或float validator.check_value_type('max_val', max_val, [int, float], self.cls_name) + # 检查max_val是否大于0.0 validator.check_number('max_val', max_val, 0.0, Rel.GT, self.cls_name) self.max_val = max_val + # 检查power_factors的类型,是否为tuple或者list,以及最大值是否大于0.0,最后赋值给power_factors validator.check_value_type('power_factors', power_factors, [tuple, list], self.cls_name) + # 检查filter_size是否大于等于1 self.filter_size = validator.check_int(filter_size, 1, Rel.GE, 'filter_size', self.cls_name) + # 检查filter_sigma是否大于等于0 self.filter_sigma = validator.check_positive_float(filter_sigma, 'filter_sigma', self.cls_name) + # 检查参k1是否为float型 self.k1 = validator.check_value_type('k1', k1, [float], self.cls_name) + # 检查参k2是否为float型 self.k2 = validator.check_value_type('k2', k2, [float], self.cls_name) + # 创建计算滤波器窗口 window = _create_window(filter_size, filter_sigma) + # 计算level self.level = len(power_factors) self.conv = [] for i in range(self.level): + # 将卷积层添加到self.conv中 self.conv.append(_conv2d(1, 1, filter_size, Tensor(window))) + # 将卷积层的权重设置为不可求导 self.conv[i].weight.requires_grad = False + # 将self.conv添加到self.multi_convs_list中 self.multi_convs_list = CellList(self.conv) + # 将self.weight_tensor设置为power_factors的浮点型值 self.weight_tensor = Tensor(power_factors, mstype.float32) + # 创建平均池化层 self.avg_pool = AvgPool2d(kernel_size=2, stride=2, pad_mode='valid') + # 创建ReLU激活层 self.relu = ReLU() + # 创建平均池化层 self.reduce_mean = P.ReduceMean() + # 创建乘法层 self.prod = P.ReduceProd() + # 创建Pow层 self.pow = P.Pow() + # 创建stack层 self.stack = P.Stack(axis=-1) + # 创建concat层 self.concat = P.Concat(axis=1) - + def construct(self, img1, img2): + # 检查img1是否为4d _check_input_4d(F.shape(img1), "img1", self.cls_name) + # 检查img2是否为4d _check_input_4d(F.shape(img2), "img2", self.cls_name) valid_type = [mstype.float64, mstype.float32, mstype.float16, mstype.uint8] + # 检查输入的数据类型 _check_input_dtype(F.dtype(img1), 'img1', valid_type, self.cls_name) + # 对img1和img2进行同类型和形状的操作 P.SameTypeShape()(img1, img2) + # 获取img1的最大值 dtype_max_val = _get_dtype_max(F.dtype(img1)) + # 设置最大值 max_val = F.scalar_cast(self.max_val, F.dtype(img1)) + # 将最大值转换为float32类型 max_val = _convert_img_dtype_to_float32(max_val, dtype_max_val) + # 将img1转换为float32类型 img1 = _convert_img_dtype_to_float32(img1, dtype_max_val) + # 将img2转换为float32类型 img2 = _convert_img_dtype_to_float32(img2, dtype_max_val) c1 = (self.k1 * max_val) ** 2 @@ -395,7 +527,7 @@ class MSSSIM(Cell): sim = () mcs = () - + # 循环遍历self.level的值 for i in range(self.level): sim, cs = _compute_multi_channel_loss(c1, c2, img1, img2, self.multi_convs_list[i], self.concat, self.reduce_mean) @@ -403,14 +535,18 @@ class MSSSIM(Cell): img1, img2 = _downsample(img1, img2, self.avg_pool) mcs = mcs[0:-1:1] + # 将mcs中的元素按照1:1:1的方式进行堆叠 mcs_and_ssim = self.stack(mcs + (self.relu(sim),)) + # 将mcs_and_ssim中的元素乘以weight_tensor的结果 mcs_and_ssim = self.pow(mcs_and_ssim, self.weight_tensor) + # 将mcs_and_ssim中的元素乘以-1的结果 ms_ssim = self.prod(mcs_and_ssim, -1) + # 将ms_ssim中的元素乘以-1的结果并且求和 loss = self.reduce_mean(ms_ssim, -1) - + # 返回loss值 return loss - +# 用于计算图像修复和增强模型中的PSNR值 class PSNR(Cell): r""" Returns Peak Signal-to-Noise Ratio of two image batches. @@ -451,37 +587,48 @@ class PSNR(Cell): >>> print(output) [-6.0206] """ + # 初始化最大值 def __init__(self, max_val=1.0): super(PSNR, self).__init__() + # 检查max_val的类型是否为int或float validator.check_value_type('max_val', max_val, [int, float], self.cls_name) + # 检查max_val的值是否大于0.0 validator.check_number('max_val', max_val, 0.0, Rel.GT, self.cls_name) self.max_val = max_val def construct(self, img1, img2): + # 检查img1是否为4d _check_input_4d(F.shape(img1), "img1", self.cls_name) + # 检查img2是否为4d _check_input_4d(F.shape(img2), "img2", self.cls_name) P.SameTypeShape()(img1, img2) dtype_max_val = _get_dtype_max(F.dtype(img1)) max_val = F.scalar_cast(self.max_val, F.dtype(img1)) + # 将max_val转换为float32类型 max_val = _convert_img_dtype_to_float32(max_val, dtype_max_val) + # 将img1和img2转换为float32类型 img1 = _convert_img_dtype_to_float32(img1, dtype_max_val) img2 = _convert_img_dtype_to_float32(img2, dtype_max_val) - + # 计算MSE均方误差 mse = P.ReduceMean()(F.square(img1 - img2), (-3, -2, -1)) + # 使用10 * P.Log函数计算psnr psnr = 10 * P.Log()(F.square(max_val) / mse) / F.scalar_log(10.0) - + # 返回psnr值 return psnr @constexpr def _raise_dims_rank_error(input_shape, param_name, func_name): """raise error if input is not 3d or 4d""" + # 抛出异常 raise ValueError(f"{func_name} {param_name} should be 3d or 4d, but got shape {input_shape}") @constexpr +# 用于计算剪裁图像时的高度、宽度的起始位置和大小 def _get_bbox(rank, shape, central_fraction): """get bbox start and size for slice""" + # 如果为rank为3 if rank == 3: c, h, w = shape else: @@ -491,17 +638,17 @@ def _get_bbox(rank, shape, central_fraction): bbox_w_start = int((float(w) - np.float32(w * central_fraction)) / 2) bbox_h_size = h - bbox_h_start * 2 bbox_w_size = w - bbox_w_start * 2 - + # 如果rank为3 if rank == 3: bbox_begin = (0, bbox_h_start, bbox_w_start) bbox_size = (c, bbox_h_size, bbox_w_size) else: bbox_begin = (0, 0, bbox_h_start, bbox_w_start) bbox_size = (n, c, bbox_h_size, bbox_w_size) - + # 返回初始位置和大小 return bbox_begin, bbox_size - +# 用于从图像中剪裁处中心部分 class CentralCrop(Cell): """ Crops the central region of the images with the central_fraction. @@ -532,20 +679,26 @@ class CentralCrop(Cell): def __init__(self, central_fraction): super(CentralCrop, self).__init__() + # 检查central_fraction是否为float型 validator.check_value_type("central_fraction", central_fraction, [float], self.cls_name) + # 检查central_fraction是否在0.0到1.0之间 validator.check_float_range(central_fraction, 0.0, 1.0, Rel.INC_RIGHT, 'central_fraction', self.cls_name) self.central_fraction = central_fraction self.slice = P.Slice() def construct(self, image): + # 获取图像的形状(高度、宽度、通道数) image_shape = F.shape(image) rank = len(image_shape) + # 检查图像的形状是否为3维或4维 if not rank in (3, 4): return _raise_dims_rank_error(image_shape, "image", self.cls_name) if self.central_fraction == 1.0: return image + # 获取图像的边界框 bbox_begin, bbox_size = _get_bbox(rank, image_shape, self.central_fraction) + # 将图像切片 image = self.slice(image, bbox_begin, bbox_size) return image diff --git a/mindspore/python/mindspore/nn/layer/math.py b/mindspore/python/mindspore/nn/layer/math.py index 672a297c020..42d6ea976ca 100644 --- a/mindspore/python/mindspore/nn/layer/math.py +++ b/mindspore/python/mindspore/nn/layer/math.py @@ -13,14 +13,23 @@ # limitations under the License. # ============================================================================ """math""" +# 导入numpy库,用于处理数值计算 import numpy as np +# 从mindspore.ops模块中导入operations模块,用于提供基本操作(如矩阵乘法、加法等) from mindspore.ops import operations as P +# 从mindspore.common.tensor模块中导入Tensor类,用于表示张量 from mindspore.common.tensor import Tensor +# 从mindspore.common._decorator模块中导入deprecated装饰器,用于标记已过时的功能 from mindspore.common._decorator import deprecated +# 从mindspore.ops.primitive模块中导入constexpr类,用于定义一个常量 from mindspore.ops.primitive import constexpr +# 从mindspore.ops.functional模块中导入functional类,用于提供一些基本功能,如条件判断、循环等 from mindspore.ops import functional as F +# 从当前模块(..cell)中导入Cell类,用于创建自定义Cell from ..cell import Cell +# 从mindspore.common模块中导入dtype类,用于表示数据类型 from ...common import dtype as mstype +# 从mindspore._checkparam模块中导入Validator类,用于验证参数的范围和类型 from ..._checkparam import Validator as validator __all__ = ['ReduceLogSumExp', @@ -34,7 +43,8 @@ __all__ = ['ReduceLogSumExp', 'MatInverse', 'MatDet', ] - +# 该系数用于Lanczos重采样算法 +# 用于将图像调整为不同分辨率,用于计算Lanczos核 _BASE_LANCZOS_COEFF = 0.99999999999980993227684700473478 _LANCZOS_COEFFICIENTS = [676.520368121885098567009190444019, -1259.13921672240287047156078755283, @@ -47,10 +57,12 @@ _LANCZOS_COEFFICIENTS = [676.520368121885098567009190444019, @constexpr +# 检查输入数据的类型是否为允许输入类型 def _check_input_dtype(param_name, input_dtype, allow_dtypes, cls_name): validator.check_type_name(param_name, input_dtype, allow_dtypes, cls_name) - +# 用于输入进行对数和归一化指数的降维的神经网络层 +# 将输入张量的每个元素求对数和归一化指数 class ReduceLogSumExp(Cell): r""" Reduces a dimension of a tensor by calculating exponential for all elements in the dimension, @@ -99,20 +111,31 @@ class ReduceLogSumExp(Cell): def __init__(self, axis, keep_dims=False): """Initialize ReduceLogSumExp.""" super(ReduceLogSumExp, self).__init__() + # 检查'axis'的类型是否为int, list, tuple validator.check_value_type('axis', axis, [int, list, tuple], self.cls_name) + # 检查'keep_dims'的类型是否为bool validator.check_value_type('keep_dims', keep_dims, [bool], self.cls_name) + # 赋值 self.axis = axis + # 创建Exp对象 self.exp = P.Exp() + # 创建ReduceSum对象 self.sum = P.ReduceSum(keep_dims) + # 创建Log对象 self.log = P.Log() - + # 构造 def construct(self, x): + # 计算exp(x) exp = self.exp(x) + # 计算sum(exp, self.axis) sumexp = self.sum(exp, self.axis) + # 计算log(sumexp) logsumexp = self.log(sumexp) + # 返回logsumexp return logsumexp - +# 用于创建一个表示单元格范围的类 +# 主要作用是储存单元格范围的起始和结束单元格地址 class Range(Cell): r""" Creates a sequence of numbers in range [start, limit) with step size delta. @@ -143,23 +166,30 @@ class Range(Cell): >>> print(output) [1 3 5 7] """ - + # 初始化Range函数 def __init__(self, start, limit=None, delta=1): """Initialize Range.""" super(Range, self).__init__() + # 如果delta为0 if delta == 0: + # 抛出异常 raise ValueError(f"For '{self.cls_name}', the 'delta' can not be zero.") + # 创建一个单元格范围的数组 data = np.arange(start, limit, delta) + # 如果data的类型为float if data.dtype == np.float: + # 设置ms_dtype为float32 self.ms_dtype = mstype.float32 else: + # 设置ms_dtype为int32 self.ms_dtype = mstype.int32 self.result_tensor = Tensor(data, dtype=self.ms_dtype) def construct(self): return self.result_tensor - +# 用于计算自然对数的gamma函数值 +# 通常用于计算概率分布的归一化因子 class LGamma(Cell): r""" Calculates LGamma using Lanczos' approximation referring to "A Precision Approximation of the Gamma Function". @@ -216,58 +246,91 @@ class LGamma(Cell): super(LGamma, self).__init__() # const numbers self.k_lanczos_gamma = 7 + # k_lanczos_gamma的值 self.k_base_lanczos_coeff = _BASE_LANCZOS_COEFF + # k_base_lanczos_coeff的值 self.k_lanczos_coefficients = _LANCZOS_COEFFICIENTS + # k_lanczos_coefficients的值 self.one_half = 0.5 + # one_half的值 self.one = 1 + # one的值 self.two = 2 + # two的值 self.inf = np.inf + # inf的值 self.pi = np.pi + # pi的值 self.log_2 = np.log(self.two) + # log_2的值 self.log_pi = np.log(np.pi) + # log_pi的值 self.log_sqrt_two_pi = (self.log_2 + self.log_pi) / self.two + # log_sqrt_two_pi的值 self.lanczos_gamma_plus_one_half = self.k_lanczos_gamma + 0.5 + # lanczos_gamma_plus_one_half的值 self.log_lanczos_gamma_plus_one_half = np.log(self.lanczos_gamma_plus_one_half) # operations self.log = P.Log() + # 计算log函数 self.log1p = P.Log1p() + # 计算log1p函数 self.abs = P.Abs() + # 计算绝对值函数 self.shape = P.Shape() + # 计算形状函数 self.dtype = P.DType() + # 计算数据类型函数 self.fill = P.Fill() + # 计算填充函数 self.floor = P.Floor() + # 计算小于函数 self.equal = P.Equal() + # 计算等于函数 self.greater = P.Greater() + # 计算大于函数 self.less = P.Less() + # 计算小于等于函数 self.lessequal = P.LessEqual() + # 计算小于等于等于函数 self.select = P.Select() + # 计算选择函数 self.sin = P.Sin() + # 计算sin函数 self.isfinite = P.IsFinite() def construct(self, x): input_dtype = self.dtype(x) - _check_input_dtype("x", input_dtype, [mstype.float16, mstype.float32], self.cls_name) + # 检查"x"类型是否为float16或float16 + _check_input_dtype("x", input_dtype, [mstype.float16, mstype.float16], self.cls_name) infinity = self.fill(input_dtype, self.shape(x), self.inf) need_to_reflect = self.less(x, 0.5) neg_input = -x + # 如果x小于0.5,则将-x放入z中,否则将x-1放入z中 z = self.select(need_to_reflect, neg_input, x - 1) @constexpr def _calculate_reflected_x(z, k_base_lanczos_coeff, k_lanczos_coefficients): reflex_x = k_base_lanczos_coeff for i in range(8): + # 计算k_lanczos_coefficients[i] / (z + i + 1) product_ = k_lanczos_coefficients[i] / (z + i + 1) + # 将product_加到reflex_x上 reflex_x = product_ + reflex_x return reflex_x + # 计算reflected_x reflex_x = _calculate_reflected_x(z, self.k_base_lanczos_coeff, self.k_lanczos_coefficients) + # 计算t t = z + self.lanczos_gamma_plus_one_half log_t = self.log1p(z / self.lanczos_gamma_plus_one_half) + self.log_lanczos_gamma_plus_one_half + # 计算log_y log_y = self.log(reflex_x) + (z + self.one_half - t / log_t) * log_t + self.log_sqrt_two_pi + # 计算abs_input abs_input = self.abs(x) abs_frac_input = abs_input - self.floor(abs_input) x = self.select(self.lessequal(x, 0.0), self.select(self.equal(abs_frac_input, 0.0), infinity, x), x) @@ -275,10 +338,12 @@ class LGamma(Cell): 1 - abs_frac_input, abs_frac_input) reflection_denom = self.log(self.sin(self.pi * reduced_frac_input)) + # 计算reflection reflection = self.select(self.isfinite(reflection_denom), -reflection_denom - log_y + self.log_pi, # pylint: disable=invalid-unary-operand-type -reflection_denom) # pylint: disable=invalid-unary-operand-type + # 计算result result = self.select(need_to_reflect, reflection, log_y) return self.select(self.isfinite(x), result, infinity) @@ -337,35 +402,54 @@ class DiGamma(Cell): # operations self.log1p = P.Log1p() + # 定义绝对值函数 self.abs = P.Abs() + # 定义获取shape函数 self.shape = P.Shape() + # 定义获取数据类型函数 self.dtype = P.DType() + # 定义填充函数 self.fill = P.Fill() + # 定义取整函数 self.floor = P.Floor() + # 定义比较函数 self.equal = P.Equal() self.less = P.Less() + # 定义选择函数 self.select = P.Select() + # 定义正弦函数 self.sin = P.Sin() + # 定义余弦函数 self.cos = P.Cos() + # 定义逻辑与函数 self.logicaland = P.LogicalAnd() def construct(self, x): input_dtype = self.dtype(x) + # 检查"x"的类型是否为float16或float32 _check_input_dtype("x", input_dtype, [mstype.float16, mstype.float32], self.cls_name) + # 将x的值小于0.5的值设置为True,其余的设置为False need_to_reflect = self.less(x, 0.5) + # 将x的值取反 neg_input = -x + # 根据need_to_reflect的值,将neg_input的值设置为x-1 z = self.select(need_to_reflect, neg_input, x - 1) @constexpr + # 用于计算一个给定表达式的分子和分母 def _calculate_num_denom(z, k_base_lanczos_coeff, k_lanczos_coefficients): num = 0 denom = k_base_lanczos_coeff + # 循环遍历 for i in range(8): + # 在每个迭代中,更新分子 by 减去k_lanczos_coefficients[i]除以(z + i + 1)乘以(z + i + 1)的值 num = num - k_lanczos_coefficients[i] / ((z + i + 1) * (z + i + 1)) + # 在每个迭代中,更新分母 by 加上k_lanczos_coefficients[i]除以(z + i + 1)的值 denom = denom + k_lanczos_coefficients[i] / (z + i + 1) + # 返回分子分母 return num, denom num, denom = _calculate_num_denom(z, self.k_base_lanczos_coeff, self.k_lanczos_coefficients) - + # 计算t、y的值 t = z + self.lanczos_gamma_plus_one_half log_t = self.log1p(z / self.lanczos_gamma_plus_one_half) + self.log_lanczos_gamma_plus_one_half @@ -382,13 +466,23 @@ class DiGamma(Cell): eps_fp32 = Tensor(np.finfo(np.float32).eps, mstype.float32) - +# 用于在满足给定条件cond时重复执行body中的代码 def _while_helper_func(cond, body, vals): + # 定义一个名为while_loop的函数,参数为cond和body while cond(vals).any(): + # 当cond函数返回值为True时,执行body函数 vals = body(vals) + # 返回vals return vals - +# 用于计算给定参数ax和x的逆元Gamma函数的series形式 +# 参数 +# logicaland:一个布尔函数,用于对两个布尔值进行逻辑与操作 +# greater:一个比较函数,用于比较两个浮点数,并返回一个布尔值 +# fill:一个填充函数,用于将一个数值填充到一个给定形状和数据类型的数组中 +# shape:一个属性函数,用于获取一个数组的形状 +# dtype:一个属性函数,用于获取一个数组的数据类型 +# select:一个选择函数,用于根据条件选择一个值 def _igamma_series(ax, x, a, enabled): """Helper function for computing Igamma using a power series.""" @@ -407,19 +501,32 @@ def _igamma_series(ax, x, a, enabled): return enabled def body(vals): + # 设置enabled为vals[0] enabled = vals[0] + # 设置r为vals[1] r = vals[1] + # 设置c为vals[2] c = vals[2] + # 设置ans为vals[3] ans = vals[3] + # 设置x为vals[4] x = vals[4] + # 设置dc_da为vals[5] dc_da = vals[5] + # 设置dans_da为vals[6] dans_da = vals[6] + # 计算r r = r + 1 + # 计算dc_da dc_da = dc_da * (x / r) + (-1 * c * x) / (r * r) + # 计算dans_da dans_da = dans_da + dc_da + # 计算c c = c * (x / r) + # 计算ans ans = ans + c + # 计算条件 conditional = logicaland(enabled, greater(c / ans, epsilon)) return (conditional, select(enabled, r, vals[1]), @@ -427,15 +534,18 @@ def _igamma_series(ax, x, a, enabled): select(enabled, x, vals[4]), select(enabled, dc_da, vals[5]), select(enabled, dans_da, vals[6])) + # 初始化变量 ones = fill(dtype(a), shape(a), 1) zeros = fill(dtype(a), shape(a), 0) vals = (enabled, a, ones, ones, x, zeros, zeros) + # 调用_while_helper_func函数 vals = _while_helper_func(cond, body, vals) + # 计算ans ans = vals[3] + # 返回结果 return (ans * ax) / a - def _igammac_continued_fraction(ax, x, a, enabled): """Helper function for computing Igammac using a continued fraction.""" @@ -453,44 +563,74 @@ def _igammac_continued_fraction(ax, x, a, enabled): epsilon = eps_fp32 def cond(vals): + # 返回一个布尔值,该布尔值表示c是否小于2000,且enabled为真 enabled = vals[0] c = vals[5] return logicaland(less(c, 2000), enabled) def body(vals): + # 设置enabled为vals[0] enabled = vals[0] + # 设置ans为vals[1] ans = vals[1] + # 设置t为vals[2] t = vals[2] + # 设置y为vals[3] y = vals[3] + # 设置z为vals[4] z = vals[4] + # 设置c为vals[5] c = vals[5] + # 设置pkm1为vals[6] pkm1 = vals[6] + # 设置qkm1为vals[7] qkm1 = vals[7] + # 设置pkm2为vals[8] pkm2 = vals[8] + # 设置qkm2为vals[9] qkm2 = vals[9] + # 设置dpkm2_da为vals[10] dpkm2_da = vals[10] + # 设置dqkm2_da为vals[11] dqkm2_da = vals[11] + # 设置dpkm1_da为vals[12] dpkm1_da = vals[12] + # 设置dqkm1_da为vals[13] dqkm1_da = vals[13] + # 设置dans_da为vals[14] dans_da = vals[14] + # c加1 c = c + 1 + # y加1 y = y + 1 + # z加2 z = z + 2 + # yc等于y乘以c yc = y * c + # pk等于pkm1乘以z减去pkm2乘以yc pk = pkm1 * z - pkm2 * yc + # qk等于qkm1乘以z减去qkm2乘以yc qk = qkm1 * z - qkm2 * yc + # qk_is_nonzero等于qk不等于0 qk_is_nonzero = notequal(qk, 0) + # r等于pk除以qk r = pk / qk + # t等于select函数,当qk_is_nonzero为真时,abs_x函数的结果等于ans减去r除以r,否则等于fill函数 t = select(qk_is_nonzero, abs_x((ans - r) / r), fill(dtype(t), shape(t), 1)) + # ans等于select函数,当qk_is_nonzero为真时,r,否则等于ans ans = select(qk_is_nonzero, r, ans) + # dpk_da等于dpkm1_da乘以z减去pkm1减去dpkm2_da乘以yc加起来乘以c dpk_da = dpkm1_da * z - pkm1 - dpkm2_da * yc + pkm2 * c + # dqk_da等于dqkm1_da乘以z减去qkm1减去dqkm2_da乘以yc加起来乘以c dqk_da = dqkm1_da * z - qkm1 - dqkm2_da * yc + qkm2 * c + # dans_da_new等于select函数,当qk_is_nonzero为真时,dpk_da减去ans乘以dqk_da除以qk,否则等于dans_da dans_da_new = select(qk_is_nonzero, (dpk_da - ans * dqk_da) / qk, dans_da) + # grad_conditional等于select函数,当qk_is_nonzero为真时,abs_x函数的结果等于dans_da_new减去dans_da,否则等于fill函数 grad_conditional = select(qk_is_nonzero, abs_x(dans_da_new - dans_da), fill(dtype(dans_da), shape(dans_da), 1)) @@ -526,26 +666,44 @@ def _igammac_continued_fraction(ax, x, a, enabled): select(enabled, dqkm2_da, vals[11]), select(enabled, dpkm1_da, vals[12]), select(enabled, dqkm1_da, vals[13]), select(enabled, dans_da_new, vals[14])) + # 计算x的值 y = 1 - a + # 计算z的值 z = x + y + 1 + # 初始化c的值 c = fill(dtype(x), shape(x), 0) + # 初始化pkm2的值 pkm2 = fill(dtype(x), shape(x), 1) + # 初始化qkm2的值 qkm2 = x + # 初始化pkm1的值 pkm1 = x + 1 + # 初始化qkm1的值 qkm1 = z * x + # 计算ans的值 ans = pkm1 / qkm1 + # 初始化t的值 t = fill(dtype(x), shape(x), 1) + # 初始化dpkm2_da的值 dpkm2_da = fill(dtype(x), shape(x), 0) + # 初始化dqkm2_da的值 dqkm2_da = fill(dtype(x), shape(x), 0) + # 初始化dpkm1_da的值 dpkm1_da = fill(dtype(x), shape(x), 0) + # 初始化dqkm1_da的值 dqkm1_da = -x + # 计算dans_da的值 dans_da = (dpkm1_da - ans * dqkm1_da) / qkm1 + # 将enabled,ans,t,y,z,c,pkm1,qkm1,pkm2,qkm2,dpkm2_da,dqkm2_da,dpkm1_da,dqkm1_da,dans_da放入vals中 vals = (enabled, ans, t, y, z, c, pkm1, qkm1, pkm2, qkm2, dpkm2_da, dqkm2_da, dpkm1_da, dqkm1_da, dans_da) + # 调用_while_helper_func函数,计算vals的值 vals = _while_helper_func(cond, body, vals) + # 获取ans的值 ans = vals[1] + # 返回ans乘以ax的值 return ans * ax - +# 用于创建一个计算逆元Gamma函数 class IGamma(Cell): r""" Calculates lower regularized incomplete Gamma function. @@ -619,27 +777,40 @@ class IGamma(Cell): x_dtype = self.dtype(x) _check_input_dtype("a", a_dtype, [mstype.float32], self.cls_name) _check_input_dtype("x", x_dtype, a_dtype, self.cls_name) + # 检查输入的类型是否正确 domain_error = self.logicalor(self.less(x, 0), self.less(a, 0)) use_igammac = self.logicaland(self.greater(x, 1), self.greater(x, a)) + # 计算a*log(x) - x - lgamma(a) ax = a * self.log(x) - x - self.lgamma(a) + # 获取ax的形状 para_shape = self.shape(ax) + # 如果ax的形状不为空,则进行广播 if para_shape != (): broadcastto = P.BroadcastTo(para_shape) x = broadcastto(x) a = broadcastto(a) + # 判断x是否为0 x_is_zero = self.equal(x, 0) + # 计算log_maxfloat log_maxfloat = self.log_maxfloat32 + # 判断ax是否小于-log_maxfloat underflow = self.less(ax, self.neg(log_maxfloat)) + # 计算ax ax = self.exp(ax) + # 判断是否启用 enabled = self.logicalnot(self.logicalor(self.logicalor(x_is_zero, domain_error), underflow)) + # 计算output output = self.select(use_igammac, 1 - _igammac_continued_fraction(ax, x, a, self.logicaland(enabled, use_igammac)), _igamma_series(ax, x, a, self.logicaland(enabled, self.logicalnot(use_igammac)))) + # 判断x是否为0,如果是,则output为0 output = self.select(x_is_zero, self.zeroslike(output), output) + # 判断domain_error,如果是,则output为nan output = self.select(domain_error, self.fill(self.dtype(a), self.shape(a), np.nan), output) + # 返回output return output - +# 用于创建一个计算二项式Beta函数 class LBeta(Cell): r""" This method avoids the numeric cancellation by explicitly @@ -684,6 +855,7 @@ class LBeta(Cell): super(LBeta, self).__init__() # const numbers self.log_2pi = np.log(2 * np.pi) + # 用于计算给定参数ax和x的逆元Gamma函数的series形式 self.minimax_coeff = [-0.165322962780713e-02, 0.837308034031215e-03, -0.595202931351870e-03, @@ -700,29 +872,39 @@ class LBeta(Cell): self.dtype = P.DType() self.lgamma = LGamma() self.const = P.ScalarToTensor() - + def construct(self, x, y): x_dtype = self.dtype(x) y_dtype = self.dtype(y) + # 检查"x"的类型是否为float16或float32 _check_input_dtype("x", x_dtype, [mstype.float16, mstype.float32], self.cls_name) + # 检查"y"的类型是否与"x"的类型相同 _check_input_dtype("y", y_dtype, x_dtype, self.cls_name) + # 求x与y的和并赋值给x_plus_y x_plus_y = x + y + # 获取x_plus_y的形状 para_shape = self.shape(x_plus_y) if para_shape != (): + #用P.BroadcastTo将x和y广播到相同的形状 broadcastto = P.BroadcastTo(para_shape) x = broadcastto(x) y = broadcastto(y) + # 比较x和y comp_less = self.less(x, y) x_min = self.select(comp_less, x, y) y_max = self.select(comp_less, y, x) @constexpr + # 用于计算给定参数x的逆元Gamma函数的log correction def _log_gamma_correction(x, minimax_coeff): + # 计算inverse_x和inverse_x_squared inverse_x = 1. / x inverse_x_squared = inverse_x * inverse_x accum = minimax_coeff[0] + # 循环遍历 for i in range(1, 6): accum = accum * inverse_x_squared + minimax_coeff[i] + # 返回accum * inverse_x的值 return accum * inverse_x log_gamma_correction_x = _log_gamma_correction(x_min, self.minimax_coeff) @@ -730,6 +912,7 @@ class LBeta(Cell): log_gamma_correction_x_y = _log_gamma_correction(x_plus_y, self.minimax_coeff) # Two large arguments case: y >= x >= 8. + # 计算log_beta_two_large的值 log_beta_two_large = self.const(0.5 * self.log_2pi, x_dtype) - 0.5 * self.log(y_max) \ + log_gamma_correction_x + log_gamma_correction_y - log_gamma_correction_x_y \ + (x_min - 0.5) * self.log(x_min / (x_min + y_max)) - y_max * self.log1p(x_min / y_max) @@ -750,29 +933,44 @@ class LBeta(Cell): @constexpr +# 用于获取两个形状x_shape和y_shape的广播矩阵乘法形状 def get_broadcast_matmul_shape(x_shape, y_shape, prim_name=None): """get broadcast_matmul shape""" msg_prefix = f"For '{prim_name}', the" if prim_name else "The" + # 检查x_shape和y_shape的长度是否小于二,如果小于二 if (len(x_shape) < 2) or (len(y_shape) < 2): + # 抛出异常 raise ValueError(f"{msg_prefix} length of 'x_shape' and 'y_shape' should be equal to or greater than 2, " f"but got the length of 'x_shape': {len(x_shape)} and the length of 'y_shape': " f"{len(y_shape)}.") + # 计算x_shape_batch和y_shape_batch x_shape_batch = x_shape[:-2] y_shape_batch = y_shape[:-2] + # 比较x与y if x_shape_batch == y_shape_batch: + # 返回x_shape,y_shape return x_shape, y_shape x_len = len(x_shape) y_len = len(y_shape) length = x_len if x_len < y_len else y_len broadcast_shape_back = [] + # 循环遍历从-length到-2 for i in range(-length, -2): + # 如果x_shape[i]为1 if x_shape[i] == 1: + # 将y_shape[i]添加到broadcast_shape_back列表中 broadcast_shape_back.append(y_shape[i]) + # 如果y_shape[i]为1 elif y_shape[i] == 1: + # 将x_shape[i]添加到broadcast_shape_back列表中 broadcast_shape_back.append(x_shape[i]) + # 如果x_shape[i]与y_shape[i]相同 elif x_shape[i] == y_shape[i]: + # 将x_shape[i]添加到broadcast_shape_back列表中 broadcast_shape_back.append(x_shape[i]) + # 否则 else: + # 抛出异常 raise ValueError(f"{msg_prefix} 'x_shape[{i}]' should be equal to 1, or the 'y_shape[{i}]' should be equal " f"to 1, or the 'x_shape[{i}]' should be equal to 'y_shape[{i}]', but got " f"'x_shape[{i}]': {x_shape[i]}, 'y_shape[{i}]': {y_shape[i]}.") @@ -784,42 +982,58 @@ def get_broadcast_matmul_shape(x_shape, y_shape, prim_name=None): @constexpr +# 用于检查两个形状x1_shape和x2_shape是否为列向量或行向量 def check_col_row_equal(x1_shape, x2_shape, transpose_x1, transpose_x2, prim_name=None): """check col and row equal""" msg_prefix = f"For '{prim_name}', the" if prim_name else "The" + # 如果x1_shape的长度为1 if len(x1_shape) == 1: transpose_x1 = False + # 转化为单列二维形状 x1_shape = (1,) + x1_shape + # 如果x2_shape的长度为1 if len(x2_shape) == 1: transpose_x2 = False + # 转化为单列二维形状 x2_shape = x2_shape + (1,) x1_last = x1_shape[-2:] x2_last = x2_shape[-2:] + # 用x1_col表示x1中矩阵维度的最后一列 x1_col = x1_last[not transpose_x1] # x1_col = x1_last[1] if (not transpose_a) else x1_last[0] + # 用x2_row表示x2中矩阵维度的最后一行 x2_row = x2_last[transpose_x2] # x2_row = x2_last[0] if (not transpose_b) else x2_last[1] + # 如果x1_col和x2_row不相等 if x1_col != x2_row: + # 抛出异常 raise ValueError(f"{msg_prefix} column of matrix dimensions of 'x1' should be equal to " f"the row of matrix dimensions of 'x2', but got 'x1_col' {x1_col} and 'x2_row' {x2_row}.") - +# 根据输入数据选择合适的矩阵乘法 def matmul_op_select(x1_shape, x2_shape, transpose_x1, transpose_x2): """select matmul op""" x1_dim, x2_dim = len(x1_shape), len(x2_shape) + # 判断x1和x2的维度 if x1_dim == 1 and x2_dim == 1: + # 如果x1和x2的维度都为1,则使用乘法操作 matmul_op = P.Mul() elif x1_dim <= 2 and x2_dim <= 2: + # 如果x1和x2的维度都小于等于2,则使用矩阵乘法操作 transpose_x1 = False if x1_dim == 1 else transpose_x1 transpose_x2 = False if x2_dim == 1 else transpose_x2 matmul_op = P.MatMul(transpose_x1, transpose_x2) elif x1_dim == 1 and x2_dim > 2: + # 如果x1的维度为1,x2的维度大于2,则使用批量矩阵乘法操作 matmul_op = P.BatchMatMul(False, transpose_x2) elif x1_dim > 2 and x2_dim == 1: + # 如果x1的维度大于2,x2的维度为1,则使用批量矩阵乘法操作 matmul_op = P.BatchMatMul(transpose_x1, False) else: + # 如果x1和x2的维度都不满足上面的条件,则使用批量矩阵乘法操作 matmul_op = P.BatchMatMul(transpose_x1, transpose_x2) + # 返回矩阵乘法操作 return matmul_op - +# 用于矩阵乘法计算图类 class MatMul(Cell): r""" The nn.MatMul interface is deprecated, please use the :class:`mindspore.ops.matmul` instead. @@ -829,14 +1043,19 @@ class MatMul(Cell): """ @deprecated('1.2', 'ops.matmul', False) + # 初始化函数 def __init__(self, transpose_x1=False, transpose_x2=False): """Initialize MatMul.""" super(MatMul, self).__init__() - + # 检查'transpose_x1'是否为bool(布尔)型 validator.check_value_type('transpose_x1', transpose_x1, [bool], self.cls_name) + # 检查'transpose_x2'是否为bool(布尔)型 validator.check_value_type('transpose_x2', transpose_x2, [bool], self.cls_name) + # 用self.transpose_x1记录transpose_x1的值 self.transpose_x1 = transpose_x1 + # 用self.transpose_x2记录transpose_x2的值 self.transpose_x2 = transpose_x2 + # 定义操作 self.shape_op = P.Shape() self.expand_op = P.ExpandDims() self.squeeze_left_op = P.Squeeze(-2) @@ -846,37 +1065,52 @@ class MatMul(Cell): def construct(self, x1, x2): x1_shape = self.shape_op(x1) x2_shape = self.shape_op(x2) + # 检查x1和x2的形状是否相等 check_col_row_equal(x1_shape, x2_shape, self.transpose_x1, self.transpose_x2, self.cls_name) + # 根据x1和x2的形状,选择矩阵乘法操作 matmul_op = matmul_op_select(x1_shape, x2_shape, self.transpose_x1, self.transpose_x2) + # 获取x1和x2的维度 x1_dim, x2_dim = len(x1_shape), len(x2_shape) + # 如果x1和x2的维度都为1 if x1_dim == x2_dim and x2_dim == 1: + # 返回矩阵乘法的结果 return self.reduce_sum_op(matmul_op(x1, x2), -1) + # 如果x1的维度为1 if x1_dim == 1: + # 将x1扩展到x1_shape x1 = self.expand_op(x1, 0) + # 获取x1的形状 x1_shape = self.shape_op(x1) + # 如果x2的维度为1 if x2_dim == 1: + # 将x2扩展到x2_shape x2 = self.expand_op(x2, 1) + # 获取x2的形状 x2_shape = self.shape_op(x2) - + # 通过 get_broadcast_matmul_shape 函数获取两个张量x1与x2的广播形状 x1_broadcast_shape, x2_broadcast_shape = get_broadcast_matmul_shape(x1_shape, x2_shape) x1_broadcast_to = P.BroadcastTo(x1_broadcast_shape) x2_broadcast_to = P.BroadcastTo(x2_broadcast_shape) + # 检查x1和x2广播形状是否与原始形状相同 if x1_broadcast_shape != x1_shape: x1 = x1_broadcast_to(x1) if x2_broadcast_shape != x2_shape: x2 = x2_broadcast_to(x2) matmul_broadcast = matmul_op(x1, x2) - + # 如果x1是1维 if x1_dim == 1: + # 对x1进行squeeze操作 matmul_broadcast = self.squeeze_left_op(matmul_broadcast) + # 如果x2是1维 if x2_dim == 1: + # 对x2进行squeeze操作 matmul_broadcast = self.squeeze_right_op(matmul_broadcast) - + # 返回matmul_broadcast return matmul_broadcast - +# 用于计算张量各个分量统计指标 class Moments(Cell): """ Calculate the mean and variance of the input `x` along the specified `axis`. @@ -943,15 +1177,19 @@ class Moments(Cell): def __init__(self, axis=None, keep_dims=None): """Initialize Moments.""" super(Moments, self).__init__() + # 检查axis的类型是否为tuple if axis is None: axis = () if isinstance(axis, tuple): for idx, item in enumerate(axis): validator.check_value_type("axis[%d]" % idx, item, [int], self.cls_name) + # 检查axis的类型是否为int或tuple self.axis = validator.check_value_type('axis', axis, [int, tuple], self.cls_name) + # 检查keep_dims的类型是否为bool if keep_dims is None: keep_dims = False self.keep_dims = validator.check_value_type('keep_dims', keep_dims, [bool], self.cls_name) + # 初始化cast,reduce_mean,square_diff,squeeze self.cast = P.Cast() self.reduce_mean = P.ReduceMean(keep_dims=True) self.square_diff = P.SquaredDifference() @@ -962,18 +1200,22 @@ class Moments(Cell): _check_input_dtype("input x", tensor_dtype, [mstype.float16, mstype.float32], self.cls_name) if tensor_dtype == mstype.float16: x = self.cast(x, mstype.float32) + # 计算x的均值 mean = self.reduce_mean(x, self.axis) + # 计算x的方差 variance = self.reduce_mean(self.square_diff(x, F.stop_gradient(mean)), self.axis) + # 如果keep_dims为False,则删除mean和variance的维度 if not self.keep_dims: mean = self.squeeze(mean) variance = self.squeeze(variance) + # 如果x的类型为float16,则将mean和variance的类型转换为float16 if tensor_dtype == mstype.float16: mean = self.cast(mean, mstype.float16) variance = self.cast(variance, mstype.float16) return mean, variance return mean, variance - +# 用于计算矩阵的逆的函数 class MatInverse(Cell): """ Calculates the inverse of Positive-Definite Hermitian matrix using Cholesky decomposition. @@ -1003,18 +1245,20 @@ class MatInverse(Cell): def __init__(self): """Initialize MatInverse.""" super(MatInverse, self).__init__() + # 初始化dtype、choleskytrsm、matmul self.dtype = P.DType() self.choleskytrsm = P.CholeskyTrsm() self.matmul = MatMul(transpose_x1=True) def construct(self, a): input_dtype = self.dtype(a) + # 检查"input_a"是否为float16或float32 _check_input_dtype("input_a", input_dtype, [mstype.float16, mstype.float32], self.cls_name) l_inverse = self.choleskytrsm(a) a_inverse = self.matmul(l_inverse, l_inverse) return a_inverse - +# 用于创建一个计算矩阵行列式 class MatDet(Cell): """ Calculates the determinant of Positive-Definite Hermitian matrix using Cholesky decomposition. @@ -1039,9 +1283,11 @@ class MatDet(Cell): >>> print(output) 35.999996 """ + # 初始化 def __init__(self): """Initialize MatDet.""" super(MatDet, self).__init__() + # 设置属性 self.dtype = P.DType() self.cholesky = P.Cholesky() self.det_triangle = P.DetTriangle() @@ -1049,6 +1295,7 @@ class MatDet(Cell): def construct(self, a): input_dtype = self.dtype(a) + # 检查"input_a"是否为float16或float32 _check_input_dtype("input_a", input_dtype, [mstype.float16, mstype.float32], self.cls_name) l = self.cholesky(a) l_det = self.det_triangle(l) diff --git a/mindspore/python/mindspore/nn/layer/normalization.py b/mindspore/python/mindspore/nn/layer/normalization.py index a06915f30a5..4fc9a99ac6e 100644 --- a/mindspore/python/mindspore/nn/layer/normalization.py +++ b/mindspore/python/mindspore/nn/layer/normalization.py @@ -15,23 +15,37 @@ """normalization""" import itertools import numbers - +# 从mindspore.ops模块中导入operations模块,用于提供基本操作(如矩阵乘法、加法等) from mindspore.ops import operations as P +# 从mindspore.ops.functional模块中导入functional类,用于提供一些基本功能,如条件判断、循环等 from mindspore.ops import functional as F +# 从mindspore.ops.operations模块中导入_inner_ops模块,用于提供内部操作 from mindspore.ops.operations import _inner_ops as inner +# 从mindspore.common.parameter模块中导入Parameter类,用于创建一个参数 from mindspore.common.parameter import Parameter +# 从mindspore.common.initializer模块中导入initializer函数(用于初始化参数)和Initializer类(用于创建初始化器) from mindspore.common.initializer import initializer, Initializer +# 从mindspore.common.tensor模块中导入Tensor类,用于表示张量 from mindspore.common.tensor import Tensor +# 从mindspore.common._decorator模块中导入deprecated装饰器,用于标记已过时的功能 from mindspore.common._decorator import deprecated from mindspore.ops.primitive import constexpr import mindspore.context as context +# 从mindspore._checkparam模块中导入Rel类(用于表示相对关系) from mindspore._checkparam import Rel +# 从mindspore._checkparam模块中导入Validator类(用于验证参数的范围和类型) from mindspore._checkparam import Validator as validator +# 从mindspore._extends模块中导入cell_attr_register函数,用于注册Cell类的属性 from mindspore._extends import cell_attr_register +# mindspore.communication.management模块中导入get_group_size和get_rank函数,用于获取集群中组的大小和当前节点的排名 from mindspore.communication.management import get_group_size, get_rank +# 从mindspore.communication模块中导入management类,用于处理通信相关功能 from mindspore.communication import management +# 从mindspore.common模块中导入dtype类,用于表示数据类型 from mindspore.common import dtype as mstype +# 从mindspore.parallel._utils模块中导入_is_in_auto_parallel_mode函数,用于判断当前是否为自动并行模式 from mindspore.parallel._utils import _is_in_auto_parallel_mode +# from ..cell import Cell:从当前模块(..cell)中导入Cell类,用于创建自定义Cell from ..cell import Cell __all__ = ['BatchNorm1d', 'BatchNorm2d', 'BatchNorm3d', 'LayerNorm', 'GroupNorm', @@ -39,11 +53,26 @@ __all__ = ['BatchNorm1d', 'BatchNorm2d', 'BatchNorm3d', 'LayerNorm', 'GroupNorm' SYNC_BN_GROUP_NAME = "" - +# 用于实现批量归一化操作 class _BatchNorm(Cell): """Batch Normalization base class.""" @cell_attr_register + # 初始化 + # 参数 + # num_features:输入数据的特征数量(通道数) + # eps:防止除以零的值,默认值为1e-5 + # momentum:用于更新移动平均和方差的动量系数。默认值为0.9 + # affine:一个布尔值,表示是否对输入数据进行缩放和平移,默认值为True + # gamma_init:缩放参数(gamma)的初始值,可以是'ones'、'zeros'或一个张量,默认值为'ones' + # beta_init:偏移参数(beta)的初始值,可以是'zeros'或一个张量,默认值为'zeros' + # moving_mean_init:移动平均的初始值,可以是'zeros'或一个张量,默认值为'zeros' + # moving_var_init:移动方差的初始值,可以是'ones'或一个张量,默认值为'ones' + # use_batch_statistics:一个布尔值,表示是否使用批量统计信息进行归一化,如果设置为None,则使用全局批量统计信息,默认值为None + # device_num_each_group:每个进程组的设备数量,默认值为1 + # process_groups:进程组的数量,默认值为0 + # input_dims:输入数据的维度,可以是'1d'、'2d'或'3d',默认值为'2d' + # data_format:输入数据的格式,可以是'NCHW'或'NHWC',默认值为'NCHW' def __init__(self, num_features, eps=1e-5, @@ -60,11 +89,15 @@ class _BatchNorm(Cell): data_format='NCHW'): """Initialize _BatchNorm.""" super(_BatchNorm, self).__init__() + # 检查'num_features'的类型是否为int validator.check_value_type('num_features', num_features, [int], self.cls_name) + # 如果num_features小于1 if num_features < 1: + # 抛出异常 raise ValueError(f"For '{self.cls_name}', the 'num_features' must be at least 1, but got {num_features}.") - + # 如果momentum小于0或大于1 if momentum < 0 or momentum > 1: + # 抛出异常 raise ValueError(f"For '{self.cls_name}', the 'momentum' should be a number in range [0, 1], " f"but got {momentum}.") self.input_dims = input_dims @@ -78,19 +111,28 @@ class _BatchNorm(Cell): f" but got {use_batch_statistics}.") self.num_features = num_features self.eps = eps + # 初始化moving_mean self.moving_mean = Parameter(initializer( moving_mean_init, num_features), name="mean", requires_grad=False) + # 初始化moving_variance self.moving_variance = Parameter(initializer( moving_var_init, num_features), name="variance", requires_grad=False) + # 初始化gamma self.gamma = Parameter(initializer( gamma_init, num_features), name="gamma", requires_grad=affine) + # 初始化beta self.beta = Parameter(initializer( beta_init, num_features), name="beta", requires_grad=affine) + # 检查device_num_each_group是否为正整数 self.group_device_num = validator.check_positive_int(device_num_each_group, "device_num_each_group", self.cls_name) + # 初始化process_groups self.process_groups = process_groups + # 初始化is_global self.is_global = False + # 获取并行模式 self.parallel_mode = context.get_auto_parallel_context("parallel_mode") + # 初始化SYNC_BN_GROUP_NAME global SYNC_BN_GROUP_NAME # for GlobalBatchNorm if self.group_device_num != 1: @@ -102,24 +144,40 @@ class _BatchNorm(Cell): self._create_global_groups() # for SyncBatchNorm if self.process_groups != 0: + # 获取当前进程的rank_id self.rank_id = get_rank() + # 获取当前进程的rank_size self.rank_size = get_group_size() + # 如果process_groups不为空 if self.process_groups is not None: + # 检查process_groups是否为list validator.check_isinstance("process_groups", self.process_groups, list) + # 检查rank_ids是否正确 self._check_rank_ids(self.process_groups, self.rank_size) + # 创建同步组 self._create_sync_groups() + # 如果process_groups为空,且rank_size大于1 elif self.rank_size > 1: + # 设置is_global为True self.is_global = True + # 设置group_device_num为rank_size self.group_device_num = self.rank_size + # 设置device_list self.device_list = [i for i in range(0, self.rank_size)] + # 如果设备类型为Ascend if context.get_context("device_target") == "Ascend": + # 如果SYNC_BN_GROUP_NAME为空 if SYNC_BN_GROUP_NAME == "": + # 设置SYNC_BN_GROUP_NAME SYNC_BN_GROUP_NAME = "sync_bn_group0" + # 创建同步组 management.create_group(SYNC_BN_GROUP_NAME, self.device_list) + # 如果设备类型为GPU elif context.get_context("device_target") == "GPU": + # 如果SYNC_BN_GROUP_NAME为空 if SYNC_BN_GROUP_NAME == "": + # 设置SYNC_BN_GROUP_NAME SYNC_BN_GROUP_NAME = "nccl_world_group" - self.shape = P.Shape() self.reduce_mean = P.ReduceMean(keep_dims=True) self.square = P.Square() @@ -135,57 +193,74 @@ class _BatchNorm(Cell): else: self.is_ge_backend = False + # 初始化训练模式下的batchnorm self.bn_train = P.BatchNorm(is_training=True, epsilon=self.eps, momentum=self.momentum, data_format=self.format) + # 如果是全局模式,则使用同步batchnorm if self.is_global: self.bn_train = inner.SyncBatchNorm(epsilon=self.eps, momentum=self.momentum, group=SYNC_BN_GROUP_NAME, device_num=self.group_device_num) + # 初始化测试模式下的batchnorm self.bn_infer = P.BatchNorm(is_training=False, epsilon=self.eps, data_format=self.format) + # 如果是自动并行模式,则设置数据并行策略 if _is_in_auto_parallel_mode(): data_parallel_strategy = ((1,), (1,)) data_parallel_strategy_one = ((1,), ()) else: data_parallel_strategy = None data_parallel_strategy_one = None + # 初始化减法操作 self.sub_mean = P.Sub().shard(data_parallel_strategy) self.sub_var = P.Sub().shard(data_parallel_strategy) + # 初始化乘法操作 self.mul_mean = P.Mul().shard(data_parallel_strategy_one) self.mul_var = P.Mul().shard(data_parallel_strategy_one) + # 初始化减法赋值操作 self.assign_sub_mean = P.AssignSub().shard(data_parallel_strategy) self.assign_sub_var = P.AssignSub().shard(data_parallel_strategy) - + # 检查输入数据x的维度 def _check_data_dim(self, x): + # 抛出异常 raise NotImplementedError - + # 用于根据进程号和进程组大小获取进程组中的所有进程 def list_group(self, world_rank, group_size): """ Check whether world_rank and group_size are valid. """ + # 如果进程组的进程数量大于本地进程数量 if group_size > get_group_size(): + # 抛出异常 raise ValueError(f"For '{self.cls_name}', the 'device_num_each_group' cannot be greater than " f"local rank size, but got 'device_num_each_group': {group_size}, " f"local rank size: {get_group_size()}.") + # 如果world_rank列表的长度不可以被group_size整除 if len(world_rank) % group_size != 0: + # 抛出异常 raise ValueError(f"For '{self.cls_name}', the dimension of device_list should be divisible by " f"'device_num_each_group', but got the length of device_list: {len(world_rank)}, " f"'device_num_each_group': {group_size}.") + # 将world_rank按照group_size分组,并将其转换为list world_rank_list = zip(*(iter(world_rank),) * group_size) group_list = [list(i) for i in world_rank_list] return group_list - + # 用于检查进程组和本地进程数量是否匹配 def _check_rank_ids(self, process_groups, rank_size): seen = set() for rid in itertools.chain(*process_groups): + # 检查rank id是否在0到rank_size之间 validator.check_int_range(rid, 0, rank_size, Rel.INC_LEFT, "rank id in process_groups", self.cls_name) + # 检查process_groups中是否有重复的rank id if rid in seen: + #抛出异常 raise ValueError(f"For '{self.cls_name}', rank id in 'process_groups' should not be duplicated, " f"but got {process_groups}.") seen.add(rid) - + # 用于创建一个包含所有进程的全局进程组 def _create_global_groups(self): + # 循环遍历self.rank_list_idx for i in range(self.rank_list_idx): if self.rank_id in self.rank_list[i]: self.is_global = True @@ -193,34 +268,46 @@ class _BatchNorm(Cell): if SYNC_BN_GROUP_NAME == "": SYNC_BN_GROUP_NAME = "sync_bn_group%d" % i management.create_group(SYNC_BN_GROUP_NAME, self.rank_list[i]) - + # 用于创建一个包含所有进程的同步进程组 def _create_sync_groups(self): - for i in range(len(self.process_groups)): + # 遍历process_groups列表 + for i in range(len(self.process_groups)): + # 检查"process_groups[%d]" validator.check_isinstance("process_groups[%d]" % i, self.process_groups[i], list) + # 获取process_groups列表中每一个元素的长度 self.group_device_num = len(self.process_groups[i]) + # 检查rank_id是否在process_groups列表中,并且列表中元素的长度大于1 if self.rank_id in self.process_groups[i] and self.group_device_num > 1: + # 设置is_global为True self.is_global = True + # 设置SYNC_BN_GROUP_NAME global SYNC_BN_GROUP_NAME if SYNC_BN_GROUP_NAME == "": SYNC_BN_GROUP_NAME = "sync_bn_group%d" % i + # 创建一个组 management.create_group(SYNC_BN_GROUP_NAME, self.process_groups[i]) def construct(self, x): + # 检查输入张量的形状是否与Batch Normalization层的参数匹配 _shape_check_bn(self.shape(x), self.input_dims, self.cls_name) + # 如果self.use_batch_statistics为None,则根据self.training的值来决定是训练模式还是推理模式 if self.use_batch_statistics is None: if self.training: + # 如果是训练模式,则调用bn_train函数 return self.bn_train(x, self.gamma, self.beta, self.moving_mean, self.moving_variance)[0] if not self.training: + # 如果是推理模式,则调用bn_infer函数 return self.bn_infer(x, self.gamma, self.beta, self.moving_mean, self.moving_variance)[0] + # 如果self.use_batch_statistics为True,则调用bn_train函数 if self.use_batch_statistics: return self.bn_train(x, self.gamma, @@ -228,6 +315,7 @@ class _BatchNorm(Cell): self.moving_mean, self.moving_variance)[0] + # 如果self.use_batch_statistics为False,则调用bn_infer函数 return self.bn_infer(x, self.gamma, self.beta, @@ -240,47 +328,65 @@ class _BatchNorm(Cell): @constexpr +# 检查输入通道数是否与num_channel相等 def _channel_check(channel, num_channel, prim_name=None): msg_prefix = f"For '{prim_name}', the" if prim_name else "The" + # 如果不相等 if channel != num_channel: + # 抛出异常 raise ValueError(f"{msg_prefix} channel(the second dim of the input 'x') should be equal to num_channels, " f"but got channel: {channel}, num_channels: {num_channel}.") @constexpr +# 检查输入shape是否为4维 def _shape_check(in_shape, prim_name=None): msg_prefix = f"For '{prim_name}', the" if prim_name else "The" + # 如果不是4维 if len(in_shape) != 4: + # 抛出异常 raise ValueError(f"{msg_prefix} in_shape must has 4 dims, but got the length of in_shape: {len(in_shape)}.") @constexpr +# 检查输入维度是否符合batch norm的要求 def _shape_check_bn(in_shape, in_dims, prim_name=None): """check input dims of batch norm.""" msg_prefix = f"For '{prim_name}', the" if prim_name else "The" dim = len(in_shape) + # 如果in_dims是1维,dims不是2维 if in_dims == '1d' and dim != 2: + # 抛出异常 raise ValueError(f"{msg_prefix} in_shape must have 2 dims, but got {len(in_shape)}.") + # 如果in_dims是2维,dims不是4维 if in_dims == '2d' and dim != 4: + # 抛出异常 raise ValueError(f"{msg_prefix} in_shape must have 4 dims, but got {len(in_shape)}.") + # 如果in_dims是3维,dims不是5维 if in_dims == '3d' and dim != 5: + # 抛出异常 raise ValueError(f"{msg_prefix} in_shape must have 5 dims, but got {len(in_shape)}.") + # 如果in_dims是both并且dim不是2维和4维 if in_dims == 'both' and dim != 2 and dim != 4: + # 抛出异常 raise ValueError(f"{msg_prefix} in_shape must have 2 dims or 4 dims, but got {len(in_shape)}.") @constexpr +# 定义一个函数,用于推断输入x的形状和axes def _shape_infer(x_shape, num_feature): """global Batch Normalization shape and axes infer""" + # 如果输入x的形状为4,则axes为(0,2,3),re_shape为(1,num_feature,1,1) if len(x_shape) == 4: axes = (0, 2, 3) re_shape = (1, num_feature, 1, 1) + # 如果输入x的形状不为4,则axes为(0,),re_shape为(1,num_feature) else: axes = (0,) re_shape = (1, num_feature) + # 返回axes和re_shape return axes, re_shape - - +# 在二维或三维输入(mini-batch 一维输入或二维输入)上应用批归一化(Batch Normalization Layer),避免内部协变量偏移 class BatchNorm1d(_BatchNorm): r""" Batch Normalization layer over a 2D input. @@ -373,7 +479,7 @@ class BatchNorm1d(_BatchNorm): if x.ndim != 2: pass - +# 在四维输入(具有额外通道维度的小批量二维输入)上应用批归一化处理(Batch Normalization Layer),以避免内部协变量偏移 class BatchNorm2d(_BatchNorm): r""" Batch Normalization layer over a 4D input. @@ -459,7 +565,7 @@ class BatchNorm2d(_BatchNorm): [[ 0.999995 0.999995 ] [ 0.999995 0.999995 ]]]] """ - + # 参数与第一个类中的init类似 def __init__(self, num_features, eps=1e-5, @@ -490,18 +596,22 @@ class BatchNorm2d(_BatchNorm): @constexpr +# 检查输入张量是否为5维 def _check_3d_shape(input_shape, prim_name=None): msg_prefix = f"For '{prim_name}', the" if prim_name else "The" + # 如果不是5维 if len(input_shape) != 5: + # 抛出异常 raise ValueError(f"{msg_prefix} input_shape must be 5-dimensional, but got the length of input_shape: " f"{len(input_shape)}.") @constexpr def _check_dtype(dtype, valid_dtypes, args_name, prim_name=None): + # 检查args_name的类型是否为dtype,并且是否在valid_dtypes中 validator.check_type_name(args_name, dtype, valid_dtypes, prim_name) - +# 在二维或三维输入(mini-batch 一维输入或二维输入)上应用批归一化(Batch Normalization Layer),避免内部协变量偏移 class BatchNorm3d(Cell): r""" Batch Normalization layer over a 5D input. @@ -566,7 +676,7 @@ class BatchNorm3d(Cell): >>> print(output.shape) (16, 3, 10, 32, 32) """ - + # 参数与第一个类中的init类似 def __init__(self, num_features, eps=1e-5, @@ -595,13 +705,17 @@ class BatchNorm3d(Cell): def construct(self, input_x): x_shape = F.shape(input_x) + # 检查x的形状是否是3维 _check_3d_shape(x_shape, self.cls_name) input_x = self.reshape(input_x, (x_shape[0], x_shape[1], x_shape[2] * x_shape[3], x_shape[4])) + # 用bn2d来处理input_x bn2d_out = self.bn2d(input_x) + # 用reshape方法将张量转换为原来形状 bn3d_out = self.reshape(bn2d_out, x_shape) + # 返回bn3d_out return bn3d_out - +# 用于处理全局输入的张量 class GlobalBatchNorm(_BatchNorm): r""" The GlobalBatchNorm interface is deprecated, please use the :class:`mindspore.nn.SyncBatchNorm` instead. @@ -609,7 +723,8 @@ class GlobalBatchNorm(_BatchNorm): Supported Platforms: deprecated """ - + + # 参数与第一个类中相似 @deprecated("1.2", "SyncBatchNorm", True) def __init__(self, num_features, @@ -634,17 +749,22 @@ class GlobalBatchNorm(_BatchNorm): use_batch_statistics, device_num_each_group, input_dims='both') + # 检查device_num_each_group是否为正数 self.group_device_num = validator.check_positive_int(device_num_each_group, "device_num_each_group", self.cls_name) + # 如果device_num_each_group小于等于1 if self.group_device_num <= 1: + # 抛出异常 raise ValueError(f"For '{self.cls_name}', the 'device_num_each_group' must be greater than 1, " f"but got {self.group_device_num}.") - + def _check_data_dim(self, x): + # 检查输入维度 if x.dim == 0: pass - +# 主要用于处理分布式输入的张量 +# 在N维输入上进行跨设备同步批归一化(Batch Normalization,BN) class SyncBatchNorm(_BatchNorm): r""" Sync Batch Normalization layer over a N-dimension input. @@ -733,7 +853,7 @@ class SyncBatchNorm(_BatchNorm): [[ 0.999995 0.999995 ] [ 0.999995 0.999995 ]]]] """ - + # 参数和第一个类相似 def __init__(self, num_features, eps=1e-5, @@ -762,7 +882,8 @@ class SyncBatchNorm(_BatchNorm): if x.dim == 0: pass - +# 用于处理输入张量,并实现层归一化 +# 在mini-batch输入上应用层归一化(Layer Normalization) class LayerNorm(Cell): r""" Applies Layer Normalization over a mini-batch of inputs. @@ -827,17 +948,21 @@ class LayerNorm(Cell): ): """Initialize LayerNorm.""" super(LayerNorm, self).__init__() + # 检查normalized_shape是否为元组或列表 if not isinstance(normalized_shape, (tuple, list)): + # 抛出异常 raise TypeError(f"For '{self.cls_name}', the type of 'normalized_shape' should be tuple[int] or list[int], " f"but got {normalized_shape} and the type is {type(normalized_shape)}.") self.normalized_shape = normalized_shape self.begin_norm_axis = begin_norm_axis self.begin_params_axis = begin_params_axis self.epsilon = epsilon + # 初始化gamma和beta self.gamma = Parameter(initializer( gamma_init, normalized_shape), name="gamma") self.beta = Parameter(initializer( beta_init, normalized_shape), name="beta") + # 初始化LayerNorm self.layer_norm = P.LayerNorm(begin_norm_axis=self.begin_norm_axis, begin_params_axis=self.begin_params_axis, epsilon=self.epsilon) @@ -850,7 +975,7 @@ class LayerNorm(Cell): return 'normalized_shape={}, begin_norm_axis={}, begin_params_axis={}, gamma{}, beta={}'.format( self.normalized_shape, self.begin_norm_axis, self.begin_params_axis, self.gamma, self.beta) - +# 该层在四维输入(带有额外通道维度的mini-batch二维输入)上应用实例归一化 class InstanceNorm2d(Cell): r""" Instance Normalization layer over a 4D input. @@ -931,9 +1056,13 @@ class InstanceNorm2d(Cell): beta_init='zeros'): """Initialize InstanceNorm2d.""" super(InstanceNorm2d, self).__init__() + # 检查'num_features'是否为int型 validator.check_value_type('num_features', num_features, [int], self.cls_name) + # 检查'eps'是否为float型 validator.check_value_type('eps', eps, [float], self.cls_name) + # 检查'momentum'是否为float型 validator.check_value_type('momentum', momentum, [float], self.cls_name) + # 检查'affine'是否为bool型 validator.check_value_type('affine', affine, [bool], self.cls_name) args_input = {"gamma_init": gamma_init, "beta_init": beta_init} self.check_types_valid(args_input, 'InstanceNorm2d') @@ -946,21 +1075,27 @@ class InstanceNorm2d(Cell): self.num_features = num_features self.eps = eps self.input_dims = '2d' + # 初始化moving_mean self.moving_mean = Parameter(initializer('zeros', num_features), name="mean", requires_grad=False) + # 初始化moving_variance self.moving_variance = Parameter(initializer('ones', num_features), name="variance", requires_grad=False) + # 初始化gamma self.gamma = Parameter(initializer( gamma_init, num_features), name="gamma", requires_grad=affine) + # 初始化beta self.beta = Parameter(initializer( beta_init, num_features), name="beta", requires_grad=affine) self.shape = P.Shape() self.momentum = momentum self.instance_bn = P.InstanceNorm(epsilon=self.eps, momentum=self.momentum) - + # 检查输入张量的维度是否为1 def _check_data_dim(self, x): + # 抛出异常NotImplementedError raise NotImplementedError def construct(self, x): + # 检查输入张量的形状是否与LayerNorm类的input_dims参数匹配 _shape_check_bn(self.shape(x), self.input_dims, self.cls_name) return self.instance_bn(x, self.gamma, @@ -973,16 +1108,21 @@ class InstanceNorm2d(Cell): self.num_features, self.eps, self.momentum, self.gamma, self.beta, self.moving_mean, self.moving_variance) def check_types_valid(self, args_dict, name): - for key, _ in args_dict.items(): + # 遍历 + for key, _ in args_dict.items(): val = args_dict[key] + # 检查每个键值的类型是否为Tensor、numbers.Number、str或Initializer if not isinstance(val, (Tensor, numbers.Number, str, Initializer)): + # 抛出异常 raise TypeError(f"For '{self.cls_name}', the type of '{key}' should be in " f"[Tensor, numbers.Number, str, Initializer], but got type {type(val).__name__}.") + # 检查了Tensor类型的值是否为float32 if isinstance(val, Tensor) and val.dtype != mstype.float32: + # 抛出异常 raise TypeError(f"For '{self.cls_name}', the type of '{key}' should be float32, " f"but got {val.dtype}.") - - +# 主要用于处理输入张量,并实现组归一化 +# 在mini-batch输入上进行组归一化 class GroupNorm(Cell): r""" Group Normalization over a mini-batch of inputs. @@ -1050,12 +1190,15 @@ class GroupNorm(Cell): self.eps = validator.check_value_type('eps', eps, (float,), type(self).__name__) self.affine = validator.check_bool(affine, arg_name="affine", prim_name=self.cls_name) + # 初始化gamma和beta self.gamma = Parameter(initializer( gamma_init, num_channels), name="gamma", requires_grad=affine) self.beta = Parameter(initializer( beta_init, num_channels), name="beta", requires_grad=affine) + # 初始化shape和reshape self.shape = F.shape self.reshape = F.reshape + # 初始化reduce_mean、square、reduce_sum和sqrt self.reduce_mean = P.ReduceMean(keep_dims=True) self.square = F.square self.reduce_sum = P.ReduceSum(keep_dims=True) @@ -1063,19 +1206,29 @@ class GroupNorm(Cell): def _cal_output(self, x): """calculate groupnorm output""" + # 检查输入的形状 batch, channel, height, width = self.shape(x) + # 检查输入的通道数 _channel_check(channel, self.num_channels, self.cls_name) + # 将输入的形状转换为(batch, num_groups, -1) x = self.reshape(x, (batch, self.num_groups, -1)) + # 计算均值 mean = self.reduce_mean(x, 2) + # 计算方差 var = self.reduce_sum(self.square(x - mean), 2) / (channel * height * width / self.num_groups) + # 计算标准差 std = self.sqrt(var + self.eps) + # 将均值和标准差应用到输入中 x = (x - mean) / std + # 将输入的形状转换为(batch, channel, height, width) x = self.reshape(x, (batch, channel, height, width)) + # 计算输出 output = x * self.reshape(self.gamma, (-1, 1, 1)) + self.reshape(self.beta, (-1, 1, 1)) return output def construct(self, x): _shape_check(self.shape(x), self.cls_name) + # 检查x.dtype是否为float16或float32 _check_dtype(x.dtype, [mstype.float16, mstype.float32], "input", self.cls_name) output = self._cal_output(x) return output diff --git a/mindspore/python/mindspore/nn/layer/pooling.py b/mindspore/python/mindspore/nn/layer/pooling.py index 8ad066cf6ff..2dbaaedf5ca 100644 --- a/mindspore/python/mindspore/nn/layer/pooling.py +++ b/mindspore/python/mindspore/nn/layer/pooling.py @@ -13,45 +13,68 @@ # limitations under the License. # ============================================================================ """pooling""" +# 从mindspore.ops模块中导入operations(操作)类,用于定义各种计算图操作 from mindspore.ops import operations as P +# 从mindspore.ops模块中导入functional(功能)类,用于提供一些实用的功能函数,如F.reduce_sum、F.shape等 from mindspore.ops import functional as F +# 从mindspore._checkparam模块中导入Rel和Validator类,用于验证参数的合法性 from mindspore._checkparam import Rel, Validator as validator +# 从mindspore.ops.primitive模块中导入constexpr函数,用于在编译时计算常量表达式 from mindspore.ops.primitive import constexpr +# 从mindspore.context模块中导入context类,用于设置计算图的运行环境 import mindspore.context as context +# 从..cell模块中导入Cell类,用于创建自定义的神经网络层 from ..cell import Cell __all__ = ['AvgPool2d', 'MaxPool2d', 'AvgPool1d', 'MaxPool1d'] - +# 用于实现各种池化操作,如最大池化、平均池化、L2池化等 class _PoolNd(Cell): """N-D AvgPool""" def __init__(self, kernel_size, stride, pad_mode, data_format="NCHW"): """Initialize _PoolNd.""" super(_PoolNd, self).__init__() + # 检查'pad_mode'的类型是否为str validator.check_value_type('pad_mode', pad_mode, [str], self.cls_name) self.pad_mode = validator.check_string(pad_mode.upper(), ['VALID', 'SAME'], 'pad_mode', self.cls_name) + # 检查输入的data_format是否为NCHW或NHWC self.format = validator.check_string(data_format, ['NCHW', 'NHWC'], 'format', self.cls_name) + # 如果设备目标不是GPU,而data_format为NHWC if context.get_context("device_target") != "GPU" and self.format == "NHWC": + # 抛出异常 raise ValueError(f"For '{self.cls_name}, the 'NHWC' format only support in GPU target, but got device " f"target {context.get_context('device_target')}.") def _check_int_or_tuple(arg_name, arg_value): + # 检查arg_name和arg_value是否为int和tuple validator.check_value_type(arg_name, arg_value, [int, tuple], self.cls_name) error_msg = f"For '{self.cls_name}', the '{arg_name}' should be an positive int number or " \ f"a tuple of two positive int numbers, but got {arg_value}" + # 检查arg_value是否为int if isinstance(arg_value, int): + # 如果arg_value小于等于0 if arg_value <= 0: + # 抛出异常 raise ValueError(error_msg) + # 如果arg_value的长度等于2 elif len(arg_value) == 2: + # 循环遍历 for item in arg_value: + # 如果都大于0 if isinstance(item, int) and item > 0: + # 则继续 continue + # 否则抛出异常 raise ValueError(error_msg) else: + # 抛出异常 raise ValueError(error_msg) + + # 返回arg_value return arg_value + # 检查kernel_size和stride的值是否为正整数或者元组 self.kernel_size = _check_int_or_tuple('kernel_size', kernel_size) self.stride = _check_int_or_tuple('stride', stride) @@ -68,7 +91,8 @@ def _shape_check(in_shape, prim_name=None): if len(in_shape) != 3: raise ValueError(f"{msg_prefix} input must has 3 dim, but got {len(in_shape)}") - +# 在一个输入Tensor上应用1D最大池化运算,该Tensor可被视为一维平面的组合 +# 用于实现2维最大池化操作 class MaxPool2d(_PoolNd): r""" 2D max pooling operation for temporal data. @@ -130,7 +154,11 @@ class MaxPool2d(_PoolNd): >>> print(output.shape) (1, 2, 2, 2) """ - + # 参数 + # kernel_size (int): 池化核大小,即池化操作的窗口大小,默认值为 1 + # stride (int): 池化步长,即在输入特征图上每次滑动的距离,默认值为 1 + # pad_mode (str): 填充模式,表示在边界处如何填充,可选值有 "valid"(有效池化)、"same"(等效池化)和 "pad"(填充池化),默认值为 "valid" + # data_format (str): 数据格式,表示输入特征图的数据 layout。可选值有 "NCHW"(通道在前,深度在后)和 "NHWC"(通道在后,深度在前),默认值为 "NCHW" def __init__(self, kernel_size=1, stride=1, pad_mode="valid", data_format="NCHW"): """Initialize MaxPool2d.""" super(MaxPool2d, self).__init__(kernel_size, stride, pad_mode, data_format) @@ -140,10 +168,14 @@ class MaxPool2d(_PoolNd): data_format=self.format) def construct(self, x): + # 将输入特征图x传递给max_pool out = self.max_pool(x) + # 返回得到out return out - + +# 在一个输入Tensor上应用1D LP池化运算,可被视为组成一个1D输入平面 +# 用于实现1维最大池化操作 class MaxPool1d(_PoolNd): r""" 1D max pooling operation for temporal data. @@ -203,30 +235,42 @@ class MaxPool1d(_PoolNd): def __init__(self, kernel_size=1, stride=1, pad_mode="valid"): """Initialize MaxPool1d.""" super(MaxPool1d, self).__init__(kernel_size, stride, pad_mode) + # 检查'kernel_size'是否为int validator.check_value_type('kernel_size', kernel_size, [int], self.cls_name) + # 检查'stride'是否为int validator.check_value_type('stride', stride, [int], self.cls_name) + # 检查pad_mode'是否为str validator.check_value_type('pad_mode', pad_mode, [str], self.cls_name) self.pad_mode = validator.check_string(pad_mode.upper(), ['VALID', 'SAME'], 'pad_mode', self.cls_name) + # 检查kernel_size和stride是否为正整数 validator.check_int(kernel_size, 1, Rel.GE, "kernel_size", self.cls_name) validator.check_int(stride, 1, Rel.GE, "stride", self.cls_name) + # 设置卷积核宽高为1 self.kernel_size = (1, kernel_size) + # 设置步长为1 self.stride = (1, stride) + # 创建MaxPool层 self.max_pool = P.MaxPool(kernel_size=self.kernel_size, strides=self.stride, pad_mode=self.pad_mode) + # 设置shape来获取张量形状 self.shape = F.shape + # 创建一个ReduceMean层 self.reduce_mean = P.ReduceMean(keep_dims=True) + # 创建一个ExpandDims层 self.expand = P.ExpandDims() + # 创建一个Squeeze层 self.squeeze = P.Squeeze(2) def construct(self, x): + # 检查形状 _shape_check(self.shape(x), self.cls_name) x = self.expand(x, 2) output = self.max_pool(x) output = self.squeeze(output) return output - - +# 在输入Tensor上应用2D平均池化运算,可视为二维输入平面的组合 +# 用于实现2维平均池化的类 class AvgPool2d(_PoolNd): r""" 2D average pooling for temporal data. @@ -289,7 +333,11 @@ class AvgPool2d(_PoolNd): >>> print(output.shape) (1, 2, 2, 2) """ - + # 参数 + # kernel_size:表示卷积核的宽度和高度,默认为1 + # stride:表示卷积核在宽度和高度方向上的步长,默认为1 + # pad_mode:表示填充模式,默认为"valid",表示不填充,还可以选择"same"或"ceil"模式 + # data_format:表示输入张量的数据格式,默认为"NCHW",表示通道在第一个维度,高度和宽度在第二个维度 def __init__(self, kernel_size=1, stride=1, @@ -305,7 +353,9 @@ class AvgPool2d(_PoolNd): def construct(self, x): return self.avg_pool(x) - + +# 在一个输入Tensor上应用1D平均池化运算,可被视为组成一个1D输入平面 +# 用于实现1维平均池化 class AvgPool1d(_PoolNd): r""" 1D average pooling for temporal data. @@ -368,6 +418,7 @@ class AvgPool1d(_PoolNd): stride=1, pad_mode="valid"): """Initialize AvgPool1d.""" + # 该模块注释与MaxPool1d(_PoolNd)中init类似 validator.check_value_type('kernel_size', kernel_size, [int], self.cls_name) validator.check_value_type('stride', stride, [int], self.cls_name) validator.check_value_type('pad_mode', pad_mode, [str], self.cls_name) @@ -388,14 +439,24 @@ class AvgPool1d(_PoolNd): def construct(self, x): x = F.depend(x, _shape_check(self.shape(x), self.cls_name)) + # 获取输入x的batch, channel, width batch, channel, width = self.shape(x) + # 如果输入x的宽度等于卷积核的宽度 if width == self.kernel_size[1]: + # 将输入x的宽度进行平均池化 x = self.reduce_mean(x, 2) + # 如果输入x的宽度减去卷积核的宽度小于步长 elif width - self.kernel_size[1] < self.stride[1]: + # 将输入x的宽度进行切片,取卷积核的宽度 x = self.slice(x, (0, 0, 0), (batch, channel, self.kernel_size[1])) + # 将输入x的宽度进行平均池化 x = self.reduce_mean(x, 2) else: + # 将输入x的宽度进行扩展 x = self.expand(x, 2) + # 将输入x的宽度进行平均池化 x = self.avg_pool(x) + # 将输入x的宽度进行 squeezing x = self.squeeze(x) + # 返回输入x return x diff --git a/mindspore/python/mindspore/nn/layer/quant.py b/mindspore/python/mindspore/nn/layer/quant.py index a5ee6b158ed..a220d09d2ff 100644 --- a/mindspore/python/mindspore/nn/layer/quant.py +++ b/mindspore/python/mindspore/nn/layer/quant.py @@ -13,25 +13,40 @@ # limitations under the License. # ============================================================================ """Quantization aware training.""" - +# 从functools模块中导入partial函数,用于创建一个新函数,该函数将使用给定的参数替换原函数中的参数 from functools import partial +# 从collections模块中导入namedtuple类,用于创建一个具有命名的元素的元组 from collections import namedtuple +# 导入numpy模块,用于处理数值计算 import numpy as np +# 导入mindspore.common.dtype模块,用于处理数据类型 import mindspore.common.dtype as mstype +# 从mindspore.ops.primitive模块中导入Primitive类,用于定义一个基本的计算图操作 from mindspore.ops.primitive import Primitive +# 从mindspore.ops模块中导入operations类,用于定义各种计算图操作 from mindspore.ops import operations as P +# 从mindspore.common.parameter模块中导入Parameter类,用于表示神经网络中的参数 from mindspore.common.parameter import Parameter +# 从mindspore.common.initializer模块中导入initializer函数,用于初始化神经网络参数 from mindspore.common.initializer import initializer +# 从mindspore.common.tensor模块中导入Tensor类,用于表示神经网络中的张量 from mindspore.common.tensor import Tensor +# 从mindspore._checkparam模块中导入Validator类,用于验证参数的合法性 from mindspore._checkparam import Validator, twice +# 从mindspore.compression.common模块中导入QuantDtype类,用于表示量化数据类型 from mindspore.compression.common import QuantDtype +# 导入mindspore.context模块,用于设置计算图的运行环境 import mindspore.context as context +# 从.normalization模块中导入BatchNorm2d类,用于实现批量归一化 from .normalization import BatchNorm2d +# 从.activation模块中导入get_activation函数,用于获取激活函数 from .activation import get_activation +# 从..cell模块中导入Cell类,用于创建自定义的神经网络层 from ..cell import Cell +# 从...模块中导入nn和ops模块,用于使用MindSpore框架中的其他功能 from ... import nn from ...ops.operations import _quant_ops as Q - +# 为quantization模块提供一个导出接口 __all__ = [ 'FakeQuantWithMinMaxObserver', 'Conv2dBnFoldQuantOneConv', @@ -44,7 +59,7 @@ __all__ = [ 'MulQuant', ] - +# 用于对卷积层和批量归一化层进行fold quantization class BatchNormFoldCell(Cell): """ Batch Normalization folded. @@ -77,26 +92,34 @@ class BatchNormFoldCell(Cell): self.epsilon = epsilon self.is_gpu = context.get_context('device_target') == "GPU" if self.is_gpu: + # 创建一个Q.BatchNormFold实例,用于训练时使用 self.bn_train = Q.BatchNormFold(momentum, epsilon, is_training=True, freeze_bn=freeze_bn) + # 创建一个Q.BatchNormFold实例,用于推理时使用 self.bn_infer = Q.BatchNormFold(momentum, epsilon, is_training=False, freeze_bn=freeze_bn) else: + # 创建一个P.BNTrainingReduce实例,用于训练时使用 self.bn_reduce = P.BNTrainingReduce() + # 创建一个Q.BatchNormFoldD实例,用于训练时使用 self.bn_update = Q.BatchNormFoldD(momentum, epsilon, is_training=True, freeze_bn=freeze_bn) def construct(self, x, mean, variance, global_step): if self.is_gpu: if self.training: + # 计算训练时的batch_mean, batch_std, running_mean, running_std batch_mean, batch_std, running_mean, running_std = self.bn_train(x, mean, variance, global_step) else: + # 计算推断时的batch_mean, batch_std, running_mean, running_std batch_mean, batch_std, running_mean, running_std = self.bn_infer(x, mean, variance, global_step) else: if self.training: + # 计算训练时的x_sum, x_square_sum, batch_mean, batch_std, running_mean, running_std x_sum, x_square_sum = self.bn_reduce(x) _, batch_mean, batch_std, running_mean, running_std, mean_updated, variance_updated = \ self.bn_update(x, x_sum, x_square_sum, mean, variance) P.Assign()(mean, mean_updated) P.Assign()(variance, variance_updated) else: + # 计算推断时的batch_mean, batch_std, running_mean, running_std batch_mean = P.ZerosLike()(variance) batch_std = P.OnesLike()(variance) running_mean = P.Add()(mean, 0.) @@ -124,6 +147,7 @@ def _partial_init(cls_or_self, **kwargs): False """ +# 定义一个_PartialWrapper类,用于创建类工厂 class _PartialWrapper: r""" class of wrapper that allows creation of class factories. @@ -140,10 +164,13 @@ def _partial_init(cls_or_self, **kwargs): partial_init = _partial_init +# 定义一个_PartialWrapper类,用于创建类 r = _PartialWrapper(partial(cls_or_self, **kwargs)) return r +# 用于实现观察器功能 +# 用于观察模型中的最小值和最大值,并根据这些值计算量化参数 class _Observer(Cell): """ Base class of Observer. Observer is used to calculate the statistics of specific layer. @@ -184,7 +211,8 @@ class UniformQuantObserver(_Observer): Returns: Tensor. """ - +# 用于存储不同quantization数据类型的最小值和最大值范围 +# 这些范围用于计算quantization参数 min_max_map = { QuantDtype.INT2: (-2, 1), QuantDtype.INT3: (-4, 3), @@ -207,12 +235,17 @@ class UniformQuantObserver(_Observer): num_channels=1): """Initialize UniformQuantObserver.""" super(UniformQuantObserver, self).__init__(quant_dtype) + # 是否每个通道独立量化 self.per_channel = per_channel + # 是否使用对称量化 self.symmetric = symmetric + # 是否使用窄量化范围 self.narrow_range = narrow_range + # 量化通道数 self.num_channels = num_channels - +# 用于实现fold quantization +# 通过继承UniformQuantObserver并重写_calculate_qparams方法来实现 class FakeQuantWithMinMaxObserver(UniformQuantObserver): r""" Quantization aware operation which provides the fake quantization observer function on data with min and max. @@ -366,7 +399,20 @@ class FakeQuantWithMinMaxObserver(UniformQuantObserver): [[ 0.9882355 1.9764705 0.9882355] [-1.9764705 0. -0.9882355]] """ - + # 参数 + # min_init:初始化最小值,默认为-6 + # max_init:初始化最大值,默认为6 + # ema:是否使用指数移动平均来计算quantization参数,默认为False + # ema_decay:指数移动平均的衰减系数,默认为0.999 + # per_channel:是否对每个通道独立进行quantization,默认为False + # channel_axis:通道轴的位置,默认为1 + # num_channels:通道数,默认为1 + # quant_dtype:quantization数据类型,默认为QuantDtype.INT8 + # symmetric:是否使用对称quantization,默认为False + # narrow_range:是否使用较小的quantization范围,默认为False + # quant_delay:批量归一化后的quantization延迟步数,默认为0 + # neg_trunc:是否对负数进行截断,默认为False + # ode:量化模式,默认为"DEFAULT" def __init__(self, min_init=-6, max_init=6, @@ -385,9 +431,13 @@ class FakeQuantWithMinMaxObserver(UniformQuantObserver): super(FakeQuantWithMinMaxObserver, self).__init__(quant_dtype=quant_dtype, per_channel=per_channel, symmetric=symmetric, narrow_range=narrow_range, num_channels=num_channels) + # 检查"min_init"的类型是否为int、float或list Validator.check_value_type("min_init", min_init, [int, float, list], type(self).__name__) + # 检查"max_init"的类型是否为int、float或list Validator.check_value_type("max_init", max_init, [int, float, list], type(self).__name__) + # 检查quant_delay是否为非负整数 Validator.check_non_negative_int(quant_delay, 'quant_delay', self.cls_name) + # 初始化参数 self.min_init = min_init self.max_init = max_init self.quant_dtype = quant_dtype @@ -405,19 +455,28 @@ class FakeQuantWithMinMaxObserver(UniformQuantObserver): self.is_ascend = context.get_context('device_target') == "Ascend" self.Neg = P.Neg() + # 给下面的每一行代码都添加中文注释返回完整的代码 min_array = self._get_init_array(self.min_init) max_array = self._get_init_array(self.max_init) + # 检查max_array是否大于min_array if not np.greater(max_array, min_array).all(): + # 如果不是,抛出异常 raise ValueError(f"For '{self.cls_name}', the 'max_init' should be greater than 'min_init', " f"but got 'max_init': {max_init}, 'min_init': {min_init}.") + # 如果mode是DEFAULT if self.mode == "DEFAULT": + # 调用_default_init函数 self._default_init(min_array, max_array) + # 如果mode是LEARNED_SCALE elif self.mode == "LEARNED_SCALE": + # 调用_learned_scale_init函数 self._learned_scale_init(min_array, max_array) + # 如果mode不是DEFAULT和LEARNED_SCALE else: + # 抛出异常 raise ValueError(f"For '{self.cls_name}', only `DEFAULT` and `LEARNED_SCALE` mode are valid, but got " f"'mode': {self.mode}.") - + # 用于重置模型的权重和激活值 def reset(self, quant_dtype=QuantDtype.INT8, min_init=-6, max_init=6): r""" Reset the quant max parameter (eg. 256) and the initial value of the minq parameter and maxq parameter, @@ -429,26 +488,41 @@ class FakeQuantWithMinMaxObserver(UniformQuantObserver): max_init (int, float, list): The initialized max value. Default: 6. """ if self.mode == "LEARNED_SCALE": + # 设置量化数据类型 self.quant_dtype = quant_dtype + # 获取量化数据类型的位数 self.num_bits = quant_dtype.num_bits + # 计算量化最大值 self._calculate_quant_max() + # 如果负截断为真 if self.neg_trunc: + # 最小初始值为0 min_init = 0 + # 设置最小初始值 self.min_init = min_init + # 设置最大初始值 self.max_init = max_init + # 获取初始最小值数组 min_array = self._get_init_array(self.min_init) + # 获取初始最大值数组 max_array = self._get_init_array(self.max_init) + # 如果最大值数组不全部大于最小值数组 if not np.greater(max_array, min_array).all(): + # 抛出异常 raise ValueError(f"For '{self.cls_name}', the 'max_init' should be greater than 'min_init', " f"but got 'max_init': {max_init}, 'min_init': {min_init}.") + # 设置最小值 self.minq.set_data(Tensor(min_array)) + # 设置最大值 self.maxq.set_data(Tensor(max_array)) + # 设置量化最大值 self.quant_max.set_data(Tensor(np.array([self._quant_max]).astype(np.float32))) else: + # 如果模式不是LEARNED_SCALE,抛出异常 raise ValueError(f"For '{self.cls_name}', only `LEARNED_SCALE` mode is valid, but got 'mode': {self.mode}.") - + # 用于初始化模型的权重和激活值 def _default_init(self, min_array, max_array): """ Initialization of `DEFAULT`(QAT) mode. @@ -465,14 +539,18 @@ class FakeQuantWithMinMaxObserver(UniformQuantObserver): quant_fun = Q.FakeQuantPerLayer ema_fun = Q.MinMaxUpdatePerLayer + # 初始化EMA更新函数 self.ema_update = ema_fun(ema=self.ema, ema_decay=self.ema_decay) + # 判断是否为Ascend环境 if self.is_ascend: + # 训练时使用fake_quant_train,推理时使用fake_quant_infer self.fake_quant_train = quant_fun(num_bits=self.quant_dtype.num_bits, symmetric=self.symmetric, narrow_range=self.narrow_range, quant_delay=self.quant_delay) self.fake_quant_infer = self.fake_quant_train else: + # 训练时使用quant_fun,推理时使用quant_fun quant_fun = partial(quant_fun, ema=self.ema, ema_decay=self.ema_decay, @@ -482,38 +560,45 @@ class FakeQuantWithMinMaxObserver(UniformQuantObserver): quant_delay=self.quant_delay) self.fake_quant_train = quant_fun(training=True) self.fake_quant_infer = quant_fun(training=False) - + # 用于初始化模型的权重和激活值,并根据学习到的缩放因子进行缩放 def _learned_scale_init(self, min_array, max_array): """ Initialization of `LEARNED_SCALE` mode. """ + # 检查symmetric参数是否设置为True,如果为False if not self.symmetric: + # 抛出异常 raise ValueError(f"For '{self.cls_name}', the 'LEARNED_SCALE' mode only support 'symmetric' quant, " f"but got 'symmetric': {self.symmetric}. Please set 'symmetric' to True.") if self.neg_trunc: + # 如果neg_trunc为True,则获取初始数组 min_array = self._get_init_array(0) + # 如果narrow_range为False,则抛出异常 if self.narrow_range: raise ValueError(f"For '{self.cls_name}', the 'LEARNED_SCALE' mode only support the combination of " f"'neg_trunc=True and narrow_range=False' config scenario, but got 'narrow_range': " f"{self.narrow_range}.") elif not self.narrow_range: + # 如果narrow_range为True,则抛出异常 raise ValueError(f"For '{self.cls_name}', the 'LEARNED_SCALE' mode only support 'narrow_range=True' " f"config, except for 'neg_trunc=True' scenario. But got 'narrow_range': " f"{self.narrow_range}.") + # 计算量化最大值 self._calculate_quant_max() + # 初始化参数 self.minq = Parameter(Tensor(min_array), name='minq') self.maxq = Parameter(Tensor(max_array), name='maxq') self.quant_max = Parameter(Tensor(np.array([self._quant_max]).astype(np.float32)), name="quant_max", requires_grad=False) - # init fake quant relative op if self.per_channel: quant_fun = partial(Q.FakeLearnedScaleQuantPerChannel, channel_axis=self.channel_axis) else: quant_fun = Q.FakeLearnedScaleQuantPerLayer + # 定义训练和推理时的量化函数 quant_fun = partial(quant_fun, quant_delay=self.quant_delay, neg_trunc=self.neg_trunc) @@ -524,22 +609,30 @@ class FakeQuantWithMinMaxObserver(UniformQuantObserver): """ Convert the initial value to array. """ + # 如果长度不等于self.num_channels if isinstance(init_date, list) and self.per_channel and len(init_date) != self.num_channels: + # 抛出异常 raise ValueError(f"For '{self.cls_name}', the length of 'min_init/max_init' list should be equal to " f"'num_channels' for perchannel quant scenario, but got 'min_init/max_init': {init_date} " f"and num_channels: {self.num_channels}.") + # 如果init_date不是一个列表,或者是一个长度大于1的列表 if isinstance(init_date, list) and not self.per_channel and len(init_date) != 1: + # 抛出异常 raise ValueError(f"For '{self.cls_name}', the length of the 'min_init/max_init' list should be 1 for " f"perlayer quant scenario, but got {len(init_date)}.") + # 将输入的参数转换为numpy数组 if isinstance(init_date, list): + # 用np.array将列表转换为np.float32类型的数组 min_max_array = np.array(init_date).astype(np.float32) elif self.per_channel and not isinstance(init_date, list): + # 如果self.per_channel为真,且init_date不是列表,则将init_date的值复制self.num_channels次,并将其转换为np.float32类型 min_max_array = np.array([init_date] * self.num_channels).astype(np.float32) else: + # 否则,将init_date转换为np.float32类型 min_max_array = np.array([init_date]).astype(np.float32) + # 返回min_max_array return min_max_array - def _calculate_quant_max(self): """ The quantization range is calculated according to num_bits. @@ -561,19 +654,26 @@ class FakeQuantWithMinMaxObserver(UniformQuantObserver): def construct(self, x): if self.mode == "LEARNED_SCALE": if self.training: + # 用学习到的最大值和最小值进行量化 out = self.fake_quant_train(x, self.maxq, self.quant_max) + # 如果负值不取反,则将最大值取反 if not self.neg_trunc: self.minq = self.Neg(self.maxq) else: + # 用学习到的最大值和最小值进行量化 out = self.fake_quant_infer(x, self.maxq, self.quant_max) else: if self.training: + # 用EMA更新最大值和最小值 min_up, max_up = self.ema_update(x, self.minq, self.maxq) self.minq = min_up self.maxq = max_up + # 用更新后的最大值和最小值进行量化 out = self.fake_quant_train(x, self.minq, self.maxq) else: + # 用最大值和最小值进行量化 out = self.fake_quant_infer(x, self.minq, self.maxq) + # 返回out return out @@ -582,7 +682,7 @@ QuantConfig = namedtuple("QuantConfig", ['weight', 'activation']) quant_config_default = QuantConfig(weight=FakeQuantWithMinMaxObserver.partial_init(), activation=FakeQuantWithMinMaxObserver.partial_init()) - +# 在quantization场景下,使用Fold方法进行folding操作的卷积层、批量归一化层和量化卷积层的实现 class Conv2dBnFoldQuantOneConv(Cell): r""" 2D convolution which use the convolution layer statistics once to calculate Batch Normalization @@ -666,7 +766,27 @@ class Conv2dBnFoldQuantOneConv(Cell): [[[[5.9296875 13.8359375] [11.859375 17.78125]]]] """ - + # 参数 + # in_channels (int): 输入通道数。 + # out_channels (int): 输出通道数。 + # kernel_size (int or tuple): 卷积核大小。 + # stride (int or tuple): 步长。 + # pad_mode (str): 填充模式,可选值为'same'、'valid'、'pad'。 + # padding (int): 填充数量。 + # dilation (int): 空洞卷积参数。 + # group (int): 分组数量。 + # eps (float): 归一化参数。 + # momentum (float): 批量归一化参数。 + # has_bias (bool): 是否包含偏置项。 + # weight_init (str): 权重初始化方法。 + # bias_init (str): 偏置项初始化方法。 + # beta_init (str): 批量归一化偏置项初始化方法。 + # gamma_init (str): 批量归一化缩放因子初始化方法。 + # mean_init (str): 批量归一化均值初始化方法。 + # var_init (str): 批量归一化方差初始化方法。 + # fake (bool): 是否为模拟量化的模式。 + # quant_config (dict): quantization配置。 + # quant_dtype (QuantDtype): quantization数据类型,可选值为QuantDtype.INT8、QuantDtype.UINT8。 def __init__(self, in_channels, out_channels, @@ -690,45 +810,65 @@ class Conv2dBnFoldQuantOneConv(Cell): quant_dtype=QuantDtype.INT8): """Initialize Conv2dBnFoldQuant layer""" super(Conv2dBnFoldQuantOneConv, self).__init__() + # 检查输入通道数是否为正整数 self.in_channels = Validator.check_positive_int(in_channels, "in_channels", self.cls_name) + # 检查输出通道数是否为正整数 self.out_channels = Validator.check_positive_int(out_channels, "out_channels", self.cls_name) + # 将kernel_size、stride、dilation转换为元组 self.kernel_size = twice(kernel_size) self.stride = twice(stride) self.dilation = twice(dilation) + # 检查kernel_size、stride、dilation中的元素是否为正整数 for kernel_size_elem in self.kernel_size: Validator.check_positive_int(kernel_size_elem, 'kernel_size item', self.cls_name) for stride_elem in self.stride: Validator.check_positive_int(stride_elem, 'stride item', self.cls_name) for dilation_elem in self.dilation: Validator.check_positive_int(dilation_elem, 'dilation item', self.cls_name) + # 检查pad_mode是否为valid、same、pad if pad_mode not in ('valid', 'same', 'pad'): raise ValueError(f"For '{self.cls_name}', the 'pad_mode' should be one of values " f"in ('valid', 'same', 'pad'), but got {pad_mode}.") + # 设置pad_mode self.pad_mode = pad_mode + # 检查padding是否为非负整数 if isinstance(padding, int): Validator.check_non_negative_int(padding, 'padding', self.cls_name) self.padding = padding + # 检查padding是否为元组 elif isinstance(padding, tuple): for pad in padding: Validator.check_non_negative_int(pad, 'padding item', self.cls_name) self.padding = padding + # 检查padding的类型是否为int/tuple(int) else: raise TypeError(f"For '{self.cls_name}', the type of 'padding' must be int/tuple(int), but got " f"{type(padding).__name__}!") self.group = Validator.check_positive_int(group, "group", self.cls_name) + # 设置eps self.eps = eps + # 设置动量 self.momentum = 1 - momentum + # 设置是否有偏置 self.has_bias = has_bias + # 设置是否是假数据 self.fake = Validator.check_bool(fake, "fake", self.cls_name) + # 设置量化配置 self.quant_config = quant_config + # 设置量化数据类型 self.quant_dtype = quant_dtype + # 设置数据格式 data_format = 'NCHW' self.format = Validator.check_string(data_format, ['NCHW', 'NHWC'], 'format', self.cls_name) + # 获取设备目标 self._target = context.get_context("device_target") + # 获取模式 self.is_graph_mode = context.get_context("mode") == context.GRAPH_MODE + # 获取是否是GE后端 self.is_ge_backend = False if context.get_context("enable_ge"): self.is_ge_backend = True + # 设置是否启用默认训练 self.enable_default_train = self.is_graph_mode and \ (self.is_ge_backend or self._target == "Ascend") @@ -741,17 +881,25 @@ class Conv2dBnFoldQuantOneConv(Cell): dilation=self.dilation, group=group) weight_shape = [out_channels, in_channels // group, *self.kernel_size] + # 定义输入通道的轴 channel_axis = 0 + # 定义输入通道的轴 self.channel_axis = channel_axis + # 初始化权重 self.weight = Parameter(initializer(weight_init, weight_shape), name='weight') + # 初始化偏置 self.bias_add = P.BiasAdd() self.bias = None + # 判断是否有偏置 if Validator.check_bool(has_bias, "has_bias", self.cls_name): + # 初始化偏置 self.bias = Parameter(initializer(bias_init, [out_channels]), name='bias') # initialize BatchNorm Parameter + # 初始化gama和beta self.gamma = Parameter(initializer(gamma_init, [out_channels]), name='gamma') self.beta = Parameter(initializer(beta_init, [out_channels]), name='beta') + # 初始化moving_mean和moving_variance self.moving_mean = Parameter(initializer(mean_init, [out_channels]), name='moving_mean', requires_grad=False) self.moving_variance = Parameter(initializer(var_init, [out_channels]), name='moving_variance', requires_grad=False) @@ -764,16 +912,25 @@ class Conv2dBnFoldQuantOneConv(Cell): self.freeze_bn = False if self.fake_quant_weight.mode == "LEARNED_SCALE": self.freeze_bn = True + # 创建训练时的batchnorm层 self.bn_train = P.BatchNorm(is_training=True, epsilon=self.eps, momentum=self.momentum, data_format=self.format) + # 创建推理时的batchnorm层 self.bn_infer = P.BatchNorm(is_training=False, epsilon=self.eps, data_format=self.format) + # 创建减去均值的操作 self.sub_mean = P.Sub() + # 创建减去方差的操作 self.sub_var = P.Sub() + # 创建乘以均值的操作 self.mul_mean = P.Mul() + # 创建乘以方差的操作 self.mul_var = P.Mul() + # 创建赋值减去均值的操作 self.assign_sub_mean = P.AssignSub() + # 创建赋值减去方差的操作 self.assign_sub_var = P.AssignSub() + # 创建reshape的操作 self.reshape = P.Reshape() def extend_repr(self): @@ -790,24 +947,35 @@ class Conv2dBnFoldQuantOneConv(Cell): def construct(self, x): running_std = P.Sqrt()(P.Add()(self.moving_variance, self.eps)) + # 计算running_std scale_factor = self.gamma / running_std + # 计算scale_factor if self.channel_axis: + # 如果self.channel_axis为真,则将scale_factor的形状调整为(1, -1, 1, 1) scale_factor = self.reshape(scale_factor, (1, -1, 1, 1)) else: + # 否则,将scale_factor的形状调整为(-1, 1, 1, 1) scale_factor = self.reshape(scale_factor, (-1, 1, 1, 1)) + # 将weight乘以scale_factor weight = self.weight * scale_factor + # 如果self.fake为真,则对weight进行fake_quant_weight操作 if self.fake: weight = self.fake_quant_weight(weight) + # 进行卷积操作 conv = self.conv(x, weight) + # 如果冻结BN,则直接返回 if self.freeze_bn: return conv + self.reshape((self.beta - self.gamma * self.moving_mean / running_std), (1, -1, 1, 1)) + # 计算scale_factor scale_factor = self.reshape(scale_factor, (1, -1, 1, 1)) + # 如果启用默认训练,则计算conv_orig if self.enable_default_train: scale_factor = P.Reciprocal()(scale_factor) conv_orig = conv * scale_factor else: conv_orig = conv / scale_factor + # 如果训练,则返回训练时的结果 if self.training: return self.bn_train(conv_orig, self.gamma, @@ -815,13 +983,13 @@ class Conv2dBnFoldQuantOneConv(Cell): self.moving_mean, self.moving_variance)[0] + # 否则返回推理时的结果 return self.bn_infer(conv_orig, self.gamma, self.beta, self.moving_mean, self.moving_variance)[0] - - +# 主要用于在quantization场景下,使用Fold方法进行folding操作的卷积层、批量归一化层和量化卷积层的实现 class Conv2dBnFoldQuant(Cell): r""" 2D convolution with Batch Normalization operation folded construct. @@ -905,7 +1073,28 @@ class Conv2dBnFoldQuant(Cell): [[[[5.9296875 13.8359375] [11.859375 17.78125]]]] """ - + # 参数 + # in_channels (int): 输入通道数。 + # out_channels (int): 输出通道数。 + # kernel_size (int or tuple): 卷积核大小。 + # stride (int or tuple): 步长。 + # pad_mode (str): 填充模式,可选值为'same'、'valid'、'pad'。 + # padding (int): 填充数量。 + # dilation (int): 空洞卷积参数。 + # group (int): 分组数量。 + # eps (float): 归一化参数。 + # momentum (float): 批量归一化参数。 + # has_bias (bool): 是否包含偏置项。 + # weight_init (str): 权重初始化方法。 + # bias_init (str): 偏置项初始化方法。 + # beta_init (str): 批量归一化偏置项初始化方法。 + # gamma_init (str): 批量归一化缩放因子初始化方法。 + # mean_init (str): 批量归一化均值初始化方法。 + # var_init (str): 批量归一化方差初始化方法。 + # fake (bool): 是否为模拟量化的模式。 + # quant_config (dict): quantization配置。 + # quant_dtype (QuantDtype): quantization数据类型,可选值为QuantDtype.INT8、QuantDtype.UINT8。 + # freeze_bn (int): 批量归一化层的冻结阈值。 def __init__(self, in_channels, out_channels, @@ -930,21 +1119,26 @@ class Conv2dBnFoldQuant(Cell): freeze_bn=100000): """Initialize Conv2dBnFoldQuant layer""" super(Conv2dBnFoldQuant, self).__init__() + # 检查输入的参数是否为正整数 self.in_channels = Validator.check_positive_int(in_channels, "in_channels", self.cls_name) self.out_channels = Validator.check_positive_int(out_channels, "out_channels", self.cls_name) + # 将kernel_size和stride转换为元组 self.kernel_size = twice(kernel_size) self.stride = twice(stride) self.dilation = twice(dilation) + # 检查kernel_size、stride、dilation中的元素是否为正整数 for kernel_size_elem in self.kernel_size: Validator.check_positive_int(kernel_size_elem, 'kernel_size item', self.cls_name) for stride_elem in self.stride: Validator.check_positive_int(stride_elem, 'stride item', self.cls_name) for dilation_elem in self.dilation: Validator.check_positive_int(dilation_elem, 'dilation item', self.cls_name) + # 检查pad_mode是否为'valid'、'same'、'pad' if pad_mode not in ('valid', 'same', 'pad'): raise ValueError(f"For '{self.cls_name}', the 'pad_mode' should be one of values in " f"('valid', 'same', 'pad'), but got {pad_mode}.") self.pad_mode = pad_mode + # 检查padding是否为正整数 if isinstance(padding, int): Validator.check_non_negative_int(padding, 'padding', self.cls_name) self.padding = padding @@ -973,9 +1167,11 @@ class Conv2dBnFoldQuant(Cell): stride=self.stride, dilation=self.dilation, group=group) + # 初始化权重 weight_shape = [out_channels, in_channels // group, *self.kernel_size] channel_axis = 0 self.weight = Parameter(initializer(weight_init, weight_shape), name='weight') + # 初始化偏置 self.bias_add = P.BiasAdd() self.bias = None if Validator.check_bool(has_bias, "has_bias", self.cls_name): @@ -984,17 +1180,23 @@ class Conv2dBnFoldQuant(Cell): # initialize BatchNorm Parameter self.gamma = Parameter(initializer(gamma_init, [out_channels]), name='gamma') self.beta = Parameter(initializer(beta_init, [out_channels]), name='beta') + # 初始化moving_mean self.moving_mean = Parameter(initializer(mean_init, [out_channels]), name='moving_mean', requires_grad=False) + # 初始化moving_variance self.moving_variance = Parameter(initializer(var_init, [out_channels]), name='moving_variance', requires_grad=False) # initialize fake ops + # 创建一个fake_quant_weight,用于对权重进行量化 self.fake_quant_weight = quant_config.weight(ema=False, channel_axis=channel_axis, num_channels=out_channels, quant_dtype=quant_dtype) + # 创建一个BatchNormFoldCell,用于对输入进行折叠 self.batchnorm_fold = BatchNormFoldCell(epsilon=eps, momentum=momentum, freeze_bn=freeze_bn) + # 创建一个CorrectionMul,用于对输入进行校正 self.correct_mul = Q.CorrectionMul(channel_axis) + # 根据设备类型昇腾或GPU,创建不同的BatchNormFold2 if context.get_context('device_target') == "Ascend": self.batchnorm_fold2_train = Q.BatchNormFold2D(freeze_bn=freeze_bn) self.batchnorm_fold2_infer = Q.BatchNormFold2D(freeze_bn=0) @@ -1004,8 +1206,11 @@ class Conv2dBnFoldQuant(Cell): else: raise ValueError(f"For '{self.cls_name}', only the 'Ascend' and 'GPU' platforms" f" are supported, but got {context.get_context('device_target')}.") + # 创建一个step,用于记录训练步数 self.step = Parameter(initializer('normal', [1], dtype=mstype.int32), name='step', requires_grad=False) + # 创建一个Tensor,用于记录1 self.one = Tensor(1, mstype.int32) + # 创建一个AssignAdd,用于更新step self.assignadd = P.AssignAdd() def extend_repr(self): @@ -1030,30 +1235,40 @@ class Conv2dBnFoldQuant(Cell): self.moving_variance, self.step) # fake weight + # 计算running_std weight = self.correct_mul(self.weight, self.gamma, running_std) + # 判断是否是假量化 if self.fake: weight = self.fake_quant_weight(weight) + # 计算输出 out = self.conv(x, weight) + # 判断是否有偏置 if self.has_bias: out = self.bias_add(out, self.bias) # BN fold2 if self.is_gpu: if self.training: + # 训练模式下,使用batchnorm_fold2_train函数 out = self.batchnorm_fold2_train(out, self.beta, self.gamma, batch_std, batch_mean, running_std, running_mean, self.step) + # 更新step self.assignadd(self.step, self.one) else: + # 推理模式下,使用batchnorm_fold2_infer函数 out = self.batchnorm_fold2_infer(out, self.beta, self.gamma, batch_std, batch_mean, running_std, running_mean, self.step) else: if self.training: + # 训练模式下,使用batchnorm_fold2_train函数 out = self.batchnorm_fold2_train(out, self.beta, self.gamma, batch_std, batch_mean, running_std) + # 更新step self.assignadd(self.step, self.one) else: + # 推理模式下,使用batchnorm_fold2_infer函数 out = self.batchnorm_fold2_infer(out, self.beta, self.gamma, running_std, running_mean, running_std) return out - +# 主要用于在quantization场景下,不使用Fold方法进行folding操作的卷积层、批量归一化层和量化卷积层的实现 class Conv2dBnWithoutFoldQuant(Cell): r""" 2D convolution and batchnorm without fold with fake quantized construct. @@ -1122,7 +1337,7 @@ class Conv2dBnWithoutFoldQuant(Cell): [[[[5.929658 13.835868] [11.859316 17.78116]]]] """ - + # 参数与Conv2dBnFoldQuant基本相同 def __init__(self, in_channels, out_channels, @@ -1141,39 +1356,60 @@ class Conv2dBnWithoutFoldQuant(Cell): quant_dtype=QuantDtype.INT8): """Initialize Conv2dBnWithoutFoldQuant.""" super(Conv2dBnWithoutFoldQuant, self).__init__() + # 检查in_channels参数是否为正整数 self.in_channels = Validator.check_positive_int(in_channels, "in_channels", self.cls_name) + # 检查out_channels参数是否为正整数 self.out_channels = Validator.check_positive_int(out_channels, "out_channels", self.cls_name) self.has_bias = has_bias self.kernel_size = twice(kernel_size) self.stride = twice(stride) self.dilation = twice(dilation) + # 检查kernel_size的每一个元素是否为正整数 for kernel_size_elem in self.kernel_size: Validator.check_positive_int(kernel_size_elem, 'kernel_size item', self.cls_name) + # 检查stride的每一个元素是否为正整数 for stride_elem in self.stride: Validator.check_positive_int(stride_elem, 'stride item', self.cls_name) + # 检查dilation的每一个元素是否为正整数 for dilation_elem in self.dilation: Validator.check_positive_int(dilation_elem, 'dilation item', self.cls_name) + # 检查pad_mode是否为valid、same、pad中的一种 if pad_mode not in ('valid', 'same', 'pad'): raise ValueError(f"For '{self.cls_name}', the 'pad_mode' should be one of values in " f"('valid', 'same', 'pad'), but got {pad_mode}.") self.pad_mode = pad_mode + # 判断padding是否为整数 if isinstance(padding, int): + # 检查padding是否为非负整数 Validator.check_non_negative_int(padding, 'padding', self.cls_name) + # 将padding赋值给self.padding self.padding = padding + # 判断padding是否为元组 elif isinstance(padding, tuple): + # 遍历padding中的每一个元素 for pad in padding: + # 检查pad是否为非负整数 Validator.check_non_negative_int(pad, 'padding item', self.cls_name) + # 将padding赋值给self.padding self.padding = padding + # 如果padding的类型不是整数或元组 else: + # 抛出异常 raise TypeError(f"For '{self.cls_name}', the type of 'padding' must be int/tuple(int), " f"but got {type(padding).__name__}!") + # 检查group是否为正整数 self.group = Validator.check_positive_int(group, "group", self.cls_name) + # 创建BiasAdd层 self.bias_add = P.BiasAdd() + # 判断has_bias是否为布尔值 if Validator.check_bool(has_bias, "has_bias", self.cls_name): + # 创建Parameter,初始值为bias_init,shape为[out_channels] self.bias = Parameter(initializer(bias_init, [out_channels]), name='bias') else: + # 将self.bias赋值为None self.bias = None # initialize convolution op and Parameter + # 定义卷积层 self.conv = P.Conv2D(out_channel=self.out_channels, kernel_size=self.kernel_size, mode=1, @@ -1182,21 +1418,28 @@ class Conv2dBnWithoutFoldQuant(Cell): stride=self.stride, dilation=self.dilation, group=self.group) + # 定义权重形状 weight_shape = [out_channels, in_channels // group, *self.kernel_size] + # 定义通道轴 channel_axis = 0 + # 初始化权重 self.weight = Parameter(initializer(weight_init, weight_shape), name='weight') + # 定义量化权重 self.fake_quant_weight = quant_config.weight(ema=False, channel_axis=channel_axis, num_channels=out_channels, quant_dtype=quant_dtype) + # 定义批量归一化层 self.batchnorm = BatchNorm2d(out_channels, eps=eps, momentum=momentum) def construct(self, x): + # 使用fake_quant_weight函数对权重进行模拟量化 weight = self.fake_quant_weight(self.weight) out = self.conv(x, weight) if self.has_bias: out = self.bias_add(out, self.bias) out = self.batchnorm(out) + # 返回out return out def extend_repr(self): @@ -1208,7 +1451,7 @@ class Conv2dBnWithoutFoldQuant(Cell): self.has_bias, self.fake_quant_weight.quant_delay) return s - +# 主要用于在quantization场景下,使用Fold方法进行folding操作的卷积层和量化卷积层的实现 class Conv2dQuant(Cell): r""" 2D convolution with fake quantized operation layer. @@ -1268,7 +1511,20 @@ class Conv2dQuant(Cell): [[[[5.9296875 13.8359375] [11.859375 17.78125]]]] """ - + # 参数 + # in_channels (int): 输入通道数 + # out_channels (int): 输出通道数 + # kernel_size (int or tuple): 卷积核大小 + # stride (int or tuple): 步长 + # pad_mode (str): 填充模式,可选值为'same'、'valid'、'pad' + # padding (int): 填充数量 + # dilation (int): 空洞卷积参数 + # group (int): 分组数量 + # has_bias (bool): 是否包含偏置项 + # weight_init (str): 权重初始化方法 + # bias_init (str): 偏置项初始化方法 + # quant_config (dict): quantization配置 + # quant_dtype (QuantDtype): quantization数据类型,可选值为QuantDtype.INT8、QuantDtype.UINT8 def __init__(self, in_channels, out_channels, @@ -1285,43 +1541,61 @@ class Conv2dQuant(Cell): quant_dtype=QuantDtype.INT8): """Initialize Conv2dQuant.""" super(Conv2dQuant, self).__init__() + # 检查in_channels参数是否为正整数 self.in_channels = Validator.check_positive_int(in_channels, "in_channels", self.cls_name) + # 检查out_channels参数是否为正整数 self.out_channels = Validator.check_positive_int(out_channels, "out_channels", self.cls_name) + # 设置是否有偏置 self.has_bias = has_bias + # 设置卷积核大小 self.kernel_size = twice(kernel_size) + # 设置步长 self.stride = twice(stride) + # 设置膨胀率 self.dilation = twice(dilation) + # 检查卷积核大小元素是否为正整数 for kernel_size_elem in self.kernel_size: Validator.check_positive_int(kernel_size_elem, 'kernel_size item', self.cls_name) + # 检查步长元素是否为正整数 for stride_elem in self.stride: Validator.check_positive_int(stride_elem, 'stride item', self.cls_name) + # 检查膨胀率元素是否为正整数 for dilation_elem in self.dilation: Validator.check_positive_int(dilation_elem, 'dilation item', self.cls_name) + # 检查pad_mode是否为有效值 if pad_mode not in ('valid', 'same', 'pad'): raise ValueError(f"For '{self.cls_name}', the 'pad_mode' should be one of values " f"in ('valid', 'same', 'pad'), but got {pad_mode}.") + # 设置pad_mode self.pad_mode = pad_mode if isinstance(padding, int): + # 检查padding是否为非负整数 Validator.check_non_negative_int(padding, 'padding', self.cls_name) self.padding = padding elif isinstance(padding, tuple): + # 检查padding的每一个元素是否为非负整数 for pad in padding: Validator.check_non_negative_int(pad, 'padding item', self.cls_name) self.padding = padding else: + # 检查padding的类型是否为int/tuple(int) raise TypeError(f"For '{self.cls_name}', the type of 'padding' must be int/tuple(int), " f"but got {type(padding).__name__}!") + # 检查group是否为正整数 self.group = Validator.check_positive_int(group, "group", self.cls_name) + # 初始化权重 weight_shape = [out_channels, in_channels // group, *self.kernel_size] self.weight = Parameter(initializer(weight_init, weight_shape), name='weight') + # 初始化偏置 self.bias_add = P.BiasAdd() if Validator.check_bool(has_bias, "has_bias", self.cls_name): self.bias = Parameter(initializer(bias_init, [out_channels]), name='bias') else: self.bias = None + # 初始化卷积 self.conv = P.Conv2D(out_channel=self.out_channels, kernel_size=self.kernel_size, mode=1, @@ -1335,7 +1609,6 @@ class Conv2dQuant(Cell): channel_axis=channel_axis, num_channels=out_channels, quant_dtype=quant_dtype) - def construct(self, x): weight = self.fake_quant_weight(self.weight) out = self.conv(x, weight) @@ -1352,7 +1625,7 @@ class Conv2dQuant(Cell): self.has_bias, self.fake_quant_weight.quant_delay) return s - +# 主要用于在quantization场景下,使用Fold方法进行folding操作的全连接层和量化全连接层的实现 class DenseQuant(Cell): r""" The fully connected layer with fake quantized operation. @@ -1409,7 +1682,15 @@ class DenseQuant(Cell): [[5.929413] [6.9176483]] """ - + # 参数 + # in_channels (int): 输入通道数。 + # out_channels (int): 输出通道数。 + # weight_init (str): 权重初始化方法。 + # bias_init (str): 偏置项初始化方法。 + # has_bias (bool): 是否包含偏置项。 + # activation (str): 激活函数,可选值为'relu'、'relu6'、'tanh'、'sigmoid'、'none'。 + # quant_config (dict): quantization配置。 + # uant_dtype (QuantDtype): quantization数据类型,可选值为QuantDtype.INT8、QuantDtype.UINT8。 def __init__(self, in_channels, out_channels, @@ -1421,24 +1702,33 @@ class DenseQuant(Cell): quant_dtype=QuantDtype.INT8): """Initialize DenseQuant.""" super(DenseQuant, self).__init__() + # 初始化类 self.in_channels = Validator.check_positive_int(in_channels, "in_channels", self.cls_name) + # 检查输入通道是否为正整数 self.out_channels = Validator.check_positive_int(out_channels, "out_channels", self.cls_name) + # 检查输出通道是否为正整数 self.has_bias = Validator.check_bool(has_bias, "has_bias", self.cls_name) + # 检查是否有偏置 if isinstance(weight_init, Tensor): + # 如果权重不是2或者[0]位置上不等于out_channels,[1]位置上不等于in_channels if weight_init.ndim != 2 or weight_init.shape[0] != out_channels or \ weight_init.shape[1] != in_channels: + # 抛出异常 raise ValueError(f"For '{self.cls_name}', weight init shape error. The ndim of 'weight_init' should " f"be equal to 2, and the first dim should be equal to 'out_channels', and the " f"second dim should be equal to 'in_channels'. But got 'weight_init': {weight_init}, " f"'out_channels': {out_channels}, 'in_channels': {in_channels}.") + # 检查权重初始化是否为Tensor self.weight = Parameter(initializer( weight_init, [out_channels, in_channels]), name="weight") if self.has_bias: if isinstance(bias_init, Tensor): + # 如果偏置项不是1或者[0]位置上不等于out_channels if bias_init.ndim != 1 or bias_init.shape[0] != out_channels: + # 抛出异常 raise ValueError(f"For '{self.cls_name}', bias init shape error. The ndim of 'bias_init' should " f"be equal to 1, and the first dim should be equal to 'out_channels'. But got " f"'bias_init': {bias_init}, 'out_channels': {out_channels}.") @@ -1446,30 +1736,37 @@ class DenseQuant(Cell): self.bias = Parameter(initializer( bias_init, [out_channels]), name="bias") + # 初始化矩阵乘法,矩阵加法,激活函数 self.matmul = P.MatMul(transpose_b=True) self.bias_add = P.BiasAdd() + # 获取激活函数 self.activation = get_activation(activation) if isinstance(activation, str) else activation if activation is not None and not isinstance(self.activation, (Cell, Primitive)): raise TypeError(f"For '{self.cls_name}', the 'activation' must be str or Cell or Primitive, " f"but got {activation}.") + # 判断激活函数是否为空 self.activation_flag = self.activation is not None + # 初始化权重 self.fake_quant_weight = quant_config.weight(ema=False, channel_axis=0, num_channels=out_channels, quant_dtype=quant_dtype) - def construct(self, x): """Use operators to construct the Dense layer. Args: x (Tensor): Input tensor. """ + # 计算权重 output = self.fake_quant_weight(self.weight) + # 矩阵乘法 output = self.matmul(x, output) + # 如果有偏置,则加上偏置 if self.has_bias: output = self.bias_add(output, self.bias) + # 如果有激活函数,则对输出进行激活 if self.activation_flag: return self.activation(output) return output @@ -1494,7 +1791,7 @@ class _QuantActivation(Cell): def get_origin(self): raise NotImplementedError - +# 主要用于在quantization场景下,对激活函数进行quantization操作 class ActQuant(_QuantActivation): r""" Quantization aware training activation function. @@ -1539,7 +1836,13 @@ class ActQuant(_QuantActivation): [[0.9882355 1.9764705 0. ] [0. 0. 0. ]] """ - + # 参数 + # activation (str): 激活函数,可选值为'relu'、'relu6'、'tanh'、'sigmoid'、'none' + # ema (bool): 是否使用指数移动平均 + # ema_decay (float): 指数移动平均的权重 + # fake_before (bool): 是否在激活函数之前添加一个虚拟量 + # quant_config (dict): quantization配置 + # quant_dtype (QuantDtype): quantization数据类型,可选值为QuantDtype.INT8、QuantDtype.UINT8 def __init__(self, activation, ema=False, @@ -1551,22 +1854,30 @@ class ActQuant(_QuantActivation): super(ActQuant, self).__init__() act_class = activation.__class__ act_list = [nn.ReLU, nn.ReLU6] + # 检查activation是否为Cell类型 self.act = Validator.check_isinstance("activation", activation, Cell) + # 检查fake_before是否为布尔类型 self.fake_before = Validator.check_bool(fake_before, "fake_before", self.cls_name) + # 如果fake_before为True,则创建fake_quant_act_before if self.fake_before: self.fake_quant_act_before = quant_config.activation(min_init=-6, max_init=6, ema=ema, ema_decay=ema_decay, quant_dtype=quant_dtype) + # 初始化neg_trunc和narrow_range为False self.neg_trunc = False self.narrow_range = False + # 获取preset_dict中的参数 preset_dict = quant_config.activation.p.keywords + # 如果preset_dict中的mode为LEARNED_SCALE,且activation为nn.ReLU或nn.ReLU6,则将neg_trunc设置为True if 'mode' in preset_dict and preset_dict['mode'] == "LEARNED_SCALE" and act_class in act_list: self.neg_trunc = True + # 如果preset_dict中有narrow_range参数,则将narrow_range设置为preset_dict中的narrow_range elif 'narrow_range' in preset_dict: self.narrow_range = preset_dict['narrow_range'] + # 创建fake_quant_act self.fake_quant_act = quant_config.activation(min_init=-6, max_init=6, ema=ema, @@ -1585,7 +1896,7 @@ class ActQuant(_QuantActivation): def get_origin(self): return self.act - +# 用于将两个张量相加,并将结果存储在一个新的张量中 class TensorAddQuant(Cell): r""" Adds fake quantized operation after TensorAdd operation. @@ -1629,18 +1940,23 @@ class TensorAddQuant(Cell): [[ 1.9764705 3.011765 1.9764705] [-0.9882355 0.9882355 0. ]] """ - + # 参数 + # ema_decay (float): 指数移动平均的权重 + # quant_config (dict): quantization配置 + # quant_dtype (QuantDtype): quantization数据类型,可选值为QuantDtype.INT8、QuantDtype.UINT8 def __init__(self, ema_decay=0.999, quant_config=quant_config_default, quant_dtype=QuantDtype.INT8): """Initialize TensorAddQuant.""" super(TensorAddQuant, self).__init__() + # 初始化激活函数 self.fake_quant_act = quant_config.activation(min_init=-6, max_init=6, ema=True, ema_decay=ema_decay, quant_dtype=quant_dtype) + # 初始化加法运算 self.add = P.Add() def construct(self, x1, x2): @@ -1648,7 +1964,7 @@ class TensorAddQuant(Cell): x = self.fake_quant_act(x) return x - +# 用于将两个张量相乘,并将结果存储在一个新的张量中 class MulQuant(Cell): r""" Adds fake quantized operation after `Mul` operation. @@ -1692,7 +2008,10 @@ class MulQuant(Cell): [[ 1.9764705 4.0000005 1.9764705] [-4. 0. -1.9764705]] """ - + # 参数 + # ema_decay (float): 指数移动平均的权重 + # quant_config (dict): quantization配置 + # quant_dtype (QuantDtype): quantization数据类型,可选值为QuantDtype.INT8、QuantDtype.UINT8 def __init__(self, ema_decay=0.999, quant_config=quant_config_default, @@ -1706,7 +2025,8 @@ class MulQuant(Cell): quant_dtype=quant_dtype) self.mul = P.Mul() - def construct(self, x1, x2): + def construct(self, x1, x2): + # 计算乘法,并使用fake_quant_act函数进行量化 x = self.mul(x1, x2) x = self.fake_quant_act(x) return x diff --git a/mindspore/python/mindspore/nn/layer/rnn_cells.py b/mindspore/python/mindspore/nn/layer/rnn_cells.py index 9103e4ae983..0d89ec0cdfd 100644 --- a/mindspore/python/mindspore/nn/layer/rnn_cells.py +++ b/mindspore/python/mindspore/nn/layer/rnn_cells.py @@ -13,63 +13,93 @@ # limitations under the License. # ============================================================================ '''RNN Cells module, include RNNCell, GRUCell, LSTMCell''' +# 导入math模块,用于处理数学计算 import math +# 导入numpy模块,用于处理数值计算 import numpy as np +# 从mindspore.ops模块中导入P类,用于定义各种计算图操作 import mindspore.ops as P +# 从mindspore.common.dtype模块中导入mstype类,用于处理数据类型 import mindspore.common.dtype as mstype +# 从mindspore模块中导入logger函数,用于记录日志 from mindspore import log as logger +# 从mindspore.common.tensor模块中导入Tensor类,用于表示神经网络中的张量 from mindspore.common.tensor import Tensor +# 从mindspore.common.parameter模块中导入Parameter类,用于表示神经网络中的参数 from mindspore.common.parameter import Parameter +# 从mindspore.common.initializer模块中导入initializer函数和Uniform函数,用于初始化神经网络参数 from mindspore.common.initializer import initializer, Uniform +# 从mindspore.ops.primitive模块中导入constexpr函数,用于在编译时计算常量表达式 from mindspore.ops.primitive import constexpr +# 从mindspore.nn.cell模块中导入Cell类,用于创建自定义的神经网络层 from mindspore.nn.cell import Cell +# 从mindspore._checkparam模块中导入Validator类,用于验证参数的合法性 from mindspore._checkparam import Validator as validator __all__ = ['LSTMCell', 'GRUCell', 'RNNCell'] @constexpr +# 检查输入_dtype是否有效,根据allow_dtypes列表 +# 如果输入_dtype不在allow_dtypes列表中,它会引发一个ValueError,带有自定义错误消息 def _check_input_dtype(input_dtype, param_name, allow_dtypes, cls_name): validator.check_type_name(param_name, input_dtype, allow_dtypes, cls_name) @constexpr +# 用于检查输入数据是否为张量 def _check_is_tensor(param_name, input_data, cls_name): """Internal function, used to check whether the input data is Tensor.""" + # # 检查输入数据是否为张量 if input_data is not None and not isinstance(P.typeof(input_data), mstype.tensor_type): + # 抛出异常 raise TypeError(f"For '{cls_name}', the '{param_name}' should be '{mstype.tensor_type}', " f"but got '{P.typeof(input_data)}'") @constexpr +# 用于检查输入数据是否为元组 def _check_is_tuple(param_name, input_data, cls_name): """Internal function, used to check whether the input data is Tensor.""" + # 检查输入数据是否为元组 if input_data is not None and not isinstance(P.typeof(input_data), mstype.Tuple): + # 抛出异常 raise TypeError(f"For '{cls_name}', the '{param_name}' should be '{mstype.Tuple}', " f"but got '{P.typeof(input_data)}'") @constexpr +# 用于检查输入数据是否为指定长度的元组 def _check_tuple_length(param_name, input_data, length, cls_name): """Internal function, used to check whether the input data is Tensor.""" + # 如果输入数据不是指定长度的元组 if input_data is not None and len(input_data) != length: + # 抛出异常 raise TypeError(f"For '{cls_name}', the length of '{param_name}' should be '{length}', " f"but got '{len(input_data)}'") @constexpr +# 用于检查两个batch size是否相等 def _check_batch_size_equal(batch_size_x, batch_size_hx, cls_name): + # 如果batch_size_x不等于batch_size_hx if batch_size_x != batch_size_hx: + # 抛出异常 raise ValueError(f"For '{cls_name}' batch size of x and hx should be equal, but got {batch_size_x} of x " f"and {batch_size_hx} of hx.") - +# 用于检查old MindSpore版本的LSTMCell初始化 +# 在old MindSpore版本中,`nn.LSTMCell`是一个完整的LSTM层,而不是单个LSTM单元 +# 为了兼容新版本的MindSpore,这个装饰器会在检测到old版本的`nn.LSTMCell`初始化时发出警告,并建议使用`nn.LSTM`代替 +# 如果仍然需要使用单层LSTM,请确保使用`nn.LSTM`而不是`nn.LSTMCell` def _check_lstmcell_init(func): def wrapper(*args, **kwargs): logger.warning(f"LSTMCell has been changed from 'single LSTM layer' to 'single LSTM cell', " f"if you still need use single LSTM layer, please use `nn.LSTM` instead.") + # 如果args长度大于4或者'batch_size' in kwargs、'dropout' in kwargs或'bidirectional' in kwargs if len(args) > 4 or 'batch_size' in kwargs or \ 'dropout' in kwargs or 'bidirectional' in kwargs: + # 抛出异常 raise ValueError(f"The arguments of `nn.LSTMCell` from old MindSpore version(<1.6) are detected, " f"if you still need use single LSTM layer, please use `nn.LSTM` instead.") return func(*args, **kwargs) @@ -78,48 +108,57 @@ def _check_lstmcell_init(func): def _rnn_tanh_cell(inputs, hidden, w_ih, w_hh, b_ih, b_hh): '''RNN cell function with tanh activation''' + # 如果b_ih为None,则使用inputs和w_ih进行矩阵乘法,否则使用inputs和w_ih加b_ih进行矩阵乘法 if b_ih is None: igates = P.MatMul(False, True)(inputs, w_ih) hgates = P.MatMul(False, True)(hidden, w_hh) else: igates = P.MatMul(False, True)(inputs, w_ih) + b_ih hgates = P.MatMul(False, True)(hidden, w_hh) + b_hh + # 返回计算激活函数 return P.Tanh()(igates + hgates) def _rnn_relu_cell(inputs, hidden, w_ih, w_hh, b_ih, b_hh): '''RNN cell function with relu activation''' + # 如果b_ih为None,则使用inputs和w_ih进行矩阵乘法,否则使用inputs和w_ih加b_ih进行矩阵乘法 if b_ih is None: igates = P.MatMul(False, True)(inputs, w_ih) hgates = P.MatMul(False, True)(hidden, w_hh) else: igates = P.MatMul(False, True)(inputs, w_ih) + b_ih hgates = P.MatMul(False, True)(hidden, w_hh) + b_hh + # 返回计算激活函数 return P.ReLU()(igates + hgates) - +# 用于实现LSTM单元 def _lstm_cell(inputs, hidden, w_ih, w_hh, b_ih, b_hh): '''LSTM cell function''' hx, cx = hidden + # 如果b_ih为None if b_ih is None: gates = P.MatMul(False, True)(inputs, w_ih) + P.MatMul(False, True)(hx, w_hh) else: gates = P.MatMul(False, True)(inputs, w_ih) + P.MatMul(False, True)(hx, w_hh) + b_ih + b_hh + # 将gates拆分为输入门、遗忘门、单元门和输出门 ingate, forgetgate, cellgate, outgate = P.Split(1, 4)(gates) + # 计算输入门、遗忘门、单元门和输出门 ingate = P.Sigmoid()(ingate) forgetgate = P.Sigmoid()(forgetgate) cellgate = P.Tanh()(cellgate) outgate = P.Sigmoid()(outgate) + # 计算当前状态 cy = (forgetgate * cx) + (ingate * cellgate) hy = outgate * P.Tanh()(cy) - + # 返回hy和cy return hy, cy def _gru_cell(inputs, hidden, w_ih, w_hh, b_ih, b_hh): '''GRU cell function''' + # 如果b_ih为None if b_ih is None: gi = P.MatMul(False, True)(inputs, w_ih) gh = P.MatMul(False, True)(hidden, w_hh) @@ -129,40 +168,55 @@ def _gru_cell(inputs, hidden, w_ih, w_hh, b_ih, b_hh): i_r, i_i, i_n = P.Split(1, 3)(gi) h_r, h_i, h_n = P.Split(1, 3)(gh) + # 计算重置门、输入门和遗忘门 resetgate = P.Sigmoid()(i_r + h_r) inputgate = P.Sigmoid()(i_i + h_i) newgate = P.Tanh()(i_n + resetgate * h_n) + # 计算隐藏状态 hy = newgate + inputgate * (hidden - newgate) return hy - +# 用于创建RNN单元的基本类 class RNNCellBase(Cell): '''Basic class for RNN Cells''' + # input_size:输入数据的特征大小。 + # hidden_size:隐藏层的特征大小。 + # has_bias:是否使用偏置。 + # num_chunks:分块数量,用于实现门控循环单元(GRU)。 def __init__(self, input_size: int, hidden_size: int, has_bias: bool, num_chunks: int): super().__init__() + # 检查has_bias是否为bool型 validator.check_value_type("has_bias", has_bias, [bool], self.cls_name) + # 检查hidden_size是否为正整数 validator.check_positive_int(hidden_size, "hidden_size", self.cls_name) + # 检查input_size是否为正整数 validator.check_positive_int(input_size, "input_size", self.cls_name) + # 初始化LSTM的参数 self.input_size = input_size self.hidden_size = hidden_size self.has_bias = has_bias + # 初始化输入门参数 self.weight_ih = Parameter(Tensor(np.random.randn(num_chunks * hidden_size, input_size).astype(np.float32))) + # 初始化遗忘门参数 self.weight_hh = Parameter(Tensor(np.random.randn(num_chunks * hidden_size, hidden_size).astype(np.float32))) + # 初始化输出门参数 if has_bias: self.bias_ih = Parameter(Tensor(np.random.randn(num_chunks * hidden_size).astype(np.float32))) self.bias_hh = Parameter(Tensor(np.random.randn(num_chunks * hidden_size).astype(np.float32))) else: self.bias_ih = None self.bias_hh = None + # 初始化参数 self.reset_parameters() - + # 重置网络参数 def reset_parameters(self): stdv = 1 / math.sqrt(self.hidden_size) for weight in self.get_parameters(): weight.set_data(initializer(Uniform(stdv), weight.shape)) - +# 用于实现RNN(递归神经网络)的通用基础类 +# RNNCell类主要用于创建和初始化RNNcell,包括输入门、输出门和cell状态。它还包含用于计算隐藏状态和输出值的函数 class RNNCell(RNNCellBase): r""" An Elman RNN cell with tanh or ReLU non-linearity. @@ -212,24 +266,35 @@ class RNNCell(RNNCellBase): def __init__(self, input_size: int, hidden_size: int, has_bias: bool = True, nonlinearity: str = "tanh"): super().__init__(input_size, hidden_size, has_bias, num_chunks=1) + # 检查"nonlinearity类型是否为str validator.check_value_type("nonlinearity", nonlinearity, [str], self.cls_name) + # 检查nonlinearity参数是否为有效的字符串 validator.check_string(nonlinearity, self._non_linearity, "nonlinearity", self.cls_name) self.nonlinearity = nonlinearity def construct(self, x, hx): + # 检查'x'是否为tenser(张量) _check_is_tensor('x', x, self.cls_name) + # 检查'hx'是否为tenser _check_is_tensor('hx', hx, self.cls_name) + # 检查"x"是否为float32或float16 _check_input_dtype(x.dtype, "x", [mstype.float32, mstype.float16], self.cls_name) + # 检查"hx"是否为float32或float16 _check_input_dtype(hx.dtype, "hx", [mstype.float32, mstype.float16], self.cls_name) + # 用于检查两个x和hx的批量大小是否相同 _check_batch_size_equal(x.shape[0], hx.shape[0], self.cls_name) + # 如果nonlinearity为tanh if self.nonlinearity == "tanh": + # 在tanh非线性情况下,它调用_rnn_tanh_cell函数 ret = _rnn_tanh_cell(x, hx, self.weight_ih, self.weight_hh, self.bias_ih, self.bias_hh) else: + # 在relu非线性情况下,它调用_rnn_relu_cell函数 ret = _rnn_relu_cell(x, hx, self.weight_ih, self.weight_hh, self.bias_ih, self.bias_hh) return ret - +# 用于创建和初始化LSTMCell类 +# LSTM(Long Short-Term Memory)是一种常用的递归神经网络类型,用于处理序列数据 class LSTMCell(RNNCellBase): r""" A LSTM (Long Short-Term Memory) cell. @@ -290,20 +355,33 @@ class LSTMCell(RNNCellBase): self.support_non_tensor_inputs = True def construct(self, x, hx): + # 检查'x'是否为tenser _check_is_tensor('x', x, self.cls_name) + # 检查'hx'是否为tuple _check_is_tuple('hx', hx, self.cls_name) + # 检查hx是否为一个长度为2的元组 _check_tuple_length('hx', hx, 2, self.cls_name) + # 检查hx[0]是否为tenser _check_is_tensor('hx[0]', hx[0], self.cls_name) + # 检查hx[1]是否为tenser _check_is_tensor('hx[1]', hx[1], self.cls_name) + # 检查x类型是否为float32或float16 _check_input_dtype(x.dtype, "x", [mstype.float32, mstype.float16], self.cls_name) + # 检查hx[0]类型是否为float32或float16 _check_input_dtype(hx[0].dtype, "hx[0]", [mstype.float32, mstype.float16], self.cls_name) + # 检查hx[1+]类型是否为float32或float16 _check_input_dtype(hx[1].dtype, "hx[1]", [mstype.float32, mstype.float16], self.cls_name) + # 检查x与hx[0]的批量大小是否相同 + # 这步操作对于创建LSTMCell十分重要。因为隐藏状态从批量大小通常与输入值的批量大小相同 _check_batch_size_equal(x.shape[0], hx[0].shape[0], self.cls_name) + # 检查x与hx[1]的批量大小是否相同 _check_batch_size_equal(x.shape[0], hx[1].shape[0], self.cls_name) return _lstm_cell(x, hx, self.weight_ih, self.weight_hh, self.bias_ih, self.bias_hh) - + # 用于检查在创建LSTM细胞时传入的输入参数是否有效 def _check_construct_args(self, *inputs, **kwargs): + # 如果长度为4 if len(inputs) == 4: + # 抛出异常 raise ValueError(f"For '{self.cls_name}', the number of input args of construct is {len(inputs)}, if you " f"are using the implementation of `nn.LSTMCell` from old MindSpore version(<1.6), " f"please notice that: LSTMCell has been changed from 'single LSTM layer' to " @@ -311,7 +389,9 @@ class LSTMCell(RNNCellBase): f"please use `nn.LSTM` instead.") return super()._check_construct_args(*inputs, **kwargs) - +# 用于创建和初始化GRUCell的类 +# GRU(Gated Recurrent Unit)是一种常用的递归神经网络类型,用于处理序列数据 +# 与LSTM不同,GRUCell具有更新门和重置门,可以更有效地处理长期依赖关系 class GRUCell(RNNCellBase): r""" A GRU(Gated Recurrent Unit) cell. @@ -372,9 +452,15 @@ class GRUCell(RNNCellBase): super().__init__(input_size, hidden_size, has_bias, num_chunks=3) def construct(self, x, hx): + # 检查x类型是否为tenser _check_is_tensor('x', x, self.cls_name) + # 检查hx类型是否为tenser _check_is_tensor('hx', hx, self.cls_name) + # 检查x类型是否为float32或float16 _check_input_dtype(x.dtype, "x", [mstype.float32, mstype.float16], self.cls_name) + # 检查hx类型是否为float32或float16 _check_input_dtype(hx.dtype, "hx", [mstype.float32, mstype.float16], self.cls_name) + # 用于检查x的批量大小是否与hx的批量大小相等 _check_batch_size_equal(x.shape[0], hx.shape[0], self.cls_name) + # 返回由_gru_cell函数计算得到的输出值 return _gru_cell(x, hx, self.weight_ih, self.weight_hh, self.bias_ih, self.bias_hh) diff --git a/mindspore/python/mindspore/nn/layer/rnn_utils.py b/mindspore/python/mindspore/nn/layer/rnn_utils.py index 6025449ab6f..903549dad51 100644 --- a/mindspore/python/mindspore/nn/layer/rnn_utils.py +++ b/mindspore/python/mindspore/nn/layer/rnn_utils.py @@ -13,15 +13,24 @@ # limitations under the License. # ============================================================================ '''Utils for RNNs CPU version, like Reverse operators''' +# 导入numpy模块,用于处理数值计算 import numpy as np +# 从mindspore.common.dtype模块中导入mstype类,用于处理数据类型 import mindspore.common.dtype as mstype +# 从mindspore.ops模块中导入P类,用于定义各种计算图操作 import mindspore.ops as P +# 从mindspore.ops.primitive模块中导入constexpr函数,用于在编译时计算常量表达式 from mindspore.ops.primitive import constexpr +# 从mindspore.nn.cell模块中导入Cell类,用于创建自定义的神经网络层 from mindspore.nn.cell import Cell +# 从mindspore.common.tensorTensor类,用于表示神经网络中的张量 from mindspore.common.tensor import Tensor @constexpr +# 用于生成一个指定范围、步长的等差数列 +# 其中三个参数分别是start(起始值),stop(结束值),和 step(步长) +# 当 start 和 stop 相等时,arange 函数返回一个空列表 def arange(start, stop, step): return Tensor(np.arange(start, stop, step), mstype.int32) @@ -33,14 +42,18 @@ class _Reverse(Cell): self.dim = dim def construct(self, input_x): + dim_size = input_x.shape[self.dim] + # 定义一个reversed_indexes函数,用arange函数生成一个等差数列 reversed_indexes = arange(dim_size-1, -1, -1) output = P.Gather()(input_x, reversed_indexes, self.dim) + # 返回output(反向索引张量) return output class _ReverseSequence(Cell): """Reverse sequence operator, like ReverseSequenceV2 in mindspore""" + # 初始化函数 def __init__(self, seq_dim, batch_dim=0): super().__init__() self.seq_dim = seq_dim @@ -48,40 +61,51 @@ class _ReverseSequence(Cell): def construct(self, x, seq_lengths): """Defines the ReverseSequence operator computation performed.""" + # 定义一个函数,用于实现序列的反向解码 batch_size = x.shape[self.batch_dim] max_seq_len = x.shape[self.seq_dim] seq_lens_type = seq_lengths.dtype + # 计算序列长度 back = P.Sub()(seq_lengths, P.OnesLike()(seq_lengths)) + # 创建batch_idx和forward_idx batch_idx = self.make_shape((batch_size, max_seq_len), seq_lens_type, 0) forward_idx = self.make_shape((batch_size, max_seq_len), seq_lens_type, 1) + # 计算back back = back.view(-1, 1) reverse_idx = P.Sub()(back, forward_idx) + # 判断reverse_idx是否小于0 condition = P.Less()(reverse_idx, P.ZerosLike()(reverse_idx)) reverse_idx = P.Select()(condition, forward_idx, reverse_idx) + # 将reverse_idx和batch_idx扩展维度 reverse_idx = P.ExpandDims()(reverse_idx, 2) batch_idx = P.ExpandDims()(batch_idx, 2) + # 如果batch_dim大于seq_dim,则对x,batch_idx,reverse_idx进行转置 if self.batch_dim > self.seq_dim: batch_idx = P.Transpose()(batch_idx, (1, 0, 2)) reverse_idx = P.Transpose()(reverse_idx, (1, 0, 2)) x = P.Transpose()(x, (1, 0, 2)) + # 计算start_indices start_indices = P.Concat(2)((batch_idx, reverse_idx)) + # 计算输出 output = P.GatherNd()(x, start_indices) - + # 返回output return output @staticmethod def make_shape(shape, dtype, range_dim): """Calculates the shape according by the inputs.""" + # 定义一个函数,用于计算输入shape的输出,并使用mstype.float32和dtype作为输出类型 output = P.Ones()(shape, mstype.float32) output = P.CumSum()(output, range_dim) output = P.Cast()(output, dtype) output = output - 1 + # 返回output return output diff --git a/mindspore/python/mindspore/nn/layer/rnns.py b/mindspore/python/mindspore/nn/layer/rnns.py index 310b3b9fa7c..a9b015d9df9 100644 --- a/mindspore/python/mindspore/nn/layer/rnns.py +++ b/mindspore/python/mindspore/nn/layer/rnns.py @@ -13,106 +13,154 @@ # limitations under the License. # ============================================================================ '''RNN operators module, include RNN, GRU''' +# 导入数学库,提供了一些基本的数学函数,如 sin、cos 等 import math +# 导入NumPy库,提供了一个强大的数组操作库 import numpy as np +# 导入MindSpore的神经网络模块 import mindspore.nn as nn +# 导入MindSpore的操作模块 import mindspore.ops as P +# 导入MindSpore的上下文模块,用于设置计算环境 import mindspore.context as context +# 导入MindSpore的数据类型模块 import mindspore.common.dtype as mstype +# 从MindSpore的操作模块中导入constexpr函数,用于在编译时计算常量 from mindspore.ops.primitive import constexpr +# 从MindSpore的张量模块中导入Tensor类,用于创建和操作张量 from mindspore.common.tensor import Tensor +# 从MindSpore的参数模块中导入ParameterTuple和Parameter类,用于创建和操作参数 from mindspore.common.parameter import ParameterTuple, Parameter +# 从MindSpore的神经网络模块中导入Cell类,用于创建和操作神经网络层 from mindspore.nn.cell import Cell +# 导入log模块,用于记录日志 from mindspore import log as logger +# 导入Validator类,用于验证参数 from mindspore._checkparam import Validator as validator +# 导入CudnnGRU类,用于实现CudnnGRU操作 from mindspore.ops.operations._rl_inner_ops import CudnnGRU from .rnn_cells import _rnn_relu_cell, _rnn_tanh_cell, _gru_cell, _lstm_cell +# 导入_rnn_relu_cell、_rnn_tanh_cell、_gru_cell和_lstm_cell等函数,用于实现常见的神经网络层 from .rnn_utils import _Reverse, _ReverseSequence __all__ = ['LSTM', 'GRU', 'RNN'] @constexpr +# 用于生成一个指定范围、步长和数据类型的等差数列 +# 四个参数,start(起始值),stop(结束值),step(步长)和 dtype(数据类型) def arange(start, stop, step, dtype): + # 返回生成数 return Tensor(np.arange(start, stop, step), dtype) @constexpr +# 用于初始化循环神经网络的状态 def _init_state(shape, dtype, is_lstm): + # 创建一个名为hx的具有指定形状的零张量 hx = Tensor(np.zeros(shape), dtype) + # 创建一个名为cx的具有指定形状的零张量 cx = Tensor(np.zeros(shape), dtype) + # 如果is_lstm参数为True if is_lstm: + # 返回hx和cx return (hx, cx) + # 返回hx return hx @constexpr def _check_input_dtype(input_dtype, param_name, allow_dtypes, cls_name): + # 检查输入张量的数据类型是否在允许的范围内 validator.check_type_name(param_name, input_dtype, allow_dtypes, cls_name) @constexpr +# 用于检查输入张量的数据类型是否相同且在允许的范围内 def _check_input_dtype_same_and_valid(args_name, args_value, valid_values, cls_name): + # args 的字典,其中包含输入参数的名称和值 + # 使用列表推导式遍历 args_name 和 args_value 列表,并将每个名称和值作为键值对添加到字典中 args = {args_name[i]: args_value[i] for i in range(len(args_value))} + # 检查输入张量的数据类型是否在允许的范围内 validator.check_types_same_and_valid(args, valid_values, cls_name) @constexpr +# 检查是否为tensor def _check_is_tensor(param_name, input_data, cls_name): """Internal function, used to check whether the input data is Tensor.""" + # 如果input_data不为None并且P.typeof(input_data)不是mstype.tensor_type if input_data is not None and not isinstance(P.typeof(input_data), mstype.tensor_type): + # 抛出异常 raise TypeError(f"For '{cls_name}', the '{param_name}' should be '{mstype.tensor_type}', " f"but got '{P.typeof(input_data)}'") @constexpr +# 检查是否为tuple def _check_is_tuple(param_name, input_data, cls_name): """Internal function, used to check whether the input data is Tensor.""" + # 如果input_data不为None并且P.typeof(input_data)不是mstype.tensor_type if input_data is not None and not isinstance(P.typeof(input_data), mstype.Tuple): + # 抛出异常 raise TypeError(f"For '{cls_name}', the '{param_name}' should be '{mstype.Tuple}', " f"but got '{P.typeof(input_data)}'") @constexpr +# 检查tuple长度 def _check_tuple_length(param_name, input_data, length, cls_name): """Internal function, used to check whether the input data is Tensor.""" + # 如果input_data不为None并且input_data 的长度(len(input_data)不等于给定的length if input_data is not None and len(input_data) != length: + # 抛出异常 raise TypeError(f"For '{cls_name}', the length of '{param_name}' should be '{length}', " f"but got '{len(input_data)}'") @constexpr +# 检查长度和大小 def _check_seq_length_size(batch_size_x, seq_length_size, cls_name): + # 如果batch_size_x不等于seq_length_size if batch_size_x != seq_length_size: + # 抛出异常 raise ValueError(f"For '{cls_name}' batch size of x and seq_length should be equal, " f"but got {batch_size_x} of x and {seq_length_size} of seq_length.") - +# 接受两个参数lengths 和 maxlen +# lengths 是一个整数列表,表示每个序列的长度;maxlen 是一个整数 def sequence_mask(lengths, maxlen): """generate mask matrix by seq_length""" + # 使用arange创建一个从0到maxlen的整数张量 range_vector = arange(0, maxlen, 1, lengths.dtype) result = range_vector < lengths.view(lengths.shape + (1,)) + # 输出转化为int型的mstype return result.astype(mstype.int32) - +# 两个参数,inputs是一个张量,表示输入序列;mask 是一个布尔张量 def select_by_mask(inputs, mask): """mask hiddens by mask matrix""" + # 返回选择的元素 return mask.view(mask.shape + (1,)).swapaxes(0, 1) \ .expand_as(inputs).astype(mstype.bool_) * inputs - +# 两个参数output是一个张量,表示隐藏层的状态;seq_length是一个整数列表 def get_hidden(output, seq_length): """get hidden state by seq_length""" + # 用arange创建一个从0到seq_length.shape[0]的整数张量 batch_index = arange(0, seq_length.shape[0], 1, seq_length.dtype) + # 用concat将seq_length合并堆叠 indices = P.Concat(1)((seq_length.view(-1, 1) - 1, batch_index.view(-1, 1))) + # 返回合并后的结果 return P.GatherNd()(output, indices) - +# 用于实现动态循环神经网络(如 LSTM、GRU 等) class _DynamicRNNBase(Cell): '''Dynamic RNN module to compute RNN cell by timesteps''' def __init__(self, mode): super().__init__() + # 根据mode给cell匹配与mode相同的参数 if mode == "RNN_RELU": cell = _rnn_relu_cell elif mode == "RNN_TANH": @@ -122,84 +170,132 @@ class _DynamicRNNBase(Cell): elif mode == "GRU": cell = _gru_cell else: + # 抛出异常 raise ValueError("Unrecognized RNN mode: " + mode) self.cell = cell self.is_lstm = mode == "LSTM" - + # 参数 + # x:张量,表示输入序列 + # h_0:张量,表示初始隐藏状态 + # w_ih:张量,表示输入到隐藏层的权重 + # w_hh:张量,表示隐藏层到隐藏层的权重 + # b_ih:张量,表示输入层偏置 + # b_hh:张量,表示隐藏层偏置 def recurrent(self, x, h_0, w_ih, w_hh, b_ih, b_hh): '''recurrent steps without sequence length''' time_step = x.shape[0] outputs = [] t = 0 h = h_0 + # 当t t + # 如果是LSTM,则计算输出和状态 if self.is_lstm: + # 计算输出和状态 state_t_0 = P.Select()(seq_cond, h_t[0], state_t[0]) state_t_1 = P.Select()(seq_cond, h_t[1], state_t[1]) output = P.Select()(seq_cond, h_t[0], zero_output) state_t = (state_t_0, state_t_1) + # 如果不是LSTM,则计算输出和状态 else: state_t = P.Select()(seq_cond, h_t, state_t) output = P.Select()(seq_cond, h_t, zero_output) + # 将输出和状态添加到输出列表中 outputs.append(output) + # 更新t t += 1 - outputs = P.Stack()(outputs) - return outputs, state_t + # 将输出列表中的元素堆叠起来 + outputs = P.Stack()(outputs) + # 返回输出和状态 + return outputs, state_t def construct(self, x, h, seq_length, w_ih, w_hh, b_ih, b_hh): x_dtype = x.dtype w_ih = w_ih.astype(x_dtype) w_hh = w_hh.astype(x_dtype) + # 如果b_ih是not None if b_ih is not None: b_ih = b_ih.astype(x_dtype) b_hh = b_hh.astype(x_dtype) + # 如果seq_length是None if seq_length is None: + # 返回recurrent return self.recurrent(x, h, w_ih, w_hh, b_ih, b_hh) + # 返回 return self.variable_recurrent(x, h, seq_length, w_ih, w_hh, b_ih, b_hh) - +# 用于实现具有 ReLU 激活函数的动态循环神经网络 class _DynamicRNNRelu(_DynamicRNNBase): '''Dynamic RNN module with Relu activation''' - + # 初始化 def __init__(self): mode = 'RNN_RELU' super().__init__(mode) - +# 用于实现具有 Tanh 激活函数的动态循环神经网络 class _DynamicRNNTanh(_DynamicRNNBase): '''Dynamic RNN module with Tanh activation''' @@ -207,26 +303,42 @@ class _DynamicRNNTanh(_DynamicRNNBase): mode = 'RNN_TANH' super().__init__(mode) - +# 用于在CPU和GPU上实现动态 GRU 循环神经网络 class _DynamicGRUCPUGPU(Cell): '''Dynamic GRU module on CPU and GPU''' def __init__(self): super().__init__() + # 创建Concat self.concat = P.Concat() + # 检查当前输出设备是否为GPU self.is_gpu = context.get_context("device_target") == "GPU" def construct(self, x, h_0, seq_length, w_ih, w_hh, b_ih, b_hh): + # 定义一个函数,用于计算GRU的输出和隐藏状态 + # 参数: + # w_ih:输入权重,形状为[gate_size, input_size] + # w_hh:输入权重,形状为[gate_size, hidden_size] + # b_ih:输入偏置,形状为[gate_size, 1] + # b_hh:输入偏置,形状为[gate_size, 1] + # x:输入张量值 + # h_0:初始隐藏状态 + # seq_length:序列长度,如果为None,则使用x的第一个维度 + + # 获取门张量大小和隐藏层大小 gate_size, input_size = w_ih.shape hidden_size = gate_size // 3 if self.is_gpu and seq_length is None: + # 如果输入是GPU,并且没有指定序列长度 if b_ih is None: + # 如果没有指定偏置 weights = self.concat(( w_ih.view(-1, 1, 1), w_hh.view(-1, 1, 1) )) has_bias = False else: + # 如果有指定偏置 has_bias = True weights = self.concat(( w_ih.view(-1, 1, 1), @@ -234,6 +346,7 @@ class _DynamicGRUCPUGPU(Cell): b_ih.view(-1, 1, 1), b_hh.view(-1, 1, 1) )) + # 调用CudnnGRU函数 output, h_n, _, _ = CudnnGRU(input_size, hidden_size, 1, has_bias, False, 0.0)( x, h_0.view(1, *h_0.shape), @@ -244,50 +357,76 @@ class _DynamicGRUCPUGPU(Cell): return output, h_n - +# 用于在 Ascend 设备上实现动态 GRU 循环神经网络 class _DynamicGRUAscend(Cell): '''Dynamic GRU module on Ascend''' - + # 用于构建动态 GRU 神经网络 def __init__(self): super().__init__() self.gru = P.DynamicGRUV2(gate_order='rzh') self.transpose = P.Transpose() self.dtype = mstype.float16 - + # 用于实现 GRU 神经网络的递归计算 + # x:输入张量,形状为[seq_length, batch_size, input_size] + # w_ih:输入权重,形状为[num_directions, 4*hidden_size, input_size] + # w_hh:输入权重,形状为[num_directions, 4*hidden_size, hidden_size] + # b_ih:输入偏置,形状为[num_directions, 4*hidden_size] + # b_hh:输入偏置,形状为[num_directions, 4*hidden_size] + # seq_length:序列长度,形状为[batch_size] + # h_0:初始隐藏状态,形状为[num_directions, batch_size, hidden_size] def construct(self, x, h_0, seq_length, w_ih, w_hh, b_ih, b_hh): + # 如果b_ih为None,则将b_ih设置为全0矩阵,b_hh设置为全0矩阵 if b_ih is None: b_ih = P.Zeros()(w_ih.shape[0], w_ih.dtype) b_hh = P.Zeros()(w_ih.shape[0], w_ih.dtype) + # 调用gru函数,计算输出,状态,梯度 outputs, _, _, _, _, _ = self.gru(self.cast(x, self.dtype), \ - self.cast(self.transpose(w_ih, (1, 0)), self.dtype), \ - self.cast(self.transpose(w_hh, (1, 0)), self.dtype), \ - self.cast(b_ih, self.dtype), \ - self.cast(b_hh, self.dtype), \ - None, self.cast(h_0, self.dtype)) + self.cast(self.transpose(w_ih, (1, 0)), self.dtype), \ + self.cast(self.transpose(w_hh, (1, 0)), self.dtype), \ + self.cast(b_ih, self.dtype), \ + self.cast(b_hh, self.dtype), \ + None, self.cast(h_0, self.dtype)) + # 判断seq_length是否为None if seq_length is not None: + # 获取outputs中每一行中seq_length个元素 h = get_hidden(outputs, seq_length) + # 根据seq_length生成mask mask = sequence_mask(seq_length, x.shape[0]) + # 根据mask从outputs中取出每一行 outputs = select_by_mask(outputs, mask) else: + # 如果seq_length为None,则h为outputs中最后一行 h = outputs[-1] + # 返回outputs值 return outputs, h - +# 用于在CPU和GPU上实现动态 LSTM 循环神经网络 class _DynamicLSTMCPUGPU(Cell): '''Dynamic LSTM module on CPU and GPU''' def __init__(self): super().__init__() + # 创建Concat self.concat = P.Concat() + # 如果输出设备为GPU self.is_gpu = context.get_context("device_target") == "GPU" - + # 参数: + # w_ih:输入权重,形状为[gate_size, input_size] + # w_hh:输入权重,形状为[gate_size, hidden_size] + # b_ih:输入偏置,形状为[gate_size, 1] + # b_hh:输入偏置,形状为[gate_size, 1] + # x:输入张量值 + # h_0:初始隐藏状态 + # seq_length:序列长度,如果为None,则使用x的第一个维度 def construct(self, x, h_0, seq_length, w_ih, w_hh, b_ih, b_hh): gate_size, input_size = w_ih.shape hidden_size = gate_size // 4 if seq_length is not None: output, (h_n, c_n) = _DynamicRNNBase('LSTM')(x, h_0, seq_length, w_ih, w_hh, b_ih, b_hh) else: + # 判断是否有偏置 if b_ih is None: + # 将权重和偏置拼接 weights = self.concat(( w_ih.view(-1, 1, 1), w_hh.view(-1, 1, 1) @@ -295,7 +434,9 @@ class _DynamicLSTMCPUGPU(Cell): has_bias = False else: has_bias = True + # 判断是否是GPU if self.is_gpu: + # 将权重和偏置拼接 weights = self.concat(( w_ih.view(-1, 1, 1), w_hh.view(-1, 1, 1), @@ -303,107 +444,154 @@ class _DynamicLSTMCPUGPU(Cell): b_hh.view(-1, 1, 1) )) else: + # 计算偏置 bias = b_ih + b_hh + # 将权重和偏置拼接 weights = self.concat(( w_ih.view(-1, 1, 1), w_hh.view(-1, 1, 1), bias.view(-1, 1, 1) )) + # 计算LSTM output, h_n, c_n, _, _ = P.LSTM(input_size, hidden_size, 1, has_bias, False, 0.0)( x, P.ExpandDims()(h_0[0], 0), P.ExpandDims()(h_0[1], 0), weights.astype(x.dtype) ) + # 返回output return output, (h_n, c_n) - +# 用于在Ascend设备上实现动态LSTM循环神经网络 class _DynamicLSTMAscend(Cell): '''Dynamic LSTM module on Ascend''' def __init__(self): super().__init__() + # 定义LSTM网络 self.lstm = P.DynamicRNN() + # 将输入的维度拼接 self.concat_dim1 = P.Concat(axis=1) + # 将输入的维度拼接 self.concat_dim0 = P.Concat(axis=0) + # 转置输入 self.transpose = P.Transpose() + # 将输入的类型转换为float16 self.cast = P.Cast() + # 将输入按照axis=0分割,输出4个 self.split = P.Split(axis=0, output_num=4) self.dtype = mstype.float16 - + # 参数: + # w_ih:输入权重,形状为[gate_size, input_size] + # w_hh:输入权重,形状为[gate_size, hidden_size] + # b_ih:输入偏置,形状为[gate_size, 1] + # b_hh:输入偏置,形状为[gate_size, 1] + # x:输入张量值 + # h_0:初始隐藏状态 + # seq_length:序列长度,如果为None,则使用x的第一个维度 def construct(self, x, h_0, seq_length, w_ih, w_hh, b_ih, b_hh): + # 定义LSTM函数,参数x为输入,w_ih为输入权重,w_hh为隐藏状态权重,b_ih为输入偏置,b_hh为隐藏状态偏置,h_0为初始隐藏状态,seq_length为序列长度 w_ih_i, w_ih_f, w_ih_g, w_ih_o = self.split(w_ih) w_hh_i, w_hh_f, w_hh_g, w_hh_o = self.split(w_hh) w_ih = self.concat_dim0((w_ih_i, w_ih_g, w_ih_f, w_ih_o)) w_hh = self.concat_dim0((w_hh_i, w_hh_g, w_hh_f, w_hh_o)) weight = self.concat_dim1((w_ih, w_hh)) + # 如果b_ih是None if b_ih is None: + # 全部设置为0 bias = P.Zeros()(w_ih.shape[0], w_ih.dtype) + # 否则 else: + # 首先对输入参数b_ih和b_hh拆分为四个部分 b_ih_i, b_ih_f, b_ih_g, b_ih_o = self.split(b_ih) b_hh_i, b_hh_f, b_hh_g, b_hh_o = self.split(b_hh) + # 最后将结果拼接在一起 bias = self.concat_dim0((b_ih_i + b_hh_i, \ b_ih_g + b_hh_g, \ b_ih_f + b_hh_f, \ b_ih_o + b_hh_o)) - + # 运算outputs输出量 outputs, h, c, _, _, _, _, _ = self.lstm(self.cast(x, self.dtype), \ self.cast(self.transpose(weight, (1, 0)), self.dtype), \ self.cast(bias, self.dtype), None, \ self.cast(P.ExpandDims()(h_0[0], 0), self.dtype), \ self.cast(P.ExpandDims()(h_0[1], 0), self.dtype)) + # 如果seq_length是not None if seq_length is not None: + # 用get_hidden函数对于隐藏状态张量h和工作记忆张量c进行切片 h = get_hidden(h, seq_length) c = get_hidden(c, seq_length) + # 根据输入长度创建mask mask = sequence_mask(seq_length, x.shape[0]) + # 使用 select_by_mask 函数根据掩码张量 mask 对输出张量进行切片 outputs = select_by_mask(outputs, mask) else: h = h[-1] c = c[-1] + # 返回处理后的输出张量、隐藏状态张量h和工作记忆张量c return outputs, (h, c) - +# 用于是先RNN循环神经网络的基本类 class _RNNBase(Cell): '''Basic class for RNN operators''' def __init__(self, mode, input_size, hidden_size, num_layers=1, has_bias=True, batch_first=False, dropout=0., bidirectional=False): super().__init__() + # 检查hidden_size是否为正整数 validator.check_positive_int(hidden_size, "hidden_size", self.cls_name) + # 检查input_size是否为正整数 validator.check_positive_int(input_size, "input_size", self.cls_name) + # 检查num_layers是否为正整数 validator.check_positive_int(num_layers, "num_layers", self.cls_name) + # 检查dropout类型是否为float型 validator.check_is_float(dropout, "dropout", self.cls_name) + # 检查has_bias类型是否为bool型 validator.check_value_type("has_bias", has_bias, [bool], self.cls_name) + # 检查batch_first类型是否为bool型 validator.check_value_type("batch_first", batch_first, [bool], self.cls_name) + # 检查bidirectional类型是否为bool型 validator.check_value_type("bidirectional", bidirectional, [bool], self.cls_name) - + # 如果dropout的值在0和1中间 if not 0 <= dropout < 1: + # 抛出异常 raise ValueError(f"For '{self.cls_name}', the 'dropout' should be a number in range [0, 1) " f"representing the probability of an element being zeroed, but got {dropout}.") - + # 如果dropout大于0且num_layers为1 if dropout > 0 and num_layers == 1: + # 给出一个警告 logger.warning("dropout option adds dropout after all but last " "recurrent layer, so non-zero dropout expects " "num_layers greater than 1, but got dropout={} and " "num_layers={}".format(dropout, num_layers)) - + # 如果接受输出装置为Ascend is_ascend = context.get_context("device_target") == "Ascend" + # 如果mode等于"LSTM" if mode == "LSTM": + # 计算门*4大小 gate_size = 4 * hidden_size self.rnn = _DynamicLSTMAscend() if is_ascend else _DynamicLSTMCPUGPU() + # 如果mode等于"GRU" elif mode == "GRU": + # 如果is_ascend和hidden_size整除16不等于0 if is_ascend and hidden_size % 16 != 0: + # 抛出异常 raise ValueError(f"GRU on ascend do not support hidden size that is not divisible by 16, " f"but get hidden size {hidden_size}, please reset the argument.") - gate_size = 3 * hidden_size + gate_size = 3 * hidden_size self.rnn = _DynamicGRUAscend() if is_ascend else _DynamicGRUCPUGPU() + # 如果mode等于"RNN_TANH" elif mode == "RNN_TANH": gate_size = hidden_size + # 用_DynamicRNNTanh函数 self.rnn = _DynamicRNNTanh() + # 如果mode等于"RNN_RELU" elif mode == "RNN_RELU": gate_size = hidden_size + # 用_DynamicRNNRelu函数 self.rnn = _DynamicRNNRelu() else: + # 抛出异常 raise ValueError(f"For '{self.cls_name}', the 'mode' should be in ['RNN_RELU', 'RNN_TANH', 'LSTM', 'GRU'], " f"but got {mode}.") @@ -413,53 +601,81 @@ class _RNNBase(Cell): else: self.reverse = P.ReverseV2([0]) self.reverse_sequence = P.ReverseSequence(0, 1) + # 初始化隐藏层大小 self.hidden_size = hidden_size + # 初始化batch_first self.batch_first = batch_first + # 初始化层数 self.num_layers = num_layers + # 初始化dropout self.dropout = dropout + # 初始化dropout操作 self.dropout_op = nn.Dropout(float(1 - dropout)) + # 初始化双向性 self.bidirectional = bidirectional + # 初始化是否有偏置 self.has_bias = has_bias + # 初始化方向数 num_directions = 2 if bidirectional else 1 + # 初始化是否为LSTM self.is_lstm = mode == "LSTM" - + # 用于存储输入门权重 self.w_ih_list = [] + # 用于存储隐藏门权重 self.w_hh_list = [] + # 用于存储输入门偏置 self.b_ih_list = [] + # 用于存储隐藏门偏置 self.b_hh_list = [] stdv = 1 / math.sqrt(self.hidden_size) + # 遍历隐藏层数量(num_layers)和方向数量(num_directions) for layer in range(num_layers): for direction in range(num_directions): + # 获取当前层的输入大小(input_size if layer == 0 else hidden_size * num_directions) layer_input_size = input_size if layer == 0 else hidden_size * num_directions + # 添加后缀('_reverse' if direction == 1 else '') suffix = '_reverse' if direction == 1 else '' + # 创建当前层的输入权重 self.w_ih_list.append(Parameter( Tensor(np.random.uniform(-stdv, stdv, (gate_size, layer_input_size)).astype(np.float32)), name='weight_ih_l{}{}'.format(layer, suffix))) + # 创建当前层的隐藏权重 self.w_hh_list.append(Parameter( Tensor(np.random.uniform(-stdv, stdv, (gate_size, hidden_size)).astype(np.float32)), name='weight_hh_l{}{}'.format(layer, suffix))) + # 如果存在偏置项 if has_bias: + # 创建当前层的输入偏置 self.b_ih_list.append(Parameter( Tensor(np.random.uniform(-stdv, stdv, (gate_size)).astype(np.float32)), name='bias_ih_l{}{}'.format(layer, suffix))) + # 创建当前层的隐藏偏置 self.b_hh_list.append(Parameter( Tensor(np.random.uniform(-stdv, stdv, (gate_size)).astype(np.float32)), name='bias_hh_l{}{}'.format(layer, suffix))) + # 将列表转换为ParameterTuple self.w_ih_list = ParameterTuple(self.w_ih_list) self.w_hh_list = ParameterTuple(self.w_hh_list) self.b_ih_list = ParameterTuple(self.b_ih_list) self.b_hh_list = ParameterTuple(self.b_hh_list) - + # 用于实现一个堆叠的双向LSTM(双向Long Short-Term Memory)网络 def _stacked_bi_dynamic_rnn(self, x, h, seq_length): + # 参数 + # self:指向包含此函数的类的实例。 + # x:输入张量,形状为[batch_size, max_time, input_size]。 + # h:初始隐藏状态张量,形状为[num_layers * 2, batch_size, hidden_size]。 + # seq_length:一个整数张量,表示每个序列的实际长度。形状为[batch_size] """stacked bidirectional dynamic_rnn""" pre_layer = x h_n = () c_n = () output = 0 + # 从0开始遍历LSTM层的数量 for i in range(self.num_layers): offset = i * 2 if self.has_bias: + # 获取LSTM层的权重矩阵、隐藏状态矩阵和偏置项,分别赋值给w_f_ih、w_f_hh、b_f_ih和b_f_hh w_f_ih, w_f_hh, b_f_ih, b_f_hh = \ self.w_ih_list[offset], self.w_hh_list[offset], \ self.b_ih_list[offset], self.b_hh_list[offset] @@ -501,62 +717,88 @@ class _RNNBase(Cell): return output, (h_n.view(h[0].shape), c_n.view(h[1].shape)) h_n = P.Concat(0)(h_n) return output, h_n.view(h.shape) - + # 实现多层RNN的计算,其中每一层都使用动态规划算法来计算隐藏层的状态 def _stacked_dynamic_rnn(self, x, h, seq_length): """stacked mutil_layer dynamic_rnn""" pre_layer = x h_n = () c_n = () output = 0 + # 从0开始循环遍历多层RNN的每一层 + # 遍历每一层 for i in range(self.num_layers): + # 如果有偏置 if self.has_bias: + # 获取权重和偏置 w_ih, w_hh, b_ih, b_hh = self.w_ih_list[i], self.w_hh_list[i], self.b_ih_list[i], self.b_hh_list[i] else: + # 获取权重 w_ih, w_hh = self.w_ih_list[i], self.w_hh_list[i] + # 偏置为None b_ih, b_hh = None, None + # 如果是LSTM if self.is_lstm: + # 获取输入的h和c h_i = (h[0][i], h[1][i]) else: + # 获取输入的h h_i = h[i] + # 运行RNN output, h_t = self.rnn(pre_layer, h_i, seq_length, w_ih, w_hh, b_ih, b_hh) + # 如果是LSTM,将输出和h_t的第一个元素和c_t的第一个元素拼接 pre_layer = self.dropout_op(output) if (self.dropout != 0 and i < self.num_layers - 1) else output if self.is_lstm: h_n += (h_t[0],) c_n += (h_t[1],) else: h_n += (h_t,) - if self.is_lstm: - h_n = P.Concat(0)(h_n) - c_n = P.Concat(0)(c_n) - h_n = h_n.view(h[0].shape) - c_n = c_n.view(h[1].shape) + # 如果是LSTM,将h_n和c_n拼接,并转换为h的形状 + if self.is_lstm: + h_n = P.Concat(0)(h_n) + c_n = P.Concat(0)(c_n) + h_n = h_n.view(h[0].shape) + c_n = c_n.view(h[1].shape) return output, (h_n.view(h[0].shape), c_n.view(h[1].shape)) + # 将h_n拼接,并转换为h的形状 h_n = P.Concat(0)(h_n) return output, h_n.view(h.shape) def construct(self, x, hx=None, seq_length=None): '''Defines the RNN like operators performed''' + # 获取输入数据batch的大小 max_batch_size = x.shape[0] if self.batch_first else x.shape[1] + # 如果使用双向RNN,那么每一层的隐藏层状态需要保留两个方向(前向和后向)的隐藏层状态 num_directions = 2 if self.bidirectional else 1 + # 检查x是否为tensor _check_is_tensor("x", x, self.cls_name) x_dtype = x.dtype if hx is not None: if not self.is_lstm: + # 检查h的类型是否为tensor _check_is_tensor("h", hx, self.cls_name) + # 检查输入数据的类型是否相同且有效 _check_input_dtype_same_and_valid(['x', 'hx'], [x_dtype, hx.dtype], \ [mstype.float32, mstype.float16], self.cls_name) else: + # 检查hx的类型是否为tuple _check_is_tuple('hx', hx, self.cls_name) + # 检查hx的长度是否为2 _check_tuple_length('hx', hx, 2, self.cls_name) + # 检查hx[0]的类型是否为tensor _check_is_tensor('hx[0]', hx[0], self.cls_name) + # 检查hx[1]的类型是否为tensor _check_is_tensor('hx[1]', hx[1], self.cls_name) + # 检查输入数据的类型是否相同且有效,类型是否为float32或float16 _check_input_dtype_same_and_valid(['x', 'hx[0]', 'hx[1]'], [x_dtype, hx[0].dtype, hx[1].dtype], \ [mstype.float32, mstype.float16], self.cls_name) else: + # 使用_init_state函数根据给定的隐藏层状态类型、批量大小和隐藏层大小初始化隐藏层状态 hx = _init_state((self.num_layers * num_directions, max_batch_size, self.hidden_size), \ x_dtype, self.is_lstm) if seq_length is not None: + # 检查seq_length的类型是否为int32或int64 _check_input_dtype(seq_length.dtype, "seq_length", [mstype.int32, mstype.int64], self.cls_name) + # 检查seq_length的形状是否与max_batch_size相匹配 _check_seq_length_size(max_batch_size, seq_length.shape[0], self.cls_name) if self.batch_first: x = P.Transpose()(x, (1, 0, 2)) @@ -570,7 +812,7 @@ class _RNNBase(Cell): return x_n.astype(x_dtype), hx_n.astype(x_dtype) return x_n.astype(x_dtype), (hx_n[0].astype(x_dtype), hx_n[1].astype(x_dtype)) - +# 用于实现简单循环神经网络(RNN)神经网络的类 class RNN(_RNNBase): r""" Stacked Elman RNN layers. @@ -638,6 +880,7 @@ class RNN(_RNNBase): """ def __init__(self, *args, **kwargs): + # 判断kwargs中是否包含nonlinearity参数,如果包含则根据nonlinearity参数的值设置mode的值,并从kwargs中删除nonlinearity参数 if 'nonlinearity' in kwargs: if kwargs['nonlinearity'] == 'tanh': mode = 'RNN_TANH' @@ -650,9 +893,10 @@ class RNN(_RNNBase): else: mode = 'RNN_TANH' + # 调用父类的构造函数,传入mode和args,kwargs参数 super(RNN, self).__init__(mode, *args, **kwargs) - +# 用于实现门控循环神经网络(GRU)神经网络的类 class GRU(_RNNBase): r""" Stacked GRU (Gated Recurrent Unit) layers. @@ -739,7 +983,7 @@ class GRU(_RNNBase): mode = 'GRU' super(GRU, self).__init__(mode, *args, **kwargs) - +# 用于实现长短期记忆(LSTM)神经网络的类 class LSTM(_RNNBase): r""" Stacked LSTM (Long Short-Term Memory) layers. diff --git a/mindspore/python/mindspore/nn/layer/thor_layer.py b/mindspore/python/mindspore/nn/layer/thor_layer.py index 6f7100154d9..320c4112054 100644 --- a/mindspore/python/mindspore/nn/layer/thor_layer.py +++ b/mindspore/python/mindspore/nn/layer/thor_layer.py @@ -13,31 +13,65 @@ # limitations under the License. # ============================================================================ +# 定义了用于第二order优化层的实现,即mindspore自研的thor优化层与optimizer中的thor优化器适配。它包含了一些用于计算梯度、更新参数等操作的函数 """layers for second order optimization""" +# 用于处理数值计算 import numpy as np +# 用于定义数据类型 import mindspore.common.dtype as mstype +# 用于记录日志 import mindspore.log as logger +# 用于处理张量(Tensor) from mindspore.common.tensor import Tensor +# 用于初始化张量 from mindspore.common.initializer import initializer, Initializer +# 这两个函数主要用于获取当前分布式训练环境下的组大小和节点排名 from mindspore.communication.management import get_group_size, get_rank +# 用于定义操作 from mindspore.ops import operations as P +# 用于定义参数 from mindspore.common.parameter import Parameter +# 用于检查参数 from mindspore._checkparam import Validator, Rel, twice +# 用于设置计算环境 from mindspore import context +# 用于定义神经网络的Cell类 from mindspore.nn.cell import Cell +# 用于定义激活函数 from mindspore.nn.layer.activation import get_activation +# 导入一些与并行计算相关的内部函数。这些函数主要用于处理分布式训练过程中的相关任务,例如检查当前节点是否为工作节点、获取分布式训练的上下文信息、设置当前节点的排名ID等 from mindspore.parallel._ps_context import _is_role_worker, _get_ps_context, \ _set_rank_id, _insert_hash_table_size, _set_cache_enable +# 用于处理并行计算 from mindspore.parallel._utils import _get_parallel_mode, _get_full_batch +# 用于设置计算环境 from mindspore.context import ParallelMode +# 用于定义constexpr装饰器 from mindspore.ops.primitive import constexpr +# 导入functional模块 from mindspore.ops import functional as F +# 用于裁剪张量。ClipByNorm类继承自Cell类,包含用于计算梯度、更新参数等操作的方法 from .basic import ClipByNorm +# 用于实现分布式张量计算、卷积计算、嵌入计算和嵌入查找等任务 __all__ = ['DenseThor', 'Conv2dThor', 'EmbeddingThor', 'EmbeddingLookupThor'] class DenseThor(Cell): + # 它是Cell类的子类。这个类主要用于实现分布式训练时的高阶API,用于处理全连接层 + # 全连接层是一种常用的神经网络层,用于将输入张量中的多个特征连接成一个特征 + # 这个类实现了将输入张量与权重矩阵相乘并加上偏置向量的问题,并使用指定的激活函数对输出张量进行处理 + """ + 该类的参数如下: + + in_channels (整数):输入通道的数目。 + out_channels (整数):输出通道的数目。 + weight_init (可选张量、字符串、初始化函数或数字):训练权重的初始化参数。其数据类型与输入 x 相同。可以是字符串、初始化函数或数字。默认值为 'normal'。 + bias_init (可选张量、字符串、初始化函数或数字):训练偏置的初始化参数。其数据类型与输入 x 相同。可以是字符串、初始化函数或数字。默认值为 'zeros'。 + has_bias (布尔值):指定层是否使用偏置向量。默认值为 True。 + activation (字符串):应用于输出张量激活函数的名称,例如 'ReLU'。默认值为 None。 + """ + r""" The dense connected layer and saving the information needed for THOR. @@ -94,109 +128,199 @@ class DenseThor(Cell): activation=None): """Initialize DenseThor.""" super(DenseThor, self).__init__() + """ + 检验输入参数并设置 + """ + # 表示应用thor层 self.thor = True + # 然后使用 Validator.check_positive_int 函数验证输入参数in_channels,out_channels的合法性,不合法则抛出异常 self.in_channels = Validator.check_positive_int(in_channels, "in_channels", self.cls_name) self.out_channels = Validator.check_positive_int(out_channels, "out_channels", self.cls_name) + # 使用 Validator.check_bool 函数验证 has_bias 参数的合法性,不合法则抛出异常 self.has_bias = Validator.check_bool(has_bias, "has_bias", self.cls_name) + # 检查 weight_init 参数是否为张量且其形状正确 if isinstance(weight_init, Tensor): + # weight_init 参数应是一个二维张量,其第一维等于 out_channels,第二维等于 in_channels if weight_init.dim() != 2 or weight_init.shape[0] != out_channels or \ weight_init.shape[1] != in_channels: + # 否则,抛出一个 ValueError 异常,提示错误信息 raise ValueError(f"For '{self.cls_name}', weight init shape error. The dim of 'weight_init' should " f"be equal to 2, and the first dim should be equal to 'out_channels', and the " f"second dim should be equal to 'in_channels'. But got 'weight_init': {weight_init}, " f"'out_channels': {out_channels}, 'in_channels': {in_channels}.") + # 使用给定的初始化参数 weight_init 和 in_channels、out_channels 初始化权重和偏置张量 self.weight = Parameter(initializer(weight_init, [out_channels, in_channels]), name="weight") + # 将bias设置为None,表示偏置张量未计算 self.bias = None + # 当 has_bias 为 True 时 if self.has_bias: + # 首先检查 bias_init 参数是否为张量且其形状正确 if isinstance(bias_init, Tensor): if bias_init.dim() != 1 or bias_init.shape[0] != out_channels: + # 否则,抛出一个 ValueError 异常,提示错误信息 raise ValueError(f"For '{self.cls_name}', bias init shape error. The dim of 'bias_init' should " f"be equal to 1, and the first dim should be equal to 'out_channels'. But got " f"'bias_init': {bias_init}, 'out_channels': {out_channels}.") + # 如果满足这些条件,则初始化偏置张量 self.bias self.bias = Parameter(initializer(bias_init, [out_channels]), name="bias") + # 并定义一个 BiasAdd 操作 self.bias_add。BiasAdd 操作用于在输入张量上添加偏置 self.bias_add = P.BiasAdd() + # 首先定义用于计算张量乘积的 MatMul 操作 self.matmul,并设置 transpose_b 参数为 True self.matmul = P.MatMul(transpose_b=True) + # 然后,使用 get_activation 函数获取激活函数的名称 self.activation = get_activation(activation) + # 设置标识存储存在激活函数的信息 self.activation_flag = self.activation is not None - + # 定义两个张量 self.matrix_a 和 self.matrix_g,其形状分别为 in_channels 和 out_channels,并将其数据类型设置为 float32。 + # 注:这些张量用于存储一些辅助变量,用于计算线性变换和激活函数 self.matrix_a = Parameter(Tensor(np.eye(in_channels).astype(np.float32)), name='matrix_a', requires_grad=False) self.matrix_g = Parameter(Tensor(np.eye(out_channels).astype(np.float32)), name="matrix_g", requires_grad=False) + """ + 定义矩阵操作函数 + """ + # 用于获取张量的形状 self.shape = P.Shape() + # 用于reshape张量,变换形状 self.reshape = P.Reshape() + # 用于转置张量 self.transpose = P.Transpose() + # 用于计算两个张量的乘积 self.mul = P.Mul() + # 示当前设备是否为 Ascend 设备 self.is_Ascend = True + # 表示张量的第 split_dim 维,用于分割张量 self.split_dim = 128 + # 当设备为 Ascend 设备时,调用 _process_ascend_dense_thor 方法进行处理 if context.get_context("device_target") == "Ascend": self._process_ascend_dense_thor(out_channels, in_channels) else: + # 否则,将 self.is_Ascend 设置为 False self.is_Ascend = False + # 并定义一个 MatMul 操作 self.cube_matmul,用于计算张量的转置乘积 self.cube_matmul = P.MatMul(transpose_a=True) + # 最后,定义一个 InsertGradientOf 操作 self.getG,用于插入计算梯度的函数 self.getG = P.InsertGradientOf(self.save_gradient) def _process_ascend_dense_thor(self, out_channels, in_channels): + # 于处理 Ascend 设备上的 dense thor 层 """process ascend dense thor""" + # 用于计算张量的转置乘积,transpose_b表示在计算张量乘积时,是否对张量的第 1 维进行转置。 + # 这里将 transpose_b 设置为 True,表示对张量的第 1 维进行转置 self.matmul = P.MatMul(transpose_b=True) + # 用于计算张量的转置乘积,它是 MatMul 函数的优化版本,适用于大型张量 self.cube_matmul = P.CusMatMulCube(transpose_a=True) + # 用于将张量的数据类型转换 self.cast = P.Cast() + # 表示输出通道是否为 2,用于判断是否为二分类问题 self.is_nsp_layer = (out_channels == 2) def save_gradient(self, dout): + # 用于保存梯度。这个函数主要用于 Thor 优化器 """ this function only for thor optimizer save_gradient """ out = dout + # 首先,检查当前设备是否为 Ascend 设备 if self.is_Ascend: + # 如果是非二分类问题 if not self.is_nsp_layer: + # 计算张量的转置乘积 matrix_g shape = self.shape(dout) + # 将张量的第 0 维转换为 float32 类型,并将其赋值给变量 normalizer 归一化因子 normalizer = self.cast(shape[0], mstype.float32) + # 作计算转置乘积 matrix_g matrix_g = self.cube_matmul(dout, dout) + # 并将其除以张量的第 0 维(即 batch_size)得到归一化的梯度 matrix_g = self.mul(matrix_g, 1.0 / normalizer) + # 将 matrix_g 保存 self.matrix_g = matrix_g else: + # 获取张量的形状 dout_shape dout_shape = self.shape(dout) + # 然后计算归一化因子 normalizer normalizer = dout_shape[0] + # 作计算转置乘积 matrix_g matrix_g = self.cube_matmul(dout, dout) + # 并将其除以张量的第 0 维(即 batch_size)得到归一化的梯度 matrix_g = self.mul(matrix_g, 1.0 / normalizer) + # 将 matrix_g 保存 self.matrix_g = matrix_g + # 返回计算得到的梯度 out return out def construct(self, x): + # 用于构建模型的前向传播过程。函数接收一个输入张量 x + # 首先检查模型是否启用 THOR 优化器 if self.thor: + # 计算张量的转置乘积 matrix_a + # 如果当前设备为 Ascend 设备 if self.is_Ascend: + # 获取张量的形状 shape,并将其转换为 float32 类型 inputs = self.cube_matmul(x, x) shape = self.shape(x) + # 计算归一化因子 normalizer normalizer = self.cast(shape[0], mstype.float32) + # 使用 CusMatMulCube 操作计算转置乘积 matrix_a,并将结果除以 normalizer matrix_a = self.mul(inputs, 1.0 / normalizer) + # 保存 self.matrix_a = matrix_a else: + # 如果当前设备非 Ascend 设备,计算方法与第 1 步相同,但不需要进行转置乘积计算 inputs = self.cube_matmul(x, x) inputs_shape = self.shape(inputs) normalizer = inputs_shape[0] matrix_a = self.mul(inputs, 1.0 / normalizer) self.matrix_a = matrix_a + # 计算张量 x 与权重矩阵的乘积 x = self.matmul(x, self.weight) + # 调用 getG 方法计算张量 x 的梯度 x = self.getG(x) else: + # 否则直接计算张量 x 与权重矩阵的乘积 x = self.matmul(x, self.weight) + # 计算得到张量 x,然后检查模型是否包含 bias 层 if self.has_bias: + # 使用 bias_add 操作将 x 与 bias 相加 x = self.bias_add(x, self.bias) + # 最后,检查模型是否启用激活函数 if self.activation_flag: + # 如果启用激活函数,使用 activation 操作计算激活函数的结果,并返回 x = self.activation(x) return x def extend_repr(self): + # 用于扩展模型的 __repr__ 方法 s = 'input_channels={}, output_channels={}'.format(self.in_channels, self.out_channels) + # 方法接收一个参数 self,返回一个字符串,用于表示模型的基本信息,包括输入通道数、输出通道数以及是否有 bias 层 if self.has_bias: s += ', has_bias={}'.format(self.has_bias) return s class _ConvThor(Cell): + # _ConvThor 类用于实现一个 N-D 卷积操作,用于处理输入信号,由多个输入平面组成 + """ + 属性: + + in_channels (int): 输入通道数。 + out_channels (int): 输出通道数。 + kernel_size (int|tuple): 卷积核大小。 + stride (int|tuple): 卷积步长。 + pad_mode (str): 填充模式,可选值为 'same'、'valid'、'pad'。 + padding (int|tuple): 填充大小。 + dilation (int|tuple): 卷积核的扩张大小。 + group (int): 分组卷积的组数。 + has_bias (bool): 是否有 bias 层。 + weight_init (Initializer): 权重初始化方法。 + bias_init (Initializer): bias 初始化方法。 + transposed (bool): 是否为转置卷积(默认为 False)。 + + """ + """ Applies a N-D convolution over an input signal composed of multiple input planes. """ @@ -205,72 +329,158 @@ class _ConvThor(Cell): padding, dilation, group, has_bias, weight_init, bias_init, transposed=False): """Initialize _ConvThor.""" super(_ConvThor, self).__init__() + """ + 检验输入参数是否符合要求并设置 + """ + # 使用 Validator.check_positive_int 方法检查输入通道数是否为正整数,输出通道数是否为正整数 self.in_channels = Validator.check_positive_int(in_channels, "in_channels", self.cls_name) self.out_channels = Validator.check_positive_int(out_channels, "out_channels", self.cls_name) + # 然后,设置卷积核大小、卷积步长、填充模式以及 bias 初始化方法 self.kernel_size = kernel_size self.stride = stride self.pad_mode = pad_mode self.bias_init = bias_init + # 检查填充模式是否为元组(tuple)或整数(int) if isinstance(padding, tuple): for pad in padding: + # 如果是元组,则遍历元组中的每个元素,并使用 Validator.check_non_negative_int 方法检查每个元素是否为非负整数 Validator.check_non_negative_int(pad, 'padding item', self.cls_name) + # 设置填充模式 self.padding = padding elif isinstance(padding, int): + # 如果填充模式是整数,则使用 Validator.check_non_negative_int 方法检查填充模式是否为非负整数 Validator.check_non_negative_int(padding, 'padding', self.cls_name) + # 设置填充模式 self.padding = padding else: + # 否则,抛出一个类型错误异常,提示填充模式必须为整数或元组 raise TypeError(f"For '{self.cls_name}', the type of 'padding' must be int/tuple(int), but got " f"{type(padding).__name__}.") self.dilation = dilation + # 首先,使用 Validator.check_positive_int 方法检查 group 是否为正整数 self.group = Validator.check_positive_int(group, "group", self.cls_name) self.has_bias = has_bias + # 然后,使用 self.__validate_kernel_size 和 self.__validate_stride self.__validate_dilation方法检查卷积核大小和扩张大小和卷积步长是否符合预期类型 self.__validate_kernel_size(kernel_size) + # 并将 dilation,kernel_size 和 has_bias 属性设置为相应的值 self.__validate_stride(stride) self.__validate_dilation(dilation) + # 检查输入通道数是否可以被组数整除 if in_channels % group != 0: + # 如果不能,则抛出一个 ValueError 异常,提示 "输入通道数" 必须被 "组数" 整除 raise ValueError(f"For '{self.cls_name}', the 'in_channels' must be divisible by 'group', but got " f"'in_channels': {in_channels} and 'group': {group}.") + # 检查输出通道数是否可以被组数整除 if out_channels % group != 0: + # 如果不能,则抛出一个 ValueError 异常,提示 "输出通道数" 必须被 "组数" 整除 raise ValueError(f"For '{self.cls_name}', the 'out_channels' must be divisible by 'group', but got " f"'out_channels': {out_channels} and 'group': {group}.") + # 检查是否为转置卷积(transposed 属性) if not transposed: + # 如果是非转置卷积,则计算输出通道数和输入通道数除以组的商,作为卷积核的大小 shape = [out_channels, in_channels // group, *kernel_size] else: + # 否则,则计算输入通道数和输出通道数除以组的商,作为卷积核的大小 shape = [in_channels, out_channels // group, *kernel_size] + # 然后,使用 Parameter 类创建权重参数,并使用初始化方法(initializer)初始化权重 self.weight = Parameter(initializer(weight_init, shape), name='weight') - + + # 接下来,检查是否有 bias 层(has_bias 属性)并检验无误 if Validator.check_bool(has_bias, "has_bias", self.cls_name): + # 并根据 has_bias 的值创建 bias 参数 self.bias = Parameter(initializer(self.bias_init, [out_channels]), name='bias') else: + # 如果 has_bias 为 False,且 bias_init 不为 'zeros' if self.bias_init != 'zeros': + # 则警告用户 bias_init 设置被忽略 logger.warning("Value of 'has_bias' is False, value of 'bias_init' will be ignored.") + # 并设置bias为None self.bias = None + """ + 以下定义了三个辅助方法,用于检验三个不同的输入参数 + """ def __validate_kernel_size(self, kernel_size): + # 用于验证卷积核大小 """validate kernel size.""" + # 检查它的类型、元素值以及是否大于等于 1 if (not isinstance(kernel_size[0], int)) or (not isinstance(kernel_size[1], int)) or \ isinstance(kernel_size[0], bool) or isinstance(kernel_size[1], bool) or \ kernel_size[0] < 1 or kernel_size[1] < 1: + # 如果不符合要求,则抛出一个 ValueError 异常 raise ValueError(f"For '{self.cls_name}', all elements in 'kernel_size' should be int or tuple and " f"equal to or greater than 1, but got 'kernel_size': {kernel_size}.") def __validate_stride(self, stride): + # 用于验证卷积步长 """validate stride.""" + # 检查它的类型、元素值以及是否大于等于 1 if (not isinstance(stride[0], int)) or (not isinstance(stride[1], int)) or \ isinstance(stride[0], bool) or isinstance(stride[1], bool) or stride[0] < 1 or stride[1] < 1: + # 如果不符合要求,则抛出一个 ValueError 异常 raise ValueError(f"For '{self.cls_name}', all elements in 'stride' should be int or tuple and " f"equal to or greater than 1, but got 'stride': {stride}.") def __validate_dilation(self, dilation): + # 用于验证填充模式 """validate dilation.""" + # 检查它的类型、元素值以及是否大于等于 1 if (not isinstance(dilation[0], int)) or (not isinstance(dilation[1], int)) or \ isinstance(dilation[0], bool) or isinstance(dilation[1], bool) or dilation[0] < 1 or dilation[1] < 1: + # 如果不符合要求,则抛出一个 ValueError 异常 raise ValueError(f"For '{self.cls_name}', all elements in 'dilation' should be int or tuple and " f"equal to or greater than 1, but got 'dilation': {dilation}.") class Conv2dThor(_ConvThor): + # 继承自 _ConvThor。Conv2dThor 类用于实现 2D 卷积层,并保存所需的信息以便在 THOR 过程中使用 + """ + 卷积层的计算公式如下: + + .. math:: + + out_j = \sum_{i=0}^{C_{in} - 1} ccor(W_{ij}, X_i) + b_j, + + 其中 :math:ccor 是交叉相关操作,:math:C_{in} 是输入通道数,:math:j 范围从 0 到 :math:C_{out} - 1, + :math:W_{ij} 对应于第 :math:i 个输入通道的第 :math:j 个滤波器,:math:out_{j} 对应于第 :math:j 个输出通道。 + 卷积核的形状为 :math:(\text{ks_h}, \text{ks_w}),其中 :math:\text{ks_h} 和 :math:\text{ks_w} 是卷积核的高度和宽度。 + 完整的卷积核形状为 :math:(C_{out}, C_{in} // \text{group}, \text{ks_h}, \text{ks_w}),其中 group 是分组数。 + + 对于 pad_mode 为 "valid" 时,输出高度和宽度将不包含填充,输出将不进行填充。对于 pad_mode 为 "same" 时,输出高度和宽度将与输入相同,但可能会进行填充。 + 对于 pad_mode 为 "pad" 时,输入的边缘将进行填充。padding 的值必须大于等于 0。 + + 输入参数: + + in_channels (int):输入通道的数量,即输入Tensor的深度。 + + out_channels (int):输出通道的数量,即输出Tensor的深度。 + + kernel_size (Union[int, tuple[int]]):卷积核的高度和宽度,可以是单个整数表示高度和宽度相同,也可以是一个包含两个整数的元组表示高度和宽度。 + + stride (Union[int, tuple[int]]):卷积核在输入Tensor上的移动距离,可以是单个整数表示在高度和宽度上的移动距离相同,也可以是一个包含两个整数的元组表示在高度和宽度上的移动距离。默认值为1。 + + pad_mode (str):填充模式,可选值为"same","valid","pad"。: + + "same": 采用 completion 方式。输出Tensor的形状与输入Tensor相同,总的填充数量将在水平和垂直方向上平均分配。如果可能的话,从顶部和底部填充,从左侧和右侧填充。如果模式为 "same",则padding 必须为0。 + "valid": 采用 discarding 方式。输出Tensor的形状不包含填充,多余的像素将被丢弃。如果模式为 "valid",则padding 必须为0。 + "pad": 显式地在输入Tensor的边界进行填充。padding 的值必须大于等于0。 + + padding (Union[int, tuple[int]]):显式地在输入Tensor的边界进行填充。如果 padding 是整数,则水平和垂直方向的填充数量相同。如果 padding 是元组,则水平和垂直方向的填充数量分别为 padding[0] 和 padding[1]。默认值为0。 + + dilation 参数表示卷积核的扩张率,如果设置为 :math:k > 1,那么对于每个采样位置,将跳过 :math:k - 1 个像素。其值必须大于等于 1 且小于等于输入的高度和宽度。 + + group 参数表示将输入通道和输出通道分为组,in_channels 和 out_channels 必须可以被组数整除。如果 group 等于 in_channels 和 out_channels,那么这个 2D 卷积层也可以被称为 2D 深度卷积层。 + + has_bias 参数表示是否使用偏置向量。 + + weight_init 和 bias_init 参数用于初始化卷积核(默认值为'normal')和偏置向量(默认值为'zeros')。 + + input:输入 x 的形状为 :math:(N, C_{in}, H_{in}, W_{in})。 + output:输出为 :math:(N, C_{out}, H_{out}, W_{out})。 + + """ + r""" 2D convolution layer and saving the information needed for THOR. @@ -373,124 +583,194 @@ class Conv2dThor(_ConvThor): pad_mode='same', padding=0, dilation=1, group=1, has_bias=False, weight_init='normal', bias_init='zeros'): """Initialize Conv2dThor.""" + # 首先将 kernel_size 乘以2 kernel_size = twice(kernel_size) + # 然后将 stride 乘以2 stride = twice(stride) + # 传入dilation self._dilation = dilation + # 将 dilation 乘以2 dilation = twice(dilation) + # 它调用父类 __init__ 方法,传入相关参数 super(Conv2dThor, self).__init__(in_channels, out_channels, kernel_size, stride, pad_mode, padding, dilation, group, has_bias, weight_init, bias_init) + # 定义了一个 conv2d 属性,用于存储 P.Conv2D 层的实例 self.conv2d = P.Conv2D(out_channel=self.out_channels, kernel_size=self.kernel_size, mode=1, pad_mode=self.pad_mode, pad=self.padding, stride=self.stride, dilation=self.dilation, group=self.group) + # 然后,初始化一个 depthwise_conv2d 方法 self._init_depthwise_conv2d(weight_init) + # 获取biasadd函数 self.bias_add = P.BiasAdd() + # 最后,将 thor 属性设置为True self.thor = True + # 首先计算卷积核的大小乘积 hw self.hw = kernel_size[0] * kernel_size[1] + # 然后计算输入通道、卷积核大小和输出通道的乘积 matrix_a_dim 和 matrix_g_dim self.matrix_a_dim = self.in_channels * self.kernel_size[0] * self.kernel_size[1] self.matrix_g_dim = self.out_channels + # 接下来,定义了一些用于后续操作的PaddlePaddle张量操作函数,如 shape、reshape、mul 和 cast self.shape = P.Shape() self.reshape = P.Reshape() self.mul = P.Mul() self.cast = P.Cast() + # 最后,定义了一个名为 a_normalizer 的参数张量,用于存储归一化因子 self.a_normalizer = Parameter(initializer(1, [1], mstype.float32), name="a_normalizer", requires_grad=False) self.g_normalizer = Parameter(initializer(1, [1], mstype.float32), name="g_normalizer", requires_grad=False) + # 并设置 is_Ascend 为True,注:父类中已检验为Ascend可以直接使用 self.is_Ascend = True + # 首先检查当前设备目标是否为 "Ascend" if context.get_context("device_target") == "Ascend": + # 如果是,则调用 内部方法_process_ascend_conv2d_thor 方法进行后续处理 self._process_ascend_conv2d_thor(kernel_size, stride) else: + # 否则,将 is_Ascend 设置为False self.is_Ascend = False + # 并根据参数定义了一些用于后续操作的PaddlePaddle张量操作函数,如 img2col、matmul、reduce_mean 和 matrix_a_cov、matrix_g_cov self.img2col = P.Im2Col(kernel_size=kernel_size, stride=stride, pad_mode="same") self.matmul = P.MatMul(transpose_b=True) self.reduce_mean = P.ReduceMean(keep_dims=False) + # 两个参数张量 matrix_a_cov 和 matrix_g_cov,分别用于存储输入通道和输出通道的协方差矩阵。这些参数在训练过程中可能会被更新,以计算归一化因子 self.matrix_a_cov = Parameter(Tensor(np.zeros([self.matrix_a_dim, self.matrix_a_dim]).astype(np.float32)), name='matrix_a', requires_grad=False) self.matrix_g_cov = Parameter(Tensor(np.zeros([self.matrix_g_dim, self.matrix_g_dim]).astype(np.float32)), name='matrix_g', requires_grad=False) + # 最后,定义了一个名为 save_gradient 的函数,用于保存梯度 self.getG = P.InsertGradientOf(self.save_gradient) def _process_ascend_conv2d_thor(self, kernel_size, stride): + # 用于处理升序的卷积2D操作。函数的主要目的是实现一个名为CusImg2Col的Custom操作,该操作用于将图像转换为列。 + # 此外,它还实现了其他一些相关操作,如矩阵乘法、转置、重塑等 """process ascend conv2d thor""" + # 定义一个名为ksizes的元组,其中包含卷积核的大小 ksizes = (1, kernel_size[0], kernel_size[1], 1) + # 名为strides的元组,其中包含卷积步长 strides = (1, stride[0], stride[1], 1) + # 名为ksizes_tbe的元组,其中包含卷积核的大小(不包括深度) ksizes_tbe = (kernel_size[0], kernel_size[1]) + # 调用P.CusImg2Col操作,传入卷积核大小、步长和填充模式,创建一个名为img2col的Custom操作 self.img2col = P.CusImg2Col(ksizes=ksizes, strides=strides) + # 创建transpose操作 self.transpose = P.Transpose() + # 创建reshape操作 self.reshape = P.Reshape() + # 创建cube_matmul操作,并设置转置标志为True self.cube_matmul = P.CusMatMulCube(transpose_a=True) + # 定义一个名为diag_block_dim的整数,表示对矩阵进行对角化分解的块大小 self.diag_block_dim = 128 + # 定义两个参数matrix_a_cov和matrix_g_cov,分别表示矩阵A和G的对角矩阵 self.matrix_a_cov = Parameter(Tensor(np.eye(self.matrix_a_dim).astype(np.float32)), name='matrix_a', requires_grad=False) self.matrix_g_cov = Parameter(Tensor(np.eye(self.matrix_g_dim).astype(np.float32)), name='matrix_g', requires_grad=False) + # 创建P.Slice操作 self.slice = P.Slice() + # 调用P.NewIm2Col操作,传入卷积核大小、步长和填充模式,创建一个名为im2col的Custom操作 self.im2col = P.NewIm2Col(ksizes=ksizes_tbe, strides=stride[0], padding_mode="SAME") def _init_depthwise_conv2d(self, weight_init): + # 用于初始化深度卷积2D操作。函数的主要目的是检查设备目标是否为"Ascend",以及确保分组参数与输入通道、输出通道的关系符合要求 """Initialize depthwise conv2d op""" + # 如果设备目标为"Ascend"且分组参数大于1 if context.get_context("device_target") == "Ascend" and self.group > 1: + # 则设置膨胀参数dilation self.dilation = self._dilation + # 调用Validator.check_int方法,检查分组参数group是否与输入通道、输出通道的关系符合要求 Validator.check_int('group', self.group, self.in_channels, Rel.EQ, self.cls_name) Validator.check_int('group', self.group, self.out_channels, Rel.EQ, self.cls_name) + # 创建一个名为conv2d的P.DepthwiseConv2dNative操作,传入相关参数,如通道乘积、卷积核大小、填充模式、填充量、步长和膨胀参数 self.conv2d = P.DepthwiseConv2dNative(channel_multiplier=1, kernel_size=self.kernel_size, pad_mode=self.pad_mode, pad=self.padding, stride=self.stride, dilation=self.dilation) + # 定义一个名为weight_shape的元组,表示权重矩阵的形状 weight_shape = [1, self.in_channels, *self.kernel_size] + # 初始化权重矩阵weight,可以使用给定的weight_init参数(可以是Tensor或Initializer类型),或者根据权重形状创建一个默认初始值 self.weight_init = weight_init + # 检查weight_init是否为Tensor或Initializer类型 if isinstance(weight_init, Tensor): + # 如果是Tensor类型,则将其转换为np.array,并交换轴 self.weight_init = Tensor(weight_init.asnumpy().swapaxes(0, 1), weight_init.dtype) if isinstance(weight_init, Initializer): + # 如果是Initializer类型,则将其形状设置为weight_shape self.weight_init.shape = weight_shape + # 最后,使用initializer函数根据weight_init和weight_shape创建一个参数weight self.weight = Parameter(initializer(self.weight_init, weight_shape), name='weight') def save_gradient(self, dout): + # 用于保存梯度。函数的主要目的是根据设备目标判断是否为"Ascend",然后对梯度进行相应的处理 """save_gradient""" + # 首先,将输入的dout传入,并在后续转换为所需形状 out = dout + # 如果设备目标为"Ascend" if self.is_Ascend: + # 获取其形状 dout_shape = self.shape(dout) + # 对梯度进行转置和重塑操作,以便将其转换为适合矩阵乘法的形式 dout = self.transpose(dout, (0, 2, 3, 1)) dout = self.reshape(dout, (-1, dout_shape[1])) + # 计算梯度的平方和,并计算归一化因子 dout_shape = self.shape(dout) normalizer = dout_shape[0] + # 对矩阵G进行矩阵乘法操作,并将结果除以归一化因子 matrix_g = self.cube_matmul(dout, dout) normalizer = self.cast(normalizer, mstype.float32) matrix_g = self.mul(matrix_g, 1.0 / normalizer) + # 保存归一化因子和矩阵G self.g_normalizer = normalizer self.matrix_g_cov = matrix_g else: + # 否则,对梯度进行 reduce_mean 操作 dout = self.reduce_mean(dout, 0) dout_shape = self.shape(dout) + # 并将其转换为适合矩阵乘法的形式 dout = self.reshape(dout, (dout_shape[0], -1)) + # 计算归一化因子和矩阵G dout_shape = self.shape(dout) normalizer = dout_shape[1] dout = self.cast(dout, mstype.float32) matrix_g = self.matmul(dout, dout) matrix_g = self.mul(matrix_g, 1.0 / normalizer) + # 保存归一化因子和矩阵G self.g_normalizer = normalizer self.matrix_g_cov = matrix_g + # 返回初始的梯度 return out def construct(self, x): + # 用于构建卷积神经网络模型。函数的主要目的是根据thor标志和设备目标判断是否进行THOR优化,然后根据情况调用相应的操作 + # 如果thor标志为True if self.thor: + # 如果设备目标为"Ascend" if self.is_Ascend: + # 使用im2col操作将输入x转换为适合矩阵乘法的形式 matrix_a = self.im2col(x) + # 计算归一化因子和矩阵A matrix_a_shape = self.shape(matrix_a) y = matrix_a_shape[3] matrix_a = self.reshape(matrix_a, (-1, y)) matrix_a_shape = self.shape(matrix_a) normalizer = matrix_a_shape[0] + # 对矩阵A进行矩阵乘法操作,并将结果除以归一化因子 matrix_a = self.cube_matmul(matrix_a, matrix_a) normalizer = self.cast(normalizer, mstype.float32) matrix_a = self.mul(matrix_a, 1.0 / normalizer) + # 保存归一化因子和矩阵A self.a_normalizer = normalizer self.matrix_a_cov = matrix_a + # 将权重转换为float16类型 weight = self.cast(self.weight, mstype.float16) + # 并调用conv2d操作计算输出 output = self.conv2d(x, weight) + # 使用getG操作计算输出 output = self.getG(output) else: + # 否则,不使用float16类型进行计算 + # 使用img2col操作将输入x转换为适合矩阵乘法的形式 matrix_a = self.img2col(x) + # 计算归一化因子和矩阵A matrix_a_shape = self.shape(matrix_a) matrix_a = self.reshape(matrix_a, (matrix_a_shape[0] * matrix_a_shape[1] * matrix_a_shape[2], matrix_a_shape[3], -1)) @@ -498,27 +778,43 @@ class Conv2dThor(_ConvThor): matrix_a_shape = self.shape(matrix_a) normalizer = matrix_a_shape[1] matrix_a = self.cast(matrix_a, mstype.float32) + # 对矩阵A进行矩阵乘法操作,并将结果除以归一化因子 matrix_a = self.matmul(matrix_a, matrix_a) matrix_a = self.mul(matrix_a, 1.0 / normalizer) + # 保存归一化因子和矩阵A self.a_normalizer = normalizer self.matrix_a_cov = matrix_a + # 调用conv2d操作计算输出 output = self.conv2d(x, self.weight) + # 使用getG操作计算输出 output = self.getG(output) else: + # 如果设备目标为"Ascend" if self.is_Ascend: + # 则将权重转换为float16类型 weight = self.cast(self.weight, mstype.float16) + # 并调用conv2d操作计算输出 output = self.conv2d(x, weight) else: + # 否则,不使用float16类型进行计算,直接调用conv2d操作计算输出 output = self.conv2d(x, self.weight) + # 如果存在偏置项 if self.has_bias: + # 如果设备目标为"Ascend" if self.is_Ascend: + # 则将偏置转换为float16类型 bias = self.cast(self.bias, mstype.float16) + # 并调用bias_add操作计算输出 output = self.bias_add(output, bias) else: + # 否则,不使用float16类型进行计算,直接调用bias_add操作计算输出 output = self.bias_add(output, self.bias) + # 返回计算得到的输出 return output def extend_repr(self): + # 用于扩展类的表示。它接收类的属性作为参数,并返回一个字符串,用于表示类的详细信息。 + # 这个函数的主要目的是方便地显示卷积神经网络模型的详细信息,以便于调试和理解 s = 'input_channels={}, output_channels={}, kernel_size={}, stride={}, ' \ 'pad_mode={}, padding={}, dilation={}, group={}, has_bias={}, ' \ 'bias_init={}'.format(self.in_channels, self.out_channels, self.kernel_size, @@ -528,6 +824,29 @@ class Conv2dThor(_ConvThor): class EmbeddingThor(Cell): + # 它是基于MindSpore的Cell类。这个类用于实现一个简单的查找表,用于存储一个固定的字典和大小,并用于保存THOR所需的信息 + """ + 主要功能包括: + + 初始化一个具有给定字典大小的查找表,可以使用给定的初始化方法(例如,normal、uniform等)。 + 接收一个整数列表作为输入,输出对应的词向量。 + 保存所需的A和G信息,这些信息将在后续的卷积神经网络层中用于THOR优化。 + + 输入参数包括: + + vocab_size:字典大小。 + embedding_size:每个嵌入向量的维度。 + use_one_hot:是否应用独热编码。默认值为False。 + embedding_table:初始化嵌入表的方法或张量。可以是字符串(例如,'normal'、'uniform'等)、张量或初始化函数。默认值为'normal'。 + dtype:输入x的数据类型,默认为mindspore.float32。 + padding_idx:当遇到索引时,输出嵌入向量的初始化方法。当padding_idx遇到索引时,输出嵌入向量将初始化为零。默认值为None,表示不进行初始化。 + + 输入输出格式如下: + + 输入:整数列表,形状为(batch_size, x_length)。 + 输出:词向量,形状为(batch_size, x_length, embedding_size)。 + + """ r""" A simple lookup table that stores embeddings of a fixed dictionary and size and saving the information needed for THOR. @@ -572,93 +891,156 @@ class EmbeddingThor(Cell): def __init__(self, vocab_size, embedding_size, use_one_hot=False, embedding_table='normal', dtype=mstype.float32, padding_idx=None): + # 它接收五个参数,分别是字典大小vocab_size、嵌入向量维度embedding_size、是否应用独热编码use_one_hot、初始化方法embedding_table和填充索引padding_idx """Initialize EmbeddingThor.""" super(EmbeddingThor, self).__init__() + """ + 检查输入参数是否符合要求并设置 + """ + # 使用Validator.check_value_type函数检查vocab_size、embedding_size和use_one_hot的类型是否为整数、布尔值和mstype.number_type self.vocab_size = Validator.check_value_type('vocab_size', vocab_size, [int], self.cls_name) self.embedding_size = Validator.check_value_type('embedding_size', embedding_size, [int], self.cls_name) Validator.check_value_type('use_one_hot', use_one_hot, [bool], self.cls_name) + # Validator.check_subclass函数检查dtype是否为mstype.number_type的子类 Validator.check_subclass("dtype", dtype, mstype.number_type, self.cls_name) + # 设置检验后的输入参数 self.use_one_hot = use_one_hot self.dtype = dtype + # 首先使用initializer函数根据embedding_table的初始化方法初始化一个具有给定字典大小vocab_size和嵌入向量维度embedding_size的张量 self.init_tensor = initializer(embedding_table, [vocab_size, embedding_size]) self.padding_idx = padding_idx + # 然后,如果padding_idx不为None if padding_idx is not None: + # 则检查padding_idx是否在有效范围内(即在0到字典大小之间),并将其设置为无效索引 self.padding_idx = Validator.check_int_range(padding_idx, 0, vocab_size, Rel.INC_BOTH, "padding_idx", self.cls_name) self.init_tensor = self.init_tensor.to_tensor().asnumpy() + # 最后,将init_tensor转换为NumPy数组,并将padding_idx对应的值设置为0 self.init_tensor[self.padding_idx] = 0 + # 这段代码主要定义了EmbeddingThor类的一些属性,如embedding_table、expand、reshape_flat等 self.embedding_table = Parameter(self.init_tensor, name='embedding_table') self.expand = P.ExpandDims() self.reshape_flat = P.Reshape() + # 这个属性将用于后续的代码中张量展开的操作 self.shp_flat = (-1,) + # 同时,还定义了一些计算图相关的操作,如gather、one_hot、array_mul、reshape、get_shp等 self.gather = P.GatherV2() self.one_hot = P.OneHot() + # 定义了两个常量on_value和off_value,分别表示1.0和0.0。这两个常量将在后续的代码中用于计算张量乘法、求和等操作 self.on_value = Tensor(1.0, self.dtype) self.off_value = Tensor(0.0, self.dtype) self.array_mul = P.MatMul() self.reshape = P.Reshape() self.get_shp = P.Shape() + # 在后续的代码中,将使用这个属性来判断是否使用THOR优化 self.thor = True + # 定义了两个参数矩阵matrix_a和matrix_g,分别具有给定字典大小vocab_size和嵌入向量维度embedding_size。这两个参数主要用于保存所需的A和G信息 self.matrix_a = Parameter(Tensor(np.zeros([vocab_size]).astype(np.float32)), name='matrix_a', requires_grad=False) self.matrix_g = Parameter(Tensor(np.zeros([embedding_size, embedding_size]).astype(np.float32)), name="matrix_g", requires_grad=False) + # reduce_sum用于对张量进行求和操作,并将结果保留在原始维度上 self.reduce_sum = P.ReduceSum(keep_dims=False) + # getG用于插入一个计算图梯度的节点 self.getG = P.InsertGradientOf(self.save_gradient) self.cast = P.Cast() + # 如果计算设备为Ascend if context.get_context("device_target") == "Ascend": + # 则使用P.CusMatMulCube进行矩阵乘法操作 self.cube_matmul = P.CusMatMulCube(transpose_a=True) else: + # 否则使用P.MatMul进行矩阵乘法操作 self.cube_matmul = P.MatMul(transpose_a=True) + # 用于实现张量乘法 self.mul = P.Mul() def save_gradient(self, dout): + # 用于在THOR优化器中保存张量的梯度。函数接收一个输入参数dout """ this function only for thor optimizer save_gradient """ out = dout + # 首先将dout转换为所需的形状 shape = self.get_shp(dout) normalizer = self.cast(shape[0], mstype.float32) + # 然后使用cube_matmul和mul操作计算张量的梯度 matrix_g = self.cube_matmul(dout, dout) + # 最后将梯度除以输入张量的形状(即样本数量)并保存 matrix_g = self.mul(matrix_g, 1.0 / normalizer) self.matrix_g = matrix_g return out def construct(self, ids): + # 用于构建模型的前向传播过程 + # 使用expand和reshape_flat操作将ids扩展和重塑为所需的形状 extended_ids = self.expand(ids, -1) + # 将形状加上embedding_size,最后将结果作为out_shape返回。这样,out_shape将是一个包含ids张量原始形状和embedding_size的元组 out_shape = self.get_shp(ids) + (self.embedding_size,) flat_ids = self.reshape_flat(extended_ids, self.shp_flat) - + + # 如果use_one_hot为True if self.use_one_hot: + # 使用one_hot操作将flat_ids转换为一个独热编码 one_hot_ids = self.one_hot(flat_ids, self.vocab_size, self.on_value, self.off_value) + # 然后将结果与embedding_table进行矩阵乘法操作 output_for_reshape = self.array_mul(one_hot_ids, self.embedding_table) else: + # 如果thor为True if self.thor: + # 使用one_hot操作将flat_ids转换为一个独热编码。one_hot操作将一个整数序列转换为一个二维张量,其中每个元素表示该整数在行向量中的位置, + # 该列向量的值为1,其他位置为0。参数self.vocab_size表示词汇表的大小,self.on_value和self.off_value分别表示独热编码中1和0的值 one_hot_ids = self.one_hot(flat_ids, self.vocab_size, self.on_value, self.off_value) + # 则使用reduce_sum和gather操作计算矩阵matrix_a matrix_a = self.reduce_sum(one_hot_ids, 0) self.matrix_a = matrix_a + # 使用gather操作将embedding_table中对应的位置提取出来 output_for_reshape = self.gather(self.embedding_table, flat_ids, 0) + # 并使用getG插入计算图梯度的节点 output_for_reshape = self.getG(output_for_reshape) else: + # 否则,直接使用gather操作将embedding_table中对应的位置提取出来 output_for_reshape = self.gather(self.embedding_table, flat_ids, 0) - + + # 使用gather和reshape操作将处理后的张量转换回原始的输入形状,并返回 output = self.reshape(output_for_reshape, out_shape) return output def extend_repr(self): + # 用于扩展模型的表示。函数返回一个字符串,包含了模型的一些属性值,例如词汇表大小、嵌入层大小、是否使用独热编码、嵌入张量、张量的数据类型和填充索引 s = 'vocab_size={}, embedding_size={}, use_one_hot={}, embedding_table={}, dtype={}, padding_idx={}'.format( self.vocab_size, self.embedding_size, self.use_one_hot, self.embedding_table, self.dtype, self.padding_idx) return s - +# @constexpr装饰器表示这个函数可以在编译时计算,而不是在运行时计算。这样,编译器可以在编译时确定axis的值,而不是在运行时 @constexpr def _make_axis_range(start, end): + # 用于创建一个从start到end的整数元组axis axis = tuple(range(start, end)) return axis class EmbeddingLookupThor(Cell): + # 它是EmbeddingLookup类的扩展,用于在THOR(张量处理自动并行)中使用。这个类的主要功能是返回输入张量中指定索引的切片,并保存所需的A和G信息 + """ + 主要参数如下: + + vocab_size (整数):字典中嵌入的尺寸。 + embedding_size (整数):每个嵌入向量的尺寸。 + param_init (Union[Tensor, str, Initializer, numbers.Number]):嵌入表的初始化方法。可以是张量、字符串、初始化方法或数字。默认值为'normal'。 + target (字符串):指定目标设备,必须是'DEVICE'或'CPU'。默认值为'CPU'。 + slice_mode (字符串):在半自动并行/自动并行中的切片方式。必须是nn.EmbeddingLookup中的有效值。默认值为nn.EmbeddingLookup.BATCH_SLICE。 + manual_shapes (元组):在字段切片模式下所需的 accompaniment 数组。 + max_norm (浮点数或None):最大裁剪值。数据类型必须为float16、float32或None。默认值为None。 + sparse (布尔值):使用稀疏模式。当'target'设置为'CPU'时,'sparse'必须为True。默认值为True。 + vocab_cache_size (整数):字典中嵌入的缓存大小。默认值为0。仅在'DEVICE'目标时有效。同时,对应优化器的时刻参数也会被设置为缓存大小。此外,需要注意的是,设置较大的缓存可能会导致'DEVICE'内存不足。 + + 输入: input_indices (张量):形状为 :math:(y_1, y_2, ..., y_S)。 + + 输出:Tensor,形状为 :math:(z_1, z_2, ..., z_N)。 + + """ + r""" Returns a slice of the input tensor based on the specified indices and saving the information needed for THOR. @@ -715,6 +1097,7 @@ class EmbeddingLookupThor(Cell): >>> print(result.shape) (2, 2, 2) """ + # 定义了四个字符串变量,分别表示批量切片、字段切片、表行切片和表列切片方式。这些变量将用于设置nn.EmbeddingLookup类中的切片方式 BATCH_SLICE = "batch_slice" FIELD_SLICE = "field_slice" TABLE_ROW_SLICE = "table_row_slice" @@ -724,229 +1107,409 @@ class EmbeddingLookupThor(Cell): target='CPU', slice_mode='batch_slice', manual_shapes=None, max_norm=None, sparse=True, vocab_cache_size=0): super(EmbeddingLookupThor, self).__init__() + """ + 检验参数是否符合要求并设置 + """ + # 首先对sparse参数进行验证,确保其数据类型为布尔值 Validator.check_value_type('sparse', sparse, [bool], self.cls_name) + # 然后,对vocab_size和vocab_cache_size参数进行验证,确保它们为正整数 self.vocab_size = Validator.check_positive_int(vocab_size, 'vocab_size', self.cls_name) self.vocab_cache_size = Validator.check_non_negative_int(vocab_cache_size, 'vocab_cache_size', self.cls_name) + # 接下来,设置target、sparse、cache_enable和dtype属性 self.target = target self.sparse = sparse self.cache_enable = self.vocab_cache_size > 0 self.forward_unique = False self.dtype = mstype.float16 + # 首先检查target参数是否在'CPU'或'DEVICE'之间 if target not in ('CPU', 'DEVICE'): + # 如果不在此范围内,则抛出一个错误 raise ValueError(f"For '{self.cls_name}', the 'target' should be one of values in ('CPU', 'DEVICE'), " f"but got {target}.") + # 然后,它检查sparse参数是否为True且target为'CPU' if not sparse and target == 'CPU': + # 如果是,则抛出一个错误 raise ValueError(f"For '{self.cls_name}', embedding_lookup must be sparse when 'target' is CPU, but got " f"'sparse': {sparse}, 'target': {target}.") + # 接下来,根据sparse参数的情况 if sparse: self.gatherv2 = P.SparseGatherV2() + # 使用P.SparseGatherV2()或P.Gather()设置gatherv2属性 else: self.gatherv2 = P.Gather() + # 创建一个EmbeddingLookup类的实例,并为其添加primitive_target属性,其值为'CPU'。这样,在调用EmbeddingLookupThor类的实例时,可以确保在'CPU'目标上执行 self.embeddinglookup = P.EmbeddingLookup().add_prim_attr('primitive_target', 'CPU') + # 最后,使用_get_ps_context("enable_ps")获取并设置PS上下文 enable_ps = _get_ps_context("enable_ps") + # 首先检查enable_ps是否为True if enable_ps: + # 如果是,则调用_process_vocab_cache方法处理字典缓存 self._process_vocab_cache(slice_mode) + # 然后,使用Validator类验证embedding_size参数是否为正整数 self.embedding_size = Validator.check_positive_int(embedding_size, 'embedding_size', self.cls_name) + # 并使用Parameter类创建一个参数张量,其形状为[vocab_size, embedding_size],数据类型为float16 self.embedding_table = Parameter(initializer(param_init, [self.vocab_size, self.embedding_size], mstype.float16), name='embedding_table') + # 最后,获取并设置并行模式 parallel_mode = _get_parallel_mode() + # 并根据并行模式是否为自动并行检查是否需要进行自动并行优化 is_auto_parallel = parallel_mode in (ParallelMode.SEMI_AUTO_PARALLEL, ParallelMode.AUTO_PARALLEL) + """ + 定义了一些用于处理张量的操作,包括Gather、Reshape、Unique和Shape等。这些操作通常用于处理张量数据的各种转换和聚合操作 + """ self.gather_revert = P.Gather() self.reshape_first = P.Reshape() self.reshape = P.Reshape() self.unique = P.Unique() self.shape = P.Shape() + # 首先检查是否是自动并行模式 if is_auto_parallel: + # 如果是,则使用Unique操作并设置分片为((1,),) self.unique = P.Unique().shard(((1,),)) + # 然后,如果cache_enable为True且enable_ps为True if self.cache_enable and enable_ps: + # 则调用_set_voacb_cache_enable_for_ps方法设置字典缓存并行 self._set_voacb_cache_enable_for_ps(vocab_cache_size, embedding_size, vocab_size) + # 检查是否是自动并行模式 if is_auto_parallel: + # 则在Unique操作中添加cache_enable属性 self.unique.add_prim_attr('cache_enable', True) + # 最后,设置indices_shape_size为2,即indices张量的形状大小 indices_shape_size = 2 + """ + 以下代码主要用于处理不同分片模式和非自动并行的情况 + + 首先,它检查slice_mode是否为"field_slice"、"table_row_slice"、"table_column_slice"或"batch_slice",且is_auto_parallel是否为False。 + 如果是,则根据不同的分片模式和自动并行情况执行相应的操作。 + 具体操作包括设置gatherv2和embeddinglookup操作的分片策略,设置indices_strategy,并设置embedding_table的unique属性。 + 最后,检查max_norm是否为正浮点数,如果是,则将其转换为float16类型的张量。 + """ + # 主要用于处理分片模式为"field_slice"且为自动并行的情况 if slice_mode == "field_slice" and is_auto_parallel: + # 如果manual_shapes不为None if not manual_shapes: + # 则检查manual_shapes是否为元组,如果不是,则抛出一个错误 raise ValueError(f"For '{self.cls_name}', the 'manual_shapes' should not be none " f"when 'slice_mode' is 'field_slice'.") + # 检查manual_shapes是否为元组 if not isinstance(manual_shapes, tuple): + # 如果不是,则抛出一个类型错误 raise TypeError(f"For '{self.cls_name}', the type of 'manual_shapes' must be tuple(int), but got " f"type {type(manual_shapes).__name__}.") + # 遍历manual_shapes中的每个维度 for dim in manual_shapes: + # 并使用Validator类验证每个维度是否为正整数 Validator.check_positive_int(dim, 'manual shape dim', self.cls_name) + # 使用gatherv2和embeddinglookup操作的shard方法设置分片,并将manual_split属性添加到这些操作中 self.gatherv2.add_prim_attr("manual_split", manual_shapes) self.embeddinglookup.add_prim_attr("manual_split", manual_shapes) + # 使用gatherv2和embeddinglookup操作的shard方法设置分片,并将相应的形状策略添加到这些操作中 self.gatherv2.shard(((get_group_size(), 1), (1, get_group_size()))) self.embeddinglookup.shard(((get_group_size(), 1), (1, get_group_size()))) + # 主要用于处理分片模式为"table_row_slice"且为自动并行的情况 elif slice_mode == "table_row_slice" and is_auto_parallel: + # 获取全批处理状态full_batch full_batch = _get_full_batch() + # 如果target为'DEVICE'且full_batch为False,或者cache_enable为True且enable_ps为True且sparse为True if (target == 'DEVICE' and not full_batch) or (self.cache_enable and enable_ps and sparse): + # 则将indices_shape_size设置为1 indices_shape_size = 1 + # 并使用gather_revert操作的shard方法设置分片 + # 在这个例子中,分片的形状为(1, 1),即表示沿第0维(行)分片1个元素,沿第1维(列)分片1个元素 self.gather_revert.shard(((1, 1), (get_group_size(),))) + # 同时,设置forward_unique属性为True self.forward_unique = True + # 计算indices_strategy,即indices张量的形状策略 indices_strategy = (1,) * indices_shape_size + # 使用gatherv2和embeddinglookup操作的shard方法设置分片,并将indices_strategy添加到这些操作中 self.gatherv2.shard(((get_group_size(), 1), indices_strategy)) self.embeddinglookup.shard(((get_group_size(), 1), indices_strategy)) + # 主要用于处理分片模式为"table_column_slice"且为自动并行的情况 elif slice_mode == "table_column_slice" and is_auto_parallel: + # 如果target为'DEVICE' if target == 'DEVICE': + # 则将indices_shape_size设置为1 indices_shape_size = 1 + # 并使用gather_revert操作的shard方法设置分片 + # 在这个例子中,分片的形状为(1, get_group_size()),即表示沿第0维(行)分片1个元素,沿第1维(列)分片get_group_size()个元素 self.gather_revert.shard(((1, get_group_size()), (1,))) + # 同时,设置forward_unique属性为True self.forward_unique = True + # 计算indices_strategy,即indices张量的形状策略 indices_strategy = (1,) * indices_shape_size + # 使用gatherv2和embeddinglookup操作的shard方法设置分片,并将indices_strategy添加到这些操作中 self.gatherv2.shard(((1, get_group_size()), indices_strategy)) self.embeddinglookup.shard(((1, get_group_size()), indices_strategy)) + # 主要用于处理分片模式为"batch_slice"且为自动并行的情况 elif slice_mode == "batch_slice" and is_auto_parallel: + # 计算indices_strategy,即indices张量的形状策略 indices_strategy = [get_group_size()] + # 将indices_strategy转换为元组 indices_strategy.extend([1] * (indices_shape_size - 1)) indices_strategy = tuple(indices_strategy) + # 并使用gatherv2和embeddinglookup操作的shard方法设置分片,并将indices_strategy添加到这些操作中 self.gatherv2.shard(((1, 1), indices_strategy)) self.embeddinglookup.shard(((1, 1), indices_strategy)) + # 主要用于处理其他分片模式或非自动并行的情况 else: + # 如果是自动并行模式 if is_auto_parallel: + # 则抛出一个错误,提示用户slice_mode应该是一个有效的值 raise ValueError(f"For '{self.cls_name}', the 'slice_mode' should be one of values in " f"['field_slice', 'table_row_slice', 'table_column_slice', 'batch_slice'], " f"but got 'slice_mode': {slice_mode}") + # 如果cache_enable为True且enable_ps为False if self.cache_enable and not enable_ps: + # 检查parallel_mode是否为ParallelMode.STAND_ALONE if parallel_mode != ParallelMode.STAND_ALONE: + # 如果是,则抛出错误,提示用户parallel_mode应该是一个有效的值 raise ValueError(f"For '{self.cls_name}', the 'parallel_mode' should be equal to " f"'ParallelMode.STAND_ALONE', but got {parallel_mode}.") + # 调用_set_cache_enable方法设置缓存功能 self._set_cache_enable() + # 然后,设置embedding_table的unique属性为forward_unique self.embedding_table.unique = self.forward_unique self.max_norm = max_norm + # 检查max_norm是否为None if self.max_norm is not None: + # 并检查max_norm是否为正浮点数 self.max_norm = Validator.check_positive_float(self.max_norm, 'max_norm', self.cls_name) + # 如果是,则将其转换为float16类型的张量 self.max_norm = Tensor(self.max_norm, dtype=mstype.float16) - + + # 设置thor属性为True self.thor = True + # 定义matrix_a参数,其值为一个零张量,大小为[vocab_size],数据类型为float32。name属性为matrix_a,requires_grad属性为False self.matrix_a = Parameter(Tensor(np.zeros([vocab_size]).astype(np.float32)), name='matrix_a', requires_grad=False) + # 定义matrix_g参数,其值为一个零张量,大小为[embedding_size, embedding_size],数据类型为float32。name属性为matrix_g,requires_grad属性为False self.matrix_g = Parameter(Tensor(np.zeros([embedding_size, embedding_size]).astype(np.float32)), name="matrix_g", requires_grad=False) + # 定义reduce_sum操作,用于计算张量的和,keep_dims属性为False self.reduce_sum = P.ReduceSum(keep_dims=False) + # 用于插入梯度计算图 self.getG = P.InsertGradientOf(self.save_gradient) + # 用于类型转换 self.cast = P.Cast() + # 用于矩阵乘法,transpose_a属性为True self.cube_matmul = P.MatMul(transpose_a=True) + # 用于张量乘法 self.mul = P.Mul() + # 定义on_value张量,其值为1.0,数据类型为float3 self.on_value = Tensor(1.0, self.dtype) + # 定义off_value张量,其值为0.0,数据类型为float32 self.off_value = Tensor(0.0, self.dtype) + # 用于创建一个独热向量 self.one_hot = P.OneHot() def save_gradient(self, dout): + # 用于为Thor优化器保存梯度 """ this function only for thor optimizer save_gradient """ out = dout + # 获取张量形状 shape = self.shape(dout) + # 它计算一个归一化因子,将其转换为float16类型的张量 normalizer = self.cast(shape[0], mstype.float16) + # 接下来,它对dout进行重塑,以便其形状为(-1,嵌入维度) dout = self.reshape(dout, (-1, self.embedding_size)) + # 然后,它使用矩阵乘法计算梯度的转置乘积 matrix_g = self.cube_matmul(dout, dout) + # 最后,它将计算得到的梯度矩阵除以归一化因子,并将结果转换为float16类型的张量 matrix_g = self.mul(matrix_g, 1.0 / normalizer) matrix_g = self.cast(matrix_g, mstype.float16) + # 最后,将计算得到的梯度矩阵保存到matrix_g属性中 self.matrix_g = matrix_g return out def _set_cache_enable(self): + # 用于设置EmbeddingLookup操作的缓存功能 """EmbeddingLookup cache check for not ps env, which is only support 'ascend'.""" + # 检查target是否为DEVICE if self.target != 'DEVICE': + # 如果不是,则抛出异常 raise ValueError(f"For '{self.cls_name}', the configuration of 'vocab_cache_size' is valid " f"only when 'target' is 'DEVICE', but got 'target': {self.target}.") + # 检查sparse是否为True if not self.sparse: + # 如果不是,则抛出异常 raise ValueError(f"For '{self.cls_name}', the configuration of 'vocab_cache_size' is valid " f"only when 'sparse' is true, but got 'sparse': {self.sparse}.") + # 如果device_target不是Ascend if context.get_context("device_target") != 'Ascend': + # 那么将抛出一个错误,提示用户配置的vocab_cache_size仅在device_target为Ascend时有效 raise ValueError(f"For '{self.cls_name}', the configuration of 'vocab_cache_size' is valid " f"only when 'device_target' is 'Ascend', but got {context.get_context('device_target')}.") - + # 用于记录一条信息,表示已经成功启用了嵌入查找的缓存 logger.info("EmbeddingLookup cache enable takes effect.") + # 如果满足上述这些条件,那么它将启用缓存并设置forward_unique和unique属性 self.forward_unique = True self.unique = P.Unique().add_prim_attr('primitive_target', 'CPU') self.unique.add_prim_attr('cache_enable', True) + # 接下来,它设置embedding_table的cache_enable和cache_shape属性 self.embedding_table.cache_enable = self.cache_enable self.embedding_table.cache_shape = (self.vocab_cache_size, self.embedding_size) + # 最后,它将reshape_first操作添加到prim_attr中,以便在计算嵌入时使用 self.reshape_first = P.Reshape().add_prim_attr('primitive_target', 'CPU') def _process_vocab_cache(self, slice_mode): + # 主要用于处理嵌入查找的缓存 """PS embeddingLookup cache check and process.""" + # 将cache_enable属性设置为False self.cache_enable = False + # 首先,它检查vocab_cache_size是否大于0 if self.vocab_cache_size > 0: + # 如果是,则检查target是否为CPU if self.target == 'CPU': + # 如果是,那么将发出警告,提示vocab_cache_size仅在DEVICE目标下有效 logger.warning("The configuration of 'vocab_cache_size' is valid only in 'DEVICE' target, " "current target is CPU, so it will be ignored.") return + # 调用_get_ps_context函数,获取enable_ps的值 enable_ps = _get_ps_context("enable_ps") + # 如果不是,它还会检查enable_ps是否为True if not enable_ps: + # 如果不是,那么将发出警告,提示vocab_cache_size仅在parameter server trainning mode下有效 logger.warning( "The configuration of 'vocab_cache_size' is valid only in parameter server trainning " "mode, current mode is not parameter server trainning mode, so it will be ignored.") return + # 如果满足这些条件,那么将启用缓存 + """ + 以下代码主要用于处理嵌入查找的缓存 + """ + # 使用_get_parallel_mode()函数获取并行模式 parallel_mode = _get_parallel_mode() + # 然后,将获取到的并行模式(parallel_mode)与ParallelMode.SEMI_AUTO_PARALLEL(半自动并行)和ParallelMode.AUTO_PARALLEL(自动并行)进行比较。 + # 如果parallel_mode等于这些值之一,那么is_auto_parallel将被设置为True,表示当前系统处于自动并行模式。否则,is_auto_parallel将被设置为False,表示当前系统不在自动并行模式 is_auto_parallel = parallel_mode in (ParallelMode.SEMI_AUTO_PARALLEL, ParallelMode.AUTO_PARALLEL) + # 首先,检查is_auto_parallel是否为True if is_auto_parallel: + # 使用get_group_size()获取当前系统的组大小(rank_size) rank_size = get_group_size() + # 使用get_rank()获取当前系统的排名(rank_id) rank_id = get_rank() + # 使用_get_full_batch()函数获取全批量(full_batch)的设置 full_batch = _get_full_batch() + # 检查当前系统是否为分布式训练。如果rank_size大于1且full_batch为False且slice_mode不为"table_row_slice" if rank_size > 1 and not (full_batch and slice_mode == "table_row_slice"): + # 那么将引发一个错误,因为在这种情况下,嵌入查找缓存仅在全批量和"table_row_slice"并行策略下使用 raise ValueError(f"For '{self.cls_name}', the embeddingLookup cache of parameter server parallel " f"only be used in 'full_batch' and 'table_row_slice' parallel strategy, but got " f"'full_batch': {full_batch}, 'slice_mode': {slice_mode}.") + # 如果满足条件,那么将嵌入查找缓存的大小乘以rank_size self.vocab_cache_size = self.vocab_cache_size * rank_size + # 使用_set_rank_id()函数设置当前系统的排名ID _set_rank_id(rank_id) + # 最后,将self.cache_enable设置为True,表示已启用嵌入查找缓存 self.cache_enable = True + # 检查_is_role_worker()函数的返回值,如果为True,则表示当前角色是工作角色 if _is_role_worker(): + # 然后,它获取上下文的enable_sparse值,并与self.sparse进行比较。如果context.get_context("enable_sparse")不等于self.sparse self.vocab_size = self.vocab_cache_size if context.get_context("enable_sparse") != self.sparse: + # 则抛出一个ValueError异常,提示用户在参数服务器缓存模式下,sparse必须与enable_sparse相等 raise ValueError(f"For '{self.cls_name}', the 'sparse' must be equal to the 'enable_sparse' " f"in context setting in parameter server cache mode, but got 'sparse': " + f"in parameter server cache mode, but got 'sparse': " f"{self.sparse}, 'enable_sparse': {context.get_context('enable_sparse')}.") def _set_voacb_cache_enable_for_ps(self, vocab_cache_size, embedding_size, vocab_size): + # 用于设置词表缓存和参数服务器缓存的相关参数 """PS embeddingLookup cache enable set.""" + # 将embedding_table.cache_enable设置为True,表示启用缓存 self.embedding_table.cache_enable = True + # 将embedding_table.is_param_ps设置为True,表示这是一个参数服务器上的表 self.embedding_table.is_param_ps = True + # 接着,调用_set_cache_enable(True)函数,用于启用参数服务器缓存 _set_cache_enable(True) + # 检查self.sparse是否为True if self.sparse: + # 如果是True,则将self.forward_unique设置为True + # 这意味着在计算时,将使用去重后的结果,以减少计算量 self.forward_unique = True + # 最后,检查当前角色是否为工作角色 if _is_role_worker(): + # 如果是工作角色,则调用_insert_hash_table_size()函数,用于插入 _insert_hash_table_size(self.embedding_table.name, vocab_cache_size, embedding_size, vocab_size) def construct(self, indices): + # 根据输入的索引计算嵌入向量 + # 首先检查self.target是否为"CPU" if self.target == "CPU": + # 如果是,则调用self.embeddinglookup函数计算嵌入向量 out = self.embeddinglookup(self.embedding_table, indices, 0) + # 如果self.target不为"CPU" else: + # 则检查是否使用了THOR模式 if self.thor: + # 如果使用了THOR模式,并且self.forward_unique为True if self.forward_unique: + # 则进行去重操作 + # 首先,根据输入的索引indices的形状和嵌入维度self.embedding_size计算新的形状shp shp = self.shape(indices) + (self.embedding_size,) + # 然后,对输入的索引进行平铺操作,得到indices_flatten indices_flatten = self.reshape_first(indices, (-1,)) + # 使用self.unique函数对输入的索引indices_flatten进行去重,得到unique_id和unique_idx。unique函数会返回一个布尔值数组,表示输入数组中的唯一值 unique_id, unique_idx = self.unique(indices_flatten) + # 接下来,使用self.one_hot函数计算独热编码,得到one_hot_ids one_hot_ids = self.one_hot(indices_flatten, self.vocab_size, self.on_value, self.off_value) + # 接着,使用self.reduce_sum函数计算矩阵A,并将结果转换为float16类型 matrix_a = self.reduce_sum(one_hot_ids, 0) matrix_a = self.cast(matrix_a, mstype.float16) + # 最后,将矩阵A赋值给self.matrix_a self.matrix_a = matrix_a + # 使用self.gatherv2函数根据索引unique_id获取weight_unique weight_unique = self.gatherv2(self.embedding_table, unique_id, 0) + # 然后使用self.getG函数计算out out = self.getG(weight_unique) + # 使用self.gather_revert函数根据索引unique_idx将weight_unique反转 + # gather_revert函数会根据输入的索引和输入的形状返回一个与输入形状相同的张量,其中索引对应的元素被提取并重新排列 weight_flatten = self.gather_revert(weight_unique, unique_idx, 0) + # 最后使用self.reshape函数根据新的形状shp返回计算得到的嵌入向量 out = self.reshape(weight_flatten, shp) else: + # 否则直接计算嵌入向量 + # 在计算嵌入向量时,首先对输入的索引进行平铺操作 indices_flatten = self.reshape_first(indices, (-1,)) + # 然后使用self.one_hot函数计算独热编码 one_hot_ids = self.one_hot(indices_flatten, self.vocab_size, self.on_value, self.off_value) + # 接着使用self.reduce_sum函数计算矩阵A matrix_a = self.reduce_sum(one_hot_ids, 0) matrix_a = self.cast(matrix_a, mstype.float16) self.matrix_a = matrix_a + # 根据索引获取嵌入向量 out = self.gatherv2(self.embedding_table, indices, 0) out = self.getG(out) else: + # 首先,检查self.forward_unique是否为True if self.forward_unique: + # 如果是,则计算新的形状shp shp = self.shape(indices) + (self.embedding_size,) + # 并根据输入的索引indices进行平铺操作 indices_flatten = self.reshape_first(indices, (-1,)) + # 接下来,使用self.unique函数对indices_flatten进行去重,得到unique_id和unique_idx unique_id, unique_idx = self.unique(indices_flatten) + # 然后,使用self.gatherv2函数根据索引unique_id获取weight_unique weight_unique = self.gatherv2(self.embedding_table, unique_id, 0) + # 根据索引unique_idx将weight_unique反转,得到weight_flatten weight_flatten = self.gather_revert(weight_unique, unique_idx, 0) + # 最后,使用self.reshape函数根据新的形状shp返回计算得到的嵌入向量 out = self.reshape(weight_flatten, shp) else: + # 如果不是,则继续执行后续操作。然后,计算不进行去重的嵌入向量, + # 即直接使用self.gatherv2函数根据输入的索引indices获取嵌入向量,并将结果赋值给out out = self.gatherv2(self.embedding_table, indices, 0) + # 在计算完成后,如果设置了self.max_norm if self.max_norm is not None: + # 计算输入indices和out张量的轴范围。_make_axis_range函数会根据输入张量的轴数量和目标张量的轴数量返回一个包含轴范围的元组 axis = _make_axis_range(F.rank(indices), F.rank(out)) + # 则使用ClipByNorm函数对嵌入向量进行裁剪 clip_by_norm = ClipByNorm(axis) out = clip_by_norm(out, self.max_norm) + # 最后返回计算得到的嵌入向量 return out diff --git a/mindspore/python/mindspore/nn/layer/timedistributed.py b/mindspore/python/mindspore/nn/layer/timedistributed.py index a80c3088549..0080c280130 100644 --- a/mindspore/python/mindspore/nn/layer/timedistributed.py +++ b/mindspore/python/mindspore/nn/layer/timedistributed.py @@ -13,9 +13,11 @@ # limitations under the License. # ============================================================================ """Time Distributed.""" - +# 从mindspore.ops.primitive中导入了一些常量、原始操作和辅助函数 from mindspore.ops.primitive import constexpr, Primitive +# 从mindspore.ops中导入了一些操作,如Reshape、Transpose、Stack和Unstack from mindspore.ops import Reshape, Transpose, Stack, Unstack +# 从mindspore.common中导入了一个Tensor类 from mindspore.common import Tensor from mindspore._checkparam import Validator from ..cell import Cell @@ -24,9 +26,12 @@ __all__ = ['TimeDistributed'] @constexpr +# 用于检查reshape_pos是否是一个有效的reshape位置 def _check_reshape_pos(reshape_pos, inputs_shape, outputs_shape, prim_name=None): + # 定义了一个变量msg_prefix,用于在生成错误消息时使用 msg_prefix = f"For '{prim_name}', the" if prim_name else "The" if reshape_pos >= len(outputs_shape) or inputs_shape[reshape_pos] != outputs_shape[reshape_pos]: + # 抛出异常 raise ValueError(f"{msg_prefix} 'reshape_with_axis' is invalid in the input and output. " f"The 'reshape_pos' should be less than the length of 'outputs_shape', and the " f"'inputs_shape[reshape_pos]' should be equal to 'outputs_shape[reshape_pos]', but got " @@ -36,33 +41,48 @@ def _check_reshape_pos(reshape_pos, inputs_shape, outputs_shape, prim_name=None) @constexpr def _check_expand_dims_axis(time_axis, ndim, prim_name=None): + # 定义了一个变量msg_prefix,用于在生成错误消息时使用 msg_prefix = f"For '{prim_name}', the" if prim_name else "The" + # 如果time_axis大于ndim if time_axis > ndim: + # 抛出异常 raise ValueError(f"{msg_prefix} value of 'time_axis' should be in range of [{-ndim - 1}, {ndim}], " f"but got {time_axis}.") @constexpr +# 用于生成一个元组,表示对某个张量的轴进行重新排序 def _generate_perm(axis_a, axis_b, length): + # 创建了一个包含从0到length - 1的整数序列的元组 perm = tuple(range(length)) + # 根据axis_a和axis_b的值是否小于等于交换它们的位置 axis_a, axis_b = (axis_a, axis_b) if axis_a < axis_b else (axis_b, axis_a) + # 返回并计算 return perm[:axis_a] + (perm[axis_b],) + perm[axis_a: axis_b] + perm[axis_b + 1:] @constexpr +# 用于检查输入数据是否符合预期的格式 def _check_data(flag, prim_name=None): + # 定义了一个变量msg_prefix,用于在生成错误消息时使用 msg_prefix = f"For '{prim_name}', the" if prim_name else "The" + # 如果是not if not flag: + # 返回异常 raise TypeError(f"{msg_prefix} inputs and outputs should be a Tensor.") @constexpr +# 检查输入张量的形状是否符合预期的格式 def _check_inputs_dim(shape, prim_name=None): + # 定义了一个变量msg_prefix,用于在生成错误消息时使用 msg_prefix = f"For '{prim_name}', the" if prim_name else "The" + # 如果长度小于3 if len(shape) < 3: + # 抛出异常 raise ValueError(f"{msg_prefix} inputs shape should be at least 3D, but got {len(shape)}.") - +# 用于实现时间分布模型的神经网络层 class TimeDistributed(Cell): r""" The time distributed layer. @@ -103,41 +123,70 @@ class TimeDistributed(Cell): def __init__(self, layer, time_axis, reshape_with_axis=None): """Initialize TimeDistributed.""" + # 如果layer不是一个Primitive对象 if not isinstance(layer, (Cell, Primitive)): raise TypeError(f"For '{self.cls_name}', the 'layer' should be Cell or Primitive instance, " f"but got type: {type(layer).__name__}.") super(TimeDistributed, self).__init__() + # 检查time_axis类型是否为int Validator.check_is_int(time_axis, "time_axis", self.cls_name) + # 如果reshape_with_axis为None if reshape_with_axis is not None: + # 检查reshape_with_axis的类型是否为int型 Validator.check_is_int(reshape_with_axis, "reshape_with_axis", self.cls_name) + # 将layer的值赋值给self.layer self.layer = layer + # 将time_axis的值赋值给self.time_axis self.time_axis = time_axis + # reshape_with_axis的值赋值给self.reshape_with_axis self.reshape_with_axis = reshape_with_axis + # 创建Transpose对象 self.transpose = Transpose() + # 创建Reshape self.reshape = Reshape() - + # 用于初始化层的状态 def construct(self, inputs): + # 检查输入数据是否是一个Tensor对象 _check_data(isinstance(inputs, Tensor), self.cls_name) + # 检查输入张量的形状是否符合时间分布模型的要求 _check_inputs_dim(inputs.shape, self.cls_name) + # 计算时间轴线位置 time_axis = self.time_axis % len(inputs.shape) + # 如果reshape_with_axis是not None if self.reshape_with_axis is not None: + # 计算余数 reshape_with_axis = self.reshape_with_axis % len(inputs.shape) + # 将inputs.shape张量的值赋给inputs_shape变量 inputs_shape = inputs.shape + # 检查reshape_with_axis是否等于inputs_shape张量的长度减1 + # 如果是,那么就将time_axis_new设置为len(inputs_shape) - 2 time_axis_new = len(inputs_shape) - 2 if reshape_with_axis == len(inputs_shape) - 1 \ else (reshape_with_axis + 1 if time_axis > reshape_with_axis else reshape_with_axis - 1) + # 计算reshape_pos的值,它等于time_axis_new如果time_axis_new小于reshape_with_axis reshape_pos = time_axis_new if time_axis_new < reshape_with_axis else reshape_with_axis + # 生曾一个排列perm perm = _generate_perm(time_axis_new, time_axis, len(inputs_shape)) + # 将inputs张量转换 inputs = self.transpose(inputs, perm) + # 变为新shape并且赋值 inputs_shape_new = inputs.shape + # 使用inputs_shape_new[: reshape_pos] + (-1,) + inputs_shape_new[reshape_pos + 2:]作为参数调用self.reshape函数,将inputs张量重塑为新的形状 inputs = self.reshape(inputs, inputs_shape_new[: reshape_pos] + (-1,) + inputs_shape_new[reshape_pos + 2:]) + # 得到outputs outputs = self.layer(inputs) + # 检查outputs的类型是否为tensor _check_data(isinstance(outputs, Tensor), self.cls_name) + # 检查reshape_pos的值是否在允许的范围内 _check_reshape_pos(reshape_pos, inputs.shape, outputs.shape, self.cls_name) + # 计算outputs张量的新的形状 outputs_shape_new = outputs.shape[:reshape_pos] + inputs_shape_new[reshape_pos: reshape_pos + 2] + # 如果reshape_pos+1小于outputs的长度 if reshape_pos + 1 < len(outputs.shape): + # 将outputs张量中从reshape_pos + 1开始的所有轴的形状添加到outputs_shape_new中 outputs_shape_new += outputs.shape[reshape_pos + 1:] outputs_shape_new = (-1,) + outputs_shape_new[1:] + # 返回新的outputs和outputs_shape_new return self.reshape(outputs, outputs_shape_new) unstack = Unstack(time_axis) @@ -145,8 +194,11 @@ class TimeDistributed(Cell): y = () for item in inputs: outputs = self.layer(item) + # 检查outputs的类型是否为tensor _check_data(isinstance(outputs, Tensor), self.cls_name) + # 检查time_axis是否在允许的范围内 _check_expand_dims_axis(time_axis, outputs.ndim, self.cls_name) y += (outputs,) y = Stack(time_axis)(y) + # 返回y return y diff --git a/mindspore/python/mindspore/nn/learning_rate_schedule.py b/mindspore/python/mindspore/nn/learning_rate_schedule.py index 121a950a6c9..5bfc7ac831a 100644 --- a/mindspore/python/mindspore/nn/learning_rate_schedule.py +++ b/mindspore/python/mindspore/nn/learning_rate_schedule.py @@ -13,17 +13,22 @@ # limitations under the License. # ============================================================================ """Learning rate schedule.""" - +# 本文件为动态学习率的定义 +# 导入数学模块 import math - +# 导入数据类型模块 from ..common import dtype as mstype +# 导入ops算子 from ..ops import operations as P +# 导入神经网络基本单元Cell from .cell import Cell +# 导入检查模块 from .._checkparam import Validator as validator class LearningRateSchedule(Cell): """Basic class of learning rate schedule.""" + # LearningRateSchedule的基本类,所有动态学习率均继承自此类 def __init__(self): super(LearningRateSchedule, self).__init__() @@ -37,15 +42,21 @@ class LearningRateSchedule(Cell): The output must be a Tensor of scalar. Inputs: - - **global_step** (Tensor) - The current step number. - - Inputs: - Tensor. Learning rate at current step with shape :math:`()`. + Tensor. The current step number. """ raise NotImplementedError def _check_inputs(learning_rate, decay_rate, decay_steps, is_stair, cls_name): + ''' + 检查输入参数是否合法 + :param learning_rate: 学习率 + :param decay_rate: 平滑系数 + :param decay_steps: 时间步长 + :param is_stair: 是否平滑 + :param cls_name: 名称 + :return: + ''' validator.check_positive_int(decay_steps, 'decay_steps', cls_name) validator.check_positive_float(learning_rate, 'learning_rate', cls_name) validator.check_is_float(learning_rate, 'learning_rate', cls_name) @@ -55,13 +66,14 @@ def _check_inputs(learning_rate, decay_rate, decay_steps, is_stair, cls_name): class ExponentialDecayLR(LearningRateSchedule): + # 基于指数衰减函数计算学习率。 r""" - Calculates learning rate based on exponential decay function. + Calculates learning rate base on exponential decay function. - For current step, the formula of computing decayed learning rate is: + For the i-th step, the formula of computing decayed_learning_rate[i] is: .. math:: - decayed\_learning\_rate = learning\_rate * decay\_rate^{p} + decayed\_learning\_rate[i] = learning\_rate * decay\_rate^{p} Where : @@ -76,14 +88,14 @@ class ExponentialDecayLR(LearningRateSchedule): Args: learning_rate (float): The initial value of learning rate. decay_rate (float): The decay rate. - decay_steps (int): Number of steps to decay over. + decay_steps (int): A value used to calculate decayed learning rate. is_stair (bool): If true, learning rate is decayed once every `decay_steps` time. Default: False. Inputs: - - **global_step** (Tensor) - The current step number. + Tensor. The current step number. Outputs: - Tensor. The learning rate value for the current step with shape :math:`()`. + Tensor. The learning rate value for the current step. Raises: TypeError: If `learning_rate` or `decay_rate` is not a float. @@ -95,19 +107,23 @@ class ExponentialDecayLR(LearningRateSchedule): ``Ascend`` ``GPU`` ``CPU`` Examples: - >>> import mindspore - >>> from mindspore import Tensor, nn - >>> >>> learning_rate = 0.1 >>> decay_rate = 0.9 >>> decay_steps = 4 - >>> global_step = Tensor(2, mindspore.int32) + >>> global_step = Tensor(2, mstype.int32) >>> exponential_decay_lr = nn.ExponentialDecayLR(learning_rate, decay_rate, decay_steps) >>> result = exponential_decay_lr(global_step) >>> print(result) 0.09486833 """ def __init__(self, learning_rate, decay_rate, decay_steps, is_stair=False): + ''' + 初始化ExponentialDecayLR类,并使用_check_inputs方法检查参数 + :param learning_rate: 学习率 + :param decay_rate: 平滑系数 + :param decay_steps: 时间步长 + :param is_stair: 是否平滑 + ''' super(ExponentialDecayLR, self).__init__() _check_inputs(learning_rate, decay_rate, decay_steps, is_stair, self.cls_name) self.learning_rate = learning_rate @@ -118,20 +134,28 @@ class ExponentialDecayLR(LearningRateSchedule): self.cast = P.Cast() def construct(self, global_step): + ''' + 构建ExponentialDecayLR类 + :param global_step: 步骤 + :return: + ''' p = self.cast(global_step, mstype.float32) / self.decay_steps + # 如果是在梯度下降时,则使用Floor函数 if self.is_stair: p = P.Floor()(p) + # 返回指数衰减学习率 return self.learning_rate * self.pow(self.decay_rate, p) class NaturalExpDecayLR(LearningRateSchedule): + # 基于自然指数衰减函数计算学习率。 r""" Calculates learning rate base on natural exponential decay function. - For current step, the formula of computing decayed learning rate is: + For the i-th step, the formula of computing decayed_learning_rate[i] is: .. math:: - decayed\_learning\_rate= learning\_rate * e^{-decay\_rate * p} + decayed\_learning\_rate[i] = learning\_rate * e^{-decay\_rate * p} Where : @@ -146,14 +170,14 @@ class NaturalExpDecayLR(LearningRateSchedule): Args: learning_rate (float): The initial value of learning rate. decay_rate (float): The decay rate. - decay_steps (int): Number of steps to decay over. + decay_steps (int): A value used to calculate decayed learning rate. is_stair (bool): If true, learning rate is decayed once every `decay_steps` time. Default: False. Inputs: - - **global_step** (Tensor) - The current step number. + Tensor. The current step number. Outputs: - Tensor. The learning rate value for the current step with shape :math:`()`. + Tensor. The learning rate value for the current step. Raises: TypeError: If `learning_rate` or `decay_rate` is not a float. @@ -165,19 +189,23 @@ class NaturalExpDecayLR(LearningRateSchedule): ``Ascend`` ``GPU`` ``CPU`` Examples: - >>> import mindspore - >>> from mindspore import Tensor, nn - >>> >>> learning_rate = 0.1 >>> decay_rate = 0.9 >>> decay_steps = 4 - >>> global_step = Tensor(2, mindspore.int32) + >>> global_step = Tensor(2, mstype.int32) >>> natural_exp_decay_lr = nn.NaturalExpDecayLR(learning_rate, decay_rate, decay_steps, True) >>> result = natural_exp_decay_lr(global_step) >>> print(result) 0.1 """ def __init__(self, learning_rate, decay_rate, decay_steps, is_stair=False): + ''' + 初始化一个NaturalExpDecayLR类,并使用_check_inputs方法检查参数 + :param learning_rate: 学习率 + :param decay_rate: 分段衰减率 + :param decay_steps: 时间步长 + :param is_stair: 是否变为折线图 + ''' super(NaturalExpDecayLR, self).__init__() _check_inputs(learning_rate, decay_rate, decay_steps, is_stair, self.cls_name) self.learning_rate = learning_rate @@ -189,20 +217,28 @@ class NaturalExpDecayLR(LearningRateSchedule): self.cast = P.Cast() def construct(self, global_step): + ''' + 构建NaturalExpDecayLR类 + :param global_step: 步骤 + :return: + ''' p = self.cast(global_step, mstype.float32) + # 如果是在梯度下降模式下,则将p乘以decay_steps,并将p除以decay_steps if self.is_stair: p = P.FloorDiv()(p, self.decay_steps) * self.decay_steps + # 返回learning_rate乘以pow(math_e, -decay_rate * p) return self.learning_rate * self.pow(self.math_e, -self.decay_rate * p) class InverseDecayLR(LearningRateSchedule): + # 基于逆时衰减函数计算学习率。 r""" Calculates learning rate base on inverse-time decay function. - For current step, the formula of computing decayed learning rate is: + For the i-th step, the formula of computing decayed_learning_rate[i] is: .. math:: - decayed\_learning\_rate = learning\_rate / (1 + decay\_rate * p) + decayed\_learning\_rate[i] = learning\_rate / (1 + decay\_rate * p) Where : @@ -217,14 +253,14 @@ class InverseDecayLR(LearningRateSchedule): Args: learning_rate (float): The initial value of learning rate. decay_rate (float): The decay rate. - decay_steps (int): Number of steps to decay over. + decay_steps (int): A value used to calculate decayed learning rate. is_stair (bool): If true, learning rate decay once every `decay_steps` times. Default: False. Inputs: - - **global_step** (Tensor) - The current step number. + Tensor. The current step number. Outputs: - Tensor. The learning rate value for the current step with shape :math:`()`. + Tensor. The learning rate value for the current step. Raises: TypeError: If `learning_rate` or `decay_rate` is not a float. @@ -236,19 +272,23 @@ class InverseDecayLR(LearningRateSchedule): ``Ascend`` ``GPU`` ``CPU`` Examples: - >>> import mindspore - >>> from mindspore import Tensor, nn - >>> >>> learning_rate = 0.1 >>> decay_rate = 0.9 >>> decay_steps = 4 - >>> global_step = Tensor(2, mindspore.int32) + >>> global_step = Tensor(2, mstype.int32) >>> inverse_decay_lr = nn.InverseDecayLR(learning_rate, decay_rate, decay_steps, True) >>> result = inverse_decay_lr(global_step) >>> print(result) 0.1 """ def __init__(self, learning_rate, decay_rate, decay_steps, is_stair=False): + ''' + 初始化InverseDecayLR,并使用_check_inputs方法检查参数 + :param learning_rate: 学习率 + :param decay_rate: 平滑系数 + :param decay_steps: 时间步长 + :param is_stair: 是否平滑 + ''' super(InverseDecayLR, self).__init__() _check_inputs(learning_rate, decay_rate, decay_steps, is_stair, self.cls_name) self.learning_rate = learning_rate @@ -258,33 +298,41 @@ class InverseDecayLR(LearningRateSchedule): self.cast = P.Cast() def construct(self, global_step): + ''' + 计算每次迭代的学习率 + :param global_step: 所有epoch的迭代次数 + :return: 每次迭代的学习率 + ''' p = self.cast(global_step, mstype.float32) / self.decay_steps + # 如果是在梯度下降过程中,则将p转换为浮点数 if self.is_stair: p = P.Floor()(p) + # 返回学习率的除法结果,使用指数衰减 return self.learning_rate / (1 + self.decay_rate * p) class CosineDecayLR(LearningRateSchedule): + # 基于余弦衰减函数计算学习率。 r""" - Calculates learning rate based on cosine decay function. + Calculates learning rate base on cosine decay function. - For current step, the formula of computing decayed learning rate is: + For the i-th step, the formula of computing decayed_learning_rate[i] is: .. math:: - decayed\_learning\_rate = min\_lr + 0.5 * (max\_lr - min\_lr) * + decayed\_learning\_rate[i] = min\_learning\_rate + 0.5 * (max\_learning\_rate - min\_learning\_rate) * (1 + cos(\frac{current\_step}{decay\_steps}\pi)) Args: min_lr (float): The minimum value of learning rate. max_lr (float): The maximum value of learning rate. - decay_steps (int): Number of steps to decay over. + decay_steps (int): A value used to calculate decayed learning rate. Inputs: - - **global_step** (Tensor) - The current step number. + Tensor. The current step number. Outputs: - Tensor. The learning rate value for the current step with shape :math:`()`. + Tensor. The learning rate value for the current step. Raises: TypeError: If `min_lr` or `max_lr` is not a float. @@ -296,52 +344,67 @@ class CosineDecayLR(LearningRateSchedule): ``Ascend`` ``GPU`` Examples: - >>> import mindspore - >>> from mindspore import Tensor, nn - >>> >>> min_lr = 0.01 >>> max_lr = 0.1 >>> decay_steps = 4 - >>> global_steps = Tensor(2, mindspore.int32) + >>> global_steps = Tensor(2, mstype.int32) >>> cosine_decay_lr = nn.CosineDecayLR(min_lr, max_lr, decay_steps) >>> result = cosine_decay_lr(global_steps) >>> print(result) 0.055 """ def __init__(self, min_lr, max_lr, decay_steps): + ''' + 初始化CosineDecayLR类,并检查参数 + :param min_lr: 最小学习率 + :param max_lr: 最大学习率 + :param decay_steps: 时间步数 + ''' super(CosineDecayLR, self).__init__() if not isinstance(min_lr, float): - raise TypeError("For 'CosineDecayLR', the argument 'min_lr' must be type of float, " - "but got 'min_lr' type: {}.".format(type(min_lr))) + raise TypeError("min_lr must be float.") validator.check_non_negative_float(min_lr, "min_lr", self.cls_name) - validator.check_positive_float(max_lr, 'max_lr', self.cls_name) - validator.check_is_float(max_lr, 'max_lr', self.cls_name) + validator.check_positive_float(max_lr,'max_lr', self.cls_name) + validator.check_is_float(max_lr,'max_lr', self.cls_name) validator.check_positive_int(decay_steps, "decay_steps", self.cls_name) if min_lr >= max_lr: - raise ValueError("For 'CosineDecayLR', the 'max_lr' should be greater than the 'min_lr', " - "but got 'max_lr' value: {}, 'min_lr' value: {}.".format(max_lr, min_lr)) + raise ValueError('`max_lr` should be greater than `min_lr`.') + # 将min_lr和max_lr赋值给变量min_lr和max_lr self.min_lr = min_lr self.max_lr = max_lr + # 将decay_steps赋值给变量decay_steps self.decay_steps = decay_steps + # 将math.pi赋值给变量math_pi self.math_pi = math.pi + # 将delta赋值给变量delta self.delta = 0.5 * (max_lr - min_lr) + # 创建一个Cos函数 self.cos = P.Cos() + # 创建一个Minimum函数 self.min = P.Minimum() + # 创建一个Cast函数 self.cast = P.Cast() def construct(self, global_step): + ''' + 构建CosineDecayLR + :param global_step: 总共的时间步数 + :return: + ''' p = self.cast(self.min(global_step, self.decay_steps), mstype.float32) + # 计算p的值,并将其转换为float32类型 return self.min_lr + self.delta * (1.0 + self.cos(self.math_pi * p / self.decay_steps)) class PolynomialDecayLR(LearningRateSchedule): + # 基于多项式衰减函数计算学习率。 r""" Calculates learning rate base on polynomial decay function. - For current step, the formula of computing decayed learning rate is: + For the i-th step, the formula of computing decayed_learning_rate[i] is: .. math:: - decayed\_learning\_rate = (learning\_rate - end\_learning\_rate) * + decayed\_learning\_rate[i] = (learning\_rate - end\_learning\_rate) * (1 - tmp\_step / tmp\_decay\_steps)^{power} + end\_learning\_rate Where : @@ -357,15 +420,15 @@ class PolynomialDecayLR(LearningRateSchedule): Args: learning_rate (float): The initial value of learning rate. end_learning_rate (float): The end value of learning rate. - decay_steps (int): Number of steps to decay over. - power (float): The power of polynomial. It must be greater than 0. + decay_steps (int): A value used to calculate decayed learning rate. + power (float): A value used to calculate decayed learning rate. This parameter must be greater than 0. update_decay_steps (bool): If true, learning rate is decayed once every `decay_steps` time. Default: False. Inputs: - - **global_step** (Tensor) - The current step number. + Tensor. The current step number. Outputs: - Tensor. The learning rate value for the current step with shape :math:`()`. + Tensor. The learning rate value for the current step. Raises: TypeError: If `learning_rate`, `end_learning_rate` or `power` is not a float. @@ -377,28 +440,30 @@ class PolynomialDecayLR(LearningRateSchedule): ``Ascend`` ``GPU`` Examples: - >>> import mindspore - >>> from mindspore import Tensor, nn - >>> >>> learning_rate = 0.1 >>> end_learning_rate = 0.01 >>> decay_steps = 4 >>> power = 0.5 - >>> global_step = Tensor(2, mindspore.int32) + >>> global_step = Tensor(2, mstype.int32) >>> polynomial_decay_lr = nn.PolynomialDecayLR(learning_rate, end_learning_rate, decay_steps, power) >>> result = polynomial_decay_lr(global_step) >>> print(result) 0.07363961 """ def __init__(self, learning_rate, end_learning_rate, decay_steps, power, update_decay_steps=False): + ''' + 初始化PolynomialDecayLR类,并检查参数 + :param learning_rate: 学习率 + :param end_learning_rate: 终止学习率 + :param decay_steps: 时间步数 + :param power: 指数 + :param update_decay_steps: 是否更新指数步长 + ''' super(PolynomialDecayLR, self).__init__() validator.check_positive_float(learning_rate, 'learning_rate') validator.check_is_float(learning_rate, 'learning_rate') if not isinstance(end_learning_rate, float): - raise TypeError("For 'PolynomialDecayLR', the argument 'end_learning_rate' " - "must be type of float, but got 'end_learning_rate' type: {}." - .format(type(end_learning_rate))) - + raise TypeError("end_learning_rate must be float.") validator.check_non_negative_float(end_learning_rate, "end_learning_rate", self.cls_name) validator.check_positive_int(decay_steps, 'decay_steps', self.cls_name) validator.check_value_type('update_decay_steps', update_decay_steps, [bool], self.cls_name) @@ -417,29 +482,41 @@ class PolynomialDecayLR(LearningRateSchedule): self.max = P.Maximum() def construct(self, global_step): + ''' + 构建PolynomialDecayLR类 + :param global_step: 步数 + :return: 学习率 + ''' tmp_global_step = P.Cast()(global_step, mstype.float32) + # 将训练步数转换为浮点数 tmp_decay_step = self.decay_steps + # 如果update_decay_steps为True,则tmp_decay_step乘以max(ceil(tmp_global_step / tmp_decay_step), 1) if self.update_decay_steps: tmp_decay_step = tmp_decay_step * self.max(self.ceil(tmp_global_step / tmp_decay_step), 1) + # 否则,tmp_global_step小于tmp_decay_step,则tmp_global_step等于tmp_decay_step else: tmp_global_step = self.min(tmp_global_step, tmp_decay_step) + # tmp_global_step / tmp_decay_step p = tmp_global_step / tmp_decay_step + # 将p的值转换为float32类型,并乘以diff_learning_rate,加上end_learning_rate lr = self.diff_learning_rate * self.pow(1.0 - p, self.power) + self.end_learning_rate + # 返回lr return lr class WarmUpLR(LearningRateSchedule): + # 预热学习率。 r""" Gets learning rate warming up. - For current step, the formula of computing warmup learning rate is: + For the i-th step, the formula of computing warmup_learning_rate[i] is: .. math:: - warmup\_learning\_rate = learning\_rate * tmp\_step / warmup\_steps + warmup\_learning\_rate[i] = learning\_rate * tmp\_step / warmup\_steps Where : - .. math:: + .. math: tmp\_step=min(current\_step, warmup\_steps) Args: @@ -447,10 +524,10 @@ class WarmUpLR(LearningRateSchedule): warmup_steps (int): The warm up steps of learning rate. Inputs: - - **global_step** (Tensor) - The current step number. + Tensor. The current step number. Outputs: - Tensor. The learning rate value for the current step with shape :math:`()`. + Tensor. The learning rate value for the current step. Raises: TypeError: If `learning_rate` is not a float. @@ -462,22 +539,23 @@ class WarmUpLR(LearningRateSchedule): ``Ascend`` ``GPU`` Examples: - >>> import mindspore - >>> from mindspore import Tensor, nn - >>> >>> learning_rate = 0.1 >>> warmup_steps = 2 - >>> global_step = Tensor(2, mindspore.int32) + >>> global_step = Tensor(2, mstype.int32) >>> warmup_lr = nn.WarmUpLR(learning_rate, warmup_steps) >>> result = warmup_lr(global_step) >>> print(result) 0.1 """ def __init__(self, learning_rate, warmup_steps): + ''' + 初始化WarmUpLR类,并检查输入参数 + :param learning_rate: 学习率 + :param warmup_steps: 过去warmup_steps步后的学习率 + ''' super(WarmUpLR, self).__init__() if not isinstance(learning_rate, float): - raise TypeError("For 'WarmUpLR', the argument 'learning_rate' must be type of float, " - "but got 'learning_rate' type: {}.".format(type(learning_rate))) + raise TypeError("learning_rate must be float.") validator.check_non_negative_float(learning_rate, "learning_rate", self.cls_name) validator.check_positive_int(warmup_steps, 'warmup_steps', self.cls_name) self.warmup_steps = warmup_steps @@ -486,10 +564,15 @@ class WarmUpLR(LearningRateSchedule): self.cast = P.Cast() def construct(self, global_step): - warmup_percent = self.cast(self.min(global_step, self.warmup_steps), mstype.float32) / self.warmup_steps + ''' + 计算学习率 + :param global_step: 步数 + :return: 学习率 + ''' + warmup_percent = self.cast(self.min(global_step, self.warmup_steps), mstype.float32)/ self.warmup_steps + # 返回预热学习率,乘以warmup_percent return self.learning_rate * warmup_percent - __all__ = [ 'ExponentialDecayLR', 'NaturalExpDecayLR', diff --git a/mindspore/python/mindspore/nn/loss/__init__.py b/mindspore/python/mindspore/nn/loss/__init__.py index 1bd4bc7714d..087d38ef760 100644 --- a/mindspore/python/mindspore/nn/loss/__init__.py +++ b/mindspore/python/mindspore/nn/loss/__init__.py @@ -18,7 +18,7 @@ Loss. Cells of loss function. Loss function in machine learning is the target of the model. It shows how well the model works on a dataset and the optimization target which the optimizer is searching. """ - +#本函数为loss函数的初始化函数 from .loss import LossBase, L1Loss, MSELoss, SmoothL1Loss, SoftMarginLoss, FocalLoss,\ SoftmaxCrossEntropyWithLogits, BCELoss, CosineEmbeddingLoss, \ SampledSoftmaxLoss, DiceLoss, BCEWithLogitsLoss, MultiClassDiceLoss,\ diff --git a/mindspore/python/mindspore/nn/loss/loss.py b/mindspore/python/mindspore/nn/loss/loss.py index 714f947b790..f1537e83dfe 100644 --- a/mindspore/python/mindspore/nn/loss/loss.py +++ b/mindspore/python/mindspore/nn/loss/loss.py @@ -1,4 +1,4 @@ -# Copyright 2020-2021 Huawei Technologies Co., Ltd +# Copyright 2020-2022 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,22 +13,48 @@ # limitations under the License. # ============================================================================ """loss""" +from __future__ import absolute_import, division +# 导入Python的math模块,其中包含了数学函数,如sin、cos、log等 +import math +# 导入MindSpore的主模块,包含了MindSpore的核心组件,如Tensor类、操作等 import mindspore +# 导入MindSpore.common.dtype模块,其中包含了MindSpore的数据类型定义 import mindspore.common.dtype as mstype +# 导入MindSpore的ops模块,其中包含了MindSpore的基本操作 +import mindspore.ops as ops +# 从MindSpore中导入log函数,用于记录日志信息 from mindspore import log +# 从MindSpore.common.tensor模块中导入Tensor类,用于表示MindSpore的张量 from mindspore.common.tensor import Tensor +# 从MindSpore.common.parameter模块中导入Parameter类,用于表示MindSpore的参数 from mindspore.common.parameter import Parameter +# 从MindSpore.ops模块中导入operations类,其中包含了基本的操作 from mindspore.ops import operations as P +# 从MindSpore.ops.operations模块中导入_inner_ops类,其中包含了内部操作 +from mindspore.ops.operations import _inner_ops as inner +# 从MindSpore.ops.operations.nn_ops模块中导入MultiMarginLoss类,其中包含了多分类的边缘损失函数 +from mindspore.ops.operations.nn_ops import MultiMarginLoss as MultiMarginLossOp +# 从MindSpore.ops.operations.nn_ops模块中导入MultilabelMarginLoss类,其中包含了多标签的边缘损失函数 +from mindspore.ops.operations.nn_ops import MultilabelMarginLoss as MultilabelMarginLossOp +# 从MindSpore.ops模块中导入functional类,其中包含了MindSpore的功能操作 from mindspore.ops import functional as F +# 从MindSpore中导入nn模块,其中包含了MindSpore的神经网络层 from mindspore import nn -from mindspore.ops.primitive import constexpr +# 从MindSpore.ops.primitive模块中导入constexpr和_primexpr函数 +# 其中constexpr函数用于定义一个编译时检查的表达式,而_primexpr函数用于计算一个表达式的质因数 +from mindspore.ops.primitive import constexpr, _primexpr +# 从MindSpore.nn.cell模块中导入Cell类,其中Cell类表示MindSpore的神经网络单元 from mindspore.nn.cell import Cell +# 从MindSpore.nn.layer.activation模块中导入get_activation函数,其中get_activation函数用于获取一个激活函数 from mindspore.nn.layer.activation import get_activation -from mindspore._checkparam import Validator as validator -from mindspore._checkparam import Rel -from ... import context - +# 从MindSpore.nn.layer.activation模块中导入get_activation函数,用于获取激活函数 +from mindspore import _checkparam as validator +# 从MindSpore中导入context函数,其中包含了用于设置运行上下文的函数 +from mindspore import context +# 在MindSpore中,LossBase类是一个基类,用于定义各种损失函数 +# 在MindSpore中,所有的损失函数都可以继承LossBase类,并实现自己的损失函数计算逻辑 +# LossBase类定义了损失函数的通用属性和计算逻辑,方便其他损失函数类继承并实现特定的损失函数计算逻辑 class LossBase(Cell): """ Base class for other losses. @@ -37,72 +63,101 @@ class LossBase(Cell): to apply reduction to loss values. Args: - reduction (str): Type of reduction to be applied to loss. The optional values are "mean", "sum", and "none". - Default: "mean". + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the (weighted) mean of elements in the output. + - ``'sum'``: the output elements will be summed. Raises: - ValueError: If `reduction` is not one of 'none', 'mean', 'sum'. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'``, ``'sum'``. Supported Platforms: ``Ascend`` ``GPU`` ``CPU`` + + Examples: + >>> import mindspore + >>> from mindspore import ops, Tensor, nn + >>> import numpy as np + >>> + >>> class Net(nn.LossBase): + ... def __init__(self, reduction='mean'): + ... super(Net, self).__init__(reduction) + ... self.abs = ops.Abs() + ... + ... def construct(self, logits, labels): + ... x = self.abs(logits - labels) + ... output = self.get_loss(x) + ... axis = self.get_axis(x) + ... return output, axis + >>> net = Net() + >>> # Case 1: logits.shape = labels.shape = (3,) + >>> logits = Tensor(np.array([1, 2, 3]), mindspore.float32) + >>> labels = Tensor(np.array([1, 2, 2]), mindspore.float32) + >>> output, axis = net(logits, labels) + >>> print(output) + 0.33333334 + >>> print(axis) + (0,) + >>> # Case 2: logits.shape = labels.shape = (3, 3) + >>> logits = Tensor(np.array([[1, 2, 3],[1, 2, 3],[1, 2, 3]]), mindspore.float32) + >>> labels = Tensor(np.array([[1, 2, 2],[1, 2, 3],[1, 2, 3]]), mindspore.float32) + >>> output, axis = net(logits, labels) + >>> print(output) + 0.11111111 + >>> print(axis) + (0, 1) """ def __init__(self, reduction='mean'): """Initialize Loss.""" super(LossBase, self).__init__() - + # 如果reduction参数不在'mean', 'sum', 'none'这三个之中 if reduction not in ('mean', 'sum', 'none'): - raise ValueError(f"For '{self.cls_name}', the 'reduction' should be in ['mean', 'sum', 'none'], " + # 抛出ValueError异常 + raise ValueError(f"For '{self.cls_name}', the 'reduction' must be in ['mean', 'sum', 'none'], " f"but got {reduction}.") - + # 将average属性设置为True,用于表示计算损失时需要对所有样本的损失求平均 self.average = True + # 将reduce属性设置为True,用于表示计算损失时需要对所有样本的损失求和 self.reduce = True + # 如果reduction为sum if reduction == 'sum': + # 将average属性设置为False,表示计算损失时不计算平均,只对所有样本的损失求和 self.average = False + # 如果reduction为none if reduction == 'none': + # 将reduce属性设置为False,,表示计算损失时需要对所有样本的损失求和 self.reduce = False - + # 定义self.reduce_mean,用于计算张量中所有元素的的平均值 self.reduce_mean = P.ReduceMean() + # 定义self.reduce_sum,用于计算张量中所有元素的和 self.reduce_sum = P.ReduceSum() + # 定义self.mul,用于计算两个张量的元素乘积 self.mul = P.Mul() + # 定义self.cast,用于将一个张量的数据类型转换为指定类型 self.cast = P.Cast() - + # 用于获取输入张量的轴范围 def get_axis(self, x): """ Get a range of axis for input. Args: x (Tensor): Tensor of any shape. - - Examples: - >>> class Net(nn.LossBase): - ... def __init__(self, reduction='mean'): - ... super(Net, self).__init__(reduction) - ... self.abs = ops.Abs() - ... - ... def construct(self, logits, labels): - ... x = self.abs(logits - labels) - ... axis = self.get_axis(x) - ... return axis - >>> net = Net() - >>> # Case 1: logits.shape = labels.shape = (3,) - >>> logits = Tensor(np.array([1, 2, 3]), mindspore.float32) - >>> labels = Tensor(np.array([1, 2, 3]), mindspore.float32) - >>> output = net(logits, labels) - >>> print(output) - (0,) - >>> # Case 2: logits.shape = labels.shape = (3, 3) - >>> logits = Tensor(np.array([[1, 2, 3],[1, 2, 3],[1, 2, 3]]), mindspore.float32) - >>> labels = Tensor(np.array([[1, 2, 3],[1, 2, 3],[1, 2, 3]]), mindspore.float32) - >>> output = net(logits, labels) - >>> print(output) - (0, 1) """ + # 用F.shape(x)函数获取输入张量的形状,并将结果存储到shape变量中 shape = F.shape(x) + # 用F.tuple_len(shape)函数获取shape元组的长度,并将结果存储到length变量中 length = F.tuple_len(shape) + # 用F.make_range(0, length)函数创建一个从0开始到length-1结束的整数张量,并将结果存储到perm变量中 perm = F.make_range(0, length) + # 返回perm,即输入张量的轴范围 return perm + # 参数 + # x为输入张量 + # weights为损失函数的权重 def get_loss(self, x, weights=1.0): """ Computes the weighted loss. @@ -112,50 +167,39 @@ class LossBase(Cell): additional dimensions. weights (Union[float, Tensor]): Optional `Tensor` whose rank is either 0, or the same rank as inputs, and must be broadcastable to inputs (i.e., all dimensions must be either `1`, - or the same as the corresponding inputs dimension). Default: 1.0. + or the same as the corresponding inputs dimension). Default: ``1.0`` . Returns: Return the weighted loss. - - Examples: - >>> class Net(nn.LossBase): - ... def __init__(self, reduction='mean'): - ... super(Net, self).__init__(reduction) - ... self.abs = ops.Abs() - ... - ... def construct(self, logits, labels): - ... x = self.abs(logits - labels) - ... output = self.get_loss(x) - ... return output - >>> net = Net() - >>> # Case 1: logits.shape = labels.shape = (3,) - >>> logits = Tensor(np.array([1, 2, 3]), mindspore.float32) - >>> labels = Tensor(np.array([1, 2, 2]), mindspore.float32) - >>> output = net(logits, labels) - >>> print(output) - 0.33333334 - >>> # Case 2: logits.shape = labels.shape = (3, 3) - >>> logits = Tensor(np.array([[1, 2, 3],[1, 2, 3],[1, 2, 3]]), mindspore.float32) - >>> labels = Tensor(np.array([[1, 2, 2],[1, 2, 3],[1, 2, 3]]), mindspore.float32) - >>> output = net(logits, labels) - >>> print(output) - 0.11111111 """ + # 获取输入张量的数据类型 input_dtype = x.dtype + # 将输入张量x的数据类型转换为mstype.float32 x = self.cast(x, mstype.float32) + # 将损失函数的权重weights的数据类型转换为mstype.float32 weights = self.cast(weights, mstype.float32) + # 将weights与x相乘 x = self.mul(weights, x) + # 如果需要计算平均 if self.reduce and self.average: + # 计算x张量中所有元素的平均值 x = self.reduce_mean(x, self.get_axis(x)) + # 如果不需要计算平均 if self.reduce and not self.average: + # 计算x张量中所有元素的平均值 x = self.reduce_sum(x, self.get_axis(x)) + # 将x张量的数据类型转换回原始数据类型 x = self.cast(x, input_dtype) + # 返回x,即损失函数计算结果 return x + def construct(self, logits, labels): + # 抛出NotImplementedError异常 raise NotImplementedError + class _Loss(LossBase): """ Base class for other losses. @@ -163,22 +207,26 @@ class _Loss(LossBase): def __init__(self, reduction='mean'): """Initialize _Loss.""" + # 警告 log.warning("'_Loss' is deprecated from version 1.3 and " "will be removed in a future version, use 'LossBase' instead.") super(_Loss, self).__init__(reduction) def construct(self, logits, labels): + # 抛出NotImplementedError异常 raise NotImplementedError -@constexpr +@constexpr(check=False) def _check_is_tensor(param_name, input_data, cls_name): """Internal function, used to check whether the input data is Tensor.""" - if input_data is not None and not isinstance(F.typeof(input_data), mstype.tensor_type): - raise TypeError(f"For '{cls_name}', the '{param_name}' should be '{mstype.tensor_type}', " + # 如果输入数据input_data不为空或者数据类型是张量类型 + if input_data is not None and not isinstance(F.typeof(input_data), mstype.TensorType): + # 抛出TypeError异常 + raise TypeError(f"For '{cls_name}', the '{param_name}' must be '{mstype.TensorType}', " f"but got '{F.typeof(input_data)}'") - +# 用于实现L1损失(均方误差)损失函数 class L1Loss(LossBase): r""" L1Loss is used to calculate the mean absolute error between the predicted value and the target value. @@ -189,7 +237,7 @@ class L1Loss(LossBase): .. math:: \ell(x, y) = L = \{l_1,\dots,l_N\}^\top, \quad \text{with } l_n = \left| x_n - y_n \right|, - where :math:`N` is the batch size. If `reduction` is not 'none', then: + where :math:`N` is the batch size. If `reduction` is not ``'none'``, then: .. math:: \ell(x, y) = @@ -199,9 +247,12 @@ class L1Loss(LossBase): \end{cases} Args: - reduction (str): Type of reduction to be applied to loss. The optional values are "mean", "sum", and "none". - Default: "mean". If `reduction` is "mean" or "sum", then output a scalar Tensor, if `reduction` is "none", - the shape of the output Tensor is the broadcasted shape. + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the mean of elements in the output. + - ``'sum'``: the output elements will be summed. Inputs: - **logits** (Tensor) - Predicted value, Tensor of any dimension. @@ -213,13 +264,16 @@ class L1Loss(LossBase): Tensor, data type is float. Raises: - ValueError: If `reduction` is not one of 'none', 'mean', 'sum'. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'``, ``'sum'``. ValueError: If `logits` and `labels` have different shapes and cannot be broadcasted to each other. Supported Platforms: ``Ascend`` ``GPU`` ``CPU`` Examples: + >>> import mindspore + >>> from mindspore import Tensor, nn + >>> import numpy as np >>> # Case 1: logits.shape = labels.shape = (3,) >>> loss = nn.L1Loss() >>> logits = Tensor(np.array([1, 2, 3]), mindspore.float32) @@ -239,16 +293,18 @@ class L1Loss(LossBase): def __init__(self, reduction='mean'): """Initialize L1Loss.""" + # 调用父类LossBase的构造函数,传入相同的reduction参数 super(L1Loss, self).__init__(reduction) - self.abs = P.Abs() + # 将reduction参数赋值给self.reduction + self.reduction = reduction def construct(self, logits, labels): - _check_is_tensor('logits', logits, self.cls_name) - _check_is_tensor('labels', labels, self.cls_name) - x = self.abs(logits - labels) - return self.get_loss(x) + # 调用F.l1_loss函数,传入三个参数:logits(模型预测的分数)、labels(真实标签)和self.reduction(损失函数的计算方式) + # F.l1_loss函数用于计算L1损失值,并返回计算结果 + return F.l1_loss(logits, labels, self.reduction) +# 用于实现均方误差损失函数(L2) class MSELoss(LossBase): r""" Calculates the mean squared error between the predicted value and the label value. @@ -259,7 +315,7 @@ class MSELoss(LossBase): .. math:: \ell(x, y) = L = \{l_1,\dots,l_N\}^\top, \quad \text{with} \quad l_n = (x_n - y_n)^2. - where :math:`N` is the batch size. If `reduction` is not 'none', then: + where :math:`N` is the batch size. If `reduction` is not ``'none'``, then: .. math:: \ell(x, y) = @@ -269,8 +325,12 @@ class MSELoss(LossBase): \end{cases} Args: - reduction (str): Type of reduction to be applied to loss. The optional values are "mean", "sum", and "none". - Default: "mean". + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the mean of elements in the output. + - ``'sum'``: the output elements will be summed. Inputs: - **logits** (Tensor) - The predicted value of the input. Tensor of any dimension. @@ -279,17 +339,20 @@ class MSELoss(LossBase): and they should be broadcasted to each other. Outputs: - Tensor, loss of type float, the shape is zero if `reduction` is 'mean' or 'sum', + Tensor, loss of type float, the shape is zero if `reduction` is ``'mean'`` or ``'sum'`` ., while the shape of output is the broadcasted shape if `reduction` is 'none'. Raises: - ValueError: If `reduction` is not one of 'none', 'mean' or 'sum'. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'`` or ``'sum'``. ValueError: If `logits` and `labels` have different shapes and cannot be broadcasted. Supported Platforms: ``Ascend`` ``GPU`` ``CPU`` Examples: + >>> import mindspore + >>> from mindspore import Tensor, nn + >>> import numpy as np >>> # Case 1: logits.shape = labels.shape = (3,) >>> loss = nn.MSELoss() >>> logits = Tensor(np.array([1, 2, 3]), mindspore.float32) @@ -308,12 +371,26 @@ class MSELoss(LossBase): """ def construct(self, logits, labels): + # 检查logits是否为张量 _check_is_tensor('logits', logits, self.cls_name) + # 检查labels是否为张量 _check_is_tensor('labels', labels, self.cls_name) + # 计算logits与labels之间的平方差,并将结果赋值给x x = F.square(logits - labels) return self.get_loss(x) + +@constexpr +def _check_rmseloss_dtype(param_dtype, not_supported_dtype, cls_name): + """Check RMSELoss not supported data type""" + # 如果参数的类型(param_dtype)在非支持的数据类型列表(not_supported_dtype)中 + if param_dtype in not_supported_dtype: + # 抛出TypeError异常 + raise TypeError(f"For '{cls_name}', the parameters data type must not be in {not_supported_dtype}, " + f"but got mindspore.{str(param_dtype).lower()}.") + +# 用于实现均方根误差 class RMSELoss(LossBase): r""" RMSELoss creates a criterion to measure the root mean square error between :math:`x` and :math:`y` @@ -333,12 +410,15 @@ class RMSELoss(LossBase): and they should be broadcasted to each other. Outputs: - Tensor, weighted loss float tensor and its shape is (). + Tensor, weighted loss float tensor and its shape is :math:`()`. Supported Platforms: ``Ascend`` ``GPU`` ``CPU`` Examples: + >>> import mindspore + >>> from mindspore import Tensor, nn + >>> import numpy as np >>> # Case 1: logits.shape = labels.shape = (3,) >>> loss = nn.RMSELoss() >>> logits = Tensor(np.array([1, 2, 3]), mindspore.float32) @@ -357,15 +437,31 @@ class RMSELoss(LossBase): def __init__(self): """Initialize RMSELoss.""" + # 调用super(RMSELoss, self).__init__()进行父类的初始化 super(RMSELoss, self).__init__() + # 设置self.dtype为数据类型 + self.dtype = P.DType() + # 创建MSELoss对象,用于计算均方误差损失 self.MSELoss = MSELoss() - + # 用于计算RMSE损失值 def construct(self, logits, label): + # 将logits转换为与self.dtype相同的数据类型 + logits_dtype = self.dtype(logits) + # 将label转换为与self.dtype相同的数据类型 + label_dtype = self.dtype(label) + # 定义一个不支持的数据类型列表(not_supported_dtype),其中包括uint8、uint16、uint32和uint64 + not_supported_dtype = [mstype.uint8, mstype.uint16, mstype.uint32, mstype.uint64] + # 用_check_rmseloss_dtype函数检查logits_dtype是否在not_supported_dtype中 + _check_rmseloss_dtype(logits_dtype, not_supported_dtype, 'RMSELoss') + # 用_check_rmseloss_dtype函数检查label_dtype是否在not_supported_dtype中 + _check_rmseloss_dtype(label_dtype, not_supported_dtype, "RMSELoss") + # 使用self.MSELoss(logits, label)计算均方误差损失 rmse_loss = F.sqrt(self.MSELoss(logits, label)) - + # 返回rmse损失函数值 return rmse_loss +# 用于实现均绝对误差 class MAELoss(LossBase): r""" MAELoss creates a criterion to measure the average absolute error between :math:`x` and :math:`y` @@ -377,7 +473,7 @@ class MAELoss(LossBase): .. math:: \ell(x, y) = L = \{l_1,\dots,l_N\}^\top, \quad \text{with } l_n = \left| x_n - y_n \right|, - where :math:`N` is the batch size. If `reduction` is not 'none', then: + where :math:`N` is the batch size. If `reduction` is not ``'none'``, then: .. math:: \ell(x, y) = @@ -387,8 +483,12 @@ class MAELoss(LossBase): \end{cases} Args: - reduction (str): Type of reduction to be applied to loss. The optional values are "mean", "sum", and "none". - Default: "mean". + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the mean of elements in the output. + - ``'sum'``: the output elements will be summed. Inputs: - **logits** (Tensor) - Tensor of shape :math:`(M, *)` where :math:`*` means, any number of @@ -398,16 +498,19 @@ class MAELoss(LossBase): and they should be broadcasted to each other. Outputs: - Tensor, weighted loss float tensor, the shape is zero if `reduction` is 'mean' or 'sum', + Tensor, weighted loss float tensor, the shape is zero if `reduction` is ``'mean'`` or ``'sum'`` ., while the shape of output is the broadcasted shape if `reduction` is 'none'. Raises: - ValueError: If `reduction` is not one of 'none', 'mean', 'sum'. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'``, ``'sum'``. Supported Platforms: ``Ascend`` ``GPU`` ``CPU`` Examples: + >>> import mindspore + >>> from mindspore import Tensor, nn + >>> import numpy as np >>> # Case 1: logits.shape = labels.shape = (3,) >>> loss = nn.MAELoss() >>> logits = Tensor(np.array([1, 2, 3]), mindspore.float32) @@ -427,16 +530,103 @@ class MAELoss(LossBase): def __init__(self, reduction='mean'): """Initialize MAELoss.""" + # 调用super(MAELoss, self).__init__(reduction)进行父类的初始化 super(MAELoss, self).__init__(reduction) + # 定义abs,用于计算预测值和实际值之间的绝对误差 self.abs = P.Abs() def construct(self, logits, label): + # 检查logits类型是否为张量 _check_is_tensor('logits', logits, self.cls_name) + # 检查labels类型是否为张量 _check_is_tensor('labels', label, self.cls_name) + x = self.abs(logits - label) + # 调用self.get_loss(x)方法来计算损失值,并返回 return self.get_loss(x) +# 用于实现了边缘损失函数 +class MarginRankingLoss(LossBase): + r""" + MarginRankingLoss creates a criterion that measures the loss. + Given two tensors :math:`input1`, :math:`input2` and a Tensor label :math:`target` with values 1 or -1, + the operation is as follows: + + .. math:: + \text{loss}(input1, input2, target) = \max(0, -target * (input1 - input2) + \text{margin}) + + Args: + margin (float, optional): Specify the adjustment factor of the operation. Default: ``0.0`` . + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the mean of elements in the output. + - ``'sum'``: the output elements will be summed. + + Inputs: + - **input1** (Tensor) - Tensor of shape :math:`(N, *)` where :math:`*` means, any number + of additional dimensions. + - **input2** (Tensor) - Tensor of shape :math:`(N, *)`, same shape and dtype as `input1`. + - **target** (Tensor) - Contains value 1 or -1. Suppose the shape of `input1` is + :math:`(x_1, x_2, x_3, ..., x_R)`, then the shape of `target` must be :math:`(x_1, x_2, x_3, ..., x_R)`. + + Outputs: + Tensor or Scalar. if `reduction` is ``"none"``, its shape is the same as `labels`. + Otherwise, a scalar value will be returned. + + Raises: + TypeError: If `margin` is not a float. + TypeError: If `input1`, `input2` or `target` is not a Tensor. + TypeError: If the types of `input1` and `input2` are inconsistent. + TypeError: If the types of `input1` and `target` are inconsistent. + ValueError: If the shape of `input1` and `input2` are inconsistent. + ValueError: If the shape of `input1` and `target` are inconsistent. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'`` , ``'sum'``. + + Supported Platforms: + ``Ascend`` ``GPU`` ``CPU`` + + Examples: + >>> import mindspore as ms + >>> from mindspore import Tensor, nn, ops + >>> import numpy as np + >>> loss1 = nn.MarginRankingLoss(reduction='none') + >>> loss2 = nn.MarginRankingLoss(reduction='mean') + >>> loss3 = nn.MarginRankingLoss(reduction='sum') + >>> sign = ops.Sign() + >>> input1 = Tensor(np.array([0.3864, -2.4093, -1.4076]), ms.float32) + >>> input2 = Tensor(np.array([-0.6012, -1.6681, 1.2928]), ms.float32) + >>> target = sign(Tensor(np.array([-2, -2, 3]), ms.float32)) + >>> output1 = loss1(input1, input2, target) + >>> print(output1) + [0.98759997 0. 2.7003999 ] + >>> output2 = loss2(input1, input2, target) + >>> print(output2) + 1.2293333 + >>> output3 = loss3(input1, input2, target) + >>> print(output3) + 3.6879997 + """ + + def __init__(self, margin=0.0, reduction='mean'): + """Initialize MarginRankingLoss.""" + # 用super进行父类的初始化 + super(MarginRankingLoss, self).__init__(reduction) + # 将reduction变量赋值给self.reduction + self.reduction = reduction + # 将margin变量赋值给self.margin + self.margin = margin + + def construct(self, input1, input2, target): + # 用ops中的margin_ranking_loss计算损失值,并传给x + x = ops.margin_ranking_loss(input1, input2, target, self.margin, self.reduction) + # 返回x值 + return x + + +# 用于实现平滑的L1损失函数 class SmoothL1Loss(LossBase): r""" SmoothL1 loss function, if the absolute error element-wise between the predicted value and the target value @@ -453,25 +643,44 @@ class SmoothL1Loss(LossBase): Where :math:`{\beta}` represents the threshold `beta`. + If `reduction` is not `none`, then: + + .. math:: + L = + \begin{cases} + \operatorname{mean}(L_{i}), & \text{if reduction} = \text{'mean';}\\ + \operatorname{sum}(L_{i}), & \text{if reduction} = \text{'sum'.} + \end{cases} + .. note:: - SmoothL1Loss can be regarded as modified version of L1Loss or a combination of L1Loss and L2Loss. - L1Loss computes the element-wise absolute difference between two input tensors while L2Loss computes the - squared difference between two input tensors. L2Loss often leads to faster convergence but it is less - robust to outliers, and the loss function has better robustness. + - On the Ascend platform, float64 data type will result in low operator performance. + - SmoothL1Loss can be regarded as modified version of L1Loss or a combination of L1Loss and L2Loss. + - L1Loss computes the element-wise absolute difference between two input tensors while L2Loss computes the + - squared difference between two input tensors. L2Loss often leads to faster convergence but it is less + - robust to outliers, and the loss function has better robustness. Args: beta (float): The loss function calculates the threshold of the transformation between L1Loss and L2Loss. - Default: 1.0. + Default: ``1.0`` . + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'none'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the mean of elements in the output. + - ``'sum'``: the output elements will be summed. Inputs: - - **logits** (Tensor) - Predictive value. Tensor of any dimension. Data type must be float16 or float32. + - **logits** (Tensor) - Predictive value. Tensor of any dimension. Data type must be one of float16, + float32 and float64. - **labels** (Tensor) - Ground truth data, same shape and dtype as the `logits`. Outputs: - Tensor, loss float tensor, same shape and dtype as the `logits`. + Tensor, if `reduction` is ``'none'``, then output is a tensor with the same shape as `logits`. + Otherwise the shape of output tensor is :math:`()`. Raises: TypeError: If `beta` is not a float. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'``, ``'sum'``. TypeError: If `logits` or `labels` are not Tensor. TypeError: If dtype of `logits` or `labels` is neither float16 not float32. TypeError: If dtype of `logits` is not the same as `labels`. @@ -482,6 +691,9 @@ class SmoothL1Loss(LossBase): ``Ascend`` ``GPU`` ``CPU`` Examples: + >>> import mindspore + >>> from mindspore import Tensor, nn + >>> import numpy as np >>> loss = nn.SmoothL1Loss() >>> logits = Tensor(np.array([1, 2, 3]), mindspore.float32) >>> labels = Tensor(np.array([1, 2, 2]), mindspore.float32) @@ -490,18 +702,23 @@ class SmoothL1Loss(LossBase): [0. 0. 0.5] """ - def __init__(self, beta=1.0): + def __init__(self, beta=1.0, reduction='none'): """Initialize SmoothL1Loss.""" - super(SmoothL1Loss, self).__init__() + # 用super进行父类的初始化 + super(SmoothL1Loss, self).__init__(reduction) + # 将beta参数赋值给self.beta self.beta = beta - self.smooth_l1_loss = P.SmoothL1Loss(self.beta) + # 将reduction参数赋值给self.reduction + self.reduction = reduction + # 将P.SmoothL1Loss(self.beta, self.reduction)赋值给self.smooth_l1_loss + self.smooth_l1_loss = P.SmoothL1Loss(self.beta, self.reduction) def construct(self, logits, labels): - _check_is_tensor('logits', logits, self.cls_name) - _check_is_tensor('labels', labels, self.cls_name) + # 将logits和labels传入计算loss,返回结果 return self.smooth_l1_loss(logits, labels) +# 用于实现软边界损失函数 class SoftMarginLoss(LossBase): r""" A loss class for two-class classification problems. @@ -511,31 +728,39 @@ class SoftMarginLoss(LossBase): (containing 1 or -1). .. math:: - \text{loss}(x, y) = \sum_i \frac{\log(1 + \exp(-y[i]*x[i]))}{\text{x.nelement}()} + \text{loss}(x, y) = \sum_i \frac{\log(1 + \exp(-y[i]*x[i]))}{x.nelement()} :math:`x.nelement()` represents the number of element of `x` . Args: - reduction (str): Apply specific reduction method to the output: 'none', 'mean', 'sum'. Default: "mean". + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the mean of elements in the output. + - ``'sum'``: the output elements will be summed. Inputs: - **logits** (Tensor) - Predict data. Data type must be float16 or float32. - **labels** (Tensor) - Ground truth data, with the same type and shape as `logits`. Outputs: - Tensor or Scalar, if `reduction` is "none", its shape is the same as `logits`. + Tensor or Scalar, if `reduction` is ``"none"``, its shape is the same as `logits`. Otherwise, a scalar value will be returned. Raises: TypeError: If `logits` or `labels` is not a Tensor. TypeError: If dtype of `logits` or `labels` is neither float16 nor float32. ValueError: If shape of `logits` is not the same as `labels`. - ValueError: If `reduction` is not one of 'none', 'mean', 'sum'. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'``, ``'sum'``. Supported Platforms: - ``Ascend`` + ``Ascend`` ``GPU`` Examples: + >>> import mindspore + >>> from mindspore import Tensor, nn + >>> import numpy as np >>> loss = nn.SoftMarginLoss() >>> logits = Tensor(np.array([[0.3, 0.7], [0.5, 0.5]]), mindspore.float32) >>> labels = Tensor(np.array([[-1, 1], [1, -1]]), mindspore.float32) @@ -545,13 +770,17 @@ class SoftMarginLoss(LossBase): """ def __init__(self, reduction='mean'): + # 用super进行父类的初始化 super(SoftMarginLoss, self).__init__() + # 将reduction参数传入计算loss self.soft_margin_loss = P.SoftMarginLoss(reduction) def construct(self, logits, labels): + # 返回 return self.soft_margin_loss(logits, labels) +# 用于实现softmax交叉熵损失函数,可以用于分类任务中 class SoftmaxCrossEntropyWithLogits(LossBase): r""" Computes softmax cross entropy between logits and labels. @@ -559,9 +788,12 @@ class SoftmaxCrossEntropyWithLogits(LossBase): Measures the distribution error between the probabilities of the input (computed with softmax function) and the labels where the classes are mutually exclusive (only one class is positive) using cross entropy loss. - Typical input into this function is unnormalized scores denoted as x whose shape is (N, C), + Typical input into this function is unnormalized scores denoted as x whose shape is :math:`(N, C)` , and the corresponding targets. + Typically, the input to this function is the fractional value of each category and the corresponding target value, + and the input format is :math:`(N, C)` . + For each instance :math:`x_i`, i ranges from 0 to N-1, the loss is given as: .. math:: @@ -576,13 +808,17 @@ class SoftmaxCrossEntropyWithLogits(LossBase): of entry is a valid one. Args: - sparse (bool): Specifies whether labels use sparse format or not. Default: False. - reduction (str): Type of reduction to be applied to loss. The optional values are "mean", "sum", and "none". - If "none", do not perform reduction. Default: "none". + sparse (bool, optional): Specifies whether labels use sparse format or not. Default: ``False`` . + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'none'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the mean of elements in the output. + - ``'sum'``: the output elements will be summed. Inputs: - - **logits** (Tensor) - Tensor of shape (N, C). Data type must be float16 or float32. - - **labels** (Tensor) - Tensor of shape (N, ). If `sparse` is True, The type of + - **logits** (Tensor) - Tensor of shape :math:`(N, C)` . Data type must be float16 or float32. + - **labels** (Tensor) - Tensor of shape :math:`(N, )` . If `sparse` is True, The type of `labels` is int32 or int64. Otherwise, the type of `labels` is the same as the type of `logits`. Outputs: @@ -592,12 +828,15 @@ class SoftmaxCrossEntropyWithLogits(LossBase): TypeError: If `sparse` is not a bool. TypeError: If `sparse` is True and dtype of `labels` is neither int32 not int64. TypeError: If `sparse` is False and dtype of `labels` is neither float16 not float32. - ValueError: If `reduction` is not one of 'none', 'mean', 'sum'. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'``, ``'sum'``. Supported Platforms: ``Ascend`` ``GPU`` ``CPU`` Examples: + >>> import mindspore + >>> from mindspore import Tensor, nn + >>> import numpy as np >>> # case 1: sparse=True >>> loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True) >>> logits = Tensor(np.array([[3, 5, 6, 9, 12, 33, 42, 12, 32, 72]]), mindspore.float32) @@ -620,37 +859,58 @@ class SoftmaxCrossEntropyWithLogits(LossBase): sparse=False, reduction='none'): """Initialize SoftmaxCrossEntropyWithLogits.""" + # 用super进行父类的初始化 super(SoftmaxCrossEntropyWithLogits, self).__init__(reduction) + # 定义sparse用于检查sparse是否为bool型 self.sparse = validator.check_bool(sparse, "sparse", self.cls_name) + # 定义self.reduction值为reduction self.reduction = reduction + # 定义self.softmax_cross_entropy,用于计算softmax交叉熵的函数 self.softmax_cross_entropy = P.SoftmaxCrossEntropyWithLogits() + # 定义self.one_hot,用于创建one-hot编码的函数 self.one_hot = P.OneHot() + # 定义self.on_value,为Tensor,表示on值,Tensor的第一个参数是一个浮点数1.0,数据类型为mstype.float32 self.on_value = Tensor(1.0, mstype.float32) + # 定义self.off_value,为Tensor,表示off值,Tensor的第一个参数是一个浮点数0,数据类型为mstype.float32 self.off_value = Tensor(0., mstype.float32) + # 定义self.is_cpugpu,表示当前的计算环境是否为CPU或GPU self.is_cpugpu = context.get_context('device_target') in ["CPU", "GPU"] + # 定义self.sparse_softmax_cross_entropy用于计算稀疏softmax交叉熵的函数 self.sparse_softmax_cross_entropy = P.SparseSoftmaxCrossEntropyWithLogits() def construct(self, logits, labels): + # 检查logits的类型是否为tensor _check_is_tensor('logits', logits, self.cls_name) + # 检查labels的类型是否为tensor _check_is_tensor('labels', labels, self.cls_name) + # 判断self.sparse的类型 if self.sparse: + # 如果self.reduction为mean if self.reduction == 'mean': + # 用self.sparse_softmax_cross_entropy函数,传入logits和labels作为参数,计算损失。并将结果赋值给变量x x = self.sparse_softmax_cross_entropy(logits, labels) + # 返回计算值x return x + # 调用self.one_hot函数,将labels转换为one-hot编码 labels = self.one_hot(labels, F.shape(logits)[-1], self.on_value, self.off_value) + # 调用self.softmax_cross_entropy函数,将logits和labels作为参数,计算损失 x = self.softmax_cross_entropy(logits, labels)[0] + # 调用self.get_loss函数,将损失值x作为参数传入,并返回计算出的损失值 return self.get_loss(x) + @constexpr +# 检查labels的类型 def _check_label_dtype(labels_dtype, cls_name): """Internal function, used to check whether the data type of labels meets the requirements.""" + # 检查labels的类型是否为int32或int64 validator.check_type_name("labels", labels_dtype, [mstype.int32, mstype.int64], cls_name) - +# 用于实现Dice损失函数,可以用于图像分割任务中 class DiceLoss(LossBase): r""" - The Dice coefficient is a set similarity loss. It is used to calculate the similarity between two samples. The + The Dice coefficient is a set similarity loss, which is used to calculate the similarity between two samples. The value of the Dice coefficient is 1 when the segmentation result is the best and is 0 when the segmentation result is the worst. The Dice coefficient indicates the ratio of the area between two objects to the total area. The function is shown as follows: @@ -662,12 +922,11 @@ class DiceLoss(LossBase): Args: smooth (float): A term added to the denominator to improve numerical stability. Should be greater than 0. - Default: 1e-5. + Default: ``1e-5`` . Inputs: - - **logits** (Tensor) - Tensor of shape :math:`(N, *)` where :math:`*` means, any number of - additional dimensions. The data type must be float16 or float32. - - **labels** (Tensor) - Tensor of shape :math:`(N, *)`, same shape as the `logits`. + - **logits** (Tensor) - Input predicted value. The data type must be float16 or float32. + - **labels** (Tensor) - Input target value. Same shape as the `logits`. The data type must be float16 or float32. Outputs: @@ -681,9 +940,12 @@ class DiceLoss(LossBase): ``Ascend`` ``GPU`` ``CPU`` Examples: + >>> import mindspore + >>> from mindspore import Tensor, nn + >>> import numpy as np >>> loss = nn.DiceLoss(smooth=1e-5) - >>> logits = Tensor(np.array([[0.2, 0.5], [0.3, 0.1], [0.9, 0.6]]), mstype.float32) - >>> labels = Tensor(np.array([[0, 1], [1, 0], [0, 1]]), mstype.float32) + >>> logits = Tensor(np.array([[0.2, 0.5], [0.3, 0.1], [0.9, 0.6]]), mindspore.float32) + >>> labels = Tensor(np.array([[0, 1], [1, 0], [0, 1]]), mindspore.float32) >>> output = loss(logits, labels) >>> print(output) 0.38596618 @@ -691,63 +953,95 @@ class DiceLoss(LossBase): def __init__(self, smooth=1e-5): """Initialize DiceLoss.""" + # 用super进行父类的初始化 super(DiceLoss, self).__init__() + # 定义self.smooth,用于检查smooth是否为正浮点数,如果不是,则会抛出异常 self.smooth = validator.check_positive_float(smooth, "smooth") + # 定义self.reshape,用于改变张量的形状 self.reshape = P.Reshape() def construct(self, logits, label): + # 检查logits的类型是否为tensor _check_is_tensor('logits', logits, self.cls_name) + # 检查labels的类型是否为tensor _check_is_tensor('labels', label, self.cls_name) + # 检查logits与label的形状是否一致 _check_shape(logits.shape, label.shape, self.cls_name) + # 如果logits的类型为mstype.uint8 + if logits.dtype == mstype.uint8: + # 抛出TypeError异常 + raise TypeError(f"For '{self.cls_name}', the dtype of 'logits' can not be uint8.") + # 如果label的类型为mstype.uint8 + if label.dtype == mstype.uint8: + # 抛出TypeError异常 + raise TypeError(f"For '{self.cls_name}', the dtype of 'labels' can not be uint8.") + # 计算两个张量的按元素乘积,然后使用reduce_sum函数对结果进行求和,得到交集的分子 intersection = self.reduce_sum(self.mul(logits.view(-1), label.view(-1))) + # 计算两个张量的按元素乘积,然后使用reduce_sum函数对结果进行求和,得到并集的分子 unionset = self.reduce_sum(self.mul(logits.view(-1), logits.view(-1))) + \ self.reduce_sum(self.mul(label.view(-1), label.view(-1))) - + # 计算Dice系数,即交集除以并集 single_dice_coeff = (2 * intersection) / (unionset + self.smooth) + # 计算Dice损失,即1减去Dice系数 dice_loss = 1 - single_dice_coeff - + # 返回Dice损失 return dice_loss -@constexpr + +@_primexpr +# 用于检查logits和label的类型是否一致 def _check_shape(logits_shape, label_shape, prim_name=None): """Internal function, used to check whether the shape of logits and labels meets the requirements.""" + # 检查logits和label的类型是否一致 validator.check('logits_shape', logits_shape, 'label_shape', label_shape, prim_name=prim_name) -@constexpr +@_primexpr def _check_ndim_multi(logits_dim, label_dim, prim_name=None): """Internal function, used to check whether the dimension of logits and label meets the requirements.""" + # 根据prim_name是否为空字符串,构造一个字符串,用于在异常信息中描述类名 + # 如果prim_name不为空,则使用f'For \'{prim_name}\', the'作为前缀;否则,使用"The"作为前缀 msg_prefix = f'For \'{prim_name}\', the' if prim_name else "The" + # 如果logits的维度小于2 if logits_dim < 2: - raise ValueError(f"{msg_prefix} 'logits' dimension should be greater than 1, but got {logits_dim}.") + # 抛出ValueError异常 + raise ValueError(f"{msg_prefix} 'logits' dimension must be greater than 1, but got {logits_dim}.") + # 如果logits的维度小于2 if label_dim < 2: - raise ValueError(f"{msg_prefix} 'labels' dimension should be greater than 1, but got {label_dim}.") + # 抛出ValueError异常 + raise ValueError(f"{msg_prefix} 'labels' dimension must be greater than 1, but got {label_dim}.") -@constexpr +@_primexpr def _check_weights(weight_shape, label_shape, prim_name=None): """Internal function, used to check whether the reduced shape meets the requirements.""" + # 根据prim_name是否为空字符串,构造一个字符串,用于在异常信息中描述类名 + # 如果prim_name不为空,则使用f'For \'{prim_name}\', the'作为前缀;否则,使用"The"作为前缀 msg_prefix = f'For \'{prim_name}\', the' if prim_name else "The" + # 如果weight的形状和label的形状不一致 if weight_shape != label_shape: - raise ValueError(f"{msg_prefix} weight_shape[0] should be equal to label_shape[1], " + # 抛出ValueError异常 + raise ValueError(f"{msg_prefix} weight_shape[0] must be equal to label_shape[1], " f"but got weight_shape[0]: {weight_shape} and label_shape[1]: {label_shape}.") - +# 用于实现多分类Dice损失函数,用于评估神经网络模型在多分类任务上的性能 class MultiClassDiceLoss(LossBase): r""" When there are multiple classifications, label is transformed into multiple binary classifications by one hot. For each channel section in the channel, it can be regarded as a binary classification problem, so it can be - obtained through the binary loss of each category, and then the average value. + obtained through the binary :class:`mindspore.nn.DiceLoss` losses of each category, + and then the average value of the binary losses. Args: weights (Union[Tensor, None]): Tensor of shape :math:`(num\_classes, dim)`. The weight shape[0] should be equal to labels shape[1]. - Default: None. + Default: ``None`` . ignore_indiex (Union[int, None]): Class index to ignore. - Default: None. + Default: ``None`` . activation (Union[str, Cell]): Activate function applied to the output of the fully connected layer, eg. 'ReLU'. - Default: 'softmax'. Choose from: ['softmax', 'logsoftmax', 'relu', 'relu6', 'tanh','Sigmoid'] + Default: ``'softmax'`` . Choose from: [ ``'softmax'`` , ``'logsoftmax'`` , ``'relu'`` , ``'relu6'`` , + ``'tanh'`` , ``'Sigmoid'`` ] Inputs: - **logits** (Tensor) - Tensor of shape :math:`(N, C, *)` where :math:`*` means, any number of additional @@ -769,9 +1063,12 @@ class MultiClassDiceLoss(LossBase): ``Ascend`` ``GPU`` ``CPU`` Examples: + >>> import mindspore + >>> from mindspore import Tensor, nn + >>> import numpy as np >>> loss = nn.MultiClassDiceLoss(weights=None, ignore_indiex=None, activation="softmax") - >>> logits = Tensor(np.array([[0.2, 0.5, 0.7], [0.3, 0.1, 0.5], [0.9, 0.6, 0.3]]), mstype.float32) - >>> labels = Tensor(np.array([[0, 1, 0], [1, 0, 0], [0, 0, 1]]), mstype.float32) + >>> logits = Tensor(np.array([[0.2, 0.5, 0.7], [0.3, 0.1, 0.5], [0.9, 0.6, 0.3]]), mindspore.float32) + >>> labels = Tensor(np.array([[0, 1, 0], [1, 0, 0], [0, 0, 1]]), mindspore.float32) >>> output = loss(logits, labels) >>> print(output) 0.54958105 @@ -779,47 +1076,71 @@ class MultiClassDiceLoss(LossBase): def __init__(self, weights=None, ignore_indiex=None, activation="softmax"): """Initialize MultiClassDiceLoss.""" + # 用super进行父类的初始化 super(MultiClassDiceLoss, self).__init__() + # 定义一个激活列表其中包含softmax,logsoftmax,relu,relu6,tanh,sigmoid activation_list = ['softmax', 'logsoftmax', 'relu', 'relu6', 'tanh', 'sigmoid'] - + # 定义self.binarydiceloss用于计算二分类Dice损失 self.binarydiceloss = DiceLoss(smooth=1e-5) + # 定义self.weights,用于指定每个类别的权重 self.weights = weights if weights is None else validator.check_value_type("weights", weights, [Tensor]) + # 如果weights为tensor并且维度不为2维 if isinstance(self.weights, Tensor) and self.weights.ndim != 2: - raise ValueError(f"For '{self.cls_name}', the dimension of 'weights' should be 2, " + # 抛出ValueError异常 + raise ValueError(f"For '{self.cls_name}', the dimension of 'weights' must be 2, " f"but got {self.weights.ndim}.") - self.ignore_indiex = ignore_indiex if ignore_indiex is None else \ - validator.check_value_type("ignore_indiex", ignore_indiex, [int]) + self.ignore_indiex = ignore_indiex if ignore_indiex is None else validator.check_value_type("ignore_indiex", + ignore_indiex, + [int]) + # 如果activation为str型并且不在activation_list列表之中 if isinstance(activation, str) and activation not in activation_list: + # 抛出异常ValueError raise ValueError(f"For '{self.cls_name}', the 'activation' must be in {activation_list}, " f"but got {activation}.") - + # 如果activation为str型,则调用get_activation函数获取相应的激活函数,并将其赋值给self.activation self.activation = get_activation(activation) if isinstance(activation, str) else activation + # 如果self.activation不为空且其类型不是Cell类型 if self.activation is not None and not isinstance(self.activation, Cell): + # 抛出TypeError异常 raise TypeError(f"For '{self.cls_name}', the 'activation' must be str or Cell, " f"but got {type(self.activation)}.") + # 定义self.reshape值为P.Reshape() self.reshape = P.Reshape() def construct(self, logits, label): + # 检查logits的类型是否为tensor _check_is_tensor('logits', logits, self.cls_name) + # 检查labels的类型是否为tensor _check_is_tensor('labels', label, self.cls_name) + # 检查logits和label的形状是否相同 _check_shape(logits.shape, label.shape, self.cls_name) + # 检查logits和label的维度 _check_ndim_multi(logits.ndim, label.ndim, self.cls_name) + # 定义total_loss为0 total_loss = 0 - + # 如果self.activation属性不为空 if self.activation is not None: + # 激活logits logits = self.activation(logits) - + # 循环遍历label的每个类别 for i in range(label.shape[1]): + # 如果当前类别索引i与忽略索引不同,则继续计算 if i != self.ignore_indiex: + # 使用二分类Dice损失函数计算当前类别的损失 dice_loss = self.binarydiceloss(logits[:, i], label[:, i]) + # 如果不存在类别权重 if self.weights is not None: + # 查类别权重维度是否与类别数量相同 _check_weights(self.weights.shape[0], label.shape[1], self.cls_name) + # 将当前类别的损失乘以对应类别的权重 dice_loss *= self.weights[i] + # 将当前类别的损失累加到总损失total_loss中 total_loss += dice_loss - + # 返回总损失除以类别数量 return total_loss / label.shape[1] +# 用于计算sampledsoftmax损失,可以用于提高模型的泛化能力 class SampledSoftmaxLoss(LossBase): r""" Computes the sampled softmax training loss. This operator can accelerate the training of the softmax classifier @@ -828,15 +1149,19 @@ class SampledSoftmaxLoss(LossBase): Args: num_sampled (int): The number of classes to randomly sample per batch. num_classes (int): The number of possible classes. - num_true (int): The number of labels classes per training example. Default: 1. + num_true (int): The number of labels classes per training example. Default: ``1`` . sampled_values (Union[list, tuple]): List or tuple of (`sampled_candidates`, `true_expected_count`, `sampled_expected_count`) returned by a `*CandidateSampler` function. - Default to None, `UniformCandidateSampler` is applied. + Default to None, `UniformCandidateSampler` is applied. Default: ``None`` . remove_accidental_hits (bool): Whether to remove "accidental hits" - where a sampled class equals to one of the labels classes. Default: True. + where a sampled class equals to one of the labels classes. Default: ``True`` . seed (int): Random seed for candidate sampling. Default: 0 - reduction (str): Type of reduction to be applied to loss. The optional values are "mean", "sum", and "none". - If "none", do not perform reduction. Default: "none". + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'none'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the mean of elements in the output. + - ``'sum'``: the output elements will be summed. Inputs: - **weights** (Tensor) - Tensor of shape :math:`(C, dim)`. @@ -845,13 +1170,13 @@ class SampledSoftmaxLoss(LossBase): - **logits** (Tensor) - Tensor of shape :math:`(N, dim)`. The forward activations of the input network. Outputs: - Tensor or Scalar, if `reduction` is 'none', then output is a tensor with shape :math:`(N,)`. + Tensor or Scalar, if `reduction` is ``'none'``, then output is a tensor with shape :math:`(N,)`. Otherwise, the output is a scalar. Raises: TypeError: If `sampled_values` is not a list or tuple. TypeError: If dtype of `labels` is neither int32 not int64. - ValueError: If `reduction` is not one of 'none', 'mean', 'sum'. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'``, ``'sum'``. ValueError: If `num_sampled` or `num_true` is greater than `num_classes`. ValueError: If length of `sampled_values` is not equal to 3. @@ -859,6 +1184,9 @@ class SampledSoftmaxLoss(LossBase): ``GPU`` Examples: + >>> import mindspore + >>> from mindspore import Tensor, nn + >>> import numpy as np >>> mindspore.set_seed(1) >>> loss = nn.SampledSoftmaxLoss(num_sampled=4, num_classes=7, num_true=1) >>> weights = Tensor(np.random.randint(0, 9, [7, 10]), mindspore.float32) @@ -874,34 +1202,49 @@ class SampledSoftmaxLoss(LossBase): sampled_values=None, remove_accidental_hits=True, seed=0, reduction='none'): """Initialize SampledSoftmaxLoss.""" + # 用super进行父类的初始化 super(SampledSoftmaxLoss, self).__init__(reduction) - + # 如果num_true小于1 if num_true < 1: + # 抛出ValueError异常 raise ValueError(f"For '{self.cls_name}', the 'num_true' must be greater than or equal to 1, " f"but got {num_true}.") + # 如果seed小于0 if seed < 0: + # 抛出ValueError异常 raise ValueError(f"For '{self.cls_name}', the 'seed' must be greater than or equal to 0, but got {seed}.") + # 如果num_sampled大于num_classes if num_sampled > num_classes: + # 抛出ValueError异常 raise ValueError(f"For '{self.cls_name}', the 'num_sampled' must be smaller than or " f"equal to 'num_classes', but got 'num_sampled': {num_sampled} " f"and 'num_classes': {num_classes}.") + # 如果num_true大于num_classes if num_true > num_classes: + # 抛出ValueError异常 raise ValueError(f"For '{self.cls_name}', the 'num_true' must be smaller than or equal to 'num_classes', " f"but got 'num_true': {num_true} amd 'num_classes': {num_classes}.") + # 如果sampled_values为None if sampled_values is not None: + # 如果sampled_values的类型不是list或tuple if not isinstance(sampled_values, (list, tuple)): + # 抛出ValueError异常 raise TypeError(f"For '{self.cls_name}', the type of 'sampled_values' must be a list or tuple, " f"but got {type(sampled_values).__name__}.") + # 如果sampled_values的长度不为3 if len(sampled_values) != 3: + # 抛出ValueError异常 raise ValueError(f"For '{self.cls_name}', the length of 'sampled_values' must be equal to 3," f"but got {len(sampled_values)}.") + # 初始化函数 self.num_sampled = num_sampled self.num_classes = num_classes self.num_true = num_true self.sampled_values = sampled_values self.remove_accidental_hits = remove_accidental_hits self.seed = seed + # 初始化采样器 self.sampler = P.UniformCandidateSampler( num_true, num_sampled, @@ -909,6 +1252,7 @@ class SampledSoftmaxLoss(LossBase): num_classes, seed, remove_accidental_hits) + # 初始化操作符 self.cast = P.Cast() self.reshape = P.Reshape() self.shape = P.Shape() @@ -929,12 +1273,17 @@ class SampledSoftmaxLoss(LossBase): self.dtype = P.DType() def construct(self, weights, biases, labels, logits): + # 检查weights的类型是否为tensor _check_is_tensor('weights', weights, self.cls_name) + # 检查biases的类型是否为tensor _check_is_tensor('biases', biases, self.cls_name) + # 检查labels的类型是否为tensor _check_is_tensor('labels', labels, self.cls_name) + # 检查logits的类型是否为tensor _check_is_tensor('logits', logits, self.cls_name) + # 检查label的类型是否符合要求,并将其转换为指定的数据类型 _check_label_dtype(self.dtype(labels), self.cls_name) - + # 传入参数 logits, labels = self._compute_sampled_logits( weights=weights, biases=biases, @@ -943,14 +1292,18 @@ class SampledSoftmaxLoss(LossBase): num_true=self.num_true, sampled_values=self.sampled_values, subtract_log_q=True) - + # 调用self._softmax_cross_entropy计算损失并传给x x = self._softmax_cross_entropy(logits, labels) + # 返回x值 return x def _softmax_cross_entropy(self, logits, targets): + # 计算logits的指数,并减去logits的最大值,以避免数值溢出 stable_exp_logits = self.exp(logits - self.reduce_max_true(logits, 1)) + # 计算每个样本中每个类别被选为正例的概率 pred = stable_exp_logits / self.reduce_sum_true(stable_exp_logits, 1) - return -self.reduce_sum(targets * self.log(pred + 1.0e-20), 1) + # 用reduce_sum函数将损失值进行求和并乘-1得到最终损失值 + return -1 * self.reduce_sum(targets * self.log(pred + 1.0e-20), 1) def _compute_sampled_logits(self, weights, biases, @@ -977,80 +1330,538 @@ class SampledSoftmaxLoss(LossBase): sampled_values: A tuple of (`sampled_candidates`, `true_expected_count`, `sampled_expected_count`) returned by a `UniformCandidateSampler` function. subtract_log_q: A `bool`. whether to subtract the log expected count of - the labels in the sample to get the logits of the true labels. Default: True. + the labels in the sample to get the logits of the true labels. Default: ``True`` . Returns: out_logits: `Tensor` object with shape `[batch_size, num_true + num_sampled]` out_labels: A tensor object with the same shape as `out_logits`. """ - + # 如果label的类型不是int32 if not labels.dtype == mstype.int32: + # 把label的类型转换为int32 labels = self.cast(labels, mstype.int32) + # 将标签转换为二维向量 labels = self.reshape(labels, (-1, num_true)) + # 将处理后的标签转换为一维向量 labels_flat = self.reshape(labels, (-1,)) # Sample the negative labels. # sampled shape: [num_sampled] tensor # true_expected_count shape is [batch_size, 1] tensor # sampled_expected_count shape is [num_sampled] tensor + + # 如果sampled_values为None if sampled_values is None: + # 调用sampler方法根据labels生成sampled_values sampled_values = self.sampler(labels) (sampled, true_expected_count, sampled_expected_count) = sampled_values - + # 如果sampled的类型不是int32 if not sampled.dtype == mstype.int32: + # 把sampled的类型转换为int32 sampled = self.cast(sampled, mstype.int32) + # 将labels_flat和sampled拼接在一起,形成一个包含所有类别的id的向量 all_ids = self.concat_dim0((labels_flat, sampled)) + # 从权重中根据all_ids的值获取对应的权重值,并将结果存储在all_w中 all_w = self.gather_v2(weights, all_ids, 0) - + # 获取labels_flat的第一个维度的大小,并将其存储在变量n_true中 n_true = self.shape(labels_flat)[0] + # 获取sampled的第一个维度的大小,并将其存储在变量n_sampled中 n_sampled = self.shape(sampled)[0] + # 获取all_w的第二个维度的大小,并将其存储在变量n_dim中 n_dim = self.shape(all_w)[1] - + # 使用self.slice_op函数从all_w中切分出n_true个权重向量,并将它们组成一个新的向量true_w true_w = self.slice_op(all_w, [0, 0], [n_true, n_dim]) + # 使用self.slice_op函数从all_w中切分出n_sampled个权重向量,并将它们组成一个新的向量sampled_w sampled_w = self.slice_op(all_w, [n_true, 0], [n_sampled, n_dim]) + # 将logits和sampled_w进行矩阵乘法 sampled_logits = self.matmul(logits, sampled_w) - + # 使用self.gather_v2函数从biases中提取与all_ids中对应的元素,并将它们组成一个新的向量all_b all_b = self.gather_v2(biases, all_ids, 0) + # 使用self.slice_op函数从all_b中切分出n_true个偏置向量,并将它们组成一个新的向量true_b true_b = self.slice_op(all_b, [0], [n_true]) + # 使用self.slice_op函数从all_b中切分出n_sampled个偏置向量,并将它们组成一个新的向量sampled_b sampled_b = self.slice_op(all_b, [n_true], [n_sampled]) - + # 定义new_true_w_shape,-1表示自动计算该维度的大小,num_true表示每个样本中true_w的个数,n_dim表示true_w的维度 new_true_w_shape = (-1, num_true, n_dim) + # 使用self.expand_dims函数将logits向量扩展为形状为(batch_size, 1, n_class)的向量 + # 使用self.reshape函数将true_w向量重置为形状为(-1, num_true, n_dim)的向量 + # 使用self.mul函数将这两个向量进行矩阵乘法,并将结果存储在row_wise_dots变量中 row_wise_dots = self.mul(self.expand_dims(logits, 1), self.reshape(true_w, new_true_w_shape)) # We want the row-wise dot plus biases which yields a # [batch_size, num_true] tensor of true_logits. + + # 将row_wise_dots向量转换为形状为(-1, n_dim)的矩阵 dots_as_matrix = self.reshape(row_wise_dots, (-1, n_dim)) + # 使用self.reduce_sum函数对dots_as_matrix矩阵进行逐行求和 + # 使用self.reshape函数将结果转换为形状为(-1, num_true)的向量 true_logits = self.reshape(self.reduce_sum(dots_as_matrix, 1), (-1, num_true)) + # 将true_b向量转换为形状为(-1, num_true)的矩阵 true_b = self.reshape(true_b, (-1, num_true)) + # 将true_b向量添加到true_logits向量 true_logits += true_b + # 将sampled_b向量添加到sampled_logits向量 sampled_logits += sampled_b + # 如果subtract_log_q为true if subtract_log_q: # Subtract log of Q(l), prior probability that l appears in sampled. + # 将true_logits向量减去先验概率 true_logits -= self.log(true_expected_count) + # 将sampled_logits向量减去先验概率 sampled_logits -= self.log(sampled_expected_count) # Construct output logits and labels. The true labels/logits start at col 0. + # 使用self.concat_dim1函数将true_logits和sampled_logits向量拼接在一起,并将结果存储在out_logits变量中 out_logits = self.concat_dim1((true_logits, sampled_logits)) # true_logits is a float tensor, ones_like(true_logits) is a float # tensor of ones. We then divide by num_true to ensure the per-example # labels sum to 1.0, i.e. form a proper probability distribution. + # 使用self.concat_dim1函数将两个向量拼接在一起,并将结果存储在out_labels变量中 out_labels = self.concat_dim1(( + # 生成一个与true_logits形状相同的向量,其中每个元素都是1 self.ones_like(true_logits) / num_true, + # 生成一个与sampled_logits形状相同的向量,其中每个元素都是0 self.zeros_like(sampled_logits) )) + # 返回out_logits和out_labels return out_logits, out_labels +# 用于计算三元组损失,其中三元组由anchor、positive和negative样本组成,它们之间的距离满足一定的约束条件 +class TripletMarginWithDistanceLoss(LossBase): + r""" + TripletMarginWithDistanceLoss operation. + + Creates a criterion that measures the triplet loss given an input + tensors :math:`x1`, :math:`x2`, :math:`x3` and a margin with a value greater than :math:`0`. + This is used for measuring a relative similarity between samples. A triplet + is composed by `a`, `p` and `n` (i.e., `anchor`, `positive examples` and `negative + examples` respectively). The shapes of all input tensors should be + :math:`(N, D)`. + + The distance swap is described in detail in the paper `Learning shallow + convolutional feature descriptors with triplet losses` by + V. Balntas, E. Riba et al. + + The loss function for each sample in the mini-batch is: + + .. math:: + L(a, p, n) = \max \{d(a_i, p_i) - d(a_i, n_i) + {\rm margin}, 0\} + + where + + .. math:: + d(x_i, y_i) = \left\lVert {\bf x}_i - {\bf y}_i \right\rVert_p + + Args: + distance_function (callable): The distance function needed to calculate the margin loss of a triplet. + if no distance metric is specified, the pairwise distance will be used. Default: ``None`` . + swap (bool): The distance swap is described in detail in the paper + `Learning shallow convolutional feature descriptors with triplet losses` by + V. Balntas, E. Riba et al. Default: ``False`` . + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the mean of elements in the output. + - ``'sum'``: the output elements will be summed. + + margin (float): Make a margin between the positive pair and the negative pair. Default: ``1.0`` . + + Inputs: + - **x** (Tensor) - A sample randomly selected from the training set. Data type must be BasicType. + The shape should be :math:`(N, D)`. + - **positive** (Tensor) - A sample belonging to the same category as x, + with the same type and shape as `x`. + - **negative** (Tensor) - A sample belonging to the different class from x, + with the same type and shape as `x`. + + Outputs: + Union[Tensor, Scalar], if `reduction` is ``'none'``, its shape is :math:`(N)`. + Otherwise, a scalar value will be returned. + + Raises: + TypeError: If `x` or `positive` or `negative` is not a Tensor. + TypeError: If `swap` is not a bool. + ValueError: If dimensions of input `x`, `positive` and `negative` are less than or equal to 1 at the same time. + ValueError: If length of shape of `margin` is not 0. + ValueError: If shape of `x`, `positive` and `negative` cannot broadcast. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'``, ``'sum'``. + + Supported Platforms: + ``Ascend`` ``GPU`` ``CPU`` + + Examples: + >>> import mindspore + >>> from mindspore import Tensor, nn + >>> x = Tensor([[0.3, 0.7], [0.5, 0.5]]) + >>> positive = Tensor([[0.4, 0.6], [0.4, 0.6]]) + >>> negative = Tensor([[0.2, 0.9], [0.3, 0.7]]) + >>> loss = nn.TripletMarginWithDistanceLoss() + >>> out = loss(x, positive, negative) + >>> print(out.asnumpy()) + 0.8881968 + """ + + def __init__(self, distance_function=None, swap=False, reduction="mean", margin=1.0): + """Initialize TripletMarginWithDistanceLoss.""" + # 用super进行父类的初始化 + super(TripletMarginWithDistanceLoss, self).__init__(reduction=reduction) + # 检查margin的类型是否为float + validator.check_is_float(margin, "margin", self.cls_name) + # 检查swap的类型是否为bool + validator.check_bool(swap, "swap", self.cls_name) + # 如果distance_function为None + if distance_function is None: + def pairwise_distance(x, y): + # 计算x和y之间的差值,然后使用abs()函数取绝对值 + d = (x - y).abs() + # 如果d的维度为0 + if d.ndim == 0: + # 抛出ValueError异常 + raise ValueError( + "For 'pairwise_distance' in 'TripletMarginWithDistanceLoss', " + "'ndim' of the input must be positive, " + f"but got {d.ndim}" + ) + # 返回P.LpNorm + return P.LpNorm(axis=1, p=2)(d) + # 将pairwise_distance函数赋值给成员变量self.distance_function + self.distance_function = pairwise_distance + else: + # 将distance_function函数赋值给成员变量self.distance_function + self.distance_function = distance_function + # 将swap赋值给成员变量self.swap + self.swap = swap + # 将reduction赋值给成员变量self.reduction + self.reduction = reduction + # 将margin赋值给成员变量self.margin + self.margin = margin + # 定义Minimum,用于计算一组数的最小值 + self.minimum = P.Minimum() + # 定义Maximum,用于计算一组数的最小值 + self.maximum = P.Maximum() + + def construct(self, x, positive, negative): + # 检查x的类型是否为tensor + _check_is_tensor("x", x, self.cls_name) + # 检查positive的类型是否为tensor + _check_is_tensor("positive", positive, self.cls_name) + # 检查negative的类型是否为tensor + _check_is_tensor("negative", negative, self.cls_name) + # 使用distance_function函数计算anchor和positive样本之间的距离,并将结果存储在变量d1中 + d1 = self.distance_function(x, positive) + # 使用distance_function函数计算anchor和negative样本之间的距离,并将结果存储在变量d2中 + d2 = self.distance_function(x, negative) + # 如果swap为True + if self.swap: + # 使用Minimum对象计算anchor和positive样本之间的距离和anchor和negative样本之间的距离之间的最小值 + d2 = self.minimum(d2, self.distance_function(positive, negative)) + # 使用Maximum对象计算d1和d2之间的差值,再加上margin,并将结果存储在变量loss中 + loss = self.maximum(d1 - d2 + self.margin, 0) + # 用get_loss函数,将loss作为输入参数,并返回计算结果 + return self.get_loss(loss) + +# 用于计算泊松分布的负对数似然损失,通常用于处理计数数据(如点击率、观看率等) +class PoissonNLLLoss(LossBase): + r""" + Poisson negative log likelihood loss. + + The loss is: + + .. math:: + \mathcal{L}_{D} = \sum_{i = 0}^{|D|}\left( x_{i} - y_{i}\ln x_{i} + \ln{y_{i}!} \right) + + where :math:`\mathcal{L}_{D}` is the loss, :math:`y_{i}` is the `target`, + :math:`x_{i}` is the `input`. + + If `log_input` is True, use :math:`e^{x_{i}} - y_{i} x_{i}` instead of :math:`x_{i} - y_{i}\ln x_{i}`. + When calculating logarithms, the lower bound of `input` is set to `eps` to avoid numerical errors. + + If `full` is False, the last term :math:`\ln{y_{i}!}` will be omitted, + otherwise the last term will be approximated using Stirling formula: + + .. math:: + n! \approx \sqrt{2\pi n}\left( \frac{n}{e} \right)^{n} + + Note: + Calculating the logarithm of a negative number or the exponent of a large positive number under Ascend + will have a different range of return values and results different from those under GPU and CPU. + + Args: + log_input (bool, optional): Whether use log input. Default: ``True`` . + full (bool, optional): Whether include the Stirling approximation term in the loss calculation. + Default: ``False`` . + eps (float, optional): Lower bound of `input` when calculating logarithms. Default: ``1e-08`` . + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the mean of elements in the output. + - ``'sum'``: the output elements will be summed. + + Inputs: + - **input** (Tensor) - The input Tensor. The shape can be any number of dimensions. + - **target** (Tensor) - The label Tensor which has the same shape as `input`. + + Outputs: + Tensor or Scalar, if `reduction` is ``'none'``, then output is a tensor and has the same shape as `input`. + Otherwise it is a scalar. + + Raises: + TypeError: If `reduction` is not a str. + TypeError: If neither `input` nor `target` is a tensor. + TypeError: If dtype of `input` or `target` is not currently supported. + + Supported Platforms: + ``Ascend`` ``GPU`` ``CPU`` + + Examples: + >>> import mindspore as ms + >>> import mindspore.nn as nn + >>> x = ms.Tensor([[0.3, 0.7], [0.5, 0.5]]) + >>> target = ms.Tensor([[1.0, 2.0], [3.0, 4.0]]) + >>> loss = nn.PoissonNLLLoss() + >>> output = loss(x, target) + >>> print(output.asnumpy()) + 0.3652635 + """ + + def __init__(self, log_input=True, full=False, eps=1e-08, reduction="mean"): + """Initialize PoissonNLLLoss.""" + # 用super进行父类的初始化 + super(PoissonNLLLoss, self).__init__(reduction=reduction) + # 将log_input赋值给成员变量self.log_input + self.log_input = log_input + # 将full赋值给成员变量self.full + self.full = full + # 将eps赋值给成员变量self.eps + self.eps = eps + # 定义Maximum,用于计算一组数中的最大值 + self.maximum = P.Maximum() + # 定义Cast,用于将输入数据转换为所需的数据类型 + self.cast = P.Cast() + + def construct(self, input, target): + # 检查input的类型是否为tensor + _check_is_tensor('input', input, self.cls_name) + # 检查target的类型是否为tensor + _check_is_tensor('target', target, self.cls_name) + # 如果input的维度为0或target的维度为0 + if input.ndim == 0 or target.ndim == 0: + # 抛出ValueError异常 + raise ValueError( + "For 'PoissonNLLLoss', the inputs must be non-scalar, but got shapes: " + f"input: {input.shape}, target: {target.shape}" + ) + # 将target转换为与input相同的数据类型 + target = self.cast(target, input.dtype) + # 如果self.log_input为true + if self.log_input: + # 用input.exp()计算输入的指数 + loss = input.exp() - target * input + else: + # 用((input + self.eps).log())计算输入的对数 + loss = input - target * ((input + self.eps).log()) + # 如果self.full为true + if self.full: + # 将target的值限制在eps(即一个很小的数)以上,避免计算过程中出现除以零的情况 + target = self.maximum(target, self.eps) + stirling_term = (target > 1) * ((target + 0.5) * target.log() - target + get_half_ln_2_pi()) + # 将stirling_term的值填充到loss中,但仅当target小于等于1时 + loss += F.masked_fill(stirling_term, target <= 1, 0) + # 调用get_loss方法,将loss作为输入参数 + out = self.get_loss(loss) + # 返回计算结果out + return out + + + +@constexpr +# 用于计算半条自然对数的2π +def get_half_ln_2_pi(): + return 0.5 * math.log(2 * math.pi) + +# 用于计算多标签分类问题中的多标签soft margin损失,通常用于处理多分类问题 +class MultiLabelSoftMarginLoss(LossBase): + r""" + Calculates the MultiLabelSoftMarginLoss. + The multi-label soft margin loss is a commonly used loss function in multi-label classification tasks + where an input sample can belong to multiple classes. + Given an input :math:`x` and binary labels :math:`y` of size :math:`(N,C)`, where :math:`N` denotes + the number of samples and :math:`C` denotes the number of classes. + + .. math:: + \mathcal{loss\left( x , y \right)} = - \frac{1}{N}\frac{1}{C}\sum_{i = 1}^{N} + \sum_{j = 1}^{C}\left(y_{ij}\log\frac{1}{1 + e^{- x_{ij}}} + \left( 1 - y_{ij} + \right)\log\frac{e^{-x_{ij}}}{1 + e^{-x_{ij}}} \right) + + where :math:`x_{ij}` represents the predicted score of sample :math:`i` for class :math:`j`. :math:`y_{ij}` + represents the binary label of sample :math:`i` for class :math:`j`, where sample :math:`i` belongs to + class :math:`j` if :math:`y_{ij}=1` , and sample :math:`i` does not belong to class :math:`j` if :math:`y_{ij}=0`. + For a multi-label classification task, each sample may have multiple labels with a value of 1 in the binary + label :math:`y`. `weight` will multiply to the loss of each class if given. + + Args: + weight (Union[Tensor, int, float]): The manual rescaling weight given to each class. Default: ``None`` . + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the weighted mean of elements in the output. + - ``'sum'``: the output elements will be summed. + + Inputs: + - **x** (Tensor) - A tensor of shape :math:`(N, C)`, where N is batch size and C is number + of classes. + - **target** (Tensor) - The label target Tensor which has the same shape as `x`. + + Outputs: + Tensor, the data type is the same as x, if the reduction is ``'none'``, its shape is (N), otherwise it is zero. + + Raises: + ValueError: If the rank of `x` or `target` is not 2. + + Supported Platforms: + ``Ascend`` ``GPU`` ``CPU`` + + Examples: + >>> import mindspore as ms + >>> import mindspore.nn as nn + >>> x = ms.Tensor([[0.3, 0.6, 0.6], [0.9, 0.4, 0.2]]) + >>> target = ms.Tensor([[0.0, 0.0, 1.0], [0.0, 0.0, 1.0]]) + >>> loss = nn.MultiLabelSoftMarginLoss(reduction='mean') + >>> out = loss(x, target) + >>> print(out.asnumpy()) + 0.84693956 + """ + + def __init__(self, weight=None, reduction="mean"): + """Initialize MultiLabelSoftMarginLoss.""" + # 用super进行父类的初始化 + super(MultiLabelSoftMarginLoss, self).__init__(reduction) + # 将weight赋值给成员变量self.weigh + self.weight = weight + # 将reduction赋值给成员变量self.reduction + self.reduction = reduction + + def construct(self, x, target): + # 使用F.multilabel_soft_margin_loss函数计算多标签分类问题中的多标签softmargin损失 + return F.multilabel_soft_margin_loss(x, target, self.weight, self.reduction) + + +# 用于计算多分类问题中的多分类margin损失,通常用于处理二分类或多分类问题 +class MultiMarginLoss(LossBase): + r""" + Creates a criterion that optimizes a multi-class classification hinge + loss (margin-based loss) between input :math:`x` (a 2D mini-batch `Tensor`) and + output :math:`y` (which is a 1D tensor of target class indices, + :math:`0 \leq y \leq \text{x.size}(1)-1`): + + For each mini-batch sample, the loss in terms of the 1D input :math:`x` and scalar + output :math:`y` is: + + .. math:: + \text{loss}(x, y) = \frac{\sum_i \max(0, w[y] * (\text{margin} - x[y] + x[i]))^p}{\text{x.size}(0)} + + where :math:`x \in \left\{0, \; \cdots , \; \text{x.size}(0) - 1\right\}` + and :math:`i \neq y`. + + Args: + p (int, optional): The norm degree for pairwise distance. Should be 1 or 2. Default: ``1`` . + margin (float, optional): A parameter to change pairwise distance. Default: 1.0. + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the weighted mean of elements in the output. + - ``'sum'``: the output elements will be summed. + + weight (Tensor, optional): The rescaling weight to each class with shape :math:`(C,)`. Data type only + support float32, float16 or float64. Default: ``None`` , all classes are weighted equally. + + Inputs: + - **x** (Tensor) - Input x, with shape :math:`(N, C)`. Data type only support float32, float16 or float64. + x is :math:`x` in the above formula. + - **target** (Tensor) - Ground truth labels, with shape :math:`(N,)`. Data type only support int64. The + value of target should be non-negative, less than C. `target` is :math:`y` in the above formula. + + Outputs: + Tensor, When `reduction` is ``'none'``, the shape is :math:`(N,)`. + Otherwise, it is a scalar. Has the same data type with `x`. + + Raises: + TypeError: If dtype of `p` or `target` is not int. + TypeError: If dtype of `margin` is not float. + TypeError: If dtype of `reduction` is not str. + TypeError: If dtype of `x` is not float16, float or float64. + TypeError: If dtype of `weight` and `x` is not the same. + ValueError: If 'p' is not 1 or 2. + ValueError: If 'reduction' is not one of { ``'none'`` , ``'sum'`` , ``'mean'`` }. + ValueError: If shape[0] of `x` is not equal to shape[0] of `target`. + ValueError: If shape[1] of `x` is not equal to shape[0] of `weight`. + ValueError: IF rank of `weight` is not 1. + ValueError: If rank of `x` is not 2 or rank of 'target' is not 1. + + Supported Platforms: + ``Ascend`` ``GPU`` ``CPU`` + + Examples: + >>> import mindspore as ms + >>> import mindspore.nn as nn + >>> import numpy as np + >>> x = ms.Tensor(np.ones(shape=[3, 3]), ms.float32) + >>> target = ms.Tensor(np.array([1, 2, 1]), ms.int64) + >>> loss = nn.MultiMarginLoss() + >>> output = loss(x, target) + >>> print(output) + 0.6666667 + """ + + def __init__(self, p=1, margin=1.0, reduction='mean', weight=None): + """Initialize MultiMarginLoss.""" + # 用super进行父类的初始化 + super(MultiMarginLoss, self).__init__() + # 定义multi_margin_loss,并将其初始化为MultiMarginLossOp对象 + self.multi_margin_loss = MultiMarginLossOp(p=p, margin=margin, reduction=reduction) + # 将weight赋值给成员变量self.weight + self.weight = weight + + def construct(self, x, target, weight=None): + # 检查x的类型是否为tensor + _check_is_tensor('x', x, self.cls_name) + # 检查target的类型是否为tensor + _check_is_tensor('target', target, self.cls_name) + # 如果self.weight为None + if self.weight is not None: + # 将self.weight赋值给weight变量 + weight = self.weight + # 检查weight是否为None,如果weight为None,则执行后续操作 + weight_one = weight is None + # 如果weight_one不为None + if not weight_one: + # 检查weight的类型是否为tensor + _check_is_tensor('weight', weight, self.cls_name) + else: + # 使用F.fill函数填充一个与x的第一个维度相同的张量,并用1填充 + weight = F.fill(x.dtype, x.astype('float32')[0].shape, 1) + # 使用self.multi_margin_loss对象计算多分类问题中的多分类margin损失,并将结果赋值给loss + loss = self.multi_margin_loss(x, target, weight) + # 返回计算后的loss值 + return loss + + +# 用于计算二分类问题中的二分类交叉熵损失,通常用于处理二分类问题 class BCELoss(LossBase): r""" BCELoss creates a criterion to measure the binary cross entropy between the true labels and predicted labels. Set the predicted labels as :math:`x`, true labels as :math:`y`, the output loss as :math:`\ell(x, y)`. - Let, + The formula is as follow: .. math:: L = \{l_1,\dots,l_N\}^\top, \quad @@ -1073,64 +1884,87 @@ class BCELoss(LossBase): Args: weight (Tensor, optional): A rescaling weight applied to the loss of each batch element. - And it must have the same shape and data type as `inputs`. Default: None - reduction (str): Specifies the reduction to be applied to the output. - Its value must be one of 'none', 'mean', 'sum'. Default: 'none'. + And it must have the same shape and data type as `inputs`. Default: ``None`` . + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the weighted mean of elements in the output. + - ``'sum'``: the output elements will be summed. Inputs: - **logits** (Tensor) - The input tensor with shape :math:`(N, *)` where :math:`*` means, any number of additional dimensions. The data type must be float16 or float32. - - **labels** (Tensor) - The label tensor with shape :math:`(N, *)`, the same shape and data type as `logits`. + - **labels** (Tensor) - The label tensor with shape :math:`(N, *)` where :math:`*` means, any number + of additional dimensions. The same shape and data type as `logits`. Outputs: - Tensor, has the same dtype as `logits`. if `reduction` is 'none', then it has the same shape as `logits`. + Tensor, has the same dtype as `logits`. if `reduction` is ``'none'``, then it has the same shape as `logits`. Otherwise, it is a scalar Tensor. Raises: TypeError: If dtype of `logits`, `labels` or `weight` (if given) is neither float16 not float32. - ValueError: If `reduction` is not one of 'none', 'mean', 'sum'. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'``, ``'sum'``. ValueError: If shape of `logits` is not the same as `labels` or `weight` (if given). Supported Platforms: ``Ascend`` ``GPU`` ``CPU`` Examples: - >>> weight = Tensor(np.array([[1.0, 2.0, 3.0], [4.0, 3.3, 2.2]]), mindspore.float32) + >>> import mindspore as ms + >>> import mindspore.nn as nn + >>> import numpy as np + >>> weight = ms.Tensor(np.array([[1.0, 2.0, 3.0], [4.0, 3.3, 2.2]]), ms.float32) >>> loss = nn.BCELoss(weight=weight, reduction='mean') - >>> logits = Tensor(np.array([[0.1, 0.2, 0.3], [0.5, 0.7, 0.9]]), mindspore.float32) - >>> labels = Tensor(np.array([[0, 1, 0], [0, 0, 1]]), mindspore.float32) + >>> logits = ms.Tensor(np.array([[0.1, 0.2, 0.3], [0.5, 0.7, 0.9]]), ms.float32) + >>> labels = ms.Tensor(np.array([[0, 1, 0], [0, 0, 1]]), ms.float32) >>> output = loss(logits, labels) >>> print(output) 1.8952923 """ - def __init__(self, weight=None, reduction='none'): + def __init__(self, weight=None, reduction='mean'): """Initialize BCELoss.""" - super(BCELoss, self).__init__() + # 用super进行父类的初始化 + super(BCELoss, self).__init__(reduction) + # 定义binary_cross_entropy,并将其初始化为P.BinaryCrossEntropy对象 self.binary_cross_entropy = P.BinaryCrossEntropy(reduction=reduction) + # 定义weight_one为None self.weight_one = weight is None + # 如果weight不为None if not self.weight_one: + # 将weight赋值给self.weight变量 self.weight = weight else: + # 定义ones,并将其初始化为P.OnesLike对象 self.ones = P.OnesLike() def construct(self, logits, labels): + # 检查logits的类型是否为tensor _check_is_tensor('logits', logits, self.cls_name) + # 检查labels的类型是否为tensor _check_is_tensor('labels', labels, self.cls_name) + # 如果weight_one不为True if self.weight_one: + # 使用self.ones对象生成一个与logits形状相同的全1张量,并赋给weight weight = self.ones(logits) else: + # 将weight赋值给weight变量 weight = self.weight + # 使用self.binary_cross_entropy对象计算二分类问题中的二分类交叉熵损失,并将结果赋值给loss loss = self.binary_cross_entropy(logits, labels, weight) + # 返回计算结果loss return loss -@constexpr + +@_primexpr def _check_reduced_shape_valid(ori_shape, reduced_shape, axis, cls_name, arg_name1, arg_name2): """Internal function, used to check whether the reduced shape meets the requirements.""" + # 检查输入的形状是否满足指定的条件 validator.check_reduce_shape(ori_shape, reduced_shape, axis, cls_name, arg_name1, arg_name2) - +# 用于计算Cosine嵌入损失,通常用于处理文本分类问题 class CosineEmbeddingLoss(LossBase): r""" CosineEmbeddingLoss creates a criterion to measure the similarity between two tensors using cosine distance. @@ -1140,13 +1974,17 @@ class CosineEmbeddingLoss(LossBase): .. math:: loss(x_1, x_2, y) = \begin{cases} 1-cos(x_1, x_2), & \text{if } y = 1\\ - max(0, cos(x_1, x_2)-margin), & \text{if } y = -1\\ + \max(0, cos(x_1, x_2)-margin), & \text{if } y = -1\\ \end{cases} Args: - margin (float): Should be in [-1.0, 1.0]. Default 0.0. - reduction (str): Specifies which reduction to be applied to the output. It must be one of - "none", "mean", and "sum", meaning no reduction, reduce mean and sum on output, respectively. Default "mean". + margin (float): Should be in [-1.0, 1.0]. Default: ``0.0`` . + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the mean of elements in the output. + - ``'sum'``: the output elements will be summed. Inputs: - **logits_x1** (Tensor) - Tensor of shape :math:`(N, *)` where :math:`*` means, any number @@ -1156,21 +1994,24 @@ class CosineEmbeddingLoss(LossBase): :math:`(x_1, x_2, x_3, ..., x_R)`, then the shape of `labels` must be :math:`(x_1, x_3, x_4, ..., x_R)`. Outputs: - Tensor or Scalar, if `reduction` is "none", its shape is the same as `labels`. + Tensor or Scalar, if `reduction` is ``"none"``, its shape is the same as `labels`. Otherwise, a scalar value will be returned. Raises: TypeError: If `margin` is not a float. - ValueError: If `reduction` is not one of 'none', 'mean', 'sum'. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'``, ``'sum'``. ValueError: If `margin` is not in range [-1, 1]. Supported Platforms: ``Ascend`` ``GPU`` ``CPU`` Examples: - >>> logits_x1 = Tensor(np.array([[0.3, 0.8], [0.4, 0.3]]), mindspore.float32) - >>> logits_x2 = Tensor(np.array([[0.4, 1.2], [-0.4, -0.9]]), mindspore.float32) - >>> labels = Tensor(np.array([1, -1]), mindspore.int32) + >>> import mindspore as ms + >>> import mindspore.nn as nn + >>> import numpy as np + >>> logits_x1 = ms.Tensor(np.array([[0.3, 0.8], [0.4, 0.3]]), ms.float32) + >>> logits_x2 = ms.Tensor(np.array([[0.4, 1.2], [-0.4, -0.9]]), ms.float32) + >>> labels = ms.Tensor(np.array([1, -1]), ms.int32) >>> cosine_embedding_loss = nn.CosineEmbeddingLoss() >>> output = cosine_embedding_loss(logits_x1, logits_x2, labels) >>> print(output) @@ -1179,36 +2020,136 @@ class CosineEmbeddingLoss(LossBase): def __init__(self, margin=0.0, reduction="mean"): """Initialize CosineEmbeddingLoss.""" + # 用super进行父类的初始化 super(CosineEmbeddingLoss, self).__init__(reduction) + # 定义reduce_sum,并将其初始化为P.ReduceSum对象 self.reduce_sum = P.ReduceSum() + # 定义maximum,并将其初始化为P.Maximum对象 self.maximum = P.Maximum() + # 检查margin的类型是否为float型 validator.check_value_type("margin", margin, [float], self.cls_name) - self.margin = validator.check_float_range(margin, -1.0, 1.0, Rel.INC_BOTH, "margin", self.cls_name) + # 检查输入的参数值是否在指定的范围内 + self.margin = validator.check_float_range(margin, -1.0, 1.0, validator.INC_BOTH, "margin", self.cls_name) def construct(self, logits_x1, logits_x2, labels): + # 检查logits_x1的类型是否为tensor _check_is_tensor('logits_x1', logits_x1, self.cls_name) + # 检查logits_2的类型是否为tensor _check_is_tensor('logits_x2', logits_x2, self.cls_name) + # 检查labels的类型是否为tensor _check_is_tensor('labels', labels, self.cls_name) - F.same_type_shape(logits_x1, logits_x2) + inner.same_type_shape_(logits_x1, logits_x2) _check_reduced_shape_valid(F.shape(logits_x1), F.shape(labels), (1,), self.cls_name, "logits_x1", "labels") # if labels > 0, 1-cosine(logits_x1, logits_x2) # else, max(0, cosine(logits_x1, logits_x2)-margin) + # 使用self.reduce_sum对象计算logits_x1和logits_x2的内积,然后将结果沿轴1进行累积 prod_sum = self.reduce_sum(logits_x1 * logits_x2, (1,)) + # 用self.reduce_sum对象计算logits_x1的平方,然后将结果沿轴1进行累积 square1 = self.reduce_sum(F.square(logits_x1), (1,)) + # 用self.reduce_sum对象计算logits_x2的平方,然后将结果沿轴1进行累积 square2 = self.reduce_sum(F.square(logits_x2), (1,)) + # 用F.sqrt对象计算square1和square2的平方根,然后将它们相乘 denom = F.sqrt(square1) * F.sqrt(square2) + # 将prod_sum除以denom,得到两个向量之间的余弦相似度 cosine = prod_sum / denom + # 计算正例的损失值 pos_value = 1.0 - cosine + # 计算负例的损失值 neg_value = self.maximum(cosine - self.margin, 0.0) + # 创建一个形状与cosine相同的全零张量 zeros = F.zeros_like(cosine) + # 使用F.select函数根据labels的值选择pos_value或zeros pos_part = F.select(labels == 1, pos_value, zeros) + # 使用F.select函数根据labels的值选择neg_value或zeros neg_part = F.select(labels == -1, neg_value, zeros) + # 将pos_part和neg_part相加,得到最终的损失值 output_unreduced = pos_part + neg_part - + # 返回损失值 return self.get_loss(output_unreduced) +# 此类定义了一个多标签分类问题中的边缘损失函数,用于多标签分类任务 +class MultilabelMarginLoss(LossBase): + r""" + Creates a loss criterion that minimizes the hinge loss for multi-class + classification tasks. + It takes a 2D mini-batch Tensor :math:`x` as input and a 2D + Tensor :math:`y` containing target class indices as output. + + Each sample in the mini-batch, the loss is computed as follows: + + .. math:: + \text{loss}(x, y) = \sum_{ij}\frac{\max(0, 1 - (x[y[j]] - x[i]))}{\text{x.size}(0)} + + where :math:`x \in \left\{0, \; \cdots , \; \text{x.size}(0) - 1\right\}`, \ + :math:`y \in \left\{0, \; \cdots , \; \text{y.size}(0) - 1\right\}`, \ + :math:`0 \leq y[j] \leq \text{x.size}(0)-1`, \ + and for all :math:`i` and :math:`j`, :math:`i` does not equal to :math:`y[j]`. + + Furthermore, both :math:`y` and :math:`x` should have identical sizes. + + Note: + For this operator, only a contiguous sequence of non-negative targets that starts at + the beginning is taken into consideration, which means that different samples can have different + number of target classes. + + Args: + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the mean of elements in the output. + - ``'sum'``: the output elements will be summed. + + Inputs: + - **x** (Tensor) - Predict data. Tensor of shape :math:`(C)` or :math:`(N, C)`, where :math:`N` + is the batch size and :math:`C` is the number of classes. Data type must be float16 or float32. + - **target** (Tensor) - Ground truth data, with the same shape as `x`, data type must be int32 and + label targets padded by -1. + + Outputs: + - **y** (Union[Tensor, Scalar]) - The loss of MultilabelMarginLoss. If `reduction` is ``"none"``, its shape + is :math:`(N)`. Otherwise, a scalar value will be returned. + + Raises: + TypeError: If `x` or `target` is not a Tensor. + TypeError: If dtype of `x` is neither float16 nor float32. + TypeError: If dtype of `target` is not int32. + ValueError: If length of shape of `x` is neither 1 nor 2. + ValueError: If shape of `x` is not the same as `target`. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'``, ``'sum'``. + + Supported Platforms: + ``Ascend`` ``GPU`` + + Examples: + >>> import mindspore as ms + >>> import mindspore.nn as nn + >>> import numpy as np + >>> loss = nn.MultilabelMarginLoss() + >>> x = ms.Tensor(np.array([[0.1, 0.2, 0.4, 0.8], [0.2, 0.3, 0.5, 0.7]]), ms.float32) + >>> target = ms.Tensor(np.array([[1, 2, 0, 3], [2, 3, -1, 1]]), ms.int32) + >>> output = loss(x, target) + >>> print(output) + 0.325 + """ + + def __init__(self, reduction='mean'): + # 用super进行父类的初始化 + super(MultilabelMarginLoss, self).__init__() + # 将MultilabelMarginLossOp类的实例赋值给self.multilabel_margin_loss成员变量 + # reduction参数被传递给MultilabelMarginLossOp类的构造函数 + self.multilabel_margin_loss = MultilabelMarginLossOp(reduction=reduction) + + def construct(self, x, target): + # 将self.multilabel_margin_loss(x, target)的结果赋值给loss + loss, _ = self.multilabel_margin_loss(x, target) + # 返回计算的loss值 + return loss + + +# 此类定义了二分类问题中的带logits的交叉熵损失函数,用于二分类任务 class BCEWithLogitsLoss(LossBase): r""" Adds sigmoid activation function to input logits, and uses the given logits to compute binary cross entropy @@ -1220,7 +2161,7 @@ class BCEWithLogitsLoss(LossBase): p_{ij} = sigmoid(X_{ij}) = \frac{1}{1 + e^{-X_{ij}}} .. math:: - L_{ij} = -[Y_{ij} \cdot log(p_{ij}) + (1 - Y_{ij}) \cdot log(1 - p_{ij})] + L_{ij} = -[Y_{ij} \cdot \log(p_{ij}) + (1 - Y_{ij}) \cdot \log(1 - p_{ij})] Then, @@ -1232,37 +2173,48 @@ class BCEWithLogitsLoss(LossBase): \end{cases} Args: - reduction (str): Type of reduction to be applied to loss. The optional values are 'mean', 'sum', and 'none'. - If 'none', do not perform reduction. Default:'mean'. + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the weighted mean of elements in the output. + - ``'sum'``: the output elements will be summed. + weight (Tensor, optional): A rescaling weight applied to the loss of each batch element. If not None, it can be broadcast to a tensor with shape of `logits`, - data type must be float16 or float32. Default: None. + data type must be float16 or float32. Default: ``None`` . pos_weight (Tensor, optional): A weight of positive examples. Must be a vector with length equal to the number of classes. If not None, it must be broadcast to a tensor with shape of `logits`, data type - must be float16 or float32. Default: None. + must be float16 or float32. Default: ``None`` . Inputs: - **logits** (Tensor) - Input logits with shape :math:`(N, *)` where :math:`*` means, any number of additional dimensions. The data type must be float16 or float32. - - **labels** (Tensor) - Ground truth label with shape :math:`(N, *)`, same shape and dtype as `logits`. + - **labels** (Tensor) - Ground truth label with shape :math:`(N, *)` where :math:`*` means, any number + of additional dimensions. The same shape and data type as `logits`. Outputs: - Tensor or Scalar, if `reduction` is "none", its shape is the same as `logits`. + Tensor or Scalar, if `reduction` is ``'none'``, its shape is the same as `logits`. Otherwise, a scalar value will be returned. Raises: + TypeError: If input `logits` or `labels` is not Tensor. TypeError: If data type of `logits` or `labels` is neither float16 nor float32. TypeError: If `weight` or `pos_weight` is a parameter. TypeError: If data type of `weight` or `pos_weight` is neither float16 nor float32. + TypeError: If data type of `reduction` is not string. ValueError: If `weight` or `pos_weight` can not be broadcast to a tensor with shape of `logits`. - ValueError: If `reduction` is not one of 'none', 'mean', 'sum'. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'``, ``'sum'``. Supported Platforms: ``Ascend`` ``GPU`` ``CPU`` Examples: - >>> logits = Tensor(np.array([[-0.8, 1.2, 0.7], [-0.1, -0.4, 0.7]]).astype(np.float32)) - >>> labels = Tensor(np.array([[0.3, 0.8, 1.2], [-0.6, 0.1, 2.2]]).astype(np.float32)) + >>> import mindspore as ms + >>> import mindspore.nn as nn + >>> import numpy as np + >>> logits = ms.Tensor(np.array([[-0.8, 1.2, 0.7], [-0.1, -0.4, 0.7]]).astype(np.float32)) + >>> labels = ms.Tensor(np.array([[0.3, 0.8, 1.2], [-0.6, 0.1, 2.2]]).astype(np.float32)) >>> loss = nn.BCEWithLogitsLoss() >>> output = loss(logits, labels) >>> print(output) @@ -1271,87 +2223,71 @@ class BCEWithLogitsLoss(LossBase): def __init__(self, reduction='mean', weight=None, pos_weight=None): """Initialize BCEWithLogitsLoss.""" + # 用super进行父类的初始化 super(BCEWithLogitsLoss, self).__init__() + # 将参数reduction的值赋值给self.reduction成员变量。 self.reduction = reduction - self.bce_with_logits_loss = P.BCEWithLogitsLoss(reduction=reduction) + # 如果weight是一个Parameter类型的对象 if isinstance(weight, Parameter): + # 抛出TypeError异常 raise TypeError(f"For '{self.cls_name}', the 'weight' can not be a Parameter.") + # 如果pos_weight是一个Parameter类型的对象 if isinstance(pos_weight, Parameter): + # 抛出TypeError异常 raise TypeError(f"For '{self.cls_name}', the 'pos_weight' can not be a Parameter.") + # 将参数weight的值赋值给self.weight成员变量 self.weight = weight + # 将参数pos_weight的值赋值给self.pos_weight成员变量 self.pos_weight = pos_weight - self.ones = P.OnesLike() - self.is_cpu = context.get_context("device_target") == "CPU" - - def _construct_cpu(self, logits, labels): - """Use native implementation for CPU.""" - max_val = F.maximum(-logits, 0) - - if self.pos_weight is not None: - log_weight = ((self.pos_weight - 1) * labels) + 1 - loss = (1 - labels) * logits - loss_1 = F.log(F.exp(F.neg_tensor(max_val)) + F.exp(F.neg_tensor(logits) - max_val)) + max_val - loss += log_weight * loss_1 - else: - loss = (1 - labels) * logits - loss += max_val - loss += F.log(F.exp(F.neg_tensor(max_val)) + F.exp(F.neg_tensor(logits) - max_val)) - - if self.weight is not None: - output = loss * self.weight - else: - output = loss - - if self.reduction == "mean": - return F.reduce_mean(output) - if self.reduction == "sum": - return F.reduce_sum(output) - return output def construct(self, logits, labels): + # 检查logits的类型是否为tensor _check_is_tensor('logits', logits, self.cls_name) + # 检查labels的类型是否为tensor _check_is_tensor('labels', labels, self.cls_name) - if self.is_cpu: - return self._construct_cpu(logits, labels) - return self._construct_gpu_ascend(logits, labels) - - def _construct_gpu_ascend(self, logits, labels): - """Use P.BCEWithLogitsLoss for Ascend and GPU.""" - ones_input = self.ones(logits) - if self.weight is not None: - weight = self.weight - else: - weight = ones_input - if self.pos_weight is not None: - pos_weight = self.pos_weight - else: - pos_weight = ones_input - loss = self.bce_with_logits_loss(logits, labels, weight, pos_weight) + # 计算二分类交叉熵损失函数的值,并将结果传给loss + loss = ops.binary_cross_entropy_with_logits(logits, labels, self.weight, self.pos_weight, self.reduction) + # 返回损失值loss return loss -@constexpr + +@_primexpr +# 检查logits_nidm和labels_ndim的维度 def _check_ndim(logits_nidm, labels_ndim, prime_name=None): '''Internal function, used to check whether the dimension of logits and labels meets the requirements.''' + # 检查prime_name是否为None,如果是,则将msg_prefix设置为"The",否则设置为f"For '{prime_name}', the" msg_prefix = f'For \'{prime_name}\', the' if prime_name else "The" + # 如果logits_nidm的维度小于2或大于4 if logits_nidm < 2 or logits_nidm > 4: - raise ValueError(f"{msg_prefix} dimensions of 'logits' should be in [2, 4], but got " + # 抛出ValueError异常 + raise ValueError(f"{msg_prefix} dimensions of 'logits' must be in [2, 4], but got " f"dimension of 'logits' {logits_nidm}.") + # 如果labels_ndim的维度小于2或大于4 if labels_ndim < 2 or labels_ndim > 4: - raise ValueError(f"{msg_prefix} dimensions of 'labels' should be in [2, 4], but got " + # 抛出ValueError异常 + raise ValueError(f"{msg_prefix} dimensions of 'labels' must be in [2, 4], but got " f"dimension of 'labels' {labels_ndim}.") + # 如果logits_nidm和labels_ndim的维度不相等 if logits_nidm != labels_ndim: + # 抛出ValueError异常 raise ValueError(f"{msg_prefix} dimensions of 'logits' and 'labels' must be equal, but got " f"dimension of 'logits' {logits_nidm} and dimension of 'labels' {labels_ndim}.") -@constexpr +@_primexpr +# 检查logits和labels的形状 def _check_channel_and_shape(logits, labels, prime_name=None): '''Internal function, used to check whether the channels or shape of logits and labels meets the requirements.''' + # 检查prime_name是否为None,如果是,则将msg_prefix设置为"The",否则设置为f"For '{prime_name}', the" msg_prefix = f'For \'{prime_name}\', the' if prime_name else "The" + # 如果logits为1 if logits == 1: + # 抛出ValueError异常 raise ValueError(f"{msg_prefix} 'logits'.shape[1] cannot be one, but got {logits}.") + # 如果labels不在(1, logits)中 if labels not in (1, logits): + # 抛出ValueError异常 raise ValueError(f"{msg_prefix} 'labels'.shape[1] must be one or equal to 'logits'.shape[1]: {logits}, " f"but got {labels}.") @@ -1359,26 +2295,35 @@ def _check_channel_and_shape(logits, labels, prime_name=None): @constexpr def _check_input_dtype(labels_dtype, cls_name): """Internal function, used to check whether the data type of labels meets the requirements.""" + # 检查labels的类型是否为int32或int64或float16或float32 validator.check_type_name("labels", labels_dtype, [mstype.int32, mstype.int64, mstype.float16, mstype.float32], cls_name) - +# 用于实现focalloss损失函数,它是深度学习中一种用于解决样本不均衡问题的损失函数 +# 在二分类问题中,当正例和负例的样本数量不均衡时,普通的crossentropy损失函数可能会导致模型学习到偏差的类别权重,从而影响模型的泛化性能 +# 而focalloss损失函数通过引入一个带有指数衰减的权重来解决这一问题,使得模型更加关注困难样本的学习,从而提高模型的泛化性能 class FocalLoss(LossBase): r""" - The loss function proposed by Kaiming team in their paper ``Focal Loss for Dense Object Detection`` improves the - effect of image object detection. It is a loss function to solve the imbalance of categories and the difference of - classification difficulty. If you want to learn more, please refer to the paper. - `Focal Loss for Dense Object Detection `_. The function is shown as follows: + It is a loss function to solve the imbalance of categories and the difference of + classification difficulty. + The loss function proposed by Kaiming team in their paper + `Focal Loss for Dense Object Detection `_ improves the + effect of image object detection. + The function is shown as follows: .. math:: - FL(p_t) = -(1-p_t)^\gamma log(p_t) + FL(p_t) = -(1-p_t)^\gamma \log(p_t) Args: - gamma (float): Gamma is used to adjust the steepness of weight curve in focal loss. Default: 2.0. + gamma (float): Gamma is used to adjust the steepness of weight curve in focal loss. Default: ``2.0`` . weight (Union[Tensor, None]): A rescaling weight applied to the loss of each batch element. The dimension of - weight should be 1. If None, no weight is applied. Default: None. - reduction (str): Type of reduction to be applied to loss. The optional values are "mean", "sum", and "none". - If "none", do not perform reduction. Default: "mean". + weight should be 1. If None, no weight is applied. Default: ``None`` . + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the weighted mean of elements in the output. + - ``'sum'``: the output elements will be summed. Inputs: - **logits** (Tensor) - Tensor of shape should be :math:`(N, C)` or :math:`(N, C, H)` or :math:`(N, C, H, W)`. @@ -1392,7 +2337,7 @@ class FocalLoss(LossBase): range [-:math:`C`, :math:`C`). Where :math:`C` is the number of classes in logits. Outputs: - Tensor or Scalar, if `reduction` is "none", its shape is the same as `logits`. + Tensor or Scalar, if `reduction` is ``"none"``, its shape is the same as `logits`. Otherwise, a scalar value will be returned. Raises: @@ -1400,14 +2345,16 @@ class FocalLoss(LossBase): TypeError: If `weight` is not a Tensor. ValueError: If `labels` dim is different from `logits`. ValueError: If `labels` channel is not 1 and `labels` shape is different from `logits`. - ValueError: If `reduction` is not one of 'none', 'mean', 'sum'. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'``, ``'sum'``. Supported Platforms: ``Ascend`` - Example: - >>> logits = Tensor([[0.8, 1.4], [0.5, 0.9], [1.2, 0.9]], mstype.float32) - >>> labels = Tensor([[1], [1], [0]], mstype.int32) + Examples: + >>> import mindspore as ms + >>> import mindspore.nn as nn + >>> logits = ms.Tensor([[0.8, 1.4], [0.5, 0.9], [1.2, 0.9]], ms.float32) + >>> labels = ms.Tensor([[1], [1], [0]], ms.int32) >>> focalloss = nn.FocalLoss(weight=Tensor([1, 2]), gamma=2.0, reduction='mean') >>> output = focalloss(logits, labels) >>> print(output) @@ -1416,15 +2363,24 @@ class FocalLoss(LossBase): def __init__(self, weight=None, gamma=2.0, reduction='mean'): """Initialize FocalLoss.""" + # 用super进行父类的初始化 super(FocalLoss, self).__init__(reduction=reduction) + + # 初始化函数,参数gamma,weight,input_mask self.gamma = validator.check_value_type("gamma", gamma, [float]) + # 如果weight不是None并且类型不是tenser if weight is not None and not isinstance(weight, Tensor): - raise TypeError(f"For '{self.cls_name}', the type of 'weight' should be a Tensor, " + # 抛出TypeError异常 + raise TypeError(f"For '{self.cls_name}', the type of 'weight' must be a Tensor, " f"but got {type(weight).__name__}.") + # 如果weight的类型为tensor并且维度不是1 if isinstance(weight, Tensor) and weight.ndim != 1: - raise ValueError(f"For '{self.cls_name}', the dimension of 'weight' should be 1, but got {weight.ndim}.") + # 抛出ValueError异常 + raise ValueError(f"For '{self.cls_name}', the dimension of 'weight' must be 1, but got {weight.ndim}.") + # 将weight赋值给self.weight self.weight = weight + # 初始化函数,参数gamma,weight,input_mask self.expand_dims = P.ExpandDims() self.gather_d = P.GatherD() self.squeeze = P.Squeeze(axis=1) @@ -1434,40 +2390,962 @@ class FocalLoss(LossBase): self.logsoftmax = nn.LogSoftmax(1) def construct(self, logits, labels): + # 检查logits的类型是否为tensor _check_is_tensor('logits', logits, self.cls_name) + # 检查labels的类型是否为tensor _check_is_tensor('labels', labels, self.cls_name) + # 将labels赋值给labelss labelss = labels + # 检查logits和labels的维度 _check_ndim(logits.ndim, labelss.ndim, self.cls_name) + # 检查logits和labelss的通道数和形状 _check_channel_and_shape(logits.shape[1], labelss.shape[1], self.cls_name) + # 检查labelss的数据类型 _check_input_dtype(self.dtype(labelss), self.cls_name) - + # 如果logits的维度大于2 if logits.ndim > 2: + # 将logits的形状转换为(batch_size, num_classes, -1) logits = logits.view(logits.shape[0], logits.shape[1], -1) + # 将labelss的形状转换为(batch_size, num_classes, -1) labelss = labelss.view(labelss.shape[0], labelss.shape[1], -1) else: + # 将logits的形状扩展为(batch_size, num_classes, 1) logits = self.expand_dims(logits, 2) + # 将labelss的形状扩展为(batch_size, num_classes, 1) labelss = self.expand_dims(labelss, 2) - + # 计算softmax概率分布,并将结果存储在log_probability变量中 log_probability = self.logsoftmax(logits) + # 如果labels.shape[1]为1 if labels.shape[1] == 1: + # 用self.gather_d()方法从log_probability中根据索引labelss的第二个维度(即axis=1)的值进行 Gather 操作 log_probability = self.gather_d(log_probability, 1, self.cast(labelss, mindspore.int32)) + # 用self.squeeze()方法从log_probability中移除大小为1的维度 log_probability = self.squeeze(log_probability) + # 用F.exp()函数将log_probability的元素逐个进行指数运算,并将结果存储在probability变量中 probability = F.exp(log_probability) + # 如果weight为None if self.weight is not None: + # 创建一个空的张量convert_weight,将其形状设置为(1, weight.shape[0], 1) convert_weight = self.weight[None, :, None] + # 用self.tile()函数将convert_weight沿第一个维度扩展到与labelss的形状相同 convert_weight = self.tile(convert_weight, (labelss.shape[0], 1, labelss.shape[2])) + # 如果labels.shape[1]等于1 if labels.shape[1] == 1: + # 用self.gather_d()函数从convert_weight中根据labelss的第二个维度(即axis=1)的值进行 Gather 操作 convert_weight = self.gather_d(convert_weight, 1, self.cast(labelss, mindspore.int32)) + # 用self.squeeze()函数从结果中移除大小为1的维度 convert_weight = self.squeeze(convert_weight) + # 将log_probability乘以convert_weight log_probability = log_probability * convert_weight - + # 计算weight weight = F.pows(-1 * probability + 1.0, self.gamma) + # 如果labels.shape[1]等于1 if labels.shape[1] == 1: + # 用self.mean()函数计算-1 * weight * log_probability沿第一个维度(即axis=1)的平均值 loss = (-1 * weight * log_probability).mean(axis=1) else: + # 用self.mean()函数计算-1 * weight * labelss * log_probability沿最后一个维度(即axis=-1)的平均值 loss = (-1 * weight * labelss * log_probability).mean(axis=-1) - + # 用self.get_loss()函数将计算出的损失值转换为loss return self.get_loss(loss) + + +# 用于实现Huber损失函数,主要用于回归任务中 +# Huber损失函数是一种比均方误差(MSE)更适合处理连续值输出的损失函数,它可以更好地平衡高斯噪声和离散值 +class HuberLoss(LossBase): + r""" + HuberLoss calculate the error between the predicted value and the target value. + It has the advantages of both L1Loss and MSELoss. + + Assuming that the :math:`x` and :math:`y` are 1-D Tensor, length :math:`N`, then calculate the loss of :math:`x` and + :math:`y` without dimensionality reduction (the reduction parameter is set to "none"). The formula is as follows: + + .. math:: + \ell(x, y) = L = \{l_1,\dots,l_N\}^\top + + with + + .. math:: + l_n = \begin{cases} + 0.5 * (x_n - y_n)^2, & \text{if } |x_n - y_n| < delta; \\ + delta * (|x_n - y_n| - 0.5 * delta), & \text{otherwise. } + \end{cases} + + where :math:`N` is the batch size. If `reduction` is not ``"none"``, then: + + .. math:: + \ell(x, y) = + \begin{cases} + \operatorname{mean}(L), & \text{if reduction} = \text{"mean";}\\ + \operatorname{sum}(L), & \text{if reduction} = \text{"sum".} + \end{cases} + + Args: + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the mean of elements in the output. + - ``'sum'``: the output elements will be summed. + + delta (Union[int, float]): The threshold to change between two type of loss. + The value must be positive. Default: ``1.0`` . + + Inputs: + - **logits** (Tensor) - Predicted value, Tensor of any dimension. The data type must be float16 or float32. + - **labels** (Tensor) - Target value, same dtype and shape as the `logits` in common cases. + However, it supports the shape of `logits` is different from the shape of `labels` + and they should be broadcasted to each other. + + Outputs: + Tensor or Scalar, if `reduction` is ``"none"``, return a Tensor with same shape and dtype as `logits`. + Otherwise, a scalar value will be returned. + + Raises: + TypeError: If data type of `logits` or `labels` is neither float16 nor float32. + TypeError: If data type of `logits` or `labels` are not the same. + TypeError: If dtype of `delta` is neither float nor int. + ValueError: If `delta` is less than or equal to 0. + ValueError: If `reduction` is not one of ``"none"``, ``"mean"``, ``"sum"``. + ValueError: If `logits` and `labels` have different shapes and cannot be broadcasted to each other. + + Supported Platforms: + ``Ascend`` ``GPU`` ``CPU`` + + Examples: + >>> import mindspore as ms + >>> import mindspore.nn as nn + >>> import numpy as np + >>> # Case 1: logits.shape = labels.shape = (3,) + >>> loss = nn.HuberLoss() + >>> logits = ms.Tensor(np.array([1, 2, 3]), ms.float32) + >>> labels = ms.Tensor(np.array([1, 2, 2]), ms.float32) + >>> output = loss(logits, labels) + >>> print(output) + 0.16666667 + >>> # Case 2: logits.shape = (3,), labels.shape = (2, 3) + >>> loss = nn.HuberLoss(reduction="none") + >>> logits = ms.Tensor(np.array([1, 2, 3]), ms.float32) + >>> labels = ms.Tensor(np.array([[1, 1, 1], [1, 2, 2]]), ms.float32) + >>> output = loss(logits, labels) + >>> print(output) + [[0. 0.5 1.5] + [0. 0. 0.5]] + """ + + def __init__(self, reduction="mean", delta=1.0): + """Initialize HuberLoss.""" + # 用super进行父类的初始化 + super(HuberLoss, self).__init__(reduction=reduction) + # 设置损失函数的缩放方式 + self.reduction = reduction + # 设置损失函数的平滑区域 + self.delta = delta + + def construct(self, logits, labels): + # 计算huberloss并返回 + return F.huber_loss(logits, labels, self.reduction, self.delta) + + +# 用于实现三元组损失函数,主要用于人脸识别、图像匹配等任务 +class TripletMarginLoss(LossBase): + r""" + TripletMarginLoss operation. + + Triple loss is used to measure the relative similarity between samples, + which is measured by a triplet and a :math:`margin` with a value greater than :math:`0` . + The triplet is composed by :math:`a`, :math:`p`, :math:`n` in the following formula. + + The shapes of all input tensors should be :math:`(N, *)` , where :math:`N` is batch size + and :math:`*` means any number of additional dimensions. + + The distance swap is described in detail in the paper + `Learning local feature descriptors with triplets and shallow convolutional neural + networks `_ + by V. Balntas, E. Riba et al. + + The loss function for each sample in the mini-batch is: + + .. math:: + L(a, p, n) = \max \{d(a_i, p_i) - d(a_i, n_i) + {\rm margin}, 0\} + + where + + .. math:: + d(x_i, y_i) = \left\lVert {\bf x}_i - {\bf y}_i \right\rVert_p + + Args: + p (int, optional): The degree of norm for pairwise distance. Default: ``2`` . + eps (float, optional): Add small value to avoid division by zero. Default: ``1e-06`` . + swap (bool, optional): The distance swap change the negative distance to the distance between positive + sample and negative sample. Default: ``False`` . + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the mean of elements in the output. + - ``'sum'``: the output elements will be summed. + + margin (Union[Tensor, float]) - Make a margin between the positive pair and the negative pair. + Default: ``1.0`` . + + Inputs: + - **x** (Tensor) - A sample randomly selected from the training set. Data type must be BasicType. + :math:`a` in the above formula. + - **positive** (Tensor) - A sample belonging to the same category as `x`, with the same type and + shape as `x`. :math:`p` in the above formula. + - **negative** (Tensor) - A sample belonging to the different class from `x`, with the same type and shape + as `x`. :math:`n` in the above formula. + - **margin** (Union[Tensor, float]) - Make a margin between the positive pair and the negative pair. + Default: ``1.0`` . + + Outputs: + Tensor. If `reduction` is ``"none"``, its shape is :math:`(N)`. Otherwise, a scalar value will be returned. + + Raises: + TypeError: If `x` or `positive` or 'negative' is not a Tensor. + TypeError: If dtype of `x`, `positive` and `negative` is not the same. + TypeError: If `p` is not an int. + TypeError: If `eps` is not a float. + TypeError: If `swap` is not a bool. + ValueError: If dimensions of input `x`, `positive` and `negative` are less than or equal to 1 at the same time. + ValueError: If the dimension of input `x` or `positive` or `negative` is bigger than or equal to 8. + ValueError: If length of shape of `margin` is not 0. + ValueError: If shape of `x`, `positive` and `negative` cannot broadcast. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'``, ``'sum'``. + + Supported Platforms: + ``GPU`` + + Examples: + >>> import mindspore as ms + >>> import mindspore.nn as nn + >>> import numpy as np + >>> loss = nn.TripletMarginLoss() + >>> x = ms.Tensor(np.array([[0.3, 0.7], [0.5, 0.5]]), ms.float32) + >>> positive = ms.Tensor(np.array([[0.4, 0.6], [0.4, 0.6]]), ms.float32) + >>> negative = ms.Tensor(np.array([[0.2, 0.9], [0.3, 0.7]]), ms.float32) + >>> output = loss(x, positive, negative) + >>> print(output) + 0.8881968 + """ + + def __init__(self, p=2, swap=False, eps=1e-06, reduction="mean", margin=1.): + # 用super进行父类的初始化 + super(TripletMarginLoss, self).__init__() + # 初始化参数 + self.p = p + self.swap = swap + self.eps = eps + self.reduction = reduction + self.margin = margin + + def construct(self, x, positive, negative, margin=1.): + # 如果self.margin不为1.0 + if self.margin != 1.0: + # 将self.margin的值赋给变量margin + margin = self.margin + # 调用F.triplet_margin_loss()函数,计算三元组损失函数的值,并将结果返回 + return F.triplet_margin_loss(x, positive, negative, margin=margin, p=self.p, + eps=self.eps, swap=self.swap, reduction=self.reduction) + + + +@constexpr +def _check_nll_loss_inputs(logits_shape, label_shape, logits_dtype, label_dtype, prim_name=None): + """Internal function, used to check whether the shape of logits and labels meets the requirements.""" + # 检查logits的类型是否为float16或float32 + validator.check_type_name('logits', logits_dtype, [mstype.float16, mstype.float32], prim_name) + # 检查labels的类型是否为int32 + validator.check_type_name('labels', label_dtype, [mstype.int32], prim_name) + + # 将logits_shape的第一个元素(即batch_size)提取出来,并将剩余的元素按顺序组合成一个新的元组logits_shape_new + logits_shape_new = (logits_shape[0], *logits_shape[2:]) + # 如果prim_name不为空,则将prim_name插入到msg_prefix字符串中,表示当前正在检查的损失函数的名称 + # 如果prim_name为空,则不插入任何内容 + msg_prefix = f'For \'{prim_name}\', the' if prim_name else "The" + # 如果logits_shape_new和label_shape形状不相等 + if logits_shape_new != label_shape: + # 抛出ValueError异常 + raise ValueError(f"{msg_prefix} shape of 'logits' should be (N, C, d_0, d_1, ...), " + f"and the shape of 'labels' should be (N, d_0, d_1, ...), " + f"but get 'logits' shape: {logits_shape} and 'labels' shape: {label_shape}") + +# 用于实现负对数似然损失函数,主要用于分类问题 +class NLLLoss(LossBase): + r""" + Gets the negative log likelihood loss between logits and labels. + + The nll loss with :math:`reduction = none` can be described as: + + .. math:: + \ell(x, t)=L=\left\{l_{1}, \ldots, l_{N}\right\}^{\top}, + \quad l_{n}=-w_{t_{n}} x_{n, t_{n}}, + \quad w_{c}=\text { weight }[c] \cdot \mathbb{1}\{c \not= \text{ignore_index}\} + + where :math:`x` is the logits, :math:`t` is the labels, :math:`w` is the weight, + :math:`N` is the batch size, :math:`c` belonging to :math:`[0, C-1]` is class index, + where :math:`C` is the number of classes. + + If `reduction` is not ``'none'`` (default 'mean'), then + + .. math:: + + \ell(x, t)=\left\{\begin{array}{ll} + \sum_{n=1}^{N} \frac{1}{\sum_{n=1}^{N} w_{t n}} l_{n}, & \text { if reduction }=\text { 'mean', } \\ + \sum_{n=1}^{N} l_{n}, & \text { if reduction }=\text { 'sum' } + \end{array}\right. + + Args: + weight (Tensor): The rescaling weight to each class. If the value is not None, the shape is :math:`(C,)`. + The data type only supports float32 or float16. Default: ``None`` . + ignore_index (int): Specifies a target value that is ignored (typically for padding value) + and does not contribute to the gradient. Default: ``-100`` . + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the weighted mean of elements in the output. + - ``'sum'``: the output elements will be summed. + + Inputs: + - **logits** (Tensor) - Tensor of shape :math:`(N, C)` + or :math:`(N, C, d_1, d_2, ..., d_K)` for :math:`K`-dimensional data, where `C = number of classes`. + Data type must be float16 or float32. `inputs` needs to be logarithmic probability. + - **labels** (Tensor) -:math:`(N)` or :math:`(N, d_1, d_2, ..., d_K)` for :math:`K`-dimensional data. + Data type must be int32. + + Returns: + Tensor, the computed negative log likelihood loss value. + + Raises: + TypeError: If `weight` is not a Tensor. + TypeError: If `ignore_index` is not an int. + TypeError: If the data type of `weight` is not float16 or float32. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'``, ``'sum'``. + TypeError: If `logits` is not a Tensor. + TypeError: If `labels` is not a Tensor. + + Supported Platforms: + ``Ascend`` ``GPU`` ``CPU`` + + Examples: + >>> import mindspore as ms + >>> import mindspore.nn as nn + >>> import numpy as np + >>> logits = ms.Tensor(np.random.randn(3, 5), ms.float32) + >>> labels = ms.Tensor(np.array([1, 0, 4]), ms.int32) + >>> loss = nn.NLLLoss() + >>> output = loss(logits, labels) + """ + + def __init__(self, weight=None, ignore_index=-100, reduction='mean'): + # 用super进行父类的初始化 + super().__init__(reduction) + # 检查ignore_index的类型是否为int + validator.check_value_type('ignore_index', ignore_index, int, self.cls_name) + # 如果weight不为None + if weight is not None: + # 检查weight的类型是否为tensor + validator.check_value_type("weight", weight, [Tensor], self.cls_name) + # 检查weight的类型是否为float16或float32 + validator.check_type_name('weight', weight.dtype, [mstype.float16, mstype.float32], self.cls_name) + + self.weight = weight + self.ignore_index = ignore_index + self.reduction = reduction + + def construct(self, logits, labels): + # 返回loss + return F.nll_loss(logits, labels, self.weight, self.ignore_index, self.reduction) + + + +@constexpr +def _check_cross_entropy_inputs(logits_shape, label_shape, + logits_rank, label_rank, + logits_dtype, label_dtype, + prim_name=None): + """Internal function, used to check whether the shape of logits and labels meets the requirements.""" + # 检查logits的类型是否为float16或float32 + validator.check_type_name('logits', logits_dtype, [mstype.float16, mstype.float32], prim_name) + # 如果prim_name不为空,则将prim_name插入到msg_prefix字符串中,表示当前正在检查的损失函数的名称 + # 如果prim_name为空,则不插入任何内容 + msg_prefix = f'For \'{prim_name}\', the' if prim_name else "The" + # 如果logits和labels的维度数量相同 + if logits_rank == label_rank: + # 检查labels的类型是否是float16或float32 + validator.check_type_name('labels', label_dtype, [mstype.float16, mstype.float32], prim_name) + # 如果logits和labels的维度数量不同 + if logits_shape != label_shape: + # 抛出ValueError异常 + raise ValueError(f"{msg_prefix} shape of 'logits' should be (N, C, d_0, d_1, ...), " + f"and the shape of 'labels' should be (N, C, d_0, d_1, ...), " + f"but get 'logits' shape: {logits_shape} and 'labels' shape: {label_shape}.") + # 如果label_rank等于logits_rank - 1 + elif label_rank == logits_rank - 1: + # 检查labels的类型是否为int32 + validator.check_type_name('labels', label_dtype, [mstype.int32], prim_name) + # 如果logits的维度数量不为1 + if logits_rank != 1: + # 创建一个新形状,其中logits_shape[0]保持不变 + logits_shape_new = (logits_shape[0], *logits_shape[2:]) + # 如果logits_shape_new和label_shape形状不同 + if logits_shape_new != label_shape: + # 抛出ValueError异常 + raise ValueError(f"{msg_prefix} shape of 'logits' should be (N, C, d_0, d_1, ...), " + f"and the shape of 'labels' should be (N, d_0, d_1, ...), " + f"but get 'logits' shape: {logits_shape} and 'labels' shape: {label_shape}.") + else: + # 抛出ValueError异常 + raise ValueError(f"{msg_prefix} rank of 'logits' and 'labels' should be:\n" + f"1. 'logits.ndim == labels.ndim' for probabilities, \n" + f"2. 'logits.ndim - 1 == labels.ndim' for class indices, \n" + f"but get 'logits' rank: {logits_rank} and 'labels' rank: {label_rank}.") + + +@constexpr +def _cross_entropy_ignore_index_warning(prim_name): + """Internal function, used to warning when ignore_index > 0 for probabilities.""" + # 警告 + log.warning(f"For \'{prim_name}\', 'ignore_index' does not work when 'labels' is Probability.") + +# 用于实现交叉熵损失函数,可以用于分类问题 +class CrossEntropyLoss(LossBase): + r""" + The cross entropy loss between input and target. + + The CrossEntropyLoss support two kind of targets: + + - Class indices (int) in the range :math:`[0, C)` where :math:`C` is the number of classes, + the loss with reduction=none can be described as: + + .. math:: + + \ell(x, y) = L = \{l_1,\dots,l_N\}^\top, \quad + l_n = - w_{y_n} \log \frac{\exp(x_{n,y_n})}{\sum_{c=1}^C \exp(x_{n,c})} + \cdot \mathbb{1}\{y_n \not= \text{ignore_index}\} + + where :math:`x` is the inputs, :math:`t` is the target, :math:`w` is the weight, + N is the batch size, :math:`c` belonging to [0, C-1] is class index, where :math:`C` is the number of classes. + + If reduction is not ``'none'`` (default 'mean'), then + + .. math:: + + \ell(x, y) = \begin{cases} + \sum_{n=1}^N \frac{1}{\sum_{n=1}^N w_{y_n} \cdot \mathbb{1}\{y_n \not= \text{ignore_index}\}} l_n, & + \text{if reduction} = \text{'mean',}\\ + \sum_{n=1}^N l_n, & + \text{if reduction} = \text{'sum'.} + \end{cases} + + - Probabilities (float) for each class, useful when labels beyond a single class per minibatch item + are required, the loss with reduction=none can be described as: + + .. math:: + + \ell(x, y) = L = \{l_1,\dots,l_N\}^\top, \quad + l_n = - \sum_{c=1}^C w_c \log \frac{\exp(x_{n,c})}{\sum_{i=1}^C \exp(x_{n,i})} y_{n,c} + + where :math:`x` is the inputs, :math:`t` is the target, :math:`w` is the weight, + N is the batch size, :math:`c` belonging to [0, C-1] is class index, where :math:`C` is the number of classes. + + If reduction is not ``'none'`` (default 'mean'), then + + .. math:: + + \ell(x, y) = \begin{cases} + \frac{\sum_{n=1}^N l_n}{N}, & + \text{if reduction} = \text{'mean',}\\ + \sum_{n=1}^N l_n, & + \text{if reduction} = \text{'sum'.} + \end{cases} + + Args: + weight (Tensor): The rescaling weight to each class. If the value is not None, the shape is :math:`(C,)`. + The data type only supports float32 or float16. Default: ``None`` . + ignore_index (int): Specifies a target value that is ignored (typically for padding value) + and does not contribute to the gradient. Default: ``-100`` . + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the weighted mean of elements in the output. + - ``'sum'``: the output elements will be summed. + + label_smoothing (float): Label smoothing values, a regularization tool used to prevent the model + from overfitting when calculating Loss. The value range is [0.0, 1.0]. Default value: ``0.0`` . + + Inputs: + - **logits** (Tensor) - Tensor of shape :math:`(C,)` :math:`(N, C)` or :math:`(N, C, d_1, d_2, ..., d_K)`, + where `C = number of classes`. Data type must be float16 or float32. + - **labels** (Tensor) - For class indices, tensor of shape :math:`()`, :math:`(N)` or + :math:`(N, d_1, d_2, ..., d_K)` , data type must be int32. + For probabilities, tensor of shape :math:`(C,)` :math:`(N, C)` or :math:`(N, C, d_1, d_2, ..., d_K)` , + data type must be float16 or float32. + + Returns: + Tensor, the computed cross entropy loss value. + + Raises: + TypeError: If `weight` is not a Tensor. + TypeError: If `ignore_index` is not an int. + TypeError: If the data type of `weight` is not float16 or float32. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'``, ``'sum'``. + TypeError: If `label_smoothing` is not a float. + TypeError: If `logits` is not a Tensor. + TypeError: If `labels` is not a Tensor. + + Supported Platforms: + ``Ascend`` ``GPU`` ``CPU`` + + Examples: + >>> import mindspore as ms + >>> import mindspore.nn as nn + >>> import numpy as np + >>> # Case 1: Indices labels + >>> inputs = ms.Tensor(np.random.randn(3, 5), ms.float32) + >>> target = ms.Tensor(np.array([1, 0, 4]), ms.int32) + >>> loss = nn.CrossEntropyLoss() + >>> output = loss(inputs, target) + >>> # Case 2: Probability labels + >>> inputs = ms.Tensor(np.random.randn(3, 5), ms.float32) + >>> target = ms.Tensor(np.random.randn(3, 5), ms.float32) + >>> loss = nn.CrossEntropyLoss() + >>> output = loss(inputs, target) + """ + + def __init__(self, weight=None, ignore_index=-100, reduction='mean', + label_smoothing=0.0): + # 用super进行父类的初始化 + super().__init__(reduction) + # 检查ignore_index的类型是否为int + validator.check_value_type('ignore_index', ignore_index, int, self.cls_name) + # 检查label_smoothing的类型是否为float + validator.check_value_type('label_smoothing', label_smoothing, float, self.cls_name) + # 检查label_smoothing参数是否在规定的0.0到1.0的范围之内 + validator.check_float_range(label_smoothing, 0.0, 1.0, validator.INC_BOTH, 'label_smoothing', self.cls_name) + + # 如果weight为None + if weight is not None: + # 检查weight的类型是否为tensor + validator.check_value_type("weight", weight, [Tensor], self.cls_name) + # 检查weight的类型是否为float16或float32 + validator.check_type_name('weight', weight.dtype, [mstype.float16, mstype.float32], self.cls_name) + + + self.weight = weight + self.ignore_index = ignore_index + self.reduction = reduction + self.label_smoothing = label_smoothing + + def construct(self, logits, labels): + _check_is_tensor('logits', logits, self.cls_name) + _check_is_tensor('labels', labels, self.cls_name) + _check_cross_entropy_inputs(logits.shape, labels.shape, + logits.ndim, labels.ndim, + logits.dtype, labels.dtype, + self.cls_name) + # 如果logits和labels的维度相同,并且ignore_index的值大于0 + if logits.ndim == labels.ndim and self.ignore_index > 0: + # 发出一个警告,提示用户在交叉熵损失中使用了忽略索引 + _cross_entropy_ignore_index_warning(self.cls_name) + # 调用F.cross_entropy()函数计算交叉熵损失,并返回计算值 + return F.cross_entropy(logits, labels, self.weight, self.ignore_index, self.reduction, self.label_smoothing) + + +# 用于计算Kullback-Leibler散度损失,通常用于生成对抗性攻击和生成对抗性网络(GAN)的损失函数 +class KLDivLoss(LossBase): + r""" + Computes the Kullback-Leibler divergence between the `logits` and the `labels`. + + For tensors of the same shape :math:`x` and :math:`target`, + the updating formulas of KLDivLoss algorithm are as follows, + + .. math:: + L(x, target) = target \cdot (\log target - x) + + Then, + + .. math:: + \ell(x, target) = \begin{cases} + L(x, target), & \text{if reduction} = \text{'none';}\\ + \operatorname{mean}(L(x, target)), & \text{if reduction} = \text{'mean';}\\ + \operatorname{sum}(L(x, target)) / x.\operatorname{shape}[0], & \text{if reduction} = \text{'batchmean';}\\ + \operatorname{sum}(L(x, target)), & \text{if reduction} = \text{'sum'.} + \end{cases} + + where :math:`x` represents `logits`, + :math:`target` represents `labels`, and + :math:`\ell(x, target)` represents `output`. + + Note: + - Currently it does not support float64 input on `Ascend`. + - The output aligns with the mathematical definition of Kullback-Leibler divergence + only when `reduction` is set to 'batchmean'. + + Args: + reduction (str): Specifies the reduction to be applied to the output. Default: ``'mean'`` . + + - On Ascend, the value of `reduction` must be one of ``'batchmean'`` , ``'none'`` or ``'sum'`` . + - On GPU, the value of `reduction` must be one of ``'mean'`` , ``'none'`` or ``'sum'`` . + - On CPU, the value of `reduction` must be one of ``'mean'`` , ``'batchmean'`` , ``'none'`` or ``'sum'`` . + + Inputs: + - **logits** (Tensor) - The input Tensor. The data type must be float16, float32 or float64. + - **labels** (Tensor) - The label Tensor which has the same shape and data type as `logits`. + + Outputs: + Tensor or Scalar, if `reduction` is ``'none'``, then output is a tensor and has the same shape as `logits`. + Otherwise, it is a scalar. + + Raises: + TypeError: If `reduction` is not a str. + TypeError: If neither `logits` nor `labels` is a Tensor. + TypeError: If dtype of `logits` or `labels` is not currently supported. + ValueError: If shape of `logits` is not the same as `labels`. + RuntimeError: If `logits` or `labels` is a scalar when `reduction` is 'batchmean'. + + Supported Platforms: + ``Ascend`` ``GPU`` ``CPU`` + + Examples: + >>> import mindspore as ms + >>> import mindspore.nn as nn + >>> import numpy as np + >>> logits = ms.Tensor(np.array([0.2, 0.7, 0.1]), ms.float32) + >>> labels = ms.Tensor(np.array([0., 1., 0.]), ms.float32) + >>> loss = nn.KLDivLoss(reduction='mean') + >>> output = loss(logits, labels) + >>> print(output) + -0.23333333 + """ + + def __init__(self, reduction='mean'): + # 用super进行父类的初始化 + super().__init__() + self.reduction = reduction + + def construct(self, logits, labels): + # 检查logits的类型是否为tensor + _check_is_tensor('logits', logits, self.cls_name) + # 检查labels的类型是否为tensor + _check_is_tensor('labels', labels, self.cls_name) + # 计算损失 + return F.kl_div(logits, labels, self.reduction) + + + +def _check_ctcloss_targets_shape(targets): + """Internal function, used to check whether the shape of CTC targets meets the requirements.""" + # 如果target的维度大于2 + if targets.ndim > 2: + # 抛出ValueError异常 + raise ValueError(f"For CTCLoss, when the shape of log_probs is (T, C), the dimension of targets should" + f"be 1 or 2, but got {targets.ndim}.") + # 如果target的维度等于2或者targets.shape[0]不为1 + if targets.ndim == 2 and targets.shape[0] != 1: + # 抛出ValueError异常 + raise ValueError(f"For CTCLoss, the first dimension of 2-D targets should be 1," + f"but got {targets.shape[0]}.") + + +class CTCLoss(LossBase): + """ + Calculates the CTC (Connectionist Temporal Classification) loss. It's mainly used to calculate the loss between + the continuous, unsegemented time series and the target series. + + For the CTC algorithm, refer to `Connectionist Temporal Classification: Labeling Unsegmented Sequence Data with + Recurrent Neural Networks `_ . + + Args: + blank (int, optional): The blank label. Default: ``0`` . + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the mean of elements in the output. + - ``'sum'``: the output elements will be summed. + + zero_infinity (bool, optional): If loss is infinite, this parameter determines whether to set that loss + and its correlated gradient to zero. Default: ``False`` . + + Inputs: + - **log_probs** (Tensor) - A tensor of shape :math:`(T, N, C)` or :math:`(T, C)`, where T is length of input, + N is size of the batch and C is the number of classes. T, N and C are positive integers. + - **targets** (Tensor) - A tensor of shape :math:`(N, S)` or (sum( `target_lengths` )), + where S is max target length, means the target sequences. + - **input_lengths** (Union[tuple, Tensor]) - A tuple or Tensor of shape(N). It means the lengths of the input. + - **target_lengths** (Union[tuple, Tensor]) - A tuple or Tensor of shape(N). It means the lengths of the target. + + Outputs: + - **neg_log_likelihood** (Tensor) - A loss value which is differentiable with respect to each input node. + + Raises: + TypeError: If `log_probs` or `targets` is not a Tensor. + TypeError: If `zero_infinity` is not a bool, `reduction` is not string. + TypeError: If the dtype of `log_probs` is not float or double. + TypeError: If the dtype of `targets`, `input_lengths` or `target_lengths` is not int32 or int64. + ValueError: If `reduction` is not ``"none"``, ``"mean"`` or ``"sum"``. + ValueError: If the value of `blank` is not in range [0, C). C is number of classes of `log_probs` . + ValueError: If the shape of `log_probs` is :math:`(T, C)`, the dimension of `targets` is not 1 or 2. + ValueError: If the shape of `log_probs` is :math:`(T, C)`, the first dimension of 2-D `target` is not 1. + RuntimeError: If any value of `input_lengths` is larger than T. T is length of `log_probs` . + RuntimeError: If any target_lengths[i] is not in range [0, input_length[i]]. + + Supported Platforms: + ``Ascend`` ``GPU`` ``CPU`` + + Examples: + >>> import mindspore as ms + >>> import mindspore.nn as nn + >>> import numpy as np + >>> T = 5 # Input sequence length + >>> C = 2 # Number of classes + >>> N = 2 # Batch size + >>> S = 3 # Target sequence length of longest target in batch (padding length) + >>> S_min = 2 # Minimum target length, for demonstration purposes + >>> arr = np.arange(T*N*C).reshape((T, N, C)) + >>> ms_input = ms.Tensor(arr, dtype=ms.float32) + >>> input_lengths = np.full(shape=(N), fill_value=T) + >>> input_lengths = ms.Tensor(input_lengths, dtype=ms.int32) + >>> target_lengths = np.full(shape=(N), fill_value=S_min) + >>> target_lengths = ms.Tensor(target_lengths, dtype=ms.int32) + >>> target = np.random.randint(1, C, size=(N, S)) + >>> target = ms.Tensor(target, dtype=ms.int32) + >>> ctc_loss = nn.CTCLoss(blank=0, reduction='none', zero_infinity=False) + >>> loss = ctc_loss(ms_input, target, input_lengths, target_lengths) + >>> print(loss) + [-45.79497 -55.794968] + >>> arr = np.arange(T*C).reshape((T, C)) + >>> ms_input = ms.Tensor(arr, dtype=ms.float32) + >>> input_lengths = ms.Tensor([T], dtype=ms.int32) + >>> target_lengths = ms.Tensor([S_min], dtype=ms.int32) + >>> target = np.random.randint(1, C, size=(S_min,)) + >>> target = ms.Tensor(target, dtype=ms.int32) + >>> ctc_loss = nn.CTCLoss(blank=0, reduction='none', zero_infinity=False) + >>> loss = ctc_loss(ms_input, target, input_lengths, target_lengths) + >>> print(loss) + -25.794968 + """ + + def __init__(self, blank=0, reduction='mean', zero_infinity=False): + # 用super进行父类的初始化 + super().__init__() + # 设置类属性self.blank的值为参数blank的值 + self.blank = blank + # 设置类属性self.reduction的值为参数reduction的值 + self.reduction = reduction + # 设置类属性self.zero_infinity的值为参数zero_infinity的值 + self.zero_infinity = zero_infinity + + def construct(self, log_probs, targets, input_lengths, target_lengths): + # 检查log_probs的类型是否为tensor + _check_is_tensor('log_probs', log_probs, self.cls_name) + # 检查targets的类型是否为tensor + _check_is_tensor('targets', targets, self.cls_name) + # 如果log_probs的维度为2 + if log_probs.ndim == 2: + # 检查输入的目标(targets)的形状是否满足函数的要求 + _check_ctcloss_targets_shape(targets) + # 如果targets的维度为1 + if targets.ndim == 1: + # 将targets的形状扩展为具有一个新轴的形状 + targets = targets.expand_dims(0) + # 将log_probs的形状扩展为具有一个新轴的形状 + log_probs = log_probs.expand_dims(-2) + # 用F.ctc_loss函数计算CTC损失 + neg_log_hood, _ = F.ctc_loss(log_probs, targets, input_lengths, target_lengths, self.blank, self.reduction, + self.zero_infinity) + # 返回计算值 + return neg_log_hood.squeeze(axis=0) + # 用F.ctc_loss函数计算CTC损失 + neg_log_hood, _ = F.ctc_loss(log_probs, targets, input_lengths, target_lengths, self.blank, self.reduction, + self.zero_infinity) + # 返回计算值 + return neg_log_hood + + + +class GaussianNLLLoss(LossBase): + r""" + Gaussian negative log likelihood loss. + + The target values are considered to be samples from a Gaussian distribution, where the expectation and variance are + predicted by a neural network. For `labels` modeled on a Gaussian distribution, `logits` to record expectations, + and the variance `var` (elements are all positive), the calculated loss is: + + .. math:: + \text{loss} = \frac{1}{2}\left(\log\left(\text{max}\left(\text{var}, + \ \text{eps}\right)\right) + \frac{\left(\text{logits} - \text{labels}\right)^2} + {\text{max}\left(\text{var}, \ \text{eps}\right)}\right) + \text{const.} + + where :math:`eps` is used for stability of :math:`log`. When :math:`full=True`, a constant will be added to + the loss. If the shape of :math:`var` and :math:`logits` are not the same (due to a homoscedastic assumption), + their shapes must allow correct broadcasting. + + Keyword Args: + full (bool, optional): Whether include the constant term in the loss calculation. When :math:`full=True`, + the constant term `const.` will be :math:`0.5 * log(2\pi)`. Default: ``False`` . + eps (float, optional): Used to improve the stability of log function. Default: ``1e-6`` . + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the mean of elements in the output. + - ``'sum'``: the output elements will be summed. + + Inputs: + - **logits** (Tensor) - Tensor of shape :math:`(N, *)` or :math:`(*)` where :math:`*` means any number of + additional dimensions. + - **labels** (Tensor) - Tensor of shape :math:`(N, *)` or :math:`(*)`, same shape as the logits, or same shape + as the logits but with one dimension equal to 1 (to allow for broadcasting). + - **var** - Tensor of shape :math:`(N, *)` or :math:`(*)`, same shape as logits, or same shape as the logits + but with one dimension equal to 1, or same shape as the logits but with one fewer dimension + (to allow for broadcasting). + + Returns: + Tensor or Tensor scalar, the computed loss depending on :math:`reduction`. + + Raises: + TypeError: If `logits` is not a Tensor. + TypeError: If `labels` is not a Tensor. + TypeError: If `full` is not a bool. + TypeError: If `eps` is not a float. + ValueError: If `eps` is not a float within (0, inf). + ValueError: If `reduction` is not one of ``'none'``, ``'mean'``, ``'sum'``. + + Supported Platforms: + ``Ascend`` ``GPU`` ``CPU`` + + Examples: + >>> import mindspore as ms + >>> import mindspore.nn as nn + >>> import numpy as np + >>> arr1 = np.arange(8).reshape((4, 2)) + >>> arr2 = np.array([2, 3, 1, 4, 6, 4, 4, 9]).reshape((4, 2)) + >>> logits = ms.Tensor(arr1, ms.float32) + >>> labels = ms.Tensor(arr2, ms.float32) + >>> loss = nn.GaussianNLLLoss(reduction='mean') + >>> var = ms.Tensor(np.ones((4, 1)), ms.float32) + >>> output = loss(logits, labels, var) + >>> print(output) + 1.4374993 + + Reference: + Nix, D. A. and Weigend, A. S., "Estimating the mean and variance of the + target probability distribution", Proceedings of 1994 IEEE International + Conference on Neural Networks (ICNN'94), Orlando, FL, USA, 1994, pp. 55-60 + vol.1, doi: 10.1109/ICNN.1994.374138. + """ + + def __init__(self, *, full=False, eps=1e-6, reduction='mean'): + # 用super进行父类的初始化 + super(GaussianNLLLoss, self).__init__() + # 检查eps参数范围是否在0到正无穷之间 + validator.check_float_range(eps, 0, float('inf'), validator.INC_NEITHER, "eps", self.cls_name) + # 检查full的类型是否为bool型 + validator.check_value_type('full', full, [bool], self.cls_name) + # 检查reduction是否为none,mean或sum中之一 + validator.check_string(reduction, ['none', 'mean', 'sum'], 'reduction', 'gaussian_nll_loss') + self.full = full + self.eps = eps + self.reduction = reduction + + def construct(self, logits, labels, var): + # 检查logits的类型是否为tensor + _check_is_tensor('logits', logits, self.cls_name) + # 检查labels的类型是否为tensor + _check_is_tensor('labels', labels, self.cls_name) + # 检查var的类型是否为tensor + _check_is_tensor('var', var, self.cls_name) + # 调用ops中的算子ops.gaussian_nll_loss计算损失并返回 + return ops.gaussian_nll_loss(logits, labels, var, self.full, self.eps, self.reduction) + + +# 用于实现HingeEmbedding损失函数,主要用于人脸识别、图像匹配等任务 +class HingeEmbeddingLoss(LossBase): + r""" + Calculate the Hinge Embedding Loss value based on the input 'logits' and' labels' (only including 1 or -1). + Usually used to measure the similarity between two inputs. + + The loss function for :math:`n`-th sample in the mini-batch is + + .. math:: + l_n = \begin{cases} + x_n, & \text{if}\; y_n = 1,\\ + \max \{0, \Delta - x_n\}, & \text{if}\; y_n = -1, + \end{cases} + + and the total loss functions is + + .. math:: + \ell(x, y) = \begin{cases} + \operatorname{mean}(L), & \text{if reduction} = \text{'mean';}\\ + \operatorname{sum}(L), & \text{if reduction} = \text{'sum'.} + \end{cases} + + where :math:`L = \{l_1,\dots,l_N\}^\top`. + + Args: + margin (float, int): Threshold defined by Hinge Embedding Loss :math:`margin`. + Represented as :math:`\Delta` in the formula. Default: ``1.0`` . + reduction (str, optional): Apply specific reduction method to the output: ``'none'`` , ``'mean'`` , + ``'sum'`` . Default: ``'mean'`` . + + - ``'none'``: no reduction will be applied. + - ``'mean'``: compute and return the mean of elements in the output. + - ``'sum'``: the output elements will be summed. + + Inputs: + - **logits** (Tensor) - The predicted value, expressed as :math:`x` in the equation. + Tensor of shape :math:`(*)` where :math:`*` means any number of dimensions. + - **labels** (Tensor) - Label value, represented as :math:`y` in the equation. + Same shape as the logits, contains -1 or 1. + + Returns: + Tensor or Tensor scalar, the computed loss depending on :math:`reduction`. + + Raises: + TypeError: If `logits` is not a Tensor. + TypeError: If `labels` is not a Tensor. + TypeError: If `margin` is not a float or int. + ValueError: If `labels` does not have the same shape as `logits` or they could not broadcast to each other. + ValueError: If `reduction` is not one of ``'none'``, ``'mean'``, ``'sum'``. + + Supported Platforms: + ``Ascend`` ``GPU`` ``CPU`` + + Examples: + >>> import mindspore as ms + >>> import mindspore.nn as nn + >>> import numpy as np + >>> arr1 = np.array([0.9, -1.2, 2, 0.8, 3.9, 2, 1, 0, -1]).reshape((3, 3)) + >>> arr2 = np.array([1, 1, -1, 1, -1, 1, -1, 1, 1]).reshape((3, 3)) + >>> logits = ms.Tensor(arr1, ms.float32) + >>> labels = ms.Tensor(arr2, ms.float32) + >>> loss = nn.HingeEmbeddingLoss(reduction='mean') + >>> output = loss(logits, labels) + >>> print(output) + 0.16666667 + """ + + def __init__(self, margin=1.0, reduction='mean'): + # 用super进行父类的初始化 + super(HingeEmbeddingLoss, self).__init__() + # 检查margin的类型是否为float或int + validator.check_value_type('margin', margin, [float, int], self.cls_name) + # 检查reduction参数的值是否为none,sum或mean + validator.check_string(reduction, ['none', 'sum', 'mean'], 'reduction', self.cls_name) + self.margin = margin + self.reduction = reduction + + def construct(self, logits, labels): + # 调用ops.hinge_embedding_loss计算HingeEmbedding损失 + loss = ops.hinge_embedding_loss(logits, labels, self.margin, self.reduction) + # 返回loss值 + return loss \ No newline at end of file diff --git a/mindspore/python/mindspore/nn/metrics/__init__.py b/mindspore/python/mindspore/nn/metrics/__init__.py index bd3a89dee34..c9f20838994 100755 --- a/mindspore/python/mindspore/nn/metrics/__init__.py +++ b/mindspore/python/mindspore/nn/metrics/__init__.py @@ -12,32 +12,58 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ +# 定义了一个名为Metrics的类,用于提供各种评估指标,用于衡量机器学习模型的性能 """ Metrics. Functions to measure the performance of the machine learning models on the evaluation dataset. It's used to choose the best model. """ +# 这个类包含了以下模块: +# 1.计算模型在分类问题中的准确率 from .accuracy import Accuracy +# 2.计算模型在分类问题中的汉氏距离 from .hausdorff_distance import HausdorffDistance +# 3.计算模型在分类问题中的均方误差(MSE)和平均绝对误差(MAE) from .error import MAE, MSE +# 4.一个抽象类,用于封装评估指标的计算 from .metric import Metric, rearrange_inputs +# 5.计算模型在多标签分类问题中的精确度 from .precision import Precision +# 6.计算模型在多标签分类问题中的召回率 from .recall import Recall +# 7.计算模型在多标签分类问题中的F分数 from .fbeta import Fbeta, F1 +# 8.计算模型在多标签分类问题中的Dice系数 from .dice import Dice +# 9.计算模型在二分类问题中的ROC曲线 from .roc import ROC +# 10.计算模型在二分类问题中的AUC值 from .auc import auc +# 11.计算模型在多分类问题中的Top-K准确率 +# 12.计算模型在多分类问题中的Top-1准确率 +# 13.计算模型在多分类问题中的Top-5准确率 from .topk import TopKCategoricalAccuracy, Top1CategoricalAccuracy, Top5CategoricalAccuracy +# 14.计算模型在回归问题中的损失函数 from .loss import Loss +# 15.计算模型在三维形状上的平均表面距离 from .mean_surface_distance import MeanSurfaceDistance +# 16.计算模型在三维形状上的根均方距离 from .root_mean_square_surface_distance import RootMeanSquareDistance +# 17.计算模型在文本生成问题中的BLEU分数 from .bleu_score import BleuScore +# 18.计算模型在文本生成问题中的余弦相似度 from .cosine_similarity import CosineSimilarity +# 19.计算模型在图像上进行遮罩测试时的敏感度 from .occlusion_sensitivity import OcclusionSensitivity +# 20.计算模型在文本生成问题中的困惑度 from .perplexity import Perplexity +# 21.计算模型在分类问题中的混淆矩阵 +# 22.计算模型在分类问题中的混淆矩阵 from .confusion_matrix import ConfusionMatrixMetric, ConfusionMatrix +# __all__是一个包含类或函数名称的列表,用于告诉Python解释器这些名称可以被导入。 +# 这样,当用户从Metrics类中导入这些名称时,不会导入整个类,而是只导入所需的名称。这有助于减少导入时间,提高代码的可读性和性能 __all__ = [ "names", "get_metric_fn", @@ -66,6 +92,7 @@ __all__ = [ "ConfusionMatrixMetric", ] +# 名为__factory__的静态方法。这个方法允许在创建类实例时提供一个工厂函数,用于根据参数创建类的具体实现。这在创建具有不同功能的子类时非常有用,例如在框架中 __factory__ = { 'accuracy': Accuracy, 'acc': Accuracy, @@ -94,6 +121,7 @@ __factory__ = { def names(): + # 用于获取Metrics类中所有指标方法的名称 """ Gets all names of the metric methods. @@ -103,10 +131,13 @@ def names(): Supported Platforms: ``Ascend`` ``GPU`` ``CPU`` """ + # 首先,通过__factory__字典获取所有指标方法的名称,然后对名称进行排序,最后返回排序后的名称列表 return sorted(__factory__.keys()) +# 这样,用户可以通过调用names函数获取Metrics类中所有可用的指标方法名称,以便根据需要选择和使用它们 def get_metric_fn(name, *args, **kwargs): + # 用于根据输入的名称获取Metrics类中对应的方法 """ Gets the metric method based on the input name. @@ -126,13 +157,18 @@ def get_metric_fn(name, *args, **kwargs): >>> from mindspore import nn >>> metric = nn.get_metric_fn('precision', eval_type='classification') """ + # 首先,通过__factory__字典检查输入的名称是否在字典中 if name not in __factory__: + # 如果不存在,则抛出一个KeyError异常 raise KeyError(f"For 'get_metric_fn', unsupported metric {name}, please refer to official website " f"for more details about supported metrics.") + # 如果存在,则根据输入的名称和参数创建一个Metrics类中的具体实现,并返回这个实现类的实例 return __factory__[name](*args, **kwargs) +# 在示例中,用户可以通过调用get_metric_fn函数来获取不同指标方法的实例,然后使用这个实例来计算评估结果 def get_metrics(metrics): + # 用于根据输入的指标名称获取Metrics类中对应的方法。函数接受一个字典或集合作为输入,集合中的指标名称将自动映射到对应的Metrics类实例 """ Get metrics used in evaluation. @@ -146,20 +182,33 @@ def get_metrics(metrics): Raises: TypeError: If the type of argument 'metrics' is not None, dict or set. """ + # 首先检查输入的指标名称是否为None if metrics is None: + # 如果是,则直接返回metrics return metrics + # 然后,检查输入的指标名称是否为字典或集合 if isinstance(metrics, dict): + # 如果是,则遍历指标名称和对应的指标方法,将它们存储在一个新的字典中,并返回这个字典 for name, metric in metrics.items(): + # 如果输入的指标名称不是字典或集合 if not isinstance(name, str) or not isinstance(metric, Metric): + # 则抛出一个TypeError异常 raise TypeError(f"For 'get_metrics', if 'metrics' is dict, the key in 'metrics' must be string and " f"value in 'metrics' must be Metric, but got key:{type(name)}, value:{type(metric)}.") + # 返回指标字典 return metrics + # 首先检查输入的指标名称是否为字典或集合 if isinstance(metrics, set): + # 新建空字典存储指标信息 out_metrics = {} + # 如果是,则遍历指标名称和对应的指标方法 for name in metrics: + # 将它们存储在一个新的字典中 out_metrics[name] = get_metric_fn(name) + # 并返回这个字典 return out_metrics + # 如果输入的指标名称不是字典或集合,则抛出一个TypeError异常 raise TypeError("For 'get_metrics', the argument 'metrics' should be None, dict or set, " "but got {}".format(metrics)) diff --git a/mindspore/python/mindspore/nn/metrics/accuracy.py b/mindspore/python/mindspore/nn/metrics/accuracy.py index 2d030f96033..826e1d47a1d 100644 --- a/mindspore/python/mindspore/nn/metrics/accuracy.py +++ b/mindspore/python/mindspore/nn/metrics/accuracy.py @@ -13,11 +13,24 @@ # limitations under the License. # ============================================================================ """Accuracy.""" +# 名为Accuracy的Python模块,其中包含了与准确率相关的功能 +# 首先,导入了numpy库,用于处理数值计算 import numpy as np +# 然后,从metric模块中导入了一个名为EvaluationBase的类,以及一个名为rearrange_inputs的函数,以及一个名为_check_onehot_data的函数。这些类和函数用于计算和处理准确率相关的内容 +# EvaluationBase,是metric模块中的一个基类,用于实现评估指标的基本功能 + +# rearrange_inputs,该函数用于重新排列输入数据,以便在计算评估指标时可以正确地处理数据。具体来说,它会检查输入数据是否是一个多维数组, +# 如果是,它会将其转换为一个二维数组,其中每一行表示一个样本,每一列表示一个特征 + +# _check_onehot_data,该函数用于检查输入数据是否符合独热编码规则。具体来说,它会检查输入数据是否是一个二维数组,其中每一行表示一个样本, +# 每一列表示一个类别。此外,它还检查输入数据中的每个类别是否是唯一的,如果不是,则会抛出一个异常 from .metric import EvaluationBase, rearrange_inputs, _check_onehot_data class Accuracy(EvaluationBase): + # Accuracy类,它是EvaluationBase类的子类,用于计算分类和多标签数据的精度。 + # 在计算精度时,它会创建两个本地变量,即正确数量的计数器和总数量的计数器。然后,它使用这些变量计算准确率, + # 具体来说,它是正确数量的除数,加上正确数量的除数和假正例数量的除数和假负例数量的除数 r""" Calculates the accuracy for classification and multilabel data. @@ -51,17 +64,27 @@ class Accuracy(EvaluationBase): 0.6666666666666666 """ def __init__(self, eval_type='classification'): + ''' + 初始化准确率 + :param eval_type: 评估类型,默认为分类 + ''' super(Accuracy, self).__init__(eval_type) + # 调用子类的clear方法,用于清除评估结果 self.clear() def clear(self): + # 清空内部评估结果 """Clears the internal evaluation result.""" + # 正确项计数器 self._correct_num = 0 + # 总数 self._total_num = 0 + # 类数量 self._class_num = 0 @rearrange_inputs def update(self, *inputs): + # 更新本地变量。对于二分类任务,如果预测值的索引与标签相匹配,则认为预测结果正确。对于多标签任务,如果预测值与标签相匹配,则认为预测结果正确 """ Updates the local variables. For 'classification', if the index of the maximum of the predict value matches the label, the predict result is correct. For 'multilabel', the predict value match the label, @@ -82,36 +105,53 @@ class Accuracy(EvaluationBase): ValueError: class numbers of last input predicted data and current predicted data not match. """ + # 检查输入参数的数量是否为2 if len(inputs) != 2: + # 如果不是,则抛出一个ValueError异常,表示需要2个输入参数,但实际提供了{}个 raise ValueError("For 'Accuracy.update', it needs 2 inputs (predicted value, true value), " "but got {}".format(len(inputs))) + # 将data型的输入值inputs[0]转换为numpy数组 y_pred = self._convert_data(inputs[0]) + # 将data型的输入值inputs[1]转换为numpy数组 y = self._convert_data(inputs[1]) + # 根据self._type的值判断是二分类任务还是多标签任务 if self._type == 'classification' and y_pred.ndim == y.ndim and _check_onehot_data(y): + # 对于分类任务,如果y_pred和y的维度相同,并且y是onehot编码的,则将y的维度降低到一个维度 y = y.argmax(axis=1) + # 最后,检查y_pred和y的形状和值是否符合要求 self._check_shape(y_pred, y) self._check_value(y_pred, y) - + # 对输入的类型做出判断,并根据不同的结果做出不同的修改 if self._class_num == 0: + # 若输入的类别数为0,则将其赋值为预测的类别数 self._class_num = y_pred.shape[1] - elif y_pred.shape[1] != self._class_num: + elif y_pred.shape[1]!= self._class_num: + # 若预测的类别数不等于输入的类别数,则抛出异常ValueError需检查预测值出错的原因 raise ValueError("For 'Accuracy.update', class number not match, last input predicted data contain {} " "classes, but current predicted data contain {} classes, please check your predicted " "value(inputs[0]).".format(self._class_num, y_pred.shape[1])) + # 根据self._type的值判断是二分类任务还是多标签任务 if self._type == 'classification': + # 对于分类任务,将y_pred的维度降低到一个维度 indices = y_pred.argmax(axis=1) + # 比较y_pred和y是否相等 result = (np.equal(indices, y) * 1).reshape(-1) - elif self._type == 'multilabel': + elif self._type =='multilabel': + # 对于多标签任务,获取预测结果的维度并减一 dimension_index = y_pred.ndim - 1 + # 将预测结果的维度改为-1 y_pred = y_pred.swapaxes(1, dimension_index).reshape(-1, self._class_num) + # 将标签的维度改为-1,将y_pred和y的维度降低到一个维度 y = y.swapaxes(1, dimension_index).reshape(-1, self._class_num) + # 将预测结果和标签做比较,并将结果改为1 result = np.equal(y_pred, y).all(axis=1) * 1 - + # 计算result中所有元素的和,并将其加到self._correct_num和self._total_num中 self._correct_num += result.sum() self._total_num += result.shape[0] def eval(self): + # 计算模型的准确性 """ Computes the accuracy. @@ -121,8 +161,12 @@ class Accuracy(EvaluationBase): Raises: RuntimeError: If the sample size is 0. """ + # 首先,检查self._total_num是否为0 if self._total_num == 0: + # 如果是,则抛出一个RuntimeError异常,表示无法计算准确性,因为样本数量为0 raise RuntimeError("The 'Accuracy' can not be calculated, because the number of samples is 0, " "please check whether your inputs(predicted value, true value) are empty, " "or has called update method before calling eval method.") + # 如果self._total_num不为0,则计算self._correct_num除以self._total_num的结果准确率,并返回该结果。 + # 注:在计算准确性时,需要确保已经调用了update方法,以便能够更新self._correct_num和self._total_num的值 return self._correct_num / self._total_num diff --git a/mindspore/python/mindspore/nn/metrics/auc.py b/mindspore/python/mindspore/nn/metrics/auc.py index e47640238ca..210d845cdf5 100644 --- a/mindspore/python/mindspore/nn/metrics/auc.py +++ b/mindspore/python/mindspore/nn/metrics/auc.py @@ -13,10 +13,16 @@ # limitations under the License. # ============================================================================ """auc""" +# 用Python编写的,主要用于计算曲线下面积(AUC),下属于metrics模块用于对机器学习的性能进行评估 +# 首先,导入了两个常用的库:numpy。numpy是Python的科学计算库,用于处理大型数组和矩阵; import numpy as np def auc(x, y, reorder=False): + # 代码的主要功能是计算曲线下面积(AUC),这是一种评估二分类模型性能的指标。AUC表示了模型将正样本分片分配到比负样本更小的概率时, + # 能成功识别正样本的百分比。在二分类问题中,通常将正样本定义为1,负样本定义为0 + # 需要注意的是,这段代码仅适用于二分类问题。对于多分类问题,需要使用其他方法来计算AUC + # 该函数接受两个列表作为输入:y_true和y_pred。y_true是一个表示真实标签的列表,y_pred是一个表示模型预测概率的列表 """ Computes the AUC(Area Under the Curve) using the trapezoidal rule. This is a general function, given points on a curve. For computing the area under the ROC-curve. @@ -49,68 +55,118 @@ def auc(x, y, reorder=False): >>> print(output) 0.5357142857142857 """ + """ + 以下这段代码主要用于检查参数并处理 + """ + # 首先检查x和y是否为numpy数组 if not isinstance(x, np.ndarray) or not isinstance(y, np.ndarray): + # 如果不是,则抛出一个TypeError,提醒使用者 raise TypeError("For 'auc', the argument 'x' and 'y' must all be np.ndarray, but got {}, {}" .format(type(x), type(y))) + # 然后,使用numpy的_check_consistent_length函数检查x和y的长度是否一致。如果长度不一致,则抛出一个ValueError _check_consistent_length(x, y) + # 最后,将x和y转换为列向量 x = _column_or_1d(x) y = _column_or_1d(y) + # 首先检查x的形状是否为2维数组 if x.shape[0] < 2: + # 如果形状为1维数组,则抛出一个ValueError raise ValueError("For 'auc', the shape of the argument 'x' in axis 0 must be greater than 2, " "but got {}.".format(x.shape[0])) + # 如果x的维度为1,定义一个名为direction的变量设置为1 + # 注:direction用于表示计算AUC时所使用的方向。通常情况下,我们使用左端点(0)和右端点(1)来计算AUC,此时direction的值为1; + # 如果使用右端点(1)和左端点(0)来计算AUC,此时direction的值为-1 direction = 1 + + """ + 以下这段代码主要用于处理x和y的顺序 + """ + # 首先,检查reorder是否为True if reorder: + # 如果为True,则使用np.lexsort对y和x进行排序 + # 注:np.lexsort是一个快速排序算法,它首先对y进行排序,然后根据x中的值对已经排序的y和x进行排序。这样,x和y的顺序就得到了处理,返回一个排列顺序order order = np.lexsort((y, x)) + # 将x和y按照order中的顺序进行排列 x, y = x[order], y[order] else: + # 计算x中相邻元素的差值,并将结果存储在dx中。dx是一个一维numpy数组,其长度比x少1 dx = np.diff(x) + # 如果reorder为False,则检查x中的相邻元素是否满足递增或递减的条件 if np.any(dx < 0): if np.all(dx <= 0): + # 满足则将direction设置为-1 direction = -1 else: + # 如果不满足,则抛出一个ValueError raise ValueError("For 'auc', if the argument is False, the argument 'x' array should be increasing " "or decreasing, but got 'x': {}".format(x)) - + """ + 这段代码计算曲线下面积 + """ + # np.trapz(y, x)表示计算从x[0]到x[-1]的等差数列中各个点对应的梯形面积,然后将结果乘以direction area = direction * np.trapz(y, x) + # 如果area是一个numpy内存映射对象 if isinstance(area, np.memmap): + # 最后,将area转换为原来的数据类型 area = area.dtype.type(area) + # 返回计算的面积 return area def _column_or_1d(y): + # 函数用于将输入的y值转换为1D数组 """ Ravel column or 1D numpy array, otherwise raise a ValueError. """ + # 获取数组y的形状 shape = np.shape(y) + # 如果y是一个1D数组,或者是一个2D数组,其中第二维的长度为1 if len(shape) == 1 or (len(shape) == 2 and shape[1] == 1): + # 函数返回1D数组 return np.ravel(y) + # 否则,抛出一个ValueError raise ValueError("For 'auc', the input must be a 1-dimensional array, or a 2-dimensional array with the second " "dimension of 1, but got shape {}.".format(shape)) def _num_samples(x): + # 函数用于计算输入x的样本数量。x可以是列表、数组或其他类似序列的集合 """Return the number of samples in array-like x.""" + """ + 该函数首先检查样本是否拥有可以直接使用的属性,然后根据该属性获取样本数量 + """ + # 函数首先检查x是否有一个fit方法 if hasattr(x, 'fit') and callable(x.fit): + # 如果是,则抛出一个TypeError raise TypeError('Expected sequence or array-like, got estimator {}.'.format(x)) + # 然后,检查x是否有一个__len__方法或一个shape属性 if not hasattr(x, '__len__') and not hasattr(x, 'shape'): + # 如果没有这些属性,则判断是否有数组属性 if hasattr(x, '__array__'): + # 如果有,则尝试将其转换为numpy数组 x = np.asarray(x) else: + # 否则,抛出一个TypeError raise TypeError("Expected sequence or array-like, got {}." .format(type(x))) + # 首先检查x是否有一个shape属性 if hasattr(x, 'shape'): if x.ndim == 0: + # 如果x是一个单元素数组,即x.shape == (),则抛出一个TypeError raise TypeError("Singleton array {} cannot be considered as a valid collection.".format(x)) + # 如果存在,则计算x的第一个维度(即样本数量) res = x.shape[0] else: + # 如果x是一个多元素数组,即x.shape != (),那么计算x的大小(即元素数量),并将其赋值给变量res res = x.size - + # 最后,返回样本数量 return res def _check_consistent_length(*arrays): + # 函数用于检查输入的多个数组(arrays)是否具有一致的第一个维度。函数的主要目的是确保输入的对象(数组)具有相同的形状或长度 r""" Check that all arrays have consistent first dimensions. Check whether all objects in arrays have the same shape or length. @@ -119,9 +175,12 @@ def _check_consistent_length(*arrays): - **(*arrays)** - (Union[tuple, list]): list or tuple of input objects. Objects that will be checked for consistent length. """ - + # 首先遍历输入的数组(arrays),并使用_num_samples函数计算每个数组的大小(第一个维度)。然后,将计算得到的大小列表存储在变量lengths中 lengths = [_num_samples(array) for array in arrays if array is not None] + # 找到lengths列表中的所有唯一长度,并将结果存储在变量uniques中。np.unique函数会返回一个有序的unique元素列表,其中元素不重复 uniques = np.unique(lengths) + # 检查uniques列表的长度是否大于1 if len(uniques) > 1: + # 如果是,则抛出一个ValueError,其中包含所有不重复的长度(即输入数组的样本数量) raise ValueError("Found input variables with inconsistent numbers of samples: {}." .format([int(length) for length in lengths])) diff --git a/mindspore/python/mindspore/nn/metrics/bleu_score.py b/mindspore/python/mindspore/nn/metrics/bleu_score.py index c2913ea6c47..d687cacb198 100644 --- a/mindspore/python/mindspore/nn/metrics/bleu_score.py +++ b/mindspore/python/mindspore/nn/metrics/bleu_score.py @@ -12,14 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ +# 该类继承自Metric。BleuScore类用于计算BLEU分数,是一种用于评估机器翻译文本质量的指标 """BleuScore.""" +# 首先,从collections模块导入Counter类,用于计数器 from collections import Counter +# 接下来,从numpy模块导入numpy库,用于处理数值计算 import numpy as np +# 然后,从mindspore._checkparam模块导入Validator类,用于参数验证 from mindspore._checkparam import Validator as validator +# 最后,从mindspore.metric模块导入Metric类,用于计算其他指标 from .metric import Metric, rearrange_inputs class BleuScore(Metric): + # 参数:n_gram和smooth。n_gram参数表示计算BLEU分数时使用的n-gram值,取值范围为1到4。smooth参数表示是否应用平滑算法,取值为True或False + # n-gram是一种用于表示文本中连续词项的方法,它将文本分成大小为n的子文本片段,然后计算每个子文本片段中不同词项的频率。 + # ,BleuScore类中n-gram的范围决定了计算BLEU分数时使用的n-gram数量。通常情况下,n-gram值为4 + # 在示例中,我们首先创建了一个BleuScore对象,然后更新其内部评估结果。最后,我们调用eval方法计算BLEU分数并输出结果 """ Calculates the BLEU score. BLEU (bilingual evaluation understudy) is a metric for evaluating the quality of text translated by machine. @@ -48,27 +57,42 @@ class BleuScore(Metric): 0.5946035575013605 """ def __init__(self, n_gram=4, smooth=False): + # 调用父类的初始化函数,确保基本功能正常工作 super().__init__() + # 然后,对n_gram和smooth进行验证,确保它们的类型为int和bool self.n_gram = validator.check_value_type("n_gram", n_gram, [int]) if self.n_gram > 4 or self.n_gram < 1: + # 如果n_gram的值大于4或小于1,则抛出一个ValueError异常,表示n_gram的值不合法 raise ValueError("For 'BleuScore', the argument 'n_gram' should range from 1 to 4, " "but got {}.".format(n_gram)) self.smooth = validator.check_value_type("smooth", smooth, [bool]) + # 清空残留的内部评估结果 self.clear() def clear(self): + # 该方法用于清空内部评估结果 """Clear the internal evaluation result.""" + # 存储计算分子时所需的值 self._numerator = np.zeros(self.n_gram) + # 存储计算分母时所需的值 self._denominator = np.zeros(self.n_gram) + # 存储计算precision分数时所需的值 self._precision_scores = np.zeros(self.n_gram) + # 存储计算BLEU分数时所需的值 self._c = 0.0 + # 存储计算BLEU分数时所需的值 self._r = 0.0 + # 存储翻译文本的长度 self._trans_len = 0 + # 存储参考文本的长度 self._ref_len = 0 + # 标记是否已经更新了类变量 self._is_update = False def _count_ngram(self, ngram_input_list, n_gram): + # 统计输入文本中每个单词出现的次数,同时使用ngram值 + # ngram_input_list是一个列表,包含要统计的翻译文本或参考文本 """ Counting how many times each word appears in a given text with ngram. @@ -82,15 +106,23 @@ class BleuScore(Metric): ngram_counter = Counter() + # 遍历输入文本 for i in range(1, n_gram + 1): + # 对于每个ngram值,从当前索引开始向前查找相应长度的子列表 for j in range(len(ngram_input_list) - i + 1): + # 将子列表转换为元组 ngram_key = tuple(ngram_input_list[j:(i + j)]) + # 作为ngram键,并将出现次数存储在Counter对象中 ngram_counter[ngram_key] += 1 + # 返回值是一个collections.Counter对象,用于存储统计结果 return ngram_counter @rearrange_inputs def update(self, *inputs): + # 该方法用于更新内部评估结果 + # 接受两个输入参数:candidate_corpus和reference_corpus。candidate_corpus是一个包含机器翻译文本的迭代器, + # reference_corpus是一个包含参考文本的迭代器。两个输入参数的长度应相同 """ Updates the internal evaluation result with `candidate_corpus` and `reference_corpus`. @@ -103,40 +135,73 @@ class BleuScore(Metric): ValueError: If the number of inputs is not 2. ValueError: If the lengths of `candidate_corpus` and `reference_corpus` are not equal. """ + """ + 检查输入,获取参数并检查其长度是否相等 + """ + # 如果输入数组长度不为2 if len(inputs) != 2: + # 抛出异常ValueError raise ValueError("For 'BleuScore.update', it needs 2 inputs (candidate_corpus, reference_corpus), " "but got {}.".format(len(inputs))) + # 从input中获取候选文本与参考文本 candidate_corpus = inputs[0] reference_corpus = inputs[1] - if len(candidate_corpus) != len(reference_corpus): - raise ValueError("For 'BleuScore.update', 'translate_corpus' (inputs[0]) and 'reference_corpus' " + # 判断两者长度是否相等 + if len(candidate_corpus)!= len(reference_corpus): + # 如果candidate_corpus和reference_corpus的长度不相等,抛出ValueError + raise ValueError("For 'BleuScore.update', 'translate_corpus' (inputs[0]) and'reference_corpus' " "(inputs[1]) should be equal in length, but got {}, {}" .format(len(candidate_corpus), len(reference_corpus))) - + """ + 计算候选文本和参考文本的n-gram计数 + """ + # 首先对输入的candidate_corpus和reference_corpus进行zip操作,将它们一一对应地组合成一个新的元组列表,然后,遍历新的元组列表 for (candidate, references) in zip(candidate_corpus, reference_corpus): + # 对于每个元组,将其第一个元素(candidate)的长度加到self._c中 self._c += len(candidate) + # 同时将其第二个元素(references)的长度加到ref_len_list中 ref_len_list = [len(ref) for ref in references] + # 接下来,计算candidate和每个reference的长度差 ref_len_diff = [abs(len(candidate) - x) for x in ref_len_list] + # 并找到长度差的最小值对应的下标 self._r += ref_len_list[ref_len_diff.index(min(ref_len_diff))] + # 最后,使用self._count_ngram方法统计candidate中的n-gram translation_counter = self._count_ngram(candidate, self.n_gram) + # 并将结果存储在translation_counter中 reference_counter = Counter() + """ + 计算候选文本和参考文本的n-gram重叠部分 + """ + # 遍历references中的每个元素ref for ref in references: + # 使用self._count_ngram方法统计ref中的n-gram reference_counter |= self._count_ngram(ref, self.n_gram) - + + # 并将结果与translation_counter进行按位与操作,最后,将结果存储在ngram_counter_clip中 ngram_counter_clip = translation_counter & reference_counter + """ + 更新n-gram计数和 denominator + """ + # 首先遍历ngram_counter_clip中的每个键值对counter_clip for counter_clip in ngram_counter_clip: + # 然后将键的长度减1,值加到self._numerator中 self._numerator[len(counter_clip) - 1] += ngram_counter_clip[counter_clip] - + # 遍历translation_counter中的每个键值对counter for counter in translation_counter: + # 将键的长度减1,值加到self._denominator中 self._denominator[len(counter) - 1] += translation_counter[counter] + # 首先将self._c转换为numpy数组,并将其存储在self._trans_len中 self._trans_len = np.array(self._c) + # 然后,将self._r转换为numpy数组,并将其存储在self._ref_len中 self._ref_len = np.array(self._r) + # 最后,将self._is_update的值设置为True self._is_update = True def eval(self): + # 该方法用于计算BLEU分数 """ Computes the bleu score. @@ -146,21 +211,36 @@ class BleuScore(Metric): Raises: RuntimeError: If the update method is not called first, an error will be reported. + """ + """ + 首先检查是否已经更新过内部评估结果 """ if self._is_update is False: + # 如果没有更新,则抛出一个运行时错误 raise RuntimeError("Please call the 'update' method before calling 'eval' method.") + # 检查self._numerator中的最小值是否为0.0 if min(self._numerator) == 0.0: + # 如果最小值等于0.0,则返回一个numpy数组中的0.0 return np.array(0.0) + """ + 计算BLEU分数 + """ + # 计算平滑后的n-gram精度分数 if self.smooth: + # 如果smooth参数为True,则应用平滑算法 precision_scores = np.add(self._numerator, np.ones(self.n_gram)) / np.add(self._denominator, np.ones(self.n_gram)) else: + # 否则,直接计算精度分数 precision_scores = self._numerator / self._denominator - + # 计算与self.n_gram长度相同的列表log_precision_scores,其中所有元素的值都为1除以self.n_gram log_precision_scores = np.array([1.0 / self.n_gram] * self.n_gram) * np.log(precision_scores) + # 计算长度几何平均值和布雷维尼系数 geometric_mean = np.exp(np.sum(log_precision_scores)) brevity_penalty = np.array(1.0) if self._c > self._r else np.exp(1 - (self._ref_len / self._trans_len)) + # 最后计算BLEU分数 bleu = brevity_penalty * geometric_mean + # 返回BLEU分数 return bleu diff --git a/mindspore/python/mindspore/nn/metrics/confusion_matrix.py b/mindspore/python/mindspore/nn/metrics/confusion_matrix.py index ffaa4aebbc8..5a980b2b467 100644 --- a/mindspore/python/mindspore/nn/metrics/confusion_matrix.py +++ b/mindspore/python/mindspore/nn/metrics/confusion_matrix.py @@ -13,12 +13,23 @@ # limitations under the License. # ============================================================================ """ConfusionMatrixMetric & ConfusionMatrix.""" +# ConfusionMatrixMetric类继承自Metric类,用于计算混淆矩阵。ConfusionMatrix函数用于根据真实标签和预测标签计算混淆矩阵 import numpy as np +# 导入所需的库:numpy库用于处理数值计算,mindspore._checkparam库中的Validator类用于参数验证 from mindspore._checkparam import Validator as validator +# 从metric模块中导入Metric类和rearrange_inputs函数。Metric类是用于计算指标的基类,而rearrange_inputs函数用于对输入数据进行重新排列,以适应某些计算需求 from .metric import Metric, rearrange_inputs class ConfusionMatrix(Metric): + # 参数:num_classes和normalize。num_classes表示数据集中的类数,normalize表示混淆矩阵的normalization模式。有四种normalization模式: + + # "no_norm" (None):不进行normalization。默认模式。 + # "target":根据真实标签进行normalization。 + # "prediction":根据预测标签进行normalization。 + # "all":根据整个混淆矩阵进行normalization。 + + # threshold参数用于比较输入张量,默认值为0.5。 """ Computes the confusion matrix, which is commonly used to evaluate the performance of classification models, including binary classification and multiple classification. @@ -55,24 +66,34 @@ class ConfusionMatrix(Metric): [1. 1.]] """ def __init__(self, num_classes, normalize="no_norm", threshold=0.5): + # 调用父类构造函数初始化 super(ConfusionMatrix, self).__init__() - + # 检查输入参数num_classes是否为int型,否则抛出TypeError self.num_classes = validator.check_value_type("num_classes", num_classes, [int]) + # 如果normalize的模式不在预定的四个之内,则抛出异常ValueError if normalize not in ["target", "prediction", "all", "no_norm"]: raise ValueError("For 'ConfusionMatrix', the argument 'normalize' should be in " "['all', 'prediction', 'label', 'no_norm'(None)], but got {}.".format(normalize)) - + # 获取normalize的值 self.normalize = normalize + # 检查输入参数threshold是否为float型,否则抛出TypeError self.threshold = validator.check_value_type("threshold", threshold, [float]) + # 清空类中残余数据 self.clear() def clear(self): + # 用于清空类中的数据 """Clears the internal evaluation result.""" + # 定义self.confusion_matrix,用于存储混淆矩阵 self.confusion_matrix = np.zeros((self.num_classes, self.num_classes)) + # 表示未更新混淆矩阵 self._is_update = False @rearrange_inputs def update(self, *inputs): + # 用于更新混淆矩阵的状态 + # inputs参数接受两个张量y_pred和y作为输入。y_pred是预测值,y是真实值。 + # y_pred的形状可以是(N, C, ...)或(N, ...),其中N是样本数量,C是类别数量。y的形状是(N, ...) """ Update state with y_pred and y. @@ -86,62 +107,88 @@ class ConfusionMatrix(Metric): ValueError: If the number of inputs is not 2. ValueError: If the dim of y_pred and y are not equal. """ + # 检查inputs参数的数量是否为2 if len(inputs) != 2: + # 如果不是,则抛出一个错误ValueError raise ValueError("For 'ConfusionMatrix.update', it needs 2 inputs (predicted value, true value), " "but got {}.".format(len(inputs))) - + # 然后,将inputs[0]和inputs[1]转换为适当的数据格式(例如,张量或numpy数组) y_pred = self._convert_data(inputs[0]) y = self._convert_data(inputs[1]) - + # 如果预测值和真实值的维度不匹配,或者预测值的维度与真实值的维度之差大于1 if not (y_pred.ndim == y.ndim or y_pred.ndim == y.ndim + 1): + # 抛出一个错误ValueError raise ValueError(f"For 'ConfusionMatrix.update', predicted value (input[0]) and true value " f"(input[1]) should have same dimensions, or the dimension of predicted value " f"equals the dimension of true value add 1, but got predicted value ndim: " f"{y_pred.ndim}, true value ndim: {y.ndim}.") + # 检查预测值的维度是否等于真实值的维度加1 if y_pred.ndim == y.ndim + 1: + # 如果是,则将预测值转换为概率分布,即使用np.argmax函数找到每个样本的预测类别,然后将预测类别转换为整数 y_pred = np.argmax(y_pred, axis=1) + # 检查预测值的维度是否等于真实值的维度,以及预测值的的数据类型是否为np.float16、np.float32或np.float64 if y_pred.ndim == y.ndim and y_pred.dtype in (np.float16, np.float32, np.float64): + # 如果是,则将预测值转换为整数,将大于等于self.threshold的预测值设置为1,其他值设置为0 y_pred = (y_pred >= self.threshold).astype(int) + # 接下来,将真实值和预测值按照num_classes进行拼接 trans = (y.reshape(-1) * self.num_classes + y_pred.reshape(-1)).astype(int) + # 然后使用np.bincount函数计算拼接后的结果的计数向量 bincount = np.bincount(trans, minlength=self.num_classes ** 2) + # 最后将计数向量转换为混淆矩阵 confusion_matrix = bincount.reshape(self.num_classes, self.num_classes) + # 将更新后的混淆矩阵赋值给self.confusion_matrix self.confusion_matrix += confusion_matrix + # 并将self._is_update设置为True self._is_update = True def eval(self): + # 用于计算混淆矩阵,需先在调用update函数后调用 """ Computes confusion matrix. Returns: numpy.ndarray, the computed result. """ - + # 首先检查self._is_update是否为True if not self._is_update: + # 如果是,则抛出一个错误RuntimeError raise RuntimeError("Please call the 'update' method before calling 'eval' method.") + # 然后,将混淆矩阵转换为浮点数类型 confusion_matrix = self.confusion_matrix.astype(float) + # 并计算目标、预测和所有类别下的混淆矩阵 matrix_target = confusion_matrix / confusion_matrix.sum(axis=1, keepdims=True) matrix_pred = confusion_matrix / confusion_matrix.sum(axis=0, keepdims=True) matrix_all = confusion_matrix / confusion_matrix.sum() + # 根据self.normalize的值,返回相应的混淆矩阵 normalize_dict = {"target": matrix_target, "prediction": matrix_pred, "all": matrix_all} + # 如果self.normalize设置为"no_norm" if self.normalize == "no_norm": + # 则直接返回混淆矩阵 return confusion_matrix - + + # 从normalize_dict中获取self.normalize对应的混淆矩阵 matrix = normalize_dict.get(self.normalize) + # 检查矩阵中是否存在np.nan值 if matrix[np.isnan(matrix)].size != 0: + # 如果存在np.nan值,则将它们设置为0 matrix[np.isnan(matrix)] = 0 + # 最后返回处理后的混淆矩阵 return matrix class ConfusionMatrixMetric(Metric): + # 继承自Metric。该类主要用于计算与混淆矩阵相关的指标,基于全量张量,并收集批次、类通道和迭代过程中的指标值。 + # 用户可以通过传入metric_name参数来选择支持 metrics 的名称,例如 "sensitivity"、"specificity"、"precision" 等。 + # 同时,用户还可以通过传入calculation_method参数来选择是否计算每个样本的指标值,以及使用哪种 reduction 方法来减少数据批次 r""" Computes metrics related to confusion matrix. The calculation based on full-scale tensor, average values of batch, class channel and iteration are collected. All metrics supported by the interface are listed in comments @@ -188,29 +235,51 @@ class ConfusionMatrixMetric(Metric): metric_name="sensitivity", calculation_method=False, decrease="mean"): + # 首先调用父类的__init__方法 super(ConfusionMatrixMetric, self).__init__() - + """ + 检查skip_channel、metric_name、calculation_method和decrease的类型 + """ + # 创建一个名为_ConfusionMatrix的类对象,并传入相关参数,用于计算混淆矩阵 self.confusion_matrix = _ConfusionMatrix(skip_channel=skip_channel, metric_name=metric_name, calculation_method=calculation_method, decrease=decrease) + # 检查skip_channel是否为bool值 self.skip_channel = validator.check_value_type("skip_channel", skip_channel, [bool]) + # 检查calculation_method是否为bool值 self.calculation_method = validator.check_value_type("calculation_method", calculation_method, [bool]) + # 检查metric_name是否为str类 self.metric_name = validator.check_value_type("metric_name", metric_name, [str]) + # 将skip_channel、calculation_method、metric_name和decrease属性设置为类的属性 decrease_list = ["none", "mean", "sum", "mean_batch", "sum_batch", "mean_channel", "sum_channel"] + # 检查decrease是否为str类 decrease = validator.check_value_type("decrease", decrease, [str]) + # 检查decrease的值是否在decrease_list中,如果在,则将其设置为类的属性decrease self.decrease = validator.check_string(decrease, decrease_list, "decrease") + # 清除类中可能残余的数据 self.clear() def clear(self): + # 用于清空类中的混淆矩阵计算结果数据 """Clears the internal evaluation result.""" + # 存储混淆矩阵的总量 self._total_num = 0 + # 存储类通道数量 self._class_num = 0 + # 存储真阳性 self._total_tp = 0.0 + # 存储假阳性 self._total_fp = 0.0 + # 存储真负性 self._total_tn = 0.0 + # 存储假负性 self._total_fn = 0.0 @rearrange_inputs def update(self, *inputs): + # 用于更新混淆矩阵的状态 + # inputs参数接受两个张量y_pred和y,以及可能的其他参数。y_pred是一个张量,表示预测结果,可以是 onehot 格式或者 category index 格式。 + # 对于二分类任务,y_pred的形状应该是 [N, C, ...] 或者 [N, ...],其中 N 大于 1。 + # 对于多分类任务,y_pred的形状应该是 [N, C, H, W] 或者 [N, H, W]。y是一个张量,表示真实标签,应该是 onehot 格式 """ Update state with predictions and targets. @@ -224,47 +293,68 @@ class ConfusionMatrixMetric(Metric): Raises: ValueError: If the number of the inputs is not 2. """ + # 检查inputs参数的数量是否为 2 if len(inputs) != 2: + # 如果不是,则抛出一个错误ValueError raise ValueError("For 'ConfusionMatrixMetric.update', it needs 2 inputs (predicted value, true value), " "but got {}.".format(len(inputs))) + # 接着,将inputs[0]和inputs[1]转换为适当的数据格式 y_pred = self._convert_data(inputs[0]) y = self._convert_data(inputs[1]) - + # 如果calculation_method为真 if self.calculation_method: + # 调用confusion_matrix方法计算混淆矩阵 score, not_nans = self.confusion_matrix(y_pred, y) + # 则计算平均值并累加到_total_num和_class_num中 + # 注:not_nans是一个张量,表示非空值的数量 + # 将结果与not_nans进行乘积 not_nans = int(not_nans.item()) + # 并累加到_total_num和_class_num中 self._total_num += score.item() * not_nans self._class_num += not_nans else: + # 调用confusion_matrix方法计算混淆矩阵 confusion_matrix = self.confusion_matrix(y_pred, y) + # 否则,直接累加混淆矩阵的各个元素 + # 最后,根据decrease方法对混淆矩阵进行处理 confusion_matrix, _ = _decrease_metric(confusion_matrix, "sum") + # 将混淆矩阵的各个元素累加到_total_tp、_total_fp、_total_tn和_total_fn中 self._total_tp += confusion_matrix[0].item() self._total_fp += confusion_matrix[1].item() self._total_tn += confusion_matrix[2].item() self._total_fn += confusion_matrix[3].item() def eval(self): + # 用于计算混淆矩阵,需先在调用update函数后调用 """ Computes confusion matrix metric. Returns: ndarray, the computed result. """ - + # 检查calculation_method是否为真 if self.calculation_method: + # 如果是,则检查_class_num是否为 0 if self._class_num == 0: + # 如果为 0,则抛出一个错误RuntimeError raise RuntimeError("The 'ConfusionMatrixMetric' can not be calculated, because the number of samples " "is 0, please check whether your inputs(predicted value, true value) are empty, or " "has called update method before calling eval method.") + # 如果非 0,则计算平均值并返回 return self._total_num / self._class_num - + # 创建了一个 numpy 数组confusion_matrix,其中包含四个元素:self._total_tp、self._total_fp、self._total_tn和self._total_fn。这个数组表示混淆矩阵,用于计算分类指标 confusion_matrix = np.array([self._total_tp, self._total_fp, self._total_tn, self._total_fn]) + # 否则,计算混淆矩阵并返回 return _compute_confusion_matrix_metric(self.metric_name, confusion_matrix) class _ConfusionMatrix: + # 用于计算混淆矩阵相关指标。类有两个参数:skip_channel和metric_name。skip_channel用于判断是否跳过第一个通道的计算。 + # metric_name用于定义指标的名称,可以设置为 industries 通用的指标名称或行业特定指标名称。 + # calculation_method用于判断是否计算每个样本的测量值。如果为真,则计算每个样本的测量值并累加;否则,直接返回混淆矩阵。 + # decrease参数用于定义 reduction 方法,用于减少计算结果的一个 batch """ Compute confusion matrix related metrics. @@ -286,13 +376,20 @@ class _ConfusionMatrix: def __init__(self, skip_channel=True, metric_name="hit_rate", calculation_method=False, decrease="mean"): + # 初始化其属性 super().__init__() + # skip_channel用于判断是否跳过第一个通道的计算,默认为 True self.skip_channel = skip_channel + # metric_name用于定义指标的名称,默认为 "hit_rate" self.metric_name = metric_name + # calculation_method用于判断是否计算每个样本的测量值,默认为 False self.calculation_method = calculation_method + # decrease参数用于定义 reduction 方法,默认为 "mean" self.decrease = decrease def __call__(self, y_pred, y): + # 用于计算混淆矩阵相关指标 + # 接收两个参数:y_pred和y。y_preds是输入数据,预期为二进制格式,第一维是批次。y是真实值,预期为二进制格式,第一维是批次。 """ 'y_preds' is expected to have binarized predictions and 'y' should be in one-hot format. @@ -304,41 +401,66 @@ class _ConfusionMatrix: ValueError: If `metric_name` is empty. ValueError: when `y_pred` has less than two dimensions. """ + # 检查y是否为二进制格式 if not np.all(y.astype(np.uint8) == y): + # 如果不是,则抛出一个错误ValueError raise ValueError("For 'ConfusionMatrix.update', the true value (input[1]) should be a binarized ndarray.") + # 计算y_pred的维度(dims),然后将其存储在变量dims中 dims = y_pred.ndim + # 接着,检查y_pred是否具有至少两个维度 if dims < 2: + # 如果不是,则抛出一个错误ValueError raise ValueError(f"For 'ConfusionMatrix.update', the predicted value (input[0]) should have at least 2 " f"dimensions, but got {dims}.") + # 如果dims小于2或(dims等于3且y_pred的最后一个维度为1) if dims == 2 or (dims == 3 and y_pred.shape[-1] == 1): + # 如果计算每个样本的测量值 if self.calculation_method: + # 则将calculation_method设置为False self.calculation_method = False + # 调用_get_confusion_matrix方法计算混淆矩阵 confusion_matrix = _get_confusion_matrix(y_pred=y_pred, y=y, skip_channel=self.skip_channel) + # 是否计算每个样本的测量值 if self.calculation_method: + # 如果是,则检查metric_name是否为字符串 if isinstance(self.metric_name, str): + # 如果是,则调用_compute_confusion_matrix_metric方法计算混淆矩阵指标 sub_confusion_matrix = _compute_confusion_matrix_metric(self.metric_name, confusion_matrix) + # 并调用_decrease_metric方法对结果进行处理 chart, not_nans = _decrease_metric(sub_confusion_matrix, self.decrease) + # 返回处理后的混淆矩阵指标chart和未处理的混淆矩阵指标not_nans return chart, not_nans + # 如果metric_name为空,则抛出一个错误ValueError if not self.metric_name: raise ValueError("For 'ConfusionMatrix', the argument 'metric_name' cannot be None.") - + + # 创建results数组用于存储计算结果 results = [] + # 遍历self.metric_name中的每个指标名称 for metric_name in self.metric_name: + # 调用_compute_confusion_matrix_metric方法计算混淆矩阵指标 sub_confusion_matrix = _compute_confusion_matrix_metric(metric_name, confusion_matrix) + # 调用_decrease_metric方法对结果进行处理 chart, not_nans = _decrease_metric(sub_confusion_matrix, self.decrease) + # 最后,将处理后的结果添加到results列表中 results.append(chart) results.append(not_nans) + # 返回计算后的结果 return results - + + # 返回混淆矩阵 return confusion_matrix def _get_confusion_matrix(y_pred, y, skip_channel=True): + # 接受两个参数:y_pred和y,以及一个可选参数skip_channel。y_pred是一个输入数据,应该是一个one-hot格式的数组,第一维是批量大小。 + # y是一个目标标签,也应该是一个one-hot格式的数组,第一维是批量大小。skip_channel是一个布尔参数,用于指定是否跳过第一个通道的计算。 + # 默认情况下,skip_channel为True """ The confusion matrix is calculated. An array of shape [BC4] is returned. The third dimension represents each channel of each sample in the input batch.Where B is the batch size and C is the number of classes to be calculated. @@ -354,79 +476,121 @@ def _get_confusion_matrix(y_pred, y, skip_channel=True): Raises: ValueError: when `y_pred` and `y` have different shapes. """ - + # 根据skip_channel的值,跳过第一个通道的计算 if not skip_channel: + # 如果是false,则根据y.shape[1]的大小,如果大于1,则跳过第一个通道,否则不跳过 y = y[:, 1:] if y.shape[1] > 1 else y + # 然后,根据y_pred.shape[1]的大小,如果大于1,则跳过第一个通道,否则不跳过 y_pred = y_pred[:, 1:] if y_pred.shape[1] > 1 else y_pred - + + # 将y和y_pred转换为浮点数类型 y = y.astype(float) y_pred = y_pred.astype(float) + # 检查y_pred和y是否有不同的形状,如果有不同的形状,则抛出一个ValueError异常 validator.check('y_shape', y.shape, 'y_pred_shape', y_pred.shape) + # 计算y_pred和y的批量大小batch_size和类别数量n_class batch_size, n_class = y_pred.shape[:2] + # 然后,将y_pred和y调整为新的形状,其中第二维是类别数 y_pred = y_pred.reshape(batch_size, n_class, -1) y = y.reshape(batch_size, n_class, -1) + # 接下来,计算真阳性(tp)、真负例(tn)、假阳性(fp)和假负例(fn)的数量 tp = ((y_pred + y) == 2).astype(float) tn = ((y_pred + y) == 0).astype(float) tp = tp.sum(axis=2) tn = tn.sum(axis=2) + # 最后,计算准确率(accuracy)、精确度(precision)、召回率(recall)和F1分数(f1_score) p = y.sum(axis=2) n = y.shape[-1] - p fn = p - tp fp = n - tn + # 将计算出的真阳性、假阳性、真负例和假负例数量封装到一个形状为[batch_size, n_class, 4]的数组中,并返回 return np.stack([tp, fp, tn, fn], axis=-1) def _decrease_mean(not_nans, chart): + # 计算不缺失值的数量和图表的平均值 + # 函数接受两个参数:not_nans和chart。not_nans是一个形状为[batch_size, n_class]的数组,用于存储不缺失值的数量。 + # chart是一个形状为[batch_size, n_class]的数组,用于存储图表的平均值 + + # 首先计算not_nans的行和列之和 not_nans = not_nans.sum(axis=1) + # 然后将结果除以不缺失值的数量,并将结果存储在chart中 chart = np.where(not_nans > 0, chart.sum(axis=1) / not_nans, np.zeros(1, dtype=float)) - + # 将not_nans转换为布尔数组,然后将布尔数组转换为浮点数数组 not_nans = (not_nans > 0).astype(float).sum(axis=0) + # 接着,计算沿着列方向的和,并将结果除以不缺失值的数量,并将结果存储在chart中 chart = np.where(not_nans > 0, chart.sum(axis=0) / not_nans, np.zeros(1, dtype=float)) - + # 最后,返回not_nans和chart return not_nans, chart def _decrease_sum(not_nans, chart): + # 计算不缺失值的数量和图表的总和 + # 计算not_nans的行和列之和,并将结果存储在not_nans中 not_nans = not_nans.sum(axis=(0, 1)) + # 接着,计算chart的行和列之和,并将结果存储在chart中 chart = np.sum(chart, axis=(0, 1)) + # 最后,返回not_nans和chart return not_nans, chart def _decrease_mean_batch(not_nans, chart): + # 批计算不缺失值的数量和图表的平均值 + # 首先计算not_nans的行之和,然后将结果除以不缺失值的数量,并将结果存储在chart中 not_nans = not_nans.sum(axis=0) + # 接着,计算不缺失值的总数,并将结果除以不缺失值的数量,并将结果存储在chart中 chart = np.where(not_nans > 0, chart.sum(axis=0) / not_nans, np.zeros(1, dtype=float)) + # 最后,返回not_nans和chart return not_nans, chart def _decrease_sum_batch(not_nans, chart): + # 批计算不缺失值的数量和图表的总和 + # 计算not_nans的行之和,并将结果存储在not_nans中 not_nans = not_nans.sum(axis=0) + # 接着,计算chart的行之和,并将结果存储在chart中 chart = chart.sum(axis=0) + # 最后,返回not_nans和chart return not_nans, chart def _decrease_mean_channel(not_nans, chart): + # 通道法计算不缺失值的数量和图表的平均值 + # 首先计算not_nans的列之和,然后将结果除以不缺失值的数量,并将结果存储在chart中 not_nans = not_nans.sum(axis=1) + # 接着,计算不缺失值的总数,并将结果除以不缺失值的数量,并将结果存储在chart中 chart = np.where(not_nans > 0, chart.sum(axis=1) / not_nans, np.zeros(1, dtype=float)) + # 最后,返回not_nans和chart return not_nans, chart def _decrease_sum_channel(not_nans, chart): + # 通道法计算不缺失值的数量和图表的总和 + # 计算not_nans的列之和,并将结果存储在not_nans中 not_nans = not_nans.sum(axis=1) + # 接着,计算chart的列之和,并将结果存储在chart中 chart = chart.sum(axis=1) + # # 最后,返回not_nans和chart return not_nans, chart def _decrease_none(not_nans, chart): + # 啥也不干地计算不缺失值的数量和图表 + + # 函数直接返回not_nans和chart,不做任何处理 return not_nans, chart def _decrease_metric(chart, decrease="mean"): + # 用于减少计算指标。函数接受两个参数:chart和decrease。 + # chart是一个形状为[batch_size, n_class]的数组,用于存储计算出的测量评分。 + # decrease是一个字符串,定义了减少计算结果的方法。默认值为"mean" """ This function is used to reduce the calculated metrics for each class of each example. @@ -437,10 +601,15 @@ def _decrease_metric(chart, decrease="mean"): when 'calculation_method' is True. Default: "mean". """ + # 首先计算chart中缺失值的位置,并将结果存储在nans中 nans = np.isnan(chart) + # 接着,将nans转换为布尔数组,,然后将布尔数组转换为浮点数数组,接着,计算not_nans的行和列之和,并将结果存储在not_nans中 not_nans = (~nans).astype(float) + # 将chart中缺失值的位置设置为0 chart[nans] = 0 - + # 然后,定义了一个名为decrease_dict的字典,用于存储不同的减少方法。decrease_dict的键是减少方法的字符串,值是对应的函数。 + # _decrease_mean、_decrease_sum、_decrease_mean_batch、_decrease_sum_batch、 + # _decrease_mean_channel和_decrease_sum_channel是这些函数在上方的具体实现 decrease_dict = {"mean": _decrease_mean(not_nans, chart), "sum": _decrease_sum(not_nans, chart), "mean_batch": _decrease_mean_batch, @@ -448,125 +617,177 @@ def _decrease_metric(chart, decrease="mean"): "mean_channel": _decrease_mean_channel(not_nans, chart), "sum_channel": _decrease_sum_channel(not_nans, chart), "none": _decrease_none(not_nans, chart)} + # 最后,根据decrease的值,调用相应的减少方法,并将结果存储在not_nans和chart中 not_nans, chart = decrease_dict.get(decrease) + # 最后,返回chart和not_nans return chart, not_nans - +""" +以下这些函数都接受两个参数:tp和p(或tn和n),用于计算不同类型的性能指标 +""" def _calculate_tpr(tp, p): + # 计算True Positive Rate (TPR)性能指标 """Calculate tpr.""" return tp, p def _calculate_tnr(tn, n): + # 计算True Negative Rate (TNR)性能指标 """Calculate tnr.""" return tn, n def _calculate_ppv(tp, fp): + # 计算Positive Predictive Value (PPV)性能指标 """Calculate ppv.""" return tp, (tp + fp) def _calculate_npv(tn, fn): + # 计算Negative Predictive Value (NPV)性能指标 """Calculate npv.""" return tn, (tn + fn) def _calculate_fnr(fn, p): + # 计算False Negative Rate (FNR)性能指标 """Calculate fnr.""" return fn, p def _calculate_fpr(fp, n): + # 计算False Positive Rate (FPR)性能指标 """Calculate fpr.""" return fp, n def _calculate_fdr(tp, fp): + # 计算False Discovery Rate (FDR)性能指标 """Calculate fdr.""" return fp, (fp + tp) def _calculate_for(tn, fn): + # 计算False Omission Rate (FOR)性能指标 """Calculate for.""" return fn, (fn + tn) def _calculate_pt(tp, tn, p, n): + # 计算Precision (PT)性能指标 """Calculate pt.""" + # 首先使用np.where函数计算真阳率(TPR)和真阴率(TNR) tpr = np.where(p > 0, tp / p, np.array(float("nan"))) tnr = np.where(n > 0, tn / n, np.array(float("nan"))) + # 然后计算分子(numerator)和分母(denominator) numerator = np.sqrt(tpr * (1.0 - tnr)) + tnr - 1.0 denominator = tpr + tnr - 1.0 + # 最后,返回分子和分母的平方根,以计算PT值 return numerator, denominator def _calculate_ts(tp, fp, fn): + # 计算Recall (TS)性能指标 """Calculate ts.""" return tp, (tp + fn + fp) def _calculate_acc(tp, tn, p, n): + # 计算Accuracy (ACC)性能指标 """Calculate acc.""" return (tp + tn), (p + n) def _calculate_ba(tp, tn, p, n): + # 计算Balanced Accuracy (BA)性能指标 """Calculate ba.""" + # 首先,计算True Positive Rate (TPR) tpr = np.where(p > 0, tp / p, np.array(float("nan"))) + # 接着,计算True Negative Rate (TNR) tnr = np.where(n > 0, tn / n, np.array(float("nan"))) + # 最后,计算numerator,denominator numerator, denominator = (tpr + tnr), 2.0 + # 返回numerator和denominator return numerator, denominator def _calculate_f1(tp, fp, fn): + # 计算F1-score (F1)性能指标 """Calculate f1.""" return tp * 2.0, (tp * 2.0 + fn + fp) def _calculate_mcc(tp, fp, tn, fn): + # 计算Matthews Correlation Coefficient (MCC)性能指标 """Calculate mcc.""" + # 首先,计算numerator numerator = tp * tn - fp * fn + # 接着,计算denominator denominator = np.sqrt((tp + fp) * (tp + fn) * (tn + fp) * (tn + fn)) + # 最后,返回numerator和denominator return numerator, denominator def _calculate_fm(tp, fp, p): + # 计算Fowlkes-Mallows Index (FM)性能指标 """Calculate fm.""" + # 首先,计算True Positive Rate (TPR) tpr = np.where(p > 0, tp / p, np.array(float("nan"))) + # 接着,计算准确率(Accuracy) ppv = np.where((tp + fp) > 0, tp / (tp + fp), np.array(float("nan"))) + # 最后,计算numerator numerator = np.sqrt(ppv * tpr) + # 设置denominator = 1.0 denominator = 1.0 + # 最后,返回numerator和denominator return numerator, denominator def _calculate_bm(tp, tn, p, n): + # 计算Brier Score (BM)性能指标 """Calculate bm.""" + # 首先,计算True Positive Rate (TPR) tpr = np.where(p > 0, tp / p, np.array(float("nan"))) + # 接着,计算True Negative Rate (TNR) tnr = np.where(n > 0, tn / n, np.array(float("nan"))) + # 最后,计算numerator numerator = tpr + tnr - 1.0 + # 设置denominator = 1.0 denominator = 1.0 + # 最后,返回numerator和denominator return numerator, denominator def _calculate_mk(tp, fp, tn, fn): + # 计算Matthews Correlation Coefficient (MCC)性能指标 """Calculate mk.""" + # 首先,计算准确率(Accuracy) ppv = np.where((tp + fp) > 0, tp / (tp + fp), np.array(float("nan"))) + # 接着,计算假正例率(False Positive Rate) npv = np.where((tn + fn) > 0, tn / (tn + fn), np.array(float("nan"))) + # 计算假负例率(False Negative Rate) npv = tn / (tn + fn) + # 接下来,计算numerator numerator = ppv + npv - 1.0 + # 最后,计算denominator denominator = 1.0 + # 最后,返回numerator和denominator return numerator, denominator def _compute_confusion_matrix_metric(metric_name, confusion_matrix): + # 该函数用于计算与混淆矩阵相关的度量 + # metric_name (字符串):表示要计算的混淆矩阵的指标名称。混淆矩阵是一种用于评估分类器性能的指标,它将实际的分类结果与预测的分类结果进行比较。 + # 不同的指标可能有不同的别名,例如“准确率”(Accuracy)和“正确率”(Correctness)等。您可以输入这些名称之一,或者直接输入混淆矩阵的指标名称。 + # confusion_matrix (数组):表示要计算的混淆矩阵。这是一个numpy数组,其中包含实际分类结果和预测分类结果。 + # _get_confusion_matrix是一个函数,用于从给定的实际分类结果和预测分类结果中计算混淆矩阵。 """ This function is used to compute confusion matrix related metric. @@ -580,23 +801,31 @@ def _compute_confusion_matrix_metric(metric_name, confusion_matrix): NotImplementedError: when specify a not implemented metric_name. """ - + # 首先检查metric_name是否有效 metric = _check_metric_name(metric_name) + # 然后计算混淆矩阵的维度 input_dim = confusion_matrix.ndim + # 如果维度为1 if input_dim == 1: + # 则扩展最后一个维度以使其为4 confusion_matrix = np.expand_dims(confusion_matrix, 0) + # 检查混淆矩阵的最后一个维度是否为4 if confusion_matrix.shape[-1] != 4: + # 如果不是,则抛出一个 ValueError 异常,提示用户混淆矩阵的维度不正确 raise ValueError(f"For 'ConfusionMatrix', the size of the last dimension of confusion_matrix should be 4, " f"but got {confusion_matrix.shape[-1]}.") + # 接下来,从混淆矩阵中提取真阳例(tp)、假阳例(fp)、真阴例(tn)和假阴例(fn) tp = confusion_matrix[..., 0] fp = confusion_matrix[..., 1] tn = confusion_matrix[..., 2] fn = confusion_matrix[..., 3] + # 并计算正例(p)和负例(n) p = tp + fn n = fp + tn + # 定义了一个字典metric_name_dict,其中包含了各种指标的计算函数 metric_name_dict = {"tpr": _calculate_tpr(tp, p), "tnr": _calculate_tnr(tn, n), "ppv": _calculate_ppv(tp, fp), @@ -614,16 +843,23 @@ def _compute_confusion_matrix_metric(metric_name, confusion_matrix): "fm": _calculate_fm(tp, fp, p), "bm": _calculate_bm(tp, tn, p, n), "mk": _calculate_mk(tp, fp, tn, fn)} + + # 从metric_name_dict字典中获取输入的metric_name对应的计算结果,并将结果存储在numerator和denominator变量中 numerator, denominator = metric_name_dict.get(metric) + # 检查denominator是否是一个numpy数组 if isinstance(denominator, np.ndarray): + # 使用numpy的where函数对denominator进行处理。处理后的结果将赋值给result变量 result = np.where(denominator != 0, numerator / denominator, np.array(float("nan"))) else: + # 直接计算numerator除以denominator的结果,并将结果存储在result变量中 result = numerator / denominator + # 返回result变量 return result def _check_metric_name(metric_name): + # 这个函数主要用于处理混淆矩阵中的指标名称,使它们更加简洁 """ There are many metrics related to confusion matrix, and some of the metrics have more than one names. In addition, some of the names are very long. Therefore, this function is used to check and simplify the name. @@ -634,8 +870,11 @@ def _check_metric_name(metric_name): Raises: NotImplementedError: when the metric is not implemented. """ + # 首先将指标名称中的空格替换为下划线 metric_name = metric_name.replace(" ", "_") + # 并将名称转换为小写 metric_name = metric_name.lower() + # 然后,使用一个字典metric_name_dict来映射指标名称到简化后的名称 metric_name_dict = {"sensitivity": "tpr", "recall": "tpr", "hit_rate": "tpr", @@ -682,9 +921,10 @@ def _check_metric_name(metric_name): "markedness": "mk", "deltap": "mk", "mk": "mk"} - + # 最后,函数从字典中获取指标的简化的名称,并将其返回 metric_name_info = metric_name_dict.get(metric_name) + # 如果在字典中找不到匹配的指标名称,那么函数将抛出一个NotImplementedError异常,表示该指标尚未实现 if metric_name_info is None: raise NotImplementedError("The metric is not implemented.") diff --git a/mindspore/python/mindspore/nn/metrics/cosine_similarity.py b/mindspore/python/mindspore/nn/metrics/cosine_similarity.py index 7623973091a..2312f6110d6 100644 --- a/mindspore/python/mindspore/nn/metrics/cosine_similarity.py +++ b/mindspore/python/mindspore/nn/metrics/cosine_similarity.py @@ -13,12 +13,27 @@ # limitations under the License. # ============================================================================ """CosineSimilarity.""" +# CosineSimilarity模块实现了两个函数:cosine_similarity和_checkparam。 +# cosine_similarity函数用于计算两个向量之间的余弦相似度,_checkparam函数用于验证参数是否符合要求 + +# 导入numpy库,用于处理矩阵和向量计算 import numpy as np +# 从mindspore库中导入Validator类,用于参数验证 from mindspore._checkparam import Validator as validator +# 从当前模块中导入Metric类和rearrange_inputs函数 from .metric import Metric, rearrange_inputs class CosineSimilarity(Metric): + # 用于计算两个向量之间的余弦相似度 + """ + 参数: + similarity (str):可选为'dot'或'cosine',表示计算余弦相似度时使用的公式。默认值为'cosine'。 + reduction (str):表示计算结果 reduction的方式。可选为'none'、'sum'或'mean',分别表示不进行reduction、按列求和、按列求平均。默认值为'none'。 + zero_diagonal (bool):表示是否将矩阵对角线上的值设置为0。默认值为True。 + + """ + """ Computes representation similarity. @@ -45,41 +60,63 @@ class CosineSimilarity(Metric): [0.95162452 0.86146098 0.]] """ def __init__(self, similarity='cosine', reduction='none', zero_diagonal=True): + # 初始化类参数 + + # 调用父类的构造函数 super().__init__() + # 定义两个列表similarity_list和reduction_list,分别包含可选的余弦相似度和reduction方式,用于后续验证 similarity_list = ['dot', 'cosine'] reduction_list = ['none', 'sum', 'mean'] + # 检查similarity参数是否为字符串,并在similarity_list中进行验证 similarity = validator.check_value_type("similarity", similarity, [str]) self.similarity = validator.check_string(similarity, similarity_list, "similarity") + # 检查reduction参数是否为字符串,并在reduction_list中进行验证 reduction = validator.check_value_type("reduction", reduction, [str]) self.reduction = validator.check_string(reduction, reduction_list, "reduction") + # 检查zero_diagonal参数是否为布尔值 self.zero_diagonal = validator.check_value_type("zero_diagonal", zero_diagonal, [bool]) + # 初始化一个变量self.sqr_mtx_res,用于存储计算结果 self.sqr_mtx_res = 0 + # 清空类中残余的数据 self.clear() + # 设置一个标志位self._is_update,用于判断是否需要更新计算结果,初始为否 self._is_update = None def clear(self): + # 用于清空类中的数据 """Clears the internal evaluation result.""" + # 将计算结果设置为0 self.sqr_mtx_res = 0 + # 表示未更新计算结果 self._is_update = False @rearrange_inputs def update(self, inputs): + # 用于更新内部评估结果 """ Updates the internal evaluation result with 'inputs'. Args: inputs (Union[Tensor, list, numpy.ndarray]): The input matrix. """ + # 使用_convert_data函数将输入数据转换为numpy数组 input_data = self._convert_data(inputs) + # 如果self.similarity为'cosine',则计算每个输入向量的二范数,然后将输入向量除以相应的expand_dims + # 注:expand_dims函数为输入数组添加一个新维度 if self.similarity == 'cosine': + # 计算每个输入向量的二范数 data = np.linalg.norm(input_data, ord=2, axis=1) + # 将输入向量除以相应的expand_dims input_data = input_data / np.expand_dims(data, 1) + # 更新self.sqr_mtx_res为输入向量与其转置的点积 self.sqr_mtx_res = np.dot(input_data, input_data.transpose(1, 0)) + # 设置self._is_update为True,表示已经更新了计算结果 self._is_update = True def eval(self): + # 用于计算相似度矩阵,通常在update函数后调用 """ Computes the similarity matrix. @@ -89,16 +126,26 @@ class CosineSimilarity(Metric): Raises: RuntimeError: If the update method is not called first, an error will be reported. """ + # 首先检查是否已经调用过update方法 if not self._is_update: + # 如果没有,则抛出一个RuntimeError raise RuntimeError("Please call the 'update' method before calling 'eval' method.") + # 如果self.zero_diagonal为True if self.zero_diagonal: + # 则将相似度矩阵的对角线上的值设置为0 np.fill_diagonal(self.sqr_mtx_res, 0) + # 如果self.reduction为'mean'(平均) if self.reduction == 'mean': + # 则计算相似度矩阵的列平均值 self.sqr_mtx_res = np.mean(self.sqr_mtx_res, axis=-1) + + # 如果self.reduction为'sum'(求和) if self.reduction == 'sum': + # 则计算相似度矩阵的列和 self.sqr_mtx_res = np.sum(self.sqr_mtx_res, axis=-1) + # 返回计算得到的相似度矩阵 return self.sqr_mtx_res diff --git a/mindspore/python/mindspore/nn/metrics/dice.py b/mindspore/python/mindspore/nn/metrics/dice.py index cac718453a8..36f91a36f56 100644 --- a/mindspore/python/mindspore/nn/metrics/dice.py +++ b/mindspore/python/mindspore/nn/metrics/dice.py @@ -13,12 +13,19 @@ # limitations under the License. # ============================================================================ """Dice""" +# 继承自Metric类。Dice类主要用于计算Dice系数,是一种常用的评估方法,用于衡量两个集合之间的相似度 + +# 导入numpy库,用于处理矩阵运算 import numpy as np +# 从mindspore库中导入_checkparam.Validator类,用于参数验证 from mindspore._checkparam import Validator as validator +# 从当前模块中导入Metric类,用于继承 from .metric import Metric, rearrange_inputs class Dice(Metric): + # 用于衡量两个集合之间的相似度。Dice系数计算公式如下:dice = \frac{2 * (pred \bigcap true)}{pred \bigcup true} + # 其中,pred表示预测结果,true表示真实结果;smooth是一个平滑参数,用于提高数值稳定性。 r""" The Dice coefficient is a set similarity metric. It is used to calculate the similarity between two samples. The value of the Dice coefficient is 1 when the segmentation result is the best and is 0 when the segmentation result @@ -50,20 +57,32 @@ class Dice(Metric): """ def __init__(self, smooth=1e-5): + # 初始化类参数 + + # 调用父类Metric的__init__方法,传入平滑参数smooth super(Dice, self).__init__() + # 初始化self.smooth为正浮点数,用于计算Dice系数,并检查其是否为正浮点数 self.smooth = validator.check_positive_float(smooth, "smooth") + # 用于存储Dice系数的和 self._dice_coeff_sum = 0 + # 用于存储样本数量 self._samples_num = 0 + # 清空类中残余的数据 self.clear() def clear(self): + # 用于清空内部评估结果 """Clears the internal evaluation result.""" + # 将self._dice_coeff_sum重置为0 self._dice_coeff_sum = 0 + # 将self._samples_num的值重置为0 self._samples_num = 0 @rearrange_inputs def update(self, *inputs): + # 用于更新内部评估结果 + # 接受两个输入:预测值y_pred和真实值y。预测值和真实值的形状都是(N, ...) """ Updates the internal evaluation result :math:`y_pred` and :math:`y`. @@ -75,26 +94,39 @@ class Dice(Metric): ValueError: If the number of the inputs is not 2. ValueError: If y_pred and y do not have the same shape. """ + + """ + 首先获取输入参数并检查 + """ + # 检查输入数量是否为2 if len(inputs) != 2: + # 如果不是,则抛出一个ValueError raise ValueError("For 'Dice.update', it needs 2 inputs (predicted value, true value), " "but got {}.".format(len(inputs))) + # 获取输入数组中的值并将输入的预测值和真实值转换为numpy.ndarray类型 y_pred = self._convert_data(inputs[0]) y = self._convert_data(inputs[1]) + # 更新self._samples_num,将输入的样本数量累加到self._samples_num中 self._samples_num += y.shape[0] + # 检查预测值和真实值的形状是否相同 if y_pred.shape != y.shape: + # 如果不相同,则抛出一个ValueError raise ValueError(f"For 'Dice.update', predicted value (input[0]) and true value (input[1]) " f"should have same shape, but got predicted value shape: {y_pred.shape}, " f"true value shape: {y.shape}.") - + # 首先,将预测值y_pred和真实值y转换为1D数组,然后使用numpy的dot函数计算交集和并集 intersection = np.dot(y_pred.flatten(), y.flatten()) unionset = np.dot(y_pred.flatten(), y_pred.flatten()) + np.dot(y.flatten(), y.flatten()) + # 计算Dice系数 single_dice_coeff = 2 * float(intersection) / float(unionset + self.smooth) + # 将单个Dice系数累加到self._dice_coeff_sum中 self._dice_coeff_sum += single_dice_coeff def eval(self): + # 用于计算Dice系数,通常在update函数后调用 r""" Computes the Dice. @@ -104,9 +136,12 @@ class Dice(Metric): Raises: RuntimeError: If the total number of samples is 0. """ + # 首先检查self._samples_num是否为0 if self._samples_num == 0: + # 如果是,则抛出一个RuntimeError raise RuntimeError("The 'Dice coefficient' can not be calculated, because the number of samples is 0, " "please check whether your inputs(predicted value, true value) are empty, or has " "called update method before calling eval method.") + # 然后,计算Dice系数的和,并将其除以self._samples_num,得到最终的结果 return self._dice_coeff_sum / float(self._samples_num) diff --git a/mindspore/python/mindspore/nn/metrics/error.py b/mindspore/python/mindspore/nn/metrics/error.py index 5226d7eb29b..fc21961ddf3 100644 --- a/mindspore/python/mindspore/nn/metrics/error.py +++ b/mindspore/python/mindspore/nn/metrics/error.py @@ -13,11 +13,16 @@ # limitations under the License. # ============================================================================ """Error.""" +# 定义了两个类,分别是MAE(均方误差)和MSE(均方误差)。这两个类都继承自Metric类,用于计算预测值和真实值之间的相关指标 +# 导入了numpy库,,用于处理矩阵和向量计算 import numpy as np +# 导入Metric类,Metric类是用于计算评估指标的基类,rearrange_inputs是用于重新排列输入的辅助函数 from .metric import Metric, rearrange_inputs class MAE(Metric): + # MAE类用于计算预测值和真实值之间的平均绝对误差(MAE),即每个元素预测值和真实值之间的绝对差值的平均值 + # 平均绝对误差(MAE)是衡量模型预测结果与真实值之间差异程度的一种指标,数值越小,表示模型预测结果与真实值越接近 r""" Calculates the mean absolute error(MAE). @@ -47,16 +52,22 @@ class MAE(Metric): 0.037499990314245224 """ def __init__(self): + # 构造函数,接受一个平滑参数smooth,用于计算Dice系数 super(MAE, self).__init__() + # 清空类中残余的数据 self.clear() def clear(self): + # 用于清空内部评估结果 """Clears the internal evaluation result.""" + # _abs_error_sum用于存储累计的绝对误差和,重置其为0 self._abs_error_sum = 0 + # _samples_num用于存储样本数量,重置其为0 self._samples_num = 0 @rearrange_inputs def update(self, *inputs): + # 用于更新内部评估结果。接受两个输入:预测值y_pred和真实值y """ Updates the internal evaluation result :math:`y_{pred}` and :math:`y`. @@ -67,16 +78,23 @@ class MAE(Metric): Raises: ValueError: If the number of the input is not 2. """ + # 通过len(inputs) != 2检查输入是否只有两个 if len(inputs) != 2: + # 如果不是,则抛出一个ValueError raise ValueError("For 'MAE.update', it needs 2 inputs (predicted value, true value), " "but got {}.".format(len(inputs))) + # 然后,获取输入值并将输入转换为numpy数组 y_pred = self._convert_data(inputs[0]) y = self._convert_data(inputs[1]) + # 并计算预测值和真实值之间的绝对差值 abs_error_sum = np.abs(y.reshape(y_pred.shape) - y_pred) + # 将绝对差值的累加和self._abs_error_sum相加 self._abs_error_sum += abs_error_sum.sum() + # 并将样本数量累加到self._samples_num中 self._samples_num += y.shape[0] def eval(self): + # 用于计算MAE """ Computes the mean absolute error(MAE). @@ -86,14 +104,19 @@ class MAE(Metric): Raises: RuntimeError: If the total number of samples is 0. """ + # 首先检查self._samples_num是否为0 if self._samples_num == 0: + # 如果是,则抛出一个RuntimeError raise RuntimeError("The 'MAE' can not be calculated, because the number of samples is 0, " "please check whether your inputs(predicted value, true value) are empty, " "or has called update method before calling eval method.") + # 然后计算Dice系数的和,并将其除以self._samples_num,得到最终的结果 return self._abs_error_sum / self._samples_num class MSE(Metric): + # MSE类用于计算预测值和真实值之间的均方误差(MSE),即每个元素预测值和真实值之间的平方差之和的平均值。 + # 均方误差(MSE)是衡量模型预测结果与真实值之间差异程度的一种指标,数值越小,表示模型预测结果与真实值越接近 r""" Measures the mean squared error(MSE). @@ -123,16 +146,22 @@ class MSE(Metric): 0.0031250009778887033 """ def __init__(self): + # 构造函数,与MAE类相同,接受一个平滑参数smooth,用于计算Dice系数。 super(MSE, self).__init__() + # 清空类中残余的数据 self.clear() def clear(self): + # 与MAE类相同,用于清空内部评估结果 """Clear the internal evaluation result.""" + # _abs_error_sum用于存储累计的绝对误差和,重置其为0 self._squared_error_sum = 0 + # _samples_num用于存储样本数量,重置其为0 self._samples_num = 0 @rearrange_inputs def update(self, *inputs): + # 与MAE类相同,用于更新内部评估结果。接受两个输入:预测值y_pred和真实值y """ Updates the internal evaluation result :math:`y_{pred}` and :math:`y`. @@ -143,17 +172,24 @@ class MSE(Metric): Raises: ValueError: If the number of inputs is not 2. """ + # 通过len(inputs) != 2检查输入是否只有两个 if len(inputs) != 2: + # 如果不是,则抛出一个ValueError raise ValueError("For 'MSE.update', it needs 2 inputs (predicted value, true value), " "but got {}.".format(len(inputs))) + # 然后,获取输入值并将输入转换为numpy数组 y_pred = self._convert_data(inputs[0]) y = self._convert_data(inputs[1]) + # 计算了预测值和真实值之间的平方差 squared_error_sum = np.power(y.reshape(y_pred.shape) - y_pred, 2) + # 并将平方差累加和self._squared_error_sum相加 self._squared_error_sum += squared_error_sum.sum() + # 并将样本数量累加到self._samples_num中 self._samples_num += y.shape[0] def eval(self): + # 用于计算MSE,通常在update函数后调用 """ Computes the mean squared error(MSE). @@ -163,8 +199,11 @@ class MSE(Metric): Raises: RuntimeError: If the number of samples is 0. """ + # 首先检查self._samples_num是否为0 if self._samples_num == 0: + # 如果是,则抛出一个RuntimeError raise RuntimeError("The 'MSE' can not be calculated, because the number of samples is 0, " "please check whether your inputs(predicted value, true value) are empty, " "or has called update method before calling eval method.") + # 然后计算平方误差的总和,并将其除以self._samples_num,得到最终的结果 return self._squared_error_sum / self._samples_num diff --git a/mindspore/python/mindspore/nn/metrics/fbeta.py b/mindspore/python/mindspore/nn/metrics/fbeta.py index 7d519ef5857..10778962ac3 100755 --- a/mindspore/python/mindspore/nn/metrics/fbeta.py +++ b/mindspore/python/mindspore/nn/metrics/fbeta.py @@ -13,13 +13,25 @@ # limitations under the License. # ============================================================================ """Fbeta.""" +# 用于计算F分数。F分数是一种评估模型预测结果与真实值之间相关性的指标,可以用于衡量模型的精确率和召回率 + +# 导入了sys模块,用于处理Python运行时状态 import sys +# 导入了numpy库,用于处理数值计算 import numpy as np +# 导入了mindspore._checkparam.Validator类,用于参数验证 from mindspore._checkparam import Validator as validator +# 导入了mindspore.metric.Metric类,用于计算评估指标 + +# 导入了_check_onehot_data函数,用于检查输入数据是否为one-hot编码。 + +# 导入了rearrange_inputs函数,用于处理输入数据,将其转换为numpy数组 from .metric import Metric, rearrange_inputs, _check_onehot_data class Fbeta(Metric): + # 用于计算F分数。F分数是一种评估模型预测结果与真实值之间相关性的指标,可以用于衡量模型的精确率和召回率。 + # Fbeta分数通过计算 precision 和 recall 的加权平均值来计算,其中beta是一个权重系数 r""" Calculates the Fbeta score. @@ -49,22 +61,36 @@ class Fbeta(Metric): [0.66666667 0.66666667] """ def __init__(self, beta): + # 在构造函数中,首先调用父类的构造函数 super(Fbeta, self).__init__() + # 然后初始化一个用于存储eps的变量self.eps self.eps = sys.float_info.min + # 检查beta是否大于0 if not beta > 0: + # 如果不满足条件,则抛出一个ValueError异常 raise ValueError("For 'Fbeta', the argument 'beta' must be greater than 0, but got {}.".format(beta)) + # 以及一个用于存储beta的变量self.beta self.beta = beta + # 清空类中残余的数据 self.clear() def clear(self): + # 用于清空内部评估结果 """Clears the internal evaluation result.""" + # 它将_true_positives的值重置为0 self._true_positives = 0 + # 它将_actual_positives的值重置为0 self._actual_positives = 0 + # 它将_positives的值重置为0 self._positives = 0 + # 它将_class_num的值重置为0 self._class_num = 0 @rearrange_inputs def update(self, *inputs): + # 用于更新内部评估结果y_pred和y。inputs是一个元组,包含y_pred和y。 + # y_pred通常是一个浮点数列表,范围在0到1之间,形状为(N, C), + # 其中N是样本数量,C是类别数量。y是一个整数列表,形状为(N, C)或(N,) """ Updates the internal evaluation result `y_pred` and `y`. @@ -79,39 +105,60 @@ class Fbeta(Metric): ValueError: class numbers of last input predicted data and current predicted data not match. ValueError: If the predicted value and true value contain different classes. """ + # 首先检查输入数据的数量是否为2 if len(inputs) != 2: + # 如果不满足条件,则抛出一个ValueError异常 raise ValueError("For 'Fbeta.update', it needs 2 inputs (predicted value, true value), " "but got {}.".format(len(inputs))) + # 然后获取输入值并将输入数据转换为numpy数组 y_pred = self._convert_data(inputs[0]) y = self._convert_data(inputs[1]) + # 并检查y_pred是否为one-hot编码 if y_pred.ndim == y.ndim and _check_onehot_data(y): + # 如果满足条件,将y_pred和y转换为二进制数组 y = y.argmax(axis=1) + # 检查_class_num是否为0 if self._class_num == 0: + # 如果是,则将其设置为y_pred的类别数量 self._class_num = y_pred.shape[1] + # 如果y_pred的类别数量与_class_num不相等 elif y_pred.shape[1] != self._class_num: + # 则抛出一个ValueError异常,提示用户检查输入的预测数据 raise ValueError("For 'Fbeta.update', class number not match, last input predicted data contain {} " "classes, but current predicted data contain {} classes, please check your " "predicted value(inputs[0]).".format(self._class_num, y_pred.shape[1])) + # 最后,将class_num设置为_class_num的值 class_num = self._class_num + # 首先检查y的最大值是否大于class_num if y.max() + 1 > class_num: + # 如果是,则抛出一个ValueError异常,提示用户检查输入的预测数据和真实数据 raise ValueError("For 'Fbeta.update', predicted value(inputs[0]) and true value(inputs[1]) " "should contain same classes, but got predicted value contains {} classes" " and true value contains {} classes.".format(class_num, y.max() + 1)) + # 接下来,将y转换为一个one-hot编码的数组 y = np.eye(class_num)[y.reshape(-1)] + # 并将y_pred转换为一个二进制数组 indices = y_pred.argmax(axis=1).reshape(-1) + # 最后,将y和y_pred调整为具有相同类别的形状 y_pred = np.eye(class_num)[indices] + # 计算y_pred中每个类别的真阳性数量 positives = y_pred.sum(axis=0) + # 计算y_pred中每个类别的正例数量 actual_positives = y.sum(axis=0) + # 计算y_pred中每个类别的实际正例数量 true_positives = (y * y_pred).sum(axis=0) + # 并将上述这些值添加到相应的_true_positives、_positives和_actual_positives属性中 + # 这样,在计算F分数时,这些变量将用于存储新的评估结果 self._true_positives += true_positives self._positives += positives self._actual_positives += actual_positives def eval(self, average=False): + # 用于计算F分数。函数接受一个布尔参数average,用于指定是否计算平均F分数,该函数需要在update函数后调用 """ Computes the fbeta. @@ -121,21 +168,29 @@ class Fbeta(Metric): Returns: numpy.ndarray or numpy.float64, the computed result. """ + # 首先,检查average参数的类型是否为布尔值,如果不满足条件,则抛出一个TypeError异常 validator.check_value_type("average", average, [bool], self.__class__.__name__) + # 然后,检查_class_num是否为0 if self._class_num == 0: + # 如果是,则抛出一个RuntimeError异常,提示用户检查输入的数据是否为空,或者是否已经调用了update方法 raise RuntimeError("The 'Fbeta' can not be calculated, because the number of samples is 0, " "please check whether your inputs(predicted value, true value) are empty, " "or has called update method before calling eval method.") + # 接下来,计算F分数 fbeta = (1.0 + self.beta ** 2) * self._true_positives / \ (self.beta ** 2 * self._actual_positives + self._positives + self.eps) + # 最后,如果average为True if average: + # 则返回F分数的平均值 return fbeta.mean() + # 否则返回完整的F分数数组 return fbeta class F1(Fbeta): + # 定义了一个名为F1的子类,继承自Fbeta类。F1类主要用于计算F1分数,它是Fbeta类的特例,当beta为1时成立 r""" Calculates the F1 score. F1 is a special case of Fbeta when beta is 1. Refer to class :class:`mindspore.nn.Fbeta` for more details. @@ -159,4 +214,5 @@ class F1(Fbeta): [0.66666667 0.66666667] """ def __init__(self): + # F1分数的计算公式为:F_1=\frac{2\cdot true\_positive}{2\cdot true\_positive + false\ super(F1, self).__init__(1.0) diff --git a/mindspore/python/mindspore/nn/metrics/hausdorff_distance.py b/mindspore/python/mindspore/nn/metrics/hausdorff_distance.py index 450699ab0bc..427af420c5e 100644 --- a/mindspore/python/mindspore/nn/metrics/hausdorff_distance.py +++ b/mindspore/python/mindspore/nn/metrics/hausdorff_distance.py @@ -13,17 +13,29 @@ # limitations under the License. # ============================================================================ """HausdorffDistance.""" +# 定义了一个名为HausdorffDistance的类,该类继承自collections.abc模块中的Sequence类,并实现了__call__方法。 +# HausdorffDistance类的主要目的是计算两个图像之间的 Hausdorff 距离 +# 首先,从abc模块导入Sequence类,这是 Python 的一个抽象基类,用于表示可迭代对象 from collections import abc from abc import ABCMeta +# # 然后,从scipy.ndimage模块导入morphology函数,用于对图像进行形态学操作 from scipy.ndimage import morphology +# 接下来,导入numpy库,用于处理数值计算 import numpy as np +# Tensor是一个表示张量的类,用于在 MindSpore 系统中处理张量数据 from mindspore.common.tensor import Tensor +# Validator是一个用于验证参数的类,用于确保输入参数符合预期的格式和范围 from mindspore._checkparam import Validator as validator +# 从metric.py文件中导入Metric类和rearrange_inputs函数。Metric类是用于计算图像质量的类,而rearrange_inputs函数用于对输入参数进行重排,以适应 Metric 类的要求 from .metric import Metric, rearrange_inputs class _ROISpatialData(metaclass=ABCMeta): + # 它继承自ABCMeta元类。这个类用于计算两个图像之间的 Hausdorff 距离。 + # _ROISpatialData类的主要功能是支持对 ND 空间数据进行裁剪,即生成一个 ROI(区域)。ROI 是指一个包含特定区域的空间子集。 + # ROISpatialData类通过使用metaclass=ABCMeta元类来声明其是一个抽象基类,这意味着不能直接实例化这个类。 + # roi_center、roi_size、roi_start和roi_end是这个类的参数,用于指定 ROI 的中心、大小、开始和结束坐标。 """ Produce Region Of Interest (ROI). Support to crop ND spatial data. The center and size of the space should be provided, if not, the start and end coordinates of the ROI must be provided. @@ -37,19 +49,30 @@ class _ROISpatialData(metaclass=ABCMeta): def __init__(self, roi_center=None, roi_size=None, roi_start=None, roi_end=None): + # 当提供roi_center和roi_size时 if roi_center is not None and roi_size is not None: + # roi_start和roi_end将由它们计算得到 + # 首先将roi_center转换为np.int16类型的数组 roi_center = np.asarray(roi_center, dtype=np.int16) + # 首先将roi_size转换为np.int16类型的数组 roi_size = np.asarray(roi_size, dtype=np.int16) + # 然后np.maximum(roi_center - np.floor_divide(roi_size, 2), 0)计算的是roi_center减去roi_size的一半向下取整 self.roi_start = np.maximum(roi_center - np.floor_divide(roi_size, 2), 0) + # 最后np.maximum(self.roi_start + roi_size, self.roi_start)计算的是roi_start加上roi_size,如果结果大于self.roi_start,则取self.roi_start self.roi_end = np.maximum(self.roi_start + roi_size, self.roi_start) else: + # 当只提供roi_start和roi_end时,需要确保roi_center和roi_size为None if roi_start is None or roi_end is None: + # 否则会抛出一个错误ValueError raise ValueError("For 'HausdorffDistance.update', When either 'roi_center' or 'roi_size' is None," "neither 'roi_start' nor 'roi_end' can be None.") + # 如果roi_start大于self.roi_start,则取self.roi_start作为roi_start self.roi_start = np.maximum(np.asarray(roi_start, dtype=np.int16), 0) + # 如果roi_end大于self.roi_end,则取self.roi_end作为roi_end self.roi_end = np.maximum(np.asarray(roi_end, dtype=np.int16), self.roi_start) def __call__(self, data): + # __call__方法接收两个图像张量作为输入,并返回它们的 Hausdorff 距离,用于根据 ROI 的范围从输入数据中裁剪出子集 """ Transform the data, if the data is channel first, slicing is not applicable to channel dim. @@ -59,12 +82,28 @@ class _ROISpatialData(metaclass=ABCMeta): Returns: np.ndarray, transform result. """ + # 首先,使用min()函数计算sd,即 ROI 的最短轴长度 sd = min(len(self.roi_start), len(self.roi_end), len(data.shape[1:])) + # 然后,使用列表推导式创建一个元组,其中每个元素都是一个切片,用于从输入数据中裁剪出子集 slices = [slice(None)] + [slice(s, e) for s, e in zip(self.roi_start[:sd], self.roi_end[:sd])] + # 最后,使用tuple()函数将切片列表转换为一个元组,并使用data[]操作符获取裁剪后的子集 return data[tuple(slices)] class HausdorffDistance(Metric): + # 用于计算两个点集之间的 Hausdorff 距离。Hausdorff 距离是两个点集之间最远距离的最大值。 + # 给定两个特征集A和B,Hausdorff距离定义为:max(h(A, B), h(B, A)) + # 其中h(A, B)是set A到set B中最近点的最大距离,h(B, A)是set B到set A中最近点的最大距离。 + # 计算距离是 oriented 的,这意味着在大多数情况下,h(A, B) 不等于 h(B, A)。 + + """ + 参数: + distance_metric (string):支持三种距离计算方法:"euclidean","chessboard"或"taxicab"。默认:"euclidean"。 + percentile (float):浮点数范围在0到100之间。指定百分比参数以获取Hausdorff距离的百分位数。默认:None。 + directed (bool):如果为True,则仅计算h(y_pred, y)距离,否则,返回max(h(y_pred, y), h(y, y_pred))。默认:False。 + crop (bool):如果为True,则裁剪输入图像并只保留前景。为了保持两个输入图像的形状,这里使用(y_pred | y)来表示它们的并集集合。默认:True。 + + """ r""" Calculates the Hausdorff distance. Hausdorff distance is the maximum and minimum distance between two point sets. Given two feature sets A and B, the Hausdorff distance between two point sets A and B is defined as follows: @@ -108,17 +147,30 @@ class HausdorffDistance(Metric): 1.4142135623730951 """ def __init__(self, distance_metric="euclidean", percentile=None, directed=False, crop=True): + # 用于初始化类的参数 super(HausdorffDistance, self).__init__() + # 定义了一个字符串列表string_list,用于存储支持的三种距离计算方法 string_list = ["euclidean", "chessboard", "taxicab"] + # 接下来,它使用validator函数验证distance_metric是否为str类 distance_metric = validator.check_value_type("distance_metric", distance_metric, [str]) + # 使用validator函数验证distance_metric, string_list是否是字符串 self.distance_metric = validator.check_string(distance_metric, string_list, "distance_metric") + # 使用if percentile is None判断percentile是否为None。如果是None,则将percentile的值赋给self.percentile; + # 否则,使用validator函数验证percentile是否为float类型,如果是,则将percentile的值赋给self.percentile self.percentile = percentile if percentile is None else validator.check_value_type("percentile", percentile, [float]) + # 首先,使用if directed is None判断directed是否为None。如果是None,则将directed的值赋给self.directed; + # 否则,使用validator函数验证directed是否为bool类型,如果是,则将directed的值赋给self.directed self.directed = directed if directed is None else validator.check_value_type("directed", directed, [bool]) + # 使用if crop is None判断crop是否为None。如果是None,则将crop的值赋给self.crop; + # 否则,使用validator函数验证crop是否为bool类型,如果是,则将crop的值赋给self.crop self.crop = crop if crop is None else validator.check_value_type("crop", crop, [bool]) + # 最后,它将这些参数赋值给类的属性 + # 清空类中残余的数据 self.clear() def _is_tuple_rep(self, tup, dim): + # 用于检查输入的元组tup是否满足给定的维度dim """ Returns the tup containing the dim value by shortening or repeating the input. @@ -126,27 +178,49 @@ class HausdorffDistance(Metric): ValueError: When tup is a sequence and tup length is not dim. """ + # 将结果设置为NONE result = None + # 如果tup不是一个可迭代序列 if not _is_iterable_sequence(tup): + # 那么将tup重复dim次,作为结果 result = (tup,) * dim + # 如果tup是一个可迭代序列,且长度等于dim elif len(tup) == dim: + # 那么将tup作为结果 result = tuple(tup) + # 如果tup的长度不等于dim if result is None: + # 那么抛出一个ValueError异常 raise ValueError(f"The sequence length should be {dim}, but got {len(tup)}.") + # 返回结果 return result def _is_tuple(self, inputs): + # 用于将输入的序列转换为元组 """ Returns a tuple of inputs. """ + # 如果inputs不是一个可迭代序列 if not _is_iterable_sequence(inputs): + # 那么将inputs重复1次,作为结果 inputs = (inputs,) + # 如果inputs是一个可迭代序列,那么将inputs作为结果 return tuple(inputs) def _create_space_bounding_box(self, image, func=lambda x: x > 0, channel_indices=None, margin=0): + # 用于从给定的图像中创建一个空间边界框,该边界框包含前景。用户可以通过定义一个函数来选择期望的前景,也可以指定要选择的通道。此外,还可以为边界框添加 margins + """ + 函数的参数如下: + + image:源图像,用于生成边界框。 + func:函数,用于选择期望的前景,默认值为选择值大于 0 的值。 + channel_indices:如果定义,则仅选择指定通道的前景,否则选择整个图像上的前景。 + margin:可以添加到空间维度上的 margins,如果只提供一个值,则使用该值for所有维度。 + + """ """ The position of the space bounding box that generates the foreground in an image with start end. The user can define any function to select the desired foreground from the whole image or the specified channel. @@ -160,21 +234,37 @@ class HausdorffDistance(Metric): margin: add margin value to spatial dims of the bounding box, if only a single value is provided, use it for all dims. """ + # 如果定义了 channel_indices,则从 image 中选择指定通道的前景 + # 否则,选择整个图像上的前景 data = image[[*(self._is_tuple(channel_indices))]] if channel_indices is not None else image + # 将前景转换为布尔值,得到一个二值图像 data = np.any(func(data), axis=0) + # 使用 np.nonzero 函数找到前景中的非零索引 nonzero_idx = np.nonzero(data) + # 首先计算边缘值 + # 注:_is_tuple_rep 函数用于将边缘值转换为具有相同长度的元组,如果提供的边缘值是一个数字,则在每个维度上使用相同的边缘值 margin = self._is_tuple_rep(margin, data.ndim) + # 然后创建两个空列表 box_start 和 box_end box_start = list() box_end = list() + + # 使用一个 for 循环遍历数据的维度。range(data.ndim) 表示从 0 到数据维度减 1 的范围,这意味着将遍历数据的所有维度 for i in range(data.ndim): + # 如果找到的索引数量为 0 if nonzero_idx[i].size <= 0: + # 则抛出一个错误ValueError,表示在空间维度上没有找到非零索引 raise ValueError("Did not find nonzero index at the spatial dim {}".format(i)) + # 计算边界框的起始和结束位置。对于每个空间维度,计算最大非零索引减去边缘值 box_start.append(max(0, np.min(nonzero_idx[i]) - margin[i])) + # 然后计算最小非零索引加上边缘值,最后将这两个值作为边界框的起始和结束位置 box_end.append(min(data.shape[i], np.max(nonzero_idx[i]) + margin[i] + 1)) + # 返回边界框的起始和结束位置 return box_start, box_end def _calculate_percent_hausdorff_distance(self, y_pred_edges, y_edges): + # 用于计算定向 Hausdorff 距离。函数的输入参数有两个:y_pred_edges 和 y_edges。 + # y_pred_edges 是一个表示预测边缘的 numpy 数组,y_edges 是一个表示真实边缘的 numpy 数组 """ Calculate the directed Hausdorff distance. @@ -182,20 +272,30 @@ class HausdorffDistance(Metric): y_pred_edges (np.ndarray): the edge of the predictions. y_edges (np.ndarray): the edge of the ground truth. """ + # 使用 _get_surface_distance 函数计算表面距离 surface_distance = self._get_surface_distance(y_pred_edges, y_edges) + # 如果表面距离的形状为 (0,) if surface_distance.shape == (0,): + # 则返回正无穷 return np.inf + # 如果 self.percentile 不存在或为 False if not self.percentile: + # 则返回表面距离的最大值 return surface_distance.max() + # 如果 0 <= self.percentile <= 100 if 0 <= self.percentile <= 100: + # 则返回百分比对应的表面距离百分位数 return np.percentile(surface_distance, self.percentile) + # 否则,抛出一个 ValueError 异常,表示 percentile 参数的值应在 [0, 100] 范围内 raise ValueError(f"For 'HausdorffDistance', the value of the argument 'percentile' should be [0, 100], " f"but got {self.percentile}.") def _get_surface_distance(self, y_pred_edges, y_edges): + # 用于计算从 y_pred_edges 到 y_edges 的表面距离。函数的输入参数有两个:y_pred_edges 和 y_edges。 + # y_pred_edges 是一个表示预测边缘的 numpy 数组,y_edges 是一个表示真实边缘的 numpy 数组 """ Calculate the surface distances from `y_pred_edges` to `y_edges`. @@ -203,23 +303,34 @@ class HausdorffDistance(Metric): y_pred_edges (np.ndarray): the edge of the predictions. y_edges (np.ndarray): the edge of the ground truth. """ - + # 如果 y_pred_edges 中没有非零值 if not np.any(y_pred_edges): + # 则返回一个空数组 return np.array([]) + # 如果 y_edges 中没有非零值 if not np.any(y_edges): + # 则返回一个全为正无穷的数组 dis = np.inf * np.ones_like(y_edges) else: + # 如果 self.distance_metric 为 "euclidean" if self.distance_metric == "euclidean": + # 则使用 morphology.distance_transform_edt 函数计算欧氏距离 dis = morphology.distance_transform_edt(~y_edges) + # 如果 self.distance_metric 为 "chessboard" 或 "taxicab" elif self.distance_metric == "chessboard" or self.distance_metric == "taxicab": + # 则使用 morphology.distance_transform_cdt 函数计算切比雪夫距离 dis = morphology.distance_transform_cdt(~y_edges, metric=self.distance_metric) + # 计算表面距离,将结果乘以 y_pred_edges 中非零值的个数 surface_distance = dis[y_pred_edges] + # 返回计算的表面距离 return surface_distance def _get_mask_edges_distance(self, y_pred, y): + # 用于计算从 y_pred 到 y 的边缘距离。函数的输入参数有两个:y_pred 和 y。 + # y_pred 是一个表示预测边缘的 numpy 数组,y 是一个表示真实边缘的 numpy 数组 """ Do binary erosion and use XOR for input to get the edges. This function is helpful to further calculate metrics such as Average Surface Distance and Hausdorff Distance. @@ -228,30 +339,48 @@ class HausdorffDistance(Metric): y_pred (np.ndarray): the edge of the predictions. y (np.ndarray): the edge of the ground truth. """ + # 如果 self.crop 为 True if self.crop: + # 首先检查 y_pred 和 y 是否全为零 if not np.any(y_pred | y): + # 如果是,首先定义两个与 y_pred 和 y 具有相同形状的全为零数组 res1 和 res2 res1 = np.zeros_like(y_pred) res2 = np.zeros_like(y) + # 返回两个全为零的数组 return res1, res2 + # 对 y_pred 和 y 进行扩展,以便它们具有相同的形状 y_pred, y = np.expand_dims(y_pred, 0), np.expand_dims(y, 0) + # 使用 _create_space_bounding_box 函数创建一个空间边界框,用于对 y_pred 和 y 进行裁剪 box_start, box_end = self._create_space_bounding_box(y_pred | y) + # 使用 _ROISpatialData 类对 y_pred 和 y 进行裁剪 cropper = _ROISpatialData(roi_start=box_start, roi_end=box_end) + # 对裁剪后的 y_pred 和 y 进行二进制腐蚀,并使用 XOR 运算符获取边缘 y_pred, y = np.squeeze(cropper(y_pred)), np.squeeze(cropper(y)) + # 首先对 y_pred 和 y 进行二进制腐蚀,然后使用 XOR 运算符获取边缘。XOR 运算符的性质是: + # 对于任意整数 x,x ^ x = 0。因此,对 y_pred 和 y 进行二进制腐蚀后,只有边缘处的值为 1,其他位置的值为 0 y_pred = morphology.binary_erosion(y_pred) ^ y_pred + # 然后将得到的边缘与原始边缘进行 XOR 运算,得到一个新的边缘数组,其中只有边缘处的值为 1,其他位置的值为 0 y = morphology.binary_erosion(y) ^ y + # 返回裁剪后的 y_pred 和 y return y_pred, y def clear(self): + # 用于清空类中的计算数据 """Clears the internal evaluation result.""" + # 将y_pred_edges的值重置为0 self.y_pred_edges = 0 + # 将y_edges的值重置为0 self.y_edges = 0 + # 设置_is_update为False,表示未更新参数 self._is_update = False @rearrange_inputs def update(self, *inputs): + # 用于更新内部评估结果。函数的输入参数有三个:y_pred、y 和 label_idx。 + # y_pred 和 y 是一个 Tensor、列表或 numpy.ndarray,y_pred 是预测的二值图像,y 是实际的二值图像。label_idx 的数据类型为 int 或 float """ Updates the internal evaluation result with the inputs: 'y_pred', 'y' and 'label_idx'. @@ -266,36 +395,52 @@ class HausdorffDistance(Metric): ValueError: If the value of label_idx is not in y_pred or y. ValueError: If y_pred and y have different shapes. """ + # 将_is_update设置为True,表示已更新参数 self._is_update = True + # 首先检查输入参数的数量是否为 3 if len(inputs) != 3: + # 如果不是,则抛出一个 ValueError 异常 raise ValueError("For 'HausdorffDistance.update', it needs 3 inputs (predicted value, true value, " "label index), but got {}.".format(len(inputs))) + # 从输入中获取参数y_pred并将其转换为适当的数据格式 y_pred = self._convert_data(inputs[0]) + # 从输入中获取参数y并将其转换为适当的数据格式 y = self._convert_data(inputs[1]) + # 从输入中获取参数 label_idx label_idx = inputs[2] + # 检查 label_idx 的数据类型是否为 int 或 float if not isinstance(label_idx, (int, float)): + # 如果不是,则抛出一个 TypeError 异常 raise ValueError(f"For 'HausdorffDistance.update', the label index (input[2]) must be int or float, " f"but got {type(label_idx)}.") + # 检查 label_idx 是否在 y_pred 中,也不在 y 中 if label_idx not in y_pred and label_idx not in y: + # 如果不是,则抛出一个 ValueError 异常 raise ValueError("For 'HausdorffDistance.update', the label index (input[2]) should be in predicted " "value (input[0]) or true value (input[1]), but {} is not.".format(label_idx)) + # # 检查 y_pred 和 y 是否具有相同的形状 if y_pred.size == 0 or y_pred.shape != y.shape: + # 如果不是,则抛出一个 ValueError 异常 raise ValueError(f"For 'HausdorffDistance.update', the size of predicted value (input[0]) and true value " f"(input[1]) should be greater than 0, in addition to that, predicted value and true " f"value should have the same shape, but got predicted value size: {y_pred.size}, shape: " f"{y_pred.shape}, true value size: {y.size}, shape: {y.shape}.") + # 首先将 y_pred 和 y 转换为布尔类型 y_pred = (y_pred == label_idx) if y_pred.dtype is not bool else y_pred + # 然后根据 label_idx 的值设置它们的值 y = (y == label_idx) if y.dtype is not bool else y + # 最后,调用 self._get_mask_edges_distance 函数计算 y_pred 和 y 的边缘距离,并将结果分别赋值给 self.y_pred_edges 和 self.y_edges self.y_pred_edges, self.y_edges = self._get_mask_edges_distance(y_pred, y) def eval(self): + # 用于计算无向或有向 Hausdorff 距离,需先在调用update函数后调用 """ Calculate the no-directed or directed Hausdorff distance. @@ -305,21 +450,32 @@ class HausdorffDistance(Metric): Raises: RuntimeError: If the update method is not called first, an error will be reported. """ + # 检查 self._is_update 是否为 False if self._is_update is False: + # 如果是,则抛出一个 RuntimeError 异常 raise RuntimeError("Please call the 'update' method before calling 'eval' method.") + # 调用 _calculate_percent_hausdorff_distance 函数计算 y_pred_edges 和 y_edges 的边缘距离 hd = self._calculate_percent_hausdorff_distance(self.y_pred_edges, self.y_edges) + # 如果 self.directed 为 True if self.directed: + # 则返回计算得到的 Hausdorff 距离 return hd + # 否则,计算 y_edges 和 y_pred_edges 的边缘距离 hd2 = self._calculate_percent_hausdorff_distance(self.y_edges, self.y_pred_edges) + # 并返回较大的值 return max(hd, hd2) def _is_iterable_sequence(inputs): + # 用于判断输入是否是一个可迭代的序列且不是字符串 """ Determine if the input is an iterable sequence and it is not a string. """ + # 首先判断输入是否是一个 Tensor 类型的对象 if isinstance(inputs, Tensor): + # 如果是,则判断其维度是否大于0并返回 return int(inputs.dim()) > 0 + # 如果不是,则判断输入是否是一个 abc.Iterable 类型的对象且不是字符串。如果满足这两个条件之一,则返回 True,否则返回 False return isinstance(inputs, abc.Iterable) and not isinstance(inputs, str) diff --git a/mindspore/python/mindspore/nn/metrics/loss.py b/mindspore/python/mindspore/nn/metrics/loss.py index 2b83743ee77..8cc846d5f0e 100644 --- a/mindspore/python/mindspore/nn/metrics/loss.py +++ b/mindspore/python/mindspore/nn/metrics/loss.py @@ -13,10 +13,14 @@ # limitations under the License. # ============================================================================ """Loss for evaluation""" +# 该类用于评估损失。这个类继承自 Metric 类,并实现了 __init__ 和 update 方法 from .metric import Metric, rearrange_inputs +# 从 metric 模块中导入 Metric 和 rearrange_inputs 函数。Metric 类是用于计算评估指标的基类,而 rearrange_inputs 函数用于对输入进行重新排列,以适应不同的计算模式 + class Loss(Metric): + # 继承自 Metric 类。Loss 类用于计算评估损失,通过重写 update 方法来计算损失值,并将损失值除以调用 update 方法的次数来计算平均损失 r""" Calculates the average of the loss. If method 'update' is called every :math:`n` iterations, the result of evaluation will be: @@ -41,16 +45,23 @@ class Loss(Metric): 0.20000000298023224 """ def __init__(self): + # 调用父类构造函数初始化 super(Loss, self).__init__() + # 清空类中残余数据 self.clear() def clear(self): + # 用于清空类中的数据 """Clears the internal evaluation result.""" + # 将_sum_loss重置为0 self._sum_loss = 0 + # 将_total_num重置为0 self._total_num = 0 @rearrange_inputs + # 使用 @rearrange_inputs 装饰器,将输入重新排列为 (loss,) 形式 def update(self, *inputs): + # 用于更新内部评估结果。接收一个或多个输入,但输入只能有一个元素,即损失 """ Updates the internal evaluation result. @@ -62,23 +73,33 @@ class Loss(Metric): ValueError: If the length of inputs is not 1. ValueError: If the dimension of loss is not 1 or 0. """ + # 检查输入的数量是否为1 if len(inputs) != 1: + # 如果不是,则抛出一个 ValueError 异常 raise ValueError("For 'Loss.update', it needs 1 input (loss), but got {}".format(len(inputs))) + # 将输入的损失值转换为张量 loss = self._convert_data(inputs[0]) + # 如果损失的维度为0 if loss.ndim == 0: + # 则将其reshape为 (1,) 形式 loss = loss.reshape(1) + # 如果损失的维度不是1 if loss.ndim != 1: + # 则抛出一个 ValueError 异常 raise ValueError("For 'Loss.update', the dimension of your input (loss) must be 1, " "but got {}.".format(loss.ndim)) - + # 计算平均损失 loss = loss.mean(-1) + # 并将损失值求和 self._sum_loss += loss + # 将平均损失添加到内部评估结果中,同时更新 total_num self._total_num += 1 def eval(self): + # 用于计算评估损失的平均值,需先在调用update函数后调用 """ Calculates the average of the loss. @@ -88,7 +109,10 @@ class Loss(Metric): Raises: RuntimeError: If the total number is 0. """ + # 首先检查 total_num 是否为0 if self._total_num == 0: + # 如果是,则抛出一个 RuntimeError 异常 raise RuntimeError("The 'Loss' can not be calculated, because the number of samples is 0, please " "check whether has called update method before calling eval method.") + # 然后计算平均损失,并将结果返回 return self._sum_loss / self._total_num diff --git a/mindspore/python/mindspore/nn/metrics/mean_surface_distance.py b/mindspore/python/mindspore/nn/metrics/mean_surface_distance.py index b3d1a1d0192..2c6ead15161 100644 --- a/mindspore/python/mindspore/nn/metrics/mean_surface_distance.py +++ b/mindspore/python/mindspore/nn/metrics/mean_surface_distance.py @@ -12,14 +12,33 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ +# 用于计算平均表面距离。这个类继承自 Metric 类,并实现了 __init__ 和 update 方法 """MeanSurfaceDistance.""" +# 导入了 scipy.ndimage 模块中的 morphology 函数,用于计算图像形态学 from scipy.ndimage import morphology +# 导入了 numpy 模块,用于处理数值计算 import numpy as np +# 从 mindspore._checkparam 模块中导入 Validator 类,用于参数验证 from mindspore._checkparam import Validator as validator +# 从 .metric 模块中导入 Metric 和 rearrange_inputs 函数,用于处理评估指标和输入重排 from .metric import Metric, rearrange_inputs class MeanSurfaceDistance(Metric): + # 通过重写 update 方法来计算损失值,并将损失值除以调用 update 方法的次数来计算平均损失 + """ + 初始化参数: + + distance_metric (string):支持三种距离度量方法:"euclidean"(欧氏距离)、"chessboard"(棋盘距离)和 "taxicab"(曼哈顿距离)。默认:"euclidean"。 + symmetric (bool):是否计算 y_pred 和 y 的平均表面距离。如果为 False,则只计算 AvgSurDis({y_pred} \rightarrow y),否则计算 MeanSurDis(y_{pred} \leftrightarrow y)。默认:False。 + + 计算方法: + + 导入所需的库。 + 定义一个名为 _convert_data 的辅助方法,用于将输入的数据转换为张量。 + 定义一个名为 update 的辅助方法,用于更新内部评估结果。 + 定义一个名为 eval 的辅助方法,用于计算评估损失的平均值。 + """ r""" Computes the Average Surface Distance from `y_pred` to `y` under the default setting. It measures how much, on average, the surface varies between the segmentation and the GT (ground truth). @@ -70,23 +89,37 @@ class MeanSurfaceDistance(Metric): """ def __init__(self, symmetric=False, distance_metric="euclidean"): + # 调用父类的 __init__ 方法,初始化 Metric 类 super(MeanSurfaceDistance, self).__init__() + # 定义一个名为 distance_metric_list 的列表,用于存储支持的三种距离度量方法 self.distance_metric_list = ["euclidean", "chessboard", "taxicab"] + # 检查 distance_metric 是否为str类并将 distance_metric 参数设置为有效的值 distance_metric = validator.check_value_type("distance_metric", distance_metric, [str]) + # 检查 distance_metric 参数的值是否在 distance_metric_list 中,如果不是,则抛出一个 ValueError 异常 self.distance_metric = validator.check_string(distance_metric, self.distance_metric_list, "distance_metric") + # 检查 symmetric 参数的值是否为布尔类型,如果不是,则抛出一个 ValueError 异常 self.symmetric = validator.check_value_type("symmetric", symmetric, [bool]) + # 清空类中残余的数据 self.clear() + # 初始化一个变量 _is_update,用于记录是否已经调用过 update 方法 self._is_update = None + # 初始化变量 _y_edges 用于记录输入的 y 数据 self._y_edges = None + # 初始化两个变量 _y_pred_edges 用于记录输入的 y_pred 数据 self._y_pred_edges = None def clear(self): + # 用于清空类中的数据 """Clears the internal evaluation result.""" + # 将_y_pred_edges设置为0 self._y_pred_edges = 0 + # 将_y_edges设置为0 self._y_edges = 0 + # 将_is_update设置为False,表示结果未更新 self._is_update = False def _get_surface_distance(self, y_pred_edges, y_edges): + # 用于计算从 y_pred_edges 到 y_edges 的表面距离 """ Calculate the surface distances from `y_pred_edges` to `y_edges`. @@ -94,21 +127,32 @@ class MeanSurfaceDistance(Metric): y_pred_edges (np.ndarray): the edge of the predictions. y_edges (np.ndarray): the edge of the ground truth. """ + # 检查 y_pred_edges 和 y_edges 是否为空数组 if not np.any(y_pred_edges): + # 如果是,则返回一个空数组 return np.array([]) + # 如果 y_edges 不为空 if np.any(y_edges): + # 如果 distance_metric 参数为 "euclidean" if self.distance_metric == "euclidean": + # 则使用 morphology.distance_transform_edt 函数计算欧氏距离 dis = morphology.distance_transform_edt(~y_edges) + # 如果 distance_metric 参数为 "chessboard" 或 "taxicab" elif self.distance_metric in self.distance_metric_list[-2:]: + # 则使用 morphology.distance_transform_cdt 函数计算棋盘距离 dis = morphology.distance_transform_cdt(~y_edges, metric=self.distance_metric) + # 如果 y_edges 为空 else: + # 返回一个全为无穷大的数组 dis = np.full(y_edges.shape, np.inf) - + # 返回 dis 数组中 y_pred_edges 对应的元素值 return dis[y_pred_edges] @rearrange_inputs + # 使用 @rearrange_inputs 装饰器处理输入参数,确保输入参数的个数为 3 def update(self, *inputs): + # 用于更新内部评估结果 y_pred、y 和 label_idx """ Updates the internal evaluation result 'y_pred', 'y' and 'label_idx'. @@ -123,37 +167,57 @@ class MeanSurfaceDistance(Metric): ValueError: If the value of label_idx is not in y_pred or y. ValueError: If y_pred and y have different shapes. """ + # 检查 label_idx 的数据类型是否为整数或浮点数 if len(inputs) != 3: + # 如果不是,则抛出一个 ValueError 异常 raise ValueError("For 'MeanSurfaceDistance.update', it needs 3 inputs (predicted value, true value, " "label index), but got {}".format(len(inputs))) + # 将输入参数 inputs 中第 0 个元素转换为 y_pred y_pred = self._convert_data(inputs[0]) + # 第 1 个元素转换为 y y = self._convert_data(inputs[1]) + # 并将第 2 个元素赋值给 label_idx。 + # _convert_data 方法用于将输入数据转换为所需的数据类型和形状 label_idx = inputs[2] + # 检查 label_idx 的值是否在 y_pred 或 y 的范围内 if not isinstance(label_idx, (int, float)): + # 如果不是,则抛出一个 ValueError 异常 raise ValueError(f"For 'MeanSurfaceDistance.update', the label index (input[2]) must be int or float, " f"but got {type(label_idx)}.") + # 检查 y_pred 和 y 是否具有相同的形状 if label_idx not in y_pred and label_idx not in y: + # 如果不是,则抛出一个 ValueError 异常 raise ValueError("For 'MeanSurfaceDistance.update', the label index (input[2]) should be in predicted " "value (input[0]) or true value (input[1]), but {} is not.".format(label_idx)) + # 检查 y_pred 和 y 的尺寸是否为 0,以及它们是否具有相同的形状 if y_pred.size == 0 or y_pred.shape != y.shape: + # 如果满足这些条件,它将检查 y_pred 和 y 的数据类型是否为布尔值 raise ValueError(f"For 'MeanSurfaceDistance.update', the size of predicted value (input[0]) and true " f"value (input[1]) should be greater than 0, in addition to that, predicted value and " f"true value should have the same shape, but got predicted value size: {y_pred.size}, " f"shape: {y_pred.shape}, true value size: {y.size}, shape: {y.shape}.") + # 检查 y_pred 的数据类型是否为布尔值 if y_pred.dtype != bool: + # 如果不是,则将它们转换为布尔值 y_pred = y_pred == label_idx + # 检查 y_pred 和 y 的数据类型是否为布尔值 if y.dtype != bool: + # 如果不是,则将它们转换为布尔值 y = y == label_idx + # 然后,使用 morphology.binary_erosion 函数对 y_pred 和 y 进行二值腐蚀操作 self._y_pred_edges = morphology.binary_erosion(y_pred) ^ y_pred + # 最后,将 self._y_pred_edges 和 self._y_edges 更新为 erosion 后的值 self._y_edges = morphology.binary_erosion(y) ^ y + # 并设置 self._is_update 为 True,表示已更新参数 self._is_update = True def eval(self): + # 用于计算平均表面距离,通常在update函数后调用 """ Calculate mean surface distance. @@ -163,22 +227,35 @@ class MeanSurfaceDistance(Metric): Raises: RuntimeError: If the update method is not called first, an error will be reported. """ + # 首先,检查 self._is_update 是否为 True if self._is_update is False: + # 如果不是,则抛出一个 RuntimeError 异常,要求在调用 eval 方法之前先调用 update 方法 raise RuntimeError("Please call the 'update' method before calling 'eval' method.") + # 如果是,则继续计算平均表面距离 mean_surface_distance = self._get_surface_distance(self._y_pred_edges, self._y_edges) + # 检查 mean_surface_distance 的形状是否为 (0,) if mean_surface_distance.shape == (0,): + # 如果是,则返回正无穷 return np.inf + # 否则计算平均表面距离,并将其赋值给 avg_surface_distance avg_surface_distance = mean_surface_distance.mean() + # 如果 self.symmetric 为 True if not self.symmetric: + # 则返回 avg_surface_distance return avg_surface_distance - + + # 接下来,使用 _get_surface_distance 方法计算 _y_pred_edges 和 _y_edges 的表面距离 contrary_mean_surface_distance = self._get_surface_distance(self._y_edges, self._y_pred_edges) + # 如果计算结果的形状为 (0,) if contrary_mean_surface_distance.shape == (0,): + # 则返回正无穷 return np.inf - + + # 否则计算平均表面距离 contrary_avg_surface_distance = contrary_mean_surface_distance.mean() + # 上述两则判断均不符合则计算两个平均表面距离的平均值并返回 return np.mean((avg_surface_distance, contrary_avg_surface_distance)) diff --git a/mindspore/python/mindspore/nn/metrics/metric.py b/mindspore/python/mindspore/nn/metrics/metric.py index b55ef6409ce..8138baed2bc 100644 --- a/mindspore/python/mindspore/nn/metrics/metric.py +++ b/mindspore/python/mindspore/nn/metrics/metric.py @@ -12,16 +12,26 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ + +# 在机器学习中,性能指标(Metrics)是衡量一个模型好坏的关键,通过衡量模型输出y_predict 和 y_true之间的某种"距离"得出的 +# 定义了一个名为MetricBase的基类,用于计算模型准确性 """Metric base class.""" +# 该类使用了ABCMeta元类,这意味着它是一个抽象基类,需要继承的子类需要实现其所有抽象方法 from abc import ABCMeta, abstractmethod +# 从functools导入了functools模块,用于实现一些高阶函数,例如partial import functools +# 导入了numpy模块,用于处理数值计算 import numpy as np +# 导入了Tensor类,用于表示计算图中的张量 from mindspore.common.tensor import Tensor +# _eval_types是一个字典,用于存储支持的评估类型,包括'classification'和'multilabel' _eval_types = {'classification', 'multilabel'} def rearrange_inputs(func): + # 这段代码是一个装饰器,用于重排输入。装饰器通常用于修改函数的行为,而这里是用于修改mindspore.nn.Metric类中的update方法的行为。 + # 这个装饰器的主要作用是根据indexes属性对输入进行重排 """ This decorator is used to rearrange the inputs according to its `indexes` attribute of the class. @@ -61,13 +71,24 @@ def rearrange_inputs(func): """ @functools.wraps(func) def wrapper(self, *inputs): + ''' + 调用函数,并将输入参数转换为索引 + :param self: + :param inputs: + :return: + ''' + # 获取indexes indexes = self.indexes + # 如果indexes不存在,则调用func函数,并将inputs作为参数传入 inputs = inputs if not indexes else [inputs[i] for i in indexes] + # 返回func函数的执行结果 return func(self, *inputs) + # 返回重排后的参数 return wrapper class Metric(metaclass=ABCMeta): + # 定义了一个名为Metric的基类,用于计算模型准确性 """ Base class of metric. This class is used to evaluate metrics. @@ -81,7 +102,9 @@ class Metric(metaclass=ABCMeta): Supported Platforms: ``Ascend`` ``GPU`` ``CPU`` """ + # 初始化Metric类的实例 def __init__(self): + # 首先将self._indexes设置为None self._indexes = None def _convert_data(self, data): @@ -94,24 +117,35 @@ class Metric(metaclass=ABCMeta): Returns: Ndarray, data with `np.ndarray` type. """ + # 判断data是否为Tensor类型 if isinstance(data, Tensor): + # 将data转换为numpy数组 data = data.asnumpy() + # 判断data是否为list类型 elif isinstance(data, list): + # 将list转换为numpy数组 data = np.array(data) + # 判断data是否为numpy.ndarray类型 elif isinstance(data, np.ndarray): pass + # 判断data是否为其他类型 else: + # 抛出类型错误TypeError raise TypeError(f"For 'Metric' and its derived classes, the input data type must be tensor, list or " f"numpy.ndarray, but got {type(data)}.") + # 返回转换后的数据 return data @property def indexes(self): + # 用于获取当前的indexes值。默认值是None,可以通过set_indexes方法修改 """Get the current indexes value. The default value is None and can be changed by `set_indexes`. """ return getattr(self, '_indexes', None) def set_indexes(self, indexes): + # 用于设置indexes属性 + # 注:设置update方法的输入顺序。通过设置indexes属性,可以重新排列update方法接收的输入参数顺序,以便根据需要从输入参数中提取所需的参数 """ This interface is to rearrange the inputs of `update`. @@ -146,13 +180,17 @@ class Metric(metaclass=ABCMeta): >>> print(accuracy) 0.3333333333333333 """ + # 检查indexes参数是否为列表且所有元素为整数 if not isinstance(indexes, list) or not all(isinstance(i, int) for i in indexes): - raise ValueError("For 'set_indexes', the argument 'indexes' should be a list and all its elements should " + # 如果不是,则抛出一个ValueError异常,表示indexes参数无效 + raise ValueError("For'set_indexes', the argument 'indexes' should be a list and all its elements should " "be int, please check whether it is correct.") + # 将indexes值设置为self._indexes,并返回self self._indexes = indexes return self def __call__(self, *inputs): + # 用于评估输入数据一次。当调用Metric类的实例时,会自动调用__call__方法 """ Evaluate input data once. @@ -162,12 +200,16 @@ class Metric(metaclass=ABCMeta): Returns: Float, compute result. """ + # 清空计算的中间结果 self.clear() + # 更新输入数据 self.update(*inputs) + # 返回计算结果 return self.eval() @abstractmethod def clear(self): + # 用于重置中间结果,在clear方法中,将self._indexes设置为None """ An interface describes the behavior of clearing the internal evaluation result. @@ -178,6 +220,9 @@ class Metric(metaclass=ABCMeta): @abstractmethod def eval(self): + # 用于计算模型的准确性。首先,它会检查self._total_num是否为0,如果是,则抛出一个RuntimeError异常,表示无法计算准确性, + # 因为样本数量为0。如果self._total_num不为0,则计算self._correct_num除以self._total_num的结果,并返回该结果。 + # 在计算准确性时,需要确保已经调用了update方法,以便能够更新self._correct_num和self._total_num的值 """ An interface describes the behavior of computing the evaluation result. @@ -188,6 +233,9 @@ class Metric(metaclass=ABCMeta): @abstractmethod def update(self, *inputs): + # 用于更新中间结果。在update方法中,首先检查indexes属性是否存在,如果不存在,则直接返回None。如果indexes属性存在, + # 则使用列表推导式根据indexes属性中的索引提取输入参数,并将提取到的输入参数作为新的输入参数列表传入原始的update方法。 + # 最后,返回update方法的结果 """ An interface describes the behavior of updating the internal evaluation result. @@ -201,6 +249,7 @@ class Metric(metaclass=ABCMeta): class EvaluationBase(Metric): + # EvaluationBase类用于进行评估,包括分类和多标签两种类型 """ Base class of evaluation. @@ -214,40 +263,59 @@ class EvaluationBase(Metric): TypeError: If the input type is not classification or multilabel. """ def __init__(self, eval_type): + # eval_type参数用于指定评估类型,必须是'classification'或'multilabel'之一。如果输入类型不是分类或多标签,会抛出一个TypeError异常 super(EvaluationBase, self).__init__() + # 评估类型如果不在分类和多标签两种类型之一 if eval_type not in _eval_types: + # 抛出一个TypeError异常 raise TypeError("The argument 'eval_type' must be in {}, but got {}".format(_eval_types, eval_type)) + # 设置评估类型 self._type = eval_type def _check_shape(self, y_pred, y): """ - Checks the shapes of y_pred and y. + Checks the shapes of y_pred and y. + + 检查y_pred和y的形状 Args: - y_pred (Tensor): Predict array. - y (Tensor): Target array. + y_pred (Tensor): Predict array. + y (Tensor): Target array. + y_pred (Tensor): 预测数组 + y (Tensor): 真实数组 """ + # 如果是分类标签 if self._type == 'classification': - if y_pred.ndim != y.ndim + 1: + # 首先检查y_pred的维度是否等于y的维度加1 + if y_pred.ndim!= y.ndim + 1: + # 如果不是,则抛出一个ValueError异常,表示在分类情况下,y_pred的维度应该等于y的维度加1,但实际维度为y_pred.ndim和y.ndim raise ValueError("In classification case, the dimension of y_pred (predicted value) should equal to " - "the dimension of y (true value) add 1, but got y_pred dimension: {} and y " - "dimension: {}.".format(y_pred.ndim, y.ndim)) - if y.shape != (y_pred.shape[0],) + y_pred.shape[2:]: + "the dimension of y (true value) add 1, but got y_pred dimension: {} and y " + "dimension: {}.".format(y_pred.ndim, y.ndim)) + # 接下来,检查y的形状是否等于y_pred的形状减去第1个维度(因为第1个维度通常表示样本数量) + if y.shape!= (y_pred.shape[0],) + y_pred.shape[2:]: + # 如果不是,则抛出一个ValueError异常,表示在分类情况下,y的形状应该等于y_pred的形状减去第1个维度,但实际形状为y.shape和y_pred.shape raise ValueError("In classification case, y_pred (predicted value) shape and y (true value) shape " - "can not match, y shape should be equal to y_pred shape that the value at index 1 " - "is deleted. Such as y_pred shape (1, 2, 3), then y shape should be (1, 3). " - "But got y_pred shape {} and y shape {}".format(y_pred.shape, y.shape)) + "can not match, y shape should be equal to y_pred shape that the value at index 1 " + "is deleted. Such as y_pred shape (1, 2, 3), then y shape should be (1, 3). " + "But got y_pred shape {} and y shape {}".format(y_pred.shape, y.shape)) + # 如果是多标签 else: - if y_pred.ndim != y.ndim: + # 首先检查y_pred的维度是否等于y的维度 + if y_pred.ndim!= y.ndim: + # 如果不是,则抛出一个ValueError异常,表示在self._type类型的评估情况下,y_pred的维度应该等于y的维度 raise ValueError("In {} case, the dimension of y_pred (predicted value) should equal to the dimension" - " of y (true value), but got y_pred dimension: {} and y dimension: {}." - .format(self._type, y_pred.ndim, y.ndim)) - if y_pred.shape != y.shape: + " of y (true value), but got y_pred dimension: {} and y dimension: {}." + .format(self._type, y_pred.ndim, y.ndim)) + # 接下来,检查y的形状是否等于y_pred的形状 + if y_pred.shape!= y.shape: + # 如果不是,则抛出一个ValueError异常,表示在self._type类型的评估情况下,y的形状应该等于y_pred的形状 raise ValueError("In {} case, the shape of y_pred (predicted value) should equal to the shape of y " - "(true value), but got y_pred shape: {} and y shape: {}." - .format(self._type, y_pred.shape, y.shape)) + "(true value), but got y_pred shape: {} and y shape: {}." + .format(self._type, y_pred.shape, y.shape)) def _check_value(self, y_pred, y): + # 用于检查y_pred和y的值是否符合要求 """ Checks the values of y_pred and y. @@ -255,11 +323,14 @@ class EvaluationBase(Metric): y_pred (Tensor): Predict array. y (Tensor): Target array. """ + # 在多标签评估情况下,这个方法用于检查y_pred和y中的所有元素是否都是0或1 if self._type != 'classification' and not (np.equal(y_pred ** 2, y_pred).all() and np.equal(y ** 2, y).all()): + # 如果不是,则抛出一个ValueError异常,表示在多标签评估情况下,y_pred和y中的所有元素应该都是0或1,但实际值不为0或1 raise ValueError("In multilabel case, all elements in y_pred (predicted value) and y (true value) should " "be 0 or 1.Please check whether your inputs y_pred and y are correct.") def clear(self): + # 用于重置中间结果,在clear方法中,将self._indexes设置为None """ A interface describes the behavior of clearing the internal evaluation result. @@ -269,6 +340,9 @@ class EvaluationBase(Metric): raise NotImplementedError def update(self, *inputs): + # 用于更新中间结果。在update方法中,首先检查indexes属性是否存在,如果不存在,则直接返回None。如果indexes属性存在, + # 则使用列表推导式根据indexes属性中的索引提取输入参数,并将提取到的输入参数作为新的输入参数列表传入原始的update方法。 + # 最后,返回update方法的结果 """ A interface describes the behavior of updating the internal evaluation result. @@ -281,6 +355,9 @@ class EvaluationBase(Metric): raise NotImplementedError def eval(self): + # 用于计算模型的准确性。首先,它会检查self._total_num是否为0,如果是,则抛出一个RuntimeError异常,表示无法计算准确性, + # 因为样本数量为0。如果self._total_num不为0,则计算self._correct_num除以self._total_num的结果,并返回该结果。 + # 在计算准确性时,需要确保已经调用了update方法,以便能够更新self._correct_num和self._total_num的值 """ A interface describes the behavior of computing the evaluation result. @@ -291,6 +368,7 @@ class EvaluationBase(Metric): def _check_onehot_data(data): + # 用于检查输入数据是否是独热编码 """ Whether input data is one-hot encoding. @@ -300,38 +378,56 @@ def _check_onehot_data(data): Returns: bool, return true, if input data is one-hot encoding. """ + # 判断data的维度是否大于1,且data是否平方等于data if data.ndim > 1 and np.equal(data ** 2, data).all(): + # 获取data的维度 shp = (data.shape[0],) + data.shape[2:] + # 判断data是否全部等于1 if np.equal(np.ones(shp), data.sum(axis=1)).all(): + # 如果满足上述两个条件,则返回True,表示输入数据是独热编码 return True + # 否则返回False return False - def _binary_clf_curve(preds, target, sample_weights=None, pos_label=1): + # 用于计算二分类分类曲线的True Positives(真正例)和False Positives(假正例) """Calculate True Positives and False Positives per binary classification threshold.""" + # 如果sample_weights不是空且不是一个ndarray,则将其转换为ndarray if sample_weights is not None and not isinstance(sample_weights, np.ndarray): sample_weights = np.array(sample_weights) + # 如果preds的维度大于target的维度,则将preds的第一维放置为1 if preds.ndim > target.ndim: preds = preds[:, 0] + # 对preds和target进行排序,使preds从大到小排列 desc_score_indices = np.argsort(-preds) + # 将preds和target按照desc_score_indices中的顺序进行排列 preds = preds[desc_score_indices] target = target[desc_score_indices] + # 如果提供了sample_weights,则对preds和target进行加权 if sample_weights is not None: weight = sample_weights[desc_score_indices] else: weight = 1. + # 首先找到preds中不相同的值的下标,这些下标表示分类曲线的分界点。np.where(preds[1:] - preds[:-1])[0]会返回一个元组, + # 其中第一个元素是分界点的下标,第二个元素是分界点对应的preds值 distinct_value_indices = np.where(preds[1:] - preds[:-1])[0] + # 使用np.pad函数对distinct_value_indices进行填充,使其长度与target的形状相等 threshold_idxs = np.pad(distinct_value_indices, (0, 1), constant_values=target.shape[0] - 1) + # 将target转换为int64 target = np.array(target == pos_label).astype(np.int64) + # 计算tps tps = np.cumsum(target * weight, axis=0)[threshold_idxs] + # 如果sample_weights不为空,则计算fps if sample_weights is not None: fps = np.cumsum((1 - target) * weight, axis=0)[threshold_idxs] else: + # 否则fps为1 + threshold_idxs - tps fps = 1 + threshold_idxs - tps + # 返回fps、tps、preds[threshold_idxs] return fps, tps, preds[threshold_idxs] diff --git a/mindspore/python/mindspore/nn/metrics/occlusion_sensitivity.py b/mindspore/python/mindspore/nn/metrics/occlusion_sensitivity.py index 5f9f5a40ae7..fa52589aafd 100644 --- a/mindspore/python/mindspore/nn/metrics/occlusion_sensitivity.py +++ b/mindspore/python/mindspore/nn/metrics/occlusion_sensitivity.py @@ -12,22 +12,50 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ -"""OcclusionSensitivity.""" -import numpy as np -from mindspore import nn -from mindspore.common.tensor import Tensor -from mindspore._checkparam import Validator as validator -from .metric import Metric, rearrange_inputs +# 用于计算遮挡敏感度(Occlusion Sensitivity)。类中包含一个名为 update 的方法,用于更新预测值和真实值;一个名为 eval 的方法,用于计算平均表面距离 +"""OcclusionSensitivity.""" +# numpy:这是一个用于数值计算的Python库,提供了高性能的多维数组对象和相关工具 +import numpy as np +# mindspore:这是一个全场景深度学习框架,适用于训练和推理 +from mindspore import nn +# mindspore.common.tensor:提供了一个用于处理张量的模块 +from mindspore.common.tensor import Tensor +# Validator:一个用于验证参数的模块 +from mindspore._checkparam import Validator as validator +# .metric:一个包含metric计算相关功能的模块 +from .metric import Metric, rearrange_inputs +# rearrange_inputs:一个用于重排输入的函数 + +# 这段代码是用于导入tqdm库,用于在训练和推理过程中显示进度条 try: + # 首先尝试从tqdm库中导入trange函数 from tqdm import trange +# 如果导入失败,则使用Python的range函数替代 except (ImportError, AttributeError): trange = range finally: + # 最后,如果导入成功或失败,都执行一个pass语句 pass class OcclusionSensitivity(Metric): + # 继承自Metric模块。Occlusion sensitivity是一种用于评估模型在遮挡部分上的敏感性的指标。 + # 它表示当遮盖住图像中的一个部分时,预测概率的变化程度。值越高,表明遮盖部分在决策过程中越重要 + """ + OcclusionSensitivity类的主要参数如下: + + pad_val (float):遮盖值,表示遮盖部分的值。默认值为0.0。 + margin (Union[int, Sequence]):创建一个 cuboid / cube around the voxel you want to occlude。默认值为2。 + n_batch (int):批量中的图像数量。默认值为128。 + b_box (Sequence):用于分析的边界框。输出图像将匹配边界框的大小。应该有最小和最大值,除了批量维度:[min1, max1, min2, max2,...]。 + 如果没有提供边界框,那么输出图像将与输入图像具有相同的大小。如果提供了边界框,那么输出图像将根据边界框进行裁剪。默认值为None。 + + 这个类继承了Metric模块,主要实现了以下方法: + + update:用于更新指标的计算。接收输入图像、预测概率和标签作为参数。 + eval:用于评估指标。接收数据列表作为参数,返回评估结果。 + """ """ Calculates the occlusion sensitivity of the model for a given image. It illustrates which parts of an image are most important for a network's classification. @@ -74,21 +102,33 @@ class OcclusionSensitivity(Metric): [0.29999995 0.6 1. 0.9] """ def __init__(self, pad_val=0.0, margin=2, n_batch=128, b_box=None): + # 调用父类的构造函数,初始化类 super().__init__() + # 检查pad_val的值是否为float浮点数,如果不符合要求,将抛出一个TypeError异常 self.pad_val = validator.check_value_type("pad_val", pad_val, [float]) + # 检查margin是否为整数或列表类型。如果不符合要求,将抛出一个TypeError异常 self.margin = validator.check_value_type("margin", margin, [int, list]) + # 检查n_batch的值是否为int型,如果不符合要求,将抛出一个TypeError异常 self.n_batch = validator.check_value_type("n_batch", n_batch, [int]) + # 检查b_box的值是否为列表类型,如果不符合要求,将抛出一个TypeError异常 self.b_box = b_box if b_box is None else validator.check_value_type("b_box", b_box, [list]) + # 清除残留的计算结果 self.clear() def clear(self): + # 用于清空内部评估结果 """Clears the internal evaluation result.""" + # 将基础概率_baseline重置为0 self._baseline = 0 + # 将敏感性图像_sensitivity_im重置为0 self._sensitivity_im = 0 + # 将_is_update重置为false,表示未更新参数 self._is_update = False @rearrange_inputs def update(self, *inputs): + # 用于更新输入。它接收任意数量的关键字参数,包括model、y_pred和label。y_pred是一个批量测试图像,可以是2D或3D。 + # label是用于检查变化的分类标签,通常为TRUE,但也可以不是。model是神经网络模型 """ Updates input, including `model`, `y_pred` and `label`. @@ -104,64 +144,114 @@ class OcclusionSensitivity(Metric): RuntimeError: If y_pred.shape[0] is not 1. RuntimeError: If the number of labels is different from the number of batches. """ + + """ + OcclusionSensitivity类中update方法的预处理部分 + """ + # 首先,它检查输入是否为3个 if len(inputs) != 3: + # 如果不是,将抛出一个ValueError异常 raise ValueError("For 'OcclusionSensitivity.update', it needs 3 inputs (classification model, " "predicted value, label), but got {}.".format(len(inputs))) - + + # 首先,它从输入列表中提取model model = inputs[0] + # 然后,它将y_pred转换为nn.Tensor类型 y_pred = self._convert_data(inputs[1]) + # 然后,它将label转换为nn.Tensor类型 label = self._convert_data(inputs[2]) + # 然后,它将输入的model转换为nn.Cell类型 model = validator.check_value_type("model", model, [nn.Cell]) + # 检查y_pred的第一个维度是否大于1 if y_pred.shape[0] > 1: + # 如果是,将抛出一个RuntimeError异常 raise RuntimeError(f"For 'OcclusionSensitivity.update', the shape at index 0 of the predicted value " f"(input[1]) should be 1, but got {y_pred.shape[0]}.") + # 检查label是否为整数类型 if isinstance(label, int): + # 如果是,将将其转换为整数类型的numpy数组 label = np.array([[label]], dtype=int) # If the label is a tensor, make sure there's only 1 element + # 否则,检查label的形状是否与y_pred的形状相同 elif np.prod(label.shape) != y_pred.shape[0]: + # 如果不相同,将抛出一个RuntimeError异常 raise RuntimeError(f"For 'OcclusionSensitivity.update', the number of the label (input[2]) should be " f"same as the batches, but got the label number {np.prod(label.shape)}, " f"and batches {y_pred.shape[0]}.") + """ + OcclusionSensitivity类中update方法的计算基础概率部分 + """ + # 首先,它计算y_pred的形状 y_pred_shape = np.array(y_pred.shape[1:]) + # 然后使用_check_input_bounding_box方法检查b_box是否有效 b_box_min, b_box_max = _check_input_bounding_box(self.b_box, y_pred_shape) + # 接下来,它使用model函数计算y_pred的值,并将结果转换为numpy数组 temp = model(Tensor(y_pred)).asnumpy() + # 将计算出的基础概率存储在self._baseline属性中 self._baseline = temp[0, label].item() + # 创建两个空列表batch_images和batch_ids,用于存储批次图像和批次ID batch_images = [] batch_ids = [] + # 首先,它创建一个空numpy数组sensitivity_im,用于存储敏感性图像 sensitivity_im = np.empty(0, dtype=float) + # 然后,它计算output_im_shape,即y_pred的形状(如果b_box为空)或者b_box的最大值减去最小值加1(否则) output_im_shape = y_pred_shape if self.b_box is None else b_box_max - b_box_min + 1 + # 最后,它计算所需的预测数量num_required_predictions,即output_im_shape的乘积 num_required_predictions = np.prod(output_im_shape) + """ + OcclusionSensitivity类中update方法的遮罩生成部分 + """ + + # 使用一个循环遍历所需的预测数量num_required_predictions for i in trange(num_required_predictions): + # 并为每个预测计算一个遮罩 idx = np.unravel_index(i, output_im_shape) + # 遮罩的生成是通过计算min_idx和max_idx来确定遮罩的边界 + # 首先,如果b_box_min不为空 if b_box_min is not None: + # 那么将idx加上b_box_min idx += b_box_min + # 然后,计算min_idx和max_idx,分别表示遮罩的左上角和右下角坐标 min_idx = [max(0, i - self.margin) for i in idx] max_idx = [min(j, i + self.margin) for i, j in zip(idx, y_pred_shape)] + # 将y_pred在遮罩范围内的部分复制到一个新变量occlu_im中 occlu_im = y_pred.copy() + # 然后将y_pred在遮罩范围内的部分替换为self.pad_val occlu_im[(...,) + tuple(slice(i, j) for i, j in zip(min_idx, max_idx))] = self.pad_val + # 最后,将生成的遮罩和对应的标签添加到batch_images列表中 batch_images.append(occlu_im) + # 将生成的遮罩和对应的标签添加到batch_ids列表中 batch_ids.append(label) + """ + OcclusionSensitivity类中update方法的最终部分 + """ + # 如果batch_images的长度达到self.n_batch if len(batch_images) == self.n_batch or i == num_required_predictions - 1: + # 那么将sensitivity_im更新为当前batch_images和batch_ids的计算结果 sensitivity_im = _append_to_sensitivity_im(model, batch_images, batch_ids, sensitivity_im) + # 并将batch_images和batch_ids重置为空列表 batch_images = [] batch_ids = [] + # 首先,它将计算得到的敏感性图像sensitivity_im重塑为与output_im_shape相同的形状,然后将其赋值给self._sensitivity_im属性 self._sensitivity_im = sensitivity_im.reshape(output_im_shape) + # 最后,将is_update属性设置为True,表示已经更新了敏感性图像 self._is_update = True def eval(self): + # 它用于计算敏感性图像的差异,通常在update函数后调用 """ Computes the occlusion_sensitivity. @@ -172,50 +262,77 @@ class OcclusionSensitivity(Metric): RuntimeError: If the update method is not called first, an error will be reported. """ + # 首先,检查is_update属性是否为True if not self._is_update: + # 如果不为True,则抛出一个RuntimeError错误 raise RuntimeError("Please call the 'update' method before calling 'eval' method.") + # 然后,计算敏感性图像sensitivity_im与基本线baseline的差异,得到最终的敏感性值sensitivity sensitivity = self._baseline - np.squeeze(self._sensitivity_im) + # 最后,返回sensitivity return sensitivity def _append_to_sensitivity_im(model, batch_images, batch_ids, sensitivity_im): + # OcclusionSensitivity类中的一个辅助函数,用于将一组图像和对应的标签添加到敏感性图像sensitivity_im中 """ For a given number of images, the probability of predicting a given label is obtained. Attach to previous assessment. """ + # 首先将batch_images堆叠成一个二维数组 batch_images = np.vstack(batch_images) + # 然后将batch_ids扩展为一个二维数组 batch_ids = np.expand_dims(batch_ids, 1) + # 接下来,使用model模型对这些图像进行预测 model_numpy = model(Tensor(batch_images)).asnumpy() + # 通过将first_indices扩展为一个二维数组,可以方便地将其与batch_ids相匹配 first_indices = np.arange(batch_ids.shape[0])[:, None] + # 并将预测结果与first_indices相匹配 scores = model_numpy[first_indices, batch_ids] + # 如果sensitivity_im为空 if sensitivity_im.size == 0: + # 则直接返回scores return np.vstack(scores) + # 否则,将sensitivity_im和scores合并并返回 return np.vstack((sensitivity_im, scores)) def _check_input_bounding_box(b_box, im_shape): + # 检查边界框(如果提供了)是否符合预期 """Check that the bounding box (if supplied) is as expected.""" + # 如果还没有提供边界框(b_box),那么就将边界框的最小值(b_box_min)和最大值(b_box_max)设置为None # If no bounding box has been supplied, set min and max to None + # 首先判断边界框(b_box)是否为None if b_box is None: + # 如果为None,则将边界框的最小值(b_box_min)和最大值(b_box_max)设置为None b_box_min = b_box_max = None + # 否则,检查边界框的长度是否为2乘以预测值的长度(im_shape) else: if len(b_box) != 2 * len(im_shape): + # 如果不是,则抛出一个ValueError异常,表示边界框的格式不正确 raise ValueError(f"For 'OcclusionSensitivity', the bounding box should contain upper and lower for " f"all dimensions (except batch number), and the length of 'b_box' should be twice " f"as long as predicted value's (except batch number), but got 'b_box' length " f"{len(b_box)}, predicted value length (except batch number) {len(im_shape)}.") + # 首先将边界框(b_box)的上下边界(b_box_min和b_box_max)转换为numpy数组 b_box_min = np.array(b_box[::2]) b_box_max = np.array(b_box[1::2]) + # 然后,对于所有小于0的值,将其设置为0 b_box_min[b_box_min < 0] = 0 + # 对于所有小于0的值,将它们设置为图像尺寸减1 b_box_max[b_box_max < 0] = im_shape[b_box_max < 0] - 1 + # 最后,检查最大边界框是否大于等于图像尺寸 if np.any(b_box_max >= im_shape): + # 如果是,则抛出一个ValueError异常,表示最大边界框尺寸不正确 raise ValueError("For 'OcclusionSensitivity', maximum bounding box should be smaller than image size " "for all values.") + # 检查最小边界框是否小于最大边界框 if np.any(b_box_min > b_box_max): + # 如果是,则抛出一个ValueError异常,表示最小边界框尺寸不正确 raise ValueError("For 'OcclusionSensitivity', minimum bounding box should be smaller than maximum " "bounding box for all values.") + # 返回边界框的最小值(b_box_min)和最大值(b_box_max) return b_box_min, b_box_max diff --git a/mindspore/python/mindspore/nn/metrics/perplexity.py b/mindspore/python/mindspore/nn/metrics/perplexity.py index ab39bef424d..e92f066ff75 100644 --- a/mindspore/python/mindspore/nn/metrics/perplexity.py +++ b/mindspore/python/mindspore/nn/metrics/perplexity.py @@ -12,14 +12,25 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ +# Perplexity模块主要用于计算困惑度(Perplexity),是一种用于评估语言模型性能的指标。 +# 在深度学习中,困惑度是一种衡量模型预测结果好坏的标准,数值越小,模型预测越准确 """Perplexity""" +# 导入数学库,包含了一些基本的数学函数,如sqrt、log等 import math +# 导入NumPy库,是一个用于数值计算和数据处理的库 import numpy as np +# 从mindspore._checkparam模块中导入Validator类,用于参数验证 from mindspore._checkparam import Validator as validator +# 从当前模块的metric文件中导入Metric类和rearrange_inputs函数 from .metric import Metric, rearrange_inputs +# Metric类是一个通用的评估指标类,可以用于计算多种指标,如准确率、精确度、召回率等。rearrange_inputs函数用于对输入的数据进行重排,以便适合于计算指标 class Perplexity(Metric): + # 类的主要作用是计算困惑度(Perplexity),是一种用于评估语言模型性能的指标。 + # Perplexity类重写了Metric类中的update方法,用于更新混淆矩阵和计算困惑度。 + # 在计算困惑度时,首先对输入的数据进行重排,然后计算概率分布的对数,最后对数求平方根。 + # 参数ignore_label用于指定一个无效标签,在计算混淆矩阵和计算困惑度时,忽略该标签。如果设置为None,则包含所有标签。默认值为None r""" Computes perplexity. Perplexity is a measurement about how well a probability distribution or a model predicts a sample. A low perplexity indicates the model can predict the sample well. The function is shown as follows: @@ -50,21 +61,33 @@ class Perplexity(Metric): """ def __init__(self, ignore_label=None): + # 首先调用父类的__init__方法 super(Perplexity, self).__init__() + # 然后检查ignore_label参数的类型 if ignore_label is None: + # 如果为None,则将ignore_label设置为None self.ignore_label = ignore_label else: + # 否则,检查ignore_label参数是否为整数类型 self.ignore_label = validator.check_value_type("ignore_label", ignore_label, [int]) + # 如果不是,则抛出异常 self.clear() + # 最后,清空混淆矩阵和计数器 def clear(self): + # 用于清空内部评估结果 """Clears the internal evaluation result.""" + # 将_sum_metric的值重置为0.0 self._sum_metric = 0.0 + # 将_num_inst的值设置为0 self._num_inst = 0 @rearrange_inputs def update(self, *inputs): + # 用于更新内部评估结果。update方法接受任意数量的关键字参数,将这些参数作为输入,并将其重新排列以适应计算指标。 + + # rearrange_inputs函数用于对输入的数据进行重排,以便适合于计算指标。重排后的数据格式为(N, C),其中N是样本数量,C是类别数量 """ Updates the internal evaluation result `preds` and `labels`. @@ -78,40 +101,73 @@ class Perplexity(Metric): RuntimeError: If preds and labels have different lengths. RuntimeError: If label shape is not equal to pred shape. """ + """ + 检查输入的参数是否符合要求 + """ + # 如果输入参数的数量不为2 if len(inputs) != 2: + # 则抛出一个ValueError异常,表示需要传入2个输入参数,但实际传入的数量为len(inputs) raise ValueError("For 'Perplexity.update', it needs 2 inputs (predicted value, label), but got {}." .format(len(inputs))) + # 从inputs元组中获取第一项为preds preds = [self._convert_data(inputs[0])] + # 从inputs元组中获取第二项为labels labels = [self._convert_data(inputs[1])] + # 检查输入的preds和labels列表的长度是否相同 if len(preds) != len(labels): + # 如果不相同,则抛出一个RuntimeError异常,表示预测值(input[0])和标签(input[1])应该具有相同的长度,但实际得到的是preds长度为len(preds),标签长度为len(labels) raise RuntimeError("For 'Perplexity.update', predicted value (input[0]) and label (input[1]) should have " "the same length, but got predicted value length {}, label length {}." .format(len(preds), len(labels))) + """ + update方法中,用于计算损失(loss)和数量(num)的部分 + """ loss = 0. num = 0 + # 首先,将labels和preds列表中的元素一一对应地组合成一个新的元组列表,然后遍历这个列表 for label, pred in zip(labels, preds): + # 检查每个元组中的标签(label)和预测值(pred)的形状是否相同 if label.size != pred.size / pred.shape[-1]: + # 如果不同,则抛出一个RuntimeError异常,表示预测值(input[0])和标签(input[1])应该具有相同的长度,但实际得到的是preds长度为len(preds),标签长度为len(labels) raise RuntimeError("For 'Perplexity.update', predicted value (input[0]) and label (input[1]) should " "have the same shape, but got predicted value shape {}, label shape {}." .format(pred.shape, label.shape)) + """ + 这段代码是用于处理标签(label)数据的 + """ + + # 首先,将标签的形状调整为一个一维数组 label = label.reshape((label.size,)) + # 然后将其转换为整数类型 label_expand = label.astype(int) + # 接下来,使用np.expand_dims方法在标签的形状上添加一个维度 label_expand = np.expand_dims(label_expand, axis=1) + # 最后使用np.arange方法生成一个从0开始的一维数组,并将其与标签数组相乘 first_indices = np.arange(label_expand.shape[0])[:, None] + # 最后,使用np.squeeze方法删除不必要的维度 pred = np.squeeze(pred[first_indices, label_expand]) + # 如果self.ignore_label不为None if self.ignore_label is not None: + # 那么对于标签(label)中的值为self.ignore_label的元素,将其转换为1,其他元素转换为0 ignore = (label == self.ignore_label).astype(pred.dtype) + # 并累加得到新的预测值 num -= np.sum(ignore) + # 然后,将转换后的标签数组与预测值(pred)相乘 pred = pred * (1 - ignore) + ignore + # 最后,将新的预测值求对数并累加得到损失值 loss -= np.sum(np.log(np.maximum(1e-10, pred))) + # 在计算损失(loss)和数量(num)之后,将预测值(pred)的尺寸累加到num中 num += pred.size + # 将损失值(loss)累加到self._sum_metric中 self._sum_metric += loss + # 并将数量(num)累加到self._num_inst中 self._num_inst += num def eval(self): + # 用于计算困惑度(Perplexity),需要在update函数后调用,否则会报错 r""" Returns the current evaluation result. @@ -121,8 +177,11 @@ class Perplexity(Metric): Raises: RuntimeError: If the sample size is 0. """ + # 首先,检查self._num_inst是否为0 if self._num_inst == 0: + # 如果为0,则抛出一个RuntimeError raise RuntimeError("The 'Perplexity' can not be calculated, because the number of samples is 0, please " "check whether has called update method before calling eval method.") + # 然后,计算困惑度,即math.exp(self._sum_metric / self._num_inst)。最后,返回计算得到的困惑度 return math.exp(self._sum_metric / self._num_inst) diff --git a/mindspore/python/mindspore/nn/metrics/precision.py b/mindspore/python/mindspore/nn/metrics/precision.py index 0ae6659ae9f..1cebe97f69f 100644 --- a/mindspore/python/mindspore/nn/metrics/precision.py +++ b/mindspore/python/mindspore/nn/metrics/precision.py @@ -12,16 +12,25 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ +# Precision的类,用于计算准确率 """Precision.""" +# 首先,导入了所需的库和模块,如sys、numpy等 import sys import numpy as np - +# 从mindspore库中导入了一个名为Validator的类,并将其重命名为validator。这个类用于验证参数是否符合预期的范围和类型 from mindspore._checkparam import Validator as validator +# 然后,定义了一个名为Precision的类,继承自EvaluationBase from .metric import EvaluationBase, rearrange_inputs, _check_onehot_data +# 从metric模块中导入了三个函数:EvaluationBase、rearrange_inputs和_check_onehot_data。这些函数用于计算准确率,并处理输入数据格式 class Precision(EvaluationBase): + # 用于计算准确率。这个类继承自EvaluationBase。在计算准确率时,会使用两个局部变量true_positive和false_positive来记录正确的正例数和错误的正例数。计算公式为: + + # math::\text{precision} = \frac{\text{true_positive}}{\text{true_positive} + \text{false_positive}} + + # 注意:在多标签 cases 中,y和y_pred中的元素必须为0或1 r""" Calculates precision for classification and multilabel data. @@ -54,25 +63,40 @@ class Precision(EvaluationBase): [0.5 1. ] """ + # 在初始化时,需要传入一个参数eval_type,默认为'classification' def __init__(self, eval_type='classification'): + # 接着,调用父类的__init__方法,将eval_type传递给父类 super(Precision, self).__init__(eval_type) + # 然后,初始化一个局部变量eps,其值为sys.float_info.min,用于避免除以0的情况 self.eps = sys.float_info.min + # 最后,调用clear方法清空记录的准确率数据 self.clear() def clear(self): + # 用于清空内部评估结果 """Clears the internal evaluation result.""" + # 首先将_class_num重置为0 self._class_num = 0 + # 然后根据_type的值清空不同的记录数据 if self._type == "multilabel": + # 在multilabel cases中,清空_true_positives和_positives两个数组 self._true_positives = np.empty(0) self._positives = np.empty(0) + # 并将_true_positives_average和_positives_average重置为0 self._true_positives_average = 0 self._positives_average = 0 else: + # 在classification cases中,只清空_true_positives和_positives两个变量 self._true_positives = 0 + # 并将_true_positives_average和_positives_average重置为0 self._positives = 0 @rearrange_inputs def update(self, *inputs): + # 在update方法中,接收两个输入参数y_pred和y,并对它们进行处理 + # 输入的y_pred和y可以是Tensor、列表或numpy.ndarray类型。在classification cases中,y_pred通常是一个浮点数列表, + # 其范围在0到1之间,且形状为(N, C),其中N是样本数,C是类别数。对于multilabel cases,输入的y_pred和y必须是one-hot编码的, + # 其中1表示正类。y的形状通常是(N, C),而y_pred的形状通常是(N, C)。 """ Updates the internal evaluation result with `y_pred` and `y`. @@ -90,50 +114,85 @@ class Precision(EvaluationBase): Raises: ValueError: If the number of inputs is not 2. """ + """ + 检查输入的y_pred和y是否符合准确率计算的要求 + """ + # 如果输入参数的数量不是2 if len(inputs) != 2: + # 将抛出一个ValueError异常 raise ValueError("For 'Precision.update', it needs 2 inputs (predicted value, true value), " "but got {}.".format(len(inputs))) + # 首先,使用_convert_data方法将输入的数据转换为正确的格式 y_pred = self._convert_data(inputs[0]) y = self._convert_data(inputs[1]) + + # 首先,检查_type是否为'classification',且y_pred和y的维度是否相同,然后,使用_check_onehot_data函数检查y是否为one-hot编码 if self._type == 'classification' and y_pred.ndim == y.ndim and _check_onehot_data(y): + # 。如果满足这些条件,将y转换为argmax索引,即实际类别标签 y = y.argmax(axis=1) + # 最后,使用_check_shape和_check_value函数检查y_pred和y的形状和值是否符合准确率计算的要求 self._check_shape(y_pred, y) self._check_value(y_pred, y) + # 检查Precision类的_class_num是否为0 if self._class_num == 0: + # 如果匹配,将_class_num更新为y_pred的类别数 self._class_num = y_pred.shape[1] + # 以及y_pred的形状是否与_class_num匹配 elif y_pred.shape[1] != self._class_num: + # 如果不匹配,将抛出一个ValueError异常 raise ValueError("For 'Precision.update', class number not match, last input predicted data contain {} " "classes, but current predicted data contain {} classes, please check your predicted " "value(inputs[0])".format(self._class_num, y_pred.shape[1])) + # 最后,从y_pred中提取class_num,以便在后续计算中使用 class_num = self._class_num + # 首先,检查_type是否为'classification'(二分类) if self._type == "classification": + # 然后,检查y的最大值是否大于class_num if y.max() + 1 > class_num: + # 如果是,则抛出一个ValueError异常 raise ValueError("For 'Precision.update', predicted value (input[0]) should have the same classes " "number as true value (input[1]), but got predicted value classes {}, true value " "classes {}.".format(class_num, y.max() + 1)) + # 然后,根据_type的值,将y转换为one-hot编码 y = np.eye(class_num)[y.reshape(-1)] indices = y_pred.argmax(axis=1).reshape(-1) + # 并将y_pred转换为相应的格式 y_pred = np.eye(class_num)[indices] + # 对于multilabel(多分类) cases elif self._type == "multilabel": + # 将y_pred和y交换轴 y_pred = y_pred.swapaxes(1, 0).reshape(class_num, -1) + # 并将其reshape为(class_num, -1)的形状 y = y.swapaxes(1, 0).reshape(class_num, -1) + """ + 以下代码计算准确率 + """ + # 首先,计算y_pred中每个类别的正例数量,并将它们累加到一个变量positives中 positives = y_pred.sum(axis=0) + # 然后,计算y和y_pred中每个类别的真实正例数量,并将它们累加到一个变量true_positives中 true_positives = (y * y_pred).sum(axis=0) + # 接下来,根据_type的值进行处理。对于multilabel cases if self._type == "multilabel": + # 将true_positives除以positives,然后对结果进行求和,并将和累加到self._true_positives_average中 self._true_positives_average += np.sum(true_positives / (positives + self.eps)) + # 同时,将positives累加到self._positives_average中 self._positives_average += len(positives) + # 最后,将true_positives和positives concatenate到相应的self._true_positives和self._positives中 self._true_positives = np.concatenate((self._true_positives, true_positives), axis=0) self._positives = np.concatenate((self._positives, positives), axis=0) + # 对于classification cases else: + # 直接将true_positives和positives累加到相应的变量中 self._true_positives += true_positives self._positives += positives def eval(self, average=False): + # eval方法,用于计算准确率,该方法需要在update方法后调用,否则会报错 """ Computes the precision. @@ -143,16 +202,25 @@ class Precision(EvaluationBase): Returns: numpy.float64, the computed result. """ + # 首先,检查self._class_num是否为0 if self._class_num == 0: + # 如果是,则抛出一个RuntimeError异常 raise RuntimeError("The 'Precision' can not be calculated, because the number of samples is 0, " "please check whether your inputs (predicted value, true value) are empty, or " "has called update method before calling eval method.") + # 然后,检查average参数的类型是否为bool,如果不是,则抛出一个TypeError异常 validator.check_value_type("average", average, [bool], self.__class__.__name__) + # 接下来,计算准确率,即true_positives除以positives,并将结果累加到一个变量result中 result = self._true_positives / (self._positives + self.eps) + # 如果average为True,对结果进行求和,并返回平均值 if average: + # 最后,根据self._type的值进行处理 if self._type == "multilabel": + # 对于multilabel cases,将self._true_positives_average和self._positives_average除以self.eps,然后对结果进行求和,并将和累加到result中 result = self._true_positives_average / (self._positives_average + self.eps) + # 对于classification cases,直接将true_positives和positives累加到result中 return result.mean() + # 否则直接返回结果 return result diff --git a/mindspore/python/mindspore/nn/metrics/recall.py b/mindspore/python/mindspore/nn/metrics/recall.py index b43d5fb79c5..50755a569af 100644 --- a/mindspore/python/mindspore/nn/metrics/recall.py +++ b/mindspore/python/mindspore/nn/metrics/recall.py @@ -12,16 +12,22 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ +# Recall类,用于计算召回率(Recall) """Recall.""" +# 用于访问与Python解释器相关的变量和函数 import sys - +# 导入了所需的库和模块,如numpy和mindspore import numpy as np - +# 从mindspore._checkparam模块中导入了一个名为validator的类,用于验证参数 from mindspore._checkparam import Validator as validator +# Recall类继承自EvaluationBase类 from .metric import EvaluationBase, rearrange_inputs, _check_onehot_data +# 从mindspore.metric模块中导入了一个名为EvaluationBase的类和三个函数,分别是rearrange_inputs、_check_onehot_data class Recall(EvaluationBase): + # 用于计算召回率(Recall)。Recall类继承自EvaluationBase类。在计算召回率时,会使用两个本地变量true_positive和false_negative来存储真实阳性和假负例的数量。 + # 需要注意的是,在多分类 cases 中,元素的y和y_pred必须为0或1 r""" Calculates recall for classification and multilabel data. @@ -54,25 +60,38 @@ class Recall(EvaluationBase): >>> print(recall) [1. 0.5] """ + # 在定义Recall类时,需要初始化一个eval_type参数,默认为'classification' def __init__(self, eval_type='classification'): + # 然后,调用父类的构造函数,将self传递给父类 super(Recall, self).__init__(eval_type) + # 接着,初始化一个eps属性,用于存储一个极小值 self.eps = sys.float_info.min + # 最后,调用clear方法来清空内部变量 self.clear() def clear(self): + # 用于清空内部评估结果 """Clears the internal evaluation result.""" + # 首先,将_class_num属性设置为0 self._class_num = 0 + # 如果_type属性为"multilabel" if self._type == "multilabel": + # 则将_true_positives和_actual_positives属性设置为空数组 self._true_positives = np.empty(0) self._actual_positives = np.empty(0) + # 并将_true_positives_average和_actual_positives_average属性设置为0 self._true_positives_average = 0 self._actual_positives_average = 0 else: + # 否则,将_true_positives和_actual_positives属性设置为0 self._true_positives = 0 self._actual_positives = 0 @rearrange_inputs def update(self, *inputs): + # 用于更新内部评估结果。接收一个或多个输入参数,分别是y_pred和y。对于'classification'评估类型,y_pred通常是一个浮点数列表,范围在0到1之间, + # 形状为(N, C),其中N是样本数量,C是类别数量。对于'multilabel'评估类型,y_pred和y必须是one-hot编码的数组,值全为0或1。 + # indices中值为1的索引表示正类别。y_pred和y的形状都是(N, C) """ Updates the internal evaluation result with `y_pred` and `y`. @@ -91,49 +110,78 @@ class Recall(EvaluationBase): Raises: ValueError: If the number of inputs is not 2. """ + # 首先检查输入参数的数量是否为2 if len(inputs) != 2: + # 如果不是,则抛出一个ValueError异常 raise ValueError("For 'Recall.update', it needs 2 inputs (predicted value, true value), " "but got {}.".format(len(inputs))) + # 然后,将输入的y_pred和true value转换为适当的数据格式 y_pred = self._convert_data(inputs[0]) y = self._convert_data(inputs[1]) + # 如果评估类型为'classification',并且y_pred的形状与y相同,并且是one-hot编码 if self._type == 'classification' and y_pred.ndim == y.ndim and _check_onehot_data(y): + # 那么将y转换为argmax轴1的值 y = y.argmax(axis=1) + # 最后,检查y_pred和y的形状和值是否符合要求 self._check_shape(y_pred, y) self._check_value(y_pred, y) + # 检查_class_num属性是否为0 if self._class_num == 0: + # 如果是,则将其设置为y_pred的形状[1] self._class_num = y_pred.shape[1] + # 如果y_pred的形状[1]与_class_num不同 elif y_pred.shape[1] != self._class_num: + # 则抛出一个ValueError异常 raise ValueError("For 'Recall.update', class number not match, last input predicted data contain {} " "classes, but current predicted data contain {} classes, please check your predicted " "value(inputs[0]).".format(self._class_num, y_pred.shape[1])) + # 首先获取_class_num属性,然后根据评估类型进行相应的处理 class_num = self._class_num + # 如果评估类型为'classification' if self._type == "classification": + # 首先检查y的最大值是否大于_class_num if y.max() + 1 > class_num: + # 如果是,则抛出一个ValueError异常 raise ValueError("For 'Recall.update', predicted value (input[0]) should have the same classes number " "as true value (input[1]), but got predicted value classes {}, true value classes {}." .format(class_num, y.max() + 1)) + # 接着,将y转换为one-hot编码 y = np.eye(class_num)[y.reshape(-1)] + # 并获取y_pred中每个类别概率最大的索引 indices = y_pred.argmax(axis=1).reshape(-1) + # 最后,将y_pred转换回one-hot编码 y_pred = np.eye(class_num)[indices] + # 如果评估类型为'multilabel' elif self._type == "multilabel": + # 则将y_pred和y交换轴 y_pred = y_pred.swapaxes(1, 0).reshape(class_num, -1) + # 并reshape为(class_num, -1) y = y.swapaxes(1, 0).reshape(class_num, -1) + # 首先,使用sum函数将y沿axis=0轴求和,得到actual_positives actual_positives = y.sum(axis=0) + # 然后使用点乘运算将y_pred和y相乘,再沿axis=0轴求和,得到true_positives true_positives = (y * y_pred).sum(axis=0) + # 如果评估类型为'multilabel' if self._type == "multilabel": + # 那么将true_positives除以(actual_positives + self.eps)后求和,然后累加到self._true_positives_average中 self._true_positives_average += np.sum(true_positives / (actual_positives + self.eps)) + # 接着,将actual_positives累加到self._actual_positives_average中 self._actual_positives_average += len(actual_positives) + # 最后,将true_positives和actual_positives拼接在一起,存储在self._true_positives和self._actual_positives中 self._true_positives = np.concatenate((self._true_positives, true_positives), axis=0) self._actual_positives = np.concatenate((self._actual_positives, actual_positives), axis=0) else: + # 如果评估类型为'classification',那么直接将true_positives和actual_positives累加在一起 self._true_positives += true_positives + # 存储在self._true_positives和self._actual_positives中 self._actual_positives += actual_positives def eval(self, average=False): + # 用于计算召回率。方法接收一个名为average的布尔参数,用于指定是否计算平均召回率 """ Computes the recall. @@ -143,16 +191,24 @@ class Recall(EvaluationBase): Returns: numpy.float64, the computed result. """ + # 检查输入参数是否为空 if self._class_num == 0: + # 如果是,则抛出一个RuntimeError异常 raise RuntimeError("The 'Recall' can not be calculated, because the number of samples is 0, please check " "whether your inputs (predicted value, true value) are empty, or has called update " "method before calling eval method.") + # 检查输入参数average是否为布尔值,如果不是,则抛出一个TypeError异常 validator.check_value_type("average", average, [bool], self.__class__.__name__) + # 然后,计算召回率 result = self._true_positives / (self._actual_positives + self.eps) + # 如果average为True,则计算平均结果 if average: + # 如果type为多标签,则计算多标签评估下的召回率 if self._type == "multilabel": result = self._true_positives_average / (self._actual_positives_average + self.eps) + # 否则,计算正确结果的平均值 return result.mean() + # 否则,返回计算得到的召回率 return result diff --git a/mindspore/python/mindspore/nn/metrics/roc.py b/mindspore/python/mindspore/nn/metrics/roc.py index f47892668cf..b2926c658e4 100644 --- a/mindspore/python/mindspore/nn/metrics/roc.py +++ b/mindspore/python/mindspore/nn/metrics/roc.py @@ -12,13 +12,22 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ +# 主要用于计算接收者操作特征(ROC)曲线。它使用了NumPy库来处理数据,以及MindSpore库中的Metric和_binary_clf_curve函数。 +# 这段代码的主要功能是计算ROC曲线,用于二分类问题的评估 """ROC""" +# 首先,导入了所需的库,包括NumPy库和MindSpore库 import numpy as np +# 导入了一个名为"validator"的类,用于验证参数 from mindspore._checkparam import Validator as validator +# 从"metric"模块中导入了一个名为"Metric"的类,用于计算评估指标 from .metric import Metric, rearrange_inputs, _binary_clf_curve +# 从"metric"模块中导入了一个名为"_binary_clf_curve"的函数,用于计算二分类问题的ROC曲线 class ROC(Metric): + # 该类的主要功能是计算接收者操作特征(ROC)曲线,用于二分类问题的评估。在多分类问题中,它会根据一个vs所有(one-vs-all)的方式计算ROC曲线 + # 参数说明:class_num (int):类别的数量。在二分类问题中,不需要提供此参数。默认值:None。 + # pos_label (int):确定正类的整数。对于二分类问题,默认值为1。对于多分类问题,此参数不应设置,因为它会根据range [0,num_classes-1]的整数迭代变化。默认值:None。 """ Calculates the ROC curve. It is suitable for solving binary classification and multi classification problems. In the case of multiclass, the values will be calculated based on a one-vs-the-rest approach. @@ -69,20 +78,28 @@ class ROC(Metric): array([1.75, 0.75, 0.05])] """ def __init__(self, class_num=None, pos_label=None): + # 首先,使用super()调用父类的构造函数,完成类的初始化 super().__init__() + # 然后,将传入的"class_num"和"pos_label"参数赋值给类的成员变量。这里使用validator库中的check_value_type函数来验证参数的类型 self.class_num = class_num if class_num is None else validator.check_value_type("class_num", class_num, [int]) self.pos_label = pos_label if pos_label is None else validator.check_value_type("pos_label", pos_label, [int]) + # 最后,调用self.clear()函数清空类的其他成员变量 self.clear() def clear(self): + # 用于清空类内部评估的结果 """Clear the internal evaluation result.""" + # 将类的成员变量"y_pred"、"y"和"sample_weights"的值重置为0 self.y_pred = 0 self.y = 0 self.sample_weights = None + # 并将"_is_update"设置为False self._is_update = False + # 这样,在计算ROC曲线时,可以重新开始计算 @rearrange_inputs def update(self, *inputs): + # 用于更新状态,以便在计算ROC曲线时使用 """ Update state with predictions and targets. @@ -93,51 +110,81 @@ class ROC(Metric): is the number of categories. y contains values of integers. The shape is :math:`(N,C)` if one-hot encoding is used. Shape can also be :math:`(N,)` if category index is used. """ + # 首先,检查传入的输入参数数量是否为2 if len(inputs) != 2: + # 如果不是,则抛出一个ValueError异常 raise ValueError("For 'ROC.update', it needs 2 inputs (predicted value, true value), but got {}" .format(len(inputs))) + # 然后,将传入的"y_pred"和"y"参数转换为NumPy数组 y_pred = self._convert_data(inputs[0]) y = self._convert_data(inputs[1]) + # 调用_precision_recall_curve_update函数,计算ROC曲线的参数。这个函数会根据传入的"y_pred"、"y"、"class_num"和"pos_label"参数计算ROC曲线的相关参数 y_pred, y, class_num, pos_label = _precision_recall_curve_update(y_pred, y, self.class_num, self.pos_label) + # 将计算得到的参数赋值给类的成员变量"y_pred"、"y"、"class_num"和"pos_label" self.y_pred = y_pred self.y = y self.class_num = class_num self.pos_label = pos_label + # 最后,将"_is_update"设置为True,表示已经更新了状态 self._is_update = True def _roc_eval(self, y_pred, y, class_num, pos_label, sample_weights=None): + # 一个辅助函数,用于计算ROC曲线。它根据传入的"y_pred"、"y"、"class_num"和"pos_label"参数计算ROC曲线的真阳率(tpr)和假阳率(fpr) """Computes the ROC curve.""" + # 首先,检查"class_num"是否为1 if class_num == 1: + # 如果是,则调用_binary_clf_curve函数计算二分类问题的ROC曲线 fps, tps, thresholds = _binary_clf_curve(y_pred, y, sample_weights=sample_weights, pos_label=pos_label) + # 将"tps"和"fps"数组分别压缩和拼接成一个新的数组。 tps = np.squeeze(np.hstack([np.zeros(1, dtype=tps.dtype), tps])) fps = np.squeeze(np.hstack([np.zeros(1, dtype=fps.dtype), fps])) + # 同时,将"thresholds"数组拼接成一个新数组,并在开头插入一个0 thresholds = np.hstack([thresholds[0][None] + 1, thresholds]) + # 这样,就可以得到一个新的数组,其中包含了所有可能的阈值,以及对应的真阳率(tpr)和假阳率(fpr) + # 检查假阳率(fpr)和真阳率(tpr)是否有效 + # 如果假阳率(fpr)小于等于0 if fps[-1] <= 0: + # 表示没有负样本在真实值中,假阳率是无意义的 raise ValueError("For 'ROC.eval', there is no negative samples in true value, " "false positive value is meaningless.") fpr = fps / fps[-1] + # 如果真阳率(tpr)为0 if tps[-1] <= 0: + # 表示没有正样本在真实值中,真阳率是无意义的 raise ValueError("For 'ROC.eval', there is no positive samples in true value, " "true positive value is meaningless.") tpr = tps / tps[-1] + # 返回假阳率(fpr)、真阳率(tpr)和阈值数组。通过计算假阳率(fpr)和真阳率(tpr)曲线,可以直观地看出模型的性能 return fpr, tpr, thresholds + # 如果class_num不等于1,首先初始化假阳率(fpr)、真阳率(tpr)和阈值数组 fpr, tpr, thresholds = [], [], [] + # 然后遍历每个类别 for c in range(class_num): + # 首先,从"y_pred"数组中提取当前类别的预测结果(preds_c) preds_c = y_pred[:, c] + # 然后调用_roc方法计算ROC曲线 res = self._roc(preds_c, y, class_num=1, pos_label=c, sample_weights=sample_weights) + # 最后,将计算得到的假阳率(fpr)、真阳率(tpr)和阈值数组分别添加到相应的数组中 fpr.append(res[0]) tpr.append(res[1]) thresholds.append(res[2]) + # 返回假阳率(fpr)、真阳率(tpr)和阈值数组 return fpr, tpr, thresholds def _roc(self, y_pred, y, class_num=None, pos_label=None, sample_weights=None): + # 一个私有方法_roc,用于计算ROC曲线。它接收以下参数: + # y_pred:预测结果,通常是一个浮点数列表,范围在0到1之间,形状为(N, C),其中N是样本数量,C是类别数量。 + # y:真实值,通常是一个整数列表。 + # class_num:类别数量。对于二分类问题,不需要提供这个参数。默认值为None。 + # pos_label:正类标签。对于二分类问题,可以设置为1。对于多分类问题,不要设置这个参数,因为它是迭代更新的。默认值为None。 + # sample_weights:样本权重。如果为None,则权重值为1;如果为ndarray,则权重值为ndarray值。 """ Update curve and return the result of the ROC curve. @@ -154,11 +201,14 @@ class ROC(Metric): sample_weights (Union[None, np.ndarray]): If sample_weights is None, the weight value is 1. If sample_weights is ndarray, the weight value is the ndarray value. """ + # 首先对y_pred和y进行预处理 y_pred, y, class_num, pos_label = _precision_recall_curve_update(y_pred, y, class_num, pos_label) + # 然后调用_roc_eval方法计算ROC曲线 return self._roc_eval(y_pred, y, class_num, pos_label, sample_weights) def eval(self): + # 用于计算ROC曲线 """ Computes the ROC curve. @@ -177,44 +227,71 @@ class ROC(Metric): RuntimeError: If the update method is not called first, an error will be reported. """ + # 如果_is_update is False,表示参数未更新 if self._is_update is False: + # 则抛出错误RuntimeError raise RuntimeError("Please call the 'update' method before calling 'eval' method.") + # 将"y_pred"和"y"数组分别压缩和拼接成一个新的数组。 y_pred = np.squeeze(np.vstack(self.y_pred)) y = np.squeeze(np.vstack(self.y)) + # 然后调用_roc_eval方法计算ROC曲线 return self._roc_eval(y_pred, y, self.class_num, self.pos_label) def _precision_recall_curve_update(y_pred, y, class_num, pos_label): """update curve""" + # 定义了一个名为_precision_recall_curve_update的私有方法,用于更新曲线。它接收以下参数: + # y_pred:预测结果,通常是一个浮点数列表,范围在0到1之间,形状为(N, C),其中N是样本数量,C是类别数量。 + # y:真实值,通常是一个整数列表。 + # class_num:类别数量。对于二分类问题,不需要提供这个参数。默认值为None。 + # pos_label:正类标签。对于二分类问题,可以设置为1。对于多分类问题,不要设置这个参数,因为它是迭代更新的。默认值为None + + # 检查预测结果(y_pred)和真实值(y)的形状是否相同,或者预测结果的形状是否等于真实值加1 if not (len(y_pred.shape) == len(y.shape) or len(y_pred.shape) == len(y.shape) + 1): + # 如果不满足这些条件,则抛出一个错误 raise ValueError(f"For 'ROC', predicted value (input[0]) and true value (input[1]) should have same " f"dimensions, or the dimension of predicted value equal the dimension of true value add " f"1, but got predicted value ndim: {len(y_pred.shape)}, true value ndim: {len(y.shape)}.") # single class evaluation + # 检查预测结果(y_pred)和真实值(y)的形状是否相同 if len(y_pred.shape) == len(y.shape): + # 以及是否提供了正确的类别数量(class_num)和正类标签(pos_label) if class_num is not None and class_num != 1: + # 如果没有提供正确的类别数量和正类标签,或者预测结果和真实值的形状相同,则抛出一个错误 raise ValueError(f"For 'ROC', when predicted value (input[0]) and true value (input[1]) have the same " f"shape, the 'class_num' should be 1, but got {class_num}.") + # 将类别数量(class_num)设置为1 class_num = 1 + # 然后检查正类标签(pos_label)是否为None if pos_label is None: + # 如果为None,则将其设置为1 pos_label = 1 + # 最后,将预测结果(y_pred)和真实值(y)展平为一个一维数组 y_pred = y_pred.flatten() y = y.flatten() # multi class evaluation + # 当预测结果的形状等于真实值加1时 elif len(y_pred.shape) == len(y.shape) + 1: + # 如果提供了正类标签(pos_label) if pos_label is not None: + # 则抛出一个错误ValueError raise ValueError(f"For 'ROC', when the dimension of predicted value (input[0]) equals the dimension " f"of true value (input[1]) add 1, the 'pos_label' should be None, " f"but got {pos_label}.") + # 如果类别数量(class_num)与预测结果的形状[1]不同 if class_num != y_pred.shape[1]: + # 则抛出一个错误ValueError raise ValueError("For 'ROC', the 'class_num' should equal the number of classes from predicted value " "(input[0]), but got 'class_num' {}, the number of classes from predicted value {}." .format(class_num, y_pred.shape[1])) + # 最后,将预测结果的形状转换为(class_num, -1) y_pred = y_pred.transpose(0, 1).reshape(class_num, -1).transpose(0, 1) + # 然后展平为一个一维数组 y = y.flatten() + # 返回假阳率(fpr)、真阳率(tpr)和阈值数组 return y_pred, y, class_num, pos_label diff --git a/mindspore/python/mindspore/nn/metrics/root_mean_square_surface_distance.py b/mindspore/python/mindspore/nn/metrics/root_mean_square_surface_distance.py index 5abd47acdfb..3d2febe991d 100644 --- a/mindspore/python/mindspore/nn/metrics/root_mean_square_surface_distance.py +++ b/mindspore/python/mindspore/nn/metrics/root_mean_square_surface_distance.py @@ -12,14 +12,27 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ +# 用于计算根均方表面距离(RMSD) """RootMeanSquareSurfaceDistance.""" +# 首先,从scipy.ndimage模块导入morphology函数,用于对图像进行形态学操作 from scipy.ndimage import morphology +# 然后,从numpy模块导入np,用于处理数值计算 import numpy as np +# 从mindspore._checkparam模块导入Validator类,用于参数验证 from mindspore._checkparam import Validator as validator +# 从mindspore.metric模块导入Metric类,用于计算评估指标 from .metric import Metric, rearrange_inputs +# rearrange_inputs函数接收一个输入数据inputs和一个整数batch_size,然后将输入数据按照batch_size进行分组,并将每组数据转换为一个一维数组 class RootMeanSquareDistance(Metric): + # 它是Metric类的子类。RootMeanSquareDistance类用于计算两个集合A和B之间的Root Mean Square Surface Distance。 + # Root Mean Square Surface Distance (RMSSD)是一种衡量两个集合A和B之间的距离度量。 + + # 参数distance_metric用于指定距离度量方法,支持"euclidean"(欧氏距离)、"chessboard"(棋盘距离)和"taxicab"(出租车距离)。默认方法是"euclidean"。 + + # 参数symmetric用于指定是否计算对称的Root Mean Square Surface Distance。如果为False,只计算RmsSurDis(y_{pred}, y), + # 即从y_pred到y的表面距离;如果为True,计算从y_pred到y和从y到y_pred的均值,即RmsSurDis({y_pred} \leftrightarrow y)。默认值为False。 r""" Computes the Root Mean Square Surface Distance from `y_pred` to `y` under the default setting. @@ -69,46 +82,70 @@ class RootMeanSquareDistance(Metric): 1.0000000000000002 """ - + # 在初始化RootMeanSquareDistance类时,需要传入两个参数:symmetric和distance_metric。 def __init__(self, symmetric=False, distance_metric="euclidean"): + # 首先,代码调用父类的__init__方法,初始化Metric类 super(RootMeanSquareDistance, self).__init__() + # 后,定义了一个名为distance_metric_list的列表,用于存储支持的所有距离度量方法 self.distance_metric_list = ["euclidean", "chessboard", "taxicab"] + # 接下来,将distance_metric参数转换为小写 distance_metric = validator.check_value_type("distance_metric", distance_metric, [str]) + # 并检查其是否在distance_metric_list中 self.distance_metric = validator.check_string(distance_metric, self.distance_metric_list, "distance_metric") + # 最后,检查symmetric是否为bool值,并设置symmetric和distance_metric为类的属性 self.symmetric = validator.check_value_type("symmetric", symmetric, [bool]) + # 初始化clear方法,清空残余数据 self.clear() + # 用于存储y_pred顶点的边,初始为None self._y_pred_edges = None + # 用于存储是否已经更新过_y_pred_edges的值,初始为None self._is_update = None + # 用于存储y顶点的边,初始为None self._y_edges = None def _get_surface_distance(self, y_pred_edges, y_edges): + # 用于计算从y_pred_edges到y_edges的表面距离。y_pred_edges是y_pred顶点的边,y_edges是y顶点的边 """ Calculate the surface distances from `y_pred_edges` to `y_edges`. - Args: + Args: y_pred_edges (np.ndarray): the edge of the predictions. y_edges (np.ndarray): the edge of the ground truth. """ + # 如果y_pred_edges为空,则返回空数组 if not np.any(y_pred_edges): + # 如果为空,则返回一个空数组 return np.array([]) + # 如果y_edges为空 if not np.any(y_edges): + # 则返回一个全部为inf的数组 dis = np.full(y_edges.shape, np.inf) + # 否则,如果distance_metric为euclidean,则计算距离 else: + # 如果distance_metric为"euclidean" if self.distance_metric == "euclidean": + # 则使用morphology.distance_transform_edt函数计算欧氏距离 dis = morphology.distance_transform_edt(~y_edges) + # 如果distance_metric在distance_metric_list中 elif self.distance_metric in self.distance_metric_list[-2:]: + # 则使用morphology.distance_transform_cdt函数计算棋盘距离 dis = morphology.distance_transform_cdt(~y_edges, metric=self.distance_metric) + # 最后,返回计算得到的表面距离数组 return dis[y_pred_edges] def clear(self): + # 用于清空内部评估结果 """Clears the internal evaluation result.""" + # 它将_y_pred_edges、_y_edges和_is_update属性的值设置为0,表示没有计算过任何结果 self._y_pred_edges = 0 self._y_edges = 0 self._is_update = False @rearrange_inputs def update(self, *inputs): + # 用于更新内部评估结果。它接受三个输入:y_pred(预测的二值图像)、y(实际的二值图像)和label_idx(整数或浮点数)。 + # update方法的主要目的是更新_y_pred_edges、_y_edges和_is_update属性,以便在计算过程中使用 """ Updates the internal evaluation result 'y_pred', 'y' and 'label_idx'. @@ -123,36 +160,52 @@ class RootMeanSquareDistance(Metric): ValueError: If the value of label_idx is not in y_pred or y. ValueError: If y_pred and y have different shapes. """ - if len(inputs) != 3: + # 首先,代码检查输入的数量是否为3 + if len(inputs)!= 3: + # 如果不是,则抛出一个ValueError异常 raise ValueError("For 'RootMeanSquareDistance.update', it needs 3 inputs" "(predicted value, true value, label index), but got {}.".format(len(inputs))) + # 然后,将y_pred和y转换为Tensor、列表或numpy.ndarray类型 y_pred = self._convert_data(inputs[0]) y = self._convert_data(inputs[1]) + # 将输入列表中的第三个元素(即label_idx)转换为整数类型 label_idx = inputs[2] + # 接下来,代码检查label_idx的数据类型是否为整数或浮点数 if not isinstance(label_idx, (int, float)): + # 如果不是,则抛出一个TypeError异常 raise TypeError("For 'RootMeanSquareDistance.update', the label index (input[2]) must be int or float, " "but got label index type: {}.".format(type(label_idx))) + # 检查label_idx是否在y_pred和y中 if label_idx not in y_pred and label_idx not in y: + # 如果不在其中,则抛出一个ValueError异常 raise ValueError("For 'RootMeanSquareDistance.update', the label index (input[2]) " "should be in predicted value (input[0]) or true value (input[1]), " "but {} is not.".format(label_idx)) + # 最后,代码检查y_pred和y是否具有相同的形状 if y_pred.size == 0 or y_pred.shape != y.shape: + # 如果不是,则抛出一个ValueError异常 raise ValueError("For 'RootMeanSquareDistance.update', the size of predicted value (input[0]) " "and true value (input[1]) should be greater than 0, in addition to that, " "predicted value and true value should have the same shape, " "but got predicted value size: {}, shape: {}, true value size: {}, shape: {}. " .format(y_pred.size, y_pred.shape, y.size, y.shape)) + # 首先检查y_pred和y的数据类型是否为布尔类型 if y_pred.dtype != bool: + # 如果不是,则将它们转换为布尔类型 y_pred = y_pred == label_idx if y.dtype != bool: + # 如果不是,则将它们转换为布尔类型 y = y == label_idx + # 接下来,使用morphology.binary_erosion函数对y_pred_edges和y_edges进行二值腐蚀 self._y_pred_edges = morphology.binary_erosion(y_pred) ^ y_pred self._y_edges = morphology.binary_erosion(y) ^ y + # 最后,将_y_pred_edges、_y_edges和_is_update属性设置为True,表示已经更新了内部评估结果 self._is_update = True def eval(self): + # 用于计算Root Mean Square Distance(RMSD),需在update函数后调用 """ Calculate Root Mean Square Distance. @@ -163,24 +216,38 @@ class RootMeanSquareDistance(Metric): RuntimeError: If the update method is not called first, an error will be reported. """ + # 首先,它检查_is_update属性是否为True if self._is_update is False: + # 如果是,则表示已经更新了内部评估结果,如果不是,则抛出一个RuntimeError异常,要求先调用update方法 raise RuntimeError("Please call the 'update' method before calling 'eval' method.") + # 接下来,代码计算_y_pred_edges和_y_edges之间的表面距离,并将其存储在residual_mean_square_distance中 residual_mean_square_distance = self._get_surface_distance(self._y_pred_edges, self._y_edges) + # 如果residual_mean_square_distance的形状为(0,) if residual_mean_square_distance.shape == (0,): + # 则表示没有计算过任何结果,返回np.inf return np.inf + # 然后,代码计算RMSD,即均方根误差 rms_surface_distance = (residual_mean_square_distance**2).mean() + # 如果symmetric属性为False if not self.symmetric: + # 则直接返回RMSD return rms_surface_distance + # 否则,计算与_y_edges相反的_y_pred_edges之间的表面距离,并将其存储在contrary_residual_mean_square_distance中 contrary_residual_mean_square_distance = self._get_surface_distance(self._y_edges, self._y_pred_edges) + # 如果contrary_residual_mean_square_distance的形状为(0,) if contrary_residual_mean_square_distance.shape == (0,): + # 则表示没有计算过任何结果,返回np.inf return np.inf + # 最后,代码计算相反的RMSD,即contrary_residual_mean_square_distance的平方根 contrary_rms_surface_distance = (contrary_residual_mean_square_distance**2).mean() + # 并将其与原始的RMSD进行平均 rms_distance = np.sqrt(np.mean((rms_surface_distance, contrary_rms_surface_distance))) + # 返回计算得到的RMSD return rms_distance diff --git a/mindspore/python/mindspore/nn/metrics/topk.py b/mindspore/python/mindspore/nn/metrics/topk.py index 2913fef282b..47a92b999c0 100644 --- a/mindspore/python/mindspore/nn/metrics/topk.py +++ b/mindspore/python/mindspore/nn/metrics/topk.py @@ -12,12 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ +# Topk类,继承自Metric类。Topk类主要用于计算top-k准确率 """Topk.""" +# 导入所需的库和模块,包括numpy库和Metric类 import numpy as np +# rearrange_inputs函数,用于处理输入数据,使其符合Topk类的计算要求。 from .metric import Metric, rearrange_inputs, _check_onehot_data +# _check_onehot_data函数,用于检查输入数据是否为one-hot编码 class TopKCategoricalAccuracy(Metric): + # TopKCategoricalAccuracy类用于计算top-k分类准确率 + # 参数:k的整数变量,用于指定计算top-k分类准确率 """ Calculates the top-k categorical accuracy. @@ -46,24 +52,35 @@ class TopKCategoricalAccuracy(Metric): >>> print(output) 0.6666666666666666 """ + # 定义Topk类的初始化方法,用于初始化类的属性 def __init__(self, k): + # 调用父类的构造函数 super(TopKCategoricalAccuracy, self).__init__() + # 首先检查k的类型是否为整数 if not isinstance(k, int): + # 如果不是,则抛出一个TypeError异常 raise TypeError("For 'TopKCategoricalAccuracy', the type of " "the argument 'k' should be int, but got 'k' type: {}.".format(type(k))) + # 然后检查k的值是否大于等于1 if k < 1: + # 如果不是,则抛出一个ValueError异常 raise ValueError("For 'TopKCategoricalAccuracy', " "the argument 'k' must be at least 1, but got 'k' value: {}.".format(k)) + # 最后,将k的值赋给self.k self.k = k + # 并调用self.clear()方法清除之前的记录 self.clear() def clear(self): + # 用于清空内部评估结果 """Clear the internal evaluation result.""" + # 将self._correct_num和self._samples_num的值重置为0 self._correct_num = 0 self._samples_num = 0 @rearrange_inputs def update(self, *inputs): + # 定义一个名为update的方法,用于更新模型的预测结果和真实结果 """ Updates the internal evaluation result `y_pred` and `y`. @@ -78,36 +95,54 @@ class TopKCategoricalAccuracy(Metric): The method `update` must receive input of the form :math:`(y_{pred}, y)`. If some samples have the same accuracy, the first sample will be chosen. """ + # 首先,检查输入数据的数量是否为2 if len(inputs) != 2: + # 如果不是,则抛出一个ValueError异常 raise ValueError("For 'TopKCategoricalAccuracy.update', " "it needs 2 inputs (predicted value, true value), " "but got 'inputs' size: {}.".format(len(inputs))) + # 然后,将输入数据转换为适当的数据类型 y_pred = self._convert_data(inputs[0]) y = self._convert_data(inputs[1]) + # 并检查y预测值和y维度是否相同,输入数据是否为one-hot编码 if y_pred.ndim == y.ndim and _check_onehot_data(y): + # 如果满足这些条件,则将输入数据转换为argmax索引 y = y.argmax(axis=1) + # 首先,使用np.argsort函数对y_pred进行降序排序,并获取前self.k个元素的索引 indices = np.argsort(-y_pred, axis=1)[:, :self.k] + # 然后,将yreshape为(-1, 1),并使用repeat函数将每个元素重复self.k次 repeated_y = y.reshape(-1, 1).repeat(self.k, axis=1) + # 最后,使用np.equal函数比较indices和repeated_y,并计算每个元素是否相等。将所有相等元素的计数累加,得到每个样本的top-k分类准确率 correct = np.equal(indices, repeated_y).sum(axis=1) + # 将correct的值累加到self._correct_num中 self._correct_num += correct.sum() + # 将repeated_y.shape[0]累加到self._samples_num中 self._samples_num += repeated_y.shape[0] def eval(self): + # 定义一个名为eval的方法,用于计算top-k准确率,该函数需要在update函数后调用 """ Computes the top-k categorical accuracy. Returns: numpy.float64, computed result. """ + # 首先,检查self._samples_num是否为0 if self._samples_num == 0: + # 如果是,则抛出一个RuntimeError异常 raise RuntimeError("The 'TopKCategoricalAccuracy' " "can not be calculated, because the number of samples is 0, " "please check whether your inputs (predicted value, true value) are empty, " "or has called update method before calling eval method.") + # 如果self._samples_num不为0 return self._correct_num / self._samples_num + # 则计算self._correct_num除以self._samples_num,得到top-k分类准确率,并返回该结果 class Top1CategoricalAccuracy(TopKCategoricalAccuracy): + # 继承自TopKCategoricalAccuracy类。这个类用于计算top-1分类准确率。 + # Top1CategoricalAccuracy类重写了eval方法,使其返回top-1准确率,而不是top-k准确率。 + # 这可以通过将self._correct_num除以self._samples_num来实现 """ Calculates the top-1 categorical accuracy. This class is a specialized class for TopKCategoricalAccuracy. Refer to :class:`TopKCategoricalAccuracy` for more details. @@ -129,11 +164,15 @@ class Top1CategoricalAccuracy(TopKCategoricalAccuracy): >>> print(output) 0.0 """ + # 调用父类的__init__方法,并将k设置为1。这样,我们创建了一个只计算top-1准确率的TopKCategoricalAccuracy对象 def __init__(self): super(Top1CategoricalAccuracy, self).__init__(1) class Top5CategoricalAccuracy(TopKCategoricalAccuracy): + # 继承自TopKCategoricalAccuracy类。这个类用于计算top-5分类准确率 + # Top5CategoricalAccuracy类重写了eval方法,使其返回top-5准确率,而不是top-k准确率。 + # 这可以通过将self._correct_num除以self._samples_num来实现 """ Calculates the top-5 categorical accuracy. This class is a specialized class for TopKCategoricalAccuracy. Refer to :class:`TopKCategoricalAccuracy` for more details. @@ -155,5 +194,6 @@ class Top5CategoricalAccuracy(TopKCategoricalAccuracy): >>> print(output) 1.0 """ + # 调用父类的__init__方法,并将k设置为5。这样,我们创建了一个只计算top-5准确率的TopKCategoricalAccuracy对象 def __init__(self): super(Top5CategoricalAccuracy, self).__init__(5) diff --git a/mindspore/python/mindspore/nn/optim/__init__.py b/mindspore/python/mindspore/nn/optim/__init__.py index 2cec817cd1a..dd35c07f66a 100644 --- a/mindspore/python/mindspore/nn/optim/__init__.py +++ b/mindspore/python/mindspore/nn/optim/__init__.py @@ -12,14 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ +# 定义了名为Optimizer的Python模块 +# 提供了许多常用的优化器,如SGD(随机梯度下降)、ADAM(适应性矩估计)、Momentum(动量)、LARS(线性权重归一化)等。这些优化器用于训练过程中计算和更新梯度 """ Optimizer. Provide common optimizers for training, such as SGD, ADAM, Momentum. The optimizer is used to calculate and update the gradients. """ +# 从optimizer模块中导入Optimizer基类 from .optimizer import Optimizer +# 从momentum模块中导入Momentum类 from .momentum import Momentum +# 从adam模块中导入Adam、AdamWeightDecay和AdamOffload类 from .adam import Adam, AdamWeightDecay, AdamOffload from .lamb import Lamb from .sgd import SGD @@ -33,8 +38,10 @@ from .lazyadam import LazyAdam from .ada_grad import Adagrad from .thor import thor from .adafactor import AdaFactor +# 从adasum模块中导入AdaSumByDeltaWeightWrapCell和AdaSumByGradWrapCell类 from .adasum import AdaSumByDeltaWeightWrapCell, AdaSumByGradWrapCell +# 在导入optimizer模块时,就可以直接导入这个列表中的所有类,而无需单独导入每个类。这有助于减少代码的重复和提高代码的可读性 __all__ = ['Optimizer', 'Momentum', 'LARS', 'Adam', 'AdamWeightDecay', 'LazyAdam', 'AdamOffload', 'Lamb', 'SGD', 'ASGD', 'Rprop', 'FTRL', 'RMSProp', 'ProximalAdagrad', 'Adagrad', 'thor', 'AdaFactor', 'AdaSumByDeltaWeightWrapCell', 'AdaSumByGradWrapCell'] diff --git a/mindspore/python/mindspore/nn/optim/ada_grad.py b/mindspore/python/mindspore/nn/optim/ada_grad.py index e93dd06f5f9..082c4024d6a 100644 --- a/mindspore/python/mindspore/nn/optim/ada_grad.py +++ b/mindspore/python/mindspore/nn/optim/ada_grad.py @@ -1,4 +1,4 @@ -# Copyright 2020-2021 Huawei Technologies Co., Ltd +# Copyright 2020 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,125 +13,106 @@ # limitations under the License. # ============================================================================ """ADA_GRAD""" +# Adagrad算法实现的优化器 + +# 导入ops算子模块 from mindspore.ops import functional as F, composite as C, operations as P +# 导入检查模块 from mindspore._checkparam import Validator as validator -from .optimizer import Optimizer -from .optimizer import opt_init_args_register +# 导入optimizer模块 +from.optimizer import Optimizer _ada_grad_opt = C.MultitypeFuncGraph("ada_grad_opt") +# 为_ada_grad_opt添加一个名为_tensor_run_opt的函数,参数为opt,learning_rate,weight,accum,gradient @_ada_grad_opt.register("Function", "Tensor", "Tensor", "Tensor", "Tensor") def _tensor_run_opt(opt, learning_rate, weight, accum, gradient): """Apply ada_grad optimizer to the weight parameter.""" success = True + # 检查weight、accum、learning_rate、gradient是否有效 success = F.depend(success, opt(weight, accum, learning_rate, gradient)) + # 返回成功状态 return success def _check_param_value(accum, update_slots, prim_name=None): """Check inputs param.""" + # 检查accum的类型是否为float validator.check_value_type("accum", accum, [float], prim_name) + # 检查update_slots的类型是否为bool validator.check_value_type("update_slots", update_slots, [bool], prim_name) + # 检查accum的值是否大于0 validator.check_non_negative_float(accum, "accum", prim_name) class Adagrad(Optimizer): r""" - Implements the Adagrad algorithm. + Implements the Adagrad algorithm with ApplyAdagrad Operator. Adagrad is an online Learning and Stochastic Optimization. Refer to paper `Efficient Learning using Forward-Backward Splitting `_. - Adagrad can adaptively assign different learning rates to each parameter in response to the uneven number of - samples for different parameters. - The updating Pseudo codes are as follows, + The updating formulas are as follows, .. math:: - \begin{aligned} \\ - &\newline - &\hline \\ - &\textbf{Parameters}: \text{learning rate } \gamma, \: \text{ params } w_0, \: - \: \text{ weight decay } \lambda, \\ - &\hspace{12mm} \text{ initial accumulator value } state\_sum\\ - &\textbf{Init}: state\_sum_0 \leftarrow 0 \\[-1.ex] - &\newline - &\hline \\ - &\textbf{for} \: t=1 \: \textbf{to} \: \ldots \: \textbf{do} \\ - &\hspace{5mm}g_t \leftarrow \nabla_{w} f_t (w_{t-1}) \\ - &\hspace{5mm} \textbf{if} \: \lambda \neq 0 \\ - &\hspace{10mm} g_t \leftarrow g_t + \lambda w_{t-1} \\ - &\hspace{5mm}state\_sum_t \leftarrow state\_sum_{t-1} + g^2_t \\ - &\hspace{5mm}w_t \leftarrow w_{t-1}- \gamma*\frac{g_t}{\sqrt{state\_sum_t} + \epsilon} \\ - &\newline - &\hline \\ - &\bf{return} \: w_t \\[-1.ex] - &\newline - &\hline \\ - \end{aligned} + \begin{array}{ll} \\ + h_{t+1} = h_{t} + g\\ + w_{t+1} = w_{t} - lr*\frac{1}{\sqrt{h_{t+1}}}*g + \end{array} + + :math:`h` represents the cumulative sum of gradient squared, :math:`g` represents `gradients`. + :math:`lr` represents `learning_rate`, :math:`w` represents `params`. Note: - If parameters are not grouped, the `weight_decay` in optimizer will be applied on the network parameters without - 'beta' or 'gamma' in their names. Users can group parameters to change the strategy of decaying weight. When - parameters are grouped, each group can set `weight_decay`, if not, the `weight_decay` in optimizer will be - applied. + When separating parameter groups, the weight decay in each group will be applied on the parameters if the + weight decay is positive. When not separating parameter groups, the `weight_decay` in the API will be applied + on the parameters without 'beta' or 'gamma' in their names if `weight_decay` is positive. + + When separating parameter groups, if you want to centralize the gradient, set grad_centralization to True, + but the gradient centralization can only be applied to the parameters of the convolution layer. + If the parameters of the non convolution layer are set to True, an error will be reported. + + To improve parameter groups performance, the customized order of parameters can be supported. Args: - params (Union[list[Parameter], list[dict]]): Must be list of `Parameter` or list of `dict`. When the - `params` is a list of `dict`, the string "params", "lr", "weight_decay", "grad_centralization" and - "order_params" are the keys can be parsed. + params (Union[list[Parameter], list[dict]]): When the `params` is a list of `Parameter` which will be updated, + the element in `params` must be class `Parameter`. When the `params` is a list of `dict`, the "params", + "lr", "weight_decay" and "order_params" are the keys can be parsed. - - params: Required. Parameters in current group. The value must be a list of `Parameter`. + - params: Required. The value must be a list of `Parameter`. - lr: Optional. If "lr" in the keys, the value of corresponding learning rate will be used. - If not, the `learning_rate` in optimizer will be used. Fixed and dynamic learning rate are supported. + If not, the `learning_rate` in the API will be used. - weight_decay: Optional. If "weight_decay" in the keys, the value of corresponding weight decay - will be used. If not, the `weight_decay` in the optimizer will be used. It should be noted that weight - decay can be a constant value or a Cell. It is a Cell only when dynamic weight decay is applied. Dynamic - weight decay is similar to dynamic learning rate, users need to customize a weight decay schedule only - with global step as input, and during training, the optimizer calls the instance of WeightDecaySchedule - to get the weight decay value of current step. + will be used. If not, the `weight_decay` in the API will be used. - - grad_centralization: Optional. Must be Boolean. If "grad_centralization" is in the keys, the set value - will be used. If not, the `grad_centralization` is False by default. This configuration only works on the - convolution layer. + - order_params: Optional. If "order_params" in the keys, the value must be the order of parameters and + the order will be followed in optimizer. There are no other keys in the `dict` and the parameters which + in the value of 'order_params' must be in one of group parameters. - - order_params: Optional. When parameters is grouped, this usually is used to maintain the order of - parameters that appeared in the network to improve performance. The value should be parameters whose - order will be followed in optimizer. - If `order_params` in the keys, other keys will be ignored and the element of 'order_params' must be in - one group of `params`. + - grad_centralization: Optional. The data type of "grad_centralization" is Bool. If "grad_centralization" + is in the keys, the set value will be used. If not, the `grad_centralization` is False by default. + This parameter only works on the convolution layer. - accum (float): The starting value for `h`, must be zero or positive values. Default: 0.1. - learning_rate (Union[float, int, Tensor, Iterable, LearningRateSchedule]): Default: 0.001. - - - float: The fixed learning rate value. Must be equal to or greater than 0. - - - int: The fixed learning rate value. Must be equal to or greater than 0. It will be converted to float. - - - Tensor: Its value should be a scalar or a 1-D vector. For scalar, fixed learning rate will be applied. - For vector, learning rate is dynamic, then the i-th step will take the i-th value as the learning rate. - - - Iterable: Learning rate is dynamic. The i-th step will take the i-th value as the learning rate. - - - LearningRateSchedule: Learning rate is dynamic. During training, the optimizer calls the instance of - LearningRateSchedule with step as the input to get the learning rate of current step. - - update_slots (bool): Whether the `h` will be updated. Default: True. + accum (float): The starting value for accumulators, must be zero or positive values. Default: 0.1. + learning_rate (Union[float, Tensor, Iterable, LearningRateSchedule]): A value or a graph for the learning rate. + When the learning_rate is an Iterable or a Tensor in a 1D dimension, use dynamic learning rate, then + the i-th step will take the i-th value as the learning rate. When the learning_rate is LearningRateSchedule, + use dynamic learning rate, the i-th learning rate will be calculated during the process of training + according to the formula of LearningRateSchedule. When the learning_rate is a float or a Tensor in a zero + dimension, use fixed learning rate. Other cases are not supported. The float learning rate must be + equal to or greater than 0. If the type of `learning_rate` is int, it will be converted to float. + Default: 0.001. + update_slots (bool): If true, update accumulation. Default: True. loss_scale (float): Value for the loss scale. It must be greater than 0.0. In general, use the default value. Only when `FixedLossScaleManager` is used for training and the `drop_overflow_update` in `FixedLossScaleManager` is set to False, then this value needs to be the same as the `loss_scale` in `FixedLossScaleManager`. Refer to class :class:`mindspore.FixedLossScaleManager` for more details. Default: 1.0. - weight_decay (Union[float, int, Cell]): Weight decay (L2 penalty). Default: 0.0. - - - float: The fixed weight decay value. Must be equal to or greater than 0. - - - int: The fixed weight decay value. Must be equal to or greater than 0. It will be converted to float. - - - Cell: Weight decay is dynamic. During training, the optimizer calls the instance of - the Cell with step as the input to get the weight decay value of current step. + weight_decay (Union[float, int]): Weight decay value to multiply weight, must be zero or positive value. + Default: 0.0. Inputs: - **grads** (tuple[Tensor]) - The gradients of `params` in the optimizer, the shape is the same as the `params` @@ -153,8 +134,6 @@ class Adagrad(Optimizer): ``Ascend`` ``CPU`` ``GPU`` Examples: - >>> from mindspore import nn, Model - >>> >>> net = Net() >>> #1) All parameters use the same learning rate and weight decay >>> optim = nn.Adagrad(params=net.trainable_params()) @@ -176,25 +155,40 @@ class Adagrad(Optimizer): >>> model = Model(net, loss_fn=loss, optimizer=optim) """ - @opt_init_args_register def __init__(self, params, accum=0.1, learning_rate=0.001, update_slots=True, loss_scale=1.0, weight_decay=0.0): + # 初始化Adagrad类,参数分别为:参数params,梯度accum,学习率learning_rate,更新槽update_slots,损失缩放因子loss_scale,权重衰减weight_decay super(Adagrad, self).__init__(learning_rate, params, weight_decay, loss_scale) + # 检查参数值,设置参数accum _check_param_value(accum, update_slots, self.cls_name) + # 创建参数clone函数,用于拷贝参数 self.accum = self.parameters.clone(prefix="accum", init=accum) + # 创建HyperMap函数,用于计算梯度 + self.hyper_map = C.HyperMap() + # 创建ApplyAdagrad函数,用于计算梯度 + self.update_slots = update_slots self.opt = P.ApplyAdagrad(update_slots=update_slots) def construct(self, grads): + # 获取参数 params = self.parameters + # 获取梯度accum accum = self.accum + # 调用decay_weight函数,计算梯度 grads = self.decay_weight(grads) + # 调用gradients_centralization函数,计算梯度 grads = self.gradients_centralization(grads) + # 调用scale_grad函数,计算梯度 grads = self.scale_grad(grads) + # 获取学习率 lr = self.get_lr() + # 如果是分组学习率,则调用map_函数 if self.is_group_lr: - success = self.map_reverse(F.partial(_ada_grad_opt, self.opt), lr, params, accum, - grads) + success = self.map_(F.partial(_ada_grad_opt, self.opt), lr, params, accum, + grads) + # 否则调用map_函数 else: - success = self.map_reverse(F.partial(_ada_grad_opt, self.opt, lr), params, accum, - grads) - return success + success = self.map_(F.partial(_ada_grad_opt, self.opt, lr), params, accum, + grads) + # 返回更新成功标志 + return success \ No newline at end of file diff --git a/mindspore/python/mindspore/nn/optim/adafactor.py b/mindspore/python/mindspore/nn/optim/adafactor.py index 4342c518520..c59c06cc49f 100644 --- a/mindspore/python/mindspore/nn/optim/adafactor.py +++ b/mindspore/python/mindspore/nn/optim/adafactor.py @@ -13,6 +13,7 @@ # limitations under the License. # ============================================================================ """adafactor""" +# AdaFactor算法是减少显存占用的Adam优化器的一种变体 from mindspore import context from mindspore.common import dtype as mstype from mindspore.log import logging @@ -25,22 +26,29 @@ from mindspore.common.tensor import Tensor from mindspore._checkparam import Validator as validator from mindspore._checkparam import Rel from mindspore.nn.optim.optimizer import opt_init_args_register -from .optimizer import Optimizer +from.optimizer import Optimizer def _rms(update_tensor): """calculate rms""" + # 计算梯度的平方根 return F.sqrt(P.ReduceMean(False)(F.square(update_tensor))) def _approx_sq_grad(exp_avg_sq_row, exp_avg_sq_col): """Approximation of exponential moving average of square of gradient""" + # 计算梯度的平方根的平均值 reduce_mean = P.ReduceMean(keep_dims=True)(exp_avg_sq_row, -1) + # 计算梯度的平均值的平方根 div_val = 1.0 / P.Sqrt()(P.Div()(exp_avg_sq_row, reduce_mean)) + # 将平均值的平方根拉平 r_factor = (P.ExpandDims()(div_val, -1)) + # 计算梯度的平方根的平均值 exp_avg_sq_col = P.ExpandDims()(exp_avg_sq_col, -2) + # 计算梯度的平均值的平方根 c_factor = 1.0 / P.Sqrt()(exp_avg_sq_col) + # 返回拉平后的梯度 return P.Mul()(r_factor, c_factor) @@ -48,6 +56,7 @@ reduce_mean_keep_alive = P.ReduceMean().add_prim_attr("keep_alive", True) _adafactor_opt = C.MultitypeFuncGraph("adafactor_opt") +# 定义一个名为_run_opt_with_one_number的函数,用于接收任意类型的参数,并返回一个布尔值 @_adafactor_opt.register("Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Bool", "Bool", "Bool", "Bool", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor") def _run_opt_with_one_number(eps, clip_threshold, beta1, beta2t, weight_decay, scale_parameter, @@ -55,64 +64,103 @@ def _run_opt_with_one_number(eps, clip_threshold, beta1, beta2t, weight_decay, s grad, param, exp_avg, exp_avg_sq_row, exp_avg_sq_col, exp_avg_sq): """Apply ada factor optimizer to the weight parameter using Tensor.""" success = True + # 获取梯度的数据类型 grad_dtype = F.dtype(grad) + # 获取梯度的形状 grad_shape = F.shape(grad) + # 将参数转换为float32类型 if grad_dtype == mstype.float16: grad = F.cast(grad, mstype.float32) p_data_fp32 = param + # 如果参数的数据类型为float16,则将参数转换为float32类型 if F.dtype(p_data_fp32) == mstype.float16: p_data_fp32 = F.cast(p_data_fp32, mstype.float32) + # 判断是否需要梯度矩阵乘法 factored = len(grad_shape) >= 2 + # 如果需要,则求平方梯度和,并将结果赋值给update if scale_parameter: + # 求平方梯度和 rms = _rms(p_data_fp32) + # 将eps[1]和rms转换为float32类型 param_scale = P.Maximum()(eps[1], rms) + # 将learning_rate乘以param_scale和rms转换为float32类型 learning_rate_update = learning_rate * param_scale * F.ones_like(rms) else: + # 否则,learning_rate乘以1 learning_rate_update = learning_rate + # 定义梯度更新函数 update = (grad ** 2) + eps[0] - if factored: + # 将exp_avg_sq_row_update(行矩阵)转换为grad_dtype exp_avg_sq_row_update = F.cast(exp_avg_sq_row, grad_dtype) + # 将exp_avg_sq_row_update乘以beta2t exp_avg_sq_row_update = P.Mul()(exp_avg_sq_row_update, beta2t) + # 更新mean update_mean = reduce_mean_keep_alive(update, -1) * (1.0 - beta2t) + # 将exp_avg_sq_row_update更新 exp_avg_sq_row_update = P.Add()(exp_avg_sq_row_update, update_mean) + # 将exp_avg_sq_row_update赋值给exp_avg_sq_row exp_avg_sq_row_update = F.assign(exp_avg_sq_row, F.cast(exp_avg_sq_row_update, F.dtype(exp_avg_sq_row))) + # 将exp_avg_sq_col_update(列矩阵)转换为grad_dtype exp_avg_sq_col_update = F.cast(exp_avg_sq_col, grad_dtype) + # 将exp_avg_sq_col_update乘以beta2t exp_avg_sq_col_update = P.Mul()(exp_avg_sq_col_update, beta2t) + # 更新mean update_mean = reduce_mean_keep_alive(update, -2) * (1.0 - beta2t) + # 将exp_avg_sq_col_update更新 exp_avg_sq_col_update = P.Add()(exp_avg_sq_col_update, update_mean) + # 将exp_avg_sq_col_update赋值给exp_avg_sq_col exp_avg_sq_col_update = F.assign(exp_avg_sq_col, F.cast(exp_avg_sq_col_update, F.dtype(exp_avg_sq_col))) + # 更新update update = _approx_sq_grad(exp_avg_sq_row_update, exp_avg_sq_col_update) + # 将update乘以grad update = P.Mul()(update, grad) else: + # 将exp_avg_sq_update(一般矩阵)转换为grad_dtype exp_avg_sq_update = F.cast(exp_avg_sq, grad_dtype) + # 更新update update = update * (1.0 - beta2t) + # 将exp_avg_sq_update更新 exp_avg_sq_update = P.Add()(P.Mul()(exp_avg_sq_update, beta2t), update) + # 将exp_avg_sq_update赋值给exp_avg_sq exp_avg_sq_update = F.assign(exp_avg_sq, F.cast(exp_avg_sq_update, F.dtype(exp_avg_sq))) + # 将exp_avg_sq_update除以1/Sqrt() exp_avg_sq_update = 1.0 / P.Sqrt()(exp_avg_sq_update) + # 将exp_avg_sq_update乘以grad update = P.Mul()(exp_avg_sq_update, grad) + # 计算更新的RMS阈值 update_rms_thres = _rms(update) / clip_threshold + # 计算更新的折叠因子 update_coff = P.Maximum()(update_rms_thres, P.OnesLike()(update_rms_thres)) + # 更新参数 update = P.Mul()(P.Div()(update, update_coff), learning_rate_update) + # 如果使用第一个梯度的平均更新 if use_first_moment: + # 计算更新的平均更新 exp_avg_update = exp_avg + # 如果压缩,将更新的数据类型转换为更新的数据类型 if compression: exp_avg_update = F.cast(exp_avg, grad_dtype) + # 计算更新的平均更新 exp_avg_update = P.Add()(P.Mul()(exp_avg_update, beta1), update * (1 - beta1)) + # 将更新的平均更新赋值给更新的参数 update = F.assign(exp_avg, F.cast(exp_avg_update, F.dtype(exp_avg))) + # 如果使用权重衰减 if weight_decay_flag: + # 计算更新的参数 p_data_fp32_coff = p_data_fp32 * -weight_decay * learning_rate_update p_data_fp32 = P.Add()(p_data_fp32, p_data_fp32_coff) - p_data_fp32 = P.Sub()(p_data_fp32, update) + p_data_fp32 = P.Sub()(p_data_fp32, update) + # 返回更新的参数 return F.depend(success, P.Assign()(param, F.cast(p_data_fp32, F.dtype(param)))) @@ -120,6 +168,23 @@ def _run_opt_with_one_number(eps, clip_threshold, beta1, beta2t, weight_decay, s "Tensor", "Tensor", "Tensor", "Tensor") def _run_fused_ada_factor(fused_ada_factor, eps, clip_threshold, beta1, beta2t, weight_decay, learning_rate, grad, param, exp_avg, exp_avg_sq_row, exp_avg_sq_col, exp_avg_sq): + ''' + 计算fused_ada_factor梯度 + :param fused_ada_factor: fused_ada_factor函数 + :param eps: epsilon + :param clip_threshold: 当前梯度的阈值 + :param beta1: beta1 + :param beta2t: beta2t + :param weight_decay: weight_decay + :param learning_rate: 学习率 + :param grad: 梯度 + :param param: 参数 + :param exp_avg: 平均值 + :param exp_avg_sq_row: 平均值的列矩阵 + :param exp_avg_sq_col: 平均值的行矩阵 + :param exp_avg_sq: 平均值的平方 + :return: 梯度 + ''' success = True ret = fused_ada_factor(eps, clip_threshold, beta1, beta2t, weight_decay, learning_rate, grad, param, exp_avg, exp_avg_sq_row, exp_avg_sq_col, exp_avg_sq) @@ -127,19 +192,24 @@ def _run_fused_ada_factor(fused_ada_factor, eps, clip_threshold, beta1, beta2t, def trans_to_tensor(param, is_tuple=False, fp32=True): + # 将参数转化为张量 """ Transform params to tensor. """ if param is None or isinstance(param, bool): + # 如果参数为None或者参数为布尔值,则返回参数 return param + # 如果参数为元组,则将元组转换为Tensor data_type = mstype.float32 if fp32 else mstype.float16 if is_tuple: new_param = [Tensor(ele, data_type) for ele in param] return tuple(new_param) + # 否则,将参数转换为Tensor return Tensor(param, data_type) class AdaFactor(Optimizer): + # AdaFactor算法是减少显存占用的Adam优化器的一种变体 r""" Updates gradients by the Adaptive Learning Rates with Sublinear Memory Cost (Adafactor) algorithm. @@ -266,56 +336,95 @@ class AdaFactor(Optimizer): >>> loss = nn.SoftmaxCrossEntropyWithLogits() >>> model = Model(net, loss_fn=loss, optimizer=optim) """ + # 支持并行模式下的优化器 _support_parallel_optimizer = True @opt_init_args_register def __init__(self, - params, - learning_rate=None, - eps=(1e-30, 1e-3), - clip_threshold=1.0, - decay_rate=0.8, - beta1=0.9, - weight_decay=0.0, - scale_parameter=True, - relative_step=True, - warmup_init=False, - compression=False, - loss_scale=1.0): + params, + learning_rate=None, + eps=(1e-30, 1e-3), + clip_threshold=1.0, + decay_rate=0.8, + beta1=0.9, + weight_decay=0.0, + scale_parameter=True, + relative_step=True, + warmup_init=False, + compression=False, + loss_scale=1.0): + ''' + 初始化模型参数 + :param params: 参数列表 + :param learning_rate: 学习率 + :param eps: 极小值 + :param clip_threshold: 允许的最大梯度 + :param decay_rate: 动量因子 + :param beta1: Beta1 + :param weight_decay: 权重衰减 + :param scale_parameter: 标量参数 + :param relative_step: 是否使用相对步长 + :param warmup_init: 是否使用热身初始化 + :param compression: 是否使用压缩 + :param loss_scale: 使用损失缩放 + :return: + ''' if learning_rate is not None and relative_step: + # 如果learning_rate和relative_step都存在,则抛出异常 raise ValueError("Cannot combine manual lr and relative_step options", learning_rate) if warmup_init and not relative_step: + # 如果warmup_init存在,但不是relative_step,则抛出异常 raise ValueError("warmup_init requires relative_step=True") + # 如果learning_rate为None,且不是relative_step,则抛出异常 if learning_rate is None and not relative_step: raise ValueError("Cannot learning_rate is None and relative_step=False") + # 如果learning_rate为None,则learning_rate设置为0.0 if learning_rate is None: learning_rate = 0.0 if beta1 is None: + # 如果beta1为空,则将beta1设置为0.0 beta1 = 0.0 + # 如果learning_rate不是数字,则将scale_lr设置为False self.scale_lr = True if not isinstance(learning_rate, (float, int)) and learning_rate is not None: + # 如果learning_rate不是数字,则将scale_lr设置为False self.scale_lr = False + # 如果learning_rate支持update learning rate,则报错 if relative_step or scale_parameter: logging.warning("When learning_rate is learning scheduler, it not support update learning rate!") - + # 调用父类构造函数 super(AdaFactor, self).__init__(learning_rate, params, weight_decay, loss_scale) + # 检查eps的类型 validator.check_value_type("eps", eps, [list, tuple], self.cls_name) - if len(eps) != 2: + # 检查eps的长度 + if len(eps)!= 2: raise ValueError("eps must have 2 value: (eps1, eps2).") + # 检查eps1和eps2的类型 for i, ele in enumerate(eps): validator.check_value_type("eps{}".format(i), ele, [float], self.cls_name) validator.check_non_negative_float(ele, "eps{}".format(i), self.cls_name) + # 检查clip_threshold的类型 validator.check_value_type("clip_threshold", clip_threshold, [float], self.cls_name) + # 检查clip_threshold的范围 validator.check_non_negative_float(clip_threshold, "clip_threshold", self.cls_name) + # 检查decay_rate的类型 validator.check_value_type("decay_rate", decay_rate, [float], self.cls_name) + # 检查decay_rate的范围 validator.check_float_range(decay_rate, 0, 1, Rel.INC_NEITHER, "decay_rate", self.cls_name) + # 检查weight_decay的类型 validator.check_float_range(weight_decay, 0, 1, Rel.INC_LEFT, "weight_decay", self.cls_name) + # 检查scale_parameter的类型 validator.check_value_type("scale_parameter", scale_parameter, [bool], self.cls_name) + # 检查relative_step的类型 validator.check_value_type("relative_step", relative_step, [bool], self.cls_name) + # 检查compression的类型 validator.check_value_type("compression", compression, [bool], self.cls_name) + # 检查beta1的类型 validator.check_value_type("beta1", beta1, [int, float], self.cls_name) + # 检查beta1的范围 validator.check_non_negative_float(float(beta1), "beta1", self.cls_name) + # 定义各个属性 self.eps = trans_to_tensor(eps) self.clip_threshold = trans_to_tensor(clip_threshold) self.decay_rate = trans_to_tensor(-decay_rate) @@ -323,68 +432,95 @@ class AdaFactor(Optimizer): self.weight_decay = trans_to_tensor(weight_decay) self.weight_decay_flag = bool(weight_decay) + # 将参数转换为Tensor类型 self.step = Parameter(Tensor(0, dtype=mstype.float32), name="train_step") + # 将scale_parameter设置为False self.scale_parameter = scale_parameter + # 将relative_step设置为False self.relative_step = relative_step + # 将warmup_init设置为False self.warmup_init = warmup_init + # 将compression设置为False self.compression = compression + # 如果scale_lr为False,将scale_parameter设置为False if not self.scale_lr: self.scale_parameter = False + # 将init_ada_factor_state设置为beta1 self.init_ada_factor_state(beta1) + # 将step设置为0 self.step = Parameter(initializer(0, [1], mstype.float32), name='afactor_step') + # 将fused_ada_factor设置为FusedAdaFactor self.fused_ada_factor = P.FusedAdaFactor(enable_scale_parameter=self.scale_parameter, enable_first_moment=self.use_first_moment, enable_weight_decay=self.weight_decay_flag) + # 如果当前设备目标为CPU,将use_fused_ada_factor设置为True if context.get_context("device_target") == "CPU": self.use_fused_ada_factor = True else: self.use_fused_ada_factor = False + # 打印AdaFactor初始化完成 print("AdaFactor init completed", self.learning_rate) def init_ada_factor_state(self, beta1): """init adafactor variables""" if beta1 > 0: + # 如果beta1大于0,则使用第一个梯度下降 self.use_first_moment = True + # 初始化exp_avg参数为克隆的参数 self.exp_avg = self.parameters.clone(prefix="exp_avg", init='zeros') else: + # 如果beta1小于等于0,则使用第一个梯度下降 self.use_first_moment = False + # 初始化exp_avg参数为多个参数合成的参数组 self.exp_avg = ParameterTuple([Parameter(Tensor(0.0))] * len(self.parameters)) + # 初始化矩阵,列矩阵,行矩阵,用于后续计算 self.exp_avg_sq = [] self.exp_avg_sq_col = [] self.exp_avg_sq_row = [] for param in self.parameters: + # 获取参数的数据类型 param_dtype = param.dtype + # 获取参数的形状 param_shape = param.shape + # 获取参数的名称 param_name = param.name + # 如果参数形状大于1 if len(param_shape) > 1: + # 将指定行矩阵和指定列矩阵添加到self.exp_avg_sq_row和self.exp_avg_sq_col中 self.exp_avg_sq_row.append(Parameter(initializer(0, shape=param_shape[:-1], dtype=param_dtype), name="exp_avg_sq_row_{}".format(param_name))) self.exp_avg_sq_col.append(Parameter(initializer(0, shape=param_shape[:-2] + param_shape[-1:], dtype=param_dtype), name="exp_avg_sq_col_{}".format(param_name))) + # 将矩阵添加到self.exp_avg_sq中 self.exp_avg_sq.append(Parameter(initializer(0, shape=(1,), dtype=param_dtype), name="exp_avg_sq_{}".format(param_name))) + # 如果参数形状小于1 else: + # 将形状相反的指定行矩阵和列矩阵添加到self.exp_avg_sq_row和self.exp_avg_sq_col中 self.exp_avg_sq_row.append(Parameter(initializer(0, shape=(1,), dtype=param_dtype), name="exp_avg_sq_row_{}".format(param_name))) self.exp_avg_sq_col.append(Parameter(initializer(0, shape=(1,), dtype=param_dtype), name="exp_avg_sq_col_{}".format(param_name))) if self.compression: + # 如果compression为True,则将指定参数为mstype16的矩阵添加到self.exp_avg_sq中 self.exp_avg_sq.append(Parameter(initializer(0, shape=param_shape, dtype=mstype.float16), name="exp_avg_sq_{}".format(param_name))) else: + # 否则,将指定参数为param_dtype的矩阵添加到self.exp_avg_sq中 self.exp_avg_sq.append(Parameter(initializer(0, shape=param_shape, dtype=param_dtype), name="exp_avg_sq_{}".format(param_name))) - + # 整理各类矩阵 self.exp_avg_sq_row = ParameterTuple(self.exp_avg_sq_row) self.exp_avg_sq_col = ParameterTuple(self.exp_avg_sq_col) self.exp_avg_sq = ParameterTuple(self.exp_avg_sq) @property def supports_memory_efficient_fp16(self): + # 对fp16型提供内存便利 """ Support memory efficient for fp16 """ @@ -392,34 +528,46 @@ class AdaFactor(Optimizer): @property def supports_flat_params(self): + # 支持扁平化参数 """ Support flatten params """ return False def construct(self, gradients): + ''' + 构建优化器 + :param gradients: 梯度 + :return: + ''' lr = self.get_lr() + # 计算步长 step = F.assign_add(self.step, 1) + # 如果scale_lr为True,且relative_step为True,则lr = min(min_step, 1/sqrt(step)) if self.scale_lr and self.relative_step: if self.warmup_init: + # 如果warmup_init为True,则min_step为1e-6 * step min_step = 1e-6 * step else: + # 否则,min_step为1e-2 min_step = 1e-2 lr = P.Minimum()(min_step, 1.0 / P.Sqrt()(step * 1.0)) + # beta2t = 1.0 - pow(step, decay_rate) beta2t = 1.0 - P.Pow()(step, self.decay_rate) + # 如果use_fused_ada_factor为True,则success = _adafactor_opt(fused_ada_factor, eps, clip_threshold, beta1, beta2t, weight_decay, lr) if self.use_fused_ada_factor: success = self.hyper_map(F.partial(_adafactor_opt, self.fused_ada_factor, self.eps, self.clip_threshold, - self.beta1, beta2t, self.weight_decay, lr), - gradients, self.parameters, self.exp_avg, self.exp_avg_sq_row, - self.exp_avg_sq_col, self.exp_avg_sq) + self.beta1, beta2t, self.weight_decay, lr), + gradients, self.parameters, self.exp_avg, self.exp_avg_sq_row, + self.exp_avg_sq_col, self.exp_avg_sq) + # 否则success = _adafactor_opt(eps, clip_threshold, beta1, beta2t, weight_decay, scale_parameter, compression, use_first_moment, weight_decay_flag, lr) else: success = self.hyper_map(F.partial(_adafactor_opt, self.eps, self.clip_threshold, self.beta1, beta2t, - self.weight_decay, self.scale_parameter, self.compression, - self.use_first_moment, self.weight_decay_flag, lr), - gradients, self.parameters, self.exp_avg, self.exp_avg_sq_row, - self.exp_avg_sq_col, self.exp_avg_sq) - + self.weight_decay, self.scale_parameter, self.compression, + self.use_first_moment, self.weight_decay_flag, lr), + gradients, self.parameters, self.exp_avg, self.exp_avg_sq_row, + self.exp_avg_sq_col, self.exp_avg_sq) return success @Optimizer.target.setter @@ -428,7 +576,9 @@ class AdaFactor(Optimizer): If the input value is set to "CPU", the parameters will be updated on the host using the Fused optimizer operation. """ + # 设置基target self._set_base_target(value) + # 如果输入值为CPU,则更新host参数,将use_fused_ada_factor设置为True if value == 'CPU': self.fused_ada_factor.add_prim_attr("primitive_target", "CPU") self.use_fused_ada_factor = True diff --git a/mindspore/python/mindspore/nn/optim/adam.py b/mindspore/python/mindspore/nn/optim/adam.py index 0f4dbf169af..7a3a01ed074 100755 --- a/mindspore/python/mindspore/nn/optim/adam.py +++ b/mindspore/python/mindspore/nn/optim/adam.py @@ -1,4 +1,4 @@ -# Copyright 2020-2021 Huawei Technologies Co., Ltd +# Copyright 2020 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,6 +13,8 @@ # limitations under the License. # ============================================================================ """adam""" +# 优化器 Adaptive Moment Estimation (Adam)算法的实现。 + import numpy as np from mindspore.common import dtype as mstype @@ -24,17 +26,21 @@ from mindspore.common.parameter import Parameter from mindspore.common.tensor import Tensor from mindspore._checkparam import Validator as validator from mindspore._checkparam import Rel -from .optimizer import Optimizer -from .optimizer import opt_init_args_register +from.optimizer import Optimizer +# 定义adam求解器 +# 定义求解器的名称 _adam_opt = C.MultitypeFuncGraph("adam_opt") +# 定义求解器的操作 _scaler_one = Tensor(1, mstype.int32) _scaler_ten = Tensor(10, mstype.float32) -@_adam_opt.register("Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", +# 定义求解器的注册值 +@_adam_opt.register("Tensor", "Tensor", "Tensor", "Tensor", "Number", "Tensor", "Tensor", "Tensor", "Tensor", "Bool", "Bool") def _update_run_op(beta1, beta2, eps, lr, weight_decay, param, m, v, gradient, decay_flag, optim_filter): + # 用于更新参数 """ Update parameters. @@ -43,7 +49,7 @@ def _update_run_op(beta1, beta2, eps, lr, weight_decay, param, m, v, gradient, d beta2 (Tensor): The exponential decay rate for the 2nd moment estimations. Should be in range (0.0, 1.0). eps (Tensor): Term added to the denominator to improve numerical stability. Should be greater than 0. lr (Tensor): Learning rate. - weight_decay (numbers.Number): Weight decay. Should be equal to or greater than 0. + weight_decay (Number): Weight decay. Should be equal to or greater than 0. param (Tensor): Parameters. m (Tensor): m value of parameters. v (Tensor): v value of parameters. @@ -54,96 +60,150 @@ def _update_run_op(beta1, beta2, eps, lr, weight_decay, param, m, v, gradient, d Returns: Tensor, the new value of v after updating. """ - op_cast = P.Cast() if optim_filter: + # 定义乘法运算 op_mul = P.Mul() + # 定义平方运算 op_square = P.Square() + # 定义平方根运算 op_sqrt = P.Sqrt() + # 定义转换运算 op_cast = P.Cast() + # 定义reshape运算 op_reshape = P.Reshape() + # 定义shape运算 op_shape = P.Shape() + + # 将参数转换为float32类型 param_fp32 = op_cast(param, mstype.float32) + # 将模转换为float32类型 m_fp32 = op_cast(m, mstype.float32) + # 将方差转换为float32类型 v_fp32 = op_cast(v, mstype.float32) + # 将梯度转换为float32类型 gradient_fp32 = op_cast(gradient, mstype.float32) + # 将beta1的类型设置为float32 next_m = op_mul(beta1, m_fp32) + op_mul(op_cast(F.tuple_to_array((1.0,)), mstype.float32) - - beta1, gradient_fp32) + - beta1, gradient_fp32) + # 将beta2的类型设置为float32 next_v = op_mul(beta2, v_fp32) + op_mul(op_cast(F.tuple_to_array((1.0,)), mstype.float32) - - beta2, op_square(gradient_fp32)) + - beta2, op_square(gradient_fp32)) + # 将梯度求平均 update = next_m / (eps + op_sqrt(next_v)) + # 如果没有指定weight_decay,则设置为0 if decay_flag: update = op_mul(weight_decay, param_fp32) + update + # 将梯度求平均,并将梯度转换为float32类型 update_with_lr = op_mul(lr, update) + # 将参数更新 next_param = param_fp32 - op_reshape(update_with_lr, op_shape(param_fp32)) + # 将参数更新,并将参数转换为float32类型 next_param = F.depend(next_param, F.assign(param, op_cast(next_param, F.dtype(param)))) + # 将模更新,并将模转换为float32类型 next_param = F.depend(next_param, F.assign(m, op_cast(next_m, F.dtype(m)))) + # 将方差更新,并将方差转换为float32类型 next_param = F.depend(next_param, F.assign(v, op_cast(next_v, F.dtype(v)))) + # 返回参数 return op_cast(next_param, F.dtype(param)) - return op_cast(gradient, F.dtype(param)) + return gradient + +# 定义一个名为_adam_opt的函数,接收参数:Function,Function,Function,Bool,Bool,Bool,Tensor,Tensor,Tensor,Tensor,Tensor,RowTensor,Tensor,Tensor,Tensor,Tensor,Bool,Bool @_adam_opt.register("Function", "Function", "Function", "Function", "Bool", "Bool", "Bool", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "RowTensor", "Tensor", "Tensor", "Tensor", "Bool", "Bool") def _run_opt_with_sparse(opt, sparse_opt, push, pull, use_locking, use_nesterov, target, beta1_power, beta2_power, beta1, beta2, eps, lr, gradient, param, m, v, ps_parameter, cache_enable): + # 启动适配稀疏矩阵梯度的adam优化器 """Apply sparse adam optimizer to the weight parameter when the gradient is sparse.""" + # 定义一个布尔值,用来表示是否成功执行opt success = True + # 获取梯度的索引 indices = gradient.indices + # 获取梯度的值 values = gradient.values if ps_parameter and not cache_enable: + # 如果ps_parameter为True,且cache_enable为False,则使用P.Shape()函数计算op_shape op_shape = P.Shape() + # 将op_shape(param), op_shape(m), op_shape(v), + # op_shape(beta1_power), op_shape(beta2_power), op_shape(lr), op_shape(beta1), + # op_shape(beta2), op_shape(eps), op_shape(values), op_shape(indices)计算出来 shapes = (op_shape(param), op_shape(m), op_shape(v), - op_shape(beta1_power), op_shape(beta2_power), op_shape(lr), op_shape(beta1), - op_shape(beta2), op_shape(eps), op_shape(values), op_shape(indices)) + op_shape(beta1_power), op_shape(beta2_power), op_shape(lr), op_shape(beta1), + op_shape(beta2), op_shape(eps), op_shape(values), op_shape(indices)) + # 将success的值与pull函数的值进行比较,如果比较结果为True,则使用push函数推送 success = F.depend(success, pull(push((beta1_power, beta2_power, lr, beta1, beta2, - eps, values, indices), shapes), param)) + eps, values, indices), shapes), param)) + # 返回success的值 return success + # 如果指定了target,则使用sparse_opt if not target: success = F.depend(success, sparse_opt(param, m, v, beta1_power, beta2_power, lr, beta1, beta2, eps, values, indices)) else: + # 创建一个乘积运算器 op_mul = P.Mul() + # 创建平方运算器 op_square = P.Square() + # 创建平方根运算器 op_sqrt = P.Sqrt() + # 创建ScatterAdd运算器 scatter_add = P.ScatterAdd(use_locking) + # 更新参数 success = F.depend(success, F.assign(m, op_mul(beta1, m))) success = F.depend(success, F.assign(v, op_mul(beta2, v))) + # 获取梯度索引 grad_indices = gradient.indices + # 获取梯度值 grad_value = gradient.values + # 更新下一个梯度 next_m = scatter_add(m, grad_indices, op_mul(F.tuple_to_array((1.0,)) - beta1, grad_value)) + # 更新下一个方差 next_v = scatter_add(v, grad_indices, op_mul(F.tuple_to_array((1.0,)) - beta2, op_square(grad_value))) + # 如果使用Nesterov梯度,则更新下一个梯度 if use_nesterov: + # 将下一个梯度乘以beta1 m_temp = next_m * _scaler_ten + # 更新m F.assign(m, op_mul(beta1, next_m)) + # 将梯度累加到m上 div_value = scatter_add(m, op_mul(grad_indices, _scaler_one), op_mul(F.tuple_to_array((1.0,)) - beta1, grad_value)) + # 更新param_update param_update = div_value / (op_sqrt(next_v) + eps) + # 更新m F.assign(m, m_temp / _scaler_ten) else: + # 更新param_update param_update = next_m / (op_sqrt(next_v) + eps) + # 更新学习率 lr_t = lr * op_sqrt(1 - beta2_power) / (1 - beta1_power) + # 更新下一个参数 next_param = param - lr_t * param_update + # 更新参数 success = F.depend(success, F.assign(param, next_param)) + # 更新梯度 success = F.depend(success, F.assign(m, next_m)) + # 更新方差 success = F.depend(success, F.assign(v, next_v)) return success @@ -154,15 +214,22 @@ def _run_opt_with_sparse(opt, sparse_opt, push, pull, use_locking, use_nesterov, def _run_opt_with_one_number(opt, sparse_opt, push, pull, use_locking, use_nesterov, target, beta1_power, beta2_power, beta1, beta2, eps, lr, gradient, param, moment1, moment2, ps_parameter, cache_enable): + # 启动适配矩阵梯度的adam优化器 """Apply adam optimizer to the weight parameter using Tensor.""" + # 定义一个布尔值,用来表示是否成功执行opt success = True + # 如果ps_parameter为True,且cache_enable为False,则执行pull,否则执行opt if ps_parameter and not cache_enable: + # 获取op_shape op_shape = P.Shape() + # 执行pull success = F.depend(success, pull(push((beta1_power, beta2_power, lr, beta1, beta2, eps, gradient), (op_shape(param), op_shape(moment1), op_shape(moment2))), param)) else: + # 执行opt success = F.depend(success, opt(param, moment1, moment2, beta1_power, beta2_power, lr, beta1, beta2, eps, gradient)) + # 返回success return success @@ -170,144 +237,116 @@ def _run_opt_with_one_number(opt, sparse_opt, push, pull, use_locking, use_neste "Tensor", "Tensor") def _run_off_load_opt(opt, beta1_power, beta2_power, beta1, beta2, eps, lr, gradient, param, moment1, moment2): """Apply AdamOffload optimizer to the weight parameter using Tensor.""" + # 启动适配矩阵梯度的adamoffload优化器 + # 初始化一个布尔值,用来表示是否执行完成 success = True + # 将AdamOffload优化器的参数更新到参数中 delat_param = opt(moment1, moment2, beta1_power, beta2_power, lr, beta1, beta2, eps, gradient) + # 将参数和参数之间的差值更新到参数中 success = F.depend(success, F.assign_add(param, delat_param)) + # 返回是否执行完成的布尔值 return success def _check_param_value(beta1, beta2, eps, prim_name): """Check the type of inputs.""" + """ + 检查输入参数 + """ + # 检查beta1的类型是否为float validator.check_value_type("beta1", beta1, [float], prim_name) + # 检查beta2的类型是否为float validator.check_value_type("beta2", beta2, [float], prim_name) + # 检查eps的类型是否为float validator.check_value_type("eps", eps, [float], prim_name) + # 检查beta1的取值范围是否在0.0和1.0之间 validator.check_float_range(beta1, 0.0, 1.0, Rel.INC_NEITHER, "beta1", prim_name) + # 检查beta2的取值范围是否在0.0和1.0之间 validator.check_float_range(beta2, 0.0, 1.0, Rel.INC_NEITHER, "beta2", prim_name) + # 检查eps的取值是否为正数 validator.check_positive_float(eps, "eps", prim_name) class Adam(Optimizer): + # Adam优化器 r""" - Implements the Adaptive Moment Estimation (Adam) algorithm. + Updates gradients by the Adaptive Moment Estimation (Adam) algorithm. - The Adam optimizer can dynamically adjust the learning rate of each parameter using the first-order - moment estimation and the second-order moment estimation of the gradient. The Adam algorithm is proposed in `Adam: A Method for Stochastic Optimization `_. The updating formulas are as follows, .. math:: - \begin{array}{l} - &\newline - &\hline \\ - &\textbf{Parameters}: \: 1^{\text {st }}\text {moment vector} \: m , \: 2^{\text {nd}} \: - \text{moment vector} \: v , \\ - &\:\text{gradients } g, \: \text{learning rate} \: \gamma, \text - { exponential decay rates for the moment estimates} \: \beta_{1} \: \beta_{2} , \\ - &\:\text {parameter vector} \: w_{0}, \:\text{timestep} \: t , \text{ weight decay } \lambda \\ - &\textbf{Init}: m_{0} \leftarrow 0, \: v_{0} \leftarrow 0, \: t \leftarrow 0, \: - \text{init parameter vector} \: w_{0} \\[-1.ex] - &\newline - &\hline \\ - &\textbf{while} \: w_{t} \: \text{not converged} \: \textbf{do} \\ - &\hspace{5mm}\boldsymbol{g}_{t} \leftarrow \nabla_{w} \boldsymbol{f}_{t}\left(\boldsymbol{w}_{t-1}\right) \\ - &\hspace{5mm}\textbf {if } \lambda \neq 0 \\ - &\hspace{10mm}\boldsymbol{g}_{t} \leftarrow \boldsymbol{g}_{t}+\lambda \boldsymbol{w}_{t-1} \\ - &\hspace{5mm}\boldsymbol{m}_{t} \leftarrow \beta_{1} \boldsymbol{m}_{t-1}+\left(1-\beta_{1}\right) - \boldsymbol{g}_{t} \\ - &\hspace{5mm}\boldsymbol{v}_{t} \leftarrow \beta_{2} \boldsymbol{v}_{t-1}+\left(1-\beta_{2}\right) - \boldsymbol{g}_{t}^{2} \\ - &\hspace{5mm}\hat{\boldsymbol{m}}_{t} \leftarrow \boldsymbol{m}_{t} /\left(1-\beta_{1}^{t}\right) \\ - &\hspace{5mm}\hat{\boldsymbol{v}}_{t} \leftarrow \boldsymbol{v}_{t} /\left(1-\beta_{2}^{t}\right) \\ - &\hspace{5mm}\boldsymbol{w}_{t} \leftarrow \boldsymbol{w}_{t-1}-\gamma \hat{\boldsymbol{m}}_{t} - /(\sqrt{\hat{\boldsymbol{v}}_{t}}+\epsilon) \\ - &\textbf{end while} \\[-1.ex] - &\newline - &\hline \\[-1.ex] - &\textbf{return} \: \boldsymbol{w}_{t} \\[-1.ex] - &\newline - &\hline \\[-1.ex] + \begin{array}{ll} \\ + m = \beta_1 * m + (1 - \beta_1) * g \\ + v = \beta_2 * v + (1 - \beta_2) * g * g \\ + l = \alpha * \frac{\sqrt{1-\beta_2^t}}{1-\beta_1^t} \\ + w = w - l * \frac{m}{\sqrt{v} + \epsilon} \end{array} - :math:`m` represents the 1st moment vector, :math:`v` represents the 2nd moment vector, - :math:`g` represents `gradients`, :math:`\beta_1, \beta_2` represent `beta1` and `beta2`, - :math:`t` represents the current step while :math:`beta_1^t` and :math:`beta_2^t` represent - `beta1_power` and `beta2_power`, :math:`\gamma` represents `learning_rate`, :math:`w` represents `params`, + :math:`m` represents the 1st moment vector `moment1`, :math:`v` represents the 2nd moment vector `moment2`, + :math:`g` represents `gradients`, :math:`l` represents scaling factor `lr`, :math:`\beta_1, \beta_2` represent + `beta1` and `beta2`, :math:`t` represents updating step while :math:`beta_1^t` and :math:`beta_2^t` represent + `beta1_power` and `beta2_power`, :math:`\alpha` represents `learning_rate`, :math:`w` represents `params`, :math:`\epsilon` represents `eps`. Note: - The sparse strategy is applied while the SparseGatherV2 operator is used for forward network. If the sparse - strategy wants to be executed on the host, set the target to the CPU. - The sparse feature is under continuous development. + When separating parameter groups, the weight decay in each group will be applied on the parameters if the + weight decay is positive. When not separating parameter groups, the `weight_decay` in the API will be applied + on the parameters without 'beta' or 'gamma' in their names if `weight_decay` is positive. - If parameters are not grouped, the `weight_decay` in optimizer will be applied on the network parameters without - 'beta' or 'gamma' in their names. Users can group parameters to change the strategy of decaying weight. When - parameters are grouped, each group can set `weight_decay`, if not, the `weight_decay` in optimizer will be - applied. + When separating parameter groups, if you want to centralize the gradient, set grad_centralization to True, + but the gradient centralization can only be applied to the parameters of the convolution layer. + If the parameters of the non convolution layer are set to True, an error will be reported. + + To improve parameter groups performance, the customized order of parameters is supported. + + The sparse strategy is applied while the SparseGatherV2 operator is used for forward network. + The sparse feature is under continuous development. If the sparse strategy wants to be executed on the host, + set the target to the CPU. Args: - params (Union[list[Parameter], list[dict]]): Must be list of `Parameter` or list of `dict`. When the - `params` is a list of `dict`, the string "params", "lr", "weight_decay", "grad_centralization" and - "order_params" are the keys can be parsed. + params (Union[list[Parameter], list[dict]]): When the `params` is a list of `Parameter` which will be updated, + the element in `params` must be class `Parameter`. When the `params` is a list of `dict`, the "params", + "lr", "weight_decay" and "order_params" are the keys can be parsed. - - params: Required. Parameters in current group. The value must be a list of `Parameter`. + - params: Required. The value must be a list of `Parameter`. - - lr: Optional. If "lr" in the keys, the value of corresponding learning rate will be used. - If not, the `learning_rate` in optimizer will be used. Fixed and dynamic learning rate are supported. + - lr: Optional. If "lr" is in the keys, the value of the corresponding learning rate will be used. + If not, the `learning_rate` in the API will be used. - - weight_decay: Optional. If "weight_decay" in the keys, the value of corresponding weight decay - will be used. If not, the `weight_decay` in the optimizer will be used. It should be noted that weight - decay can be a constant value or a Cell. It is a Cell only when dynamic weight decay is applied. Dynamic - weight decay is similar to dynamic learning rate, users need to customize a weight decay schedule only - with global step as input, and during training, the optimizer calls the instance of WeightDecaySchedule - to get the weight decay value of current step. + - weight_decay: Optional. If "weight_decay" is in the keys, the value of the corresponding weight decay + will be used. If not, the `weight_decay` in the API will be used. - - grad_centralization: Optional. Must be Boolean. If "grad_centralization" is in the keys, the set value - will be used. If not, the `grad_centralization` is False by default. This configuration only works on the - convolution layer. + - order_params: Optional. If "order_params" is in the keys, the value must be the order of parameters and + the order will be followed in the optimizer. There are no other keys in the `dict` and the parameters + which in the 'order_params' must be in one of group parameters. - - order_params: Optional. When parameters is grouped, this usually is used to maintain the order of - parameters that appeared in the network to improve performance. The value should be parameters whose - order will be followed in optimizer. - If `order_params` in the keys, other keys will be ignored and the element of 'order_params' must be in - one group of `params`. - - learning_rate (Union[float, int, Tensor, Iterable, LearningRateSchedule]): Default: 1e-3. - - - float: The fixed learning rate value. Must be equal to or greater than 0. - - - int: The fixed learning rate value. Must be equal to or greater than 0. It will be converted to float. - - - Tensor: Its value should be a scalar or a 1-D vector. For scalar, fixed learning rate will be applied. - For vector, learning rate is dynamic, then the i-th step will take the i-th value as the learning rate. - - - Iterable: Learning rate is dynamic. The i-th step will take the i-th value as the learning rate. - - - LearningRateSchedule: Learning rate is dynamic. During training, the optimizer calls the instance of - LearningRateSchedule with step as the input to get the learning rate of current step. + - grad_centralization: Optional. The data type of "grad_centralization" is Bool. If "grad_centralization" + is in the keys, the set value will be used. If not, the `grad_centralization` is False by default. + This parameter only works on the convolution layer. + learning_rate (Union[float, Tensor, Iterable, LearningRateSchedule]): A value or a graph for the learning rate. + When the learning_rate is an Iterable or a Tensor in a 1D dimension, use the dynamic learning rate, then + the i-th step will take the i-th value as the learning rate. When the learning_rate is LearningRateSchedule, + use dynamic learning rate, the i-th learning rate will be calculated during the process of training + according to the formula of LearningRateSchedule. When the learning_rate is a float or a Tensor in a zero + dimension, use fixed learning rate. Other cases are not supported. The float learning rate must be + equal to or greater than 0. If the type of `learning_rate` is int, it will be converted to float. + Default: 1e-3. beta1 (float): The exponential decay rate for the 1st moment estimations. Should be in range (0.0, 1.0). Default: 0.9. beta2 (float): The exponential decay rate for the 2nd moment estimations. Should be in range (0.0, 1.0). Default: 0.999. eps (float): Term added to the denominator to improve numerical stability. Should be greater than 0. Default: 1e-8. - use_locking (bool): Whether to enable a lock to protect the updating process of variable tensors. - If true, updates of the `w`, `m`, and `v` tensors will be protected by a lock. + use_locking (bool): Whether to enable a lock to protect variable tensors from being updated. + If true, updates of the var, m, and v tensors will be protected by a lock. If false, the result is unpredictable. Default: False. use_nesterov (bool): Whether to use Nesterov Accelerated Gradient (NAG) algorithm to update the gradients. If true, update the gradients using NAG. If false, update the gradients without using NAG. Default: False. - - weight_decay (Union[float, int, Cell]): Weight decay (L2 penalty). Default: 0.0. - - - float: The fixed weight decay value. Must be equal to or greater than 0. - - - int: The fixed weight decay value. Must be equal to or greater than 0. It will be converted to float. - - - Cell: Weight decay is dynamic. During training, the optimizer calls the instance of - the Cell with step as the input to get the weight decay value of current step. - + weight_decay (float): Weight decay (L2 penalty). It must be equal to or greater than 0. Default: 0.0. loss_scale (float): A floating point value for the loss scale. Should be greater than 0. In general, use the default value. Only when `FixedLossScaleManager` is used for training and the `drop_overflow_update` in `FixedLossScaleManager` is set to False, then this value needs to be the same as the `loss_scale` in @@ -331,11 +370,9 @@ class Adam(Optimizer): ValueError: If `weight_decay` is less than 0. Supported Platforms: - ``Ascend`` ``GPU`` ``CPU`` + ``Ascend`` ``GPU`` Examples: - >>> from mindspore import nn, Model - >>> >>> net = Net() >>> #1) All parameters use the same learning rate and weight decay >>> optim = nn.Adam(params=net.trainable_params()) @@ -357,14 +394,17 @@ class Adam(Optimizer): >>> model = Model(net, loss_fn=loss, optimizer=optim) """ - @opt_init_args_register def __init__(self, params, learning_rate=1e-3, beta1=0.9, beta2=0.999, eps=1e-8, use_locking=False, use_nesterov=False, weight_decay=0.0, loss_scale=1.0): + # 初始化Adam类 super(Adam, self).__init__(learning_rate, params, weight_decay, loss_scale) + # 检查参数值 _check_param_value(beta1, beta2, eps, self.cls_name) + # 检查参数类型 validator.check_value_type("use_locking", use_locking, [bool], self.cls_name) validator.check_value_type("use_nesterov", use_nesterov, [bool], self.cls_name) + # 初始化参数 self.beta1 = Tensor(beta1, mstype.float32) self.beta2 = Tensor(beta2, mstype.float32) self.beta1_power = Parameter(initializer(1, [1], mstype.float32), name="beta1_power") @@ -375,154 +415,138 @@ class Adam(Optimizer): self.moment1 = self.parameters.clone(prefix="moment1", init='zeros') self.moment2 = self.parameters.clone(prefix="moment2", init='zeros') + # 是否为设备 self._is_device = True + # HyperMap算法 + self.hyper_map = C.HyperMap() + # Adam算法 self.opt = P.Adam(use_locking, use_nesterov) + # FusedSparseAdam self.sparse_opt = P.FusedSparseAdam(use_locking, use_nesterov) + # FusedSparseAdam的primitive_target属性 self.sparse_opt.add_prim_attr("primitive_target", "CPU") + # ops算子Pull self._ps_pull = P.Pull() + # ops算子Push self._ps_push = P.Push("Adam", [0, 1, 2]) + # Push的use_nesterov属性 self._ps_push.add_prim_attr("use_nesterov", use_nesterov) def construct(self, gradients): + ''' + 构建Adam优化器 + :param gradients: 梯度 + :return: + ''' params = self.parameters moment1 = self.moment1 moment2 = self.moment2 gradients = self.decay_weight(gradients) + # 将梯度按照学习率调整 gradients = self.gradients_centralization(gradients) + # 将梯度归一化 gradients = self.scale_grad(gradients) + # 将梯度转换为稀疏矩阵 gradients = self._grad_sparse_indices_deduplicate(gradients) + # 获取学习率 lr = self.get_lr() + # 计算beta1_power beta1_power = self.beta1_power * self.beta1 + # 更新beta1_power self.beta1_power = beta1_power + # 计算beta2_power beta2_power = self.beta2_power * self.beta2 + # 更新beta2_power self.beta2_power = beta2_power + # 如果是分组学习率 if self.is_group_lr: + # 用分组方法将梯度传入adam_opt函数 success = self.map_(F.partial(_adam_opt, self.opt, self.sparse_opt, self._ps_push, self._ps_pull, - self.use_locking, self.use_nesterov, self._is_device, - beta1_power, beta2_power, self.beta1, self.beta2, self.eps), + self.use_locking, self.use_nesterov, self._is_device, + beta1_power, beta2_power, self.beta1, self.beta2, self.eps), lr, gradients, params, moment1, moment2, self.ps_parameters, self.cache_enable) + # 否则 else: + # 将梯度传入adam_opt函数 success = self.map_(F.partial(_adam_opt, self.opt, self.sparse_opt, self._ps_push, self._ps_pull, - self.use_locking, self.use_nesterov, self._is_device, - beta1_power, beta2_power, self.beta1, self.beta2, self.eps, lr), + self.use_locking, self.use_nesterov, self._is_device, + beta1_power, beta2_power, self.beta1, self.beta2, self.eps, lr), gradients, params, moment1, moment2, self.ps_parameters, self.cache_enable) + # 返回成功状态 return success @Optimizer.target.setter + # 设置optimizer基类中的target属性 def target(self, value): - """ - If the input value is set to "CPU", the parameters will be updated on the host using the Fused - optimizer operation. - """ - self._set_base_target(value) + """If the input value is set to "CPU", the parameters will be updated on the host using the Fused + optimizer operation.""" + # 如果输入的值不是字符串类型,抛出类型错误 + if not isinstance(value, str): + raise TypeError("The value must be str type, but got value type is {}".format(type(value))) + + # 如果输入的值不在CPU、Ascend、GPU中,抛出值错误 + if value not in ('CPU', 'Ascend', 'GPU'): + raise ValueError("The value must be 'CPU', 'Ascend' or 'GPU', but got value {}".format(value)) + + # 如果设置的target值为CPU,且输入的值为Ascend或GPU,抛出值错误 + if self._target == "CPU" and value in('Ascend', 'GPU'): + raise ValueError("In the CPU environment, target cannot be set to 'GPU' and 'Ascend'.") + + # 如果设置的target值为Ascend,且输入的值为GPU,抛出值错误 + if self._target == "Ascend" and value == 'GPU': + raise ValueError("In the Ascend environment, target cannot be set to 'GPU'.") + + # 设置target值为不是CPU + self._is_device = (value!= 'CPU') + # 设置target值为输入的值 + self._target = value class AdamWeightDecay(Optimizer): - r""" - Implements the Adam algorithm with weight decay. - - .. math:: - \begin{array}{l} - &\newline - &\hline \\ - &\textbf{Parameters}: \: 1^{\text {st }}\text {moment vector} \: m , \: 2^{\text {nd}} \: - \text{moment vector} \: v , \\ - &\: gradients \: g, \: \text{learning rate} \: \gamma, - \text {exponential decay rates for the moment estimates} \: \beta_{1} \: \beta_{2} , \\ - &\:\text {parameter vector} \: w_{0}, \:\text{timestep} \: t, \: \text{weight decay} \: \lambda \\ - &\textbf{Init}: m_{0} \leftarrow 0, \: v_{0} \leftarrow 0, \: t \leftarrow 0, \: - \text{init parameter vector} \: w_{0} \\[-1.ex] - &\newline - &\hline \\ - &\textbf{repeat} \\ - &\hspace{5mm} t \leftarrow t+1 \\ - &\hspace{5mm}\boldsymbol{g}_{t} \leftarrow \nabla f_{t}\left(\boldsymbol{w}_{t-1}\right) \\ - &\hspace{5mm}\boldsymbol{m}_{t} \leftarrow \beta_{1} \boldsymbol{m}_{t-1}+\left(1-\beta_{1}\right) - \boldsymbol{g}_{t} \\ - &\hspace{5mm}\boldsymbol{v}_{t} \leftarrow \beta_{2} \boldsymbol{v}_{t-1}+\left(1-\beta_{2}\right) - \boldsymbol{g}_{t}^{2} \\ - &\hspace{5mm}\hat{\boldsymbol{m}}_{t} \leftarrow \boldsymbol{m}_{t} /\left(1-\beta_{1}^{t}\right) \\ - &\hspace{5mm}\hat{\boldsymbol{v}}_{t} \leftarrow \boldsymbol{v}_{t} /\left(1-\beta_{2}^{t}\right) \\ - &\hspace{5mm}\boldsymbol{w}_{t} \leftarrow \boldsymbol{w}_{t-1}-\left(\gamma \hat{\boldsymbol{m}}_{t} - /\left(\sqrt{\hat{\boldsymbol{v}}_{t}}+\epsilon\right)+\lambda \boldsymbol{w}_{t-1}\right) \\ - &\textbf{until}\text { stopping criterion is met } \\[-1.ex] - &\newline - &\hline \\[-1.ex] - &\textbf{return} \: \boldsymbol{w}_{t} \\[-1.ex] - &\newline - &\hline \\[-1.ex] - \end{array} - - :math:`m` represents the 1st moment vector `moment1`, :math:`v` represents the 2nd moment vector `moment2`, - :math:`g` represents `gradients`, :math:`\gamma` represents `learning_rate`, - :math:`\beta_1, \beta_2` represent `beta1` and `beta2`, :math:`t` represents the current step, - :math:`w` represents `params`, :math:`\gamma` represents `weight_decay`. + # Adam优化器梯度衰减 + """ + Implements the Adam algorithm to fix the weight decay. Note: - There is usually no connection between a optimizer and mixed precision. But when `FixedLossScaleManager` is used - and `drop_overflow_update` in `FixedLossScaleManager` is set to False, optimizer needs to set the 'loss_scale'. - As this optimizer has no argument of `loss_scale`, so `loss_scale` needs to be processed by other means, refer - document `LossScale `_ to process - `loss_scale` correctly. + When separating parameter groups, the weight decay in each group will be applied on the parameters if the + weight decay is positive. When not separating parameter groups, the `weight_decay` in the API will be applied + on the parameters without 'beta' or 'gamma' in their names if `weight_decay` is positive. - If parameters are not grouped, the `weight_decay` in optimizer will be applied on the network parameters without - 'beta' or 'gamma' in their names. Users can group parameters to change the strategy of decaying weight. When - parameters are grouped, each group can set `weight_decay`, if not, the `weight_decay` in optimizer will be - applied. + To improve parameter groups performance, the customized order of parameters can be supported. Args: - params (Union[list[Parameter], list[dict]]): Must be list of `Parameter` or list of `dict`. When the - `params` is a list of `dict`, the string "params", "lr", "weight_decay", and "order_params" - are the keys can be parsed. + params (Union[list[Parameter], list[dict]]): When the `params` is a list of `Parameter` which will be updated, + the element in `params` must be class `Parameter`. When the `params` is a list of `dict`, the "params", + "lr", "weight_decay" and "order_params" are the keys can be parsed. - - params: Required. Parameters in current group. The value must be a list of `Parameter`. + - params: Required. The value must be a list of `Parameter`. - - lr: Optional. If "lr" in the keys, the value of corresponding learning rate will be used. - If not, the `learning_rate` in optimizer will be used. Fixed and dynamic learning rate are supported. + - lr: Optional. If "lr" is in the keys, the value of the corresponding learning rate will be used. + If not, the `learning_rate` in the API will be used. - - weight_decay: Optional. If "weight_decay" in the keys, the value of corresponding weight decay - will be used. If not, the `weight_decay` in the optimizer will be used. It should be noted that weight - decay can be a constant value or a Cell. It is a Cell only when dynamic weight decay is applied. Dynamic - weight decay is similar to dynamic learning rate, users need to customize a weight decay schedule only - with global step as input, and during training, the optimizer calls the instance of WeightDecaySchedule - to get the weight decay value of current step. + - weight_decay: Optional. If "weight_decay" is in the keys, the value of the corresponding weight decay + will be used. If not, the `weight_decay` in the API will be used. - - order_params: Optional. When parameters is grouped, this usually is used to maintain the order of - parameters that appeared in the network to improve performance. The value should be parameters whose - order will be followed in optimizer. - If `order_params` in the keys, other keys will be ignored and the element of 'order_params' must be in - one group of `params`. - - learning_rate (Union[float, int, Tensor, Iterable, LearningRateSchedule]): Default: 1e-3. - - - float: The fixed learning rate value. Must be equal to or greater than 0. - - - int: The fixed learning rate value. Must be equal to or greater than 0. It will be converted to float. - - - Tensor: Its value should be a scalar or a 1-D vector. For scalar, fixed learning rate will be applied. - For vector, learning rate is dynamic, then the i-th step will take the i-th value as the learning rate. - - - Iterable: Learning rate is dynamic. The i-th step will take the i-th value as the learning rate. - - - LearningRateSchedule: Learning rate is dynamic. During training, the optimizer calls the instance of - LearningRateSchedule with step as the input to get the learning rate of current step. + - order_params: Optional. If "order_params" is in the keys, the value must be the order of parameters and + the order will be followed in the optimizer. There are no other keys in the `dict` and the parameters + which in the 'order_params' must be in one of group parameters. + learning_rate (Union[float, Tensor, Iterable, LearningRateSchedule]): A value or a graph for the learning rate. + When the learning_rate is an Iterable or a Tensor in a 1D dimension, use the dynamic learning rate, then + the i-th step will take the i-th value as the learning rate. When the learning_rate is LearningRateSchedule, + use dynamic learning rate, the i-th learning rate will be calculated during the process of training + according to the formula of LearningRateSchedule. When the learning_rate is a float or a Tensor in a zero + dimension, use fixed learning rate. Other cases are not supported. The float learning rate must be + equal to or greater than 0. If the type of `learning_rate` is int, it will be converted to float. + Default: 1e-3. beta1 (float): The exponential decay rate for the 1st moment estimations. Default: 0.9. Should be in range (0.0, 1.0). beta2 (float): The exponential decay rate for the 2nd moment estimations. Default: 0.999. Should be in range (0.0, 1.0). eps (float): Term added to the denominator to improve numerical stability. Default: 1e-6. Should be greater than 0. - - weight_decay (Union[float, int, Cell]): Weight decay (L2 penalty). Default: 0.0. - - - float: The fixed weight decay value. Must be equal to or greater than 0. - - - int: The fixed weight decay value. Must be equal to or greater than 0. It will be converted to float. - - - Cell: Weight decay is dynamic. During training, the optimizer calls the instance of - the Cell with step as the input to get the weight decay value of current step. + weight_decay (float): Weight decay (L2 penalty). It must be equal to or greater than 0. Default: 0.0. Inputs: - **gradients** (tuple[Tensor]) - The gradients of `params`, the shape is the same as `params`. @@ -540,11 +564,9 @@ class AdamWeightDecay(Optimizer): ValueError: If `weight_decay` is less than 0. Supported Platforms: - ``Ascend`` ``GPU`` ``CPU`` + ``Ascend`` ``GPU`` Examples: - >>> from mindspore import nn, Model - >>> >>> net = Net() >>> #1) All parameters use the same learning rate and weight decay >>> optim = nn.AdamWeightDecay(params=net.trainable_params()) @@ -563,40 +585,50 @@ class AdamWeightDecay(Optimizer): >>> loss = nn.SoftmaxCrossEntropyWithLogits() >>> model = Model(net, loss_fn=loss, optimizer=optim) """ - _support_parallel_optimizer = True - def __init__(self, params, learning_rate=1e-3, beta1=0.9, beta2=0.999, eps=1e-6, weight_decay=0.0): + # 初始化AdamWeightDecay类 super(AdamWeightDecay, self).__init__(learning_rate, params, weight_decay) + # 检查参数值 _check_param_value(beta1, beta2, eps, self.cls_name) + # 初始化beta1, beta2, eps self.beta1 = Tensor(np.array([beta1]).astype(np.float32)) self.beta2 = Tensor(np.array([beta2]).astype(np.float32)) self.eps = Tensor(np.array([eps]).astype(np.float32)) + # 初始化moments1, moments2 self.moments1 = self.parameters.clone(prefix="adam_m", init='zeros') self.moments2 = self.parameters.clone(prefix="adam_v", init='zeros') + # 初始化HyperMap + self.hyper_map = C.HyperMap() def construct(self, gradients): - weight_decay = self.get_weight_decay() + # 获取学习率 lr = self.get_lr() + # 如果是分组,则使用分组学习率 if self.is_group: if self.is_group_lr: + # 如果是分组学习率,则按分组方式将学习率按分组方式传入使用HyperMap optim_result = self.hyper_map(F.partial(_adam_opt, self.beta1, self.beta2, self.eps), - lr, weight_decay, self.parameters, self.moments1, - self.moments2, gradients, self.decay_flags, self.optim_filter) + lr, self.weight_decay, self.parameters, self.moments1, self.moments2, + gradients, self.decay_flags, self.optim_filter) else: + # 否则按分组方式使用HyperMap optim_result = self.hyper_map(F.partial(_adam_opt, self.beta1, self.beta2, self.eps, lr), - weight_decay, self.parameters, self.moments1, self.moments2, + self.weight_decay, self.parameters, self.moments1, self.moments2, gradients, self.decay_flags, self.optim_filter) else: - optim_result = self.hyper_map(F.partial(_adam_opt, self.beta1, self.beta2, self.eps, lr, weight_decay), + # 否则使用HyperMap + optim_result = self.hyper_map(F.partial(_adam_opt, self.beta1, self.beta2, self.eps, lr, self.weight_decay), self.parameters, self.moments1, self.moments2, gradients, self.decay_flags, self.optim_filter) + # 如果使用并行,则广播参数 if self.use_parallel: self.broadcast_params(optim_result) - + # 返回优化结果 return optim_result class AdamOffload(Optimizer): + #AdamOffload优化器 r""" This optimizer will offload Adam optimizer to host CPU and keep parameters being updated on the device, to minimize the memory cost. Although that would bring about an increase of performance overhead, @@ -608,85 +640,65 @@ class AdamOffload(Optimizer): .. math:: \begin{array}{ll} \\ - m_{t+1} = \beta_1 * m_{t} + (1 - \beta_1) * g \\ - v_{t+1} = \beta_2 * v_{t} + (1 - \beta_2) * g * g \\ + m = \beta_1 * m + (1 - \beta_1) * g \\ + v = \beta_2 * v + (1 - \beta_2) * g * g \\ l = \alpha * \frac{\sqrt{1-\beta_2^t}}{1-\beta_1^t} \\ - w_{t+1} = w_{t} - l * \frac{m_{t+1}}{\sqrt{v_{t+1}} + \epsilon} + w = w - l * \frac{m}{\sqrt{v} + \epsilon} \end{array} :math:`m` represents the 1st moment vector `moment1`, :math:`v` represents the 2nd moment vector `moment2`, - :math:`g` represents `gradients`, :math:`l` represents scaling factor, :math:`\beta_1, \beta_2` represent - `beta1` and `beta2`, :math:`t` represents the current step while :math:`beta_1^t` and :math:`beta_2^t` represent + :math:`g` represents `gradients`, :math:`l` represents scaling factor `lr`, :math:`\beta_1, \beta_2` represent + `beta1` and `beta2`, :math:`t` represents updating step while :math:`beta_1^t` and :math:`beta_2^t` represent `beta1_power` and `beta2_power`, :math:`\alpha` represents `learning_rate`, :math:`w` represents `params`, :math:`\epsilon` represents `eps`. Note: This optimizer only supports `GRAPH_MODE` currently. - If parameters are not grouped, the `weight_decay` in optimizer will be applied on the network parameters without - 'beta' or 'gamma' in their names. Users can group parameters to change the strategy of decaying weight. When - parameters are grouped, each group can set `weight_decay`, if not, the `weight_decay` in optimizer will be - applied. + When separating parameter groups, the weight decay in each group will be applied on the parameters if the + weight decay is positive. When not separating parameter groups, the `weight_decay` in the API will be applied + on the parameters without 'beta' or 'gamma' in their names if `weight_decay` is positive. + + To improve parameter groups performance, the customized order of parameters is supported. Args: - params (Union[list[Parameter], list[dict]]): Must be list of `Parameter` or list of `dict`. When the - `params` is a list of `dict`, the string "params", "lr", "weight_decay", and "order_params" - are the keys can be parsed. + params (Union[list[Parameter], list[dict]]): When the `params` is a list of `Parameter` which will be updated, + the element in `params` must be class `Parameter`. When the `params` is a list of `dict`, the "params", + "lr", "weight_decay" and "order_params" are the keys can be parsed. - - params: Required. Parameters in current group. The value must be a list of `Parameter`. + - params: Required. The value must be a list of `Parameter`. - - lr: Optional. If "lr" in the keys, the value of corresponding learning rate will be used. - If not, the `learning_rate` in optimizer will be used. Fixed and dynamic learning rate are supported. + - lr: Optional. If "lr" is in the keys, the value of the corresponding learning rate will be used. + If not, the `learning_rate` in the API will be used. - - weight_decay: Optional. If "weight_decay" in the keys, the value of corresponding weight decay - will be used. If not, the `weight_decay` in the optimizer will be used. It should be noted that weight - decay can be a constant value or a Cell. It is a Cell only when dynamic weight decay is applied. Dynamic - weight decay is similar to dynamic learning rate, users need to customize a weight decay schedule only - with global step as input, and during training, the optimizer calls the instance of WeightDecaySchedule - to get the weight decay value of current step. + - weight_decay: Optional. If "weight_decay" is in the keys, the value of the corresponding weight decay + will be used. If not, the `weight_decay` in the API will be used. - - order_params: Optional. When parameters is grouped, this usually is used to maintain the order of - parameters that appeared in the network to improve performance. The value should be parameters whose - order will be followed in optimizer. - If `order_params` in the keys, other keys will be ignored and the element of 'order_params' must be in - one group of `params`. - - learning_rate (Union[float, int, Tensor, Iterable, LearningRateSchedule]): Default: 1e-3. - - - float: The fixed learning rate value. Must be equal to or greater than 0. - - - int: The fixed learning rate value. Must be equal to or greater than 0. It will be converted to float. - - - Tensor: Its value should be a scalar or a 1-D vector. For scalar, fixed learning rate will be applied. - For vector, learning rate is dynamic, then the i-th step will take the i-th value as the learning rate. - - - Iterable: Learning rate is dynamic. The i-th step will take the i-th value as the learning rate. - - - LearningRateSchedule: Learning rate is dynamic. During training, the optimizer calls the instance of - LearningRateSchedule with step as the input to get the learning rate of current step. + - order_params: Optional. If "order_params" is in the keys, the value must be the order of parameters and + the order will be followed in the optimizer. There are no other keys in the `dict` and the parameters + which in the 'order_params' must be in one of group parameters. + learning_rate (Union[float, Tensor, Iterable, LearningRateSchedule]): A value or a graph for the learning rate. + When the learning_rate is an Iterable or a Tensor in a 1D dimension, use the dynamic learning rate, then + the i-th step will take the i-th value as the learning rate. When the learning_rate is LearningRateSchedule, + use dynamic learning rate, the i-th learning rate will be calculated during the process of training + according to the formula of LearningRateSchedule. When the learning_rate is a float or a Tensor in a zero + dimension, use fixed learning rate. Other cases are not supported. The float learning rate must be + equal to or greater than 0. If the type of `learning_rate` is int, it will be converted to float. + Default: 1e-3. beta1 (float): The exponential decay rate for the 1st moment estimations. Should be in range (0.0, 1.0). Default: 0.9. beta2 (float): The exponential decay rate for the 2nd moment estimations. Should be in range (0.0, 1.0). Default: 0.999. eps (float): Term added to the denominator to improve numerical stability. Should be greater than 0. Default: 1e-8. - use_locking (bool): Whether to enable a lock to protect the updating process of variable tensors. - If true, updates of the `w`, `m`, and `v` tensors will be protected by a lock. + use_locking (bool): Whether to enable a lock to protect variable tensors from being updated. + If true, updates of the var, m, and v tensors will be protected by a lock. If false, the result is unpredictable. Default: False. use_nesterov (bool): Whether to use Nesterov Accelerated Gradient (NAG) algorithm to update the gradients. If true, update the gradients using NAG. If false, update the gradients without using NAG. Default: False. - - weight_decay (Union[float, int, Cell]): Weight decay (L2 penalty). Default: 0.0. - - - float: The fixed weight decay value. Must be equal to or greater than 0. - - - int: The fixed weight decay value. Must be equal to or greater than 0. It will be converted to float. - - - Cell: Weight decay is dynamic. During training, the optimizer calls the instance of - the Cell with step as the input to get the weight decay value of current step. - + weight_decay (float): Weight decay (L2 penalty). It must be equal to or greater than 0. Default: 0.0. loss_scale (float): A floating point value for the loss scale. Should be greater than 0. In general, use the default value. Only when `FixedLossScaleManager` is used for training and the `drop_overflow_update` in `FixedLossScaleManager` is set to False, then this value needs to be the same as the `loss_scale` in @@ -713,8 +725,6 @@ class AdamOffload(Optimizer): ``Ascend`` ``GPU`` ``CPU`` Examples: - >>> from mindspore import nn, Model - >>> >>> net = Net() >>> #1) All parameters use the same learning rate and weight decay >>> optim = nn.AdamOffload(params=net.trainable_params()) @@ -736,39 +746,77 @@ class AdamOffload(Optimizer): def __init__(self, params, learning_rate=1e-3, beta1=0.9, beta2=0.999, eps=1e-8, use_locking=False, use_nesterov=False, weight_decay=0.0, loss_scale=1.0): + ''' + 参数: + params:参数列表 + learning_rate:学习率 + beta1:beta1的值 + beta2:beta2的值 + eps:epsilon的值 + use_locking:是否使用锁定 + use_nesterov:是否使用Nesterov梯度 + weight_decay:权重衰减 + loss_scale:损失缩放因子 + ''' super(AdamOffload, self).__init__(learning_rate, params, weight_decay, loss_scale) + # 调用_check_param_value方法检测输入参数值是否符合要求 _check_param_value(beta1, beta2, eps, self.cls_name) + # 检查beta1、beta2、eps参数的值是否符合要求 validator.check_value_type("use_locking", use_locking, [bool], self.cls_name) validator.check_value_type("use_nesterov", use_nesterov, [bool], self.cls_name) + # 将参数赋值给变量 self.beta1 = Tensor(beta1, mstype.float32) self.beta2 = Tensor(beta2, mstype.float32) self.beta1_power = Parameter(initializer(1, [1], mstype.float32), name="beta1_power") self.beta2_power = Parameter(initializer(1, [1], mstype.float32), name="beta2_power") self.eps = Tensor(eps, mstype.float32) + self.use_nesterov = use_nesterov + self.use_locking = use_locking + # 创建参数 self.moment1 = self.parameters.clone(prefix="moment1", init='zeros') self.moment2 = self.parameters.clone(prefix="moment2", init='zeros') + + # 创建HyperMap + self.hyper_map = C.HyperMap() + # 创建AdamNoUpdateParam self.opt = P.AdamNoUpdateParam(use_locking, use_nesterov) + # 将primitive_target设置为CPU self.opt.add_prim_attr("primitive_target", "CPU") def construct(self, gradients): + ''' + 参数: + gradients:梯度 + ''' params = self.parameters moment1 = self.moment1 moment2 = self.moment2 + # 将梯度衰减 gradients = self.decay_weight(gradients) + # 将梯度缩放 gradients = self.scale_grad(gradients) + # 获取学习率 lr = self.get_lr() + # 计算beta1的平方 beta1_power = self.beta1_power * self.beta1 + # 更新beta1的平方 self.beta1_power = beta1_power + # 计算beta2的平方 beta2_power = self.beta2_power * self.beta2 + # 更新beta2的平方 self.beta2_power = beta2_power + # 如果是分组学习率 if self.is_group_lr: - success = self.map_reverse(F.partial(_adam_opt, self.opt, - beta1_power, beta2_power, self.beta1, self.beta2, self.eps), - lr, gradients, params, moment1, moment2) + # 调用_adam_opt函数,传入参数 + success = self.map_(F.partial(_adam_opt, self.opt, + beta1_power, beta2_power, self.beta1, self.beta2, self.eps), + lr, gradients, params, moment1, moment2) + # 否则 else: - success = self.map_reverse(F.partial(_adam_opt, self.opt, - beta1_power, beta2_power, self.beta1, self.beta2, self.eps, lr), - gradients, params, moment1, moment2) - return success + # 调用_adam_opt函数,传入参数 + success = self.map_(F.partial(_adam_opt, self.opt, + beta1_power, beta2_power, self.beta1, self.beta2, self.eps, lr), + gradients, params, moment1, moment2) + return success \ No newline at end of file diff --git a/mindspore/python/mindspore/nn/optim/adasum.py b/mindspore/python/mindspore/nn/optim/adasum.py index 009b91316d9..04d7ae08caf 100644 --- a/mindspore/python/mindspore/nn/optim/adasum.py +++ b/mindspore/python/mindspore/nn/optim/adasum.py @@ -13,6 +13,7 @@ # limitations under the License. # ============================================================================ """adasum""" +# Adaptive Summation (AdaSum)算法的实现 import copy import hashlib import math @@ -30,72 +31,114 @@ from mindspore.ops.operations._inner_ops import Send, Receive from mindspore.common.tensor import Tensor from mindspore.common import dtype as mstype from mindspore.communication.management import create_group - +# 包含两种功能相近的优化器 __all__ = ["AdaSumByDeltaWeightWrapCell", "AdaSumByGradWrapCell"] MAX_NUM_HASH = 2 ** 31 +# 创建一个MultitypeFuncGraph对象,用于更新参数 _update_parameters = C.MultitypeFuncGraph("update_parameters") +# 创建一个MultitypeFuncGraph对象,用于reshape梯度 _reshape_grads = C.MultitypeFuncGraph("reshape_grads") - @_update_parameters.register("Tensor", "Tensor", "Tensor", "Tensor", "Function") def _update_parameters_adasum(delta_weight, update_delta_weight, parameter, old_parameter, reshape): + ''' + 更新参数 + :param delta_weight: 参数梯度 + :param update_delta_weight: 更新参数梯度 + :param parameter: 参数 + :param old_parameter: 原始参数 + :param reshape: 将参数转换为形状 + :return: 更新后的参数 + ''' shape = F.shape(delta_weight) update_delta_weight = reshape(update_delta_weight, shape) new_parameter = old_parameter - update_delta_weight return P.Assign()(parameter, new_parameter) - @_reshape_grads.register("Tensor", "Tensor", "Function") def reshape_grads_adasum(grads, update_grads, reshape): """ Reshape gradient. """ + # 获取梯度的形状 shape = F.shape(grads) + # 将梯度reshape到指定形状 update_grads = reshape(update_grads, shape) + # 返回reshaped梯度 return update_grads def _send_before_receive(send_part, send, recv): + ''' + 接收前发送 + :param send_part: 发送的部分 + :param send: 发送函数 + :param recv: 接收函数 + :return: 接收结果 + ''' send_ok = send(send_part) + # 发送send_part到服务器 return recv(send_ok) def _receive_before_send(send_part, send, recv): + ''' + 接收前发送 + ''' + # 接收发送部分 receive_ok = recv(send_part) + # 将发送部分依赖 send_part = F.depend(send_part, receive_ok) + # 将依赖发送 return F.depend(receive_ok, send(send_part)) def _send_recv_res(left_send, recv_part, local_part, allreduce, parameter_divisibility, allreduce_node_num): """send result and receive result.""" if parameter_divisibility: + # 将recv_part拆分为两部分 recv_part = P.Squeeze()(recv_part) + # 如果recv_part的形状为None,则将recv_part设置为一个元素 if F.shape(recv_part) is None: recv_part = Tensor([recv_part]) + # 将local_part和recv_part合并 local_part = F.depend(local_part, recv_part) + # 将eps设置为1e-12 eps = 1e-12 + # 计算local_part的最大值,并将其值除以scale_value scale_value = P.ReduceMax()(local_part) + eps + # 将local_part除以scale_value local_part_scale = local_part / scale_value + # 将recv_part除以scale_value recv_part_scale = recv_part / scale_value + # 将recv_part_scale和local_part_scale合并 recv_part_scale = F.depend(recv_part_scale, local_part_scale) + # 计算value_0 value_0 = P.ReduceSum()(local_part_scale * recv_part_scale) + eps + # 如果left_send为True,计算value_1 if left_send: value_1 = P.ReduceSum()(local_part_scale * local_part_scale) + eps value_2 = P.ReduceSum()(recv_part_scale * recv_part_scale) + eps + # 如果left_send为False,计算value_1 else: value_1 = P.ReduceSum()(recv_part_scale * recv_part_scale) + eps value_2 = P.ReduceSum()(local_part_scale * local_part_scale) + eps + # 将value_0和value_1合并 value_0 = allreduce(value_0) value_1 = F.depend(allreduce(value_1), value_0) value_2 = F.depend(allreduce(value_2), value_1) if left_send: + # 如果左边发送,则res等于1-value_0/(2*value_1)的和,否则res等于1-value_0/(2*value_1)的和 res = (1 - (value_0 / (2 * value_1))) * local_part + (1 - (value_0 / (2 * value_2))) * recv_part else: + # 如果右边发送,则res等于1-value_0/(2*value_1)的和,否则res等于1-value_0/(2*value_1)的和 res = (1 - (value_0 / (2 * value_1))) * recv_part + (1 - (value_0 / (2 * value_2))) * local_part else: + # 如果所有节点都是自身,则直接使用allreduce res = allreduce(local_part) + # 计算并行计算的节点数 res = res / allreduce_node_num return res @@ -107,60 +150,92 @@ _adasum_opt_rollback = C.MultitypeFuncGraph("adasum_opt_rollback") @_adasum_opt_forward.register("Bool", "Function", "Bool", "Int64", "Function", "Function", "Tensor") def _adasum_opt_forward_process(left_send, allreduce, parameter_divisibility, allreduce_node_num, send, recv, delta_w): """adasum optimizer process.""" + # 如果parameter_divisibility为True,则delta_w的维度为1 if parameter_divisibility: delta_w = P.Squeeze()(delta_w) + # 将delta_w的维度除以2 ori_len = F.shape(delta_w)[0] divide_len = ori_len / 2 + # 将delta_w的前divide_len个元素取平均 left_part = delta_w[:divide_len] right_part = delta_w[divide_len:] + # 如果parameter_divisibility为False,则delta_w的维度为2 else: left_part = delta_w right_part = delta_w + # 如果left_send为True,则发送delta_w到发送端 if left_send: if parameter_divisibility: + # 如果参数除法,则将左边部分发送到右边部分,并将右边部分接收到左边部分 recv_part = _send_before_receive(left_part, send, recv) else: + # 否则,将右边部分发送到左边部分,并将左边部分接收到右边部分 recv_part = right_part + # 发送delta_w到发送端,并计算update_delta_w update_delta_w = _send_recv_res(left_send, recv_part, right_part, allreduce, parameter_divisibility, allreduce_node_num) + # 如果left_send为False,则接收delta_w从发送端 else: + # 如果参数除法,则将右边部分发送到左边部分,并将左边部分接收到右边部分 if parameter_divisibility: recv_part = _receive_before_send(right_part, send, recv) + # 否则,将左边部分发送到右边部分,并将右边部分接收到左边部分 else: recv_part = left_part + # 接收delta_w从发送端,并计算update_delta_w update_delta_w = _send_recv_res(left_send, recv_part, left_part, allreduce, parameter_divisibility, allreduce_node_num) + # 返回update_delta_w return update_delta_w @_adasum_opt_rollback.register("Bool", "Bool", "Tensor", "Function", "Function") def _adasum_opt_rollback_process(left_send, parameter_divisibility, delta_w, send, recv): """adasum optimizer rollback process.""" + # 如果参数除法 if parameter_divisibility: + # 如果左发送 if left_send: + # 发送前接收 recv_part = _send_before_receive(delta_w, send, recv) + # 如果右发送 else: + # 接收前发送 recv_part = _receive_before_send(delta_w, send, recv) + # 将发送和接收部分拉平 recv_part = P.Squeeze()(recv_part) + # 如果接收部分为空 if F.shape(recv_part) is None: + # 将接收部分设置为一个tensor recv_part = Tensor([recv_part]) + # 如果发送部分为空 if F.shape(delta_w) is None: + # 将发送部分设置为一个tensor delta_w = Tensor([delta_w]) + # 将接收部分和发送部分压缩 recv_part = P.Reshape()(recv_part, (-1,)) delta_w = P.Reshape()(delta_w, (-1,)) + # 如果左发送 if left_send: + # 将接收部分和发送部分拼接 res = P.Concat()((recv_part, delta_w)) + # 如果右发送 else: + # 将发送部分和接收部分拼接 res = P.Concat()((delta_w, recv_part)) + # 如果不参数除法 else: + # 将发送部分设置为发送部分 res = delta_w + # 返回拼接后的结果 return res class _AdaSum(Cell): + # 自适应求和算法是一种改进深度学习模型的分布式数据并行训练的新算法。 r""" The Adaptive Summation, or AdaSum, is a novel algorithm for improving distributed data parallel training of Deep Learning models. @@ -174,44 +249,68 @@ class _AdaSum(Cell): - **adasum_parameters** (Tuple(Tensor)) - Tuple of parameters after adasum process. """ def __init__(self, rank, device_number, group_number, parameter_tuple): + # 初始化_AdaSum类 super(_AdaSum, self).__init__() + # 设置rank self.rank = rank + # 设置device_number self.device_number = device_number + # 设置group_number self.group_number = group_number + # 设置参数tuple self.parameter_tuple = parameter_tuple + # 生成通信操作 self._generate_communication_op() + # 初始化HyperMap self.hyper_map = C.HyperMap() + # 初始化update_reshape_list self.update_reshape_list = [] + # 遍历参数tuple for parameter in self.parameter_tuple: + # 初始化reshape reshape = P.Reshape().add_prim_attr("target_param", "adasum_delta_weight." + parameter.name) + # 将reshape添加到update_reshape_list中 self.update_reshape_list.append(reshape) @staticmethod def _hash(step, target, weights_index): + # 将step、target、weights_index拼接成字符串 target = "tag" + str(step) + str(target) + str(weights_index) + # 将字符串转换为sha1格式的字节码 target_hash = hashlib.sha1(target.encode()).hexdigest() + # 将字节码转换为整数 hash_res = int(int(target_hash, 16) % MAX_NUM_HASH) + # 返回hash_res return hash_res - def construct(self, delta_weights, parameters, old_parameters): + # 初始化反向传播权重 forward_weights = [delta_weights] + # 循环反向传播 for i in range(self.calc_times): + # 计算反向传播权重 process_weights = self.hyper_map(F.partial(_adasum_opt_forward, self.send_node[i]), self.allreduce_list[i], self.parameter_divisibility_list[i], self.allreduce_node_num_list[i], self.send_list_forward[i], self.recv_list_forward[i], forward_weights[-1]) + # 将反向传播权重添加到反向传播权重列表中 forward_weights.append(process_weights) + # 循环反向传播 for i in range(self.calc_times): + # 计算反向传播权重 j = self.calc_times - i - 1 process_weights = self.hyper_map(F.partial(_adasum_opt_rollback, self.send_node[j]), self.parameter_divisibility_list[j], forward_weights[j + 1], self.send_list_rollback[j], self.recv_list_rollback[j]) + # 将反向传播权重添加到反向传播权重列表中 forward_weights[j] = process_weights + # 计算反向传播参数 adasum_parameters = self.hyper_map(F.partial(_update_parameters), delta_weights, forward_weights[0], parameters, old_parameters, self.update_reshape_list) + # 返回反向传播参数 return adasum_parameters def _generate_communication_op(self): """generate communication op.""" + # 初始化参数 self.calc_times = int(math.log(self.group_number, 2)) self.send_node = [] self.send_list_forward = [] @@ -222,14 +321,19 @@ class _AdaSum(Cell): self.parameter_divisibility_list = [] self.allreduce_node_num_list = [] last_delta_weights = [] + # 如果当前设置的并行模式为data_parallel或hybrid_parallel,则将fusion_attr设置为fusion fusion_attr = "fusion" if context.get_auto_parallel_context("parallel_mode") \ in ["data_parallel", "hybrid_parallel"] else "origin_fusion" for step in range(self.calc_times): + # 计算步骤 current_group = self.device_number * (2 ** step) + # 设置源节点 sr_target = self.rank + # 如果源节点的组数为奇数,则将源节点的组数加上当前步骤的组数 if (sr_target // current_group) % 2 == 0: dest_target = sr_target + current_group self.send_node.append(True) + # 如果源节点的组数为偶数,则将源节点的组数减去当前步骤的组数 else: dest_target = sr_target - current_group self.send_node.append(False) @@ -239,26 +343,43 @@ class _AdaSum(Cell): recv_left = [] recv_right = [] allreduce_node_num = () + # 获取上一次的delta_weights信息 left_delta_weights, right_delta_weights, delta_weights_divisibility = \ self._get_delta_weights_info(last_delta_weights) + # 将delta_weights_divisibility添加到参数分配列表中 self.parameter_divisibility_list.append(delta_weights_divisibility) + # 初始化变量weights_index weights_index = 0 + # 计算fusion_id fusion_id = (step + 1) * 3 for shape, dtype, name in left_delta_weights: + # 创建发送标签 send_tag = self._hash(step, sr_target, weights_index) + # 创建发送对象 send = Send(sr_tag=send_tag, dest_rank=dest_target, group="hccl_world_group") + # 添加fusion_attr属性 send.add_prim_attr(fusion_attr, fusion_id) + # 添加opposite_rank属性 send.add_prim_attr("opposite_rank", dest_target) + # 添加target_param属性 send.add_prim_attr("target_param", name) + # 创建接收标签 recv_tag = self._hash(step, dest_target, weights_index) + # 创建接收对象 recv = Receive(sr_tag=recv_tag, src_rank=dest_target, shape=shape, dtype=dtype, group="hccl_world_group") + # 添加fusion_attr属性 recv.add_prim_attr(fusion_attr, fusion_id) + # 添加opposite_rank属性 recv.add_prim_attr("opposite_rank", dest_target) + # 添加target_param属性 recv.add_prim_attr("target_param", name) + # 将发送对象和接收对象添加到send_left列表中 send_left.append(send) recv_left.append(recv) + # 更新weights_index weights_index += 1 + # 重复上述操作 for shape, dtype, name in right_delta_weights: send_tag = self._hash(step, sr_target, weights_index) send = Send(sr_tag=send_tag, dest_rank=dest_target, group="hccl_world_group") @@ -275,131 +396,206 @@ class _AdaSum(Cell): recv_right.append(recv) weights_index += 1 if self.send_node and self.send_node[-1]: + # 如果发送节点有最后一个节点,则将发送节点添加到发送列表中 self.send_list_forward.append(send_left) self.send_list_rollback.append(send_right) + # 将接收节点添加到接收列表中 self.recv_list_forward.append(recv_right) self.recv_list_rollback.append(recv_left) + # 将右边的delta_weights添加到最后一个delta_weights中 last_delta_weights = right_delta_weights else: + # 如果发送节点没有最后一个节点,则将右边的节点添加到发送列表中 self.send_list_forward.append(send_right) self.send_list_rollback.append(send_left) + # 将左边的节点添加到接收列表中 self.recv_list_forward.append(recv_left) self.recv_list_rollback.append(recv_right) + # 将左边的delta_weights添加到最后一个delta_weights中 last_delta_weights = left_delta_weights param_allreduce_list = [] neighbor_ids = [] rank_ids = [] for index in range(2 ** (step + 1)): + # 计算节点的编号 node_rank = self.rank // self.device_number + # 计算节点的双边 double_d = 2 ** (step + 1) + # 计算邻节点的编号 neighbor_id = (node_rank // double_d * double_d + index) * self.device_number + \ self.rank % self.device_number + # 将节点的编号添加到neighbor_ids列表中 neighbor_ids.append(str(neighbor_id)) + # 将节点的编号添加到rank_ids列表中 rank_ids.append(neighbor_id) + # 将neighbor_ids列表转换为字符串 group_name = "-".join(neighbor_ids) + # 如果parallel_mode为data_parallel或hybrid_parallel,则创建组 if context.get_auto_parallel_context("parallel_mode") in ["data_parallel", "hybrid_parallel"]: create_group(group_name, rank_ids) for parameter in self.parameter_tuple: + # 创建一个所有计算的参数 allreduce = P.AllReduce("sum", group_name) + # 添加target_param属性,值为adasum_delta_weight.parameter.name allreduce.add_prim_attr("target_param", "adasum_delta_weight." + parameter.name) + # 添加fusion_attr属性,值为fusion_id + 2 allreduce.add_prim_attr(fusion_attr, fusion_id + 2) + # 添加step属性,值为step allreduce.add_prim_attr("step", step) + # 添加param_allreduce_list列表 param_allreduce_list.append(allreduce) + # 添加param_allreduce_list列表 self.allreduce_list.append(param_allreduce_list) + # 创建一个每个参数的除数 for param_divisibility in delta_weights_divisibility: + # 如果param_divisibility为真,则计算节点数为0 if param_divisibility: allreduce_node_num += (0,) + # 否则计算节点数为2的幂次方 else: allreduce_node_num += (2 ** (step + 1),) + # 添加allreduce_node_num_list列表 self.allreduce_node_num_list.append(allreduce_node_num) def _get_delta_weights_info(self, last_delta_weights): + # 获取delta_weights的信息 """get delta weights info.""" half_delta_weights = [] if last_delta_weights: + # 如果有上一次的delta_weights half_delta_weights = last_delta_weights else: + # 否则 for parameter in self.parameter_tuple: + # 遍历参数 new_shape = [int(x) for x in parameter.shape] + # 获取参数形状 half_delta_weights.append((new_shape, parameter.dtype, "adasum_delta_weight." + parameter.name)) left_delta_weights = [] right_delta_weights = [] delta_weights_divisibility = () for shape, dtype, name in half_delta_weights: + # 创建一个新的shape,用来存储左右边界 left_shape = copy.deepcopy(shape) right_shape = copy.deepcopy(shape) + # 初始化divisibility_flag为False divisibility_flag = False + # 遍历shape,如果当前值大于1,则将当前值除以2,并将除以2后的值存入left_shape,并将当前值减去除以2后的值存入right_shape for i, value in enumerate(shape): if value > 1: left_shape[i] = int(value // 2) right_shape[i] = value - int(value // 2) divisibility_flag = True break + # 将左右边界shape和dtype和name添加到left_delta_weights和right_delta_weights中 left_delta_weights.append((left_shape, dtype, name)) right_delta_weights.append((right_shape, dtype, name)) + # 将divisibility_flag赋值给delta_weights_divisibility delta_weights_divisibility += (divisibility_flag,) + # 返回left_delta_weights和right_delta_weights和delta_weights_divisibility return left_delta_weights, right_delta_weights, delta_weights_divisibility class _AdaSumByGrad(_AdaSum): + # Adaptive Summation (AdaSum)算法的实现,根据梯度计算。 """Apply adasum by gradients""" def construct(self, grads): + # 初始化前向传播的梯度 forward_grads = [grads] + # 循环计算梯度 for i in range(self.calc_times): + # 计算前向传播的梯度 process_weights = self.hyper_map(F.partial(_adasum_opt_forward, self.send_node[i]), self.allreduce_list[i], - self.parameter_divisibility_list[i], self.allreduce_node_num_list[i], - self.send_list_forward[i], self.recv_list_forward[i], forward_grads[-1]) + self.parameter_divisibility_list[i], self.allreduce_node_num_list[i], + self.send_list_forward[i], self.recv_list_forward[i], forward_grads[-1]) + # 将计算的梯度添加到前向传播的梯度列表中 forward_grads.append(process_weights) + # 循环计算回滚梯度 for i in range(self.calc_times): + # 计算回滚梯度的梯度 j = self.calc_times - i - 1 process_weights = self.hyper_map(F.partial(_adasum_opt_rollback, self.send_node[j]), - self.parameter_divisibility_list[j], forward_grads[j + 1], - self.send_list_rollback[j], self.recv_list_rollback[j]) + self.parameter_divisibility_list[j], forward_grads[j + 1], + self.send_list_rollback[j], self.recv_list_rollback[j]) + # 将计算的梯度添加到回滚梯度的梯度列表中 forward_grads[j] = process_weights + # 计算更新梯度 update_grads = self.hyper_map(F.partial(_reshape_grads), grads, forward_grads[0], - self.update_reshape_list) + self.update_reshape_list) + # 返回更新梯度 return update_grads + +# 定义_get_delta_weight函数,用于获取delta的权重 _get_delta_weight = C.MultitypeFuncGraph("_get_delta_weight") +# 定义_save_weight函数,用于保存权重 _save_weight = C.MultitypeFuncGraph("_save_weight") +# 定义scale_mul函数,用于乘以delta的权重 scale_mul = P.Mul().add_prim_attr("keep_alive", True) +# 定义_clone_weight函数,用于克隆权重 _clone_weight = C.MultitypeFuncGraph("_clone_weight") @_get_delta_weight.register("Tensor", "Tensor") def _get_delta_weight_process(new_parameter, old_parameter): + ''' + 计算参数更新的梯度 + :param new_parameter: 新的参数 + :param old_parameter: 旧的参数 + :return: 梯度 + ''' delta_w = old_parameter - new_parameter return delta_w @_save_weight.register("Tensor", "Tensor") def _save_weight_process(new_parameter, old_parameter): + ''' + 保存参数的处理函数 + :param new_parameter: 新参数 + :param old_parameter: 之前的参数 + :return: + ''' return P.Assign()(new_parameter, old_parameter) @_clone_weight.register("Tensor", "Tensor") def _clone_weight_process(scale, weight): + ''' + 给定一个缩放因子和权重,返回缩放后的权重 + :param scale: 缩放因子 + :param weight: 权重 + :return: 缩放后的权重 + ''' return scale_mul(weight, scale) def _parallel_check(): + # 检查自动并行信息 """Parallel infos checking""" + # 如果自动并行模式为stand_alone,抛出异常 if context.get_auto_parallel_context("parallel_mode") == "stand_alone": raise RuntimeError("Stand alone mode is not supported to apply adasum.") + # 如果自动并行模式为data_parallel或hybrid_parallel,警告 if context.get_auto_parallel_context("parallel_mode") in ["data_parallel", "hybrid_parallel"]: logger.warning("For data parallel mode or hybrid parallel mode, " "it is recommended to using mindspore.boost to enable adasum.") + # 如果自动并行模式为enable_parallel_optimizer,抛出异常 if context.get_auto_parallel_context("enable_parallel_optimizer"): raise RuntimeError("Currently, the optimizer shard is not supported with applying adasum.") + # 如果自动并行模式为pipeline_stages大于1,抛出异常 if context.get_auto_parallel_context("pipeline_stages") > 1: raise RuntimeError("Currently, the pipeline parallel is not supported with applying adasum.") + # 获取每个stage的设备数量 stage_device_num = _get_stage_device_num() - if stage_device_num < 16 or (stage_device_num & (stage_device_num - 1) != 0): + # 如果stage的设备数量小于16或者stage的设备数量与stage的设备数量的最低位不同,抛出异常 + if stage_device_num < 16 or (stage_device_num & (stage_device_num - 1)!= 0): raise RuntimeError("The device_num should be at least 16 and should be the power of 2 when applying adasum.") class AdaSumByGradWrapCell(Cell): + # Adaptive Summation (AdaSum)算法的实现,根据梯度计算,且用于自动并行模式。 r""" Enable the adasum in "auto_parallel/semi_auto_parallel" mode. The implementation of the Adaptive Summation (AdaSum) algorithm is calculated by gradients. @@ -446,26 +642,51 @@ class AdaSumByGradWrapCell(Cell): >>> model = Model(net, loss_fn=loss, optimizer=optim, metrics=None) """ def __init__(self, optimizer): + ''' + 初始化AdaSumByGradWrapCell类 + :param optimizer: 优化器 + :return: None + ''' super(AdaSumByGradWrapCell, self).__init__(auto_prefix=False) + # 定义设备数量 _device_number = 8 + # 检查设备数量 _parallel_check() + # 将优化器赋值给变量 self.optimizer = optimizer + # 检查优化器类型 validator.check_value_type('optimizer', optimizer, (nn.Optimizer,)) + # 将参数赋值给变量 self.parameters = optimizer.parameters + # 定义网络映射 self.hyper_map = C.HyperMap() + # 获取网络设备数量 group_number = _get_stage_device_num() // _device_number + # 定义参数克隆 self.grad_clone = ParameterTuple(self.parameters) + # 定义AdaSum self.adasum = _AdaSumByGrad(_get_global_rank(), _device_number, group_number, self.grad_clone) + # 定义同步Tensor self.sync_tensor = Parameter(Tensor(0, dtype=mstype.int32)) def construct(self, grads): + ''' + 构建优化器 + :param grads: 优化器梯度 + :return: + ''' + # 调用adasum函数求和 adasum_res = self.adasum(grads) + # 调用sync_tensor函数同步 sync_tensor = F.depend(self.sync_tensor, adasum_res) + # 调用AllReduce函数并发 sync_flag = P.AllReduce()(sync_tensor) + # 调用optimizer函数优化 return F.depend(self.optimizer(adasum_res), sync_flag) class AdaSumByDeltaWeightWrapCell(Cell): + # Adaptive Summation (AdaSum)算法的实现,根据更新前后的参数差计算。 r""" Enable the adasum in "auto_parallel/semi_auto_parallel" mode. The implementation of the Adaptive Summation (AdaSum) algorithm is calculated based on the difference of weights @@ -513,27 +734,49 @@ class AdaSumByDeltaWeightWrapCell(Cell): >>> model = Model(net, loss_fn=loss, optimizer=optim, metrics=None) """ def __init__(self, optimizer): + # 初始化类 super(AdaSumByDeltaWeightWrapCell, self).__init__(auto_prefix=False) + # 检查优化器是否为nn.Optimizer类型 _parallel_check() + # 将优化器赋值给变量 self.optimizer = optimizer + # 检查优化器是否为nn.Optimizer类型 validator.check_value_type('optimizer', optimizer, (nn.Optimizer,)) + # 将参数赋值给变量 self.parameters = optimizer.parameters + # 初始化网络 self.hyper_map = C.HyperMap() + # 获取设备数量 _device_number = 8 + # 获取网络分块数量 group_number = _get_stage_device_num() // _device_number + # 初始化参数克隆 self.grad_clone = ParameterTuple(self.parameters) + # 初始化AdaSum self.adasum = _AdaSum(_get_global_rank(), _device_number, group_number, self.grad_clone) + # 初始化同步变量 self.sync_tensor = Parameter(Tensor(0, dtype=mstype.int32)) + # 初始化缩放因子 self.scale = Tensor(1.0, dtype=mstype.float32) def construct(self, grads): + # 将梯度复制到参数上 grad_clone = self.hyper_map(F.partial(_clone_weight, self.scale), self.parameters) + # 将复制的梯度添加到优化器中 grads = F.depend(grads, grad_clone) + # 使用优化器更新参数 opt_result = self.optimizer(grads) + # 使用参数更新后的梯度更新参数 parameters = F.depend(self.parameters, opt_result) + # 计算参数的梯度 delta_w = self.hyper_map(F.partial(_get_delta_weight), parameters, grad_clone) + # 使用AdaSum更新参数 adasum_res = self.adasum(delta_w, parameters, grad_clone) + # 使用同步计算 sync_tensor = F.depend(self.sync_tensor, adasum_res) + # 使用AllReduce更新参数 sync_flag = P.AllReduce()(sync_tensor) + # 使用参数更新后的参数 updated_weights = F.depend(parameters, sync_flag) + # 返回更新后的参数 return updated_weights diff --git a/mindspore/python/mindspore/nn/optim/asgd.py b/mindspore/python/mindspore/nn/optim/asgd.py index ac20f3d35ad..d7bf1aff906 100755 --- a/mindspore/python/mindspore/nn/optim/asgd.py +++ b/mindspore/python/mindspore/nn/optim/asgd.py @@ -13,6 +13,10 @@ # limitations under the License. # ============================================================================ """asgd""" +# 随机平均梯度下降(ASGD)算法的实现。ASGD也成为SAG,均表示随机平均梯度下降(Averaged Stochastic Gradient Descent),简单地说ASGD就是用空间换时间的一种SGD +# 首先从mindspore.ops中导入了一些操作类,如operations as P用于创建一些操作,Parameter用于表示参数变量,Tensor用于表示张量变量。 +# 接着,导入了一些其他模块,如mindspore.common.dtype用于表示数据类型,mindspore.common.tensor用于表示张量。 +# 最后,从mindspore._checkparam模块中导入了一个Validator类,用于验证参数是否符合要求。 from mindspore.ops import operations as P from mindspore.common.parameter import Parameter from mindspore.common.tensor import Tensor @@ -147,22 +151,38 @@ class ASGD(Optimizer): @opt_init_args_register def __init__(self, params, learning_rate=0.1, lambd=1e-4, alpha=0.75, t0=1e6, weight_decay=0.): + ''' + 初始化ASGD算法,包括设置学习率、权重衰减等参数,以及初始化移动平均和指数加权移动平均等变量。 + :param params: 参数 + :param learning_rate: 学习率 + :param lambd: 损失函数 + :param alpha: 平滑系数 + :param t0: 开始时间的点 + :param weight_decay: 权重衰减 + ''' super(ASGD, self).__init__(learning_rate, params, weight_decay) + # 检查lambd是否为浮点型 validator.check_value_type("lambd", lambd, [float], self.cls_name) + # 检查alpha是否为浮点型 validator.check_value_type("alpha", alpha, [float], self.cls_name) + # 检查t0是否为浮点型 validator.check_value_type("t0", t0, [float], self.cls_name) - + # 初始化参数 self.lambd = lambd self.alpha = alpha self.t0 = Tensor([t0], dtype=mstype.float32) mu, eta = [], [] for param in self.parameters: + # 创建mu和eta变量并指定其矩阵类型 mu.append(Parameter(Tensor(1., dtype=mstype.float32), name='%s%s' % ("mu_", param.name))) eta.append(Parameter(Tensor(0., dtype=mstype.float32), name='%s%s' % ("eta_", param.name))) self.lens = len(self.parameters) + # 将一个包含多个参数的列表mu转换为一个ParameterTuple对象。 self.mu = mindspore.ParameterTuple(mu) + # 将一个包含多个参数的列表eta转换为一个ParameterTuple对象。 self.eta = mindspore.ParameterTuple(eta) + # ax是一个张量变量,用于存储梯度的指数移动平均参数值。 self.ax = self.parameters.clone(prefix="ax_", init='zeros') self.pow = P.Pow() self.maximum = P.Maximum() @@ -173,33 +193,59 @@ class ASGD(Optimizer): self.squeeze = P.Squeeze() def construct(self, gradients): + # 实现ASGD算法的更新规则。首先对梯度进行权重衰减、梯度 centralization 处理,然后将梯度缩放以适应不同的学习率,最后更新参数、移动平均和指数加权移动平均等变量。 + # 对梯度进行权重衰减 gradients = self.decay_weight(gradients) + # 将梯度中心化处理 gradients = self.gradients_centralization(gradients) + # 将梯度缩放 gradients = self.scale_grad(gradients) + # 获取学习率 lrs = self.get_lr() + # 是否成功 success = True + # 遍历梯度 for index, (grad, param, mu, eta, ax) in enumerate(zip(gradients, self.parameters, self.mu, self.eta, self.ax)): + # 获取学习率 lr = lrs[index] if self.is_group_lr else lrs + # 将学习率平均 lr = self.squeeze(lr) + # 如果当前步数为1,则将学习率设置为原来的学习率 if self.squeeze(self.global_step) == 1: self.assign(eta, lr) + # 获取参数 param_fp32 = self.cast(param, mstype.float32) + # 获取梯度 gradient_fp32 = self.cast(grad, mstype.float32) + # 获取平均参数值 ax_fp32 = self.cast(ax, mstype.float32) + # 将参数乘以1-lambd*eta,并与梯度相加(函数算法) param_fp32 = param_fp32 * (1. - self.lambd * eta) - eta * gradient_fp32 + # 将参数赋值给参数 self.assign(param, self.cast(param_fp32, param.dtype)) - if mu != 1: + # 如果mu不等于1,则将参数乘以mu,并与参数相加 + if mu!= 1: self.assignadd(ax, self.cast((param_fp32 - ax_fp32) * mu, ax.dtype)) else: + # 否则,将参数赋值给参数 self.assign(ax, param) + # 1.将学习率除以(1.0 + (self.lambd * lr * self.cast(self.squeeze(self.global_step), mstype.float32))^self.alpha),得到一个计算公式。 + # 2.将计算公式赋值给变量eta。 self.assign(eta, lr / (self.pow((1. + (self.lambd * lr * self.cast( self.squeeze(self.global_step), mstype.float32))), self.alpha))) + # 1.将全局步数self.squeeze(self.global_step)转换为浮点数类型,并命名为step_float。 + # 2.将step_float与1.0进行比较,得到一个布尔值。 + # 3.将布尔值转换为浮点数类型,并命名为compare_float。 + # 4.使用self.maximum()函数计算compare_float与1.0的最大值,并命名为max_float。 + # 5.将max_float除以step_float,得到一个计算公式。 + # 6.将计算公式赋值给变量mu。 self.assign(mu, 1. / self.squeeze(self.maximum(1., self.cast( self.squeeze(self.global_step), mstype.float32) - self.t0))) + # 返回成功消息 return success diff --git a/mindspore/python/mindspore/nn/optim/ftrl.py b/mindspore/python/mindspore/nn/optim/ftrl.py index f9283c2aa0e..942b31065c3 100644 --- a/mindspore/python/mindspore/nn/optim/ftrl.py +++ b/mindspore/python/mindspore/nn/optim/ftrl.py @@ -1,4 +1,4 @@ -# Copyright 2020-2021 Huawei Technologies Co., Ltd +# Copyright 2020 Huawei Technologies Co., Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,14 +13,15 @@ # limitations under the License. # ============================================================================ """FTRL""" +# 本文件为FTRL优化器的构建 from mindspore.ops import functional as F, composite as C, operations as P from mindspore.common import Tensor import mindspore.common.dtype as mstype from mindspore._checkparam import Validator as validator from mindspore._checkparam import Rel -from .optimizer import Optimizer, _apply_decay, _grad_scale -from .optimizer import opt_init_args_register +from.optimizer import Optimizer, _apply_decay, _grad_scale +# 定义_ftrl_opt函数,用于接收四个参数:opt(优化器),spars_opt(模式优化器),push(推送),pull(拉取),l1(L1正则化),l2(L2正则化),lr_power(学习率指数),learning_rate(学习率),linear(线性),gradient(梯度),weight(权重),moment(动量),ps_parameter(参数),cache_enable(是否使用缓存) _ftrl_opt = C.MultitypeFuncGraph("ftrl_opt") @@ -29,14 +30,23 @@ _ftrl_opt = C.MultitypeFuncGraph("ftrl_opt") def _tensor_run_opt_with_sparse(opt, spars_opt, push, pull, l1, l2, lr_power, learning_rate, linear, gradient, weight, moment, ps_parameter, cache_enable): """Apply sparse ftrl optimizer to the weight parameter when the gradient is sparse.""" + # 对为稀疏矩阵的参数权重使用FTRL优化器 + # 判断是否满足条件 success = True + # 获取梯度的索引和值 indices = gradient.indices values = gradient.values + # 如果没有指定ps参数,且不满足cache_enable条件 if ps_parameter and not cache_enable: + # 获取操作的形状 op_shape = P.Shape() + # 获取形状 shapes = (op_shape(weight), op_shape(moment), op_shape(linear), op_shape(values), op_shape(indices)) + # 执行pull操作 success = F.depend(success, pull(push((values, indices), shapes), weight)) + # 如果指定了ps参数,且满足cache_enable条件 else: + # 执行spars_opt操作 success = F.depend(success, spars_opt(weight, moment, linear, values, indices)) return success @@ -45,37 +55,54 @@ def _tensor_run_opt_with_sparse(opt, spars_opt, push, pull, l1, l2, lr_power, le "Tensor", "Tensor", "Tensor", "Bool", "Bool") def _tensor_run_opt(opt, spars_opt, push, pull, l1, l2, lr_power, learning_rate, linear, gradient, weight, moment, ps_parameter, cache_enable): + # 对权重参数应用FTRL优化器 """Apply ftrl optimizer to the weight parameter.""" success = True + # 如果ps_parameter为True,且不支持缓存,则使用pull函数拉取push函数的输出 if ps_parameter and not cache_enable: op_shape = P.Shape() success = F.depend(success, pull(push((gradient, learning_rate, l1, l2, lr_power), (op_shape(weight), op_shape(moment), op_shape(linear))), weight)) + # 否则,使用opt函数计算weight的梯度,并使用push函数推送梯度 else: success = F.depend(success, opt(weight, moment, linear, gradient, learning_rate, l1, l2, lr_power)) + # 返回更新成功标志 return success def _check_param(initial_accum, lr_power, l1, l2, use_locking, prim_name=None): """Check param.""" + """ + 检查参数 + """ + # 检查initial_accum的类型是否为float validator.check_value_type("initial_accum", initial_accum, [float], prim_name) + # 检查initial_accum的值是否在0.0以上 validator.check_number("initial_accum", initial_accum, 0.0, Rel.GE, prim_name) + # 检查lr_power的类型是否为float validator.check_value_type("lr_power", lr_power, [float], prim_name) + # 检查lr_power的值是否在0.0和Rel.LE之间 validator.check_number("lr_power", lr_power, 0.0, Rel.LE, prim_name) + # 检查l1的类型是否为float validator.check_value_type("l1", l1, [float], prim_name) + # 检查l1的值是否在0.0和Rel.GE之间 validator.check_number("l1", l1, 0.0, Rel.GE, prim_name) + # 检查l2的类型是否为float validator.check_value_type("l2", l2, [float], prim_name) + # 检查l2的值是否在0.0和Rel.GE之间 validator.check_number("l2", l2, 0.0, Rel.GE, prim_name) + # 检查use_locking的类型是否为bool validator.check_value_type("use_locking", use_locking, [bool], prim_name) class FTRL(Optimizer): + # FTRL优化器 r""" - Implements the FTRL algorithm. + Implements the FTRL algorithm with ApplyFtrl Operator. FTRL is an online convex optimization algorithm that adaptively chooses its regularization function based on the loss functions. Refer to paper `Adaptive Bound Optimization for Online Convex Optimization @@ -98,47 +125,46 @@ class FTRL(Optimizer): \end{cases}\\ \end{array} - :math:`m` represents accumulators, :math:`g` represents `grads`, :math:`t` represents the current step, - :math:`u` represents the linear coefficient to be updated, :math:`p` represents `lr_power`, :math:`\alpha` - represents `learning_rate`, :math:`\omega` represents `params`. + :math:`m` represents `accum`, :math:`g` represents `grads`, :math:`t` represents updating step, + :math:`u` represents `linear`, :math:`p` represents `lr_power`, :math:`\alpha` represents `learning_rate`, + :math:`\omega` represents `params`. Note: - The sparse strategy is applied while the SparseGatherV2 operator is used for forward network. If the sparse - strategy wants to be executed on the host, set the target to the CPU. - The sparse feature is under continuous development. + When separating parameter groups, the weight decay in each group will be applied on the parameters if the + weight decay is positive. When not separating parameter groups, the `weight_decay` in the API will be applied + on all of the parameters. - If parameters are not grouped, the `weight_decay` in optimizer will be applied on the network parameters without - 'beta' or 'gamma' in their names. Users can group parameters to change the strategy of decaying weight. When - parameters are grouped, each group can set `weight_decay`, if not, the `weight_decay` in optimizer will be - applied. + When separating parameter groups, if you want to centralize the gradient, set grad_centralization to True, + but the gradient centralization can only be applied to the parameters of the convolution layer. + If the parameters of the non convolution layer are set to True, an error will be reported. + + To improve parameter groups performance, the customized order of parameters can be supported. + + The sparse strategy is applied while the SparseGatherV2 operator being used for forward network. + The sparse feature is under continuous development. If the sparse strategy wants to be executed on the host, + set the target to the CPU. Args: - params (Union[list[Parameter], list[dict]]): Must be list of `Parameter` or list of `dict`. When the - `params` is a list of `dict`, the string "params", "weight_decay", "grad_centralization" and "order_params" - are the keys can be parsed. + params (Union[list[Parameter], list[dict]]): When the `params` is a list of `Parameter` which will be updated, + the element in `params` must be class `Parameter`. When the `params` is a list of `dict`, the "params", + "lr", "weight_decay" and "order_params" are the keys can be parsed. - - params: Required. Parameters in current group. The value must be a list of `Parameter`. + - params: Required. The value must be a list of `Parameter`. - - lr: Using different learning rate by grouping parameters is currently not supported. + - lr: Using different learning rate by separating parameters is currently not supported. - weight_decay: Optional. If "weight_decay" in the keys, the value of corresponding weight decay - will be used. If not, the `weight_decay` in the optimizer will be used. It should be noted that weight - decay can be a constant value or a Cell. It is a Cell only when dynamic weight decay is applied. Dynamic - weight decay is similar to dynamic learning rate, users need to customize a weight decay schedule only - with global step as input, and during training, the optimizer calls the instance of WeightDecaySchedule - to get the weight decay value of current step. + will be used. If not, the `weight_decay` in the API will be used. - - grad_centralization: Optional. Must be Boolean. If "grad_centralization" is in the keys, the set value - will be used. If not, the `grad_centralization` is False by default. This configuration only works on the - convolution layer. + - order_params: Optional. If "order_params" in the keys, the value must be the order of parameters and + the order will be followed in optimizer. There are no other keys in the `dict` and the parameters which + in the value of 'order_params' must be in one of group parameters. - - order_params: Optional. When parameters is grouped, this usually is used to maintain the order of - parameters that appeared in the network to improve performance. The value should be parameters whose - order will be followed in optimizer. - If `order_params` in the keys, other keys will be ignored and the element of 'order_params' must be in - one group of `params`. + - grad_centralization: Optional. The data type of "grad_centralization" is Bool. If "grad_centralization" + is in the keys, the set value will be used. If not, the `grad_centralization` is False by default. + This parameter only works on the convolution layer. - initial_accum (float): The starting value for accumulators `m`, must be zero or positive values. Default: 0.1. + initial_accum (float): The starting value for accumulators, must be zero or positive values. Default: 0.1. learning_rate (float): The learning rate value, must be zero or positive, dynamic learning rate is currently not supported. Default: 0.001. lr_power (float): Learning rate power controls how the learning rate decreases during training, must be less @@ -151,21 +177,15 @@ class FTRL(Optimizer): `FixedLossScaleManager` is set to False, then this value needs to be the same as the `loss_scale` in `FixedLossScaleManager`. Refer to class :class:`mindspore.FixedLossScaleManager` for more details. Default: 1.0. - weight_decay (Union[float, int, Cell]): Weight decay (L2 penalty). Default: 0.0. - - - float: The fixed weight decay value. Must be equal to or greater than 0. - - - int: The fixed weight decay value. Must be equal to or greater than 0. It will be converted to float. - - - Cell: Weight decay is dynamic. During training, the optimizer calls the instance of - the Cell with step as the input to get the weight decay value of current step. + weight_decay (Union[float, int]): Weight decay value to multiply weight, must be zero or positive value. + Default: 0.0. Inputs: - **grads** (tuple[Tensor]) - The gradients of `params` in the optimizer, the shape is the same as the `params` in optimizer. Outputs: - Tuple[Parameter], the updated parameters, the shape is the same as `params`. + tuple[Parameter], the updated parameters, the shape is the same as `params`. Raises: TypeError: If `initial_accum`, `learning_rate`, `lr_power`, `l1`, `l2` or `loss_scale` is not a float. @@ -180,8 +200,6 @@ class FTRL(Optimizer): ``Ascend`` ``GPU`` Examples: - >>> from mindspore import nn, Model - >>> >>> net = Net() >>> #1) All parameters use the same learning rate and weight decay >>> optim = nn.FTRL(params=net.trainable_params()) @@ -195,75 +213,97 @@ class FTRL(Optimizer): >>> optim = nn.FTRL(group_params, learning_rate=0.1, weight_decay=0.0) >>> # The conv_params's parameters will use default learning rate of 0.1 and weight decay of 0.01 and grad >>> # centralization of True. - >>> # The no_conv_params's parameters will use default learning rate of 0.1 will use default weight decay - >>> # of 0.0 and grad centralization of False. + >>> # The no_conv_params's parameters will use default weight decay of 0.0 and grad centralization of False. >>> # The final parameters order in which the optimizer will be followed is the value of 'order_params'. >>> >>> loss = nn.SoftmaxCrossEntropyWithLogits() >>> model = Model(net, loss_fn=loss, optimizer=optim) """ - - @opt_init_args_register def __init__(self, params, initial_accum=0.1, learning_rate=0.001, lr_power=-0.5, l1=0.0, l2=0.0, use_locking=False, loss_scale=1.0, weight_decay=0.0): + # 初始化FTRL类 super(FTRL, self).__init__(learning_rate, params, weight_decay, loss_scale=loss_scale) + # 检查参数 if self.dynamic_lr or self.is_group_lr: - raise ValueError(f"For 'FTRL', dynamic learning rate and group learning rate are currently not supported " - f"in FTRL, they should all be false, but got dynamic learning rate {self.dynamic_lr} and" - f" group learning rate {self.is_group_lr}.") + raise ValueError('Dynamic learning rate or group learning rate is currently not supported.') _check_param(initial_accum, lr_power, l1, l2, use_locking, self.cls_name) + # 初始化参数 self.moments = self.parameters.clone(prefix="moments", init=initial_accum) self.linear = self.parameters.clone(prefix="linear", init='zeros') self.l1 = l1 self.l2 = l2 self.lr = learning_rate self.lr_power = lr_power + # 判断是否是分组 if not self.is_group: + # 如果不是组,则将decay_flags设置为True self.decay_flags = tuple((lambda: True)() for x in self.parameters) + # 初始化优化器 + self.hyper_map = C.HyperMap() self.opt = P.ApplyFtrl(use_locking=use_locking) self.use_locking = use_locking + # 初始化sparse优化器 self.sparse_opt = P.SparseApplyFtrl(learning_rate, l1, l2, lr_power, use_locking=use_locking) + # 初始化sparse_opt self._ps_pull = P.Pull() + # 初始化_ps_pull self._ps_push = P.Push("Ftrl", [0, 1, 2]) + # 初始化_ps_push self._ps_push.add_prim_attr("init_accum", initial_accum) + # 向_ps_push中添加init_accum属性 self._ps_push.add_prim_attr("lr", learning_rate) + # 向_ps_push中添加lr属性 self._ps_push.add_prim_attr("l1", l1) + # 向_ps_push中添加l1属性 self._ps_push.add_prim_attr("l2", l2) + # 向_ps_push中添加l2属性 self._ps_push.add_prim_attr("lr_power", lr_power) def construct(self, grads): + ''' + 构建FTRL优化器 + :param grads: 梯度 + :return: 更新后的参数 + ''' params = self.parameters moments = self.moments linear = self.linear + # 对梯度权重衰减 grads = self.decay_weight(grads) + # 对梯度中心化 grads = self.gradients_centralization(grads) + # 对梯度进行缩放 grads = self.scale_grad(grads) + # 对梯度进行去重 grads = self._grad_sparse_indices_deduplicate(grads) + # 更新参数 lr = self.get_lr() + # 将_ftrl_opt函数放入参数 success = self.map_(F.partial(_ftrl_opt, self.opt, self.sparse_opt, self._ps_push, self._ps_pull, - self.l1, self.l2, self.lr_power, lr), - linear, grads, params, moments, self.ps_parameters, self.cache_enable) + self.l1, self.l2, self.lr_power, lr), + linear, grads, params, moments, self.ps_parameters, self.cache_enable) return success @Optimizer.target.setter + # 优化器target设置 def target(self, value): - """ - If the input value is set to "CPU", the parameters will be updated on the host using the Fused - optimizer operation. - """ + """If the input value is set to "CPU", the parameters will be updated on the host using the Fused + optimizer operation.""" + # 如果value不为str类,则抛出TypeError异常 if not isinstance(value, str): - raise TypeError("For 'FTRL', the property 'target' must be string type, " - "but got type {}.".format(type(value))) - + raise TypeError("The value must be str type, but got value type is {}".format(type(value))) + # 如果value值不在三者之中,则抛出ValueError异常 if value not in ('CPU', 'Ascend', 'GPU'): - raise ValueError("For 'FTRL', the property 'target' must be 'CPU', 'Ascend' or 'GPU', " - "but got {}".format(value)) + raise ValueError("The value must be 'CPU', 'Ascend' or 'GPU', but got value {}".format(value)) if value == 'CPU': + # 如果输入值为CPU,则使用FusedSparseFtrl优化器更新参数 self.sparse_opt = P.FusedSparseFtrl(self.lr, self.l1, self.l2, self.lr_power, self.use_locking) self.sparse_opt.add_prim_attr("primitive_target", "CPU") else: + # 如果输入值为GPU,则使用SparseApplyFtrl优化器更新参数 self.sparse_opt = P.SparseApplyFtrl(self.lr, self.l1, self.l2, self.lr_power, self.use_locking) - self._target = value + # 设置目标值 + self._target = value \ No newline at end of file diff --git a/mindspore/python/mindspore/nn/optim/lamb.py b/mindspore/python/mindspore/nn/optim/lamb.py index 918dc8215ea..691a2dff556 100755 --- a/mindspore/python/mindspore/nn/optim/lamb.py +++ b/mindspore/python/mindspore/nn/optim/lamb.py @@ -13,26 +13,29 @@ # limitations under the License. # ============================================================================ """lamb""" +# 优化器LAMB(Layer-wise Adaptive Moments optimizer for Batching training,用于批训练的分层自适应矩优化器)算法的实现。 import numpy as np from mindspore import context from mindspore.common import dtype as mstype +from mindspore.common.initializer import initializer from mindspore.ops import operations as P from mindspore.ops import composite as C from mindspore.ops import functional as F +from mindspore.common.parameter import Parameter from mindspore.common.tensor import Tensor from mindspore._checkparam import Validator as validator from mindspore._checkparam import Rel from .optimizer import Optimizer -from .optimizer import opt_init_args_register -from .. import layer +from.. import layer num_one = Tensor(np.ones([1]), mstype.float32) +# 定义lamb_opt操作 _lamb_opt = C.MultitypeFuncGraph("lamb_opt") -@_lamb_opt.register("Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", +@_lamb_opt.register("Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Number", "Tensor", "Tensor", "Tensor", "Tensor", "Bool", "Bool") def _update_run_op(beta1, beta2, eps, global_step, lr, weight_decay, param, m, v, gradient, decay_flag, optim_filter): """ @@ -43,7 +46,7 @@ def _update_run_op(beta1, beta2, eps, global_step, lr, weight_decay, param, m, v beta2 (Tensor): The exponential decay rate for the 2nd moment estimations. Should be in range (0.0, 1.0). eps (Tensor): Term added to the denominator to improve numerical stability. Should be greater than 0. lr (Tensor): Learning rate. - weight_decay (numbers.Number): Weight decay. Should be equal to or greater than 0. + weight_decay (Number): Weight decay. Should be equal to or greater than 0. global_step (Tensor): Global step. param (Tensor): Parameters. m (Tensor): m value of parameters. @@ -56,65 +59,99 @@ def _update_run_op(beta1, beta2, eps, global_step, lr, weight_decay, param, m, v Tensor, the new value of v after updating. """ if optim_filter: + # 创建一个Mul操作 op_mul = P.Mul() + # 创建一个Sqrt操作 op_sqrt = P.Sqrt() + # 创建一个Rsqrt操作 op_rsqrt = P.Rsqrt() + # 创建一个Square操作 op_square = P.Square() + # 创建一个Cast操作 op_cast = P.Cast() + # 创建一个Reshape操作 op_reshape = P.Reshape() + # 创建一个Shape操作 op_shape = P.Shape() + # 创建一个Pow操作 op_pow = P.Pow() + # 创建一个Norm操作 op_norm = layer.Norm() + # 创建一个Select操作 op_select = P.Select() + # 创建一个Greater操作 op_greater = P.Greater() + # 创建一个Fill操作 op_fill = P.Fill() + # 创建一个DType操作 op_dtype = P.DType() + # 将param转换为float32类型 param_fp32 = op_cast(param, mstype.float32) + # 将m转换为float32类型 m_fp32 = op_cast(m, mstype.float32) + # 将v转换为float32类型 v_fp32 = op_cast(v, mstype.float32) + # 将gradient转换为float32类型 gradient_fp32 = op_cast(gradient, mstype.float32) + # 计算next_m next_m = op_mul(beta1, m_fp32) + op_mul(op_cast(num_one, mstype.float32) - beta1, gradient_fp32) + # 计算next_v next_v = op_mul(beta2, v_fp32) + op_mul(op_cast(num_one, mstype.float32) - beta2, op_square(gradient_fp32)) + # 计算next_mm next_mm = next_m / (op_cast(num_one, mstype.float32) - - op_pow(beta1, op_cast(global_step, mstype.float32))) + - op_pow(beta1, op_cast(global_step + num_one, mstype.float32))) + # 计算next_vv next_vv = next_v / (op_cast(num_one, mstype.float32) - - op_pow(beta2, op_cast(global_step, mstype.float32))) + op_pow(beta2, op_cast(global_step + num_one, mstype.float32))) + # 计算w_norm w_norm = op_norm(param_fp32) + # 计算g_norm g_norm = op_norm(gradient_fp32) + # 计算g_norm_hat g_norm_hat = op_norm(op_mul(next_mm, op_rsqrt(next_vv + eps)) + weight_decay * param_fp32) + # 创建一个zeros变量 zeros = F.zeros_like(w_norm) + # 创建一个ones变量 ones = op_fill(op_dtype(w_norm), op_shape(w_norm), 1.0) + # 计算trust_ratio trust_ratio = op_select( op_greater(w_norm, zeros), op_select(op_greater(g_norm, zeros), w_norm / g_norm_hat, ones), ones) + # 将trust_ratio转换为float32类型 tens = op_fill(op_dtype(trust_ratio), op_shape(trust_ratio), 10.0) + # 将trust_ratio限制在zeros和tens之间 trust_ratio = C.clip_by_value(trust_ratio, zeros, tens) + # 计算update update = next_mm / (op_sqrt(next_vv) + eps) if decay_flag: + # 将梯度乘以权重衰减 update = update + op_mul(weight_decay, param_fp32) + # 将梯度乘以学习率 update_with_lr = op_mul(op_mul(trust_ratio, lr), update) + # 将更新后的参数更新到参数列表中 next_param = param_fp32 - op_reshape(update_with_lr, op_shape(param_fp32)) + # 将参数列表中的参数更新到参数列表中 next_param = F.depend(next_param, F.assign(param, op_cast(next_param, F.dtype(param)))) next_param = F.depend(next_param, F.assign(m, op_cast(next_m, F.dtype(m)))) next_param = F.depend(next_param, F.assign(v, op_cast(next_v, F.dtype(v)))) + # 将参数列表中的参数更新到参数列表中 return op_cast(next_param, F.dtype(param)) return gradient _lamb_opt_ascend = C.MultitypeFuncGraph("lamb_opt_ascend") - -@_lamb_opt_ascend.register("Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", +@_lamb_opt_ascend.register("Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Number", "Tensor", "Tensor", "Tensor", "Tensor", "Bool", "Bool") def _update_run_op_ascend(beta1, beta2, eps, global_step, lr, weight_decay, param, m, v, gradient, decay_flag, optim_filter): @@ -126,7 +163,7 @@ def _update_run_op_ascend(beta1, beta2, eps, global_step, lr, weight_decay, para beta2 (Tensor): The exponential decay rate for the 2nd moment estimations. Should be in range (0.0, 1.0). eps (Tensor): Term added to the denominator to improve numerical stability. Should be greater than 0. lr (Tensor): Learning rate. - weight_decay (numbers.Number): Weight decay. Should be equal to or greater than 0. + weight_decay (Number): Weight decay. Should be equal to or greater than 0. global_step (Tensor): Global step. param (Tensor): Parameters. m (Tensor): m value of parameters. @@ -139,158 +176,109 @@ def _update_run_op_ascend(beta1, beta2, eps, global_step, lr, weight_decay, para Tensor, the new value of v after updating. """ if optim_filter: + # 定义一个Cast操作符 op_cast = P.Cast() + # 定义一个Norm操作符 op_norm = layer.Norm() + # 定义一个LambApplyOptimizerAssign操作符 op_lamb_apply_optimizer_assign = P.LambApplyOptimizerAssign() + # 定义一个LambApplyWeightAssign操作符 op_lamb_apply_weight_assign = P.LambApplyWeightAssign() + # 将参数转换为float32类型 param_fp32 = op_cast(param, mstype.float32) + # 将梯度转换为float32类型 gradient_fp32 = op_cast(gradient, mstype.float32) - new_global_step = op_cast(global_step, mstype.float32) + # 将全局步数转换为float32类型 + new_global_step = op_cast(global_step + num_one, mstype.float32) + # 将权重衰减标志转换为float32类型 weight_decay_flag = op_cast(decay_flag, mstype.float32) + # 将梯度更新,使用LambApplyOptimizerAssign操作符 update, _, _ = op_lamb_apply_optimizer_assign(gradient_fp32, v, m, param_fp32, - beta1, 1.0 - beta1, beta2, 1.0 - beta2, eps, - new_global_step, weight_decay_flag, weight_decay) + beta1, 1.0 - beta1, beta2, 1.0 - beta2, eps, + new_global_step, weight_decay_flag, weight_decay) + # 将参数梯度转换为float32类型 w_norm = op_norm(param_fp32) + # 将梯度更新转换为float32类型 g_norm = op_norm(update) + # 将梯度更新,使用LambApplyWeightAssign操作符 update = F.depend(update, op_lamb_apply_weight_assign(w_norm, g_norm, lr, update, param)) + # 返回梯度更新 return update return gradient def _check_param_value(beta1, beta2, eps, prim_name): + '''检查参数值的类型和范围''' validator.check_value_type("beta1", beta1, [float], prim_name) + # 检查beta1的类型是否为float,是否在0.0和1.0之间,是否为INC_NEITHER,检查beta1是否在0.0和1.0之间 validator.check_value_type("beta2", beta2, [float], prim_name) + # 检查beta2的类型是否为float,是否在0.0和1.0之间,是否为INC_NEITHER,检查beta2是否在0.0和1.0之间 validator.check_value_type("eps", eps, [float], prim_name) + # 检查eps的类型是否为float,是否在0.0和1.0之间,是否为INC_NEITHER,检查eps是否在0.0和1.0之间 validator.check_float_range(beta1, 0.0, 1.0, Rel.INC_NEITHER, "beta1", prim_name) + # 检查beta1是否在0.0和1.0之间 validator.check_float_range(beta2, 0.0, 1.0, Rel.INC_NEITHER, "beta2", prim_name) + # 检查beta2是否在0.0和1.0之间 validator.check_positive_float(eps, "eps", prim_name) class Lamb(Optimizer): - r""" - Implements the Lamb(Layer-wise Adaptive Moments optimizer for Batching training) algorithm. + # 优化器LAMB(Layer-wise Adaptive Moments optimizer for Batching training,用于批训练的分层自适应矩优化器)算法的实现。 + """ + Lamb Dynamic Learning Rate. - LAMB is an optimization algorithm employing a layerwise adaptive large batch optimization technique. - Refer to the paper `LARGE BATCH OPTIMIZATION FOR DEEP LEARNING: TRAINING BERT IN 76 + LAMB is an optimization algorithm employing a layerwise adaptive large batch + optimization technique. Refer to the paper `LARGE BATCH OPTIMIZATION FOR DEEP LEARNING: TRAINING BERT IN 76 MINUTES `_. - The LAMB optimizer aims to increase the training batch size without reducing the accuracy, - and it supports adaptive element-by-element update and accurate layered correction. - - The updating of parameters follows: - - .. math:: - \begin{array}{l} - &\newline - &\hline \\ - &\textbf{Parameters}: \: 1^{\text {st }}\text {moment vector} \: m , \: 2^{\text {nd}} \: - \text{moment vector} \: v , \\ - &\hspace{5mm}\text{learning rate } \left\{ \gamma_{t}\right\}_{t=1}^{T} , \: \text - {exponential decay rates for the moment estimates} \: \beta_{1} \: \beta_{2} , \\ - &\hspace{5mm}\text{scaling function } \phi \\ - &\textbf{Init}: \boldsymbol{m}_{0} \leftarrow 0, \: \boldsymbol{v}_{0} \leftarrow 0 \\[-1.ex] - &\newline - &\hline \\ - &\textbf{for} \text { t=1 to T } \textbf{do} \\ - &\hspace{5mm}\text{Draw b samples } S_{t} \text{ from } \mathbb{P} \text{ . } \\ - &\hspace{5mm}\text{Compute } g_{t}=\frac{1}{\left|\mathcal{S}_{t}\right|} \sum_{s_{t} \in \mathcal{S}_{t}} - \nabla \ell\left(x_{t}, s_{t}\right) . \\ - &\hspace{5mm}\boldsymbol{m}_{t} \leftarrow \beta_{1} \boldsymbol{m}_{t-1}+\left(1-\beta_{1}\right) - \boldsymbol{g}_{t} \\ - &\hspace{5mm}\boldsymbol{v}_{t} \leftarrow \beta_{2} \boldsymbol{v}_{t-1}+\left(1-\beta_{2}\right) - \boldsymbol{g}_{t}^{2} \\ - &\hspace{5mm}\hat{\boldsymbol{m}}_{t} \leftarrow \boldsymbol{m}_{t} /\left(1-\beta_{1}^{t}\right) \\ - &\hspace{5mm}\hat{\boldsymbol{v}}_{t} \leftarrow \boldsymbol{v}_{t} /\left(1-\beta_{2}^{t}\right) \\ - &\hspace{5mm}\text{Compute ratio } \boldsymbol{r}_{t}=\hat{\boldsymbol{m}}_{t} - /(\sqrt{\hat{\boldsymbol{v}}_{t}}+\epsilon) \\ - &\hspace{5mm}\boldsymbol{w}_{t+1}^{(i)}=\boldsymbol{w}_{t}^{(i)}- \gamma_{t} - \frac{\boldsymbol{\phi}\left(\left\|\boldsymbol{w}_{t}^{(i)}\right\|\right)} - {\left\|\boldsymbol{w}_{t}^{(i)}+\lambda \boldsymbol{w}_{t}^{(i)}\right\|}\left(\boldsymbol{r}_{t}^{(i)}+ - \lambda \boldsymbol{w}_{t}^{(i)}\right) \\ - &\textbf{end for} \\[-1.ex] - &\newline - &\hline \\[-1.ex] - &\textbf{return} \: \boldsymbol{w}_{t+1}\\[-1.ex] - &\newline - &\hline \\[-1.ex] - \end{array} - - :math:`m` represents the 1st moment vector `moment1`, :math:`v` represents the 2nd moment vector `moment2`, - :math:`g` represents `gradients`, :math:`\beta_1, \beta_2` represent `beta1` and `beta2`, - :math:`t` represents the current step while :math:`beta_1^t` and :math:`beta_2^t` represent - `beta1_power` and `beta2_power`, :math:`\gamma` represents `learning_rate`, :math:`w` represents `params`, - :math:`\epsilon` represents `eps`, :math:`\lambda` represents `weight_decay`. - Note: - There is usually no connection between a optimizer and mixed precision. But when `FixedLossScaleManager` is used - and `drop_overflow_update` in `FixedLossScaleManager` is set to False, optimizer needs to set the 'loss_scale'. - As this optimizer has no argument of `loss_scale`, so `loss_scale` needs to be processed by other means, refer - document `LossScale `_ to process - `loss_scale` correctly. + When separating parameter groups, the weight decay in each group will be applied on the parameters if the + weight decay is positive. When not separating parameter groups, the `weight_decay` in the API will be applied + on the parameters without 'beta' or 'gamma' in their names if `weight_decay` is positive. - If parameters are not grouped, the `weight_decay` in optimizer will be applied on the network parameters without - 'beta' or 'gamma' in their names. Users can group parameters to change the strategy of decaying weight. When - parameters are grouped, each group can set `weight_decay`, if not, the `weight_decay` in optimizer will be - applied. + When separating parameter groups, if you want to centralize the gradient, set grad_centralization to True, + but the gradient centralization can only be applied to the parameters of the convolution layer. + If the parameters of the non convolution layer are set to True, an error will be reported. + + To improve parameter groups performance, the customized order of parameters can be supported. Args: - params (Union[list[Parameter], list[dict]]): Must be list of `Parameter` or list of `dict`. When the - `params` is a list of `dict`, the string "params", "lr", "weight_decay", "grad_centralization" and - "order_params" are the keys can be parsed. + params (Union[list[Parameter], list[dict]]): When the `params` is a list of `Parameter` which will be updated, + the element in `params` must be class `Parameter`. When the `params` is a list of `dict`, the "params", + "lr", "weight_decay" and "order_params" are the keys can be parsed. - - params: Required. Parameters in current group. The value must be a list of `Parameter`. + - params: Required. The value must be a list of `Parameter`. - lr: Optional. If "lr" in the keys, the value of corresponding learning rate will be used. - If not, the `learning_rate` in optimizer will be used. Fixed and dynamic learning rate are supported. + If not, the `learning_rate` in the API will be used. - weight_decay: Optional. If "weight_decay" in the keys, the value of corresponding weight decay - will be used. If not, the `weight_decay` in the optimizer will be used. It should be noted that weight - decay can be a constant value or a Cell. It is a Cell only when dynamic weight decay is applied. Dynamic - weight decay is similar to dynamic learning rate, users need to customize a weight decay schedule only - with global step as input, and during training, the optimizer calls the instance of WeightDecaySchedule - to get the weight decay value of current step. + will be used. If not, the `weight_decay` in the API will be used. - - grad_centralization: Optional. Must be Boolean. If "grad_centralization" is in the keys, the set value - will be used. If not, the `grad_centralization` is False by default. This configuration only works on the - convolution layer. + - order_params: Optional. If "order_params" in the keys, the value must be the order of parameters and + the order will be followed in optimizer. There are no other keys in the `dict` and the parameters which + in the value of 'order_params' must be in one of group parameters. - - order_params: Optional. When parameters is grouped, this usually is used to maintain the order of - parameters that appeared in the network to improve performance. The value should be parameters whose - order will be followed in optimizer. - If `order_params` in the keys, other keys will be ignored and the element of 'order_params' must be in - one group of `params`. - - learning_rate (Union[float, int, Tensor, Iterable, LearningRateSchedule]): - - - float: The fixed learning rate value. Must be equal to or greater than 0. - - - int: The fixed learning rate value. Must be equal to or greater than 0. It will be converted to float. - - - Tensor: Its value should be a scalar or a 1-D vector. For scalar, fixed learning rate will be applied. - For vector, learning rate is dynamic, then the i-th step will take the i-th value as the learning rate. - - - Iterable: Learning rate is dynamic. The i-th step will take the i-th value as the learning rate. - - - LearningRateSchedule: Learning rate is dynamic. During training, the optimizer calls the instance of - LearningRateSchedule with step as the input to get the learning rate of current step. + - grad_centralization: Optional. The data type of "grad_centralization" is Bool. If "grad_centralization" + is in the keys, the set value will be used. If not, the `grad_centralization` is False by default. + This parameter only works on the convolution layer. + learning_rate (Union[float, Tensor, Iterable, LearningRateSchedule]): A value or a graph for the learning rate. + When the learning_rate is an Iterable or a Tensor in a 1D dimension, use dynamic learning rate, then + the i-th step will take the i-th value as the learning rate. When the learning_rate is LearningRateSchedule, + use dynamic learning rate, the i-th learning rate will be calculated during the process of training + according to the formula of LearningRateSchedule. When the learning_rate is a float or a Tensor in a zero + dimension, use fixed learning rate. Other cases are not supported. The float learning rate must be + equal to or greater than 0. If the type of `learning_rate` is int, it will be converted to float. beta1 (float): The exponential decay rate for the 1st moment estimations. Default: 0.9. Should be in range (0.0, 1.0). beta2 (float): The exponential decay rate for the 2nd moment estimations. Default: 0.999. Should be in range (0.0, 1.0). eps (float): Term added to the denominator to improve numerical stability. Default: 1e-6. Should be greater than 0. - - weight_decay (Union[float, int, Cell]): Weight decay (L2 penalty). Default: 0.0. - - - float: The fixed weight decay value. Must be equal to or greater than 0. - - - int: The fixed weight decay value. Must be equal to or greater than 0. It will be converted to float. - - - Cell: Weight decay is dynamic. During training, the optimizer calls the instance of - the Cell with step as the input to get the weight decay value of current step. + weight_decay (float): Weight decay (L2 penalty). Default: 0.0. Should be equal to or greater than 0. Inputs: - **gradients** (tuple[Tensor]) - The gradients of `params`, the shape is the same as `params`. @@ -311,9 +299,6 @@ class Lamb(Optimizer): ``Ascend`` ``GPU`` Examples: - >>> from mindspore import nn, Model - >>> from mindspore.nn import learning_rate_schedule - >>> >>> net = Net() >>> #1) All parameters use the same learning rate and weight decay >>> optim = nn.Lamb(params=net.trainable_params(), learning_rate=0.1) @@ -336,45 +321,75 @@ class Lamb(Optimizer): >>> loss = nn.SoftmaxCrossEntropyWithLogits() >>> model = Model(net, loss_fn=loss, optimizer=optim) """ - _support_parallel_optimizer = True - @opt_init_args_register def __init__(self, params, learning_rate, beta1=0.9, beta2=0.999, eps=1e-6, weight_decay=0.0): + # 初始化Lamb类 super(Lamb, self).__init__(learning_rate, params, weight_decay) + # 检查参数值 _check_param_value(beta1, beta2, eps, self.cls_name) # turn them to scalar when me support scalar/tensor mix operations + # 将参数转换为tensor self.beta1 = Tensor(np.array([beta1]).astype(np.float32)) self.beta2 = Tensor(np.array([beta2]).astype(np.float32)) self.eps = Tensor(np.array([eps]).astype(np.float32)) self.params = self.parameters + # 初始化moments1和moments2参数 self.moments1 = self.params.clone(prefix="lamb_m", init='zeros') self.moments2 = self.params.clone(prefix="lamb_v", init='zeros') + + # 如果不支持动态学习率,则将全局步骤设置为0 + if not self.dynamic_lr: + self.global_step = Parameter(initializer(0, [1]), name='global_step') + self.assignadd = P.AssignAdd() + # 获取当前设备的排序 + self.hyper_map = C.HyperMap() + # 获取当前上下文的device_target属性,并判断是否为Ascend self.device_ascend = context.get_context("device_target") == "Ascend" def construct(self, gradients): - weight_decay = self.get_weight_decay() + ''' + 构建优化器 + :param gradients: 梯度 + :return: 优化器 + ''' lr = self.get_lr() + # 判断是否使用深度学习 lamb_opt = _lamb_opt_ascend if self.device_ascend else _lamb_opt + # 判断是否使用分组 gradients = self.gradients_centralization(gradients) + # 判断是否使用分组学习 if self.is_group: + # 判断是否使用分组学习率 if self.is_group_lr: + # 使用分组学习并使用分组学习率 optim_result = self.hyper_map(F.partial(lamb_opt, self.beta1, self.beta2, self.eps, - self.global_step), - lr, weight_decay, self.params, self.moments1, self.moments2, - gradients, self.decay_flags, self.optim_filter) + self.global_step), + lr, self.weight_decay, self.params, self.moments1, self.moments2, + gradients, self.decay_flags, self.optim_filter) else: + # 使用分组学习普通学习率 optim_result = self.hyper_map(F.partial(lamb_opt, self.beta1, self.beta2, self.eps, - self.global_step, lr), - weight_decay, self.params, self.moments1, self.moments2, - gradients, self.decay_flags, self.optim_filter) + self.global_step, lr), + self.weight_decay, self.params, self.moments1, self.moments2, + gradients, self.decay_flags, self.optim_filter) + # 判断是否使用分组学习 else: + # 使用普通学习 optim_result = self.hyper_map(F.partial(lamb_opt, self.beta1, self.beta2, self.eps, - self.global_step, lr, weight_decay), - self.params, self.moments1, self.moments2, gradients, - self.decay_flags, self.optim_filter) + self.global_step, lr, self.weight_decay), + self.params, self.moments1, self.moments2, gradients, + self.decay_flags, self.optim_filter) + # 判断是否使用并行模式 if self.use_parallel: + # 输出结果 optim_result = F.depend(optim_result, self.broadcast_params(optim_result)) - return optim_result + # 判断是否使用动态学习率 + if not self.dynamic_lr: + # 输出结果 + optim_result = F.depend(optim_result, self.assignadd(self.global_step, 1)) + + # 返回优化结果 + return optim_result \ No newline at end of file diff --git a/mindspore/python/mindspore/nn/optim/lars.py b/mindspore/python/mindspore/nn/optim/lars.py index 5132cb83ad1..2cb45a38d61 100755 --- a/mindspore/python/mindspore/nn/optim/lars.py +++ b/mindspore/python/mindspore/nn/optim/lars.py @@ -13,37 +13,59 @@ # limitations under the License. # ============================================================================ """lars optimizer""" +# LARS算法的实现。采用大量的优化技术 +# 用于操作定义的模块 from mindspore.ops import operations as P +# 用于组合操作的模块 from mindspore.ops import composite as C +# 用于定义一些辅助函数的模块 from mindspore.ops import functional as F +# 用于验证参数的模块 from mindspore._checkparam import Validator as validator +# 用于表示张量,参数,数据类型 from mindspore.common import Tensor, Parameter, dtype as mstype +# 用于对梯度进行缩放,优化器的函数 from .optimizer import _grad_scale, Optimizer +# 用于注册优化器初始化时的参数的函数 from .optimizer import opt_init_args_register - +# 创建一个名为lars_opt的多类型函数图。多类型函数图是一种用于表示多类型函数的图结构。 _lars_opt = C.MultitypeFuncGraph("lars_opt") @_lars_opt.register("Function", "Number", "Tensor", "Tensor", "Tensor", "Tensor", "Bool", "Bool") def _tensor_run_opt(lars, loss_scale, learning_rate, weight_decay, gradient, weight, decay_flag, lars_flag): + # 用于对张量weight进行梯度下降更新。 """Apply lars optimizer to the weight parameter.""" + # 如果lars_flag为True,则使用LARS优化器 if lars_flag: + # 计算权重张量的平方和与梯度的平方和,并将结果传入P.SquareSumAll()对象op_reduce_sum。 op_reduce_sum = P.SquareSumAll() w_square_sum, grad_square_sum = op_reduce_sum(weight, gradient) + # 计算学习率与权重衰减系数的乘积,并传入lars()函数。如果应用了权重衰减(decay_flag),则将结果除以loss_scale。 if decay_flag: + # 应用权重衰减 grad_t = lars(weight, gradient, w_square_sum, grad_square_sum, weight_decay / loss_scale, learning_rate) else: + # 初始化num_zero num_zero = 0.0 + # 不应用权重衰减 grad_t = lars(weight, gradient, w_square_sum, grad_square_sum, num_zero, learning_rate) + # 返回LARS后的gradient return grad_t + # 否则,直接返回gradient return gradient def _check_param_value(optimizer, epsilon, coefficient, use_clip, prim_name): + # 用于检查优化器、epsilon、coefficient和use_clip的参数值是否符合要求。 + # 检查optimizer的类型是否符合要求 validator.check_value_type("optimizer", optimizer, Optimizer, prim_name) + # 检查epsilon的类型是否符合要求 validator.check_value_type("epsilon", epsilon, [float], prim_name) + # 检查coefficient的类型是否符合要求 validator.check_value_type("coefficient", coefficient, [float], prim_name) + # 检查use_clip的类型是否符合要求 validator.check_value_type("use_clip", use_clip, [bool], prim_name) @@ -114,77 +136,120 @@ class LARS(Optimizer): @opt_init_args_register def __init__(self, optimizer, epsilon=1e-05, coefficient=0.001, use_clip=False, lars_filter=lambda x: 'LayerNorm' not in x.name and 'bias' not in x.name): + # 调用父类Optimizer的初始化方法,传入0.0作为学习率,并创建一个虚拟参数变量fake_param。 super(LARS, self).__init__(0.0, [Parameter(Tensor(0.0), name="fake_param")]) + # 检查参数值是否合法,包括optimizer、epsilon、coefficient、use_clip和lars_filter。 _check_param_value(optimizer, epsilon, coefficient, use_clip, self.cls_name) + # 将optimizer赋值给变量self.opt。 self.opt = optimizer + # 获取optimizer的动态衰减标志dynamic_decay_flags、动态权重衰减dynamic_weight_decay、权重衰减weight_decay、全局步数global_step和参数变量parameters。 self.dynamic_decay_flags = optimizer.dynamic_decay_flags self.dynamic_weight_decay = optimizer.dynamic_weight_decay self.weight_decay = optimizer.weight_decay self.global_step = optimizer.global_step self.parameters = optimizer.parameters + # 将parameters添加到self._user_parameters中。 self._user_parameters += [param.name for param in self.parameters] + # 判断use_clip是否为True,如果是,则将lars_filter函数应用到每个参数上,并将结果作为self.lars_flag。 self.use_clip = use_clip self.lars_flag = tuple(lars_filter(x) for x in self.parameters) + # 判断optimizer是否为分组优化器,如果是,则将is_group设置为True。 self.is_group = optimizer.is_group + # 创建一个虚拟参数变量fake_lr,并将其赋值给self.learning_rate。 self.learning_rate = Parameter(Tensor(0.0, dtype=mstype.float32), name="fake_lr") + # 获取optimizer的衰减标志decay_flags、递归缩放reciprocal_scale和需要缩放need_scale。 self.decay_flags = optimizer.decay_flags self.reciprocal_scale = optimizer.reciprocal_scale + # 如果need_scale为True,则将reciprocal_scale乘以loss_scale,并将结果赋值给self.scale。 self.need_scale = optimizer.need_scale + # 创建一个P.LARSUpdate对象self.lars,传入epsilon、coefficient和use_clip。 self.lars = P.LARSUpdate(epsilon, coefficient, use_clip) + # 创建一个P.Cast对象self.cast。 self.cast = P.Cast() + # 获取optimizer的损失缩放loss_scale。 self.loss_scale = optimizer.loss_scale + # 根据use_clip参数判断是否使用裁剪。 if use_clip: + # 使用裁剪,将优化器的is_group_lr、dynamic_lr和learning_rate属性赋值给self。 self.is_group_lr = optimizer.is_group_lr self.dynamic_lr = optimizer.dynamic_lr self.origin_learning_rate = optimizer.learning_rate + # 如果同时设置use_clip和is_group为True,则抛出异常。 if self.is_group_lr and self.dynamic_lr: raise ValueError("For 'LARS', if the argument 'use_clip' is set to True, then the dynamic " "learning rate and group learning rate cannot both be true.") + # 根据is_group参数判断是否使用分组学习率。 if self.is_group: + # 如果使用分组学习率,将优化器的dynamic_decay_flags设置为元组,其中所有元素都为False。 optimizer.dynamic_decay_flags = tuple(map(lambda x: False, self.dynamic_decay_flags)) else: + # 否则,将优化器的dynamic_decay_flags设置为False。 optimizer.dynamic_decay_flags = False + # 将优化器的decay_flags、dynamic_weight_decay和reciprocal_scale属性设置为False。 optimizer.decay_flags = tuple(map(lambda x: False, self.decay_flags)) optimizer.dynamic_weight_decay = False optimizer.reciprocal_scale = 1.0 optimizer.exec_weight_decay = False def _get_lr(self): + # 用于获取当前步骤的学习率 """Get the learning rate of current step.""" + # 从self中获取origin_learning_rate属性 lr = self.origin_learning_rate + # 判断是否使用动态学习率 if self.dynamic_lr: + # 如果使用动态学习率,根据is_group_lr参数判断是否为分组学习率 if self.is_group_lr: + # 遍历origin_learning_rate元组,计算每个分组的动态学习率,并将结果作为元组添加到lr中 lr = () for learning_rate in self.origin_learning_rate: + # 获取当前步数的动态学习率 current_dynamic_lr = learning_rate(self.global_step) + # 将动态学习率添加到列表中 lr += (current_dynamic_lr,) + # 否则,直接使用origin_learning_rate作为lr。最后返回lr else: lr = self.origin_learning_rate(self.global_step) return lr def construct(self, gradients): + # 构建LARS优化器的计算图 + # 从self中获取参数属性 params = self.parameters + # 如果使用裁剪 if self.use_clip: + # 获取学习率并计算裁剪后的梯度 lr = self._get_lr() else: + # 否则,直接使用learning_rate作为学习率。 lr = self.learning_rate + # 获取权重衰减 weight_decay = self.get_weight_decay() + # 根据need_scale属性判断是否需要缩放梯度 if self.need_scale: + # 使用hyper_map函数缩放梯度 gradients = self.hyper_map(F.partial(_grad_scale, self.reciprocal_scale), gradients) + # 是否使用分组 if self.is_group: + # 如果是分组学习率 if self.is_group_lr: + # 使用hyper_map函数结合_lars_opt函数进行分组学习率优化 gradients = self.hyper_map(F.partial(_lars_opt, self.lars, self.loss_scale), lr, weight_decay, gradients, params, self.decay_flags, self.lars_flag) else: + # 使用hyper_map函数结合_lars_opt函数进行分组优化 gradients = self.hyper_map(F.partial(_lars_opt, self.lars, self.loss_scale, lr), weight_decay, gradients, params, self.decay_flags, self.lars_flag) else: + # 否则,使用hyper_map函数结合_lars_opt函数进行普通学习率优化 gradients = self.hyper_map(F.partial(_lars_opt, self.lars, self.loss_scale, lr, weight_decay), gradients, params, self.decay_flags, self.lars_flag) + # 运行优化 success = self.opt(gradients) + # 返回结果 return success diff --git a/mindspore/python/mindspore/nn/optim/lazyadam.py b/mindspore/python/mindspore/nn/optim/lazyadam.py index c8fa7f4870d..69ff26eec22 100644 --- a/mindspore/python/mindspore/nn/optim/lazyadam.py +++ b/mindspore/python/mindspore/nn/optim/lazyadam.py @@ -13,6 +13,11 @@ # limitations under the License. # ============================================================================ """lazy adam""" +# 首先,从MindSpore的common模块中导入了一些常用的类,如dtype(数据类型)、initializer(初始化器)、operations(操作)、composite(组合操作)等。 +# 接下来,从MindSpore的common模块中导入了一些验证器(Validator)类,用于验证参数的合法性。 +# 接着,定义了一个名为"opt_init_args_register"的函数,用于注册优化器的初始化参数。这个函数的主要作用是让优化器在初始化时传入一些参数,以便在训练过程中使用。 +# 最后,定义了一些Adam优化器的内部方法,如更新参数、计算梯度等。这些方法在实现Adam优化器时会使用到。 +# 本文件包含了一个名为Adam的优化器类,用于实现自适应学习率优化。这个库的主要目的是为了让用户在训练深度学习模型时方便地使用Adam优化器。 from mindspore.common import dtype as mstype from mindspore.common.initializer import initializer from mindspore.ops import operations as P @@ -25,6 +30,7 @@ from mindspore._checkparam import Rel from .optimizer import Optimizer from .optimizer import opt_init_args_register +# 定义了一个名为_lazy_adam_opt的多类型函数图(MultitypeFuncGraph),用于实现Adam优化器。 _lazy_adam_opt = C.MultitypeFuncGraph("lazy_adam_opt") @@ -33,42 +39,61 @@ _lazy_adam_opt = C.MultitypeFuncGraph("lazy_adam_opt") "Bool") def _run_opt_with_sparse(opt, sparse_opt, push, pull, use_locking, use_nesterov, target, beta1_power, beta2_power, beta1, beta2, eps, lr, gradient, params, m, v, ps_parameter, cache_enable): + # 用于对权重参数应用稀疏Adam优化器。 """Apply sparse lazy adam optimizer to the weight parameter when the gradient is sparse.""" success = True indices = gradient.indices values = gradient.values + # 如果没有ps_parameter,且cache_enable为False if ps_parameter and not cache_enable: + # 创建op_shape函数,用于获取参数的形状。 op_shape = P.Shape() + # 获取参数的形状,并将它们存储在一个元组(tuple)中。 shapes = (op_shape(params), op_shape(m), op_shape(v), op_shape(beta1_power), op_shape(beta2_power), op_shape(lr), op_shape(beta1), op_shape(beta2), op_shape(eps), op_shape(values), op_shape(indices)) + # 将参数拉取到push函数中 success = F.depend(success, pull(push((beta1_power, beta2_power, lr, beta1, beta2, eps, values, indices), shapes), params)) + # 检查拉取操作是否成功,返回success return success - + + # 检查目标参数是否为空(target为空表示没有要更新的参数) if not target: + # 将success作为依赖项,应用稀疏Adam优化器(sparse_opt)到参数上。 success = F.depend(success, sparse_opt(params, m, v, beta1_power, beta2_power, lr, beta1, beta2, eps, values, indices)) else: + # 定义一个用于从张量中获取指定索引的值的操作。 op_gather = P.Gather() + # 定义一个用于计算平方根的操作。 op_sqrt = P.Sqrt() + # 定义一个用于对张量中的指定索引的值进行加法操作的函数,并支持同步锁。 scatter_add = P.ScatterAdd(use_locking) + # 定义一个用于对张量中的指定索引的值进行更新操作的函数,并支持同步锁。 scatter_update = P.ScatterUpdate(use_locking) + # 使用op_gather()函数从m和v中获取指定索引的值,并将它们存储在m_slice和v_slice中。 m_slice = op_gather(m, indices, 0) v_slice = op_gather(v, indices, 0) + # 1.计算下一个动量(next_m)和下一个方差(next_v),公式为:next_m = m_slice * beta1 + values * (1 - beta1) + # 2.和next_v = v_slice * beta2 + values * values * (1 - beta2)。 next_m = m_slice * beta1 + values * (1 - beta1) next_v = v_slice * beta2 + values * values * (1 - beta2) + # 3.计算学习率(lr_t),公式为:lr_t = lr * op_sqrt(1 - beta2_power) / (1 - beta1_power)。 lr_t = lr * op_sqrt(1 - beta2_power) / (1 - beta1_power) + # 如果使用了Nesterov动量,计算动量平均值,并更新参数。 if use_nesterov: m_temp = beta1 * next_m + values * (1 - beta1) param_update = m_temp / (op_sqrt(next_v) + eps) + # 否则,直接更新参数。 else: param_update = next_m / (op_sqrt(next_v) + eps) + # 使用P.ScatterAdd()、P.ScatterUpdate()等函数更新模型参数。 success = F.depend(success, scatter_add(params, indices, - lr_t * param_update)) success = F.depend(success, scatter_update(m, indices, next_m)) success = F.depend(success, scatter_update(v, indices, next_v)) @@ -80,31 +105,50 @@ def _run_opt_with_sparse(opt, sparse_opt, push, pull, use_locking, use_nesterov, "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Bool", "Bool") def _run_opt_with_one_number(opt, sparse_opt, push, pull, use_locking, use_nesterov, target, beta1_power, beta2_power, beta1, beta2, eps, lr, gradient, params, moment1, moment2, ps_parameter, cache_enable): + # 该函数用于对权重参数应用 lazy adam 优化器 + # opt: 优化器,用于更新参数。sparse_opt: 稀疏优化器,用于更新稀疏参数。push: 用于将参数和梯度对推入队列的函数。pull: 用于从队列中取出参数和梯度的函数。 + # use_locking: 是否使用锁标志。use_nesterov: 是否使用 Nesterov 动量。target: 目标值。beta1_power: 一阶矩估计的指数移动平均系数。beta2_power: 二阶矩估计的指数移动平均系数。 + # beta1: 一阶矩估计的系数。beta2: 二阶矩估计的系数。eps: 动态学习率调整系数。lr: 学习率。gradient: 梯度。params: 参数。moment1: 一阶矩估计。moment2: 二阶矩估计。 + # ps_parameter: 是否为稀疏参数。cache_enable: 是否启用缓存。 """Apply lazy adam optimizer to the weight parameter using Tensor.""" success = True + # 检查ps_parameter是否为真且cache_enable是否为假 if ps_parameter and not cache_enable: + # 如果是,则使用P.Shape()函数获取参数的形状 op_shape = P.Shape() + # 并使用F.depend()函数将参数和梯度对推入队列。接下来,使用pull()函数从队列中取出参数和梯度 success = F.depend(success, pull(push((beta1_power, beta2_power, lr, beta1, beta2, eps, gradient), (op_shape(params), op_shape(moment1), op_shape(moment2))), params)) else: + # 否则使用opt()函数直接更新参数 success = F.depend(success, opt(params, moment1, moment2, beta1_power, beta2_power, lr, beta1, beta2, eps, gradient)) + # 最后,返回success标志 return success def _check_param_value(beta1, beta2, eps, weight_decay, prim_name): + # 检查输入参数的类型和范围 """Check the type of inputs.""" + # 检查beta1、beta2、eps和weight_decay的类型是否为float validator.check_value_type("beta1", beta1, [float], prim_name) validator.check_value_type("beta2", beta2, [float], prim_name) validator.check_value_type("eps", eps, [float], prim_name) validator.check_value_type("weight_dacay", weight_decay, [float], prim_name) + # 检查beta1的类型是否为float,是否在0.0和1.0之间,是否为Rel.INC_NEITHER,名称为beta1 validator.check_float_range(beta1, 0.0, 1.0, Rel.INC_NEITHER, "beta1", prim_name) + # 检查beta2的类型是否为float,是否在0.0和1.0之间,是否为Rel.INC_NEITHER,名称为beta2 validator.check_float_range(beta2, 0.0, 1.0, Rel.INC_NEITHER, "beta2", prim_name) + # 检查eps的类型是否为float,是否在0.0和1.0之间,是否为Rel.INC_NEITHER,名称为eps validator.check_positive_float(eps, "eps", prim_name) + # 检查weight_decay的类型是否为float,是否在0.0和1.0之间,是否为Rel.INC_NEITHER,名称为weight_decay validator.check_non_negative_float(weight_decay, "weight_decay", prim_name) class LazyAdam(Optimizer): + # 定义了一个名为LazyAdam的类,继承自Optimizer。LazyAdam类实现了 Adaptive Moment Estimation (Adam) 算法, + # 该算法由 Adam: A Method for Stochastic Optimization _ 论文提出。 + # 当梯度为稀疏时,LazyAdam优化器将应用懒汉式 adam 算法进行更新。 r""" Implements the Adaptive Moment Estimation (Adam) algorithm. The Adam algorithm is proposed in `Adam: A Method for Stochastic Optimization `_. @@ -254,57 +298,79 @@ class LazyAdam(Optimizer): @opt_init_args_register def __init__(self, params, learning_rate=1e-3, beta1=0.9, beta2=0.999, eps=1e-8, use_locking=False, use_nesterov=False, weight_decay=0.0, loss_scale=1.0): + # 初始化一些参数,并设置一些相关属性,如学习率、一阶矩估计和二阶矩估计的系数等。同时,代码还检查了输入参数的类型和范围,以确保它们在后续操作中正确使用。 super(LazyAdam, self).__init__(learning_rate, params, weight_decay, loss_scale) + # 检查输入参数的类型和范围 _check_param_value(beta1, beta2, eps, weight_decay, self.cls_name) validator.check_value_type("use_locking", use_locking, [bool], self.cls_name) validator.check_value_type("use_nesterov", use_nesterov, [bool], self.cls_name) + # 初始化beta1、beta2(衰减速率)、eps(添加到分母中,以提高数值稳定性)和use_locking(是否对参数更新加锁保护)等参数,并将它们转换为Tensor类型 self.beta1 = Tensor(beta1, mstype.float32) self.beta2 = Tensor(beta2, mstype.float32) + # 初始化beta1_power和beta2_power为Parameter类型,并设置初始值为1 self.beta1_power = Parameter(initializer(1, [1], mstype.float32), name="beta1_power") self.beta2_power = Parameter(initializer(1, [1], mstype.float32), name="beta2_power") self.eps = Tensor(eps, mstype.float32) self.use_nesterov = use_nesterov self.use_locking = use_locking + # 表示当前优化器正在使用设备(如GPU或CPU) self._is_device = True + # 初始化moment1和moment2为Parameter类型,并设置初始值为0 self.moment1 = self.parameters.clone(prefix="moment1", init='zeros') self.moment2 = self.parameters.clone(prefix="moment2", init='zeros') + # 定义opt和sparse_opt为P.Adam和P.FusedSparseLazyAdam类型,并设置相关属性,如use_locking和use_nesterov(是否使用NAG算法更新梯度) self.opt = P.Adam(use_locking, use_nesterov) self.sparse_opt = P.FusedSparseLazyAdam(use_locking, use_nesterov) self.sparse_opt.add_prim_attr("primitive_target", "CPU") + # 将primitive_target添加到sparse_opt中 self._ps_pull = P.Pull() + # 创建一个Pull操作,拉取操作 self._ps_push = P.Push("Adam", [0, 1, 2]) self._ps_push.add_prim_attr("use_nesterov", use_nesterov) def construct(self, gradients): + # 构建LazyAdam优化器的更新逻辑 + # 对梯度进行权重衰减 gradients = self.decay_weight(gradients) + # 对梯度进行中心化 gradients = self.gradients_centralization(gradients) + # 对梯度进行缩放 gradients = self.scale_grad(gradients) + # 对梯度进行去重 gradients = self._grad_sparse_indices_deduplicate(gradients) + # 获取学习率 lr = self.get_lr() - + # 更新一阶矩估计和二阶矩估计的指数移动平均系数。 + # 将beta1_power乘以beta1 self.beta1_power = self.beta1_power * self.beta1 + # 将beta2_power乘以beta2 self.beta2_power = self.beta2_power * self.beta2 - + # 如果使用分组学习率 if self.is_group_lr: + # 调用map_reverse方法,传入_lazy_adam_opt函数、梯度、参数、一阶矩估计、二阶矩估计、稀疏梯度索引去重工具、缓存启用标志等参数,进行分组学习率优化更新。 success = self.map_reverse(F.partial(_lazy_adam_opt, self.opt, self.sparse_opt, self._ps_push, self._ps_pull, self.use_locking, self.use_nesterov, self._is_device, self.beta1_power, self.beta2_power, self.beta1, self.beta2, self.eps), lr, gradients, self.parameters, self.moment1, self.moment2, self.ps_parameters, self.cache_enable) else: + # 调用map_reverse方法,传入_lazy_adam_opt函数、梯度、参数、一阶矩估计、二阶矩估计、稀疏梯度索引去重工具、缓存启用标志等参数,进行普通更新。 success = self.map_reverse(F.partial(_lazy_adam_opt, self.opt, self.sparse_opt, self._ps_push, self._ps_pull, self.use_locking, self.use_nesterov, self._is_device, self.beta1_power, self.beta2_power, self.beta1, self.beta2, self.eps, lr), gradients, self.parameters, self.moment1, self.moment2, self.ps_parameters, self.cache_enable) + # 返回更新成功与否的布尔值 return success @Optimizer.target.setter def target(self, value): + # 根据输入的value字符串来更新模型的训练目标。 """ If the input value is set to "CPU", the parameters will be updated on the host using the Fused optimizer operation. """ + # 调用_set_base_target方法来设置模型的基本目标。 self._set_base_target(value) diff --git a/mindspore/python/mindspore/nn/optim/momentum.py b/mindspore/python/mindspore/nn/optim/momentum.py index 96bd8722e47..1615ceada7e 100755 --- a/mindspore/python/mindspore/nn/optim/momentum.py +++ b/mindspore/python/mindspore/nn/optim/momentum.py @@ -13,32 +13,47 @@ # limitations under the License. # ============================================================================ """momentum""" +# 该文件的主要目的是为了实现动量优化方法,即使用动量(momentum)来加速梯度下降过程。动量优化方法是一种常用的优化方法,可以提高训练效率。 + +# 首先,从mindspore.ops模块中导入了一些常用的功能,如F(功能函数)、C(组合函数)、P(操作)等。 +# 然后,从mindspore.common.parameter模块中导入了一个Parameter类,用于创建参数。接着,从mindspore.common.tensor模块中导入了一个Tensor类,用于表示张量。 from mindspore.ops import functional as F, composite as C, operations as P from mindspore.common.parameter import Parameter from mindspore.common.tensor import Tensor +# 接下来,定义了一个名为mstype的常量,用于表示MindSpore中的数据类型。然后,从mindspore._checkparam模块中导入了一个Validator类,用于验证参数。 import mindspore.common.dtype as mstype from mindspore._checkparam import Validator +# 最后,从momentum.optimizer模块中导入了一个Optimizer类,用于实现动量优化方法。从momentum.optimizer模块中导入了一个名为opt_init_args_register的函数,用于注册优化器的关键参数。 from .optimizer import Optimizer from .optimizer import opt_init_args_register +# 定义了一个名为_momentum_opt的组合函数图(Composite Function Graph),用于实现动量优化方法 _momentum_opt = C.MultitypeFuncGraph("momentum_opt") @_momentum_opt.register("Function", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Bool", "Bool") def _tensor_run_opt_ext(opt, momentum, learning_rate, gradient, weight, moment, ps_parameter, cache_enable): + # 应用动量优化方法到张量权重参数。momentum:动量系数,用于平滑梯度更新。moment:动量张量,用于存储 previous step 的梯度。 """Apply momentum optimizer to the weight parameter using Tensor.""" + # 首先判断ps_parameter和cache_enable是否为True if ps_parameter and not cache_enable: + # 使用P.Shape()获取learning_rate、gradient和momentum张量的形状。 op_shape = P.Shape() + # 使用P.Pull()和P.Push()创建一个名为ApplyMomentum的操作,并将learning_rate、gradient和momentum作为输入,可以在后续的计算中使用,以确保输入参数的形状符合预期。 _ps_pull = P.Pull() _ps_push = P.Push("ApplyMomentum", []) shapes = (op_shape(learning_rate), op_shape(gradient), op_shape(momentum)) + # 使用F.depend()函数将True作为依赖项,执行完动量优化方法后,返回优化后的权重张量作为计算结果。 success = F.depend(True, _ps_pull(_ps_push((learning_rate, gradient, momentum), shapes), weight)) else: + # 使用opt函数应用动量优化方法,并传入weight、moment、learning_rate、gradient和momentum作为参数 success = F.depend(True, opt(weight, moment, learning_rate, gradient, momentum)) + # 返回优化后的权重张量 return success class Momentum(Optimizer): + # 动量优化方法是一种在训练神经网络时常用的优化方法,它通过在每次迭代时存储上一迭代的梯度,并使用动量系数u来平滑梯度更新,从而提高训练速度。 r""" Implements the Momentum algorithm. @@ -169,28 +184,45 @@ class Momentum(Optimizer): """ @opt_init_args_register def __init__(self, params, learning_rate, momentum, weight_decay=0.0, loss_scale=1.0, use_nesterov=False): + # 初始化Momentum类,参数分别为:参数,学习率,梯度,权重衰减,损失函数缩放因子,是否使用Nesterov动量加速梯度下降 super(Momentum, self).__init__(learning_rate, params, weight_decay, loss_scale) + # 检查momentum是否为float类型,且小于0 Validator.check_value_type("momentum", momentum, [float], self.cls_name) if isinstance(momentum, float) and momentum < 0.0: - raise ValueError("For 'Momentum', the argument 'momentum' should be at least 0.0, " - "but got {}".format(momentum)) + # 如果momentum为float类型且小于0,抛出异常 + raise ValueError("For 'Momentum', the argument'momentum' should be at least 0.0, " + "but got {}".format(momentum)) + # 创建参数momentum,用于存储上一迭代的梯度 self.momentum = Parameter(Tensor(momentum, mstype.float32), name="momentum") + # 将params和moments克隆出来,分别命名为self.params和self.moments self.params = self.parameters + # 检查use_nesterov类型是否为bool,并克隆 self.use_nesterov = Validator.check_bool(use_nesterov) self.moments = self.params.clone(prefix="moments", init='zeros') + # 代码创建一个P.ApplyMomentum操作实例,使用Nesterov动量 self.opt = P.ApplyMomentum(use_nesterov=self.use_nesterov) def construct(self, gradients): + # 实现了动量优化方法。 + # 首先对gradients进行以下操作 params = self.params moments = self.moments + # 计算权重衰减后的梯度。 gradients = self.decay_weight(gradients) + # 对梯度进行 centralization 处理 gradients = self.gradients_centralization(gradients) + # 对梯度进行 scale 处理 gradients = self.scale_grad(gradients) + # 获取学习率 lr = self.get_lr() + # 如果使用了分组学习率 if self.is_group_lr: + # 用分组方法对lr、gradients、params、moments和ps_parameters进行处理,并调用_momentum_opt函数进行动量优化。 success = self.hyper_map_reverse(F.partial(_momentum_opt, self.opt, self.momentum), lr, gradients, params, moments, self.ps_parameters, self.cache_enable) else: + # 用普通方法对lr、gradients、params、moments和ps_parameters进行处理,并调用_momentum_opt函数进行动量优化。 success = self.hyper_map_reverse(F.partial(_momentum_opt, self.opt, self.momentum, lr), gradients, params, moments, self.ps_parameters, self.cache_enable) + # 返回优化后的权重张量。 return success diff --git a/mindspore/python/mindspore/nn/optim/optimizer.py b/mindspore/python/mindspore/nn/optim/optimizer.py index c9cb3a6b9c2..d1db5183ef8 100644 --- a/mindspore/python/mindspore/nn/optim/optimizer.py +++ b/mindspore/python/mindspore/nn/optim/optimizer.py @@ -13,52 +13,89 @@ # limitations under the License. # ============================================================================ """optimizer""" +# 本文件为优化器基类的定义 +# 导入获取对象信息的模块 import inspect +# 导入可迭代类型 from typing import Iterable - +# 导入numpy模块 import numpy as np import mindspore +# 导入ops算子模块 from mindspore.ops import functional as F, composite as C, operations as P +# 导入inner ops算子 from mindspore.ops.operations import _inner_ops as inner +# 导入Cell模块 from mindspore.nn.cell import Cell +# 导入Cell数组模块 from mindspore.nn.layer.container import CellList +# 导入参数模块 from mindspore.common.parameter import Parameter, ParameterTuple +# 导入神经元初始化器 from mindspore.common.initializer import initializer +# 导入矩阵,稀疏矩阵 from mindspore.common.tensor import Tensor, RowTensor +# 导入数据类型 import mindspore.common.dtype as mstype +# 导入检查参数 from mindspore._checkparam import Validator as validator +# 导入日志模块 from mindspore import log as logger +# 导入计算模块 from mindspore.parallel._utils import _get_global_rank, _get_device_num, _get_parallel_mode +# 导入自动并行模块 from mindspore.context import ParallelMode from mindspore import context +# 导入动态学习率模块 from mindspore.nn.learning_rate_schedule import LearningRateSchedule __all__ = ['Optimizer', 'opt_init_args_register'] def opt_init_args_register(fn): + # 注册优化器初始参数 """Register optimizer init args.""" def deco(self, *args, **kwargs): + # 获取函数的参数 bound_args = inspect.signature(fn).bind(self, *args, **kwargs) + # 调整参数 bound_args.apply_defaults() + # 获取参数 arguments = bound_args.arguments + # 删除self参数 arguments.pop('self') + # 判断是否有params参数 if 'params' in arguments.keys(): + # 将params参数设置为init_params setattr(self, 'init_params', dict({"params": arguments['params']})) + # 删除params参数 arguments.pop('params') + # 判断是否有optimizer参数 if 'optimizer' in arguments.keys(): + # 将optimizer参数设置为init_params setattr(self, 'init_params', dict({"params": arguments['optimizer'].init_params["params"]})) + # 删除optimizer参数 arguments.pop('optimizer') + # 判断是否有learning_rate参数 if 'learning_rate' in arguments.keys(): + # 判断learning_rate参数是否为Tensor if isinstance(arguments['learning_rate'], Tensor): + # 将learning_rate参数转换为list arguments['learning_rate'] = arguments['learning_rate'].asnumpy().tolist() + # 判断learning_rate参数是否为Cell if isinstance(arguments['learning_rate'], Cell): + # 将init_learning_rate设置为None setattr(self, 'init_learning_rate', None) + # 否则 else: + # 将init_learning_rate设置为learning_rate参数 setattr(self, 'init_learning_rate', arguments['learning_rate']) + # 删除learning_rate参数 arguments.pop('learning_rate') + # 将init_args设置为参数 setattr(self, 'init_args', arguments) + # 调用函数 fn(self, *args, **kwargs) return deco @@ -135,55 +172,103 @@ class Optimizer(Cell): Supported Platforms: ``Ascend`` ``GPU`` ``CPU`` """ + # 初始不支持并行模式优化器 _support_parallel_optimizer = False def __init__(self, learning_rate, parameters, weight_decay=0.0, loss_scale=1.0): + ''' + 初始化优化器 + :param learning_rate: 学习率 + :param parameters: 参数 + :param weight_decay: 权重衰减 + :param loss_scale: 损失比例 + :return: + ''' super(Optimizer, self).__init__(auto_prefix=False) + # 初始化参数 parameters = self._parameters_base_check(parameters, "parameters") + # 参数排序 self.param_rank = None + # 标记过滤器 self.optim_filter = None + # 若parameters中含有不符合要求的参数则报错TypeError if not all(isinstance(x, Parameter) for x in parameters) and not all(isinstance(x, dict) for x in parameters): raise TypeError("For 'Optimizer', all elements of the argument 'parameters' must be 'Parameter' or 'dict'," " please check the 'parameters'.") + # 如果loss_scale参数是int类型,则将其转换为float类型 if isinstance(loss_scale, int): loss_scale = float(loss_scale) - validator.check_value_type("loss_scale", loss_scale, [float], self.cls_name) + validator.check_value_type("loss_scale", loss_scale, [float], self.cls_name) + # 检查loss_scale的类型是否为float validator.check_positive_float(loss_scale, "loss_scale", self.cls_name) + # 检查loss_scale的大小是否为正数 self.loss_scale = loss_scale + # 设置loss_scale self.dynamic_weight_decay = False + # 设置动态权重衰减 self.grad_centralization = False + # 设置梯度中间 + self._unique = True + # 设置唯一性 self._target = context.get_context("device_target") + # 获取目标 self._use_flattened_params = False + # 设置是否使用紧组参数 self.dynamic_lr = False + # 设置动态学习率 self.assignadd = P.AssignAdd() + # 设置赋值加 self.global_step = Parameter(initializer(0, [1], mindspore.int32), name='global_step') + # 设置全局步数 self.is_group = False + # 设置是否为组 self.is_group_lr = False + # 设置是否为组学习率 self.is_group_params_ordered = False + # 设置是否为组参数顺序 self.use_parallel = False + # 设置是否使用并行 learning_rate = self._preprocess_single_lr(learning_rate) if isinstance(parameters[0], dict): + # 如果参数为字典类型 self.is_group = True + # 标记为分组 self.group_params = [] + # 分组参数 self.group_lr = [] + # 分组学习率 self.group_weight_decay = [] + # 分组权重衰减 self.group_grad_centralization = [] + # 分组梯度中间率 self._init_group_params(parameters, learning_rate, weight_decay, self.grad_centralization) + # 初始化学习率、权重衰减或参数群的梯度中间率 + # 初始化优化属性 self._init_opt_attrs(learning_rate, parameters, weight_decay) def _init_opt_attrs(self, learning_rate, parameters, weight_decay): """initialize optimizer attributions""" + # 将weight_decay转换为CellList weight_decay = self._preprocess_weight_decay(weight_decay) if self.is_group_lr: - self.learning_rate = CellList(self.group_lr, auto_prefix=False) if self.dynamic_lr \ - else ParameterTuple(self.group_lr) + # 如果是分组学习率 + if self.dynamic_lr: + # 如果动态学习率 + self.learning_rate = CellList(self.group_lr, auto_prefix=False) + # 将分组学习率设置为CellList + else: + # 如果不动态学习率 + self.learning_rate =ParameterTuple(self.group_lr) + # 将分组学习率设置为ParameterTuple else: + # 如果不是分组学习率 self.learning_rate = self._build_single_lr(learning_rate, 'learning_rate') + # 如果是分组,则按组合方式将parameters设置为ParameterTuple,并设置decay_filter和dynamic_decay_filter if self.is_group: self.parameters = ParameterTuple(self.group_params) decay_filter = lambda x: isinstance(x, Cell) or x > 0 @@ -194,6 +279,7 @@ class Optimizer(Cell): for x, flag in zip(self.group_weight_decay, self.dynamic_decay_flags)) self.exec_weight_decay = any(self.decay_flags) self.grad_centralization_flags = tuple(self.group_grad_centralization) + # 否则,按一般方式将parameters设置为ParameterTuple,并设置decay_filter else: parameters = self._get_flattened_params(parameters) self.parameters = ParameterTuple(parameters) @@ -204,80 +290,116 @@ class Optimizer(Cell): self.weight_decay = Tensor(weight_decay, mstype.float32) if not self.dynamic_decay_flags else weight_decay # when a parameter has been unique, there is no need do another unique in optimizer. for param in self.parameters: + # 如果param的unique属性为True,则将_unique设置为False if param.unique: self._unique = False break # set user's parameters as local parameters + # 设置user的参数为本地参数 for param in self.parameters: self._user_parameters.append(param.name) + # 过滤出参数名称中是ps的参数 ps_filter = lambda x: x.is_param_ps self.ps_parameters = tuple(ps_filter(x) for x in self.parameters) + # 过滤出参数名称中是cache的参数 cache_filter = lambda x: x.cache_enable self.cache_enable = tuple(cache_filter(x) for x in self.parameters) + # 将loss_scale转换为float32类型 self.reciprocal_scale = Tensor(1.0 / self.loss_scale, mstype.float32) - self.need_scale = self.loss_scale != 1.0 + # 将loss_scale不等于1.0的参数设置为False + self.need_scale = self.loss_scale!= 1.0 + # 将global_step_increase_tensor设置为1 self.global_step_increase_tensor = Tensor(1, mstype.int32) + # 设置参数的长度 self.param_length = len(self.parameters) + # 初始化map self.map_ = C.Map() + # 初始化map_reverse self.map_reverse = C.Map(None, True) + # 初始化hyper_map self.hyper_map = C.HyperMap() + # 初始化hyper_map_reverse self.hyper_map_reverse = C.HyperMap(None, True) + # 调用_use_parallel_optimizer函数 self._use_parallel_optimizer() + # 设置是否开启tuple广播 self.enable_tuple_broaden = True def _get_flattened_params(self, parameters): """Get parameters for each contiguous memory chunks used by input parameters if they are flattened.""" + # 如果是分组,不使用扁平参数并返回参数 if self.is_group: # We don't use flattened parameters when parameters are grouped. return parameters # Check whether parameters are flattened. flattened = Tensor._is_flattened(parameters) # pylint: disable=W0212 + # 如果参数不是扁平的,直接返回参数 if not flattened: # Parameters are not flattened. return parameters # Try to get chunk tensors from flattened parameters. + # 尝试从扁平的参数中获得块张量 chunk_tensors = Tensor._get_flattened_tensors(parameters) # pylint: disable=W0212 + # 如果块张量为空,返回参数 if not chunk_tensors: # Failed to get chunk tensors. logger.warning("Parameters are not properly falttened, fallback to not flattened parameters.") return parameters # Convert chunk tensors to parameters. + # 转换块张量到参数 self._use_flattened_params = True + # 返回扁平参数 return [Parameter._from_tensor(t, name='_chunk_param_' + str(t.dtype)) # pylint: disable=W0212 for t in chunk_tensors] def _use_parallel_optimizer(self): """Indicates whether to use automatic parallelism.""" if context.get_auto_parallel_context("enable_parallel_optimizer"): + # 如果设置了parallel optimizer,并且当前设备的target为 Ascend if _get_parallel_mode() == ParallelMode.DATA_PARALLEL and context.get_context("device_target") == "Ascend": + # 将use_parallel设置为True self.use_parallel = True + # 如果设置了parallel optimizer,并且当前设备的target不为 Ascend elif _get_parallel_mode() == ParallelMode.DATA_PARALLEL \ - and context.get_context("device_target") != "Ascend": + and context.get_context("device_target")!= "Ascend": + # 抛出异常 raise RuntimeError(f'For "Optimizer", parallel optimizer only supports "Ascend" in data parallel mode, ' f'but got {context.get_context("device_target")}.') + # 如果设置了parallel optimizer,并且当前模式为 Standalone 或 Hybrid Parallel elif _get_parallel_mode() in (ParallelMode.STAND_ALONE, ParallelMode.HYBRID_PARALLEL): + # 抛出异常 raise RuntimeError("For 'Optimizer', parallel optimizer is not supported in {}, you should set " - "parallel mode to 'data_parallel', 'semi_auto_parallel' or 'auto_parallel'." + "parallel mode to 'data_parallel','semi_auto_parallel' or 'auto_parallel'." .format(_get_parallel_mode())) if self.use_parallel: + # 如果不支持并行优化器,抛出异常 if not self._support_parallel_optimizer: raise RuntimeError("For 'Optimizer', parallel optimizer shard doest not support " "optimizer {}.".format(self.cls_name)) + # 获取设备数量 self.dev_num = _get_device_num() + # 如果参数数量大于设备数量,抛出异常 if self.dev_num > self.param_length: raise RuntimeError("Parallel optimizer can not be applied when the number of parameters {} is" " less than the number of devices {}".format(self.param_length, self.dev_num)) + # 获取参数组id self.param_rank = self._get_parameter_group_id() + # 判断参数组id是否在参数数量中 self.optim_filter = tuple(map(lambda x: x == _get_global_rank(), self.param_rank)) + # 初始化参数名称 self.param_names = [] + # 遍历参数 for param in self.parameters: + # 将参数名称添加到参数名称列表中 self.param_names.append(param.name) else: + # 如果不使用并行优化器,则将参数组id设置为布尔值,其中元素个数为参数数量 self.optim_filter = (True,) * self.param_length @property def unique(self): + # 该属性表示是否在优化器中进行梯度去重,通常用于稀疏网络。如果优化器的梯度是稀疏的,则设为 True。如果前向稀疏网络已对权重去重,即优化器的梯度不稀疏,则设为 False。未设置时,默认值为 True。 """ Whether to make the gradients unique in optimizer. Generally, it is used in sparse networks. Set to True if the gradients of the optimizer are sparse. Set to False if the forward network has made the parameters unique, @@ -288,14 +410,18 @@ class Optimizer(Cell): @unique.setter def unique(self, value): + # 设置unique的值 """Set the `unique` attribute.""" + # 如果value不是bool类型,抛出TypeError异常 if not isinstance(value, bool): raise TypeError("For 'Optimizer', the property 'unique' must be bool, " "but got {}".format(type(value))) + # 将value赋值给_unique self._unique = value @property def target(self): + # 该属性用于确定参数是在主机还是设备上更新。输入类型 str 只能是 "CPU"、"Ascend "或 "GPU"。 """ The property is used to determine whether the parameter is updated on host or device. The input type is str and can only be 'CPU', 'Ascend' or 'GPU'. @@ -304,6 +430,7 @@ class Optimizer(Cell): @target.setter def target(self, value): + # 如果输入值设置为 "CPU",则将使用融合优化器操作在主机上更新参数。 """ If the input value is set to "CPU", the parameters will be updated on the host using the Fused optimizer operation. @@ -312,6 +439,12 @@ class Optimizer(Cell): @staticmethod def _preprocess_grad_centralization(grad_centralization): + ''' + 预处理梯度中间化 + :param grad_centralization: 梯度中间化 + :return:grad_centralization + ''' + # 若梯度中间化值不为bool值,则报出TypeError if not isinstance(grad_centralization, bool): raise TypeError("For 'Optimizer', the 'gradients_centralization' should be bool type, " "but got {}.".format(type(grad_centralization))) @@ -319,43 +452,55 @@ class Optimizer(Cell): @staticmethod def _parameters_base_check(parameters, param_info): + # 对参数进行基本检查 """Parameters base check.""" + # 如果参数为None,抛出异常 if parameters is None: raise ValueError(f"For 'Optimizer', the argument {param_info} can not be None.") + # 如果参数不是Iterable类型,抛出异常 if not isinstance(parameters, Iterable): raise TypeError(f"For 'Optimizer', the argument {param_info} must be Iterable type, " f"but got {type(parameters)}.") + # 将参数转换为列表 parameters = list(parameters) + # 如果参数为空,抛出异常 if not parameters: raise ValueError(f"For 'Optimizer', the argument {param_info} must not be empty.") + # 返回参数 return parameters def _set_base_target(self, value): + # 如果输入值设置为 "CPU",则将使用融合优化器操作在主机上更新参数。 """ If the input value is set to "CPU", the parameters will be updated on the host using the Fused optimizer operation. """ + # 如果value值不为字符串,则抛出异常 if not isinstance(value, str): raise TypeError("For 'Optimizer', the property 'target' must be string, but got {}".format(type(value))) - + # 如果value值不在CPU,Ascend,GPU中,则抛出异常 if value not in ('CPU', 'Ascend', 'GPU'): - raise ValueError("For 'Optimizer', the property 'target' must be one of ['CPU', 'Ascend' ,'GPU'], " + raise ValueError("For 'Optimizer', the property 'target' must be one of ['CPU', 'Ascend','GPU'], " "but got {}".format(value)) + # 如果target为CPU,且value为Ascend或GPU,则抛出异常 if self._target == "CPU" and value in ('Ascend', 'GPU'): raise ValueError("For 'Optimizer', the property 'target' cannot be set to 'GPU' or 'Ascend' " "in the 'CPU' environment.") if self._target == "Ascend" and value == 'GPU': + # 如果target为Ascend并且value为GPU,抛出异常 raise ValueError("For 'Optimizer', the property 'target' cannot be set to 'GPU' " "in the 'Ascend' environment.") if self._target == "GPU" and value == 'Ascend': + # 如果target为GPU并且value为Ascend,抛出异常 raise ValueError("For 'Optimizer', the property 'target' cannot be set to 'Ascend' " "in the 'GPU' environment.") - self._is_device = (value != 'CPU') + self._is_device = (value!= 'CPU') + # 将value设置为不是CPU self._target = value def decay_weight(self, gradients): @@ -371,14 +516,20 @@ class Optimizer(Cell): Returns: tuple[Tensor], The gradients after weight decay. """ + # 如果exec_weight_decay为True,则调用_apply_decay函数 if self.exec_weight_decay: + # 获取参数 params = self.parameters + # 获取权重衰减 weight_decay = self.get_weight_decay() + # 如果是分组模式,则调用map_函数统一衰减权重 if self.is_group: gradients = self.map_(F.partial(_apply_decay), weight_decay, self.decay_flags, params, gradients) + # 否则,调用map_函数逐个衰减权重 else: gradients = self.map_(F.partial(_apply_decay, weight_decay), self.decay_flags, params, gradients) + # 返回参数和梯度 return gradients def gradients_centralization(self, gradients): @@ -395,9 +546,11 @@ class Optimizer(Cell): Returns: tuple[Tensor], The gradients after gradients centralization. """ + # 如果是分组模式,则调用map_函数,将gradients中的每一个参数都调用_apply_grad_centralization函数 if self.is_group: gradients = self.map_(F.partial(_apply_grad_centralization), self.grad_centralization_flags, gradients) + # 返回每一个参数的梯度,并将梯度中间化 return gradients def scale_grad(self, gradients): @@ -414,142 +567,207 @@ class Optimizer(Cell): tuple[Tensor], The gradients after loss scale. """ + # 如果需要求值,则对梯度进行求值操作 if self.need_scale: gradients = self.map_(F.partial(_grad_scale, self.reciprocal_scale), gradients) + # 返回求值后的梯度 return gradients def _grad_sparse_indices_deduplicate(self, gradients): + # 在使用大运算符的情况下,将gradients中的indexes去重 """ In the case of using big operators, deduplicate the 'indexes' in gradients.""" - if self._target != 'CPU' and self._unique: + # 如果target不为CPU且unique属性为TRUE + if self._target!= 'CPU' and self._unique: + # 使用_indices_deduplicate函数,将gradients中的indexes去重 gradients = self.map_(F.partial(_indices_deduplicate), gradients) return gradients def _preprocess_weight_decay(self, weight_decay): + # 对weight_decay进行预处理 """preprocess weight decay""" + # 如果weight_decay是一个float或者int类型 if isinstance(weight_decay, (float, int)): + # 将weight_decay转换为float类型 weight_decay = float(weight_decay) + # 检查weight_decay是否达到了最小值 validator.check_non_negative_float(weight_decay, "weight_decay", self.cls_name) + # 将weight_decay乘以loss_scale weight_decay = weight_decay * self.loss_scale + # 如果weight_decay是Cell类型 elif isinstance(weight_decay, Cell): + # 设置dynamic_weight_decay为True self.dynamic_weight_decay = True + # 将weight_decay转换为WrappedWeightDecay类型 weight_decay = _WrappedWeightDecay(weight_decay, self.loss_scale) + # 如果weight_decay不是int,float或者Cell类型 else: + # 抛出异常 raise TypeError("For 'Optimizer', the argument 'Weight_decay' should be int, " "float or Cell.but got {}".format(type(weight_decay))) + # 返回weight_decay return weight_decay def _preprocess_single_lr(self, learning_rate): + # 对单个learning_rate进行预处理,将其转换为float或tensor或LearningRateSchedule """Check lr value, and convert lr to a float, a Tensor or a LearningRateSchedule.""" + # 检查learning_rate的值,并将其转换为float类型,如果是int类型,则将其转换为float类型 if isinstance(learning_rate, (float, int)): learning_rate = float(learning_rate) validator.check_non_negative_float(learning_rate, "learning rate", self.cls_name) return learning_rate + # 如果learning_rate是Tensor类型,且ndim等于0,则直接返回learning_rate if isinstance(learning_rate, Tensor) and learning_rate.ndim == 0: return learning_rate + # 如果learning_rate是Iterable类型,则将其转换为Tensor类型 self.dynamic_lr = True if isinstance(learning_rate, Iterable): return Tensor(np.array(list(learning_rate)).astype(np.float32)) + # 如果learning_rate是Tensor类型,且ndim大于1,则报错 if isinstance(learning_rate, Tensor): if learning_rate.ndim > 1: raise ValueError(f"For 'Optimizer', if 'learning_rate' is Tensor type, then the dimension of it should " f"be 0 or 1, but got {learning_rate.ndim}.") + # 如果learning_rate的ndim等于1,size小于2,则警告 if learning_rate.ndim == 1 and learning_rate.size < 2: logger.warning("For 'Optimizer', if use 'Tensor' type dynamic learning rate, " "please make sure that the number " "of elements in the tensor is greater than 1, " "but got {}.".format(learning_rate.size)) return learning_rate + # 如果learning_rate是LearningRateSchedule类型,则直接返回learning_rate if isinstance(learning_rate, LearningRateSchedule): return learning_rate + # 如果learning_rate的类型不是int,float,Tensor,Iterable,或LearningRateSchedule,则抛出TypeError异常 raise TypeError("For 'Optimizer', the argument 'learning_rate' should be int, float, Tensor, Iterable or " "LearningRateSchedule, but got {}.".format(type(learning_rate))) def _build_single_lr(self, learning_rate, name): + # 对单个learning_rate进行构筑,转换其为parameter或LearningRateSchedule """Build learning rate value, convert learning rate to a Parameter or a LearningRateSchedule.""" + # 如果learning_rate是一个float类型,则将其转换为Parameter if isinstance(learning_rate, float): learning_rate = Parameter(Tensor(learning_rate, mstype.float32), name) + # 如果self.is_group_lr为True且self.dynamic_lr为True,则将其转换为Cell if self.is_group_lr and self.dynamic_lr: learning_rate = _ConvertToCell(learning_rate) return learning_rate + # 如果learning_rate是一个Tensor类型,且ndim等于0,则将其转换为Parameter if isinstance(learning_rate, Tensor) and learning_rate.ndim == 0: learning_rate = Parameter(learning_rate, name) + # 如果self.is_group_lr为True且self.dynamic_lr为True,则将其转换为Cell if self.is_group_lr and self.dynamic_lr: learning_rate = _ConvertToCell(learning_rate) return learning_rate + # 如果learning_rate是一个Tensor类型,且ndim等于1,则返回IteratorLearningRate if isinstance(learning_rate, Tensor) and learning_rate.ndim == 1: return _IteratorLearningRate(learning_rate, name) return learning_rate def _check_group_params(self, parameters): + # 检查群组params """Check group params.""" + # 解析参数 parse_keys = ['params', 'lr', 'weight_decay', 'order_params', 'grad_centralization'] + # 遍历参数 for group_param in parameters: + # 过滤掉不在解析参数中的key invalid_key = list(filter(lambda x: x not in parse_keys, group_param.keys())) + # 如果有不在解析参数中的key,抛出异常 if invalid_key: raise KeyError(f"For 'Optimizer', the key in group params should be one of in {parse_keys}, " f"but got {invalid_key}.") + # 如果有order_params,判断其类型 if 'order_params' in group_param.keys(): if len(group_param.keys()) > 1: raise ValueError(f"For 'Optimizer', the order params dict in group parameters should only " f"include the 'order_params' key, but got {group_param.keys()}.") + # 如果order_params不是可迭代的,抛出异常 if not isinstance(group_param['order_params'], Iterable): raise TypeError("For 'Optimizer', the value of 'order_params' in group parameters should " "be Iterable type, but got {}.".format(type(group_param['order_params']))) continue + # 检查group_param['params']是否是可迭代的 parameters = self._parameters_base_check(group_param['params'], "group `params`") + # 遍历参数 for index, param in enumerate(parameters): + # 如果参数不是parameter的,抛出异常 if not isinstance(param, Parameter): raise TypeError(f"For 'Optimizer', the elemeter in group parameters must be Parameter type, " f"but got {type(param)} at index {index}.") def _parse_group_params(self, parameters, learning_rate): + # 解析参数组 """Parse group params.""" + # 检查参数组 self._check_group_params(parameters) + # 如果learning_rate是Tensor类型,并且ndim等于1,学习率张量长度设为1,否则设为0 if isinstance(learning_rate, Tensor) and learning_rate.ndim == 1: tensor_lr_length = learning_rate.size else: tensor_lr_length = 0 + # 遍历参数,检验参数是否规律,设置学习率的长度参数 for group_param in parameters: + # 如果其中存在order_params if 'order_params' in group_param.keys(): + # 如果只有一个order_params if len(group_param.keys()) > 1: + # 抛出异常 raise ValueError(f"For 'Optimizer', the order params dict in group parameters should only include " f"the 'order_params' key, but got {group_param.keys()}.") + # 如果order_params是Iterable类型 if not isinstance(group_param['order_params'], Iterable): + # 抛出异常 raise TypeError("For 'Optimizer', the value of 'order_params' in group parameters should be " "Iterable type, but got {}.".format(type(group_param['order_params']))) + # 设置is_group_params_ordered为True self.is_group_params_ordered = True + # 跳出循环 continue + # 如果存在lr if 'lr' in group_param.keys(): + # 设置is_group_lr为True self.is_group_lr = True + # 从参数中获取lr group_lr = self._preprocess_single_lr(group_param['lr']) + # 如果lr是Tensor类型,并且ndim等于1 if isinstance(group_lr, Tensor) and group_lr.ndim == 1: + # 设置group_lr_length为group_lr的size group_lr_length = group_lr.size + # 如果tensor_lr_length为0 if tensor_lr_length == 0: + # 设置tensor_lr_length为group_lr_length tensor_lr_length = group_lr_length - elif group_lr_length != tensor_lr_length: + # 如果group_lr_length不等于tensor_lr_length + elif group_lr_length!= tensor_lr_length: + # 抛出异常 raise ValueError("For 'Optimizer', the Tensor type dynamic learning rate in group should be " "the same size as the argument 'learning_rate'.") def _init_group_params(self, parameters, learning_rate, weight_decay, grad_centralization): """Initialize learning rate, weight decay or grad centralization in group params.""" + # 解析组参数 self._parse_group_params(parameters, learning_rate) + # 构建单个学习率 default_lr = self._build_single_lr(learning_rate, 'learning_rate') params_store = [] for group_num, group_param in enumerate(parameters): + # 如果group_param中包含order_params,则跳过 if 'order_params' in group_param.keys(): ordered_parameters = group_param['order_params'] continue + # 将group_param中的参数添加到self.group_params中 self.group_params += group_param['params'] + # 如果group_param中包含lr,则获取lr的值作为学习率,否则使用默认值 if 'lr' in group_param.keys(): lr_param_name = 'learning_rate_group_' + str(group_num) lr = self._preprocess_single_lr(group_param['lr']) @@ -557,11 +775,13 @@ class Optimizer(Cell): else: lr = default_lr + # 如果group_param中包含weight_decay,则获取weight_decay,否则使用默认值 if 'weight_decay' in group_param.keys(): weight_decay_ = self._preprocess_weight_decay(group_param['weight_decay']) else: weight_decay_ = self._preprocess_weight_decay(weight_decay) + # 如果group_param中包含grad_centralization,则获取grad_centralization,否则使用默认值 if 'grad_centralization' in group_param.keys(): self.grad_centralization = self._preprocess_grad_centralization(group_param['grad_centralization']) for param in group_param['params']: @@ -569,58 +789,83 @@ class Optimizer(Cell): grad_centralization_ = self.grad_centralization else: grad_centralization_ = grad_centralization - + # 遍历参数列表 for key in group_param.keys(): + # 如果key不在参数列表中,则报错 if key not in ('params', 'lr', 'weight_decay', 'grad_centralization'): logger.warning(f"The optimizer cannot parse '{key}' when setting parameter groups, " f"the key should in ['params', 'lr', 'weight_decay', 'grad_centralization']") + # 将参数列表中的参数添加到group_param字典中 for param in group_param['params']: + # 检查参数的类型 validator.check_value_type("parameter", param, [Parameter], self.cls_name) + # 如果参数已经存在,抛出运行时错误 if param.name in params_store: raise RuntimeError(f"For 'Optimizer', the {param.name} parameter already exists, it does not " f"support repeated setting. Please check whether the optimizer parameter " f"has been set multiple times.") + # 将参数名称和学习率、权重衰减和梯度中心化添加到字典中 params_store.append(param.name) self.group_lr.append(lr) self.group_weight_decay.append(weight_decay_) self.group_grad_centralization.append(grad_centralization_) + # 如果group_param字典中的参数是顺序的,则调整group_param字典 if self.is_group_params_ordered: self._order_and_adjust_group_params(ordered_parameters) def _order_and_adjust_group_params(self, ordered_parameters): + # 在组参数中对参数,学习率,衰减权重,梯度中心化值进行集中排序 """ Order group parameter, learning rate, weight decay and grad centralization in group params. """ + # 获取参数列表的长度 params_length = len(self.group_params) - if len(ordered_parameters) != len(self.group_params): + # 如果传入的参数列表的长度不等于组参数的长度,抛出异常 + if len(ordered_parameters)!= len(self.group_params): raise ValueError(f"For 'Optimizer'," f"the length of order parameters should be the same as the length of group parameters, " f"but got order parameters' length {len(ordered_parameters)}, " f"group parameters' length {len(self.group_params)}.") + # 初始化排序后的参数列表 ordered_params = [None] * params_length + # 初始化排序后的学习率列表 ordered_learning_rate = [None] * params_length + # 初始化排序后的权重衰减列表 ordered_weight_decay = [None] * params_length + # 初始化排序后的梯度中心化列表 ordered_grad_centralization = [None] * params_length + # 获取参数列表中的参数名称 params_name = [param.name for param in ordered_parameters] + # 遍历组参数 for param, lr, wd, gc in zip(self.group_params, self.group_lr, self.group_weight_decay, self.group_grad_centralization): + # 获取参数名称在参数列表中的索引 index = params_name.index(param.name) + # 将参数添加到排序后的参数列表中 ordered_params[index] = param + # 将学习率添加到排序后的学习率列表中 ordered_learning_rate[index] = lr + # 将权重衰减添加到排序后的权重衰减列表中 ordered_weight_decay[index] = wd + # 将梯度中心化添加到排序后的梯度中心化列表中 ordered_grad_centralization[index] = gc + # 将排序后的参数列表赋值给类变量 self.group_params = ordered_params + # 将排序后的学习率列表赋值给类变量 self.group_lr = ordered_learning_rate + # 将排序后的权重衰减列表赋值给类变量 self.group_weight_decay = ordered_weight_decay + # 将排序后的梯度中心化列表赋值给类变量 self.group_grad_centralization = ordered_grad_centralization def get_weight_decay(self): + # 获取当前步骤(step)的weight decay值 """ The optimizer calls this interface to get the weight decay value for the current step. User-defined optimizers based on :class:`mindspore.nn.Optimizer` can also call this interface @@ -630,9 +875,12 @@ class Optimizer(Cell): float, the weight decay value of current step. """ if self.dynamic_weight_decay: + # 如果dynamic_weight_decay为True if self.is_group: + # 则调用weight_decay_函数,并将结果放入weight_decay中 weight_decay = () for weight_decay_, flag_ in zip(self.weight_decay, self.dynamic_decay_flags): + # 如果flag_为True,则调用weight_decay_函数将目前的weight_decay增加到weight_decay上 current_weight_decay = weight_decay_(self.global_step) if flag_ else weight_decay_ weight_decay += (current_weight_decay,) return weight_decay @@ -640,6 +888,7 @@ class Optimizer(Cell): return self.weight_decay def get_lr(self): + # 获取学习率 """ The optimizer calls this interface to get the learning rate for the current step. User-defined optimizers based on :class:`mindspore.nn.Optimizer` can also call this interface before updating the parameters. @@ -647,19 +896,29 @@ class Optimizer(Cell): Returns: float, the learning rate of current step. """ + # 获取lr值 lr = self.learning_rate if self.dynamic_lr: + # 如果是分组学习率 if self.is_group_lr: + # 创建一个空元组 lr = () + # 遍历学习率 for learning_rate in self.learning_rate: + # 计算动态学习率 current_dynamic_lr = learning_rate(self.global_step) + # 将动态学习率添加到元组中 lr += (current_dynamic_lr,) else: + # 计算动态学习率 lr = self.learning_rate(self.global_step) + # 将当前计算的计数加上global_step_increase_tensor self.assignadd(self.global_step, self.global_step_increase_tensor) + # 返回动态学习率 return lr def get_lr_parameter(self, param): + # 获取指定参数的学习率 """ When parameters is grouped and learning rate is different for each group. Get the learning rate of the specified `param`. @@ -685,35 +944,53 @@ class Optimizer(Cell): 0.05 """ def get_lr_value(learning_rate): + ''' + 获取学习率值 + :param learning_rate: 学习率 + :return: 学习率值 + ''' if isinstance(learning_rate, (_ConvertToCell, _IteratorLearningRate)): + # 如果learning_rate是_ConvertToCell或者_IteratorLearningRate类型,则返回learning_rate中的learning_rate return learning_rate.learning_rate + # 否则,返回learning_rate return learning_rate if isinstance(param, Parameter): + # 如果param是Parameter类型,则将其赋值给param_list param_list = [param] elif isinstance(param, list): + # 如果param是list类型,则将其赋值给param_list param_list = param else: + # 如果param不是Parameter或list类型,则抛出TypeError异常 raise TypeError(f"For 'get_lr_parameter', the 'param' must be 'Parameter' or 'list' type, " f"but got {type(param)}.") lr = [] + # 获取参数列表中的参数 ids = [id(p) for p in self.parameters] + # 遍历参数列表中的参数 for p in param_list: + # 检查参数类型 validator.check_value_type("parameter", p, [Parameter], self.cls_name) + # 如果参数不在参数列表中,抛出异常 if id(p) not in ids: raise ValueError(f"For 'get_lr_parameter', the parameter {p.name} is not in optimizer, please check " f"whether the argument 'param' is correct.") + # 如果是分组学习率,则获取学习率值 if self.is_group_lr: index = ids.index(id(p)) lr.append(get_lr_value(self.learning_rate[index])) + # 否则获取学习率值 else: lr.append(get_lr_value(self.learning_rate)) + # 如果参数是列表,则返回字典,否则返回字符串 return lr if isinstance(param, list) else lr[0] def _get_parameter_group_id(self): + # 获取小于设备数量的参数分区组 ID """ Get the parameter partition group id, which is less than the number of devices. @@ -722,14 +999,20 @@ class Optimizer(Cell): """ rank_list = () count = 0 + # 遍历参数长度 for _ in range(self.param_length): + # 将rank_list元组加一个元素 rank_list = rank_list + (count,) + # 将count赋值为count加1 count = count + 1 + # 如果count等于dev_num,则将count赋值为0 if count == self.dev_num: count = 0 + # 返回rank_list return rank_list def broadcast_params(self, optim_result): + # 按参数组的顺序进行参数广播 """ Apply Broadcast operations in the sequential order of parameter groups. @@ -739,173 +1022,258 @@ class Optimizer(Cell): Returns: bool, the status flag. """ + # 创建一个空列表,用于存储参数组 param_group = [] + # 创建一个空列表,用于存储key组 key_group = [] + # 遍历参数组的每一个元素 for _ in range(self.dev_num): + # 将参数组添加到列表中 param_group.append(F.make_tuple()) + # 将key组添加到列表中 key_group.append(F.make_tuple()) + # 遍历参数组的每一个元素 for i in range(self.param_length): + # 将参数组添加到列表中 param_group[self.param_rank[i]] = param_group[self.param_rank[i]] + (self.parameters[i],) + # 将key组添加到列表中 key = P.MakeRefKey(self.param_names[i])() key_group[self.param_rank[i]] = key_group[self.param_rank[i]] + (key,) + # 创建一个空列表,用于存储新的参数组 new_param_group = [] + # 遍历参数组的每一个元素 for root in range(self.dev_num): + # 将参数组添加到新的参数组中 ops = P.Broadcast(root) + # 如果是第一个参数组,则将参数组添加到新的参数组中 if root > 0: param_group[root] = F.depend(param_group[root], new_param_group[root-1]) + # 否则,将参数组添加到新的参数组中 else: param_group[root] = F.depend(param_group[root], optim_result) + # 调用Broadcast函数,并将参数组赋值给新的参数组 next_params = ops(param_group[root]) + # 将新的参数组添加到列表中 new_param_group.append(next_params) + # 遍历新的参数组 for i in range(F.tuple_len(next_params)): + # 将新的参数组中的每一个元素赋值给key组 F.assign(key_group[root][i], next_params[i]) + # 返回新的参数组 return new_param_group def construct(self, *hyper_params): + # 抛出NotImplementedError异常 raise NotImplementedError op_add = P.AddN() +# 创建一个AddN操作 op_gather = P.Gather() +# 创建一个Gather操作 op_mul = P.Mul() +# 创建一个Mul操作 op_gc = inner.Centralization() +# 创建一个gc操作 +# 定义apply_decay属性 _apply_decay = C.MultitypeFuncGraph("apply_decay") +# 定义apply_grad_centralization属性 _apply_grad_centralization = C.MultitypeFuncGraph("apply_grad_centralization") @_apply_decay.register("Tensor", "Bool", "Tensor", "RowTensor") def _tensor_apply_decay_with_sparse(weight_decay, if_apply, weight, gradient): + # 使用衰减权重获取梯度稀疏矩阵 """Get grad with weight_decay.""" + # 如果if_apply为真,则获取gradient中的indices和values,并将weight_decay乘以weight,并将结果放入values中 if if_apply: indices = gradient.indices values = op_add((op_gather(weight, indices, 0) * F.cast(weight_decay, F.dtype(weight)), gradient.values)) shape = gradient.dense_shape return RowTensor(indices, values, shape) + # 否则,返回gradient return gradient @_apply_decay.register("Tensor", "Bool", "Tensor", "Tensor") def _tensor_apply_decay(weight_decay, if_apply, weight, gradient): + # 使用衰减权重获取梯度 """Get grad with weight_decay.""" if if_apply: + # 如果if_apply为True,则返回用衰减权重获取的梯度 return op_add((op_mul(weight, F.cast(weight_decay, F.dtype(weight))), gradient)) + # 否则返回gradient return gradient @_apply_grad_centralization.register("Bool", "RowTensor") def _tensor_apply_grad_centralization_with_sparse(if_apply, gradient): + # 使用梯度中心值获取梯度稀疏矩阵 """Get grad with grad_centralization.""" if if_apply: + # 获取gradient的indices和shape indices = gradient.indices shape = gradient.dense_shape + # 获取gradient的shape grad_shape = F.shape(gradient) + # 初始化axis axis = [] + # 循环遍历梯度的形状 for i in range(1, len(grad_shape)): + # 将梯度的形状中的索引赋值给axis axis.append(i) + # 如果axis大于等于1,则检查gradient的shape[1]是否是16的倍数 if len(axis) >= 1: - if grad_shape[1] % 16 != 0: + if grad_shape[1] % 16!= 0: return gradient + # 调用op_gc函数获取values values = op_gc(gradient.values, axis) + # 返回RowTensor return RowTensor(indices, values, shape) + # 返回gradient return gradient @_apply_grad_centralization.register("Bool", "Tensor") def _tensor_apply_grad_centralization(if_apply, gradient): + # 使用梯度中心值获取梯度 """Get grad with grad_centralization.""" if if_apply: + # 创建一个空列表,用于存储梯度的维度 axis = [] + # 获取梯度的形状 grad_shape = F.shape(gradient) + # 遍历梯度的维度 for i in range(1, len(grad_shape)): + # 将维度添加到列表中 axis.append(i) + # 如果列表长度大于1,则判断梯度的维度是否为16的倍数 if len(axis) >= 1: - if grad_shape[1] % 16 != 0: + # 如果不是,则返回梯度 + if grad_shape[1] % 16!= 0: return gradient + # 否则,返回梯度的梯度操作 return op_gc(gradient, axis) + # 如果if_apply为True,则返回梯度 return gradient + # 定义梯度缩放函数 _grad_scale = C.MultitypeFuncGraph("grad_scale") + # 定义索引去重函数 _indices_deduplicate = C.MultitypeFuncGraph("indices_deduplicate") - @_grad_scale.register("Number", "Tensor") def tensor_grad_scale(scale, grad): + # 使用scale获取梯度 """Get grad with scale.""" + # 如果scale为1.0,则直接返回grad if scale == 1.0: return grad + # 否则,将grad乘以scale,并将结果转换为指定的数据类型 return op_mul(grad, F.cast(scale, F.dtype(grad))) @_grad_scale.register("Tensor", "Tensor") def tensor_grad_scale_with_tensor(scale, grad): + # 使用scale获取指定数据类型的梯度 """Get grad with scale.""" return op_mul(grad, F.cast(scale, F.dtype(grad))) @_grad_scale.register("Tensor", "RowTensor") def tensor_grad_scale_with_sparse(scale, grad): + # 使用scale获取指定数据类型的梯度稀疏矩阵 """Get grad with scale.""" return RowTensor(grad.indices, grad.values * F.cast(scale, F.dtype(grad.values)), grad.dense_shape) @_indices_deduplicate.register("RowTensor") def rowtensor_deduplicate_indices_slices(grad): + # 定义一个索引,并对去重索引对应的 "值 "求和 """Unique the indices and sums the 'values' corresponding to the duplicate indices.""" + # 定义一个indices变量,用来存储梯度的索引 indices = grad.indices + # 定义一个values变量,用来存储梯度的值 values = grad.values + # 使用P.Unique()函数,将indices变量和values变量进行去重 unique_indices, index_position = P.Unique()(indices) + # 使用P.UnsortedSegmentSum()函数,将values变量和index_position变量进行求和 summed_values = P.UnsortedSegmentSum()(values, index_position, P.TensorShape()(unique_indices)[0]) + # 返回一个RowTensor,包含去重后的索引和和 return RowTensor(unique_indices, summed_values, grad.dense_shape) @_indices_deduplicate.register("Tensor") def tensor_deduplicate_indice_slices(grad): + # 返回密集参数中的输入梯度 """Return the input gradient directly in the dense sences.""" return grad class _ConvertToCell(LearningRateSchedule): + # 内部 api,将标量的学习率转换为学习率计划 """Inner api, convert learning rate of scalar to LearningRateSchedule.""" def __init__(self, learning_rate): super(_ConvertToCell, self).__init__() + # 判断learning_rate是否为Parameter类型 if not isinstance(learning_rate, Parameter): + # 如果不是,则抛出异常TypeError raise TypeError("For 'Optimizer', the argument 'learning_rate' must be Parameter, " "but got {}.".format(type(learning_rate))) + # 将learning_rate赋值给self.learning_rate self.learning_rate = learning_rate def construct(self, global_step): + # 返回self.learning_rate + 1.0 - 1.0 return self.learning_rate + 1.0 - 1.0 class _IteratorLearningRate(LearningRateSchedule): + # 内部 api,将张量(组)的学习率转换为学习率计划 """Inner api, convert learning rate of Tensor(list) to LearningRateSchedule.""" def __init__(self, learning_rate, name): super(_IteratorLearningRate, self).__init__() + # 判断learning_rate是否为Tensor if isinstance(learning_rate, Tensor): - if learning_rate.ndim != 1: + # 如果是Tensor,判断Tensor的维度是否为1 + if learning_rate.ndim!= 1: + # 如果不是,则抛出异常ValueError raise ValueError(f"For 'Optimizer', the dimension of the argument 'learning_rate' should " f"be 1, but got {learning_rate.ndim}.") + # 如果不是,则抛出异常TypeError else: raise TypeError("For 'Optimizer', the argument 'learning_rate' should be Tensor, " "but got {}.".format(type(learning_rate))) + # 将learning_rate赋值给self.learning_rate self.learning_rate = Parameter(learning_rate, name) + # 初始化gather self.gather = P.Gather() def construct(self, global_step): + # 返回gather函数的结果 return self.gather(self.learning_rate, global_step, 0) class _WrappedWeightDecay(Cell): + # 内部 api,动态或非动态权重衰减的组合 """Inner api, a combination of dynamic or non-dynamic weight decay""" def __init__(self, weight_decay, loss_scale=1.0): + ''' + 初始化一个_WrappedWeightDecay类,用来添加权重衰减 + :param weight_decay: 权重衰减的系数 + :param loss_scale: 损失缩放因子 + ''' super(_WrappedWeightDecay, self).__init__() + # 初始化weight_decay self.weight_decay = weight_decay + # 初始化loss_scale self.loss_scale = Tensor(loss_scale, mstype.float32) def construct(self, global_step): + # 返回weight_decay和loss_scale的乘积 return self.weight_decay(global_step) * self.loss_scale diff --git a/mindspore/python/mindspore/nn/optim/proximal_ada_grad.py b/mindspore/python/mindspore/nn/optim/proximal_ada_grad.py index d2b37563cf2..5bd8385816c 100644 --- a/mindspore/python/mindspore/nn/optim/proximal_ada_grad.py +++ b/mindspore/python/mindspore/nn/optim/proximal_ada_grad.py @@ -1,234 +1,289 @@ -# Copyright 2020 Huawei Technologies Co., Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================ -"""PROXIMAL_ADA_GRAD""" -from mindspore.ops import functional as F, composite as C, operations as P -from mindspore.common import Tensor -import mindspore.common.dtype as mstype -from mindspore._checkparam import Validator as validator -from .optimizer import Optimizer -from .optimizer import opt_init_args_register - -_proximal_ada_grad_opt = C.MultitypeFuncGraph("proximal_ada_grad_opt") - -@_proximal_ada_grad_opt.register("Function", "Function", "Tensor", "Tensor", "Tensor", "RowTensor", "Tensor", - "Tensor") - -def _tensor_run_opt_with_sparse(opt, sparse_opt, l1, l2, learning_rate, gradient, weight, accum): - """Apply sparse proximal_ada_grad optimizer to the weight parameter.""" - success = True - success = F.depend(success, sparse_opt(weight, accum, learning_rate, l1, l2, gradient.values, gradient.indices)) - return success - - -@_proximal_ada_grad_opt.register("Function", "Function", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor") - -def _tensor_run_opt(opt, sparse_opt, l1, l2, learning_rate, gradient, weight, accum): - """Apply proximal_ada_grad optimizer to the weight parameter.""" - success = True - success = F.depend(success, opt(weight, accum, learning_rate, l1, l2, gradient)) - return success - - -def _check_param_value(accum, l1, l2, use_locking, prim_name=None): - """Check inputs param.""" - validator.check_value_type("accum", accum, [float], prim_name) - validator.check_value_type("l1", l1, [float], prim_name) - validator.check_value_type("l2", l2, [float], prim_name) - validator.check_value_type("use_locking", use_locking, [bool], prim_name) - validator.check_non_negative_float(accum, "accum", prim_name) - validator.check_non_negative_float(l1, "l1", prim_name) - validator.check_non_negative_float(l2, "l2", prim_name) - - -class ProximalAdagrad(Optimizer): - r""" - Implements the ProximalAdagrad algorithm. - - ProximalAdagrad is an online Learning and Stochastic Optimization. - Refer to paper `Efficient Learning using Forward-Backward Splitting - `_. - - .. math:: - accum_{t+1} = accum_{t} + g * g - - .. math:: - \text{prox_v} = w_{t} - \gamma * g * \frac{1}{\sqrt{accum_{t+1}}} - - .. math:: - w_{t+1} = \frac{sign(\text{prox_v})}{1 + \gamma * l2} * \max(\left| \text{prox_v} \right| - \gamma * l1, 0) - - Here : where :math:`g` , :math:`\gamma`, :math:`w` , :math:`accum` and :math:`t` denote the `grads`, - `learning_rate`, `params`, accumulation and current step respectively. - - Note: - The sparse strategy is applied while the SparseGatherV2 operator is used for forward network. If the sparse - strategy wants to be executed on the host, set the target to the CPU. - The sparse feature is under continuous development. - - If parameters are not grouped, the `weight_decay` in optimizer will be applied on the network parameters without - 'beta' or 'gamma' in their names. Users can group parameters to change the strategy of decaying weight. When - parameters are grouped, each group can set `weight_decay`, if not, the `weight_decay` in optimizer will be - applied. - - Args: - params (Union[list[Parameter], list[dict]]): Must be list of `Parameter` or list of `dict`. When the - `params` is a list of `dict`, the string "params", "lr", "weight_decay", "grad_centralization" and - "order_params" are the keys can be parsed. - - - params: Required. Parameters in current group. The value must be a list of `Parameter`. - - - lr: Optional. If "lr" in the keys, the value of corresponding learning rate will be used. - If not, the `learning_rate` in optimizer will be used. Fixed and dynamic learning rate are supported. - - - weight_decay: Optional. If "weight_decay" in the keys, the value of corresponding weight decay - will be used. If not, the `weight_decay` in the optimizer will be used. It should be noted that weight - decay can be a constant value or a Cell. It is a Cell only when dynamic weight decay is applied. Dynamic - weight decay is similar to dynamic learning rate, users need to customize a weight decay schedule only - with global step as input, and during training, the optimizer calls the instance of WeightDecaySchedule - to get the weight decay value of current step. - - - grad_centralization: Optional. Must be Boolean. If "grad_centralization" is in the keys, the set value - will be used. If not, the `grad_centralization` is False by default. This configuration only works on the - convolution layer. - - - order_params: Optional. When parameters are grouped, this usually is used to maintain the order of - parameters that appeared in the network to improve performance. The value should be parameters whose - order will be followed in optimizer. - If `order_params` in the keys, other keys will be ignored and the element of 'order_params' must be in - one group of `params`. - - accum (float): The starting value for accumulators `accum`, must be zero or positive values. Default: 0.1. - learning_rate (Union[float, int, Tensor, Iterable, LearningRateSchedule]): Default: 0.001. - - - float: The fixed learning rate value. Must be equal to or greater than 0. - - - int: The fixed learning rate value. Must be equal to or greater than 0. It will be converted to float. - - - Tensor: Its value should be a scalar or a 1-D vector. For scalar, fixed learning rate will be applied. - For vector, learning rate is dynamic, then the i-th step will take the i-th value as the learning rate. - - - Iterable: Learning rate is dynamic. The i-th step will take the i-th value as the learning rate. - - - LearningRateSchedule: Learning rate is dynamic. During training, the optimizer calls the instance of - LearningRateSchedule with step as the input to get the learning rate of the current step. - - l1 (float): l1 regularization strength, must be greater than or equal to zero. Default: 0.0. - l2 (float): l2 regularization strength, must be greater than or equal to zero. Default: 0.0. - use_locking (bool): If true, use locks for updating operation. Default: False. - loss_scale (float): Value for the loss scale. It must be greater than 0.0. In general, use the default value. - Only when `FixedLossScaleManager` is used for training and the `drop_overflow_update` in - `FixedLossScaleManager` is set to False, then this value needs to be the same as the `loss_scale` in - `FixedLossScaleManager`. Refer to class :class:`mindspore.FixedLossScaleManager` for more details. - Default: 1.0. - weight_decay (Union[float, int, Cell]): Weight decay (L2 penalty). Default: 0.0. - - - float: The fixed weight decay value. Must be equal to or greater than 0. - - - int: The fixed weight decay value. Must be equal to or greater than 0. It will be converted to float. - - - Cell: Weight decay is dynamic. During training, the optimizer calls the instance of - the Cell with step as the input to get the weight decay value of current step. - - Inputs: - - **grads** (tuple[Tensor]) - The gradients of `params` in the optimizer, the shape is the same as the `params` - in optimizer. - - Outputs: - Tensor[bool], the value is True. - - Raises: - TypeError: If `learning_rate` is not one of int, float, Tensor, Iterable, LearningRateSchedule. - TypeError: If element of `parameters` is neither Parameter nor dict. - TypeError: If `accum`, `l1`, `l2` or `loss_scale` is not a float. - TypeError: If `weight_decay` is neither float nor int. - ValueError: If `loss_scale` is less than or equal to 0. - ValueError: If `accum`, `l1`, `l2` or `weight_decay` is less than 0. - - Supported Platforms: - ``Ascend`` - - Examples: - >>> from mindspore import nn, Model - >>> - >>> net = Net() - >>> #1) All parameters use the same learning rate and weight decay - >>> optim = nn.ProximalAdagrad(params=net.trainable_params()) - >>> - >>> #2) Use parameter groups and set different values - >>> conv_params = list(filter(lambda x: 'conv' in x.name, net.trainable_params())) - >>> no_conv_params = list(filter(lambda x: 'conv' not in x.name, net.trainable_params())) - >>> group_params = [{'params': conv_params, 'weight_decay': 0.01, 'grad_centralization':True}, - ... {'params': no_conv_params, 'lr': 0.01}, - ... {'order_params': net.trainable_params()}] - >>> optim = nn.ProximalAdagrad(group_params, learning_rate=0.1, weight_decay=0.0) - >>> # The conv_params's parameters will use default learning rate of 0.1 and weight decay of 0.01 and grad - >>> # centralization of True. - >>> # The no_conv_params's parameters will use learning rate of 0.01 and default weight decay of 0.0 and grad - >>> # centralization of False. - >>> # The final parameters order in which the optimizer will be followed is the value of 'order_params'. - >>> - >>> loss = nn.SoftmaxCrossEntropyWithLogits() - >>> model = Model(net, loss_fn=loss, optimizer=optim) - """ - - @opt_init_args_register - def __init__(self, params, accum=0.1, learning_rate=0.001, l1=0.0, l2=0.0, - use_locking=False, loss_scale=1.0, weight_decay=0.0): - super(ProximalAdagrad, self).__init__(learning_rate, params, weight_decay, loss_scale) - _check_param_value(accum, l1, l2, use_locking, self.cls_name) - self.accum = self.parameters.clone(prefix="accum", init=accum) - self.l1 = Tensor(l1, mstype.float32) - self.l2 = Tensor(l2, mstype.float32) - self.use_locking = use_locking - self.opt = P.ApplyProximalAdagrad(use_locking=use_locking) - self.sparse_opt = P.SparseApplyProximalAdagrad(use_locking=use_locking) - - def construct(self, grads): - params = self.parameters - accum = self.accum - grads = self.decay_weight(grads) - grads = self.gradients_centralization(grads) - grads = self.scale_grad(grads) - grads = self._grad_sparse_indices_deduplicate(grads) - lr = self.get_lr() - if self.is_group_lr: - success = self.map_reverse(F.partial(_proximal_ada_grad_opt, self.opt, self.sparse_opt, self.l1, self.l2), - lr, grads, params, accum) - else: - success = self.map_reverse(F.partial(_proximal_ada_grad_opt, self.opt, self.sparse_opt, self.l1, self.l2, - lr), - grads, params, accum) - return success - - @Optimizer.target.setter - def target(self, value): - """ - If the input value is set to "CPU", the parameters will be updated on the host using the Fused - optimizer operation. - """ - if not isinstance(value, str): - raise TypeError("For 'ProximalAdagrad', the property 'target' must be string type, " - "but got {}".format(type(value))) - - if value not in ('CPU', 'Ascend', 'GPU'): - raise ValueError("For 'ProximalAdagrad', the property 'target' must be 'CPU', 'Ascend' or 'GPU', " - "but got {}.".format(value)) - - if value == 'CPU': - self.sparse_opt = P.FusedSparseProximalAdagrad(self.use_locking).add_prim_attr("primitive_target", "CPU") - else: - self.sparse_opt = P.SparseApplyProximalAdagrad(self.use_locking) - - self._target = value +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""PROXIMAL_ADA_GRAD""" +# 从mindspore.ops模块中导入了functional、composite和operations等类,这些类主要用于实现各种计算图操作。 +from mindspore.ops import functional as F, composite as C, operations as P +# 从mindspore.common模块中导入了Tensor和mstype类,这些类主要用于处理张量数据和数据类型。 +from mindspore.common import Tensor +import mindspore.common.dtype as mstype +# 从mindspore._checkparam模块中导入了Validator类,这个类主要用于验证参数是否符合预期的值。 +from mindspore._checkparam import Validator as validator +# 从当前文件夹optimizer.py中导入了Optimizer类和opt_init_args_register函数,这些类和函数主要用于实现优化器的基本功能。 +from .optimizer import Optimizer +from .optimizer import opt_init_args_register + +# 定义了一个名为proximal_ada_grad_opt的多类型计算图函数 +_proximal_ada_grad_opt = C.MultitypeFuncGraph("proximal_ada_grad_opt") + +@_proximal_ada_grad_opt.register("Function", "Function", "Tensor", "Tensor", "Tensor", "RowTensor", "Tensor", + "Tensor") + +def _tensor_run_opt_with_sparse(opt, sparse_opt, l1, l2, learning_rate, gradient, weight, accum): + """Apply sparse proximal_ada_grad optimizer to the weight parameter.""" + # 使用sparse_opt对weight进行更新,并将更新后的结果存储在accum中。同时,返回一个布尔值,表示更新是否成功。 + """ + opt: 优化器实例。 + sparse_opt: 稀疏优化器实例。 + l1: L1 正则化参数。 + l2: L2 正则化参数。 + learning_rate: 学习率。 + gradient: 损失函数的梯度。 + weight: 需要更新的权重参数。 + accum: 累积变量,用于存储上一个迭代的权重更新。 + """ + success = True + # 使用sparse_opt对weight进行更新,并将更新后的结果存储在accum中 + success = F.depend(success, sparse_opt(weight, accum, learning_rate, l1, l2, gradient.values, gradient.indices)) + # 返回一个布尔值,表示更新是否成功 + return success + + +@_proximal_ada_grad_opt.register("Function", "Function", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor") + +def _tensor_run_opt(opt, sparse_opt, l1, l2, learning_rate, gradient, weight, accum): + """Apply proximal_ada_grad optimizer to the weight parameter.""" + # 使用opt对weight进行更新,并将更新后的结果存储在accum中。同时,返回一个布尔值,表示更新是否成功。 + success = True + success = F.depend(success, opt(weight, accum, learning_rate, l1, l2, gradient)) + return success + + +def _check_param_value(accum, l1, l2, use_locking, prim_name=None): + # 检查accum、l1、l2和use_locking的值是否符合预期 + """Check inputs param.""" + # 检查accum参数的类型是否为float,如果不是,抛出异常 + validator.check_value_type("accum", accum, [float], prim_name) + # 检查l1参数的类型是否为float,如果不是,抛出异常 + validator.check_value_type("l1", l1, [float], prim_name) + # 检查l2参数的类型是否为float,如果不是,抛出异常 + validator.check_value_type("l2", l2, [float], prim_name) + # 检查use_locking参数的类型是否为bool,如果不是,抛出异常 + validator.check_value_type("use_locking", use_locking, [bool], prim_name) + # 检查accum参数的值是否小于0,如果不小于0,抛出异常 + validator.check_non_negative_float(accum, "accum", prim_name) + # 检查l1参数的值是否小于0,如果不小于0,抛出异常 + validator.check_non_negative_float(l1, "l1", prim_name) + # 检查l2参数的值是否小于0,如果不小于0,抛出异常 + validator.check_non_negative_float(l2, "l2", prim_name) + + +class ProximalAdagrad(Optimizer): + # 继承自Optimizer类。ProximalAdagrad是一种在线学习率和优化算法,用于解决大规模数据集的学习问题。 + # 首先,计算累积梯度的更新公式。然后,计算 proximal_v 公式,最后,计算更新后的权重向量 w_{t+1} 的公式 + # 其中,:math:g , :math:\gamma, :math:w , :math:accum and :math:t 分别表示梯度、学习率、参数、累积梯度和当前步数。 + r""" + Implements the ProximalAdagrad algorithm. + + ProximalAdagrad is an online Learning and Stochastic Optimization. + Refer to paper `Efficient Learning using Forward-Backward Splitting + `_. + + .. math:: + accum_{t+1} = accum_{t} + g * g + + .. math:: + \text{prox_v} = w_{t} - \gamma * g * \frac{1}{\sqrt{accum_{t+1}}} + + .. math:: + w_{t+1} = \frac{sign(\text{prox_v})}{1 + \gamma * l2} * \max(\left| \text{prox_v} \right| - \gamma * l1, 0) + + Here : where :math:`g` , :math:`\gamma`, :math:`w` , :math:`accum` and :math:`t` denote the `grads`, + `learning_rate`, `params`, accumulation and current step respectively. + + Note: + The sparse strategy is applied while the SparseGatherV2 operator is used for forward network. If the sparse + strategy wants to be executed on the host, set the target to the CPU. + The sparse feature is under continuous development. + + If parameters are not grouped, the `weight_decay` in optimizer will be applied on the network parameters without + 'beta' or 'gamma' in their names. Users can group parameters to change the strategy of decaying weight. When + parameters are grouped, each group can set `weight_decay`, if not, the `weight_decay` in optimizer will be + applied. + + Args: + params (Union[list[Parameter], list[dict]]): Must be list of `Parameter` or list of `dict`. When the + `params` is a list of `dict`, the string "params", "lr", "weight_decay", "grad_centralization" and + "order_params" are the keys can be parsed. + + - params: Required. Parameters in current group. The value must be a list of `Parameter`. + + - lr: Optional. If "lr" in the keys, the value of corresponding learning rate will be used. + If not, the `learning_rate` in optimizer will be used. Fixed and dynamic learning rate are supported. + + - weight_decay: Optional. If "weight_decay" in the keys, the value of corresponding weight decay + will be used. If not, the `weight_decay` in the optimizer will be used. It should be noted that weight + decay can be a constant value or a Cell. It is a Cell only when dynamic weight decay is applied. Dynamic + weight decay is similar to dynamic learning rate, users need to customize a weight decay schedule only + with global step as input, and during training, the optimizer calls the instance of WeightDecaySchedule + to get the weight decay value of current step. + + - grad_centralization: Optional. Must be Boolean. If "grad_centralization" is in the keys, the set value + will be used. If not, the `grad_centralization` is False by default. This configuration only works on the + convolution layer. + + - order_params: Optional. When parameters are grouped, this usually is used to maintain the order of + parameters that appeared in the network to improve performance. The value should be parameters whose + order will be followed in optimizer. + If `order_params` in the keys, other keys will be ignored and the element of 'order_params' must be in + one group of `params`. + + accum (float): The starting value for accumulators `accum`, must be zero or positive values. Default: 0.1. + learning_rate (Union[float, int, Tensor, Iterable, LearningRateSchedule]): Default: 0.001. + + - float: The fixed learning rate value. Must be equal to or greater than 0. + + - int: The fixed learning rate value. Must be equal to or greater than 0. It will be converted to float. + + - Tensor: Its value should be a scalar or a 1-D vector. For scalar, fixed learning rate will be applied. + For vector, learning rate is dynamic, then the i-th step will take the i-th value as the learning rate. + + - Iterable: Learning rate is dynamic. The i-th step will take the i-th value as the learning rate. + + - LearningRateSchedule: Learning rate is dynamic. During training, the optimizer calls the instance of + LearningRateSchedule with step as the input to get the learning rate of the current step. + + l1 (float): l1 regularization strength, must be greater than or equal to zero. Default: 0.0. + l2 (float): l2 regularization strength, must be greater than or equal to zero. Default: 0.0. + use_locking (bool): If true, use locks for updating operation. Default: False. + loss_scale (float): Value for the loss scale. It must be greater than 0.0. In general, use the default value. + Only when `FixedLossScaleManager` is used for training and the `drop_overflow_update` in + `FixedLossScaleManager` is set to False, then this value needs to be the same as the `loss_scale` in + `FixedLossScaleManager`. Refer to class :class:`mindspore.FixedLossScaleManager` for more details. + Default: 1.0. + weight_decay (Union[float, int, Cell]): Weight decay (L2 penalty). Default: 0.0. + + - float: The fixed weight decay value. Must be equal to or greater than 0. + + - int: The fixed weight decay value. Must be equal to or greater than 0. It will be converted to float. + + - Cell: Weight decay is dynamic. During training, the optimizer calls the instance of + the Cell with step as the input to get the weight decay value of current step. + + Inputs: + - **grads** (tuple[Tensor]) - The gradients of `params` in the optimizer, the shape is the same as the `params` + in optimizer. + + Outputs: + Tensor[bool], the value is True. + + Raises: + TypeError: If `learning_rate` is not one of int, float, Tensor, Iterable, LearningRateSchedule. + TypeError: If element of `parameters` is neither Parameter nor dict. + TypeError: If `accum`, `l1`, `l2` or `loss_scale` is not a float. + TypeError: If `weight_decay` is neither float nor int. + ValueError: If `loss_scale` is less than or equal to 0. + ValueError: If `accum`, `l1`, `l2` or `weight_decay` is less than 0. + + Supported Platforms: + ``Ascend`` + + Examples: + >>> from mindspore import nn, Model + >>> + >>> net = Net() + >>> #1) All parameters use the same learning rate and weight decay + >>> optim = nn.ProximalAdagrad(params=net.trainable_params()) + >>> + >>> #2) Use parameter groups and set different values + >>> conv_params = list(filter(lambda x: 'conv' in x.name, net.trainable_params())) + >>> no_conv_params = list(filter(lambda x: 'conv' not in x.name, net.trainable_params())) + >>> group_params = [{'params': conv_params, 'weight_decay': 0.01, 'grad_centralization':True}, + ... {'params': no_conv_params, 'lr': 0.01}, + ... {'order_params': net.trainable_params()}] + >>> optim = nn.ProximalAdagrad(group_params, learning_rate=0.1, weight_decay=0.0) + >>> # The conv_params's parameters will use default learning rate of 0.1 and weight decay of 0.01 and grad + >>> # centralization of True. + >>> # The no_conv_params's parameters will use learning rate of 0.01 and default weight decay of 0.0 and grad + >>> # centralization of False. + >>> # The final parameters order in which the optimizer will be followed is the value of 'order_params'. + >>> + >>> loss = nn.SoftmaxCrossEntropyWithLogits() + >>> model = Model(net, loss_fn=loss, optimizer=optim) + """ + + @opt_init_args_register + def __init__(self, params, accum=0.1, learning_rate=0.001, l1=0.0, l2=0.0, + use_locking=False, loss_scale=1.0, weight_decay=0.0): + # 调用父类__init__方法,传入learning_rate、params、weight_decay和loss_scale + super(ProximalAdagrad, self).__init__(learning_rate, params, weight_decay, loss_scale) + # 使用_check_param_value函数检查accum、l1和l2的值是否符合预期 + _check_param_value(accum, l1, l2, use_locking, self.cls_name) + # 创建一个名为accum的变量,并将其克隆赋值给self.accum。 + self.accum = self.parameters.clone(prefix="accum", init=accum) + # 创建一个名为l1的张量,并将其赋值给self.l1 + self.l1 = Tensor(l1, mstype.float32) + # 创建一个名为l2的张量,并将其赋值给self.l2 + self.l2 = Tensor(l2, mstype.float32) + # 传入更新操作使用锁保护,默认False + self.use_locking = use_locking + # 创建一个名为opt的计算图函数,用于应用 proximal_ada_grad 优化器 + self.opt = P.ApplyProximalAdagrad(use_locking=use_locking) + # 创建一个名为sparse_opt的计算图函数,用于应用稀疏 proximal_ada_grad 优化器 + self.sparse_opt = P.SparseApplyProximalAdagrad(use_locking=use_locking) + + def construct(self, grads): + # 用于计算更新权重参数 + # 定义参数,获取params和accum变量 + params = self.parameters + # 定义求导累加器 + accum = self.accum + # 对grads进行去重处理,并计算权重衰减后的梯度。 + grads = self.decay_weight(grads) + # 对grads进行 centralization 处理 + grads = self.gradients_centralization(grads) + # 对grads进行 scale 处理。 + grads = self.scale_grad(grads) + # 对grads进行稀疏索引处理 + grads = self._grad_sparse_indices_deduplicate(grads) + # 获取学习率 + lr = self.get_lr() + # 如果是分组学习率 + if self.is_group_lr: + # 调用map_reverse函数,用分组方法计算梯度 + success = self.map_reverse(F.partial(_proximal_ada_grad_opt, self.opt, self.sparse_opt, self.l1, self.l2), + lr, grads, params, accum) + else: + # 调用map_reverse函数,用普通方法计算梯度 + success = self.map_reverse(F.partial(_proximal_ada_grad_opt, self.opt, self.sparse_opt, self.l1, self.l2, + lr), + grads, params, accum) + # 返回成功结果 + return success + + @Optimizer.target.setter + def target(self, value): + # 用于设置ProximalAdagrad类的target属性 + """ + If the input value is set to "CPU", the parameters will be updated on the host using the Fused + optimizer operation. + """ + # 检查target属性的类型是否为字符串。如果不是字符串类型,将引发TypeError异常。 + if not isinstance(value, str): + raise TypeError("For 'ProximalAdagrad', the property 'target' must be string type, " + "but got {}".format(type(value))) + # 如果target属性设置为非"CPU"、"Ascend"或"GPU"的值,将引发错误。 + if value not in ('CPU', 'Ascend', 'GPU'): + raise ValueError("For 'ProximalAdagrad', the property 'target' must be 'CPU', 'Ascend' or 'GPU', " + "but got {}.".format(value)) + + if value == 'CPU': + # 如果target属性设置为CPU,则使用FusedSparseProximalAdagrad优化器进行更新 + self.sparse_opt = P.FusedSparseProximalAdagrad(self.use_locking).add_prim_attr("primitive_target", "CPU") + else: + # 如果target属性设置为GPU,则使用SparseApplyProximalAdagrad稀疏优化器进行更新 + self.sparse_opt = P.SparseApplyProximalAdagrad(self.use_locking) + + # 将输入值设置为target + self._target = value diff --git a/mindspore/python/mindspore/nn/optim/rmsprop.py b/mindspore/python/mindspore/nn/optim/rmsprop.py index 103e4a2306d..9f8f7617d88 100644 --- a/mindspore/python/mindspore/nn/optim/rmsprop.py +++ b/mindspore/python/mindspore/nn/optim/rmsprop.py @@ -13,33 +13,73 @@ # limitations under the License. # ============================================================================ """rmsprop""" +# 实现RMSProp优化器,均方根传播(RMSProp)算法的实现。 +# 1.从mindspore.ops模块中导入所需的函数,例如functional、composite和operations。 from mindspore.ops import functional as F, composite as C, operations as P +# 2.从mindspore._checkparam模块中导入Validator类。 from mindspore._checkparam import Validator as validator +# 3.定义一个名为Optimizer的类,用于封装优化器的实现。 from .optimizer import Optimizer +# 4.定义一个名为opt_init_args_register的函数,用于注册优化器的关键参数。 from .optimizer import opt_init_args_register - +# 定义了两个名为_rmsprop_opt和_centered_rmsprop_opt的MultitypeFuncGraph对象。MultitypeFuncGraph是MindSpore中的一个用于封装多类型函数图的类。 _rmsprop_opt = C.MultitypeFuncGraph("rmsprop_opt") _centered_rmsprop_opt = C.MultitypeFuncGraph("rmsprop_opt") +# 这两个函数图可以用于实现RMSProp和centered RMSProp优化器。 @_rmsprop_opt.register("Function", "Number", "Number", "Number", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor") def _rmsprop_opt_(opt, decay, epsilon, momentum, learning_rate, weight, ms, mom, grad): + # 用于应用RMSProp优化器到权重参数上 """Apply rmsprop optimizer to the weight parameter using dynamic learning rate.""" + """ + opt:优化器实例,例如_rmsprop_opt或_centered_rmsprop_opt。 + decay:一阶矩衰减系数。 + epsilon:用于防止除以零的常量。 + momentum:一阶矩系数。 + learning_rate:学习率。 + weight:权重参数。 + ms:一阶矩参数。 + mom:二阶矩参数。 + grad:梯度参数。 + """ + # 将success设置为True success = True + # 调用opt函数,将weight、ms、mom、learning_rate、grad、decay、momentum和epsilon作为参数传入。将优化后的权重参数返回,并将success设置为优化成功。 success = F.depend(success, opt(weight, ms, mom, learning_rate, grad, decay, momentum, epsilon)) + # 返回success return success @_centered_rmsprop_opt.register("Function", "Number", "Number", "Number", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor") def _centered_rmsprop_opt_(opt, decay, epsilon, momentum, learning_rate, weight, mg, ms, mom, grad): + # 函数用于应用centered RMSProp优化器到权重参数上 """Apply centered rmsprop optimizer to the weight parameter using dynamic learning rate.""" + """ + opt:优化器实例,例如_rmsprop_opt或_centered_rmsprop_opt。 + decay:一阶矩衰减系数。 + epsilon:用于防止除以零的常量。 + momentum:一阶矩系数。 + learning_rate:学习率。 + weight:权重参数。 + mg:一阶矩参数。 + ms:二阶矩参数。 + mom:二阶矩参数。 + grad:梯度参数。 + """ success = True + # # 调用opt函数,将weight、ms、mom、learning_rate、grad、decay、momentum和epsilon作为参数传入。将优化后的权重参数返回,并将success设置为优化成功。 success = F.depend(success, opt(weight, mg, ms, mom, grad, learning_rate, decay, momentum, epsilon)) + # 如果成功,则将success的值传入opt函数,并将success的值设置为opt函数的返回值 return success class RMSProp(Optimizer): + # 定义了一个名为RMSProp的类,继承自Optimizer类。主要实现了RMSProp优化器的基本算法 + # 1.计算一阶矩ms和二阶矩mg的更新公式 + # 2.计算权重更新w的更新公式 + # 3.更新权重参数 """ Implements Root Mean Squared Propagation (RMSProp) algorithm. @@ -198,51 +238,97 @@ class RMSProp(Optimizer): @opt_init_args_register def __init__(self, params, learning_rate=0.1, decay=0.9, momentum=0.0, epsilon=1e-10, use_locking=False, centered=False, loss_scale=1.0, weight_decay=0.0): + ''' + 初始化RMSProp算法 + :param params: 权重参数列表 + :param learning_rate: 学习率 + :param decay: 一阶矩衰减系数 + :param momentum: 一阶矩系数 + :param epsilon: 用于防止除以零的常量 + :param use_locking: 是否使用锁定 + :param centered: 如果为True,则梯度将通过梯度的估计方差进行归一 + :param loss_scale: 损失缩放因子 + :param weight_decay: 权重衰减系数 + :return: + ''' super(RMSProp, self).__init__(learning_rate, params, weight_decay, loss_scale) + # 验证参数类型和范围 + # 检查decay的类型 validator.check_value_type("decay", decay, [float], self.cls_name) + # 检查decay的最小值 validator.check_non_negative_float(decay, "decay", self.cls_name) + # 检查momentum的类型 validator.check_value_type("momentum", momentum, [float], self.cls_name) + # 检查momentum的最小值 validator.check_non_negative_float(momentum, "momentum", self.cls_name) + # 检查epsilon的类型 validator.check_value_type("epsilon", epsilon, [float], self.cls_name) + # 检查epsilon的最小值 validator.check_positive_float(epsilon, "epsilon", self.cls_name) + # 检查use_locking的类型 validator.check_value_type("use_locking", use_locking, [bool], self.cls_name) + # 检查centered的类型 validator.check_value_type("centered", centered, [bool], self.cls_name) self.centered = centered + # 如果centered为真,则使用ApplyCenteredRMSProp算法 if centered: self.opt = P.ApplyCenteredRMSProp(use_locking) + # 创建偏差参数 self.mg = self.parameters.clone(prefix="mean_grad", init='zeros') else: + # 否则使用ApplyRMSProp算法 self.opt = P.ApplyRMSProp(use_locking) + # 创建优化系数 self.momentum = momentum + # 创建平方梯度参数 self.ms = self.parameters.clone(prefix="mean_square", init='ones') + # 创建优化系数 self.moment = self.parameters.clone(prefix="moment", init='zeros') + # 创建正则化系数 self.epsilon = epsilon + # 创建抑制系数 + self.decay = decayilon self.decay = decay def construct(self, gradients): + ''' + 构建优化器 + :param gradients: 梯度 + :return: 是否成功 + ''' params = self.parameters + # 首先对梯度进行去噪处理(decay_weight权重衰减) gradients = self.decay_weight(gradients) + # 对梯度进行中心化 gradients = self.gradients_centralization(gradients) + # 对梯度进行缩放 gradients = self.scale_grad(gradients) + # 获取学习率 lr = self.get_lr() + # 判断应用算法,若为TRUE,则梯度将通过梯度的估计方差进行归一。否则,应用一般的RMSProp算法 if self.centered: + # 如果是分组学习率,则用分组方法使用_centered_rmsprop_opt函数 if self.is_group_lr: success = self.hyper_map_reverse(F.partial(_centered_rmsprop_opt, self.opt, self.decay, self.epsilon, self.momentum), lr, params, self.mg, self.ms, self.moment, gradients) else: + # 否则,用普通方法使用_centered_rmsprop_opt函数 success = self.hyper_map_reverse(F.partial(_centered_rmsprop_opt, self.opt, self.decay, self.epsilon, self.momentum, lr), params, self.mg, self.ms, self.moment, gradients) else: + # 不为TRUE,则用分组方法使用_rmsprop_opt函数 if self.is_group_lr: success = self.hyper_map_reverse(F.partial(_rmsprop_opt, self.opt, self.decay, self.epsilon, self.momentum), lr, params, self.ms, self.moment, gradients) else: + # 否则,用普通方法使用_rmsprop_opt函数 success = self.hyper_map_reverse(F.partial(_rmsprop_opt, self.opt, self.decay, self.epsilon, self.momentum, lr), params, self.ms, self.moment, gradients) + # 返回成功消息 return success diff --git a/mindspore/python/mindspore/nn/optim/rprop.py b/mindspore/python/mindspore/nn/optim/rprop.py index 8ce0abff56a..ecabe696eaa 100755 --- a/mindspore/python/mindspore/nn/optim/rprop.py +++ b/mindspore/python/mindspore/nn/optim/rprop.py @@ -13,6 +13,10 @@ # limitations under the License. # ============================================================================ """rprop""" +# 定义了一个名为rprop的模块。rprop是反向传播优化算法的一种,主要用于处理非线性问题 +# 1.从mindspore库中导入所需的库和模块,例如ops、P、mstype等 +# 2.定义一个名为rprop的类,继承自Optimizer类 +# 3.注册一个名为opt_init_args_register的函数,用于初始化rprop优化器的参数 from mindspore import ops from mindspore.ops import operations as P import mindspore.common.dtype as mstype @@ -23,6 +27,17 @@ from .optimizer import opt_init_args_register class Rprop(Optimizer): + # Rprop的类,继承自Optimizer类。Rprop优化器是一种反向传播优化算法,用于处理非线性问题。 + # 在更新参数时,它会根据梯度的正负性来调整步长,从而提高在非线性问题中的性能。 + # 更新公式如下: + """ + 如果g_{t-1} g_t > 0,则Delta_t = min(Delta_{t-1} * eta_{+}, Delta_{max})。 + 如果g_{t-1} g_t < 0,则Delta_t = max(Delta_{t-1} * eta_{-}, Delta_{min})。 + 如果g_{t-1} g_t = 0,则Delta_t = Delta_{t-1}。 + 更新参数w_{t} = w_{t-1} - Delta_t * sign(g_t)。 + 这里的\eta_{+/-}表示正负梯度时的步长因子,\Delta_{min/max}表示最小/最大的步长。 + """ + r""" Implements Resilient backpropagation. @@ -150,35 +165,55 @@ class Rprop(Optimizer): @opt_init_args_register def __init__(self, params, learning_rate=0.1, etas=(0.5, 1.2), step_sizes=(1e-6, 50.), weight_decay=0.): + ''' + 初始化Rprop算法 + :param params: 参数 + :param learning_rate: 学习率 + :param etas: 缩放因子 + :param step_sizes: 步长 + :param weight_decay: 权重衰减 + :return: + ''' + # 首先调用父类的__init__方法,传入learning_rate、params和weight_decay参数,然后将结果赋值给当前类的实例 super(Rprop, self).__init__(learning_rate, params, weight_decay) + # 检查etas和step_sizes的类型和大小是否符合Rprop优化器的要求 + # 首先,检查etas是否为tuple类型,如果不是,则抛出TypeError if not isinstance(etas, tuple): raise TypeError("For Rprop, etas should be a tuple, but got {}.".format(type(etas))) - if len(etas) != 2: + # 然后,检查etas的长度是否为2,如果不是,则抛出ValueError + if len(etas)!= 2: raise ValueError("For Rprop, etas should be a tuple with the size of 2, but got {}.".format(len(etas))) + # 接着,检查step_sizes是否为tuple类型,如果不是,则抛出TypeError if not isinstance(step_sizes, tuple): raise TypeError("For Rprop, step_sizes should be a tuple, but got {}.".format(type(etas))) - if len(step_sizes) != 2: + # 然后,检查step_sizes的长度是否为2,如果不是,则抛出ValueError + if len(step_sizes)!= 2: raise ValueError("For Rprop, step_sizes should be a tuple with the size of 2, " - "but got {}.".format(len(step_sizes))) - + "but got {}.".format(len(step_sizes))) + # 最后,检查step_sizes的第一个元素是否大于第二个元素,如果不是,则抛出ValueError if step_sizes[0] > step_sizes[1]: raise ValueError("For Rprop, maximal step size should not be less than minimal step size, " "but got {} > {}.".format(step_sizes[0], step_sizes[1])) + # 首先,使用validator.check_float_range函数检查etas[0]是否在0.0到1.0之间,如果不是,则抛出ValueError validator.check_float_range(etas[0], 0.0, 1.0, Rel.INC_NEITHER, "etaminus", self.cls_name) + # 接着,使用validator.check_value_type函数检查etas[1]是否为float类型,如果不是,则抛出TypeError。 validator.check_value_type("etaplus", etas[1], [float], self.cls_name) + # 最后,检查etas[1]是否大于1.0,如果是,则抛出ValueError if etas[1] <= 1.0: raise ValueError("For Rprop, etaplus should be greater than 1.0, but got etaplus {}.".format(etas[1])) - + # 检查step_sizes的第一个元素和第二个元素是否为float类型,如果不是,则抛出TypeError。 validator.check_value_type("min_step_sizes", step_sizes[0], [float], self.cls_name) validator.check_value_type("max_step_sizes", step_sizes[1], [float], self.cls_name) + # 定义了Rprop优化器的属性,包括etaminus、etaplus、step_size_min和step_size_max。 self.etaminus, self.etaplus = etas self.step_size_min, self.step_size_max = step_sizes self.prev = self.parameters.clone(prefix="prev", init='zeros') self.step_size = self.parameters.clone(prefix="step_size", init='zeros') + # 同时,它还定义了一些用于操作张量的P函数,如Fill、Sign、Assign、AssignAdd、Cast、Select和OnesLike。这些P函数通常用于构建和操作Tensor。 self.fill = P.Fill() self.sign = P.Sign() self.assign = P.Assign() @@ -188,37 +223,56 @@ class Rprop(Optimizer): self.ones_like = P.OnesLike() def construct(self, gradients): + # 对梯度进行权重衰减 gradients = self.decay_weight(gradients) + # 对梯度进行求和,并且添加中心化操作 gradients = self.gradients_centralization(gradients) + # 对梯度进行缩放操作 gradients = self.scale_grad(gradients) + # 获取学习率 lrs = self.get_lr() + # 判断是否成功 success = True + # 用于遍历gradients、self.parameters、self.prev和self.step_size的元素 for index, (grad, param, prev, step_size) in enumerate(zip(gradients, self.parameters, self.prev, self.step_size)): + # 在循环内部,首先计算学习率lr lr = lrs[index] if self.is_group_lr else lrs + # 并根据self.global_step是否为1来选择初始化step_size_fp32 if self.global_step == 1: + # 如果全局步数为1,则使用self.ones_like函数创建一个与step_size形状相同的张量,并将其乘以学习率lr step_size_fp32 = self.ones_like(step_size) * lr else: + # 否则,将step_size转换为fp32类型,并将其赋值给step_size_fp32 step_size_fp32 = self.cast(step_size, mstype.float32) + # 然后,将grad转换为float32类型,param也转换为float32类型 gradient_fp32 = self.cast(grad, mstype.float32) param_fp32 = self.cast(param, mstype.float32) + # 接下来,计算sign,它是gradient_fp32与prev的符号函数 sign = self.sign(gradient_fp32 * prev) + # sign被裁剪到[self.etaplus, self.etaminus]之间,然后被转换为float32类型 sign = self.select(sign > 0, self.fill(mstype.float32, sign.shape, self.etaplus), sign) sign = self.select(sign < 0, self.fill(mstype.float32, sign.shape, self.etaminus), sign) sign = self.select(sign == 0, self.fill(mstype.float32, sign.shape, 1.), sign) + # 使用函数裁剪step_size_fp32,使其在[self.step_size_min, self.step_size_max]之间 step_size_fp32 = ops.clip_by_value(step_size_fp32 * sign, self.step_size_min, self.step_size_max) + # 最后,根据sign的值计算gradient_update,当sign为负时,gradient_update为0;当sign为正时,gradient_update为-gradient_fp32 gradient_update = self.select(sign == self.etaminus, self.fill(mstype.float32, sign.shape, 0.), gradient_fp32) + # 然后计算next_param,它是param_fp32减去sign乘以step_size_fp32 next_param = param_fp32 - self.sign(gradient_update) * step_size_fp32 + # 将next_param转换为与param相同的数据类型 self.assign(param, self.cast(next_param, param.dtype)) + # 将gradient_update转换为与prev相同的数据类型 self.assign(prev, self.cast(gradient_update, prev.dtype)) + # 将step_size_fp32转换为与step_size相同的数据类型,并赋值给param、prev和step_size self.assign(step_size, self.cast(step_size_fp32, step_size.dtype)) - + # 返回成功信息 return success diff --git a/mindspore/python/mindspore/nn/optim/sgd.py b/mindspore/python/mindspore/nn/optim/sgd.py index b94fc42b51d..87828057145 100755 --- a/mindspore/python/mindspore/nn/optim/sgd.py +++ b/mindspore/python/mindspore/nn/optim/sgd.py @@ -13,26 +13,58 @@ # limitations under the License. # ============================================================================ """sgd""" +# 定义了SGD类,继承自Optimizer。SGD类实现了 stochastic gradient descent(SGD)优化算法,支持动态学习率和权重衰减。 +# SGD类的主要目的是为了处理多层神经网络中的参数更新。 + +# 1.从mindspore.ops模块中导入所需的函数,例如functional、composite和operations。 from mindspore.ops import functional as F, composite as C, operations as P +# 2.从mindspore.common.parameter模块中导入Parameter类,用于创建和操作参数。 from mindspore.common.parameter import Parameter +# 3.从mindspore.common.tensor模块中导入Tensor类,用于创建和操作张量。 from mindspore.common.tensor import Tensor +# 4.从mindspore.common.dtype模块中导入mstype类,用于表示数据类型。 import mindspore.common.dtype as mstype +# 5.从mindspore._checkparam模块中导入Validator类。 from mindspore._checkparam import Validator as validator +# 6.定义一个名为Optimizer的类,用于封装优化器的实现。 from .optimizer import Optimizer +# 7.定义一个名为opt_init_args_register的函数,用于注册优化器的关键参数。 from .optimizer import opt_init_args_register +# 创建一个名为"sgd_opt"的多类型函数图。多类型函数图是一种用于表示多类型函数的图形结构,可以用于实现各种优化算法。 _sgd_opt = C.MultitypeFuncGraph("sgd_opt") @_sgd_opt.register("Function", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor") def _tensor_run_opt_ext(opt, momentum, learning_rate, gradient, weight, accum, stat): """Apply sgd optimizer to the weight parameter using Tensor.""" + # 使用优化器对权重参数进行更新,并返回更新是否成功的标志。 success = True + # 调用opt函数,传入weight,gradient,learning_rate,accum,momentum,stat参数 success = F.depend(success, opt(weight, gradient, learning_rate, accum, momentum, stat)) return success class SGD(Optimizer): + # SGD(随机梯度下降)是一种常用的优化算法,用于解决深度学习中的梯度下降问题。 + # SGD的主要思想是每次更新权重时,使用随机梯度(gradient)来计算更新方向,然后沿着这个方向更新权重。 + # 在更新过程中,可以使用动量(momentum)来平滑梯度更新,以提高训练速度。 + # SGD类实现了两种动量算法:标准动量(standard momentum)和Nesterov动量(Nesterov momentum)。标准动量计算公式如下: + """ + math:: + v_{t+1} = u \ast v_{t} + gradient \ast (1-dampening) + + 其中,u是一个超参数,用于控制动量系数。 + + 如果使用Nesterov动量,计算公式如下: + + math:: + p_{t+1} = p_{t} - lr \ast (gradient + u \ast v_{t+1}) + + 这里,p表示权重参数,v表示累积梯度,u表示动量系数。与标准动量不同,Nesterov动量在计算更新方向时,将当前梯度与上一次更新方向(v)相加。这样可以提高在梯度下降过程中,权重参数的更新速度。 + + 为了实现Nesterov动量,需要在SGD类中添加一个名为"nesterov"的布尔参数,并在更新权重时根据该参数选择使用哪种动量算法。 + """ r""" Implements stochastic gradient descent. Momentum is optional. @@ -144,56 +176,88 @@ class SGD(Optimizer): def __init__(self, params, learning_rate=0.1, momentum=0.0, dampening=0.0, weight_decay=0.0, nesterov=False, loss_scale=1.0): + # 初始化SGD类,并设置SGD类的参数 + super(SGD, self).__init__(learning_rate, params, weight_decay, loss_scale) + # 判断momentum的类型 if isinstance(momentum, int): + # 如果是int类型,则将其转换为float类型 momentum = float(momentum) + # 如果momentum的类型不是int类型,则抛出异常TypeError if not isinstance(momentum, float): - raise TypeError("For 'SGD', the argument 'momentum' should be float type, " + raise TypeError("For 'SGD', the argument'momentum' should be float type, " "but got {}.".format(type(momentum))) + # 判断momentum的值 if isinstance(momentum, float) and momentum < 0.0: - raise ValueError("For 'SGD', the argument 'momentum' should be at least 0.0, " - "but got {}".format(momentum)) + # 如果momentum的值小于0,则抛出异常ValueError + raise ValueError("For 'SGD', the argument'momentum' should be at least 0.0, " + "but got {}".format(momentum)) + # 判断dampening的类型 if isinstance(dampening, int): + # 如果是int类型,则将其转换为float类型 dampening = float(dampening) + # 如果转换后dampening的类型不是float类型,则抛出异常TypeError if not isinstance(dampening, float): raise TypeError("For 'SGD', the argument 'dampening' should be float type, " "but got {}.".format(type(dampening))) + # 定义SGD梯度解析器,参数dampening为梯度衰减系数,weight_decay为权重衰减系数,nesterov为是否使用Nesterov梯度解析器,momentum为动量,dampening为权重衰减系数 + # 如果dampening的值小于0 if dampening < 0.0: + # 则抛出异常ValueError raise ValueError("For 'SGD', the argument 'dampening' should be at least 0.0, " "but got 'dampening' {}".format(dampening)) + # 传入dampening的值 self.dampening = dampening + # 判断weight_decay的类型 if isinstance(weight_decay, int): + # 如果是int类型,则将其转换为float类型 weight_decay = float(weight_decay) + # 使用"validator.check_value_type"函数检查"nesterov"参数的类型是否为布尔类型,如果不是,则抛出异常。 validator.check_value_type("nesterov", nesterov, [bool], self.cls_name) - - if nesterov and (momentum <= 0.0 or dampening != 0.0): - raise ValueError("For 'SGD', if 'nesterov' is true, 'momentum' must be > 0.0 and 'dampening' must " - "equal to 0.0, but got 'momentum' {}, 'dampening' {}".format(momentum, dampening)) + + # 如果nesterov为True,且momentum小于等于0.0或者dampening不等于0.0,抛出异常ValueError + if nesterov and (momentum <= 0.0 or dampening!= 0.0): + raise ValueError("For 'SGD', if 'nesterov' is true,'momentum' must be > 0.0 and 'dampening' must " + "equal to 0.0, but got'momentum' {}, 'dampening' {}".format(momentum, dampening)) + # 传入nesterov的值 self.nesterov = nesterov + # 使用P.SGD函数创建一个SGD优化器 self.opt = P.SGD(dampening, weight_decay, nesterov) + # 创建一个Parameter类型的变量,并赋值为momentum self.momentum = Parameter(Tensor(momentum, mstype.float32), name="momentum") + # 创建一个Parameter类型的变量,并赋值为accum self.accum = self.parameters.clone(prefix="accum", init='zeros') + # 创建一个Parameter类型的变量,并赋值为stat self.stat = self.parameters.clone(prefix="stat", init='ones') def construct(self, gradients): + # 处理输入的梯度并更新参数 params = self.parameters accum = self.accum stat = self.stat + # 首先对梯度进行 centralization(去均值)处理 gradients = self.gradients_centralization(gradients) + # 然后对梯度进行 scale_grad(缩放)处理 gradients = self.scale_grad(gradients) + # 获取学习率 lr = self.get_lr() + # 最后根据学习率和参数更新规则进行参数更新。 + # 实现了hyper_map_reverse方法,用于对输入的参数进行批量操作。这个方法可以用于实现学习率动态更新、参数组分组等功能。 + # 如果是分组学习率,则用分组方法使用hyper_map_reverse方法 if self.is_group_lr: success = self.hyper_map_reverse(F.partial(_sgd_opt, self.opt, self.momentum), lr, gradients, params, accum, stat) else: + # 否则,用普通方法使用hyper_map_reverse方法 success = self.hyper_map_reverse(F.partial(_sgd_opt, self.opt, self.momentum, lr), gradients, params, accum, stat) + # 返回成功消息 return success diff --git a/mindspore/python/mindspore/nn/optim/thor.py b/mindspore/python/mindspore/nn/optim/thor.py index 8616ffcb38e..0185cd7b459 100644 --- a/mindspore/python/mindspore/nn/optim/thor.py +++ b/mindspore/python/mindspore/nn/optim/thor.py @@ -13,40 +13,86 @@ # limitations under the License. # ============================================================================ """thor""" +# 通过二阶算法THOR更新参数(mindspore原创) +""" + 注: + 深度学习训练过程可以看成损失函数损失值下降过程,合适的优化器可以让深度学习训练时间大大减少。优化器可以分为一阶优化器和二阶优化器, + 目前业界主流使用的仍然是一阶优化器,二阶优化器因为单步训练时间过久而没有被广泛应用,而近年来,将二阶优化应用到深度学习训练中有了理论突破, + 并取得了不错的结果。 + + 当前业界已有的二阶优化算法计算量较大,与一阶相比优势不明显或者使用场景较为单一。MindSpore提出了自研算法THOR(Trace-based Hardware-driven layer-ORiented Natural Gradient Descent Computation), + 算法已被AAAI录用,THOR在多个场景中均有明显收益,如在BERT和ResNet50中,收敛速度均有明显优势。 +""" +# 1.引入了NumPy库,用于处理数值计算。 import numpy as np +# 2.从"mindspore.ops"模块中导入了一些功能函数,如"F"、"C"、"P"等,用于实现深度学习中的各种操作。 from mindspore.ops import functional as F, composite as C, operations as P +# 3.从"mindspore.common.initializer"模块中导入了一些初始化函数,用于初始化模型参数。 from mindspore.common.initializer import initializer +# 4.从"mindspore.common.parameter"模块中导入了一些参数类,如"Parameter"和"ParameterTuple"。 from mindspore.common.parameter import Parameter, ParameterTuple +# 5.从"mindspore.common.tensor"模块中导入了一些张量类,如"Tensor"。 from mindspore.common.tensor import Tensor +# 6.导入了与深度学习相关的模块nn import mindspore.nn as nn +# 7.从"mindspore.common.dtype"模块中导入了一个"mstype"类,用于表示数据类型 import mindspore.common.dtype as mstype +# 8.从"mindspore.log"模块中导入了一个"logger"类,用于记录日志 import mindspore.log as logger +# 9.从"mindspore._checkparam"模块中导入了一个"Validator"类,用于检查参数的合法性 from mindspore._checkparam import Validator +# 10.从"mindspore.nn.optim.optimizer"模块中导入了一个"Optimizer"基类,用于实现优化器 from mindspore.nn.optim.optimizer import Optimizer +# 11.从"mindspore.parallel._utils"模块中导入了一些函数,用于处理并行训练 +# 首先从"mindspore.parallel._utils"模块中导入了一个名为"_get_device_num"的函数,用于获取设备的数量,导入了一个名为"_get_gradients_mean"的函数,作用是计算梯度的平均值 from mindspore.parallel._utils import _get_device_num, _get_gradients_mean +# 然后,从"mindspore"模块中导入了一个名为"context"的类,用于处理上下文 from mindspore import context +# 最后,导入了一个名为"ParallelMode"的类,用于表示并行模式 from mindspore.context import ParallelMode +# 12.从"mindspore.nn.layer"模块中导入了一些层类,如"DenseThor"(全连接层)、"Conv2dThor"(卷积层)、"EmbeddingThor"和"EmbeddingLookupThor"。 from mindspore.nn.layer import DenseThor, Conv2dThor, EmbeddingThor, EmbeddingLookupThor +# 13.从"mindspore.nn.wrap"模块中导入了一个"DistributedGradReducer"类,用于处理分布式训练中的梯度合并 from mindspore.nn.wrap import DistributedGradReducer +# 14.从"mindspore.train.train_thor.convert_utils"模块中导入了一个"ConvertNetUtils"类,用于处理模型转换 from mindspore.train.train_thor.convert_utils import ConvertNetUtils +# 15.从"mindspore.parallel._auto_parallel_context"模块中导入了一个"auto_parallel_context"类,用于处理自动并行训练 from mindspore.parallel._auto_parallel_context import auto_parallel_context # Enumerates types of Layer +# 定义了一些常量,用于表示操作的类型 +# Other表示其他类型的操作 Other = -1 +# Conv表示卷积操作 Conv = 1 +# FC表示全连接操作 FC = 2 +# Embedding表示嵌入操作 Embedding = 3 +# LayerNorm表示层归一化操作 LayerNorm = 4 +# BatchNorm表示批量归一化操作 BatchNorm = 5 +# 定义了一个名为op_add的多类型函数图,用于实现加法操作。多类型函数图是一种用于表示多输入输出操作的图结构 op_add = P.AddN() +# 定义了一个名为apply_decay的多类型函数图,用于应用学习率衰减 apply_decay = C.MultitypeFuncGraph("apply_decay") +# 定义了一个名为_momentum_opt的多类型函数图,用于实现动量优化 _momentum_opt = C.MultitypeFuncGraph("momentum_opt") @apply_decay.register("Number", "Bool", "Tensor", "Tensor") def _tensor_apply_decay(weight_decay, if_apply, weight, gradient): + """ + weight_decay(学习率衰减系数) + if_apply(一个布尔值,表示是否应用学习率衰减) + weight(权重张量) + gradient(梯度张量) + """ + # 根据if_apply的值返回加权梯度,即weight * weight_decay * gradient。 """Get grad with weight_decay.""" + # 如果if_apply为真,则返回weight乘以weight_decay的梯度,否则返回梯度 if if_apply: return op_add((weight * weight_decay, gradient)) return gradient @@ -54,21 +100,37 @@ def _tensor_apply_decay(weight_decay, if_apply, weight, gradient): @_momentum_opt.register("Function", "Tensor", "Tensor", "Tensor", "Tensor", "Tensor") def _tensor_run_opt_ext(opt, momentum, learning_rate, gradient, weight, moment): + # 使用opt函数更新weight张量,并返回一个布尔值,表示优化成功与否 """Apply momentum optimizer to the weight parameter using Tensor.""" + """ + opt(优化器函数,例如momentum_sgd或adam) + momentum(动量系数) + learning_rate(学习率) + gradient(梯度张量) + weight(权重张量) + moment(动量张量) + """ success = True + # 添加depend函数,使得我们可以在每次迭代时使用opt函数 success = F.depend(success, opt(weight, moment, learning_rate, gradient, momentum)) return success - +# IS_ENABLE_GLOBAL_NORM:一个布尔值,表示是否启用全局归一化 IS_ENABLE_GLOBAL_NORM = False +# GRADIENT_CLIP_TYPE:一个整数,表示裁剪梯度的类型,可选值为0(不裁剪)和1(裁剪到指定范围) GRADIENT_CLIP_TYPE = 1 +# GRADIENT_CLIP_VALUE:一个浮点数,表示裁剪梯度的范围。 GRADIENT_CLIP_VALUE = 1.0 +# 定义clip_grad函数,用于剪切梯度 clip_grad = C.MultitypeFuncGraph("clip_grad") +# 定义HyperMap操作,用于处理梯度 hyper_map_op = C.HyperMap() @clip_grad.register("Number", "Number", "Tensor") def _clip_grad(clip_type, clip_value, grad): + # 用于裁剪梯度,clip_type(裁剪类型,0表示裁剪到指定范围,1表示裁剪到全局范数), + # clip_value(裁剪范围),grad(梯度张量)。函数根据clip_type的值裁剪梯度,并返回裁剪后的梯度张量。 """ Clip gradients. @@ -81,53 +143,85 @@ def _clip_grad(clip_type, clip_value, grad): tuple[Tensor], clipped gradients. """ if clip_type not in [0, 1]: + # 如果clip_type不是0或1,则直接返回grad return grad + # 获取当前梯度的数据类型 dt = F.dtype(grad) + # 如果clip_type为0,则使用clip_by_value函数进行裁剪 if clip_type == 0: new_grad = C.clip_by_value(grad, F.cast(F.tuple_to_array((-clip_value,)), dt), F.cast(F.tuple_to_array((clip_value,)), dt)) + # 如果clip_type为1,则使用ClipByNorm函数进行裁剪 else: new_grad = nn.ClipByNorm()(grad, F.cast(F.tuple_to_array((clip_value,)), dt)) + # 返回裁剪后的梯度 return new_grad def clip_gradient(enable_clip_grad, gradients): + # 用于裁剪梯度。它接受一个布尔值enable_clip_grad和一个梯度张量列表gradients作为参数。与上面函数功能相同,用法不同。 """clip gradients""" if enable_clip_grad: + # 如果启用全局梯度,则使用C.clip_by_global_norm函数对梯度进行裁剪 if IS_ENABLE_GLOBAL_NORM: gradients = C.clip_by_global_norm(gradients, GRADIENT_CLIP_VALUE, None) + # 否则,使用hyper_map_op函数对梯度进行裁剪 else: gradients = hyper_map_op(F.partial(clip_grad, GRADIENT_CLIP_TYPE, GRADIENT_CLIP_VALUE), gradients) + # 返回裁剪后的梯度张量列表 return gradients - +# 定义全局变量C0(矩阵行列上限)为16用于矩阵计算 C0 = 16 def _check_param(momentum, frequency, lr, cls_name): + # 检查参数是否符合预期 """Check param.""" + # 检查参数momentum的类型是否为float Validator.check_value_type("momentum", momentum, [float], cls_name) + # 检查参数momentum是否为float类型,且小于0 if isinstance(momentum, float) and momentum < 0.0: - raise ValueError("For 'thor', the argument 'momentum' should be at least 0.0, " - "but got 'momentum' {}.".format(momentum)) + # 若不是则抛出异常ValueError + raise ValueError("For 'thor', the argument'momentum' should be at least 0.0, " + "but got'momentum' {}.".format(momentum)) + # 检查参数frequency的类型是否为int Validator.check_value_type("frequency", frequency, [int], cls_name) + # 检查参数frequency是否为int类型,且小于2 if isinstance(frequency, int) and frequency < 2: + # 若不是则抛出异常ValueError raise ValueError("For 'thor', the argument 'frequency' should be at least 2, " "but got 'frequency' {}.".format(frequency)) + # 检查参数lr的类型是否为张量 Validator.check_value_type("learning rate", lr, [Tensor], cls_name) def caculate_device_shape(matrix_dim, channel, is_a): + ''' + 计算设备形状 + :param matrix_dim: 矩阵大小 + :param channel: 通道数 + :param is_a: 是否为矩阵A + :return: 设备形状 + ''' + # 如果是矩阵A if is_a: + # 首先检查channel是否可以被C0整除 if channel // C0 == 0: + # 如果能,那么将matrix_dim除以channel乘以C0。这样做的目的是为了保证矩阵的行和列数量不超过C0,以避免在后续计算中出现溢出问题 matrix_dim = (matrix_dim / channel) * C0 + # 然后计算设备形状,即一个四元组,其中第一维和第二维表示矩阵的行和列,第三维和第四维表示矩阵的通道数 ll = (int(matrix_dim // C0), int(matrix_dim // C0), C0, C0), int(matrix_dim) + # 最后,函数返回计算得到的设备形状 return ll def is_conv_matmul_support_shape(matrix_a_shape, matrix_g_shape): + # 检查卷积层矩阵乘法是否支持给定的矩阵形状 """is conv layer matmul support shape""" + # 将matrix_g_shape(矩阵A的形状)和matrix_a_shape(矩阵G的形状)分别赋值给temp temp = (matrix_g_shape, matrix_a_shape) + # 创建一个列表,用来存储支持卷积层矩阵乘法形状的例子的shape support_shape = [((4, 4, 16, 16), (49, 49, 16, 16)), ((4, 4, 16, 16), (4, 4, 16, 16)), ((4, 4, 16, 16), (36, 36, 16, 16)), @@ -148,24 +242,33 @@ def is_conv_matmul_support_shape(matrix_a_shape, matrix_g_shape): ((128, 128, 16, 16), (32, 32, 16, 16)), ((128, 128, 16, 16), (64, 64, 16, 16)), ((32, 32, 16, 16), (128, 128, 16, 16))] + # 检查给定的矩阵形状是否存在于support_shape列表中,如果temp在支持的shape列表中,则返回True if temp in support_shape: return True + # 否则返回False return False def caculate_matmul_shape(matrix_a_dim, matrix_g_dim, split_dim): + # 计算矩阵乘法所需的形状 """get matmul shape""" + # 传入分割维度split_dim split_dima = split_dim split_dimg = split_dim + # 检查matrix_a_dim是否可以被split_dim整除,如果矩阵a的维度能被分割,则batch_w为矩阵a的除以分割维度的商 if matrix_a_dim % split_dim == 0: batch_w = matrix_a_dim // split_dim else: + # 否则,如果matrix_a_dim小于split_dim if matrix_a_dim < split_dim: + # 那么将split_dima设置为matrix_a_dim,并将批量宽度设置为1 batch_w = 1 split_dima = matrix_a_dim else: + # 计算矩阵A的批量宽度,即矩阵A的行数除以分割维度得到的值加1 batch_w = matrix_a_dim // split_dim + 1 + # 重复上述操作 if matrix_g_dim % split_dim == 0: batch_h = matrix_g_dim // split_dim else: @@ -174,79 +277,153 @@ def caculate_matmul_shape(matrix_a_dim, matrix_g_dim, split_dim): split_dimg = matrix_g_dim else: batch_h = matrix_g_dim // split_dim + 1 + # 计算矩阵a的形状 matrix_a_shape = (batch_h, batch_w, split_dima, split_dima) + # 计算矩阵g的形状 matrix_g_shape = (batch_h, split_dimg, split_dimg) + # 返回矩阵a的形状和矩阵g的形状 return matrix_a_shape, matrix_g_shape def get_layer_type_for_dense_and_conv(subcell, prefix, layertype_map): + # 根据子层和前缀获取层类型, + # 参数:subcell(子层),prefix(前缀)和layertype_map(层类型映射) """get layer type for dense layer and conv layer""" + # 检查subcell的权重是否需要梯度 if subcell.weight.requires_grad: + # 如果需要梯度,那么检查前缀是否包含rpn_with_loss.rpn_convs_list与rpn_with_loss.rpn_convs_list.0. if "rpn_with_loss.rpn_convs_list." not in prefix.lower() \ or "rpn_with_loss.rpn_convs_list.0." in prefix.lower(): + # 如果不包含前者或包含后者,那么将层类型添加到layertype_map中 layertype_map.append(Other) def find_net_layertype_recur(net, layertype_map): + # 函数用于递归地获取网络的层类型 + # 参数:net(网络)和layertype_map(层类型映射) """get net layer type recursively.""" + # 首先获取网络的所有子cell(子层) cells = net.name_cells() + # 遍历cell for name in cells: + # 获取cell名称 subcell = cells[name] + # 获取cell的前缀 prefix = subcell.param_prefix + # 如果cell为网络,则跳过 if subcell == net: continue + # 如果cell为卷积层,则将卷积层类型添加到layertype_map中 elif isinstance(subcell, Conv2dThor): layertype_map.append(Conv) + # 如果cell为全连接层,则将全连接层类型添加到layertype_map中 elif isinstance(subcell, DenseThor): layertype_map.append(FC) + # 如果cell为Embedding层,则将Embedding层类型添加到layertype_map中 elif isinstance(subcell, (EmbeddingThor, EmbeddingLookupThor)): layertype_map.append(Embedding) + # 如果cell为LayerNorm层,则将LayerNorm层类型添加到layertype_map中 elif isinstance(subcell, nn.LayerNorm): layertype_map.append(LayerNorm) + # 如果cell为BatchNorm2d层,则如果gamma的可训练性被设置,则将BatchNorm层类型添加到layertype_map中 elif isinstance(subcell, nn.BatchNorm2d): if subcell.gamma.requires_grad: + # 如果gamma参数是requires_grad的,则将BatchNorm添加到layertype_map中 layertype_map.append(BatchNorm) + # 如果cell为卷积层和全连接层,则递归调用find_net_layertype_recur函数 elif isinstance(subcell, (nn.Conv2d, nn.Dense, nn.Embedding, nn.Conv2dTranspose, nn.Conv1d, nn.Conv1dTranspose, nn.BatchNorm1d, nn.GroupNorm, nn.GlobalBatchNorm)): if isinstance(subcell, (nn.Dense, nn.Conv2d)): + # 判断subcell是否是nn.Dense或nn.Conv2d的实例 get_layer_type_for_dense_and_conv(subcell, prefix, layertype_map) else: + # 如果不是,则将Other添加到layertype_map中 layertype_map.append(Other) + # 其他cell,则递归调用find_net_layertype_recur函数 else: find_net_layertype_recur(subcell, layertype_map) def get_net_layertype_mask(net): + ''' + 获取网络层类型的掩码 + ''' + # 创建一个空列表layertype_map layertype_map = [] + # 调用find_net_layertype_recur函数递归地获取网络的层类型,并将结果添加到layertype_map中 find_net_layertype_recur(net, layertype_map) + # 返回获取到的掩码 return layertype_map def get_layer_counter(layer_type, layer_counter, params, idx): + # 计算特定层类型的层计数器 """get layer counter""" + # 首先检查layer_type是否在[Conv, FC, LayerNorm, BatchNorm]中 + # 如果layer_type是Conv(卷积层)或FC(全连接层) if layer_type in [Conv, FC]: + # 如果params[idx]的name是bias if "bias" in params[idx].name.lower(): + # 将layer_counter加1 layer_counter = layer_counter + 1 + # 否则 else: + # 如果idx小于params的长度-1,且params[idx+1]的name不是bias if idx < len(params) - 1 and "bias" not in params[idx + 1].name.lower(): + # 将layer_counter加1 layer_counter = layer_counter + 1 + # 如果layer_type是LayerNorm(归一化层)或BatchNorm(批量归一化层) elif layer_type in [LayerNorm, BatchNorm]: + # 如果params[idx]的name是beta if "beta" in params[idx].name.lower(): + # 将layer_counter加1 layer_counter = layer_counter + 1 + # 计算其他情况 else: + # 如果params[idx]的name是bias if "bias" in params[idx].name.lower(): + # 将layer_counter加1 layer_counter = layer_counter + 1 + # 否则 elif "weight" in params[idx].name.lower(): + # 如果idx小于params的长度-1,且params[idx+1]的name不是bias if idx < len(params) - 1 and "bias" not in params[idx + 1].name.lower(): + # 将layer_counter加1 layer_counter = layer_counter + 1 + # 否则 else: + # 将layer_counter加1 layer_counter = layer_counter + 1 + # 返回layer_counter(层计数器) return layer_counter def thor(net, learning_rate, damping, momentum, weight_decay=0.0, loss_scale=1.0, batch_size=32, use_nesterov=False, decay_filter=lambda x: x.name not in [], split_indices=None, enable_clip_grad=False, frequency=100): + # THOR算法是一种基于跟踪的硬件驱动的层正则化自然梯度下降算法,由清华大学 KEG 实验室提出。 + # THOR支持GPU和Ascend,分别为class THOR_GPU(Optimizer)和 class THOR_Ascend(Optimizer) + """ + 在THOR算法中,更新梯度的公式如下: + + .. math:: + \begin{array}{ll} + & \textbf{Parameter:} : \text{the learning rate } \gamma\text{, the damping parameter }\lambda \ + & \textbf{Init:} : \lambda \leftarrow 0 \ + & A_{i-1}=\mathbb{E}\left[a_{i-1} a_{i-1}^{T}\right] \ + & G_{i}=\mathbb{E}\left[D_{s_i} D_{s_i}^{T}\right] \ + & w_{i}^{(k+1)} \leftarrow w_{i}^{(k)}-\gamma\left(\left(A_{i-1}^{(k)}+\lambda I\right)^{-1} + \otimes\left(G_{i}^{(k)}+\lambda I\right)^{-1}\right) \nabla_{w_{i}} J^{(k)} + \end{array} + + 其中,:math:a_{i-1}表示第i-1层的输入,表示前一层激活值, + :math:D_{s_i}表示第i层的导数,表示输出层相对于输入层的导数, + :math:I表示单位矩阵, + :math:\lambda表示damping参数,:math:g_i表示第i层的梯度, + :math:\otimes表示Kronecker乘积,:math:\gamma表示学习率。 + + 这个函数的主要作用是根据给定的参数和梯度更新模型参数。它首先计算前一层和当前层之间的关系,然后使用THOR算法更新当前层的参数。 + """ r""" Updates gradients by second-order algorithm--THOR. @@ -357,17 +534,22 @@ def thor(net, learning_rate, damping, momentum, weight_decay=0.0, loss_scale=1.0 >>> model.train(1, dataset, callbacks=loss_cb, sink_size=4, dataset_sink_mode=True) """ + # 设置Python上下文的最大递归深度为10000。这在某些情况下可能需要设置以避免递归深度过深导致的栈溢出问题 context.set_context(max_call_depth=10000) + # 将给定的网络模型net转换为THOR算法的网络模型。THOR算法是一种基于跟踪的硬件驱动的层正则化自然梯度下降算法 ConvertNetUtils().convert_to_thor_net(net) + # 如果当前设备的target为Ascend,则返回ThorAscend类的实例,ThorAscend类是一个实现THOR算法的高层API,用于在Ascend设备上运行模型 if context.get_context("device_target") == "Ascend": return ThorAscend(net, learning_rate, damping, momentum, weight_decay, loss_scale, batch_size, decay_filter, split_indices=split_indices, enable_clip_grad=enable_clip_grad, frequency=frequency) + # 否则返回ThorGpu类的实例,ThorGpu类是一个实现THOR算法的高层API,用于在Gpu设备上运行模型(下方类) return ThorGpu(net, learning_rate, damping, momentum, weight_decay, loss_scale, batch_size, use_nesterov, decay_filter, split_indices=split_indices, enable_clip_grad=enable_clip_grad, frequency=frequency) class ThorGpu(Optimizer): + # ThorGpu类主要用于在GPU设备上运行THOR算法 """ ThorGpu """ @@ -375,934 +557,1706 @@ class ThorGpu(Optimizer): def __init__(self, net, learning_rate, damping, momentum, weight_decay=0.0, loss_scale=1.0, batch_size=32, use_nesterov=False, decay_filter=lambda x: x.name not in [], split_indices=None, enable_clip_grad=False, frequency=100): + # 1.初始化THOR算法的相关参数,定义了一些基本函数(如获取学习率,梯度缩放,权重衰减等)。 + # THOR初始化时将传进去的超参定义为类属性方便调用,并且定义了后续计算会使用到的算子 + """ + net:本次训练建立的模型; + learning_rate:学习率超参; + damping:二阶矩阵中加的正则化项的超参; + momentum:动量超参; + weight_decay:权值衰减,用于防止过拟合,默认值为0.0,即不使用权值衰减; + loss_scale:用于缩放训练过程中的loss,防止梯度越界,默认值为1.0,即不使用缩放; + batch_size:当前训练一个step所使用的数据量,默认为32; + decay_filter:选择对哪些层做weight decay,当weight_decay>0时起作用; + split_indices:这个参数的作用是用于加速allreduce过程。 + _get_Ainv_Ginv_Amax_Gmax_list函数用于计算协方差矩阵A/G的逆,并返回求完逆后的矩阵。具体过程是遍历模型所有层,按层处理,对每一层的协方差矩阵加上正则化项,然后对矩阵进行cholesky分解从而来求逆。当前开源代码THOR中支持全连接层和卷积层的处理。 + """ + # 2.使用Python的filter函数过滤出net中的所有需要计算梯度的参数 params = filter(lambda x: x.requires_grad, net.get_parameters()) super(ThorGpu, self).__init__(learning_rate, params, weight_decay, loss_scale) + # 3.检查momentum等参数是否符合要求,(固定步骤) _check_param(momentum, frequency, learning_rate, self.__class__.__name__) + # 创建参数momentum self.momentum = Parameter(Tensor(momentum, mstype.float32), name="momentum") + # 创建参数params self.params = self.parameters + # 判断use_nesterov是否为布尔值 self.use_nesterov = Validator.check_bool(use_nesterov) + # 创建参数moments,init='zeros'表示使用全零初始化 self.moments = self.params.clone(prefix="moments", init='zeros') + # 创建参数hyper_map用于处理张量操作 self.hyper_map = C.HyperMap() + # 创建参数opt,并定义是否是否使用Nesterov动量 self.opt = P.ApplyMomentum(use_nesterov=self.use_nesterov) + # 创建参数net self.net = net - self.matrix_a_cov = ParameterTuple(filter(lambda x: 'matrix_a' in x.name, net.get_parameters())) - self.matrix_g_cov = ParameterTuple(filter(lambda x: 'matrix_g' in x.name, net.get_parameters())) + # 从网络中获取参数并传入创建参数matrix_a_cov + self.matrix_a_cov = ParameterTuple(filter(lambda x:'matrix_a' in x.name, net.get_parameters())) + # 从网络中获取参数并传入创建参数matrix_g_cov + self.matrix_g_cov = ParameterTuple(filter(lambda x:'matrix_g' in x.name, net.get_parameters())) + # 从网络中获取参数并传入创建参数a_normalizer self.a_normalizer = ParameterTuple(filter(lambda x: 'a_normalizer' in x.name, net.get_parameters())) + # 从网络中获取参数并传入创建参数g_normalizer self.g_normalizer = ParameterTuple(filter(lambda x: 'g_normalizer' in x.name, net.get_parameters())) + # 创建合适大小的参数batch_size self.batch_size = Tensor(batch_size, mstype.float32) + # 创建参数loss_scale self.loss_scale = Tensor(1 / (loss_scale * loss_scale), mstype.float32) + # 创建参数batch_size_scale self.batch_size_scale = Tensor(batch_size * batch_size, mstype.float32) + # 创建参数damping self.damping = damping + # 定义了一个_define_gpu_operator方法,用于定义在GPU设备上的操作 self._define_gpu_operator() + # 这段代码日志记录了matrix_a_cov的长度。logger.info方法用于记录信息的,其参数为一个字符串,通常用于表示信息的摘要。 + # 这里,代码记录了matrix_a_cov的长度,以便在后续的日志中使用 logger.info("matrix_a_cov len is {}".format(len(self.matrix_a_cov))) + # 标志为thor优化器 self.thor = True + # 初始化matrix_a self.matrix_a = () + # 初始化matrix_g self.matrix_g = () + # 初始化matrix_a_shape self.matrix_a_shape = () + # 初始化thor_layer_count self.thor_layer_count = 0 + # 初始化conv_layer_count self.conv_layer_count = 0 + # 初始化weight_fim_idx_map self.weight_fim_idx_map = () + # 初始化weight_conv_idx_map self.weight_conv_idx_map = () + # 初始化weight_layertype_idx_map self.weight_layertype_idx_map = () + # 4.定义了一个_process_matrix_init_and_weight_idx_map方法,用于处理矩阵的初始化和权重索引映射 self._process_matrix_init_and_weight_idx_map(self.net) + # 封装参数张量matrix_a,matrix_g self.matrix_a = ParameterTuple(self.matrix_a) self.matrix_g = ParameterTuple(self.matrix_g) + # 将weight_decay赋值给参数self.weight_decay self.weight_decay = weight_decay + # 将参数self.parameters中的每一个参数转换为tuple类型,并过滤掉参数self.decay_flags中的参数 self.decay_flags = tuple(decay_filter(x) for x in self.parameters) + # 将参数self.update_gradient赋值给参数self.update_gradient self.update_gradient = P.UpdateThorGradient(split_dim=self.split_dim) + # 将参数self.enable_clip_grad赋值给参数self.enable_clip_grad self.enable_clip_grad = enable_clip_grad + # 将参数self.frequency赋值给参数self.frequency self.frequency = frequency + # 5.定义了一个_define_gpu_reducer方法,用于定义在GPU设备上的 reduce 操作,reduce操作用于在GPU设备上对张量进行 reduction,以便在训练过程中更新模型参数 self._define_gpu_reducer(split_indices) def get_frequency(self): + # 获取训练过程中的频率 """get thor frequency""" return self.frequency def _define_gpu_operator(self): + # 定义优化器GPU操作 """define gpu operator""" + # 定义transpose操作 self.transpose = P.Transpose() + # 定义shape操作 self.shape = P.Shape() + # 定义reshape操作 self.reshape = P.Reshape() + # 定义matmul操作 self.matmul = P.MatMul() + # 定义assign操作 self.assign = P.Assign() + # 定义mul操作 self.mul = P.Mul() + # 定义GatherV2操作 self.gather = P.GatherV2() + # 定义一个全1的Tensor变量,其类型为mstype.int32。 self.one = Tensor(1, mstype.int32) + # 定义一个值为1.0的Tensor变量,其类型为mstype.float32 self.feature_map = Tensor(1.0, mstype.float32) + # 定义axis变量,其值为0 self.axis = 0 + # 定义一个名为cov_step的参数变量,其初始值为0,类型为mstype.int32,并设置requires_grad=False表示不需要计算梯度 self.cov_step = Parameter(initializer(0, [1], mstype.int32), name="cov_step", requires_grad=False) + # 定义cast操作 self.cast = P.Cast() + # 定义sqrt操作 self.sqrt = P.Sqrt() + # 定义eye操作创建单位矩阵 self.eye = P.Eye() + # 定义一个名为split_dim的变量,其值为128 self.split_dim = 128 + # 定义embedding_cholesky操作 self.embedding_cholesky = P.CholeskyTrsm() + # 定义一个CholeskyTrsm操作,其split_dim参数为self.split_dim self.cholesky = P.CholeskyTrsm(split_dim=self.split_dim) + # 定义一个BatchMatMul操作,其transpose_a参数为True self.vector_matmul = P.BatchMatMul(transpose_a=True) + # 定义一个ReduceSum操作,其keep_dims参数为False self.reduce_sum = P.ReduceSum(keep_dims=False) + # 定义Reciprocal操作计算倒数 self.inv = P.Reciprocal() + # 定义square操作计算张量平方 self.square = P.Square() + # 定义expand操作增加张量的维度 self.expand = P.ExpandDims() def _define_gpu_reducer(self, split_indices): + # 定义GPU reducer + # 参数:split_indices (list) - 按A/G层(A/G含义见上述公式)索引设置allreduce融合策略。仅在分布式计算中有效。 + # 以ResNet50为例,A/G的层数分别为54层,当split_indices设置为[26,53]时,表示A/G被分成两组allreduce,一组为0~26层,另一组是27~53层。 + # 默认值: None + # 注:那么如何确定矩阵分块维度的呢。具体方法为: + # (1)根据费雪矩阵中维度最大的那一层,确定矩阵切分维度,拿ResNet50举例,网络层中的最大维度为2048,确定矩阵切分维度为[1,16,32,64,128,256,512,1024,2048]。 + # (2)根据确定的矩阵维度,根据谱范数计算每个维度下的矩阵损失 + # (3)根据确定的矩阵维度,计算每个维度下的矩阵求逆时间,再通过公式normalizedn = p1/pn得到每个维度下标准化后性能数据,其中p1表示维度最小的矩阵的性能数据,pn表示第n个维度下的性能数据。 + # (4)根据标注化后的矩阵损失信息和标准化后的性能数据绘图 + """define gpu reducer""" + # 获取并设置自动并行上下文中的并行模式 self.parallel_mode = context.get_auto_parallel_context("parallel_mode") - self.is_distributed = (self.parallel_mode != ParallelMode.STAND_ALONE) + # 根据自动并行上下文判断是否为分布式 + self.is_distributed = (self.parallel_mode!= ParallelMode.STAND_ALONE) + # 如果是分布式,则设置梯度汇总器 if self.is_distributed: + # 获取梯度平均值 mean = _get_gradients_mean() + # 获取设备数量 degree = _get_device_num() + # 如果没有指定分割索引,则设置分割索引为数组的长度减1 if not split_indices: self.split_indices = [len(self.matrix_a_cov) - 1] + # 否则设置分割索引 else: self.split_indices = split_indices + # 设置自动并行上下文的所有梯度汇总器的分割索引并命名 auto_parallel_context().set_all_reduce_fusion_split_indices(self.split_indices, "hccl_world_groupsum6") auto_parallel_context().set_all_reduce_fusion_split_indices(self.split_indices, "hccl_world_groupsum8") + # 设置梯度汇总器,用于分布式梯度 reduction self.grad_reducer_a = DistributedGradReducer(self.matrix_a_cov, mean, degree, fusion_type=6) self.grad_reducer_g = DistributedGradReducer(self.matrix_a_cov, mean, degree, fusion_type=8) def _process_matrix_init_and_weight_idx_map(self, net): + # 初始化矩阵matrix_a和matrix_g,以及构建权重索引映射weight_fim_idx_map """for GPU, process matrix init shape, and get weight idx map""" + # 获取网络层类型的掩码保存到参数layer_type_map中 layer_type_map = get_net_layertype_mask(net) + # 层计数器初始化为0 layer_counter = 0 + # 遍历参数组 for idx in range(len(self.params)): + # 获取当前层的类型 layer_type = layer_type_map[layer_counter] + # 获取当前层的权重 weight = self.params[idx] + # 获取当前层的权重形状 weight_shape = self.shape(weight) + # 根据网络层类型和权重名称,判断是否需要初始化矩阵matrix_a和matrix_g if layer_type in [Conv, FC] and "bias" not in self.params[idx].name.lower(): + # 定义了两个变量in_channels和out_channels,分别表示输入通道数和输出通道数 in_channels = weight_shape[1] out_channels = weight_shape[0] + # 定义了一个变量matrix_a_dim,表示矩阵matrix_a的维度,即输入通道数 matrix_a_dim = in_channels + # 在层类型为卷积层时。计算矩阵matrix_a的维度,即输入通道数乘以权重矩阵的宽度和高度 if layer_type == Conv: + # 计算矩阵matrix_a的维度 matrix_a_dim = in_channels * weight_shape[2] * weight_shape[3] + # 计算matrix_g的维度,即输出通道数 matrix_g_dim = out_channels + # 调用caculate_matmul_shape函数,根据matrix_a_dim、matrix_g_dim和self.split_dim(维度分割)计算matrix_a_shape(形状)和matrix_g_shape matrix_a_shape, matrix_g_shape = caculate_matmul_shape(matrix_a_dim, matrix_g_dim, self.split_dim) + # 初始化矩阵matrix_a_inv matrix_a_inv = Parameter(np.zeros(matrix_a_shape).astype(np.float32), name='matrix_a_inv_' + str(self.thor_layer_count), requires_grad=False) + # 初始化矩阵matrix_g_inv matrix_g_inv = Parameter(np.zeros(matrix_g_shape).astype(np.float32), name="matrix_g_inv_" + str(self.thor_layer_count), requires_grad=False) + # 并将它们添加到matrix_a和matrix_g中 + # 更新matrix_a self.matrix_a = self.matrix_a + (matrix_a_inv,) + # 更新matrix_g self.matrix_g = self.matrix_g + (matrix_g_inv,) + # 更新matrix_a_shape self.matrix_a_shape = self.matrix_a_shape + (matrix_a_shape,) + # 在层类型为嵌入层时执行 elif layer_type == Embedding: + # 获取输入的词汇量大小 vocab_size = weight_shape[0] + # 获取嵌入维度 embedding_size = weight_shape[1] + # 创建参数矩阵matrix_a_inv_,用于存储 inverse matrix matrix_a,并设置参数名为matrix_a_inv_,不需要梯度 matrix_a_inv = Parameter(Tensor(np.zeros([vocab_size]).astype(np.float32)), name='matrix_a_inv_' + str(self.thor_layer_count), requires_grad=False) + # 创建参数矩阵matrix_g_inv_,用于存储 inverse matrix matrix_g,并设置参数名为matrix_g_inv_,不需要梯度 matrix_g_inv = Parameter(Tensor(np.zeros([embedding_size, embedding_size]).astype(np.float32)), name="matrix_g_inv_" + str(self.thor_layer_count), requires_grad=False) + # 将参数matrix_a_inv_和matrix_g_inv_添加到self.matrix_a和self.matrix_g中 self.matrix_a = self.matrix_a + (matrix_a_inv,) self.matrix_g = self.matrix_g + (matrix_g_inv,) + # 将参数matrix_a_shape_添加到self.matrix_a_shape中 self.matrix_a_shape = self.matrix_a_shape + ((vocab_size,),) + # 判断当前层类型是否在[Conv, FC, Embedding]中,并且权重名称中不包含"bias"。如果满足这两个条件,则执行后续操作 if layer_type in [Conv, FC, Embedding] and "bias" not in self.params[idx].name.lower(): + # 更新权重索引映射weight_fim_idx_map,添加当前层的索引和层类型 self.weight_fim_idx_map = self.weight_fim_idx_map + (self.thor_layer_count,) + # 更新thor_layer_count和conv_layer_count self.thor_layer_count = self.thor_layer_count + 1 + # 将当前层的层类型添加到weight_layertype_idx_map中 self.weight_layertype_idx_map = self.weight_layertype_idx_map + (layer_type,) if layer_type == Conv: + # 如果为卷积层则更新weight_conv_idx_map self.weight_conv_idx_map = self.weight_conv_idx_map + (self.conv_layer_count,) + # 更新卷积层数量 self.conv_layer_count = self.conv_layer_count + 1 else: + # 更新weight_layertype_idx_map self.weight_conv_idx_map = self.weight_conv_idx_map + (-1,) else: + # 如果当前层的类型为LayerNorm,则将其索引设置为-1 self.weight_conv_idx_map = self.weight_conv_idx_map + (-1,) + # 将当前层的索引设置为-1 self.weight_fim_idx_map = self.weight_fim_idx_map + (-1,) + # 如果当前层的类型为LayerNorm,则将其索引设置为LayerNorm if layer_type == LayerNorm: + # 将当前层的索引设置为LayerNorm self.weight_layertype_idx_map = self.weight_layertype_idx_map + (LayerNorm,) else: + # 否则,将当前层的索引设置为Other self.weight_layertype_idx_map = self.weight_layertype_idx_map + (Other,) # bert.cls1.output_bias: not a network layer, only a trainable param + # 调用get_layer_counter函数,根据层类型、层计数器和权重列表,更新层计数器 if "output_bias" not in self.params[idx].name.lower(): layer_counter = get_layer_counter(layer_type, layer_counter, self.params, idx) def _get_ainv_ginv_list(self, gradients, damping_step, matrix_a_allreduce, matrix_g_allreduce): + # 获取矩阵matrix_a的逆列表和矩阵matrix_g的逆列表 """get matrixA inverse list and matrix G inverse list""" + # 1.遍历权重列表中的所有权重 for i in range(len(self.params)): + # 2.分别获取thor_layer_count、conv_layer_count和layer_type thor_layer_count = self.weight_fim_idx_map[i] conv_layer_count = self.weight_conv_idx_map[i] layer_type = self.weight_layertype_idx_map[i] + # 3.然后,根据这些信息,从gradients列表中获取相应的权重梯度,并调用caculate_matmul_shape函数计算矩阵matrix_a和matrix_g的形状 if layer_type in [Conv, FC, Embedding]: + # 获取当前权重梯度 g = gradients[i] + # 计算矩阵A matrix_a = self.matrix_a_cov[thor_layer_count] + # 计算矩阵G matrix_g = self.matrix_g_cov[thor_layer_count] + # 将矩阵A和矩阵G依赖放入矩阵A matrix_a = F.depend(matrix_a, g) + # 将矩阵G和矩阵A依赖放入矩阵G matrix_g = F.depend(matrix_g, g) + # 设置矩阵A的行列数 damping_a = damping_step + # 设置矩阵G的行列数 damping_g = damping_step + # 计算特征映射 feature_map = self.feature_map + # 4.最后,它将这些计算结果添加到相应的列表中 + + # 如果该层为卷积层 if layer_type == Conv: + # 获取卷积层的参数 + # 从self.a_normalizer列表中获取第conv_layer_count个元素的值,并将其赋值给变量a_normalizer。g_normalizer同理 + # 注:self.a_normalizer是一个存储归一化因子(如最大值、最小值等)的列表,用于对权重矩阵matrix_a进行归一化 + # 不同的卷积层可能需要不同的归一化方法,因此需要为每个卷积层维护一个单独的归一化因子 a_normalizer = self.a_normalizer[conv_layer_count] g_normalizer = self.g_normalizer[conv_layer_count] + # 将参数进行依赖关系处理 + # 注:通过将a_normalizer与g进行依赖关系处理,可以确保在计算过程中自动更新a_normalizer的值 a_normalizer = F.depend(a_normalizer, g) g_normalizer = F.depend(g_normalizer, g) + # 计算damping系数,这个新值将用于更新矩阵matrix_g damping_a = self.mul(damping_step, 1.0 / a_normalizer) damping_g = self.mul(damping_step, 1.0 / g_normalizer) + # 计算feature map + # 首先,它计算矩阵matrix_a的逆平方根,即feature_map等于 1.0 / a_normalizer的平方根。 + # 注:这种计算方法可以有效地对权重矩阵进行归一化,使其特征映射的方差为 1 feature_map = self.sqrt(1.0 / a_normalizer) + # 获取矩阵a的形状 a_shape = self.shape(matrix_a) + # 创建一个长度为a_shape[0]的矩阵,其中每个元素都为1 a_eye = self.eye(a_shape[0], a_shape[0], mstype.float32) + # 对矩阵a进行开方操作 damping_a = self.sqrt(damping_a) + # 对矩阵g进行开方操作 damping_g = self.sqrt(damping_g) + # 获取矩阵matrix_g的形状,并将其赋值给变量g_shape g_shape = self.shape(matrix_g) + # 创建一个长度为a_shape[0]*a_shape[1]的单位矩阵,用于将矩阵matrix_g的每一行进行归一化。单位矩阵是一个对角线为 1,其他元素为 0 的矩阵 + # 注:可以确保在计算过程中,矩阵matrix_g的每一行都被归一化到相同的范围 g_eye = self.eye(g_shape[0], g_shape[1], mstype.float32) + # 首先将矩阵matrix_g与一个常数self.loss_scale相乘, + # 然后与一个常数self.batch_size_scale相乘,最后将结果与矩阵damping_g的逆平方根相加。 + # 这样做可以有效地对权重梯度矩阵进行归一化,并保持其方差为 1 matrix_g = self.mul(matrix_g, self.loss_scale) matrix_g = self.mul(matrix_g, self.batch_size_scale) matrix_g = matrix_g + damping_g * g_eye + # 如果该层为嵌入层 if layer_type == Embedding: + # 创建一个全为 1 的矩阵,其形状与矩阵matrix_a相同。这样,将矩阵matrix_a与全为 1 的矩阵相乘时,结果将保持矩阵matrix_a的形状 a_eye = P.OnesLike()(matrix_a) + # 将矩阵matrix_a的每个元素除以常数self.batch_size,从而将矩阵matrix_a的每个元素归一化到相同的范围。 + # 这样,在计算过程中,矩阵matrix_a的每个元素都会被归一化到相同的范围,有助于提高模型的训练效果 matrix_a = self.mul(matrix_a, 1.0 / self.batch_size) + # 将matrix_a的值加上damping_a与a_eye矩阵的乘积,这种方法可以有效地对权重矩阵进行归一化,并保持其方差为 1 matrix_a = matrix_a + damping_a * a_eye + # 将matrix_a的值转换为逆矩阵 matrix_a = self.inv(matrix_a) + # 使用self.embedding_cholesky方法对矩阵matrix_g进行高斯过程嵌入 matrix_g = self.embedding_cholesky(matrix_g) + # 使用self.matmul方法将矩阵matrix_g与其自身的转置相乘 + # 注:这种方法可以有效地对权重梯度矩阵进行正交化,并保持其特征值均为正 matrix_g = self.matmul(matrix_g, matrix_g) else: + # 将matrix_a的值加上damping_a与a_eye矩阵的乘积,这种方法可以有效地对权重矩阵进行归一化,并保持其方差为 1 matrix_a = matrix_a + damping_a * a_eye + # 使用self.cholesky方法对矩阵matrix_a进行高斯过程嵌入。高斯过程嵌入是一种用于将非正定矩阵正交化的方法,它可以提高模型的训练效果 matrix_a = self.cholesky(matrix_a) + # 将矩阵matrix_a与其自身的转置相乘。这种方法可以有效地对权重矩阵进行正交化,并保持其特征值均为正 matrix_a = self.vector_matmul(matrix_a, matrix_a) + # 将矩阵matrix_a广播到与self.matrix_a_shape[thor_layer_count]相同的大小。广播是一种用于处理不同形状的张量之间的操作的机制,它允许在不同形状的张量之间进行元素级别的操作 matrix_a = P.BroadcastTo(self.matrix_a_shape[thor_layer_count])(matrix_a) + # 使用self.cholesky方法对矩阵matrix_g进行高斯过程嵌入 matrix_g = self.cholesky(matrix_g) + # 将矩阵matrix_g与其自身的转置相乘 matrix_g = self.vector_matmul(matrix_g, matrix_g) + # 使用self.mul方法将矩阵matrix_a与特征映射feature_map相乘 matrix_a = self.mul(matrix_a, feature_map) + # 使用self.mul方法将矩阵matrix_g与特征映射feature_map相乘 matrix_g = self.mul(matrix_g, feature_map) + # 将矩阵matrix_a和matrix_g的元组添加到matrix_a_allreduce和matrix_g_allreduce中 + # 注:这种方法可以用于在多个 GPU 设备上同步地计算矩阵乘法,以提高模型的训练速度 matrix_a_allreduce = matrix_a_allreduce + (matrix_a,) matrix_g_allreduce = matrix_g_allreduce + (matrix_g,) + # 返回一个包含两个张量的元组,分别是matrix_a_allreduce和matrix_g_allreduce。这两个张量是在多个 GPU 设备上同步计算得到的矩阵乘法的结果,这样可以提高模型的训练速度 return matrix_a_allreduce, matrix_g_allreduce def _process_layernorm(self, damping_step, gradient): + # 处理层归一化。层归一化是一种常用的预处理方法,用于对神经网络中的权重矩阵进行归一化,以提高模型的训练效果 """process layernorm""" + # 计算指数步长,damping 值用于控制 FIM(Fisher Information Matrix)的更新速度,以避免在训练过程中出现数值问题 damping = self.sqrt(damping_step) + # 计算归一化系数normalizer,将其转换为 float32 类型 normalizer = self.batch_size normalizer = self.cast(normalizer, mstype.float32) + # 计算指数步长的指数矩阵 fim_cov = self.square(gradient) + # 将其与归一化系数相乘,然后除以 normalizer,这里使用平方和再除以 normalizer 的方法计算 FIM 矩阵,这样可以提高计算效率 fim_cov = self.mul(fim_cov, 1.0 / normalizer) + # 将 FIM 矩阵fim_cov与 damping 值相加 fim_cov = fim_cov + damping + # 计算指数步长的逆矩阵 fim_inv = self.inv(fim_cov) + # 将 FIM 的逆矩阵与梯度相乘,得到更新后的梯度 gradient = self.mul(fim_inv, gradient) + # 返回更新后的梯度 return gradient def _reshape_gradient(self, conv_layer_count, g, g_shape): + # 将梯度greshape 到预期的形状,在深度学习中,通常需要对梯度进行 reshape 操作,以适应不同层的网络结构 """reshape gradient""" - if conv_layer_count != -1: + # 首先,检查conv_layer_count是否不等于-1,如果不等于-1,则使用self.reshape方法将梯度greshape 到预期的形状g_shape + if conv_layer_count!= -1: g = self.reshape(g, g_shape) + # 返回 reshaped 后的梯度g return g def construct(self, gradients): + # 获取参数 params = self.params + # 获取模型中的moments(动量)。 + # 注:moments是一种优化方法,用于加速深度学习模型的训练过程。 + # 在训练过程中,moments会记录每个参数的移动平均值,并在每次更新参数时使用这些移动平均值来更新参数。 + # 这样可以提高模型的训练速度,减少训练过程中的波动 moments = self.moments + # 将梯度缩放为合适的值 gradients = self.scale_grad(gradients) + # 从self.damping中获取damping_step,然后将其转换为 float32 类型 damping_step = self.gather(self.damping, self.cov_step, self.axis) damping_step = self.cast(damping_step, mstype.float32) + # 接下来,初始化一个空元组new_grads,用于存储计算得到的新的梯度 new_grads = () + # 如果是thor优化器 if self.thor: + # 定义了两个空列表,用于存储矩阵的逆矩阵 matrix_ainv_list = () matrix_ginv_list = () + # 计算矩阵的逆矩阵。首先,它从 gradients 列表中获取所有梯度,damping_step 作为 damping 系数,并将结果存储在 matrix_ainv_list 和 matrix_ginv_list 两个列表中 matrix_a_allreduce, matrix_g_allreduce = self._get_ainv_ginv_list(gradients, damping_step, matrix_ainv_list, matrix_ginv_list) + # 如果是分布式训练,则将矩阵反向传播列表转换为分布式矩阵反向传播列表 if self.is_distributed: matrix_a_allreduce = self.grad_reducer_a(matrix_a_allreduce) matrix_g_allreduce = self.grad_reducer_g(matrix_g_allreduce) + # 遍历参数 for i in range(len(self.params)): + # 获取梯度 g = gradients[i] + # 根据层类型增加对应的层计数器 thor_layer_count = self.weight_fim_idx_map[i] conv_layer_count = self.weight_conv_idx_map[i] layer_type = self.weight_layertype_idx_map[i] + # 如果是卷积层或者全连接层,则获取矩阵反向传播列表 if layer_type in [Conv, FC]: + # 获取g的形状 g_shape = self.shape(g) + # 将梯度的形状从原来的形状转换为 (batch_size, -1),其中 -1 表示自动计算剩余维度。这样,梯度就可以在矩阵乘法中与其他向量正确地相乘 g = self.reshape(g, (g_shape[0], -1)) + # 获取矩阵a和矩阵g的逆矩阵 matrix_a = matrix_a_allreduce[thor_layer_count] matrix_g = matrix_g_allreduce[thor_layer_count] + # 将矩阵g的梯度更新到矩阵a g = self.update_gradient(matrix_g, g, matrix_a) + # 将矩阵 matrix_a 的第 thor_layer_count 行赋值给矩阵 self.matrix_a 的对应行。这样,矩阵 self.matrix_a 的第 thor_layer_count 行就等于矩阵 matrix_a 的逆矩阵 self.assign(self.matrix_a[thor_layer_count], matrix_a) + # 将矩阵 matrix_g 的第 thor_layer_count 行赋值给矩阵 self.matrix_g 的对应行 self.assign(self.matrix_g[thor_layer_count], matrix_g) + # 将矩阵g的梯度转换为卷积层的输入形状 g = self._reshape_gradient(conv_layer_count, g, g_shape) + # 如果是Embedding层,则将矩阵反向传播列表转换为分布式矩阵反向传播列表 elif layer_type == Embedding: + # 从两个列表 matrix_a_allreduce 和 matrix_g_allreduce 中分别获取第 thor_layer_count 行的矩阵 matrix_a 和矩阵 matrix_g。 + # 这样,矩阵 matrix_a 和 matrix_g 的第 thor_layer_count 行就分别等于它们对应的逆矩阵 matrix_a = matrix_a_allreduce[thor_layer_count] matrix_g = matrix_g_allreduce[thor_layer_count] + # 将矩阵 matrix_a 的第 thor_layer_count 行赋值给矩阵 self.matrix_a 的对应行 self.assign(self.matrix_a[thor_layer_count], matrix_a) + # 将矩阵 matrix_g 的第 thor_layer_count 行赋值给矩阵 self.matrix_g 的对应行 self.assign(self.matrix_g[thor_layer_count], matrix_g) + # 将矩阵 matrix_a 扩展为一个二维矩阵,其中行数为原来的列数,列数为 1。这样,矩阵 matrix_a 就可以与向量 g 相乘 temp_a = self.expand(matrix_a, 1) + # 将矩阵 temp_a 与向量 g 相乘,并将结果存储在向量 g 中 g = self.mul(temp_a, g) + # 将向量 g 与矩阵 matrix_g 相乘,并将结果存储在向量 g 中 g = self.matmul(g, matrix_g) + # 如果是LayerNorm层,则用LayerNorm处理 elif layer_type == LayerNorm: + # 对梯度进行处理,包括计算 damping_step(权重衰减步数)以及应用层归一化。首先,计算梯度的导数,然后对梯度进行层归一化,最后将处理后的梯度赋值给 g g = self._process_layernorm(damping_step, g) + # 将更新的梯度添加到新的梯度列表 new_grads = new_grads + (g,) + # 不是thor优化器 else: + # 遍历参数 for j in range(len(self.params)): + # 获取梯度 g = gradients[j] + # 根据层类型增加对应的层计数器 thor_layer_count = self.weight_fim_idx_map[j] conv_layer_count = self.weight_conv_idx_map[j] layer_type = self.weight_layertype_idx_map[j] + # 如果是卷积层或者全连接层,则获取矩阵反向传播列表 if layer_type in [Conv, FC]: + # 获取g的形状 g_shape = self.shape(g) + # 将梯度的形状从原来的形状转换为 (batch_size, -1),其中 -1 表示自动计算剩余维度 g = self.reshape(g, (g_shape[0], -1)) + # 从两个列表 matrix_a_allreduce 和 matrix_g_allreduce 中分别获取第 thor_layer_count 行的矩阵 matrix_a 和矩阵 matrix_g。 + # 这样,矩阵 matrix_a 和 matrix_g 的第 thor_layer_count 行就分别等于它们对应的逆矩阵 matrix_a = self.matrix_a[thor_layer_count] matrix_g = self.matrix_g[thor_layer_count] + # 将矩阵g的梯度更新到矩阵a g = self.update_gradient(matrix_g, g, matrix_a) + # 将矩阵g的梯度转换为卷积层的输入形状 g = self._reshape_gradient(conv_layer_count, g, g_shape) + # 如果是Embedding层,则将矩阵反向传播列表转换为分布式矩阵反向传播列表 elif layer_type == Embedding: + # 从两个列表 matrix_a_allreduce 和 matrix_g_allreduce 中分别获取第 thor_layer_count 行的矩阵 matrix_a 和矩阵 matrix_g。 + # 这样,矩阵 matrix_a 和 matrix_g 的第 thor_layer_count 行就分别等于它们对应的逆矩阵 matrix_a = self.matrix_a[thor_layer_count] matrix_g = self.matrix_g[thor_layer_count] + # 获取梯度 g = gradients[j] + # 将矩阵 matrix_a 扩展为一个二维矩阵,其中行数为原来的列数,列数为 1。这样,矩阵 matrix_a 就可以与向量 g 相乘 temp_a = self.expand(matrix_a, 1) + # 将矩阵 temp_a 与向量 g 相乘,并将结果存储在向量 g 中 g = self.mul(temp_a, g) + # 将向量 g 与矩阵 matrix_g 相乘,并将结果存储在向量 g 中 g = self.matmul(g, matrix_g) + # 如果是LayerNorm层,则用LayerNorm处理 elif layer_type == LayerNorm: + # 对梯度进行处理,包括计算 damping_step(权重衰减步数)以及应用层归一化 g = self._process_layernorm(damping_step, g) + # 将更新的梯度添加到新的梯度列表 new_grads = new_grads + (g,) + # 更新计算完成的梯度 gradients = new_grads + # 计算权重矩阵的协方差矩阵的步数,即将 self.cov_step 加上 1 self.cov_step = self.cov_step + self.one + # 检查 self.weight_decay 是否大于 0 if self.weight_decay > 0: + # 如果是,则使用 F.partial() 函数创建一个 partial 函数,该函数接受 self.weight_decay 和 params 作为参数,并应用权重衰减,然后,将梯度应用权重衰减,并将结果存储在 gradients 中 gradients = self.hyper_map(F.partial(apply_decay, self.weight_decay), self.decay_flags, params, gradients) + # 最后,使用 clip_gradient() 函数根据 self.enable_clip_grad 的值对梯度进行裁剪 gradients = clip_gradient(self.enable_clip_grad, gradients) + # 获取学习率 lr = self.get_lr() + # 使用动态权重优化方法(如动量优化)来更新模型参数。首先,使用 F.partial() 函数创建一个 partial 函数, + # 该函数接受 self.opt、self.momentum 和 lr 作为参数,并使用这些参数进行优化。然后,将优化后的参数和动量存储在 moments 中 success = self.hyper_map(F.partial(_momentum_opt, self.opt, self.momentum, lr), gradients, params, moments) + # 检查优化是否成功 return success class ThorAscend(Optimizer): + # ThorAscend类主要用于在Ascend设备上运行THOR算法,用于实现对神经网络 net 的训练 """ThorAscend""" + """ + 属性: + params:包含模型中所有需要梯度的参数的元组。 + moments:包含模型中所有动量向量的元组。 + hyper_map:用于处理梯度的张量操作图。 + opt:用于实现动量优化的优化器。 + net:包含模型结构的神经网络对象。 + matrix_a_cov:包含模型中所有权重矩阵的协方差矩阵的参数元组。 + matrix_g_cov:包含模型中所有权重矩阵的梯度协方差矩阵的参数元组。 + a_normalizer:包含模型中所有权重矩阵的归一化矩阵的参数元组。 + g_normalizer:包含模型中所有权重矩阵的梯度归一化矩阵的参数元组。 + + """ def __init__(self, net, learning_rate, damping, momentum, weight_decay=0.0, loss_scale=1.0, batch_size=32, decay_filter=lambda x: x.name not in [], split_indices=None, enable_clip_grad=False, frequency=100): + # 初始化参数 params = filter(lambda x: x.requires_grad, net.get_parameters()) super(ThorAscend, self).__init__(learning_rate, params, weight_decay, loss_scale) + # 检查参数 _check_param(momentum, frequency, learning_rate, self.__class__.__name__) + # 创建参数 self.momentum = Parameter(Tensor(momentum, mstype.float32), name="momentum") self.params = self.parameters self.moments = self.params.clone(prefix="moments", init='zeros') + # 创建网络 self.hyper_map = C.HyperMap() self.opt = P.ApplyMomentum() self.net = net - self.matrix_a_cov = ParameterTuple(filter(lambda x: 'matrix_a' in x.name, net.get_parameters())) - self.matrix_g_cov = ParameterTuple(filter(lambda x: 'matrix_g' in x.name, net.get_parameters())) + # 创建矩阵A和G + self.matrix_a_cov = ParameterTuple(filter(lambda x:'matrix_a' in x.name, net.get_parameters())) + self.matrix_g_cov = ParameterTuple(filter(lambda x:'matrix_g' in x.name, net.get_parameters())) + # 创建A和G的标准化器 self.a_normalizer = ParameterTuple(filter(lambda x: 'a_normalizer' in x.name, net.get_parameters())) self.g_normalizer = ParameterTuple(filter(lambda x: 'g_normalizer' in x.name, net.get_parameters())) + # 输出调试信息 logger.info("matrix_a_cov len is {}".format(len(self.matrix_a_cov))) + # 定义一个名为 _define_ascend_operator 的方法,用于在 Ascend 设备上运行模型 self._define_ascend_operator() + # 表示第一个卷积层的输出通道数 self.C0 = 16 + # 表示设备形状补全标志 self.device_shape_pad_flag = () + # 表示对角块的维度 self.diag_block_dim = 128 + # 表示权重矩阵 self.matrix_a = () self.matrix_g = () + # 表示 THOR 层和卷积层的数量 self.thor_layer_count = 0 self.conv_layer_count = 0 + # 存储权重矩阵的卷积层索引映射到空元组 self.weight_conv_idx_map = () + # 存储权重矩阵的 FIM(特征信息矩阵)索引映射到空元组 self.weight_fim_idx_map = () + # 于存储权重矩阵的层类型和索引映射到空元组 self.weight_layertype_idx_map = () + # 存储权重矩阵的拆分和填充维度映射到空元组 self.a_split_pad_dim_map = () + # 将填充维度映射到空元组 self.g_split_pad_dim_map = () + # 将卷积矩阵支持映射到空元组 self.conv_matmul_support_map = () + # 用于存储批量矩阵乘法(Batch Matrix Multiplication,BMM)的支持列表 self.batch_matmul_support_list = [1, 2, 4, 5, 6, 8, 9, 16, 18, 24, 32, 36] + # 用于存储绝对最大值支持列表 + # 注:这些列表中的数字表示不同的输入尺寸(即批量大小、通道数和特征图尺寸)组合,用于说明模型是否支持这些输入尺寸 self.abs_max_support_list = [1, 2, 4, 8, 16, 5, 9, 18, 36, 32] + # 处理矩阵的初始化和权重索引映射 self._process_matrix_init_and_weight_idx_map(self.net) + # 将 matrix_a 和 matrix_g 转换为 ParameterTuple 类型的对象,以便在模型中使用。ParameterTuple 是一个包含模型中所有参数的元组,用于在训练过程中对参数进行梯度计算和更新 self.matrix_a = ParameterTuple(self.matrix_a) self.matrix_g = ParameterTuple(self.matrix_g) + # 存储权重矩阵的最大倒数(即模型的倒数) self.matrix_max_inv = () + # 遍历 matrix_max_inv 的长度(即权重矩阵的个数) for i in range(len(self.matrix_a)): + # 将 matrix_max_inv 的每个元素初始化为一个张量,张量的值为 1,数据类型为 float32,并且不需要梯度 self.matrix_max_inv = self.matrix_max_inv + ( Parameter(initializer(1, [1], mstype.float32), name='%s%s' % ("matrix_max", str(i)), requires_grad=False),) + # 将matrix_max_inv转换为ParameterTuple类型 self.matrix_max_inv = ParameterTuple(self.matrix_max_inv) + # 设置为thor优化器 self.thor = True + # 定义权重衰减 self.weight_decay = weight_decay + # decay_filter 函数接收一个参数 x,如果 x 的名字中包含 "weight" 或 "bias",则返回 True,否则返回 False。 + # 然后将这个布尔值转换为元组,作为 decay_flags 的元素。这样,decay_flags 是一个布尔元组,表示模型中的参数是否需要进行权重衰减 self.decay_flags = tuple(decay_filter(x) for x in self.parameters) + # 存储模型的 damping 参数 self.damping = damping + # batch_size 用于存储批次大小 self.batch_size = Tensor(batch_size, mstype.float32) + # loss_scale 用于存储损失的缩放因子 self.loss_scale = Tensor(1 / (loss_scale * loss_scale), mstype.float32) + # batch_size_scale 用于存储批次大小的平方 self.batch_size_scale = Tensor(batch_size * batch_size, mstype.float32) + # 存储一个布尔值,表示是否启用梯度裁剪 self.enable_clip_grad = enable_clip_grad + # 存储梯度裁剪的频率,即每训练 frequency 次迭代后进行梯度裁剪 self.frequency = frequency + # 定义一个用于 reduce 梯度的 Ascend reduction self._define_ascend_reducer(split_indices) def get_frequency(self): + # 获取 frequency 属性的值。这个方法通常用于获取模型在训练过程中梯度裁剪的频率 """get thor frequency""" return self.frequency def _get_pad_dim(self, matrix_dim): + # 获取一个张量的维度,以便将其分割成多个小块 """get diag split pad dim """ split_pad_dim = 0 + # 如果矩阵维度为64 if matrix_dim == 64: + # 返回split_pad_dim return split_pad_dim + # 计算矩阵维度除以diag_block_dim的余数 res = matrix_dim % self.diag_block_dim - if res != 0: + # 如果余数不为0 + if res!= 0: + # 计算split_pad_dim,计算需要添加的填充维度(即 diag_block_dim 减去余数),并返回这个值 split_pad_dim = self.diag_block_dim - res + # 返回split_pad_dim return split_pad_dim def _define_ascend_operator(self): + # 定义一个用于执行 Ascend 操作的集合。这些操作包括矩阵乘法、张量乘法、转置、形状、重塑、元素乘法、对数、指数、开方、 + # gathering、赋值、类型转换、单位矩阵、连接、Cholesky 分解、批量矩阵乘法、TBE 批量矩阵乘法、合并绝对值最大值、矩阵组合、 + # 切片、扩展维度、 reduce_sum、平方、逆矩阵等。这些操作通常用于实现深度学习模型的计算逻辑。 """define ascend operator""" + # 定义并行操作 self.cube_matmul_left = P.CusMatMulCubeFraczLeftCast() + # 定义矩阵乘法操作 self.cube_matmul_left_fc = P.CusMatMulCubeDenseLeft() + # 定义矩阵乘法操作 self.cube_matmul_right_fc = P.CusMatMulCubeDenseRight() + # 定义矩阵乘法操作 self.cube_matmul_right_mul = P.CusMatMulCubeFraczRightMul() + # 定义转置操作 self.transpose = P.Transpose() + # 定义形状操作 self.shape = P.Shape() + # 定义reshape操作 self.reshape = P.Reshape() + # 定义乘法操作 self.mul = P.Mul() + # 定义log操作 self.log = P.Log() + # 定义exp操作 self.exp = P.Exp() + # 定义sqrt操作 self.sqrt = P.Sqrt() + # 定义gather操作 self.gather = P.GatherV2() + # 定义赋值操作 self.assign = P.Assign() + # 定义cast操作 self.cast = P.Cast() + # 定义高斯操作 self.eye = P.Eye() + # 定义concat操作,这个操作具有 0 作为连接轴的属性,表示连接操作是从第一个输入的维度开始,将第二个输入的对应维度连接到第一个输入的对应维度 self.concat = P.Concat(0) + # 定义cholesky操作 self.cholesky = P.CusCholeskyTrsm() + # 定义向量矩阵乘法操作 self.vector_matmul = P.CusBatchMatMul() + # 定义tensorbatch矩阵乘法操作,这个操作具有 transpose_a=True 属性,表示矩阵乘法的转置方向是从左到右 self.tbe_batch_matmul = P.BatchMatMul(transpose_a=True) + # 定义fused_abs_max操作 self.fused_abs_max2 = P.CusFusedAbsMax1() + # 定义矩阵合并操作 self.matrix_combine = P.CusMatrixCombine() + # 定义slice操作 self.slice = P.Slice() + # 定义expand操作 self.expand = P.ExpandDims() + # 定义reduce_sum操作,这个操作具有 keep_dims=False 属性,表示在计算 reduce_sum 时,会直接将结果减小到一维 self.reduce_sum = P.ReduceSum(keep_dims=False) + # 定义平方操作 self.square = P.Square() + # 定义阿尔法操作 self.inv = P.Inv() + # 定义矩阵乘法操作 self.matmul = P.MatMul() + # 定义轴操作 self.axis = 0 + # 定义一个int32类型的变量 self.one = Tensor(1, mstype.int32) + # 定义cov_step参数,存储一个用于记录 covariance 计算步骤的整数变量 self.cov_step = Parameter(initializer(0, [1], mstype.int32), name="cov_step", requires_grad=False) def _define_ascend_reducer(self, split_indices): + # 定义Ascdend reducer + # 参数:split_indices (list) - 按A/G层(A/G含义见上述公式)索引设置allreduce融合策略。仅在分布式计算中有效。 + # 以ResNet50为例,A/G的层数分别为54层,当split_indices设置为[26,53]时,表示A/G被分成两组allreduce,一组为0~26层,另一组是27~53层。 + # 默认值: None + # 注:那么如何确定矩阵分块维度的呢。具体方法为: + # (1)根据费雪矩阵中维度最大的那一层,确定矩阵切分维度,拿ResNet50举例,网络层中的最大维度为2048,确定矩阵切分维度为[1,16,32,64,128,256,512,1024,2048]。 + # (2)根据确定的矩阵维度,根据谱范数计算每个维度下的矩阵损失 + # (3)根据确定的矩阵维度,计算每个维度下的矩阵求逆时间,再通过公式normalizedn = p1/pn得到每个维度下标准化后性能数据,其中p1表示维度最小的矩阵的性能数据,pn表示第n个维度下的性能数据。 + # (4)根据标注化后的矩阵损失信息和标准化后的性能数据绘图 """define ascend reducer""" + # 获取并设置自动并行上下文中的并行模式 self.parallel_mode = context.get_auto_parallel_context("parallel_mode") - self.is_distributed = (self.parallel_mode != ParallelMode.STAND_ALONE) + # 根据自动并行上下文判断是否为分布式 + self.is_distributed = (self.parallel_mode!= ParallelMode.STAND_ALONE) + # 如果是分布式,则设置梯度汇总器 if self.is_distributed: + # 获取梯度平均值 mean = _get_gradients_mean() + # 获取设备数量 degree = _get_device_num() + # 如果没有指定分割索引,则设置分割索引为数组的长度减1 if not split_indices: self.split_indices = [len(self.matrix_a_cov) - 1] + # 否则设置分割索引 else: self.split_indices = split_indices + # 如果卷积层数量大于0 if self.conv_layer_count > 0: + # 设置自动并行上下文的所有梯度汇总器的分割索引并命名 auto_parallel_context().set_all_reduce_fusion_split_indices(self.split_indices, "hccl_world_groupsum2") auto_parallel_context().set_all_reduce_fusion_split_indices(self.split_indices, "hccl_world_groupsum4") + # 执行按元素最大值归约的分布式梯度归约,其中 self.matrix_a_cov 是一个张量, + # mean、degree 分别为张量的平均值和设备数量,fusion_type=2 和 fusion_type=4 分别表示融合类型为按元素最大值和按梯度最大值 self.grad_reducer_amax = DistributedGradReducer(self.matrix_a_cov, mean, degree, fusion_type=2) self.grad_reducer_gmax = DistributedGradReducer(self.matrix_a_cov, mean, degree, fusion_type=4) + # 设置自动并行上下文的所有梯度汇总器的分割索引并命名 auto_parallel_context().set_all_reduce_fusion_split_indices(self.split_indices, "hccl_world_groupsum6") auto_parallel_context().set_all_reduce_fusion_split_indices(self.split_indices, "hccl_world_groupsum8") + # 执行按元素最大值归约的分布式梯度归约,其中 self.matrix_a_cov 是一个张量, + # mean、degree 分别为张量的平均值和设备数量,fusion_type=6 和 fusion_type=8 分别表示融合类型为按元素最大值和按梯度最大值 self.grad_reducer_a = DistributedGradReducer(self.matrix_a_cov, mean, degree, fusion_type=6) self.grad_reducer_g = DistributedGradReducer(self.matrix_a_cov, mean, degree, fusion_type=8) def _get_weight_idx_map(self, layer_type, idx, weight_shape): + # 根据层类型、索引和权重形状获取权重索引映射 """for Ascend, get weight idx map""" + # 1.首先,检查层类型是否在允许的层类型列表中(如 Conv、FC 和 Embedding),并且权重名称中不包含 "bias"(以便跳过偏置权重) if layer_type in [Conv, FC, Embedding] and "bias" not in self.params[idx].name.lower(): + # 如果满足条件,将 weight_fim_idx_map 添加到记录权重索引的列表中 self.weight_fim_idx_map = self.weight_fim_idx_map + (self.thor_layer_count,) + # 同时将 weight_layertype_idx_map 添加到记录层类型的列表中 self.weight_layertype_idx_map = self.weight_layertype_idx_map + (layer_type,) + # 如果layer_type为Embedding,则a_pad_dim和g_pad_dim都设置为0 if layer_type == Embedding: + # 获取其输入的填充维度(a_pad_dim) a_pad_dim = 0 g_pad_dim = 0 + # 将a_split_pad_dim_map添加到记录填充维度的列表中 self.a_split_pad_dim_map = self.a_split_pad_dim_map + (a_pad_dim,) + # 将g_split_pad_dim_map添加到记录填充维度的列表中 self.g_split_pad_dim_map = self.g_split_pad_dim_map + (g_pad_dim,) else: + # 获取其输入的输出通道数(out_channels) out_channels = weight_shape[0] + # 计算g_pad_dim g_pad_dim = self._get_pad_dim(out_channels) + # 将 g_split_pad_dim_map 添加到记录填充维度的列表中 self.g_split_pad_dim_map = self.g_split_pad_dim_map + (g_pad_dim,) + # 计算权重矩阵(matrix_a)的维度(matrix_a_dim) matrix_a_dim = weight_shape[1] + # 如果layer_type为Conv,则matrix_a_dim乘以weight_shape[2]乘以weight_shape[3] if layer_type == Conv: matrix_a_dim = weight_shape[1] * weight_shape[2] * weight_shape[3] + # 计算a_pad_dim a_pad_dim = self._get_pad_dim(matrix_a_dim) + # 将 a_split_pad_dim_map 添加到记录填充维度的列表中 self.a_split_pad_dim_map = self.a_split_pad_dim_map + (a_pad_dim,) + # 将thor_layer_count加1 self.thor_layer_count = self.thor_layer_count + 1 + # 如果layer_type为卷积层,则计算weight_conv_idx_map if layer_type == Conv: + # 为每个卷积层分配一个唯一的索引,以便在分布式训练中正确地更新权重 + # 首先,将 self.weight_conv_idx_map 添加到记录卷积层索引的列表中,然后将 self.conv_layer_count 加1 self.weight_conv_idx_map = self.weight_conv_idx_map + (self.conv_layer_count,) self.conv_layer_count = self.conv_layer_count + 1 else: + # 否则,计算weight_conv_idx_map self.weight_conv_idx_map = self.weight_conv_idx_map + (-1,) else: + # 计算weight_fim_idx_map self.weight_fim_idx_map = self.weight_fim_idx_map + (-1,) + # 计算weight_conv_idx_map self.weight_conv_idx_map = self.weight_conv_idx_map + (-1,) + # 如果layer_type为LayerNorm,则计算weight_layertype_idx_map if layer_type == LayerNorm: self.weight_layertype_idx_map = self.weight_layertype_idx_map + (LayerNorm,) else: + # 否则,将 self.weight_conv_idx_map 添加到记录卷积层索引的列表中,并添加一个表示未分配索引的值(-1)。 + # 这样,当处理到未分配索引的层时,可以知道需要跳过更新权重 self.weight_layertype_idx_map = self.weight_layertype_idx_map + (Other,) def _get_fc_matrix(self, weight_shape): + # 为全连接层计算逆矩阵 matrix_a 和 matrix_g """for Ascend, get fc matrix_a and matrix_g""" + # 根据权重形状计算输出通道数 out_channels = weight_shape[0] + # 根据权重形状计算输入通道数 in_channels = weight_shape[1] + # 如果卷积层数大于0,需要计算逆矩阵 if self.conv_layer_count > 0: + # 如果输出通道数为1001,说明是全连接层,此时需要计算逆矩阵 if out_channels == 1001: + # 初始化fc_matrix_a fc_matrix_a = Parameter(Tensor(np.zeros([128, 128, 16, 16]).astype(np.float16)), name='matrix_a_inv_' + str(self.thor_layer_count), requires_grad=False) + # 初始化fc_matrix_g fc_matrix_g = Parameter(Tensor(np.zeros([63, 63, 16, 16]).astype(np.float16)), name="matrix_g_inv_" + str(self.thor_layer_count), requires_grad=False) + # 否则,说明是卷积层,此时需要计算卷积层到全连接层的逆矩阵 else: + # 初始化fc_matrix_a fc_matrix_a = Parameter(Tensor(np.eye(in_channels).astype(np.float16)), name='matrix_a_inv_' + str(self.thor_layer_count), requires_grad=False) + # 初始化fc_matrix_g fc_matrix_g = Parameter(Tensor(np.eye(out_channels).astype(np.float16)), name="matrix_g_inv_" + str(self.thor_layer_count), requires_grad=False) + # 将fc_matrix_a和fc_matrix_g添加到self.matrix_a和self.matrix_g中 self.matrix_a = self.matrix_a + (fc_matrix_a,) self.matrix_g = self.matrix_g + (fc_matrix_g,) def _process_matrix_init_and_weight_idx_map(self, net): + # 初始化逆矩阵 matrix_a 和 matrix_g,以及为卷积层分配逆矩阵的索引 """for Ascend, process matrix init shape, and get weight idx map""" + # 设置层计数器为0 layer_counter = 0 layer_type_map = get_net_layertype_mask(net) + # 遍历网络中的所有层,记录层类型和权重参数 for idx in range(len(self.params)): layer_type = layer_type_map[layer_counter] + # 获取当前层的类型 weight = self.params[idx] + # 获取当前层的权重 weight_shape = self.shape(weight) + # 对于卷积层,检查是否支持矩阵乘法 if layer_type == Conv and "bias" not in self.params[idx].name.lower(): in_channels = weight_shape[1] out_channels = weight_shape[0] + # 计算矩阵A的输入通道数和输出通道数 matrix_a_dim = in_channels * weight_shape[2] * weight_shape[3] + # 计算矩阵G的输出通道数 matrix_g_dim = out_channels + # 计算矩阵A的设备形状和设备维度 matrix_a_device_shape, matrix_a_device_dim = caculate_device_shape(matrix_a_dim, in_channels, True) + # 计算矩阵G的设备形状和设备维度 matrix_g_device_shape, matrix_g_device_dim = caculate_device_shape(matrix_g_dim, in_channels, False) + # 计算是否支持矩阵乘法的形状 ret = is_conv_matmul_support_shape(matrix_a_device_shape, matrix_g_device_shape) if ret: + # 如果支持,计算逆矩阵 matrix_a_inv 和 matrix_g_inv matrix_a_inv = Parameter( Tensor(np.reshape(np.identity(matrix_a_device_dim).astype(np.float16), matrix_a_device_shape)), name='matrix_a_inv_' + str(self.thor_layer_count), requires_grad=False) + # 首先,使用 np.reshape 函数将单位矩阵转换为符合设备要求的形状,然后使用 Parameter 类创建一个新的逆矩阵参数。这个参数将在后续的矩阵乘法计算中用于计算全连接层的梯度 matrix_g_inv = Parameter( Tensor(np.reshape(np.identity(matrix_g_device_dim).astype(np.float16), matrix_g_device_shape)), name="matrix_g_inv_" + str(self.thor_layer_count), requires_grad=False) + # 同时,设置 conv_matmul_support_map 标记为 1,表示当前层支持矩阵乘法 self.conv_matmul_support_map = self.conv_matmul_support_map + (1,) else: + # 如果不支持,计算逆矩阵 matrix_a_inv 和 matrix_g_inv,并将它们添加到记录逆矩阵的列表中 matrix_a_inv = Parameter(Tensor(np.eye(matrix_a_dim).astype(np.float16)), name='matrix_a_inv_' + str(self.thor_layer_count), requires_grad=False) + # 首先,使用 np.eye 函数创建一个单位矩阵,然后将其转换为符合设备要求的形状,最后使用 Parameter 类创建一个新的逆矩阵参数。这个参数将在后续的矩阵乘法计算中用于计算全连接层的梯度 matrix_g_inv = Parameter(Tensor(np.eye(matrix_g_dim).astype(np.float16)), name="matrix_g_inv_" + str(self.thor_layer_count), requires_grad=False) + # # 同时,设置 conv_matmul_support_map 标记为 0,表示当前层不支持矩阵乘法 self.conv_matmul_support_map = self.conv_matmul_support_map + (0,) + # 将 matrix_a_inv 和 matrix_g_inv 添加到记录逆矩阵的列表中 self.matrix_a = self.matrix_a + (matrix_a_inv,) self.matrix_g = self.matrix_g + (matrix_g_inv,) + # 用于标记是否需要对矩阵的维度进行填充以适应设备 device_shape_pad_flag = False + # 检查 matrix_a_dim 和 matrix_a_device_dim 是否相等,如果不相等,说明矩阵的维度需要进行填充以适应设备 if matrix_a_dim != matrix_a_device_dim: + # 将 device_shape_pad_flag 设置为 True device_shape_pad_flag = True + # 并将 device_shape_pad_flag 添加到记录设备形状填充标志的列表中 self.device_shape_pad_flag = self.device_shape_pad_flag + (device_shape_pad_flag,) + # 检查当前层的类型是否为全连接层(FC),并且 "bias" 不存在于层的名称中 elif layer_type == FC and "bias" not in self.params[idx].name.lower(): + # 调用 _get_fc_matrix 方法根据权重形状创建一个全连接层 self._get_fc_matrix(weight_shape) + # 调用 _get_weight_idx_map 方法根据层类型、索引和权重形状获取权重索引映射 self._get_weight_idx_map(layer_type, idx, weight_shape) # bert.cls1.output_bias: not a network layer, only a trainable param + # 如果 "output_bias" 不存在于层的名称中,则增加层计数器 if "output_bias" not in self.params[idx].name.lower(): layer_counter = get_layer_counter(layer_type, layer_counter, self.params, idx) def _process_batch_matmul(self, input_matrix): + # 处理批量矩阵乘法 """process batch matmul""" + # 接收一个输入矩阵 input_matrix,首先计算其形状 input_matrix_shape = self.shape(input_matrix) + # 如果输入矩阵的第一个维度在支持列表中,则调用vector_matmul函数 if input_matrix_shape[0] in self.batch_matmul_support_list: + # 使用 vector_matmul 函数进行向量矩阵乘法 input_matrix = self.vector_matmul(input_matrix, input_matrix) + # 否则调用tbe_batch_matmul函数,进行批量矩阵乘法 else: input_matrix = self.tbe_batch_matmul(input_matrix, input_matrix) + # 返回处理后的矩阵 return input_matrix def _process_cholesky_pad(self, pad_dim, input_matrix, matrix_shape0): + # 处理 Cholesky 填充 """process cholesky pad""" + # 如果pad_dim大于0 if pad_dim > 0: + # 创建一个pad_dim*pad_dim的单位矩阵 matrix_sup = self.eye(pad_dim, pad_dim, mstype.float32) + # 将矩阵的行数和列数设置为matrix_shape0填充到适当的大小 matrix_sup = P.Pad(((0, 0), (matrix_shape0, 0)))(matrix_sup) + # 将input_matrix填充到适当的大小 input_matrix = P.Pad(((0, 0), (0, pad_dim)))(input_matrix) + # 将填充后的 input_matrix 和 matrix_sup 进行拼接 input_matrix = self.concat((input_matrix, matrix_sup)) + # 返回input_matrix(处理后矩阵) return input_matrix def _get_abs_max(self, matrix_inv, origin_dim): + # 获取矩阵 matrix_inv 的绝对最大值 """get matrix abs max""" + # 获取matrix_inv的形状 cholesky_shape = self.shape(matrix_inv) + # 判断matrix_inv 的第一个维度是否在abs_max_support_list中 if cholesky_shape[0] in self.abs_max_support_list: + # 如果在,获取matrix_inv的最大值 matrix_inv_max = P.CusFusedAbsMax1([origin_dim, origin_dim])(matrix_inv) + # 获取matrix_inv的最大值的绝对值 matrix_max = self.fused_abs_max2(matrix_inv_max) + # 将matrix_inv转换成矩阵 matrix_inv = self.matrix_combine(matrix_inv) else: + # 将matrix_inv转换成矩阵 matrix_inv = self.matrix_combine(matrix_inv) + # 获取matrix_inv的绝对值 matrix_abs = P.Abs()(matrix_inv) + # 获取matrix_inv的最大值 matrix_max = P.ReduceMax(keep_dims=False)(matrix_abs) + # 返回matrix_max和matrix_inv return matrix_max, matrix_inv def _get_fc_ainv_ginv(self, index, damping_step, gradients, matrix_a_allreduce, matrix_g_allreduce, matrix_a_max_allreduce, matrix_g_max_allreduce): + # 用于获取全连接层(FC)的 ainv 和 ginv """get fc layer ainv and ginv""" + # 获取层的数量 thor_layer_count = self.weight_fim_idx_map[index] + # 获取梯度 g = gradients[index] + # 从 matrix_a_cov 列表中获取第 thor_layer_count 层的矩阵 matrix_a matrix_a = self.matrix_a_cov[thor_layer_count] + # 从 matrix_g_cov 列表中获取第 thor_layer_count 层的矩阵 matrix_g matrix_g = self.matrix_g_cov[thor_layer_count] + # 使用depend函数将matrix_a和matrix_g的值依赖于g matrix_a = F.depend(matrix_a, g) matrix_g = F.depend(matrix_g, g) + # 获取matrix_a的形状 a_shape = self.shape(matrix_a) a_eye = self.eye(a_shape[0], a_shape[0], mstype.float32) + # 获取matrix_g的形状 g_shape = self.shape(matrix_g) g_eye = self.eye(g_shape[0], g_shape[0], mstype.float32) + # 获取damping的值 damping = self.sqrt(damping_step) + # 将matrix_a的值和damping的值相乘,并将结果赋值给matrix_a matrix_a = matrix_a + damping * a_eye + # 获取a_pad_dim的值 a_pad_dim = self.a_split_pad_dim_map[thor_layer_count] + # 调用_process_cholesky_pad函数处理a_pad_dim matrix_a = self._process_cholesky_pad(a_pad_dim, matrix_a, a_shape[0]) + # 调用cholesky函数处理matrix_a matrix_a_inv = self.cholesky(matrix_a) + # 调用_process_batch_matmul函数处理matrix_a_inv matrix_a_inv = self._process_batch_matmul(matrix_a_inv) - + # 从参数中获取 weight_shape weight_shape = self.shape(self.params[index]) + # 计算输出通道数和输入通道数 out_channels = weight_shape[0] in_channels = weight_shape[1] + # 判断输出通道数是否为2 if out_channels == 2: + # 将 matrix_a_inv 和 g_eye 进行拼接,并将结果赋值给 matrix_a_inv 和 matrix_g_inv matrix_a_inv = self.matrix_combine(matrix_a_inv) matrix_g_inv = g_eye else: + # 将 matrix_g 乘以 self.loss_scale,并将结果赋值给 matrix_g matrix_g = self.mul(matrix_g, self.loss_scale) + # 将 matrix_g 乘以 self.batch_size_scale,并将结果赋值给 matrix_g matrix_g = self.mul(matrix_g, self.batch_size_scale) + # 将 matrix_g 与 damping 与 g_eye乘积的结果相加,并将结果赋值给 matrix_g matrix_g = matrix_g + damping * g_eye + # 获取第 thor_layer_count 层的 g_pad_dim 值 g_pad_dim = self.g_split_pad_dim_map[thor_layer_count] + # 使用 _process_cholesky_pad 函数处理 g_pad_dim、matrix_g 和 g_shape[0],并将结果赋值给 matrix_g matrix_g = self._process_cholesky_pad(g_pad_dim, matrix_g, g_shape[0]) + # 使用 cholesky 方法计算 matrix_g 的逆矩阵 matrix_g_inv = self.cholesky(matrix_g) + # 使用 cholesky 方法计算 matrix_g 的逆矩阵 matrix_g_inv = self._process_batch_matmul(matrix_g_inv) + # 如果卷积层数量大于0 if self.conv_layer_count > 0: + # 获取矩阵A的最大值 a_max, matrix_a_inv = self._get_abs_max(matrix_a_inv, in_channels) + # 获取矩阵G的最大值 g_max, matrix_g_inv = self._get_abs_max(matrix_g_inv, out_channels) + # 计算A和G的最大值 a_max = F.depend(a_max, g) g_max = F.depend(g_max, g) + # 将A和G的最大值添加到矩阵A和G的最大值的列表中 matrix_a_max_allreduce = matrix_a_max_allreduce + (a_max,) matrix_g_max_allreduce = matrix_g_max_allreduce + (g_max,) else: + # 将矩阵A和G的逐元素相乘 matrix_a_inv = self.matrix_combine(matrix_a_inv) matrix_g_inv = self.matrix_combine(matrix_g_inv) if a_pad_dim > 0: + # 将matrix_a_inv的第一行和第一列拼接起来 matrix_a_inv = self.slice(matrix_a_inv, (0, 0), (in_channels, in_channels)) if g_pad_dim > 0: + # 将matrix_g_inv的第一行和第一列拼接起来 matrix_g_inv = self.slice(matrix_g_inv, (0, 0), (out_channels, out_channels)) + # 获取matrix_a_inv的形状 matrix_a_inv_shape = self.shape(matrix_a_inv) + # 获取matrix_g_inv的形状 matrix_g_combine_shape = self.shape(matrix_g_inv) + # 如果matrix_a_inv的形状是2048和1001,则将matrix_a_inv的第一行和第一列拼接起来 if matrix_a_inv_shape[0] == 2048 and matrix_g_combine_shape[0] == 1001: + # 将 matrix_a_inv 调整为一个二维张量,其中第一维的大小是 matrix_a_inv_shape[0] / 16, + # 第二维的大小是 16,第三维的大小是 matrix_a_inv_shape[0] / 16,第四维的大小是 16 matrix_a_inv = self.reshape(matrix_a_inv, (matrix_a_inv_shape[0] / 16, 16, matrix_a_inv_shape[0] / 16, 16)) + # 将 matrix_a_inv 的前两个维度(即第 2 维和第 3 维)交换位置 matrix_a_inv = self.transpose(matrix_a_inv, (2, 0, 1, 3)) + # 使用 P.Pad 函数对 matrix_g_inv 进行填充。填充区域的高度和宽度都是 8 matrix_g_inv = P.Pad(((0, 7), (0, 7)))(matrix_g_inv) + # 获取matrix_g_inv的形状 matrix_g_inv_shape = self.shape(matrix_g_inv) + # 将 matrix_g_inv 调整为一个二维张量,其中第一维的大小是 matrix_g_inv_shape[0] / 16, + # 第二维的大小是 16,第三维的大小是 matrix_g_inv_shape[0] / 16,第四维的大小是 16 matrix_g_inv = self.reshape(matrix_g_inv, (matrix_g_inv_shape[0] / 16, 16, matrix_g_inv_shape[0] / 16, 16)) + # 将 matrix_g_inv 的前两个维度(即第 2 维和第 3 维)交换位置 matrix_g_inv = self.transpose(matrix_g_inv, (2, 0, 1, 3)) + # 将 matrix_a_inv 和 matrix_g_inv 添加到它们的归一化版本(matrix_a_allreduce 和 matrix_g_allreduce)中 matrix_a_allreduce = matrix_a_allreduce + (matrix_a_inv,) matrix_g_allreduce = matrix_g_allreduce + (matrix_g_inv,) + # 返回这些归一化版本的平均值和最大值(matrix_a_max_allreduce 和 matrix_g_max_allreduce) return matrix_a_allreduce, matrix_g_allreduce, matrix_a_max_allreduce, matrix_g_max_allreduce def _process_conv_matmul_device_pad(self, conv_layer_count, weight_shape, matrix_a_inv): + # 用于处理卷积矩阵乘法设备填充 """process conv matmul device pad""" + # 如果设备填充标志位为True if self.device_shape_pad_flag[conv_layer_count]: + # 计算核的高度和宽度 kernel_hw = weight_shape[2] * weight_shape[3] + # 计算输入通道数 in_channels = weight_shape[1] + # 将矩阵A的通道数和核的高度和宽度拉平 matrix_a_inv = self.reshape(matrix_a_inv, (kernel_hw, in_channels, kernel_hw, in_channels)) + # 对矩阵A的通道数和核的高度和宽度填充, + # 填充操作如下:将输入矩阵的第二个维度(输入通道数)扩展到设备上的特征图通道数 C0,同时将输入矩阵的第三个和第四个维度(卷积核的高度和宽度)保留不变 matrix_a_inv = P.Pad(((0, 0), (0, self.C0 - in_channels), (0, 0), (0, self.C0 - in_channels)))(matrix_a_inv) + # 返回处理后的矩阵 return matrix_a_inv def _get_ainv_ginv_amax_gmax_list(self, gradients, damping_step, matrix_a_allreduce, matrix_g_allreduce, matrix_a_max_allreduce, matrix_g_max_allreduce): + # 函数用于计算协方差矩阵A/G的逆,并返回求完逆后的矩阵。具体过程是遍历模型所有层,按层处理, + # 对每一层的协方差矩阵加上正则化项,然后对矩阵进行cholesky分解从而来求逆。当前开源代码THOR中支持全连接层和卷积层的处理。 """get matrixA inverse list, matrixG inverse list, matrixA_max list, matrixG_max list""" + # 遍历参数 for i in range(len(self.params)): + # 获取总层数 thor_layer_count = self.weight_fim_idx_map[i] + # 获取卷积层数 conv_layer_count = self.weight_conv_idx_map[i] + # 获取每一层的类型 layer_type = self.weight_layertype_idx_map[i] + # 获取每一层的权重形状 weight_shape = self.shape(self.params[i]) + # 获取输出通道数 out_channels = weight_shape[0] + # 如果此层为卷积层 if layer_type == Conv: + # 获取梯度 g = gradients[i] + # 计算卷积层A的维度,weight_shape是一个表示卷积层权重矩阵形状的元组,其中weight_shape[1]表示通道数, + # weight_shape[2]表示高度,weight_shape[3]表示宽度。将这三个值相乘,得到卷积层A的元素数量 matrix_a_dim = weight_shape[1] * weight_shape[2] * weight_shape[3] + # 检查是否支持矩阵乘法操作。self.conv_matmul_support_map是一个字典,其中键是卷积层的数量,值是布尔值, + # 表示该卷积层是否支持矩阵乘法操作。conv_layer_count是一个整数,表示当前处理的卷积层的数量 matmul_support_flag = self.conv_matmul_support_map[conv_layer_count] + # 从矩阵A中提取相应的子矩阵。self.matrix_a_cov是一个列表,其中包含每个卷积层对应的矩阵A。thor_layer_count是一个整数,表示当前处理的卷积层的数量 matrix_a = self.matrix_a_cov[thor_layer_count] + # 从矩阵G中提取相应的子矩阵 matrix_g = self.matrix_g_cov[thor_layer_count] + # 将矩阵A的依赖项设置为g,F.depend()用于将张量的依赖项设置为给定的张量。这里,我们将矩阵A的依赖项设置为g,以便在后续计算中使用 matrix_a = F.depend(matrix_a, g) + # 将矩阵G的依赖项设置为g。与上述步骤类似 matrix_g = F.depend(matrix_g, g) + # 计算矩阵A的形状 a_shape = self.shape(matrix_a) + # 创建一个单位矩阵,用于对矩阵A进行归一化 a_eye = self.eye(a_shape[0], a_shape[0], mstype.float32) + # 计算矩阵G的形状 g_shape = self.shape(matrix_g) + # 创建一个单位矩阵,用于对矩阵G进行归一化 g_eye = self.eye(g_shape[0], g_shape[0], mstype.float32) + # 从矩阵A的归一化参数列表中提取相应的参数。self.a_normalizer是一个列表,其中包含每个卷积层对应的归一化参数。conv_layer_count是一个整数,表示当前处理的卷积层的数量 a_normalizer = self.a_normalizer[conv_layer_count] + # 从矩阵G的归一化参数列表中提取相应的参数 g_normalizer = self.g_normalizer[conv_layer_count] + # 将矩阵A的归一化参数的依赖项设置为g a_normalizer = F.depend(a_normalizer, g) + # 将矩阵G的归一化参数的依赖项设置为g g_normalizer = F.depend(g_normalizer, g) + # 计算矩阵A的归一化参数,并将结果与damping_step和batch_size相乘 damping_a = self.mul(damping_step, self.batch_size / a_normalizer) + # 计算矩阵G的归一化参数,并将结果与damping_step和batch_size相乘 damping_g = self.mul(damping_step, self.batch_size / g_normalizer) + # 对矩阵A进行归一化,即将每个元素平方后求和,再开平方根 damping_a = self.sqrt(damping_a) + # 将矩阵A加上归一化后的单位矩阵与damping_a相乘 matrix_a = matrix_a + damping_a * a_eye + # 根据卷积层A的形状计算填充维度 a_pad_dim = self.a_split_pad_dim_map[thor_layer_count] + # 对矩阵A进行处理,使其满足Cholesky分解的条件 matrix_a = self._process_cholesky_pad(a_pad_dim, matrix_a, a_shape[0]) + # 对矩阵A的逆进行处理,使其满足矩阵乘法操作的条件 matrix_a_inv = self.cholesky(matrix_a) + # 获取矩阵A的绝对最大值,并将其存储在matrix_a_inv和a_max中 matrix_a_inv = self._process_batch_matmul(matrix_a_inv) a_max, matrix_a_inv = self._get_abs_max(matrix_a_inv, matrix_a_dim) + # 计算矩阵G的归一化参数,并将结果开平方根 damping_g = self.sqrt(damping_g) + # 将矩阵G乘以损失系数、批量大小系数 matrix_g = self.mul(matrix_g, self.loss_scale) matrix_g = self.mul(matrix_g, self.batch_size_scale) + # 将矩阵G加上归一化后的单位矩阵与damping_g相乘 matrix_g = matrix_g + damping_g * g_eye + # 根据卷积层G的形状计算填充维度。 g_pad_dim = self.g_split_pad_dim_map[thor_layer_count] + # 对矩阵G进行处理,使其满足Cholesky分解的条件 matrix_g = self._process_cholesky_pad(g_pad_dim, matrix_g, g_shape[0]) matrix_g_inv = self.cholesky(matrix_g) + # 对矩阵G的逆进行处理,使其满足矩阵乘法操作的条件 matrix_g_inv = self._process_batch_matmul(matrix_g_inv) + # 获取矩阵G的绝对最大值,并将其存储在matrix_g_inv和g_max中 g_max, matrix_g_inv = self._get_abs_max(matrix_g_inv, out_channels) + # 检查矩阵A的填充维度是否大于0。如果大于0,则执行后续操作,否则不执行 if a_pad_dim > 0: + # 将matrix_a_inv的第一行和第一列设置为0 matrix_a_inv = self.slice(matrix_a_inv, (0, 0), (matrix_a_dim, matrix_a_dim)) + # 检查矩阵G的填充维度是否大于0 if g_pad_dim > 0: + # 将matrix_g_inv的第一行和第一列设置为0 matrix_g_inv = self.slice(matrix_g_inv, (0, 0), (out_channels, out_channels)) + # 如果matmul_support_flag为1,则将matrix_a_inv和matrix_g_inv进行转置 if matmul_support_flag == 1: + # 调用_process_conv_matmul_device_pad函数,将matrix_a_inv和matrix_g_inv进行转置 matrix_a_inv = self._process_conv_matmul_device_pad(conv_layer_count, weight_shape, matrix_a_inv) + # 用于计算矩阵A的逆的形状 matrix_a_inv_shape = self.shape(self.matrix_a[thor_layer_count]) + # 将矩阵A的逆的形状转换为适合在设备上使用的形状 matrix_a_device_temp_shape = (matrix_a_inv_shape[0], matrix_a_inv_shape[2], matrix_a_inv_shape[1], matrix_a_inv_shape[3]) + # 将matrix_a_inv转置 matrix_a_inv = self.reshape(matrix_a_inv, matrix_a_device_temp_shape) matrix_a_inv = self.transpose(matrix_a_inv, (2, 0, 1, 3)) + # 用于计算矩阵G的逆的形状 matrix_g_inv_shape = self.shape(self.matrix_g[thor_layer_count]) + # 将矩阵G的逆的形状转换为适合在设备上使用的形状 matrix_g_device_temp_shape = (matrix_g_inv_shape[0], matrix_g_inv_shape[2], matrix_g_inv_shape[1], matrix_g_inv_shape[3]) + # 将matrix_g_inv转置 matrix_g_inv = self.reshape(matrix_g_inv, matrix_g_device_temp_shape) matrix_g_inv = self.transpose(matrix_g_inv, (2, 0, 1, 3)) - + # 将梯度A的绝对最大值存储在a_max变量中,并将梯度G的绝对最大值存储在g_max变量中 a_max = F.depend(a_max, g) g_max = F.depend(g_max, g) + # 将matrix_a_inv和matrix_g_inv合并到matrix_a_allreduce和matrix_g_allreduce中 matrix_a_allreduce = matrix_a_allreduce + (matrix_a_inv,) matrix_g_allreduce = matrix_g_allreduce + (matrix_g_inv,) + # 将a_max和g_max合并到matrix_a_max_allreduce和matrix_g_max_allreduce中 matrix_a_max_allreduce = matrix_a_max_allreduce + (a_max,) matrix_g_max_allreduce = matrix_g_max_allreduce + (g_max,) + # 如果该层为全连接层 elif layer_type == FC: + # 计算matrix_a_allreduce和matrix_g_allreduce,用于计算第i层全连接层的A和G的倒数,并将结果存储在相应的变量中 matrix_a_allreduce, matrix_g_allreduce, matrix_a_max_allreduce, matrix_g_max_allreduce = \ self._get_fc_ainv_ginv(i, damping_step, gradients, matrix_a_allreduce, matrix_g_allreduce, matrix_a_max_allreduce, matrix_g_max_allreduce) + # 如果该层为嵌入层 elif layer_type == Embedding: + # 获取梯度 g = gradients[i] + # 提取当前处理的卷积层的权重矩阵A matrix_a = self.matrix_a_cov[thor_layer_count] + # 提取当前处理的卷积层的权重矩阵G matrix_g = self.matrix_g_cov[thor_layer_count] + # 使用F.depend()函数将矩阵A和G的依赖关系添加到计算图中 matrix_a = F.depend(matrix_a, g) matrix_g = F.depend(matrix_g, g) + # 它计算矩阵G的形状 g_shape = self.shape(matrix_g) + # 创建一个单位矩阵g_eye,其形状与矩阵G相同 g_eye = self.eye(g_shape[0], g_shape[0], mstype.float32) + # 计算 damping(即梯度的平方根),并将结果存储在damping变量中 damping = self.sqrt(damping_step) + # 创建一个单位矩阵a_eye,其形状与矩阵A相同 a_eye = P.OnesLike()(matrix_a) + # 将矩阵A乘以1.0 / self.batch_size,并将结果与damping变量乘积之和存储在矩阵A中 matrix_a = self.mul(matrix_a, 1.0 / self.batch_size) matrix_a = matrix_a + damping * a_eye + # 计算矩阵A的逆,并将结果存储在matrix_a_inv变量中 matrix_a_inv = self.inv(matrix_a) + # 将矩阵G乘以self.loss_scale和self.batch_size_scale,并将结果与damping变量乘积之和存储在矩阵G中 matrix_g = self.mul(matrix_g, self.loss_scale) matrix_g = self.mul(matrix_g, self.batch_size_scale) matrix_g = matrix_g + damping * g_eye + # 计算矩阵G的逆,并将结果存储在matrix_g_inv变量中 matrix_g_inv = self.cholesky(matrix_g) + # 调用self._process_batch_matmul()方法处理矩阵G的逆 matrix_g_inv = self._process_batch_matmul(matrix_g_inv) + # 将矩阵G的逆合并为一个矩阵 matrix_g_inv = self.matrix_combine(matrix_g_inv) + # 将 matrix_a_inv 和 matrix_g_inv 添加到它们的归一化版本(matrix_a_allreduce 和 matrix_g_allreduce)中 matrix_a_allreduce = matrix_a_allreduce + (matrix_a_inv,) matrix_g_allreduce = matrix_g_allreduce + (matrix_g_inv,) + # 返回这些归一化版本的平均值和最大值(matrix_a_max_allreduce 和 matrix_g_max_allreduce) return matrix_a_allreduce, matrix_g_allreduce, matrix_a_max_allreduce, matrix_g_max_allreduce def _process_layernorm(self, damping_step, gradient): + # 用于处理 layernorm 层。layernorm 是一种归一化层,用于对神经网络中的张量进行归一化处理,以提高模型的性能 """process layernorm layer for thor""" + # 计算damping( damping 是 FIM(Fisher Information Matrix)的倒数平方根) damping = self.sqrt(damping_step) + # 计算normalizer(normalizer 等于 batch_size(批次大小)) normalizer = self.cast(self.batch_size, mstype.float32) + # 计算fim_cov fim_cov = self.square(gradient) + # 然后除以 normalizer fim_cov = self.mul(fim_cov, 1.0 / normalizer) + # 并将结果与 damping 相加 fim_cov = fim_cov + damping + # 计算fim_inv的逆 fim_inv = self.inv(fim_cov) + # 并将结果乘以 gradient,得到归一化后的 gradient gradient = self.mul(fim_inv, gradient) + # 返回更新的梯度 return gradient def _process_thor_fc(self, thor_layer_count, matrix_a_allreduce, matrix_g_allreduce, g): + # 用于处理 THOR 图形中的全连接层 """process thor graph fc layer""" + # 获取thor网络的第thor_layer_count层的矩阵A temp_a = matrix_a_allreduce[thor_layer_count] + # 获取thor网络的第thor_layer_count层的矩阵G temp_g = matrix_g_allreduce[thor_layer_count] + # 将矩阵A赋值给矩阵A_cov self.assign(self.matrix_a_cov[thor_layer_count], temp_a) + # 将矩阵G赋值给矩阵G_cov self.assign(self.matrix_g_cov[thor_layer_count], temp_g) + # 将矩阵A转换为float16类型,以减小计算量 temp_a = self.cast(temp_a, mstype.float16) + # 将矩阵G转换为float16类型 temp_g = self.cast(temp_g, mstype.float16) + # 将矩阵G乘以矩阵A g = self.cast(g, mstype.float16) g = self.matmul(temp_g, g) + # 将矩阵G乘以矩阵A g = self.matmul(g, temp_a) + # 将矩阵G转换为float32类型 g = self.cast(g, mstype.float32) + # 返回矩阵G return g def _get_second_gradients_one(self, params_len, gradients, new_grads): + # 遍历参数列表中的每个参数,计算其第二微分,并将结果存储在 new_grads 列表中。过程中,需要根据参数的层数、类型等信息来计算第二微分 """get second gradients one""" + # 遍历参数 for i in range(params_len): + # 获取梯度 g = gradients[i] + # 获取总层数 thor_layer_count = self.weight_fim_idx_map[i] + # 获取卷积层鼠 conv_layer_count = self.weight_conv_idx_map[i] + # 获取该层类型 layer_type = self.weight_layertype_idx_map[i] + # 首先获取当前参数的层数 thor_layer_count,然后根据层数获取对应的矩阵 matrix_a matrix_a = self.matrix_a[thor_layer_count] + # 首先获取当前参数的层数 thor_layer_count,然后根据层数获取对应的矩阵 matrix_g matrix_g = self.matrix_g[thor_layer_count] + # 根据当前层数获取对应的矩阵 matrix_max 的 inverse matrix_max = self.matrix_max_inv[thor_layer_count] + # 获取矩阵G的形状 grad_shape = self.shape(g) + # 如果该层为全连接层 if layer_type == FC: if grad_shape[0] == 1001: + # 如果当前层类型为FC,且当前层的维度为1001 + # 计算 g 与 matrix_g 的乘积 g = self.cube_matmul_left_fc(matrix_g, g) + # 计算 g 与 matrix_a 和 matrix_max 的乘积 g = self.cube_matmul_right_fc(g, matrix_a, matrix_max) else: + # 否则,将matrix_a,matrix_g,g转换为float16类型 temp_a = self.cast(matrix_a, mstype.float16) temp_g = self.cast(matrix_g, mstype.float16) g = self.cast(g, mstype.float16) + # 将g与矩阵g相乘 g = self.matmul(temp_g, g) + # 将矩阵a与g相乘 g = self.matmul(g, temp_a) + # 将g转换为float32类型 g = self.cast(g, mstype.float32) + # 将 g 与 matrix_max 进行乘法运算 g = self.mul(g, matrix_max) + # 如果是卷积层 elif layer_type == Conv: + # 根据当前层的类型(Conv)获取对应的矩阵 matmul_support_flag matmul_support_flag = self.conv_matmul_support_map[conv_layer_count] + # matmul_support_flag 是否为1 if matmul_support_flag == 1: + # 计算 g 与 matrix_g 和 matrix_a 的乘积 g = self.cube_matmul_left(matrix_g, g) g = self.cube_matmul_right_mul(g, matrix_a, matrix_max) else: + # 否则,将g转换为指定形状 g = self.reshape(g, (grad_shape[0], grad_shape[1] * grad_shape[2] * grad_shape[3])) + # 将matrix_a,matrix_g,g转换为float16类型 temp_a = self.cast(matrix_a, mstype.float16) temp_g = self.cast(matrix_g, mstype.float16) g = self.cast(g, mstype.float16) + # 计算 g 与 matrix_a 和 matrix_max 的乘积 g = self.matmul(temp_g, g) g = self.matmul(g, temp_a) + # 将结果转换回 float32 类型 g = self.cast(g, mstype.float32) g = self.mul(g, matrix_max) + # 将g转换为grad_shape形状 g = self.reshape(g, grad_shape) + # 将计算结果与 new_grads 相加 new_grads = new_grads + (g,) + # 返回计算后的结果 return new_grads def _get_second_gradients(self, new_grads, damping_step, gradients): + # 计算二次梯度,计算最终参数更新方向 """get second gradients for thor""" + # 获取参数的长度 params_len = len(self.params) + # 如果卷积层的数量大于0 if self.conv_layer_count > 0: + # 则用上一个的方法计算new_grads的第二微分,并将结果存储在 new_grads 列表中 new_grads = self._get_second_gradients_one(params_len, gradients, new_grads) else: + # 遍历参数 for i in range(params_len): + # 获取梯度 g = gradients[i] + # 获取当前层数 thor_layer_count = self.weight_fim_idx_map[i] + # 获取当前层的类型 layer_type = self.weight_layertype_idx_map[i] + # 如果该层为嵌入层 if layer_type == Embedding: + # 获取第 thor_layer_count 层的 matrix_a_cov 和 matrix_g_cov temp_a_ori = self.matrix_a_cov[thor_layer_count] temp_g = self.matrix_g_cov[thor_layer_count] + # 然后将 temp_a_ori 扩展为一个维度为1的矩阵 temp_a = self.expand(temp_a_ori, 1) + # 接着,将 g 与 temp_a 进行乘法运算 g = self.mul(temp_a, g) + # 将结果转换为 float16 类型 temp_g = self.cast(temp_g, mstype.float16) g = self.cast(g, mstype.float16) + # 接着,将 g 与 temp_g 进行乘法运算 g = self.matmul(g, temp_g) + # 最后,将结果转换回 float32 类型 g = self.cast(g, mstype.float32) + # 如果该层为全连接层 elif layer_type == FC: + # 获取第 thor_layer_count 层的 matrix_a_cov 和 matrix_g_cov temp_a = self.matrix_a_cov[thor_layer_count] temp_g = self.matrix_g_cov[thor_layer_count] + # 将结果转换为 float16 类型 temp_a = self.cast(temp_a, mstype.float16) temp_g = self.cast(temp_g, mstype.float16) g = self.cast(g, mstype.float16) + # 接着,将 g 与 temp_g,temp_a 进行乘法运算 g = self.matmul(temp_g, g) g = self.matmul(g, temp_a) + # 最后,将结果转换回 float32 类型 g = self.cast(g, mstype.float32) + # 如果该层为归一化层 elif layer_type == LayerNorm: + # 根据 damping_step 计算归一化系数的倒数,然后将 g 乘以归一化系数的倒数 g = self._process_layernorm(damping_step, g) + # 最后,将计算结果与 new_grads 相加 new_grads = new_grads + (g,) + # 返回计算后的二次梯度 return new_grads def _get_second_grad_by_matmul(self, index, temp_a, temp_g, g, temp_max): + # 根据给定的索引、临时矩阵 temp_a、临时梯度 temp_g、梯度 g 和最大值 temp_max 来计算二次梯度 """get second gradient by matmul""" + # 获取卷积层数 conv_layer_count = self.weight_conv_idx_map[index] + # 获取当前层类型 layer_type = self.weight_layertype_idx_map[index] + # 获取矩阵g的形状 grad_shape = self.shape(g) + # 如果该层为全连接层 if layer_type == FC: + # 如果该层的形状为1001 if grad_shape[0] == 1001: + # 如果当前层类型为FC,则计算 g 与 matrix_g 和 matrix_a 的乘积 g = self.cube_matmul_left_fc(temp_g, g) g = self.cube_matmul_right_fc(g, temp_a, temp_max) else: + # 否则,使用cast函数将 temp_a ,temp_g ,g 转换为 float16 类型 temp_a = self.cast(temp_a, mstype.float16) temp_g = self.cast(temp_g, mstype.float16) g = self.cast(g, mstype.float16) + # 将 g 与 temp_g 进行乘法运算 g = self.matmul(temp_g, g) + # 将 g 与 temp_a 进行乘法运算 g = self.matmul(g, temp_a) + # 最后,将结果转换回 float32 类型 g = self.cast(g, mstype.float32) + # 将 g 乘以最大值 temp_max g = self.mul(g, temp_max) + # 如果该层为卷积层 elif layer_type == Conv: + # 首先,计算当前层的归一化系数 a_normalizer a_normalizer = self.a_normalizer[conv_layer_count] + # 将 a_normalizer 依赖到 g a_normalizer = F.depend(a_normalizer, g) + # 然后将 temp_max 乘以批量大小除以 a_normalizer temp_max = self.mul(temp_max, self.batch_size / a_normalizer) + # 获取当前层是否支持matmul matmul_support_flag = self.conv_matmul_support_map[conv_layer_count] + # 判断矩阵乘法是否支持标志 matmul_support_flag if matmul_support_flag == 1: + # 如果当前层支持matmul,则计算 g 与 matrix_g 和 matrix_a 的乘积 g = self.cube_matmul_left(temp_g, g) g = self.cube_matmul_right_mul(g, temp_a, temp_max) else: + # 否则,使用reshape函数将g转换为指定形状 g = self.reshape(g, (grad_shape[0], grad_shape[1] * grad_shape[2] * grad_shape[3])) + # 否则,使用cast函数将 temp_a ,temp_g ,g 转换为 float16 类型 temp_a = self.cast(temp_a, mstype.float16) temp_g = self.cast(temp_g, mstype.float16) g = self.cast(g, mstype.float16) + # 将 g 与 temp_g 进行乘法运算 g = self.matmul(temp_g, g) + # 将 g 与 temp_a 进行乘法运算 g = self.matmul(g, temp_a) + # 最后,将结果转换回 float32 类型,并乘以最大值 temp_max g = self.cast(g, mstype.float32) g = self.mul(g, temp_max) + # 使用reshape函数将g转换为grad_shape形状 g = self.reshape(g, grad_shape) + # 返回计算得到的二次梯度和最大值 temp_max return g, temp_max def _get_second_grad_by_layertype(self, index, matrix_a_allreduce, matrix_g_allreduce, g, damping_step): + # 根据层类型计算二次梯度 """get second gradient by layertype""" + # 获取该层层数 thor_layer_count = self.weight_fim_idx_map[index] + # 获取该层类型 layer_type = self.weight_layertype_idx_map[index] + # 如果该层为卷积层 if layer_type == Embedding: + # 获取thor网络的第thor_layer_count层的矩阵A temp_a_ori = matrix_a_allreduce[thor_layer_count] + # 获取thor网络的第thor_layer_count层的矩阵G temp_g = matrix_g_allreduce[thor_layer_count] + # 将第 thor_layer_count 层的 matrix_a_cov 和 matrix_g_cov 的值赋给 temp_a_ori 和 temp_g self.assign(self.matrix_a_cov[thor_layer_count], temp_a_ori) self.assign(self.matrix_g_cov[thor_layer_count], temp_g) + # 将 temp_a_ori 扩展为一个形状为 (batch_size, 1, -1) 的张量 temp_a = self.expand(temp_a_ori, 1) + # 将 g 与 temp_a 进行乘法运算 g = self.mul(temp_a, g) + # 将 temp_g ,g ,转换为 float16 类型 temp_g = self.cast(temp_g, mstype.float16) g = self.cast(g, mstype.float16) + # 将 g 与 temp_g 进行乘法运算 g = self.matmul(g, temp_g) + # # 最后,将结果转换回 float32 类型 g = self.cast(g, mstype.float32) + # 如果该层为全连接层 elif layer_type == FC: + # 使用类中函数处理全连接层,返回张量g g = self._process_thor_fc(thor_layer_count, matrix_a_allreduce, matrix_g_allreduce, g) + # 如果该层为归一化层 elif layer_type == LayerNorm: + # 使用类中函数处理归一化层,返回张量g g = self._process_layernorm(damping_step, g) + # 返回计算得到的二次梯度 return g def construct(self, gradients): + ''' + 构建梯度构建 + :param gradients: 梯度 + :return:成功标志 + ''' + # 获取参数 params = self.params + # 获取模型中的moments(动量)。 + # 注:moments是一种优化方法,用于加速深度学习模型的训练过程。 + # 在训练过程中,moments会记录每个参数的移动平均值,并在每次更新参数时使用这些移动平均值来更新参数。 + # 这样可以提高模型的训练速度,减少训练过程中的波动 moments = self.moments + # 将梯度缩放为合适的值 gradients = self.scale_grad(gradients) + # 从self.damping和self.cov_step中获取梯度和更新步长,然后使用self.axis对它们进行聚合。 + # 注:这里聚合的意思是将所有分量统一成一个值,以便在后续的矩阵乘法操作中使用 damping_step = self.gather(self.damping, self.cov_step, self.axis) + # 将damping_step类型转换为float32 damping_step = self.cast(damping_step, mstype.float32) + # 如果定义为thor优化器 if self.thor: + # 定义矩阵A,G matrix_a_allreduce = () matrix_g_allreduce = () + # 定义矩阵A,G的最大值 matrix_a_max_allreduce = () matrix_g_max_allreduce = () + # 从gradients中获取各个参数的梯度,然后使用_get_ainv_ginv_amax_gmax_list方法计算各个参数的逆向传播系数。这些系数将用于后续的矩阵乘法操作 matrix_a_allreduce, matrix_g_allreduce, matrix_a_max_allreduce, matrix_g_max_allreduce = \ self._get_ainv_ginv_amax_gmax_list(gradients, damping_step, matrix_a_allreduce, matrix_g_allreduce, matrix_a_max_allreduce, matrix_g_max_allreduce) + # 如果是分布式计算 if self.is_distributed: + # 将矩阵A的所有计算结果同步到全局计算节点 matrix_a_allreduce = self.grad_reducer_a(matrix_a_allreduce) + # 将矩阵G的所有计算结果同步到全局计算节点 matrix_g_allreduce = self.grad_reducer_g(matrix_g_allreduce) + # 如果有卷积层,则将矩阵A的最大值同步到全局计算节点 if self.conv_layer_count > 0: matrix_a_max_allreduce = self.grad_reducer_amax(matrix_a_max_allreduce) + # 将矩阵G的最大值同步到全局计算节点 matrix_g_max_allreduce = self.grad_reducer_gmax(matrix_g_max_allreduce) - + # 创建参数new_grads用于储存计算后的梯度 new_grads = () + # 如果卷积层的数量大于0 if self.conv_layer_count > 0: + # 遍历参数 for i in range(len(self.params)): + # 获取梯度 g = gradients[i] + # 获取总层数 thor_layer_count = self.weight_fim_idx_map[i] + # 获取thor网络的第thor_layer_count层的矩阵A temp_a = matrix_a_allreduce[thor_layer_count] + # 获取thor网络的第thor_layer_count层的矩阵G temp_g = matrix_g_allreduce[thor_layer_count] + + # 计算参数matrix_a的最大值逆向传播系数 + # 注:这里log函数表示对数运算,它的作用是将一个数的自然对数转换为以2为底的对数 matrix_a_inv_max = self.log(matrix_a_max_allreduce[thor_layer_count]) + # 将matrix_a_inv_max乘以-1。这样,就可以得到一个负数,用于后续的矩阵乘法操作 matrix_a_inv_max = self.mul(matrix_a_inv_max, -1) + # exp函数表示以e为底数的指数运算,它的作用是将一个数的自然对数转换为以e为底的对数 matrix_a_inv_max = self.exp(matrix_a_inv_max) + # 将temp_a乘以matrix_a_inv_max。这样,就可以将temp_a的值按照matrix_a_inv_max进行缩放,以适应不同的参数 temp_a = self.mul(temp_a, matrix_a_inv_max) + # 从matrix_g_max_allreduce中获取matrix_g的最大值,然后使用log函数对其进行log运算 matrix_g_inv_max = self.log(matrix_g_max_allreduce[thor_layer_count]) + # 将matrix_g_inv_max乘以-1 matrix_g_inv_max = self.mul(matrix_g_inv_max, -1) + # 对matrix_g_inv_max进行指数运算 matrix_g_inv_max = self.exp(matrix_g_inv_max) + # 将temp_g乘以matrix_g_inv_max temp_g = self.mul(temp_g, matrix_g_inv_max) + # 计算两个最大值之间的乘积。首先,它从matrix_g_max_allreduce中获取当前层的matrix_g的最大值,然后将这个最大值与自身相乘。 + # 这样,就可以计算出两个最大值之间的乘积,以便在后续计算中使用 temp_max = self.mul(matrix_g_max_allreduce[thor_layer_count], matrix_g_max_allreduce[thor_layer_count]) + # 将 temp_a 转换为 float16 类型 temp_a = self.cast(temp_a, mstype.float16) + # 将 temp_g 转换为 float16 类型 temp_g = self.cast(temp_g, mstype.float16) + # 用类中函数根据给定的索引、临时矩阵 temp_a、临时梯度 temp_g、梯度 g 和最大值 temp_max 来计算二次梯度 g, temp_max = self._get_second_grad_by_matmul(i, temp_a, temp_g, g, temp_max) + # 将第 thor_layer_count 层的 matrix_a_cov 和 matrix_g_cov,matrix_max_inv 的值赋给 temp_a 和 temp_g,temp_max self.assign(self.matrix_a[thor_layer_count], temp_a) self.assign(self.matrix_g[thor_layer_count], temp_g) self.assign(self.matrix_max_inv[thor_layer_count], temp_max) + # 将更新的梯度加入到new_grads中 new_grads = new_grads + (g,) + # 更新梯度 gradients = new_grads else: + # 否则,遍历参数 for i in range(len(self.params)): + # 获取梯度 g = gradients[i] + # 根据层的类型计算二次梯度g g = self._get_second_grad_by_layertype(i, matrix_a_allreduce, matrix_g_allreduce, g, damping_step) + # 将更新的梯度加入到new_grads中 new_grads = new_grads + (g,) + # 更新梯度 gradients = new_grads else: + # 创建参数new_grads用于储存计算后的梯度 new_grads = () + # 计算二次梯度g,计算最终参数更新方向 gradients = self._get_second_gradients(new_grads, damping_step, gradients) + # 将cov_step加上一个自增步骤 self.cov_step = self.cov_step + self.one + # 如果weight_decay(权重衰减)大于0,则对参数进行weight_decay if self.weight_decay > 0: + # 使用hyper_map函数对模型中的参数和梯度进行应用权重衰减 gradients = self.hyper_map(F.partial(apply_decay, self.weight_decay), self.decay_flags, params, gradients) + # 将梯度剪切 gradients = clip_gradient(self.enable_clip_grad, gradients) + # 获取学习率 lr = self.get_lr() + # 对模型中的参数和梯度进行momentum优化 success = self.hyper_map(F.partial(_momentum_opt, self.opt, self.momentum, lr), gradients, params, moments) + # 返回成功消息 return success diff --git a/mindspore/python/mindspore/nn/reinforcement/__init__.py b/mindspore/python/mindspore/nn/reinforcement/__init__.py index 467adef4d0e..e5d22efd3d2 100644 --- a/mindspore/python/mindspore/nn/reinforcement/__init__.py +++ b/mindspore/python/mindspore/nn/reinforcement/__init__.py @@ -15,8 +15,11 @@ """ TensorArray. """ +# TensorArray类用于实现一个类似于Python列表的Tensor数组,用于存储和操作Tensor from .tensor_array import (TensorArray) +# 从tensor_array模块中导入TensorArray类 __all__ = [ "TensorArray", ] +# 将TensorArray添加到__all__列表中,这样在导入时,只需要导入__all__列表中的内容即可 diff --git a/mindspore/python/mindspore/nn/reinforcement/_batch_read_write.py b/mindspore/python/mindspore/nn/reinforcement/_batch_read_write.py index e28d87596ce..0aba4de54f6 100644 --- a/mindspore/python/mindspore/nn/reinforcement/_batch_read_write.py +++ b/mindspore/python/mindspore/nn/reinforcement/_batch_read_write.py @@ -12,12 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ +# 这段代码定义了两个类,BatchWrite和BatchRead,它们都是Cell类的子类。 +# BatchWrite类用于将源参数列表写入目标参数列表,而BatchRead类用于从源参数列表读取目标参数列表。 """ BatchReadWrite """ +# 从mindspore.nn.cell模块中导入Cell类和BatchAssign操作 from mindspore.nn.cell import Cell +# BatchAssign操作是mindspore.ops模块中的一个内部操作,用于将源参数列表写入目标参数列表 from mindspore.ops.operations._rl_inner_ops import BatchAssign - +# 在示例中,我们创建了两个网络模型SNet和DNet,分别用于源模型和目标模型。 +# 然后,我们创建了一个Write类,它使用BatchWrite操作将源模型的参数写入目标模型,并创建了一个Read类,它使用BatchRead操作从源模型的参数读取目标模型。 +# 最后,我们使用这两个类分别对源模型和目标模型进行参数的写入和读取操作 class BatchWrite(Cell): r"""BatchWrite: write a list of parameters to assign the target. @@ -62,9 +68,11 @@ class BatchWrite(Cell): def __init__(self): """Initialize BatchWrite""" super(BatchWrite, self).__init__() + # 初始化BatchAssign self.write = BatchAssign(lock=True) def construct(self, dst, src): + # BatchWrite类的construct方法接受两个参数dst和src,分别表示目标参数列表和源参数列表。它首先使用BatchAssign操作将源参数列表写入目标参数列表,然后返回True """ Write the source parameter list to assign the dst. @@ -75,6 +83,9 @@ class BatchWrite(Cell): Returns: Bool, true. """ + """ + 将src指向的内容复制到dst指向的内容中 + """ self.write(dst, src) return True @@ -123,9 +134,11 @@ class BatchRead(Cell): def __init__(self): """Initialize BatchRead""" super(BatchRead, self).__init__() + # 初始化读取 self.read = BatchAssign(lock=False) def construct(self, dst, src): + # BatchRead类的construct方法也接受两个参数dst和src,分别表示目标参数列表和源参数列表。它首先使用BatchAssign操作从源参数列表读取目标参数列表,然后返回True """ Read the source parameter list to assign the dst. @@ -136,5 +149,6 @@ class BatchRead(Cell): Returns: Bool, true. """ + # 读取源参数列表 self.read(dst, src) return True diff --git a/mindspore/python/mindspore/nn/reinforcement/_tensors_queue.py b/mindspore/python/mindspore/nn/reinforcement/_tensors_queue.py index 32dfa7540cb..5b912af6cb2 100644 --- a/mindspore/python/mindspore/nn/reinforcement/_tensors_queue.py +++ b/mindspore/python/mindspore/nn/reinforcement/_tensors_queue.py @@ -12,16 +12,25 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ + +# TensorsQueue类,继承自Cell。TensorsQueue是一个队列,用于存储张量列表。它是mindspore.nn模块的一部分,用于实现深度学习任务中的张量队列 """ TensorsQueue, each element in the queue is a list of tensors. """ +# 导入所需的库 +# 模块中包含了Cell类,用于构建神经网络 from mindspore.nn.cell import Cell +# 模块中包含了_rl_inner_ops,用于实现一些与Reinforcement Learning相关的操作 from mindspore.ops.operations import _rl_inner_ops as rl_ops +# 模块中包含了Rel和Validator类,用于验证参数的合法性 from mindspore._checkparam import Rel, Validator from mindspore.common import dtype as mstype +# 在示例代码中,我们创建了一个TensorsQueue对象,并将其用于存储两个张量变量data1和data2。然后,我们将这两个张量元组放入队列中,并从队列中获取一个张量元组并打印出来 class TensorsQueue(Cell): + # TensorsQueue类的构造函数接受四个参数:数据类型dtype、形状shapes、大小size和名称name。 + # 其中,dtype必须为mindspore.dtype类型的数字类型,size必须大于等于0,shapes的大小必须大于等于1 r''' TensorsQueue: a queue which stores tensors lists. @@ -56,17 +65,29 @@ class TensorsQueue(Cell): def __init__(self, dtype, shapes, size=0, name="TQ"): """Initialize TensorsQueue""" super(TensorsQueue, self).__init__() + # 检查dtype是否为数字类型或布尔型,Rel.GE表示大于等于(greater than or equal to)的关系 Validator.check_subclass("dtype", dtype, mstype.number_type + (mstype.bool_,), self.cls_name) + # 检查size是否为0或大于0 Validator.check_int(size, 0, Rel.GE, "size", self.cls_name) + # 获取shapes的长度 elements_num = len(shapes) + # 检查elements_num是否为1或大于1 Validator.check_int(elements_num, 1, Rel.GE, "len(shapes)", self.cls_name) + # 创建TensorsQueue,传入dtype、shapes、size和name作为参数 self.handle_ = rl_ops.TensorsQueueCreate(dtype, shapes, size, name)() + # 创建TensorsQueuePut,分别表示TensorsQueue的put、get、pop、clear、close和size方法。这些属性都是rl_ops模块中的函数,用于执行相应的操作 self.tensors_q_put = rl_ops.TensorsQueuePut(dtype, shapes) + # 创建TensorsQueueGet self.tensors_q_get = rl_ops.TensorsQueueGet(dtype, shapes) + # 创建TensorsQueueGet self.tensors_q_pop = rl_ops.TensorsQueueGet(dtype, shapes, pop_after_get=True) + # 创建TensorsQueueClear self.tensors_q_clear = rl_ops.TensorsQueueClear() + # 创建TensorsQueueClose self.tensors_q_close = rl_ops.TensorsQueueClose() + # 获取TensorsQueueSize self.tensors_q_size = rl_ops.TensorsQueueSize() + # 总之,这段代码定义了一个名为TensorsQueue的类,继承自nn.cell,并实现了__init__方法来初始化类的属性。同时,定义了一些方法来执行TensorsQueue的相关操作 def put(self, element): """ @@ -78,6 +99,7 @@ class TensorsQueue(Cell): Returns: Bool, true. """ + # 将元组(Tensors)转换为TensorsQueue的元素 self.tensors_q_put(self.handle_, element) return True @@ -88,6 +110,7 @@ class TensorsQueue(Cell): Returns: tuple(Tensors), the element in TensorsQueue. """ + # 从TensorsQueue中取出一个元素 element = self.tensors_q_get(self.handle_) return element @@ -98,7 +121,9 @@ class TensorsQueue(Cell): Returns: tuple(Tensors), the element in TensorsQueue. """ + # 获取第一个元素 element = self.tensors_q_pop(self.handle_) + # 返回元素 return element def size(self): @@ -108,9 +133,10 @@ class TensorsQueue(Cell): Returns: Tensor(mindspore.int64), the used size of TensorsQueue. """ + # 获取当前可用/可用大小 size = self.tensors_q_size(self.handle_) + # 返回当前可用/可用大小 return size - def close(self): """ Close the created TensorsQueue. @@ -123,6 +149,7 @@ class TensorsQueue(Cell): Returns: Bool, true. """ + # 关闭TensorsQueue self.tensors_q_close(self.handle_) return True @@ -134,5 +161,7 @@ class TensorsQueue(Cell): Returns: Bool, true. """ + # 清空tensors_q_clear函数,并将handle_参数赋值给self.handle_ self.tensors_q_clear(self.handle_) + # 返回True return True diff --git a/mindspore/python/mindspore/nn/reinforcement/tensor_array.py b/mindspore/python/mindspore/nn/reinforcement/tensor_array.py index 08fb3b0f09b..1227ddb4e36 100644 --- a/mindspore/python/mindspore/nn/reinforcement/tensor_array.py +++ b/mindspore/python/mindspore/nn/reinforcement/tensor_array.py @@ -12,16 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ + +# 继承自Cell类。TensorArray类用于实现一个类似于Python列表的Tensor数组,用于存储和操作Tensor """ TensorArray """ +# Cell类,用于构建神经网络 from mindspore.nn.cell import Cell +# _tensor_array,用于实现与TensorArray相关的操作 from mindspore.ops.operations import _tensor_array as ta +# Rel和Validator类,用于验证参数的合法性 from mindspore._checkparam import Rel, Validator +# dtype类,用于表示数据类型 from mindspore.common import dtype as mstype class TensorArray(Cell): + # TensorArray类用于实现一个类似于Python列表的Tensor数组,用于存储和操作Tensor r"""TensorArray: a dynamic array to store tensors. .. warning:: @@ -59,14 +66,23 @@ class TensorArray(Cell): def __init__(self, dtype, element_shape, dynamic_size=True, size=0, name="TA"): """Initialize TensorArray""" super(TensorArray, self).__init__() + # 检查dtype是否是数字类型或布尔类型 Validator.check_subclass("dtype", dtype, mstype.number_type + (mstype.bool_,), self.cls_name) + # 检查size是否大于等于0 Validator.check_int(size, 0, Rel.GE, "size", self.cls_name) + # 初始化TensorArray self.handle_ = ta.TensorArray(dtype, element_shape, dynamic_size, size, name)() + # 初始化TensorArrayWrite self.tensor_array_write = ta.TensorArrayWrite() + # 初始化TensorArrayRead self.tensor_array_read = ta.TensorArrayRead(dtype, element_shape) + # 初始化TensorArrayClose self.tensor_array_close = ta.TensorArrayClose() + # 初始化TensorArrayClear self.tensor_array_clear = ta.TensorArrayClear() + # 初始化TensorArrayStack self.tensor_array_stack = ta.TensorArrayStack(dtype, element_shape, dynamic_size, size) + # 初始化TensorArraySize self.tensor_array_size = ta.TensorArraySize() def write(self, index, value): @@ -80,6 +96,7 @@ class TensorArray(Cell): Returns: Bool, true. """ + # 将value写入TensorArray,并返回True self.tensor_array_write(self.handle_, index, value) return True @@ -93,6 +110,7 @@ class TensorArray(Cell): Returns: Tensor, the value in position index. """ + # 读取TensorArray中指定位置的值,并返回 value = self.tensor_array_read(self.handle_, index) return value @@ -108,6 +126,7 @@ class TensorArray(Cell): Returns: Bool, true. """ + # 关闭TensorArray self.tensor_array_close(self.handle_) return True @@ -119,6 +138,7 @@ class TensorArray(Cell): Returns: Bool, true. """ + # 清空TensorArray self.tensor_array_clear(self.handle_) return True @@ -129,6 +149,7 @@ class TensorArray(Cell): Returns: Tensor, all the values will be stacked into one tensor. """ + # 使用tensor_array_stack函数将TensorArray中的值堆叠到一个新的Tensor中 ans = self.tensor_array_stack(self.handle_) return ans @@ -139,5 +160,6 @@ class TensorArray(Cell): Returns: Tensor, the size of TensorArray. """ + # 使用tensor_array_size函数获取TensorArray中的大小 size = self.tensor_array_size(self.handle_) return size diff --git a/mindspore/python/mindspore/nn/sparse/__init__.py b/mindspore/python/mindspore/nn/sparse/__init__.py index 938064cfab0..dfb25d1f515 100644 --- a/mindspore/python/mindspore/nn/sparse/__init__.py +++ b/mindspore/python/mindspore/nn/sparse/__init__.py @@ -15,8 +15,11 @@ """ Sparse related transformation. """ +# 定义了一个名为sparse的模块,其中包含了与稀疏相关转换的类和函数。 from .sparse import (SparseToDense, SparseTensorDenseMatmul) +# 首先,我们从sparse模块中导入SparseToDense和SparseTensorDenseMatmul类。 +# 最后,我们导出了这两个类,以便在其他地方使用 __all__ = [ "SparseToDense", "SparseTensorDenseMatmul", diff --git a/mindspore/python/mindspore/nn/sparse/sparse.py b/mindspore/python/mindspore/nn/sparse/sparse.py index f505dfad5b5..c7d2cf5dde5 100644 --- a/mindspore/python/mindspore/nn/sparse/sparse.py +++ b/mindspore/python/mindspore/nn/sparse/sparse.py @@ -12,12 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ + +# SparseToDense的类,用于将稀疏张量转换为稠密张量。稀疏张量是一种用三元组表示的张量,其中包含非零值的位置、值和稀疏张量的形状。 +# SparseToDense类的主要目的是实现一个名为construct的函数,该函数接受一个稀疏张量作为输入,并返回一个稠密张量 """Sparse related tools.""" +# mindspore.ops是一个操作模块,用于提供各种计算图操作,例如张量计算、数学运算、线性代数运算等。通过从mindspore.ops模块中导入operations,我们可以使用其中的各种操作来处理稀疏数据 from mindspore.ops import operations as P +# 导入Cell类的定义。Cell是MindSpore中的一个基类,用于创建自定义的神经网络层。在大多数情况下,我们不需要直接使用Cell类,但当你需要创建一个自定义层时,可以使用Cell类作为基类 from ..cell import Cell class SparseToDense(Cell): + # 用于将稀疏张量转换为稠密张量。在Python中,为了方便使用,我们将稀疏张量的三元组(索引、值和形状)收集到一个名为SparseTensor的类中。 + # MindSpore使用三个独立的稠密张量(索引张量、值张量和形状张量)来表示稀疏张量。独立的张量可以 wrapped 在SparseTensor对象中,在传递给操作之前。 + # 输入参数coo_tensor是一个表示稀疏张量的COOTensor对象。输出参数是一个稠密张量,它是从稀疏张量转换而来的 """ Converts a sparse tensor(COOTensor) into dense. @@ -67,16 +75,22 @@ class SparseToDense(Cell): def __init__(self): """Initialize SparseToDense.""" + # 在__init__方法中,我们首先调用super(SparseToDense, self).__init__()来初始化父类Cell super(SparseToDense, self).__init__() + # 然后创建一个SparseToDense操作对象self.sparse_to_dense self.sparse_to_dense = P.SparseToDense() def construct(self, sparse_tensor): + # 在construct方法中,我们使用P.SparseToDense操作来实现稀疏张量与稠密张量的乘法。 return self.sparse_to_dense(sparse_tensor.indices, sparse_tensor.values, sparse_tensor.shape) + # 我们还可以通过设置adjoint_st和adjoint_dt属性来控制矩阵的转置操作。最后,我们返回转换后的稠密张量 class SparseTensorDenseMatmul(Cell): + # 定义了一个名为SparseTensorDenseMatmul的类,用于实现稀疏张量与稠密张量的乘法。这个类的主要目的是实现一个名为construct的函数, + # 该函数接受四个参数:稀疏张量的索引、值、稀疏张量的形状和稠密张量。函数的返回是一个稠密张量 """ Multiplies sparse matrix `a` and dense matrix `b`. The rank of sparse matrix and dense matrix must be equal to `2`. @@ -130,10 +144,15 @@ class SparseTensorDenseMatmul(Cell): def __init__(self, adjoint_st=False, adjoint_dt=False): """Initialize SparseTensorDenseMatmul""" + # construct函数中,我们使用P.SparseTensorDenseMatmul操作来实现稀疏张量与稠密张量的乘法 super(SparseTensorDenseMatmul, self).__init__() + # 我们还可以通过设置adjoint_st和adjoint_dt属性来控制矩阵的转置操作 self.adj_st = adjoint_st self.adj_dt = adjoint_dt + # 创建一个SparseTensorDenseMatmul操作对象self.sparse_dense_matmul self.sparse_dense_matmul = P.SparseTensorDenseMatmul(adjoint_st=self.adj_st, adjoint_dt=self.adj_dt) + # 用于实现稀疏张量与稠密张量的乘法 def construct(self, indices, values, sparse_shape, dense): + # 实现稀疏张量与稠密张量的乘法。我们传入indices、values、sparse_shape和dense作为参数,并返回转换后的稠密张量 return self.sparse_dense_matmul(indices, values, sparse_shape, dense) diff --git a/mindspore/python/mindspore/nn/wrap/__init__.py b/mindspore/python/mindspore/nn/wrap/__init__.py index e308761fb1a..305b515fb82 100644 --- a/mindspore/python/mindspore/nn/wrap/__init__.py +++ b/mindspore/python/mindspore/nn/wrap/__init__.py @@ -17,13 +17,27 @@ Wrap cells for networks. Use the Wrapper to combine the loss or build the training steps. """ +""" +主要用于实现深度学习网络的封装。主要包括以下部分: + +定义了一些用于封装网络的类,如ForwardValueAndGrad、TrainOneStepCell、WithLossCell、WithGradCell等。这些类主要用于组合损失函数或构建训练步骤。 +定义了一些用于控制损失缩放和梯度归一化的类,如TrainOneStepWithLossScaleCell、DynamicLossScaleUpdateCell、FixedLossScaleUpdateCell等。这些类主要用于实现损失缩放和梯度归一化。 +定义了一些用于处理数据并行度的类,如DistributedGradReducer、TimeDistributed等。这些类主要用于实现数据并行度和时间分布处理。 +定义了一些用于更新参数的类,如ParameterUpdate等。这些类主要用于实现参数的更新。 +定义了一些用于创建虚拟数据集的类,如VirtualDatasetCellTriple等。这些类主要用于创建虚拟数据集。 +定义了一些用于处理数据流并行的类,如MicroBatchInterleaved、PipelineCell等。这些类主要用于处理数据流并行。 +""" +# 从cell_wrapper文件中导入了一些用于封装网络的类 from .cell_wrapper import ForwardValueAndGrad, TrainOneStepCell, WithLossCell, WithGradCell, WithEvalCell, \ ParameterUpdate, GetNextSingleOp, VirtualDatasetCellTriple, MicroBatchInterleaved, PipelineCell +# 同时,从loss_scale文件中导入了一些用于控制损失缩放和梯度归一化的类 from .loss_scale import TrainOneStepWithLossScaleCell, DynamicLossScaleUpdateCell, FixedLossScaleUpdateCell +# 从grad_reducer文件中导入了一些用于处理数据并行度的类 from .grad_reducer import DistributedGradReducer +# 从layer.timedistributed文件中导入了一些用于创建虚拟数据集的类 from ..layer.timedistributed import TimeDistributed - +# 定义了一个名为__all__的列表,其中包含了深度学习网络封装和优化中的一些关键类 __all__ = [ "TimeDistributed", "ForwardValueAndGrad", diff --git a/mindspore/python/mindspore/nn/wrap/cell_wrapper.py b/mindspore/python/mindspore/nn/wrap/cell_wrapper.py index ae82fe92451..d338f8eb9a0 100644 --- a/mindspore/python/mindspore/nn/wrap/cell_wrapper.py +++ b/mindspore/python/mindspore/nn/wrap/cell_wrapper.py @@ -12,30 +12,48 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ + +# 封装层:它主要用于封装一个Cell类,以便在分布式训练中使用。封装后的Cell类可以在不同设备之间进行梯度同步和聚合,从而确保梯度的准确性并提高训练速度 """Cell_wrapper.""" +# 从types模块中导入两个类型:FunctionType和MethodType from types import FunctionType, MethodType from mindspore import log as logger +# 导入了一些常量和函数,用于获取设备数量、梯度平均值、并行模式、是否启用并行优化等 from mindspore.parallel._utils import (_get_device_num, _get_gradients_mean, _get_parallel_mode, _get_enable_parallel_optimizer) +# 导入一个类ParallelMode,用于表示并行模式 from mindspore.context import ParallelMode +# 导入一个类Validator,用于验证参数 from mindspore._checkparam import Validator as validator +# 导入一个操作符ops,用于处理图像、文本等数据 from mindspore import ops, nn +# 导入一个数据类型mstype,用于表示数据类型 from ...common import dtype as mstype +# 导入两个类:Parameter和ParameterTuple,用于创建神经网络中的参数 from ...common.parameter import Parameter, ParameterTuple +# 导入一个类constexpr,用于表示常量 from ...ops.primitive import constexpr +# 导入一个类C,用于表示组合操作 from ...ops import composite as C +# 导入一个类F,用于表示功能操作 from ...ops import functional as F +# 用于表示操作符。操作符是用于处理图像、文本等数据的一种内置方法,可以用于构建神经网络 from ...ops import operations as P +# 导入一个类_VirtualDataset,用于表示虚拟数据集 from ...ops.operations.comm_ops import _VirtualDataset +# 导入一个类Cell,用于表示神经网络的单元 from ..cell import Cell +# 导入一个类DistributedGradReducer,用于处理分布式训练中的梯度 reduction from .grad_reducer import DistributedGradReducer +# 创建了一个名为_get_datatype的多类型函数图。多类型函数图是一种用于处理多种数据类型的计算图,可以用于构建神经网络 _get_datatype = C.MultitypeFuncGraph("_get_datatype") @_get_datatype.register("Tensor") def _tensors_get_datatype(param): + # 这个处理函数接收一个Tensor类型的参数param,并返回它的数据类型mstype """ Acquire parameter datatype. @@ -47,12 +65,13 @@ def _tensors_get_datatype(param): """ return F.dtype(param) - +# 创建了一个名为_cast_datatype的多类型函数图。多类型函数图是一种用于处理多种数据类型的计算图,可以用于构建神经网络 _cast_datatype = C.MultitypeFuncGraph("_cast_datatype") @_cast_datatype.register("TypeType", "Tensor") def _tensors_cast_datatype(datatype, param): + # 用于将梯度param转换为数据类型datatype。函数接收两个参数:datatype和param,其中datatype是一个数据类型,param是一个Tensor类型的参数 """ Cast gradient to datatype. @@ -63,10 +82,13 @@ def _tensors_cast_datatype(datatype, param): Returns: Tensor, the parameter after operation. """ + # 函数返回一个Tensor类型的参数,表示转换后的梯度 return F.cast(param, datatype) class WithLossCell(Cell): + # 用于封装带有损失函数的神经网络。这个类接受输入数据data和标签label,并计算损失值。WithLossCell类继承自Cell类,并实现了construct方法,用于计算损失值。 + # 同时,WithLossCell类还提供了backbone_network属性,用于获取封装的神经网络 r""" Cell with loss function. @@ -101,28 +123,47 @@ class WithLossCell(Cell): >>> >>> output_data = net_with_criterion(data, label) """ - + # 在初始化WithLossCell类时,需要传入一个Backbone网络backbone和一个损失函数loss_fn def __init__(self, backbone, loss_fn): + # auto_prefix=False表示不自动添加前缀 super(WithLossCell, self).__init__(auto_prefix=False) + # backbone是一个神经网络结构,用于提取特征 self._backbone = backbone + # loss_fn是一个损失函数,用于计算网络预测值与实际值之间的差距 self._loss_fn = loss_fn + # data是输入数据,label是实际标签 def construct(self, data, label): + # 调用self._backbone方法,传入data作为参数,获取神经网络结构backbone的输出。将输出结果赋值给out out = self._backbone(data) + # 调用self._loss_fn方法,传入out和label作为参数,获取损失函数的计算结果。返回结果 return self._loss_fn(out, label) @property def backbone_network(self): + # 属性方法,用于获取WithLossCell类中的_backbone属性(骨干网络) """ Get the backbone network. Returns: Cell, the backbone network. """ + # 当调用backbone_network属性时,会返回_backbone属性的值 return self._backbone class WithGradCell(Cell): + # 用于计算网络梯度的类。它继承自Cell类,它包装了一个具有损失函数的神经网络,用于计算梯度,如果损失函数为空,那么网络必须是包含网络和损失函数的包装器 + # 注:这个类只能在PyNative模式下运行 + """ + 输入参数: + network (Cell): 它是一个具有单个输出的神经网络结构。 + loss_fn (Cell): 它是一个用于计算梯度的基本损失函数,默认值为空。 + sens (Union[None, Tensor, Scalar, Tuple ...]): 用于反向传播,其类型和形状必须与网络输出相同。如果为空,我们将填充一个与输出值相同类型的默认值。 + + Inputs:- **(\*inputs)** (Tuple(Tensor)) 定义了一个名为inputs的输入参数,它是一个包含N个张量的元组,每个张量的形状为(\ldots)。 + Outputs::,它是一个包含与训练参数相同形状的张量的列表。 + """ r""" Cell that returns the gradients. @@ -163,30 +204,54 @@ class WithGradCell(Cell): >>> net_with_criterion = nn.WithLossCell(net, loss_fn) >>> grad_net = nn.WithGradCell(net_with_criterion) """ - + # 定义了一个名为__init__的构造函数,接收三个参数:network、loss_fn和sens。network是一个神经网络结构,用于提取特征; + # loss_fn是一个损失函数,用于计算网络预测值与实际值之间的差距;sens是一个敏感参数,用于反向传播 def __init__(self, network, loss_fn=None, sens=None): + # 调用父类的__init__方法,用于初始化WithGradCell类的属性和方法。auto_prefix=False表示不自动添加前缀 super(WithGradCell, self).__init__(auto_prefix=False) self.network = network self.loss_fn = loss_fn + # 使用ParameterTuple方法将network中的可训练参数转换为一个元组 self.weights = ParameterTuple(network.trainable_params()) + # 使用C.GradOperation方法创建一个用于计算梯度的操作,get_by_list=True表示按照参数列表获取梯度,sens_param=(sens is not None)表示如果sens不为空,则使用sens作为敏感参数 self.grad = C.GradOperation(get_by_list=True, sens_param=(sens is not None)) self.sens = sens + # 如果loss_fn为空,则执行以下操作 if loss_fn is None: + # 将network赋值给self.network_with_loss属性 self.network_with_loss = network else: + # 创建一个WithLossCell对象,将self.network和self.loss_fn作为参数传入 self.network_with_loss = WithLossCell(self.network, self.loss_fn) + # 将网络设置为训练模式 self.network_with_loss.set_train() def construct(self, *inputs): + # 获取WithGradCell类中的weights属性,用于存储网络中的可训练参数 weights = self.weights + # 如果self.sens为空,则执行以下操作,sens是一个敏感参数,用于反向传播 if self.sens is None: + # 使用self.grad方法计算网络self.network_with_loss的梯度,将结果赋值给grads grads = self.grad(self.network_with_loss, weights)(*inputs) else: + # 使用self.grad方法计算网络self.network_with_loss的梯度,将结果乘以self.sens作为敏感参数,赋值给grads grads = self.grad(self.network_with_loss, weights)(*inputs, self.sens) + # 返回计算得到的梯度值 return grads class ForwardValueAndGrad(Cell): + # 用于封装训练网络的类。它包括一个神经网络结构network和一个梯度函数。通过输入*inputs来训练网络,并在梯度函数中计算梯度 + + # 参数weights用于存储网络中的可训练参数,默认值为空。 + # get_all用于控制是否获取所有梯度,默认值为False。 + # get_by_list用于控制是否按参数列表获取梯度,默认值为False。 + # sens_param用于控制是否将敏感参数(梯度对输出)作为输入,默认值为False + + # 输入参数*inputs是一个包含N个张量的元组,每个张量的形状为(\ldots)。sens是一个敏感参数,用于反向传播。 + # 如果网络有一个输出,那么sens是一个张量。如果网络有多个输出,那么sens是一个元组(张量) + + # 输出参数forward value表示网络前向运行的结果。gradients是一个包含网络参数和输入梯度的元组 r""" Encapsulate training network. @@ -253,43 +318,75 @@ class ForwardValueAndGrad(Cell): """ def __init__(self, network, weights=None, get_all=False, get_by_list=False, sens_param=False): + # auto_prefix=False表示不自动添加前缀 super(ForwardValueAndGrad, self).__init__(auto_prefix=False) + """ + 检验输入参数是否符合要求 + """ + # 检查network的类型是否为Cell、FunctionType或MethodType if not isinstance(network, (Cell, FunctionType, MethodType)): + # 如果不是,则抛出类型错误异常 raise TypeError(f"For 'ForwardValueAndGrad', " f"the argument 'network' should be cell, function type or method type, " f"but got '{type(network)}'") + # 检查get_all的类型是否为bool if not isinstance(get_all, bool): + # 如果不是,则抛出类型错误异常 raise TypeError(f"For 'ForwardValueAndGrad', " f"the type of 'get_all' should be bool, but got '{type(get_all)}'") + # 检查get_by_list的类型是否为bool if not isinstance(get_by_list, bool): + # 如果不是,则抛出类型错误异常 raise TypeError(f"For 'ForwardValueAndGrad', " f"the type of 'get_by_list' should be bool, but got '{type(get_by_list)}'") + # 检查get_by_list是否为True且weights的类型是否为ParameterTuple if get_by_list and not isinstance(weights, ParameterTuple): + # 如果不是,则抛出类型错误异常 raise TypeError(f"For 'ForwardValueAndGrad', " f"when 'get_by_list' is set to True, the argument 'weights' should be " f"ParameterTuple type, but got '{type(weights)}'") + """ + 设置所需参数 + """ self.network = network + # 检查network是否为Cell类型 if isinstance(network, Cell): + # 如果是Cell类型,则调用set_grad方法设置梯度 self.network.set_grad() self.weights = weights self.get_all = get_all self.get_by_list = get_by_list self.sens_param = sens_param + # 然后创建一个C.GradOperation对象,用于计算梯度,并将相关参数赋值给self.grad属性 self.grad = C.GradOperation(get_all=self.get_all, get_by_list=self.get_by_list, sens_param=self.sens_param) + # 用于计算网络梯度 def construct(self, *inputs): grad_inputs = inputs + # 如果self.sens_param为True if self.sens_param: + # 将输入参数列表中的最后一个元素删除,因为敏感参数通常不需要计算梯度 inputs = inputs[:-1] + # 调用self.network方法计算网络前向运行的结果,并将结果赋值给loss loss = self.network(*inputs) + # 如果 按参数列表获取梯度 为True if self.get_by_list: + # 使用self.grad方法计算网络self.network和self.weights的梯度,并将结果赋值给grads grads = self.grad(self.network, self.weights)(*grad_inputs) else: + # 使用self.grad方法计算网络self.network的梯度,并将结果赋值给grads grads = self.grad(self.network)(*grad_inputs) + # 返回计算得到的损失值loss和梯度值grads return loss, grads class TrainOneStepCell(Cell): + # 用于封装训练网络的类。它包含一个神经网络结构network和一个优化器optimizer。通过输入*inputs来训练网络,并在构造函数中创建一个反向图,用于更新网络参数。 + # 不同类型的并行模式用于训练 + + # 参数network用于存储训练网络的结构,optimizer用于存储优化器,sens用于存储敏感参数(梯度对输出),默认值为1.0 + + # 输入参数*inputs是一个包含N个张量的元组,每个张量的形状为(\ldots)。输出参数为一个张量,表示损失值,通常为() r""" Network training package class. @@ -342,43 +439,79 @@ class TrainOneStepCell(Cell): """ def __init__(self, network, optimizer, sens=1.0): + # 调用父类Cell的构造函数,并设置auto_prefix为False,表示不添加前缀 super(TrainOneStepCell, self).__init__(auto_prefix=False) self.network = network + # 调用self.network的set_grad方法设置梯度 self.network.set_grad() self.optimizer = optimizer + # 调用self.optimizer的parameters方法获取参数列表,并赋值给self.weights属性 self.weights = self.optimizer.parameters + # 创建一个C.GradOperation对象,用于计算梯度,并设置get_by_list为True和sens_param为True self.grad = C.GradOperation(get_by_list=True, sens_param=True) self.sens = sens + # 将reducer_flag设置为False self.reducer_flag = False self.grad_reducer = F.identity + # 调用_get_parallel_mode函数获取并行模式 self.parallel_mode = _get_parallel_mode() + # 判断parallel_mode是否在(ParallelMode.DATA_PARALLEL, ParallelMode.HYBRID_PARALLEL)范围内,如果是,则将reducer_flag设置为True self.reducer_flag = self.parallel_mode in (ParallelMode.DATA_PARALLEL, ParallelMode.HYBRID_PARALLEL) + # 如果reducer_flag为True if self.reducer_flag: + # 调用_get_gradients_mean函数获取梯度平均值 self.mean = _get_gradients_mean() + # 调用_get_device_num函数获取设备数量 self.degree = _get_device_num() if isinstance(self.optimizer, (nn.AdaSumByGradWrapCell, nn.AdaSumByDeltaWeightWrapCell)): + # 导入get_group_size、create_group和get_rank等通信管理函数 from mindspore.communication.management import get_group_size, create_group, get_rank + # 计算分组数量,将其设置为get_group_size()的一半 group_number = get_group_size() // 8 + # 将degree除以group_number得到每个分组的设备数量 self.degree = int(self.degree / group_number) + # 创建一个分组列表,每个列表包含一个分组的设备ID group_list = [list(range(x * self.degree, (x + 1) * self.degree)) for x in range(group_number)] + # 获取当前设备的ID,并将其设置为分组列表的索引 current_index = get_rank() // 8 + # 创建一个服务器分组名称 server_group_name = "allreduce_" + str(current_index) + # 创建一个分组,将当前设备添加到该分组中 create_group(server_group_name, group_list[current_index]) + # 用于分布式梯度归约,并将weights、mean、degree和group属性赋值给该对象 self.grad_reducer = DistributedGradReducer(self.weights, self.mean, self.degree, group=server_group_name) else: + # 用于分布式梯度归约,并将weights、mean和degree属性赋值给该对象 self.grad_reducer = DistributedGradReducer(self.weights, self.mean, self.degree) def construct(self, *inputs): + # 计算网络前向运行的结果loss,并将结果赋值给loss loss = self.network(*inputs) + # 创建一个与loss形状相同的敏感参数sens,并将self.sens赋值给sens sens = F.fill(loss.dtype, loss.shape, self.sens) + # 计算网络self.network和self.weights的梯度 grads = self.grad(self.network, self.weights)(*inputs, sens) + # 对grads进行分布式梯度归约 grads = self.grad_reducer(grads) + # 更新网络参数loss loss = F.depend(loss, self.optimizer(grads)) + # 返回更新后的网络参数loss return loss class GetNextSingleOp(Cell): + # 它是用于获取下一个操作的类。它主要用于在训练过程中获取下一个数据样本 + """ + 主要参数如下: + + dataset_types (list[:class:mindspore.dtype]): 数据集的类型。 + dataset_shapes (list[tuple[int]]): 数据集的形状。 + queue_name (str): 队列名称,用于获取数据。 + + 输出参数为一个元组,包含从数据集中获取的数据 + """ + """ Cell to run for getting the next operation. @@ -417,13 +550,19 @@ class GetNextSingleOp(Cell): def __init__(self, dataset_types, dataset_shapes, queue_name): super(GetNextSingleOp, self).__init__() + # 创建一个P.GetNext对象,用于获取数据集的下一个数据样本 self.get_next = P.GetNext(dataset_types, dataset_shapes, len(dataset_types), queue_name) def construct(self): + # 直接调用get_next对象的__call__方法来获取下一个数据样本 return self.get_next() class _VirtualDatasetCell(Cell): + # 它是用于封装网络的类。在训练过程中,它会将数据并行转换为模型并行。_VirtualDataset是一个虚拟的原子,它在最终执行图中不存在。 + # 输入和输出在数据并行模式下分布,在编译过程中,会自动插入分布式重分配Primitive + # 注:仅在半自动并行和自动并行模式下使用 + # 参数backbone用于存储要封装的神经网络结构 """ Wrap the network with virtual dataset to convert data parallel layout to model parallel layout. @@ -445,23 +584,29 @@ class _VirtualDatasetCell(Cell): def __init__(self, backbone): super(_VirtualDatasetCell, self).__init__(auto_prefix=False) self._backbone = backbone + # 创建一个_VirtualDataset对象,它是用于实现虚拟数据集的类 self._virtual_dataset = _VirtualDataset() def construct(self, *inputs): + # 将inputs传递给_virtual_dataset对象 output = self._virtual_dataset(*inputs) + # 然后将返回的输出传递给_backbone对象 return self._backbone(*output) @constexpr def _check_shape_value_on_axis_divided_by_target_value(input_shape, dim, param_name, cls_name, target_value): + # 常量函数,用于检查输入形状在指定轴上的值是否可以被目标值整除。如果不能被目标值整除,则抛出一个错误 if input_shape[dim] % target_value != 0: raise ValueError(f"For MicroBatchInterleaved initialization, " f"{cls_name} {param_name} at {dim} shape should be divided by {target_value}," f"but got {input_shape[dim]}") + # 否则,返回正常 return True class _MicroBatch(Cell): + # 用于将mini-batch转换为micro-batch的类 """ transform mini-batch to micro-batch in pipeline parallel. @@ -470,31 +615,46 @@ class _MicroBatch(Cell): """ def __init__(self, micro_size): super(_MicroBatch, self).__init__() + # 创建一个P.Shape对象获取输入张量的形状和一个P.StridedSlice对象对输入张量进行切片 self.shape = P.Shape() self.micro_size = micro_size self.strided_slice = P.StridedSlice() def construct(self, i, *inputs): + # 首先定义了一个空元组micro_inputs micro_inputs = () + # 然后遍历输入张量inputs for each_input in inputs: + # 对于每个输入张量,首先获取其形状input_shape input_shape = self.shape(each_input) + # 然后调用函数检查input_shape在指定轴上的值是否可以被目标值整除,如果不能被目标值整除,则抛出一个错误 _check_shape_value_on_axis_divided_by_target_value(input_shape, 0, "inputs", self.cls_name, self.micro_size) + # 接下来,计算micro_batch_begin和micro_batch_end,分别表示当前micro-batch的起始和结束位置 micro_batch_begin = i * input_shape[0] // self.micro_size micro_batch_end = (i + 1) * input_shape[0] // self.micro_size + # 然后,创建一个StridedSlice对象,用于按照指定的strided_slice_begin、strided_slice_strides和strided_slice_end对输入张量进行切片 strided_slice_begin = (micro_batch_begin,) strided_slice_strides = (1,) + # 用一个for循环遍历输入张量的形状,除了最后一个轴 for _ in range(len(input_shape) - 1): + # 在每次循环中,strided_slice_begin和strided_slice_strides分别添加一个0和一个1 strided_slice_begin += (0,) strided_slice_strides += (1,) strided_slice_end = (micro_batch_end,) + # 然后添加输入张量的其他轴的形状 strided_slice_end += input_shape[1:] + # 使用StridedSlice对象self.strided_slice对输入张量each_input进行切片。切片的起始位置为strided_slice_begin, + # 步长为strided_slice_strides,结束位置为strided_slice_end。最后,将切片的输出赋值给变量micro_input micro_input = self.strided_slice(each_input, strided_slice_begin, strided_slice_end, strided_slice_strides) + # 最后,将切片的输出添加到micro_inputs元组中 micro_inputs += (micro_input,) return micro_inputs class MicroBatchInterleaved(Cell): + # 用于包装一个网络,以便在输入时使用批量大小。 + # 接受两个参数:network(要包装的神经网络)和interleave_num(批量大小分割的数目,默认为2) """ Wrap the network with Batch Size. @@ -511,29 +671,48 @@ class MicroBatchInterleaved(Cell): """ def __init__(self, network, interleave_num=2): super(MicroBatchInterleaved, self).__init__(auto_prefix=False) + # 首先,代码检查interleave_num(批量大小分割的数目)是否为整数 if not isinstance(interleave_num, int): + # 如果不是,则抛出一个类型错误 raise TypeError("For 'MicroBatchInterleaved', the argument 'interleave_num' should be integer, " "but got the type : {}.".format(type(interleave_num))) + # 接着,检查interleave_num是否大于0 if interleave_num <= 0: + # 如果不是,则抛出一个值错误 raise ValueError("For 'MicroBatchInterleaved', the argument 'interleave_num' should be greater than 0, " "but got {}.".format(interleave_num)) + # 然后,代码将network作为属性添加到self中 self.network = network self.interleave_num = interleave_num + # 接着,代码创建一个nn.CellList对象,用于存储对interleave_inputs的引用 self.interleave_inputs = nn.CellList() + # 遍历interleave_num数目 for _ in range(interleave_num): + # 为interleave_inputs中的每个元素创建一个_MicroBatch对象 interleave_data = _MicroBatch(interleave_num) + # 为interleave_data中的StridedSlice对象添加一个名为strided_slice_flag的属性,并将其值设置为True。 + # 这样,在后续处理中,可以通过检查这个属性来判断这个StridedSlice对象是否是用于处理批量大小的 interleave_data.strided_slice.add_prim_attr("strided_slice_flag", True) + # 并将其添加到nn.CellList中 self.interleave_inputs.append(interleave_data) def construct(self, *inputs): + # 首先,将output初始化为0.0 output = 0.0 + # 遍历interleave_num次 for i in range(self.interleave_num): + # 在每次循环中,从interleave_inputs中获取第i个元素,将其作为interleave_input,并将interleave_input传递给network interleave_input = self.interleave_inputs[i](i, *inputs) + # 将network的输出添加到output中 output += self.network(*interleave_input) + # 最后,返回output return output class PipelineCell(Cell): + # 用于包装一个网络,以便在输入时使用微批次数量。将MiniBatch切分成更细粒度的MicroBatch,用于流水线并行的训练中 + # PipelineCell的构造函数接受两个参数:network(要包装的神经网络)和micro_size(用于处理批量大小的微批次数量) + # 注:micro_size必须大于或等于流水线stage的个数 """ Wrap the network with Micro Batch. @@ -554,60 +733,99 @@ class PipelineCell(Cell): def __init__(self, network, micro_size): super(PipelineCell, self).__init__(auto_prefix=False) self.network = network + # 创建一个nn.CellList对象,用于存储对micro_inputs的引用 self.micro_inputs = nn.CellList() self.micro_size = micro_size + # 定义了一个名为add_list的列表,用于存储_MicroBatch对象的结果 self.add_list = [] + # 遍历micro_size次 for i in range(micro_size): + # 然后,为micro_inputs中的每个元素创建一个_MicroBatch对象 micro_input = _MicroBatch(micro_size) + # 并将其添加到nn.CellList中 self.micro_inputs.append(micro_input) + # 创建了一个名为add的P.Add对象,为其添加了一个名为pipeline_end的属性,该属性的值为i self.add = P.Add().add_prim_attr("pipeline_end", i) + # 然后,将add对象添加到add_list中 self.add_list.append(self.add) def construct(self, *inputs): + # 定义了一个名为ret的变量,初始值为None ret = None + # 遍历micro_size次 for i in range(self.micro_size): + # 在每次循环中,首先从micro_inputs中获取第i个元素的输入 micro_input = self.micro_inputs[i](i, *inputs) + # 然后将它们传递给network。将network的输出存储在output中 output = self.network(*micro_input) + # 接下来,判断ret是否为None if ret is not None: + # 否则,将add_list[i]与output相加,并将结果作为ret的值 ret = self.add_list[i](ret, output) else: + # 如果是,则将output作为ret的值 ret = output return ret def _pipeline_clear_grad(accu_grad, grad): + # 用于清除accu_grad的梯度 + # 首先,将accu_grad与grad相加 accu_grad = F.depend(accu_grad, grad) + # 接下来,创建一个全为0的zeros张量,并将其与accu_grad相乘 zeros = F.tensor_mul(accu_grad, 0.0) + # 返回accu_grad return F.assign(accu_grad, zeros) class _TrainPipelineAccuStepCell(TrainOneStepCell): + # 用于在训练过程中封装网络及其优化器,以实现管道模式 """ Wraps the network with an optimizer in pipeline mode. """ def __init__(self, network, optimizer, sens=1.0): + # 首先,初始化_TrainPipelineAccuStepCell类的实例,传入网络、优化器和sens参数。其中,sens参数用于设置损失函数的权重。 super(_TrainPipelineAccuStepCell, self).__init__(network, optimizer, sens) + # 定义一个名为accu_grads的变量,其值为weights的副本,并使用clone方法初始化为全零张量 self.accu_grads = self.weights.clone(prefix="accu_grads", init="zeros") + # 同时,定义一个名为hyper_map的ops.HyperMap对象,用于执行下方的_pipeline_clear_grad函数 self.hyper_map = ops.HyperMap() + # 获取是否使用并行优化器 self.opt_shard = _get_enable_parallel_optimizer() def construct(self, *inputs): weights = self.weights + # 首先计算网络的损失函数loss loss = self.network(*inputs) + # 使用ops.Fill函数创建一个全零张量,并将其数据类型设置为loss的数据类型,形状设置为loss的形状。然后,将该张量与sens相加,并将结果赋值给sens。 + # 这里使用ops.Fill函数而不是F.fill函数,是因为ops.Fill函数可以用于自动求导 sens = ops.Fill()(ops.DType()(loss), ops.Shape()(loss), self.sens) + # 然后计算损失函数的梯度grads grads = self.grad(self.network, weights)(*inputs, sens) + # 将梯度与accu_grads相加 accu_grads = ops.depend(self.accu_grads, grads) + # 判断是否启用并行优化器 if self.opt_shard: + # 如果启用,则使用optimizer函数直接计算梯度 succ = self.optimizer(grads) else: + # 否则,传入accu_grads计算 succ = self.optimizer(accu_grads) + # 通过使用ops.depend函数,可以将succ的值作为loss的梯度,从而实现自动求导 loss = ops.depend(loss, succ) + # 最后,调用_pipeline_clear_grad函数清理accu_grads clear = self.hyper_map(_pipeline_clear_grad, accu_grads, grads) + # 通过使用ops.depend函数,可以将clear的值作为loss的梯度,从而实现自动求导 loss = ops.depend(loss, clear) + # 并返回损失函数 return loss class VirtualDatasetCellTriple(Cell): + # 用于将数据并行布局转换为模型并行布局,已经弃用 + # VirtualDatasetCellTriple是一个虚拟的原始类,它在最终的执行图中不存在。输入和输出在数据并行模式下分布式,在编译过程中动态插入张量重分配Primitive + # 注意:这段代码仅在半自动并行和自动并行模式下使用。在这个类中,有三个输入,与相反的两个输入不同,已经弃用 + # 接受一个Backbone网络作为参数,用于封装 """ Wrap the network with virtual dataset to convert data parallel layout to model parallel layout. @@ -629,14 +847,18 @@ class VirtualDatasetCellTriple(Cell): def __init__(self, backbone): super(VirtualDatasetCellTriple, self).__init__(auto_prefix=False) + # logger.warning函数发出警告,表示VirtualDatasetCellTriple已被弃用 logger.warning("WARN_DEPRECATED: The usage of VirtualDatasetCellTriple is deprecated.") self._backbone = backbone def construct(self, a, b, c): + # 接收三个输入参数a、b和c,并调用self._backbone函数计算结果 return self._backbone(a, b, c) class WithEvalCell(Cell): + # 该类继承自Cell。WithEvalCell类用于包装前向网络和损失函数,以便在计算评估指标时使用 + # 接收三个参数:network(前向网络)、loss_fn(损失函数)和add_cast_fp32(是否将数据类型调整为float32)。输入参数data和label分别是网络的输入和标签 r""" Wraps the forward network with the loss function. @@ -669,21 +891,35 @@ class WithEvalCell(Cell): """ def __init__(self, network, loss_fn, add_cast_fp32=False): + # 不添加前缀 super(WithEvalCell, self).__init__(auto_prefix=False) self._network = network self._loss_fn = loss_fn + # 检验输入参数add_cast_fp32是否为bool值,否则抛出异常 self.add_cast_fp32 = validator.check_value_type("add_cast_fp32", add_cast_fp32, [bool], self.cls_name) + # 用于计算损失、网络输出和标签 def construct(self, data, label): + # 调用self._network函数计算网络的输出。data是网络的输入数据 outputs = self._network(data) + # 如果add_cast_fp32为True if self.add_cast_fp32: + # 则将标签调整为float32 label = F.mixed_precision_cast(mstype.float32, label) + # 使用F.cast函数将输出调整为float32数据类型 outputs = F.cast(outputs, mstype.float32) + # 计算损失 loss = self._loss_fn(outputs, label) + # 最后,返回一个包含损失、网络输出和标签的元组 return loss, outputs, label class ParameterUpdate(Cell): + # 用于手动更新参数 + + # 在示例中,首先创建一个具有3个输入神经元的全连接层网络,并获取该层的参数。然后,创建一个ParameterUpdate对象,并将参数传递给它。 + # 接着,将phase设置为"update_param",以便在训练过程中使用此更新器。最后,创建一个随机张量作为输入,并使用ParameterUpdate对象更新参数 + """ Cell that updates parameter. @@ -720,42 +956,62 @@ class ParameterUpdate(Cell): [ 6. 7. 8.] [ 9. 10. 11.]] """ - + # 构造函数接收一个参数param,该参数是一个Parameter对象 def __init__(self, param): super(ParameterUpdate, self).__init__(auto_prefix=False) + # 检查param是否为Parameter类型 if not isinstance(param, Parameter): + # 如果不是,则抛出一个TypeError,表示param必须是Parameter类型 raise TypeError("For 'ParameterUpdate', 'param' must be 'Parameter', but got {}.".format(type(param))) self._param = param + # 输入参数x是用于更新参数的输入张量,其形状和类型与param相同 def construct(self, x): + # 使用F.assign函数将输入x的值赋值给参数param F.assign(self._param, x) + # 最后,返回输入x return x class _BroadCastCell(Cell): + # 用于将参数从设备0广播到其他设备 """ Broadcast the parameters from device 0 to other devices. Args: params (list): The parameters of Net. """ - + # 构造函数接收一个参数params,该参数是一个list,包含需要广播的参数 def __init__(self, params): super(_BroadCastCell, self).__init__() + # 导入get_group_size和create_group函数。这两个函数可能用于处理分布式训练中的通信管理 from mindspore.communication.management import get_group_size, create_group + # 从MindSpore库中导入context模块。context模块主要用于设置和获取MindSpore计算图的运行环境,如设备类型、执行模式等 from mindspore import context + # C.Map函数通常用于将一个函数应用于序列的每个元素。这样,我们可以为序列中的每个元素应用相同的操作,从而简化代码 self.map_ = C.Map() + # 将params转换为一个元组,并将其存储在self.params中 self.params = tuple(params) + # 判断当前设备的目标是否为Ascend,并且当前模式是否不是PYNATIVE_MODE if context.get_context("device_target") == "Ascend" and context.get_context("mode") != context.PYNATIVE_MODE: + # 如果是,则创建一个名为BroadcastWorldGroup的组,并将所有设备的ID存储在rank_list中 rank_list = [id for id in range(0, get_group_size())] + # 获取当前设备的数量,并将其存储在rank_list中 create_group("BroadcastWorldGroup", rank_list) + # 最后,使用P.Broadcast函数创建一个将参数广播到所有设备的对象 self.broadcast = P.Broadcast(0, group="BroadcastWorldGroup") else: + # 此时使用P.Broadcast函数创建一个将参数广播到所有设备的对象,并设置其组为None self.broadcast = P.Broadcast(0) def construct(self): + # 首先获取当前设备的数量,并根据设备类型和模式判断是否需要创建一个组 datatypes = self.map_(F.partial(_get_datatype), self.params) + # 然后,使用C.Map函数将参数的数据类型映射为float32,并使用F.partial函数创建一个将数据类型转换为float32的函数 params = self.map_(F.partial(_cast_datatype, mstype.float32), self.params) + # 最后,使用P.Broadcast函数将参数广播到所有设备 params = self.broadcast(params) + # 将datatypes和params组合成一个元组,然后使用F.partial函数创建一个将数据类型转换为float32的函数 new_params = self.map_(F.partial(_cast_datatype), datatypes, params) + # 返回一个新的参数元组,其中包含了转换后的参数 return new_params diff --git a/mindspore/python/mindspore/nn/wrap/grad_reducer.py b/mindspore/python/mindspore/nn/wrap/grad_reducer.py index df879847bfc..326082ce470 100644 --- a/mindspore/python/mindspore/nn/wrap/grad_reducer.py +++ b/mindspore/python/mindspore/nn/wrap/grad_reducer.py @@ -12,83 +12,151 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ + +# 实现分布式训练中的梯度 reduction cell,它的用途是用于对分布式训练中的梯度进行处理,以确保其准确性并提高训练速度。在训练过程中,梯度需要在不同的设备之间进行交换和聚合, +# 这可能会导致数据精度 mixed 的问题。它可以帮助解决这个问题,它会在处理梯度之前将梯度转换为 float32,然后在处理完成后将其转换回原始数据类型。这样,结果才会更可靠 """grad reducer cell for distributed training""" +# context:用于设置和获取计算设备 from mindspore import context +# logger:用于记录日志 from mindspore import log as logger +# Cell:用于创建自定义的神经网络单元 from mindspore.nn.cell import Cell +# GlobalComm:用于管理全局通信 +# get_group_size:获取当前进程所在的组大小 from mindspore.communication.management import GlobalComm, get_group_size +# RowTensor:用于表示二维稀疏张量 from mindspore.common.tensor import RowTensor +# composite:用于提供一些组合操作,如C.multiply等 from mindspore.ops import functional as F, composite as C +# comm_ops:用于实现通信操作,如AllReduce和AllGather from mindspore.ops.operations.comm_ops import AllReduce, AllGather +# auto_parallel_context:用于自动并行上下文 from mindspore.parallel._auto_parallel_context import auto_parallel_context +# mstype:用于表示数据类型 import mindspore.common.dtype as mstype +# Tensor:用于表示张量 from mindspore.common.tensor import Tensor - +# 多类型函数图是一种用于创建和处理多类型操作的图。在这里,我们使用C.MultitypeFuncGraph创建了一个名为reduce_opt的多类型函数图 reduce_opt = C.MultitypeFuncGraph("reduce_opt") def _init_allreduce_operators(length, split_indices, group=GlobalComm.WORLD_COMM_GROUP): + # 用于初始化所有reduce通信操作 """ initialize allreduce communication operators""" + # 遍历split_indices for indices in split_indices: + # 检查每个索引是否大于等于梯度长度length if indices >= length: + # 如果大于等于,则输出警告信息 logger.warning(f"AllReduce's split index {indices} is greater than or equal to" f"the total gradient's number of {length}") + # 计算融合类型和融合操作的列表。融合类型是一个整数,表示不同梯度之间的融合方式。 fusion_type = 2 ** 10 split = 0 + # 融合操作是一个元组,表示不同梯度的融合操作 fusion = () + # 遍历梯度长度 for i in range(length): + # 将融合类型fusion_type加到融合操作fusion中 fusion = fusion + (fusion_type,) + # 检查split是否大于等于split_indices的长度。如果是,则继续下一次循环 if split >= len(split_indices): continue + # 如果split_indices[split]小于等于当前索引i if split_indices[split] <= i: + # 则将融合类型fusion_type加1,并更新split fusion_type += 1 split += 1 + # 定义了一个名为index的元组,表示从1到length的整数 index = tuple(range(1, length + 1)) + # 然后,创建了一个空元组op_list,用于存储所有AllReduce操作 op_list = () for i in range(length): + # 创建AllReduce操作op op = AllReduce('sum', group) + # 于每个操作,添加fusion和index属性,分别表示融合类型和索引 op.add_prim_attr('fusion', fusion[i]) op.add_prim_attr('index', index[i]) + # 将op添加到op_list后,op_list将变为一个包含op的AllReduce操作元组 op_list = op_list + (op,) + # 将创建的AllReduce操作列表返回 return op_list def _init_allreduce_operators_by_parameters(parameters, split_indices, group, fusion_type=1): + # 用于根据参数初始化所有reduce通信操作 """ initialize allreduce communication operators by parameters""" + # 创建一个空元组op_list,用于存储所有AllReduce操作 op_list = () + # 用于表示是否需要融合参数,初始为False param_fusion = False + # 用于存储上一个融合操作,初始为无 last_comm_fusion = None + # 用于表示是否是第一个参数,初始为True first_parameter_flag = True + # 用于表示参数的索引 index = 1 + # 遍历参数列表parameters中的每个参数 for parameter in parameters: + # 读取参数中的融合操作 comm_fusion = parameter.comm_fusion + # 在循环中,它首先检查first_parameter_flag是否为True,是第一个参数 if first_parameter_flag: + # 则将last_comm_fusion设置为当前comm_fusion last_comm_fusion = comm_fusion + # 并将判断用参数设置为False first_parameter_flag = False + # 然后,检查param_fusion是否为False elif not param_fusion: + # 如果是,则检查当前comm_fusion是否与last_comm_fusion不同 if comm_fusion != last_comm_fusion: + # 如果是,则将param_fusion设置为True,表示需要融合参数 param_fusion = True + # 并将last_comm_fusion设置为当前comm_fusion last_comm_fusion = comm_fusion + # 接下来,创建一个AllReduce操作op op = AllReduce('sum', group) + # 并添加fusion和index属性 op.add_prim_attr('fusion', comm_fusion) op.add_prim_attr('index', index) + # 后更新index index += 1 + # 将op添加到op_list中 op_list = op_list + (op,) + # 首先检查经过上述操作后param_fusion是否仍为False if not param_fusion: + # 如果是,则检查split_indices是否存在且fusion_type为1 if split_indices and fusion_type == 1: + # 初始化所有reduce通信操作 op_list = _init_allreduce_operators(len(parameters), split_indices, group) + # 最后,将param_fusion设置为True param_fusion = True else: + # 否则,创建一个空的存储AllReduce操作的元组 op_list = () + # 返回更新后的元组,并返回是否需要融合参数 return op_list, param_fusion @reduce_opt.register("Tensor", "Bool", "Function", "Function", "Bool", "Tensor") def _tensors_allreduce(degree, mean, allgather, allreduce, allreduce_filter, grad): + # 用于根据给定的参数对张量进行allreduce操作 + """ + 函数的主要参数如下: + + degree(整数):表示均值系数。 + mean(布尔值):当mean为True时,将均值系数(degree)应用于梯度。 + allgather(Primitive):用于处理稀疏梯度的通信操作。 + allreduce(Primitive):用于处理梯度的通信操作。 + allreduce_filter(布尔值):当allreduce_filter为True时,对梯度进行allreduce操作。 + grad(张量):表示输入的梯度张量,在操作之前。 + """ + """ Apply allreduce on gradient. @@ -103,16 +171,33 @@ def _tensors_allreduce(degree, mean, allgather, allreduce, allreduce_filter, gra Returns: Tensor, the gradient tensor after operation. """ + # 检查allreduce_filter是否为True if allreduce_filter: + # 如果是,则对grad进行allreduce操作 grad = allreduce(grad) + # 如果mean为True if mean: + # 则将degree乘以grad,并将结果赋值给grad grad = F.tensor_mul(grad, F.cast(degree, F.dtype(grad))) + # 返回处理后的grad return grad return grad @reduce_opt.register("Tensor", "Bool", "Bool", "Tensor") def _tensors_allreduce_post(degree, mean, allreduce_filter, grad): + # 用于在PyNative模式下根据给定的参数对张量进行allreduce操作 + """ + 函数的主要参数如下: + + degree(整数):表示均值系数。 + mean(布尔值):当mean为True时,将均值系数(degree)应用于梯度。 + allgather(Primitive):用于处理稀疏梯度的通信操作。 + allreduce(Primitive):用于处理梯度的通信操作。 + allreduce_filter(布尔值):当allreduce_filter为True时,对梯度进行allreduce操作。 + grad(张量):表示输入的梯度张量,在操作之前。 + """ + """ Apply allreduce on gradient in PyNative mode. @@ -127,15 +212,33 @@ def _tensors_allreduce_post(degree, mean, allreduce_filter, grad): Returns: Tensor, the gradient tensor after operation. """ + # 检查allreduce_filter是否为True if allreduce_filter: + # 如果是,则根据mean的值对grad进行处理 if mean: + # 如果mean为True,则将degree乘以grad,并将结果赋值给grad + # cast: degree转换为与grad相同的数据类型,以便在后续计算中使用 grad = F.tensor_mul(grad, F.cast(degree, F.dtype(grad))) + # 返回处理后的grad return grad return grad @reduce_opt.register("Tensor", "Bool", "Function", "Function", "Bool", "Tensor", "Bool") def _tensors_allreduce_ps(degree, mean, allgather, allreduce, allreduce_filter, grad, ps_parameter): + # 用于根据给定的ps参数对张量进行allreduce操作 + """ + 函数的主要参数如下: + + degree(整数):表示均值系数。 + mean(布尔值):当mean为True时,将均值系数(degree)应用于梯度。 + allgather(Primitive):用于处理稀疏梯度的通信操作。 + allreduce(Primitive):用于处理梯度的通信操作。 + allreduce_filter(布尔值):当allreduce_filter为True时,对梯度进行allreduce操作。 + grad(张量):表示输入的梯度张量,在操作之前。 + ps_parameter(布尔值):使用参数服务器或不是。 + """ + """ Apply allreduce on gradient. @@ -151,19 +254,37 @@ def _tensors_allreduce_ps(degree, mean, allgather, allreduce, allreduce_filter, Returns: Tensor, the gradient tensor after operation. """ + # 如果使用参数服务器 if ps_parameter: + # 则直接返回grad return grad - + # 检查allreduce_filter是否为True if allreduce_filter: + # 如果是,则对grad进行allreduce操作 grad = allreduce(grad) + # 如果mean为True if mean: + # 则将degree乘以grad,并将结果赋值给grad grad = F.tensor_mul(grad, F.cast(degree, F.dtype(grad))) + # 返回处理后的grad return grad return grad @reduce_opt.register("Tensor", "Bool", "Function", "Function", "Bool", "RowTensor") def _tensors_allreduce_with_sparse(degree, mean, allgather, allreduce, allreduce_filter, grad): + # 这个函数主要用于在处理稀疏特征时对张量进行allgather操作,并在满足条件时对张量的平均值进行处理 + """ + 函数的主要参数如下: + + degree(整数):表示均值系数。 + mean(布尔值):当mean为True时,将均值系数(degree)应用于梯度。 + allgather(Primitive):用于处理稀疏梯度的通信操作。 + allreduce(Primitive):用于处理梯度的通信操作。 + allreduce_filter(布尔值):当allreduce_filter为True时,对梯度进行allgather操作。 + grad(张量):表示输入的梯度张量,在操作之前。 + """ + """ Apply allgather on gradient instead of allreduce for sparse feature. Allgather is a communication operation used for distributed deep learning. @@ -179,17 +300,24 @@ def _tensors_allreduce_with_sparse(degree, mean, allgather, allreduce, allreduce Returns: RowTensor, the gradient after operation. """ + # 检查allreduce_filter是否为True if allreduce_filter: + # 如果是,则对grad和indeces进行allgather操作 indices = allgather(grad.indices) dout = allgather(grad.values) + # 如果mean为True if mean: + # 将dout张量乘以一个常数degree,并将结果的类型转换为与dout相同的数据类型,然后将结果赋值给grad dout = F.tensor_mul(dout, F.cast(degree, F.dtype(dout))) + # 这里RowTensor是一个表示稀疏张量的数据结构,它包含三个部分:indices(张量的非零值的下标)、values(张量的非零值)和dense_shape(张量的形状) grad = RowTensor(indices, dout, grad.dense_shape) + # 返回处理后的grad return grad @reduce_opt.register("Tensor", "Bool", "Function", "Function", "Bool", "RowTensor", "Bool") def _tensors_allreduce_with_sparse_ps(degree, mean, allgather, allreduce, allreduce_filter, grad, ps_parameter): + # 这个函数主要用于根据给定的ps参数在处理稀疏特征时对张量进行allgather操作,并在满足条件时对张量的平均值进行处理 """ Apply allgather on gradient instead of allreduce for sparse feature. Allgather is a communication operation used for distributed deep learning. @@ -206,23 +334,32 @@ def _tensors_allreduce_with_sparse_ps(degree, mean, allgather, allreduce, allred Returns: RowTensor, the gradient after operation. """ + # 如果使用参数服务器 if ps_parameter: + # 则直接返回grad return grad + # 检查allreduce_filter是否为True if allreduce_filter: + # 如果是,则对grad和indeces进行allgather操作 indices = allgather(grad.indices) dout = allgather(grad.values) + # 如果mean为True if mean: + # 将dout张量乘以一个常数degree,并将结果的类型转换为与dout相同的数据类型,然后将结果赋值给grad dout = F.tensor_mul(dout, F.cast(degree, F.dtype(dout))) + # 这里RowTensor是一个表示稀疏张量的数据结构,它包含三个部分:indices(张量的非零值的下标)、values(张量的非零值)和dense_shape(张量的形状) grad = RowTensor(indices, dout, grad.dense_shape) + # 返回处理后的grad return grad - +# 定义了一个名为_get_datatype的多类型函数图(Multitype Func Graph),用于获取输入数据的类型 _get_datatype = C.MultitypeFuncGraph("_get_datatype") @_get_datatype.register("Tensor") def _tensors_get_datatype(grad): + # 用于获取输入张量的数据类型 """ Acquire gradient datatype. @@ -232,11 +369,13 @@ def _tensors_get_datatype(grad): Returns: mstype, the datatype of gradient. """ + # 使用F.dtype(grad)获取grad张量的数据类型,并将结果赋值给mstype并返回 return F.dtype(grad) @_get_datatype.register("RowTensor") def _tensors_get_datatype_with_sparse(grad): + # 用于获取输入稀疏张量的数据类型 """ Acquire gradient datatype. @@ -246,14 +385,16 @@ def _tensors_get_datatype_with_sparse(grad): Returns: mstype, the datatype of gradient. """ + # grad.values表示张量的非零值部分 return F.dtype(grad.values) - +# 定义了一个名为_cast_datatype的多类型函数图(Multitype Func Graph),用于将输入数据的类型转换为指定类型 _cast_datatype = C.MultitypeFuncGraph("_cast_datatype") @_cast_datatype.register("TypeType", "Tensor") def _tensors_cast_datatype(datatype, grad): + # 用于将输入张量的数据类型转换为指定类型。函数的主要参数为一个datatype,表示目标数据类型,以及一个grad张量 """ Cast gradient to datatype. @@ -264,11 +405,13 @@ def _tensors_cast_datatype(datatype, grad): Returns: Tensor, the gradient tensor after operation. """ + # 使用F.cast(grad, datatype)将grad张量转换为指定数据类型,并将结果赋值给grad并返回 return F.cast(grad, datatype) @_cast_datatype.register("TypeType", "RowTensor") def _tensors_cast_datatype_with_sparse(datatype, grad): + # 主要用于在处理稀疏特征时,将张量的非零值部分转换为指定数据类型,以便在后续的运算中正确地处理数据 """ Cast gradient to datatype. @@ -279,11 +422,23 @@ def _tensors_cast_datatype_with_sparse(datatype, grad): Returns: RowTensor, the gradient after operation. """ + # 使用F.cast(grad.values, datatype)将grad张量的非零值部分转换为指定数据类型,并将结果赋值给dout dout = F.cast(grad.values, datatype) + # 创建一个新的稀疏张量,将grad的indices、dout和dense_shape作为参数传入,并将其赋值给new_grad并返回 return RowTensor(grad.indices, dout, grad.dense_shape) class DistributedGradReducer(Cell): + # 用于实现分布式优化器。它通过创建一个Cell类来实现分布式训练,并在训练过程中应用通信和平均操作。这个类通常用于数据并行 + """ + 主要参数如下: + + parameters (list):需要更新的参数列表。 + mean (bool):当mean为True时,将平均系数(度数)应用于梯度。默认值为True。 + degree (int):平均系数。通常等于设备数量。默认值为None。 + fusion_type (int):所有 reduce 融合类型。默认值为1。 + group (str):工作组,通常由create_group创建。默认值为WORLD_COMM_GROUP + """ """ A distributed optimizer. @@ -375,40 +530,64 @@ class DistributedGradReducer(Cell): >>> print(grads) 256.0 """ - + # 初始化函数,用于设置一些参数,如通信模式、梯度合并方式等 def __init__(self, parameters, mean=True, degree=None, fusion_type=1, group=GlobalComm.WORLD_COMM_GROUP): + # 初始化DistributedGradReducer类的成员变量map_为一个C.Map对象 super(DistributedGradReducer, self).__init__(auto_prefix=False) self.map_ = C.Map() + # 如果degree为None if degree is None: + # 则从get_group_size()函数获取设备数量作为degree self.degree = get_group_size() else: + # 否则,检查degree是否为整数且大于0 if not isinstance(degree, int) or degree <= 0: + # 如果不是,则抛出一个错误ValueError raise ValueError("For 'DistributedGradReducer', " "parameter 'degree' in DistributedGradReducer " "should large than 0 and be int, degree: {}.".format(degree)) + # 获取平均系数degree self.degree = degree + # 将degree除以1.0,并将结果赋值给self.degree self.degree = Tensor(1.0 / self.degree, mstype.float32) + # 初始化self.mean为True,表示应用平均系数 self.mean = mean + # 创建一个布尔元组,用于标记参数是否需要进行层间并行 self.allreduce_filter = tuple(x.layerwise_parallel is False for x in parameters) + # 调用context.get_auto_parallel_context("enable_parallel_optimizer")获取自动并行优化器的启用状态 is_parallel_optimizer = context.get_auto_parallel_context("enable_parallel_optimizer") + # 检查是否启用自动并行优化器,如果是,则获取split_indices split_indices = auto_parallel_context().get_all_reduce_fusion_split_indices() + # 检查层间并行和参数进行层间并行优化是否为True if is_parallel_optimizer and split_indices: + # 如果是,则将split_fusion设置为True self.split_fusion = True + # 并调用内部函数_init_allreduce_operators初始化op_list(存储所有AllReduce操作) self.op_list = _init_allreduce_operators(len(parameters), split_indices, group) else: + # 否则,将split_fusion设置为True self.split_fusion = True + # 并调用内部函数_init_allreduce_operators_by_parameters根据参数初始化op_list和param_fusion self.op_list, param_fusion = _init_allreduce_operators_by_parameters(parameters, split_indices, group, fusion_type) + # 如果param_fusion(对参数进行融合优化)为False if not param_fusion: + # 则将split_fusion设置为False,并调用AllReduce对象初始化allreduce self.split_fusion = False self.allreduce = AllReduce('sum', group).add_prim_attr('fusion', fusion_type) + # 调用AllReduce对象初始化allreduce self.allgather = AllGather(group) + # 创建一个lambda函数ps_filter,用于过滤出parameters中的Parameter对象 ps_filter = lambda x: x.is_param_ps + # 将ps_filter函数应用于parameters self.ps_parameters = tuple(ps_filter(x) for x in parameters) + # 检查ps_parameters是否为空,如果是,则将enable_parameter_server设置为False self.enable_parameter_server = any(self.ps_parameters) + # 获取当前模式(mode),并将其赋值给mode self.mode = context.get_context("mode") def construct(self, grads): + # 这段代码在处理梯度时,可能会遇到数据精度 mixed 的问题。为了解决这个问题,梯度需要先转换为 float32,然后再进行 AllReduce 操作。这样,AllReduce 的结果才会更可靠 """ Under certain circumstances, the data precision of grads could be mixed with float16 and float32. Thus, the result of AllReduce is unreliable. To solve the problem, grads must be cast to float32 before AllReduce, @@ -420,23 +599,43 @@ class DistributedGradReducer(Cell): Returns: new_grads (Union[Tensor, tuple[Tensor]]), the gradient tensor or tuple after operation. """ + + """ + 梯度需要先转换为 float32 + """ + # 使用map_方法遍历输入的梯度grads,获取每个梯度的数据类型 datatypes = self.map_(F.partial(_get_datatype), grads) + # 将梯度转换为 float32 grads = self.map_(F.partial(_cast_datatype, mstype.float32), grads) + """ + 然后再进行 AllReduce 操作 + """ + # 检查当前模式是否为PYNATIVE_MODE if self.mode == context.PYNATIVE_MODE: + # 如果是,则使用reduce_opt函数进行 AllReduce 操作 new_grad = self.map_(F.partial(reduce_opt, self.degree, self.mean), self.allreduce_filter, grads) + # 检查split_fusion是否为True elif self.split_fusion: + # 如果是,则检查是否需要进行参数服务器训练 if self.enable_parameter_server: + # 根据情况,选择使用ps_parameters进行 AllReduce 操作 new_grad = self.map_(F.partial(reduce_opt, self.degree, self.mean, self.allgather), self.op_list, self.allreduce_filter, grads, self.ps_parameters) else: + # 否则,使用一般参数进行 AllReduce 操作 new_grad = self.map_(F.partial(reduce_opt, self.degree, self.mean, self.allgather), self.op_list, self.allreduce_filter, grads) else: + # 两者都不是是,则检查是否需要进行参数服务器训练 if self.enable_parameter_server: + # 则使用reduce_opt函数使用ps_parameters与self.allreduce进行 AllReduce 操作 new_grad = self.map_(F.partial(reduce_opt, self.degree, self.mean, self.allgather, self.allreduce), self.allreduce_filter, grads, self.ps_parameters) else: + # 否则,使用reduce_opt函数使用self.allreduce进行 AllReduce 操作 new_grad = self.map_(F.partial(reduce_opt, self.degree, self.mean, self.allgather, self.allreduce), self.allreduce_filter, grads) + # 首先,使用map_方法遍历datatypes和new_grad,然后使用_cast_datatype函数将数据类型转换回原始类型 new_grad = self.map_(F.partial(_cast_datatype), datatypes, new_grad) + # 最后,返回处理后的梯度 return new_grad diff --git a/mindspore/python/mindspore/nn/wrap/loss_scale.py b/mindspore/python/mindspore/nn/wrap/loss_scale.py index 8cbd3be78b0..8173fadfe1d 100644 --- a/mindspore/python/mindspore/nn/wrap/loss_scale.py +++ b/mindspore/python/mindspore/nn/wrap/loss_scale.py @@ -12,49 +12,87 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================ + +# LossScaleCell类,用于实现损失缩放训练 """Loss scale cell for loss scale training.""" +# 首先,我们从mindspore.context模块中导入context和ParallelMode,以便在后续代码中使用 import mindspore.context as context from mindspore.context import ParallelMode +# 然后,我们从mindspore.parallel._utils模块中导入_get_enable_parallel_optimizer,用于获取并行优化器的启用状态 from mindspore.parallel._utils import _get_enable_parallel_optimizer +# 导入TrainOneStepCell用于实现一次训练步骤 from .cell_wrapper import TrainOneStepCell +# 导入其父类神经网络基本单位Cell from ..cell import Cell +# 并从mindspore.common模块中导入Tensor、RowTensor和Parameter from ...common import Tensor, RowTensor from ...common.parameter import Parameter +# 我们还从mindspore.ops模块中导入了一些操作,如F(功能性操作)、C(组合操作)和P(操作) from ...ops import functional as F from ...ops import composite as C from ...ops import operations as P +# 最后,我们从mindspore.common模块中导入dtype,用于处理数据类型 from ...common import dtype as mstype +# 定义了一个名为_grad_scale的多类型功能图,用于计算损失缩放 _grad_scale = C.MultitypeFuncGraph("grad_scale") +# 然后使用P.Reciprocal()创建了一个反向传播 reciprocal 操作 reciprocal = P.Reciprocal() - +""" +定义了两个多类型函数图,用于计算损失缩放和检查梯度溢出 +""" @_grad_scale.register("Tensor", "Tensor") def tensor_grad_scale(scale, grad): + # 使用F.cast将逆向传播的 Reciprocal 操作的结果转换为与输入梯度相同的数据类型,然后将结果乘以损失缩放因子 return grad * F.cast(reciprocal(scale), F.dtype(grad)) @_grad_scale.register("Tensor", "RowTensor") def tensor_grad_scale_row_tensor(scale, grad): + # 首先将输入的RowTensor的值乘以损失缩放因子,然后将结果转换为RowTensor类型,最后返回结果 return RowTensor(grad.indices, grad.values * F.cast(reciprocal(scale), F.dtype(grad.values)), grad.dense_shape) +# 定义了一个名为_grad_overflow的多类型功能图,用于检查梯度溢出 _grad_overflow = C.MultitypeFuncGraph("_grad_overflow") +# 使用P.FloatStatus()创建了一个检查梯度溢出的操作 grad_overflow = P.FloatStatus() +""" +定义了两个多类型函数图,用于处理Tensor和RowTensor类型的梯度溢出检查 +""" @_grad_overflow.register("Tensor") def _tensor_grad_overflow(grad): + # 使用grad_overflow操作检查输入Tensor的梯度溢出,然后返回结果 return grad_overflow(grad) @_grad_overflow.register("RowTensor") def _tensor_grad_overflow_row_tensor(grad): + # 首先使用grad_overflow操作检查输入RowTensor的值是否溢出,然后返回结果 return grad_overflow(grad.values) class DynamicLossScaleUpdateCell(Cell): + # 用于实现动态损失缩放更新。在损失缩放训练中,初始损失缩放值将被设置为loss_scale_value。在每次训练步骤中, + # 当发生溢出时,损失缩放值将减少loss_scale/scale_factor,并在没有溢出连续scale_window步后增加loss_scale * scale_factor。 + # et_update_cell方法由mindspore.DynamicLossScaleManager类返回,它将在训练过程中调用此类,以便在训练过程中更新损失缩放。 + """ + 参数: + + loss_scale_value (float):初始损失缩放值。 + scale_factor (int):增加和减少的系数。 + scale_window (int):没有溢出连续scale_window步后增加损失缩放值的步数。 + + 输入: + + loss_scale (Tensor):训练过程中损失缩放值,形状为:()。 + overflow (bool):是否发生溢出。 + + """ r""" Dynamic Loss scale update cell. @@ -113,17 +151,24 @@ class DynamicLossScaleUpdateCell(Cell): loss_scale_value, scale_factor, scale_window): + # 在初始化方法中,我们继承了Cell类的__init__方法 super(DynamicLossScaleUpdateCell, self).__init__() + # 并定义了三个参数:scale_window、scale_factor和loss_scale_value + # scale_window是一个张量,表示没有溢出连续scale_window步后增加损失缩放值的步数 self.scale_window = Tensor(scale_window, dtype=mstype.int32) + # scale_factor是一个张量,表示增加和减少的系数 self.scale_factor = Tensor(scale_factor, dtype=mstype.float32) + # loss_scale_value是损失缩放值 self.loss_scale_value = loss_scale_value + # 还定义了四个参数:cur_iter、last_overflow_iter、select、max和minimum_loss_scale self.cur_iter = Parameter(Tensor(1, dtype=mstype.int32), name="current_iterator_step") self.last_overflow_iter = Parameter(Tensor(0, dtype=mstype.int32), name="last_overflow_iterator_step") self.select = P.Select() self.max = P.Maximum() self.minimum_loss_scale = Tensor(1.0, dtype=mstype.float32) + # 我们定义了一些操作,如reciprocal、less_equal、logic_and、logic_not和logic_or等,用于实现动态损失缩放更新 self.reciprocal = P.Reciprocal() self.less_equal = P.LessEqual() self.logic_and = P.LogicalAnd() @@ -132,6 +177,7 @@ class DynamicLossScaleUpdateCell(Cell): self.const_true = Tensor(True, dtype=mstype.bool_) def get_loss_scale(self): + # 在get_loss_scale方法中,我们返回损失缩放值 """ Get Loss Scale value. @@ -141,24 +187,47 @@ class DynamicLossScaleUpdateCell(Cell): return self.loss_scale_value def construct(self, loss_scale, overflow): + # 根据overflow参数判断是否发生溢出 overflow_cond = overflow + # 并计算损失缩放值 loss_scale_on_overflow = self.select(overflow_cond, self.max(loss_scale * self.reciprocal(self.scale_factor), self.minimum_loss_scale), loss_scale) + # 当没有溢出时,我们保持损失缩放值不变。然后我们根据scale_window和cur_iter参数判断是否需要增加cur_iter should_inc = self.less_equal(self.scale_window, self.cur_iter - self.last_overflow_iter) + # 并更新last_overflow_iter last_iter_cond = self.logic_or(overflow_cond, should_inc) + # 当发生溢出时,我们使用select操作选择最大值和最小值中的较大值作为损失缩放值 last_overflow_iter = self.select(last_iter_cond, self.cur_iter, self.last_overflow_iter) last_iter = F.assign(self.last_overflow_iter, last_overflow_iter) + # 根据should_inc和overflow_cond的值判断是否需要更新损失缩放值 update_scale_cond = self.logic_and(should_inc, self.logic_not(overflow_cond)) scale_mul_res = loss_scale_on_overflow * self.scale_factor + # 如果需要更新,我们使用select操作选择scale_mul_res和loss_scale_on_overflow中的较大值作为损失缩放值 scaled_loss_scale = self.select(update_scale_cond, scale_mul_res, loss_scale_on_overflow) F.assign(loss_scale, scaled_loss_scale) + # 将cur_iter的值加1,并使用F.depend操作将计算结果依赖到last_iter上 inc_cur_iter = self.cur_iter + 1 inc_cur_iter = F.depend(inc_cur_iter, last_iter) F.assign(self.cur_iter, inc_cur_iter) + # 最后,我们返回overflow return overflow class FixedLossScaleUpdateCell(Cell): + """ + FixedLossScaleUpdateCell的类,用于实现固定损失缩放更新。在get_update_cell方法中,由mindspore.FixedLossScaleManager类返回,它将在训练过程中调用此类。 + + 参数: + + loss_scale_value (float):初始损失缩放值。 + 输入: + + loss_scale (Tensor):训练过程中损失缩放值,形状为:(),在FixedLossScaleUpdateCell类中将被忽略。 + overflow (bool):是否发生溢出。 + 输出: + + bool,输入overflow。 + """ """ Update cell with fixed loss scaling value. @@ -208,9 +277,11 @@ class FixedLossScaleUpdateCell(Cell): def __init__(self, loss_scale_value): super(FixedLossScaleUpdateCell, self).__init__() + # 定义了一个参数loss_scale_value表示损失缩放值 self.loss_scale_value = loss_scale_value def get_loss_scale(self): + # 在get_loss_scale方法中,我们返回损失缩放值 """ Get Loss Scale value. @@ -220,10 +291,13 @@ class FixedLossScaleUpdateCell(Cell): return self.loss_scale_value def construct(self, _, overflow): + # 在construct方法中,我们直接返回overflow return overflow class TrainOneStepWithLossScaleCell(TrainOneStepCell): + # 用于实现带损失缩放的网络训练步骤。它继承了TrainOneStepCell类,并接受一个网络、一个优化器和一个损失缩放值。 + # 损失缩放值可以是是一个Tensor类型,也可以是一个Cell实例。在训练过程中,这个类会根据损失缩放值更新损失缩放值,并在需要时进行溢出处理 r""" Network training with loss scaling. @@ -302,66 +376,102 @@ class TrainOneStepWithLossScaleCell(TrainOneStepCell): >>> output = train_network(inputs, label) """ def __init__(self, network, optimizer, scale_sense): + # 它继承了TrainOneStepCell类,并接受一个网络、一个优化器和一个损失缩放值 + # 损失缩放值可以是是一个Tensor类型,也可以是一个Cell实例 super(TrainOneStepWithLossScaleCell, self).__init__(network, optimizer, sens=None) + # 然后初始化一些变量,如hyper_map、base、reduce_sum、less_equal和allreduce + # 用于对输入进行映射的函数 self.hyper_map = C.HyperMap() + # base是一个常量值1 self.base = Tensor(1, mstype.float32) + # reduce_sum是一个计算和的函数 self.reduce_sum = P.ReduceSum(keep_dims=False) + # less_equal是一个比较函数 self.less_equal = P.LessEqual() + # allreduce是一个进行AllReduce操作的函数 self.allreduce = P.AllReduce() + # 首先判断parallel_mode是否为STAND_ALONE,如果不是,则表示正在使用分布式训练,将is_distributed设置为True self.is_distributed = (self.parallel_mode != ParallelMode.STAND_ALONE) + # 然后,判断device_target是否为GPU,如果是,则表示正在使用GPU训练,将gpu_target设置为True self.gpu_target = (context.get_context("device_target") == "GPU") + # 最后,将loss_scaling_manager设置为None self.loss_scaling_manager = None + # 我们判断损失缩放值是否为一个Cell实例 if isinstance(scale_sense, Cell): + # 用于存储损失缩放的管理器 self.loss_scaling_manager = scale_sense + # 如果是,则将其转换为Parameter类型并命名为scale_sense self.scale_sense = Parameter(Tensor(scale_sense.get_loss_scale(), dtype=mstype.float32), name="scale_sense") + # 判断损失缩放值是否为一个Tensor类型 elif isinstance(scale_sense, Tensor): + # 如果是,则检查其形状是否为(1,)或(1,) if scale_sense.shape == (1,) or scale_sense.shape == (): + # 如果是,则将其转换为Parameter类型并命名为scale_sense self.scale_sense = Parameter(scale_sense, name='scale_sense') else: + # 否则,抛出一个TypeError异常,表示参数形状不正确 raise ValueError("For 'TrainOneStepWithLossScaleCell', " "the shape of 'scale_sense' must be (1,) or (), but got {}." .format(scale_sense.shape)) else: + # 否则,抛出一个TypeError异常,表示损失缩放值的类型不正确 raise TypeError("For 'TrainOneStepWithLossScaleCell', " "the 'scale_sense' must be Cell or Tensor, but got 'scale_sense' type: {}." .format(type(scale_sense))) + # 用于构建训练过程。它接收输入数据,计算损失,然后根据损失缩放值调整损失 def construct(self, *inputs): + # 获取网络权重weights weights = self.weights + # 计算损失loss loss = self.network(*inputs) + # 获取损失缩放值scaling_sens scaling_sens = self.scale_sense + # 检查溢出状态status和scaling_sens status, scaling_sens = self.start_overflow_check(loss, scaling_sens) + # 如果发生溢出,对损失缩放值进行填充 scaling_sens_filled = C.ones_like(loss) * F.cast(scaling_sens, F.dtype(loss)) + # 计算梯度grads grads = self.grad(self.network, weights)(*inputs, scaling_sens_filled) + # 对梯度进行缩放 grads = self.hyper_map(F.partial(_grad_scale, scaling_sens), grads) # apply grad reducer on grads + # 应用梯度 reducer grads = self.grad_reducer(grads) # get the overflow buffer + # 获取溢出缓冲区并存入cond中,再根据溢出情况计算loss_scale cond = self.get_overflow_status(status, grads) overflow = self.process_loss_scale(cond) # if there is no overflow, do optimize if not overflow: + # 如果未发生溢出,使用优化器更新梯度 loss = F.depend(loss, self.optimizer(grads)) + # 返回损失loss、条件cond和损失缩放值scaling_sens return loss, cond, scaling_sens def set_sense_scale(self, sens): + # 用于设置损失缩放值。如果用户已经设置了scale_sense,可以通过调用这个方法重新赋值。参数sens的形状和类型应与原始scale_sense相同 """ If the user has set the `scale_sense` of Tensor type, he can call this function to reassign the value. Args: sens(Tensor): The new sense whose shape and type are the same with original `scale_sense`. """ + # 判断scale_sense是否已经设置,并且sens的类型是否为Tensor if self.scale_sense and isinstance(sens, Tensor): + # 如果是,则将sens的值设置为scale_sense self.scale_sense.set_data(sens) else: + # 否则,抛出一个类型错误异常,表示sens的类型应该为Tensor,但实际上为{} raise TypeError("For 'TrainOneStepWithLossScaleCell', " "the type of 'sens' must be Tensor, but got {}".format(type(sens))) def start_overflow_check(self, pre_cond, compute_input): + # 用于开始浮点溢出检测。它接收两个参数pre_cond和compute_input,用于确保在特定时间清除溢出检测状态 """ Start floating-point overflow detection. Create and clear the overflow detection state. @@ -385,17 +495,24 @@ class TrainOneStepWithLossScaleCell(TrainOneStepCell): The second value is the same as the input of `compute_input`, but contains some information about the execution order. """ + # 包含溢出标志的向量status status = False + # 判断gpu_target是否为False if not self.gpu_target: # init overflow buffer + # 如果是,则使用NPUAllocFloatStatus()方法创建一个溢出缓冲区 status = P.NPUAllocFloatStatus()() + # 并将其与pre_cond进行依赖操作,以确保在执行pre_cond之后清除溢出缓冲区 status = F.depend(status, pre_cond) # clear overflow buffer + # 对status进行NPUClearFloat操作,清除溢出缓冲区 clear_status = P.NPUClearFloatStatus()(status) + # 进行依赖操作,确保在清除溢出缓冲区之后执行compute_input compute_input = F.depend(compute_input, clear_status) return status, compute_input def get_overflow_status(self, status, compute_output): + # 用于获取浮点溢出状态。它接收一个status对象和一个计算输出作为参数,用于在溢出检测过程中获取溢出结果。在TrainOneStepWithLossScaleCell类中,这个方法被用于检查计算输出是否发生溢出 """ Get floating-point overflow status. @@ -411,27 +528,42 @@ class TrainOneStepWithLossScaleCell(TrainOneStepCell): Returns: bool, whether the overflow occurs or not. """ + # 判断gpu_target是否为False if not self.gpu_target: + # 如果是,则将status与compute_output进行依赖操作,确保在执行compute_output之前获取到溢出status status = F.depend(status, compute_output) + # 获取溢出状态 get_status = P.NPUGetFloatStatus()(status) + # 并将其与status进行依赖操作 status = F.depend(status, get_status) # sum overflow buffer elements, 0:not overflow , >0:overflow + # 最后,使用self.reduce_sum()方法对溢出缓冲区元素进行求和操作,得到一个标量flag_sum,表示溢出发生与否的值 flag_sum = self.reduce_sum(status, (0,)) + # 如果gpu_target为True else: + # 则使用self.hyper_map()对compute_output进行处理,得到一个包含溢出标志的向量flag_sum flag_sum = self.hyper_map(F.partial(_grad_overflow), compute_output) + # 接着,使用P.AddN()方法将flag_sum进行求和操作,得到一个标量flag_sum flag_sum = P.AddN()(flag_sum) # convert flag_sum to scalar + # 最后,使用P.Reshape()方法将flag_sum转换为一个标量 flag_sum = P.Reshape()(flag_sum, (())) + # 首先判断是否是分布式的 if self.is_distributed: # sum overflow flag over devices + # 如果是,则使用self.allreduce()方法对flag_sum进行分布式求和操作,得到一个分布式溢出标志flag_reduce flag_reduce = self.allreduce(flag_sum) + # 接着,使用self.less_equal()方法比较self.base与flag_reduce,得到一个布尔值overflow,表示溢出是否发生 overflow = self.less_equal(self.base, flag_reduce) else: + # 如果不是分布式的,则直接使用self.less_equal()方法比较self.base与flag_sum,得到一个布尔值overflow,表示溢出是否发生 overflow = self.less_equal(self.base, flag_sum) + # 最后,返回得出的溢出结果 return overflow def process_loss_scale(self, overflow): + # 根据溢出情况计算损失缩放值。用户基于这个类编写的训练网络可以调用这个接口来处理溢出 """ Calculate loss scale according to the overflow. @@ -443,35 +575,51 @@ class TrainOneStepWithLossScaleCell(TrainOneStepCell): Returns: bool, the input overflow value. """ + # 参数overflow表示溢出是否发生。 + # 如果loss_scaling_manager不为None if self.loss_scaling_manager is not None: + # 则调用loss_scaling_manager中的相应方法计算损失缩放值,否则直接返回overflow return self.loss_scaling_manager(self.scale_sense, overflow) return overflow - +# MultitypeFuncGraph是一个用于创建多类型函数图的接口 grad_scale = C.MultitypeFuncGraph("grad_scale") shard_grad_scale = C.MultitypeFuncGraph("shard_grad_scale") +# 而Reciprocal操作用于计算倒数 reciprocal = P.Reciprocal() @grad_scale.register("Tensor", "Tensor", "Tensor") def tensor_grad_scale_pipeline(scale, grad, accu_grad): + # 用于根据损失缩放值scale对梯度grad进行处理 + # 首先,将accu_grad与grad进行依赖操作 accu_grad = F.depend(accu_grad, grad) + # 然后计算新的梯度new_grad,方法是将accu_grad乘以reciprocal(scale) new_grad = accu_grad * reciprocal(scale) + # 接着,将accu_grad与new_grad进行依赖操作 accu_grad = F.depend(accu_grad, new_grad) + # 然后计算零向量zeros,方法是将accu_grad乘以0.0 zeros = F.tensor_mul(accu_grad, 0.0) + # 最后,将new_grad与zeros进行依赖操作,从而实现梯度的缩放 new_grad = F.depend(new_grad, F.assign(accu_grad, zeros)) return new_grad @shard_grad_scale.register("Tensor", "Tensor", "Tensor") +# 用于对梯度进行缩放处理 def tensor_shard_grad_scale_pipeline(scale, grad, accu_grad): + # 首先将grad乘以reciprocal(scale) new_grad = grad * reciprocal(scale) + # 然后将结果与accu_grad进行依赖操作 accu_grad = F.depend(accu_grad, new_grad) + # 接着将new_grad与zeros_like(accu_grad)进行依赖操作 new_grad = F.depend(new_grad, F.assign(accu_grad, F.zeros_like(accu_grad))) + # 最后返回new_grad return new_grad class _TrainPipelineWithLossScaleCell(TrainOneStepCell): + # 继承自TrainOneStepCell。这个类的作用是将训练网络network后面添加一个优化器optimizer,并在构造函数中调用scale_sense """ Append an optimizer to the training network after that the construct function can be called to create the backward graph. @@ -483,69 +631,121 @@ class _TrainPipelineWithLossScaleCell(TrainOneStepCell): """ def __init__(self, network, optimizer, scale_sense): super(_TrainPipelineWithLossScaleCell, self).__init__(network, optimizer, sens=None) + # 初始化network、optimizer和scale_sense等参数 self.network = network + # 将network的defer_inline标志设置为True self.network.add_flags(defer_inline=True) self.weights = optimizer.parameters + # 创建一个名为accu_grads的变量,用于存储累加的梯度 self.accu_grads = self.weights.clone(prefix="accu_grads", init="zeros") + # 初始化optimizer self.optimizer = optimizer + # 创建一个GradOperation对象grad,用于计算梯度 self.grad = C.GradOperation(get_by_list=True, sens_param=True) + # 用于 reduce_sum操作 self.grad_reducer = F.identity + # 设置degree为1 self.degree = 1 + # 创建一个P.Cast对象cast self.cast = P.Cast() + # 三个PaddlePaddle内置对象,分别用于分配浮点数状态、获取浮点数状态和清除浮点数状态。这些对象通常用于NPU(神经网络处理单元)上的浮点数计算 self.alloc_status = P.NPUAllocFloatStatus() self.get_status = P.NPUGetFloatStatus() self.clear_before_grad = P.NPUClearFloatStatus() + # 用于计算 reduce_sum操作,如果设置为True,则输出将保留原始维度。在本例中,由于是求和操作,因此设置为False self.reduce_sum = P.ReduceSum(keep_dims=False) + # 判断parallel_mode是否为自动并行模式 if self.parallel_mode not in [ParallelMode.SEMI_AUTO_PARALLEL, ParallelMode.AUTO_PARALLEL]: + # 如果不是,则抛出一个错误 raise ValueError(f"ParallelMode should be one of " f"[ParallelMode.SEMI_AUTO_PARALLEL, ParallelMode.AUTO_PARALLEL], but found " f"{self.parallel_mode}.") + # 接着,它创建一个AllReduce对象self.allreduce,用于实现allreduce操作 self.allreduce = P.AllReduce() + # 然后,它创建一个Tensor对象self.base,用于存储1 self.base = Tensor(1, mstype.float32) + # 接着,它创建一个P.LessEqual对象self.less_equal,用于比较两个张量 self.less_equal = P.LessEqual() + # 最后,它创建一个C.HyperMap对象self.hyper_map,用于执行一些操作 self.hyper_map = C.HyperMap() + # 创建一个P.Reshape对象self.reshape,用于调整张量的形状 self.reshape = P.Reshape() + # 初始化存储损失缩放的管理器为空 self.loss_scaling_manager = None + # 接着,判断scale_sense是否为Cell或Tensor类型 if isinstance(scale_sense, Cell): + # 如果是,则创建一个Parameter对象self.scale_sense,用于存储损失缩放因子 self.loss_scaling_manager = scale_sense self.scale_sense = Parameter(Tensor(scale_sense.get_loss_scale(), dtype=mstype.float32), name="scale_sense") + # 如果是Tensor类型 elif isinstance(scale_sense, Tensor): + # 如果scale_sense的形状为(1,)或(() if scale_sense.shape == (1,) or scale_sense.shape == (): + # 则将其作为参数存储 self.scale_sense = Parameter(scale_sense, name='scale_sense') else: + # 如果不是,则抛出一个错误ValueError,提示数值错误 raise ValueError("The shape of 'scale_sense' must be (1,) or (), but got {}" .format(scale_sense.shape)) else: + # 如果不是,则抛出一个类型错误 raise TypeError("The 'scale_sense' must be Cell or Tensor, but got {}".format(type(scale_sense))) + # 最后,它判断opt_shard是否为True,如果是,则启用并行优化器 self.opt_shard = _get_enable_parallel_optimizer() - def construct(self, *inputs): +# 主要用于计算损失值loss,并处理损失缩放因子scaling_sens +def construct(self, *inputs): + # 调用self.network函数计算网络输出,并将结果赋值给loss loss = self.network(*inputs) + # 然后,它获取缩放敏感度的值,并将其填充为与loss相同形状的张量 scaling_sens = self.scale_sense + # 获取分配状态 init = self.alloc_status() + # 将缩放敏感度填充到与损失相同形状 scaling_sens_filled = C.ones_like(loss) * F.cast(scaling_sens, F.dtype(loss)) + # 计算损失的梯度 grads = self.grad(self.network, self.weights)(*inputs, scaling_sens_filled) + # 它将init和grads相依赖,生成一个新的init张量 init = F.depend(init, grads) + # 获取状态 get_status = self.get_status(init) + # 将init和get_status相依赖,生成一个新的init张量 init = F.depend(init, get_status) + # 调用self.reduce_sum函数计算init张量的总和,并将结果赋值给flag_sum flag_sum = self.reduce_sum(init, (0,)) + # 最后,它调用self.clear_before_grad函数清除init张量的浮点数状态,并将结果赋值给loss loss = F.depend(loss, self.clear_before_grad(init)) + # 是否使用shard if self.opt_shard: + # 如果是,则调用self.grad_reducer函数计算梯度 grads = self.grad_reducer(grads) + # 并使用self.hyper_map执行一个部分函数shard_grad_scale,将损失缩放因子scaling_sens乘以self.degree,然后将结果与accu_grads相乘 grads = self.hyper_map(F.partial(shard_grad_scale, scaling_sens * self.degree), grads, self.accu_grads) else: + # 如果不是,则调用self.grad_reducer函数计算累加梯度accu_grads accu_grads = self.grad_reducer(self.accu_grads) + # 并使用self.hyper_map执行一个部分函数grad_scale,将损失缩放因子scaling_sens乘以self.degree,然后将结果与grads相乘 grads = self.hyper_map(F.partial(grad_scale, scaling_sens * self.degree), grads, accu_grads) # sum overflow flag over devices + # 计算flag_sum张量的全局和 flag_reduce = self.allreduce(flag_sum) + # 计算溢出条件cond,即flag_reduce是否小于等于self.base cond = self.less_equal(self.base, flag_reduce) + # 计算溢出标志 overflow = cond + # 是否使用损失缩放管理器 if self.loss_scaling_manager is not None: + # 如果不是,则调用函数计算溢出标志 overflow = self.loss_scaling_manager(self.scale_sense, cond) + # 最后,它判断overflow是否为True if overflow: + # 如果是,则将succ设置为False succ = False else: + # 否则,调用self.optimizer函数计算优化 succ = self.optimizer(grads) + # 返回损失、溢出标志和缩放敏感度 ret = (loss, overflow, scaling_sens) + # 使用F.depend函数依赖succ return F.depend(ret, succ)