Cleanlab工具零基础

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

在机器学习和计算机视觉的任务中,数据的质量对模型性能的影响至关重要。标签错误、数据不一致性以及数据集中的标注问题可能导致模型性能下降,甚至无法正常工作。为了提高数据质量并确保模型的可靠性,需要对数据进行深入的分析和清理。Cleanlab 是一种工具,专注于自动化数据质量检查和修正,以便提升数据集的质量,从而提高模型的表现。


提示:以下是本篇文章正文内容,下面案例可供参考

一、Cleanlab是什么?

Cleanlab是什么
Cleanlab 是一个开源库,旨在帮助数据科学家和机器学习工程师识别和修复数据集中存在的标签问题。它提供了多种功能来检测和处理数据中的错误标签和标注问题。Cleanlab 的主要特点包括:
标签问题检测: Cleanlab 提供了检测标签错误的工具,帮助识别数据集中可能存在的标签问题,比如标签噪声或错误标注。这些问题可能会导致模型训练时出现偏差。
标签质量评分: Cleanlab 通过计算标签质量分数,评估每个标签的可靠性。分数较低的标签可能需要进一步的检查和修正,以提高数据集的整体质量。
总之,Cleanlab 是一个强大的工具,旨在通过识别和修复数据集中的标签问题,帮助用户提高数据质量,最终提升模型的准确性和鲁棒性。

二、官方代码使用

1.安装依赖

代码如下(示例):

pip install matplotlib cleanlab

2.下载示例数据和模型预测

代码如下(示例):

wget -nc 'https://cleanlab-public.s3.amazonaws.com/ObjectDetectionBenchmarking/tutorial_obj/predictions.pkl'
wget -nc 'https://cleanlab-public.s3.amazonaws.com/ObjectDetectionBenchmarking/tutorial_obj/labels.pkl'
wget -nc 'https://cleanlab-public.s3.amazonaws.com/ObjectDetectionBenchmarking/tutorial_obj/example_images.zip' && unzip -q -o example_images.zip

3.导入必要的库

代码如下(示例):

import pickle
import matplotlib.pyplot as plt
from PIL import Image
from cleanlab.object_detection.filter import find_label_issues
from cleanlab.object_detection.rank import get_label_quality_scores
from cleanlab.object_detection.summary import visualize

4.部分代码

代码如下(示例):

# 定义图像路径
IMAGE_PATH = '/home/zhushanbing/temp/example_images/'

# 加载标签和预测
predictions = pickle.load(open("/home/zhushanbing/temp/predictions.pkl", "rb"))
labels = pickle.load(open("/home/zhushanbing/temp/labels.pkl", "rb"))

print("labels数:",len(labels))
print("predictions数:",len(predictions))

# 查找标签问题的图像索引
label_issue_idx = find_label_issues(labels, predictions, return_indices_ranked_by_score=True)

# 显示具有最严重标签问题的图像索引
num_examples_to_show = 5
print("具有标签问题的图像索引:", label_issue_idx[:num_examples_to_show])

# 获取每个图像的标签质量分数
scores = get_label_quality_scores(labels, predictions)
print("标签质量分数:", scores[:num_examples_to_show])

# 根据分数阈值获取标签问题图像索引
from cleanlab.object_detection.rank import issues_from_scores
issue_idx = issues_from_scores(scores, threshold=0.5)
print("根据分数阈值获取的标签问题图像索引:", issue_idx[:num_examples_to_show])
print("这些问题图像的分数:", scores[issue_idx][:num_examples_to_show])

# 5. 可视化标签问题
def visualize_issue(index):
    image_path = IMAGE_PATH + labels[index]['seg_map']
    label = labels[index]
    prediction = predictions[index]
    score = scores[index]

    print(f"图像路径:{image_path} | 索引:{index} | 标签质量分数:{score} | 是否存在问题:是")
    visualize(image_path, label=label, prediction=prediction, overlay=False)

# 示例可视化标签问题图像
visualize_issue(issue_idx[0])  # 更改为其他图像索引以查看不同图像
visualize_issue(issue_idx[1]) 

三、使用yolo目标检测数据(xml)

1.process_labels

处理标签文件并提取边界框、标签、忽略边界框和掩码。

def process_labels(label_paths, label_map):
    """
    处理标签文件并提取边界框、标签、忽略边界框和掩码。
    
    参数:
        label_paths (list): 标签文件路径的列表。
        label_map (dict): 标签名称到整数标签的映射。
        
    返回:
        list: 包含每个标签数据的字典列表。
    """
    labels = []

    for label_file in label_paths:
        # 解析标签文件
        label_tree = ET.parse(label_file)
        label_root = label_tree.getroot()

        # 提取真实标签的边界框、标签和掩码
        true_boxes = []
        true_labels = []
        masks = []
        bboxes_ignore = []

        for obj in label_root.findall('object'):
            # 提取边界框
            box = obj.find('bndbox')
            true_box = (float(box.find('xmin').text), float(box.find('ymin').text),
                        float(box.find('xmax').text), float(box.find('ymax').text))
            true_boxes.append(true_box)
            
            # 提取标签
            label_name = obj.find('name').text
            true_labels.append(label_map.get(label_name, -1))  # 使用 -1 表示未知标签
            
            # 提取掩码(如果存在)
            mask = obj.find('mask')
            if mask is not None:
                mask_points = [float(point.text) for point in mask.findall('point')]
                masks.append(mask_points)
            else:
                masks.append([])  # 没有掩码则添加空列表

            # 提取忽略边界框(如果存在)
            bndbox_ignore = obj.find('bndbox_ignore')
            if bndbox_ignore is not None:
                ignore_box = (float(bndbox_ignore.find('xmin').text), float(bndbox_ignore.find('ymin').text),
                              float(bndbox_ignore.find('xmax').text), float(bndbox_ignore.find('ymax').text))
                bboxes_ignore.append(ignore_box)
            else:
                bboxes_ignore.append([])  # 没有忽略边界框则添加空列表

        # 转换为 numpy 数组
        true_boxes = np.array(true_boxes, dtype=np.float32)
        true_labels = np.array(true_labels, dtype=np.int32)
        bboxes_ignore = np.array(bboxes_ignore, dtype=np.float32)
        masks = np.array(masks, dtype=np.float32)

        # 创建 labels_data 字典形式
        labels_data = {
            'bboxes': true_boxes,  # 使用 numpy 数组
            'labels': true_labels, # 使用 numpy 数组
            'bboxes_ignore': bboxes_ignore, # 使用 numpy 数组
            'masks': masks, # 使用 numpy 数组
            'seg_map': os.path.basename(label_file) # 存储分割图的文件名
        }

        # 将标签数据追加到列表中
        labels.append(labels_data)

    return labels

2.process_predictions

处理预测文件并提取边界框和置信度,将每个类别的预测数据生成一个数组,并合并成列表。

def process_predictions(label_paths, prediction_dir, label_map):
    """
    处理预测文件并提取边界框和置信度,将每个类别的预测数据生成一个数组,并合并成列表。
    
    参数:
        label_paths (list): 标签文件路径的列表。
        prediction_dir (dict): 预测文件路径字典,键为标签文件名,值为预测文件路径。
        label_map (dict): 标签名称到类别编号的映射。
        
    返回:
        list: 每个标签文件生成的类别预测数组的列表,每个类别生成一个 (N, 5) 的数组。
    """
    all_predictions = []

    for label_file in label_paths:
        # 根据标签文件获取相应的预测文件
        pred_file_path = prediction_dir.get(os.path.basename(label_file))

        # 预创建每个类别的空预测数组,初始为 (0, 5)
        predictions_per_class = {label: np.empty((0, 5), dtype=np.float32) for label in label_map.values()}

        if pred_file_path and os.path.exists(pred_file_path):
            # 解析预测文件
            pred_tree = ET.parse(pred_file_path)
            pred_root = pred_tree.getroot()

            # 提取预测边界框和置信度
            for obj in pred_root.findall('object'):
                label_name = obj.find('name').text
                if label_name in label_map:
                    # 取得该类别对应的标签编号
                    label_idx = label_map[label_name]

                    box = obj.find('bndbox')
                    confidence = float(obj.find('confidence').text)
                    pred_box = (float(box.find('xmin').text), float(box.find('ymin').text),
                                float(box.find('xmax').text), float(box.find('ymax').text))

                    # 将预测框和置信度合并为一个列表,追加到对应类别的数组中
                    predictions_per_class[label_idx] = np.vstack(
                        [predictions_per_class[label_idx], np.array([*pred_box, confidence], dtype=np.float32)]
                    )
        
        # 按类别顺序生成最终的预测结果列表
        predictions_data = [predictions_per_class[label] for label in range(len(label_map))]
        all_predictions.append(predictions_data)

    return all_predictions

3.整体框架

base_dir = '/home/xxx/temp/'

def read_paths(file_path):
    """从文件中读取路径列表"""
    with open(file_path, 'r') as f:
        paths = [line.strip() for line in f]
    return paths

# 定义路径
prediction_paths = read_paths(os.path.join(base_dir, 'pred_xml_path.txt'))
label_paths = read_paths(os.path.join(base_dir, 'xml_path.txt'))

# 从文件中加载预测和标签
prediction_dir = {os.path.basename(p): p for p in prediction_paths}
label_map = {'thyroid_nodule': 0}  # 标签名称到整数标签的映射

# 处理标签和预测
labels = process_labels(label_paths, label_map)
predictions = process_predictions(label_paths, prediction_dir, label_map)

# print("labels数:",len(labels))
# print("predictions数:",len(predictions))

# 查找标签问题的图像索引
label_issue_idx = find_label_issues(labels, predictions, return_indices_ranked_by_score=True)
num_examples_to_show = 5
print("具有标签问题的图像索引:", label_issue_idx[:num_examples_to_show])

# 获取每个图像的标签质量分数
scores = get_label_quality_scores(labels, predictions)
print("标签质量分数:", scores[:num_examples_to_show])

# 根据分数阈值获取标签问题图像索引
issue_idx = issues_from_scores(scores, threshold=0.5)
print("根据分数阈值获取的标签问题图像索引:", issue_idx[:num_examples_to_show])
print("这些问题图像的分数:", scores[issue_idx][:num_examples_to_show])

def save_low_score_xml_paths(xml_paths, scores, threshold, output_file):
    """
    保存分数低于阈值的标签文件名称(不含扩展名)到一个 TXT 文件中。

    参数:
        xml_paths (list): 原始的 XML 文件路径列表。
        scores (list): 每个图像的标签质量分数列表。
        threshold (float): 标签质量分数的阈值,小于该值的图像将被保存。
        output_file (str): 保存路径的文件名。
    """
    low_score_filenames = []
    
    for i, score in enumerate(scores):
        if score < threshold:
            # 获取文件名并去除 .xml 扩展名
            filename = os.path.splitext(os.path.basename(xml_paths[i]))[0]
            low_score_filenames.append(filename)
    
    with open(output_file, 'w') as f:
        for filename in low_score_filenames:
            f.write(filename + '\n')
    
    print(f"已将分数低于 {threshold}{len(low_score_filenames)} 个图像名称保存到 {output_file}。")

# 设置保存路径和分数阈值
output_file = os.path.join(base_dir, '/home/xxx/temp/low_score_xml_paths.txt')
score_threshold = 0.5

# 保存分数低的图像的 XML 文件路径
save_low_score_xml_paths(label_paths, scores, score_threshold, output_file)

四、遇到的问题

1.标签数据至少包含两个类

(base) jovyan@79dcba6e8eca:/home/xxx/temp$ python three.py 
/opt/conda/lib/python3.11/site-packages/cleanlab/filter.py:904: UserWarning: May not flag all label issues in class: 1, it has too few examples (see `min_examples_per_class` argument)
  warnings.warn(
存在问题的标签索引:  []
Traceback (most recent call last):
  File "/home/xxx/temp/three.py", line 72, in <module>
    clean_clf.fit(predictions, labels)
  File "/opt/conda/lib/python3.11/site-packages/cleanlab/classification.py", line 490, in fit
    label_issues = self.find_label_issues(
                   ^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/conda/lib/python3.11/site-packages/cleanlab/classification.py", line 762, in find_label_issues
    assert_valid_inputs(X, labels, pred_probs)
  File "/opt/conda/lib/python3.11/site-packages/cleanlab/internal/validation.py", line 42, in assert_valid_inputs
    assert_valid_class_labels(
  File "/opt/conda/lib/python3.11/site-packages/cleanlab/internal/validation.py", line 128, in assert_valid_class_labels
    raise ValueError("Labels must contain at least 2 classes.")
ValueError: Labels must contain at least 2 classes.

cleanlab 要求标签数据至少包含两个类,而数据集中可能只有一个类(“thyroid_nodule”)或者在处理标签时未能正确区分类别。这导致了 ValueError: Labels must contain at least 2 classes. 错误。

2.assert_valid_inputs内部库代码提示:

def assert_valid_inputs(
    labels: List[Dict[str, Any]],
    predictions,
    method: Optional[str] = None,
    threshold: Optional[float] = None,
):
    """Asserts proper input format."""
    if len(labels) != len(predictions):
        raise ValueError(
            f"labels and predictions length needs to match. len(labels) == {len(labels)} while len(predictions) == {len(predictions)}."
        )
    # Typecheck labels and predictions
    if not isinstance(labels[0], dict):
        raise ValueError(
            f"Labels has to be a list of dicts. Instead it is list of {type(labels[0])}."
        )
    # check last column of predictions is probabilities ( < 1.)?
    if not isinstance(predictions[0], (list, np.ndarray)):
        raise ValueError(
            f"Prediction has to be a list or np.ndarray. Instead it is type {type(predictions[0])}."
        )
    if not predictions[0][0].shape[1] == 5:
        raise ValueError(
            f"Prediction values have to be of format [x1,y1,x2,y2,pred_prob]. Please refer to the documentation for predicted probabilities under object_detection.rank.get_label_quality_scores for details"
        )

    valid_methods = ["objectlab"]
    if method is not None and method not in valid_methods:
        raise ValueError(
            f"""
            {method} is not a valid object detection scoring method!
            Please choose a valid scoring_method: {valid_methods}
            """
        )

    if threshold is not None and threshold > 1.0:
        raise ValueError(
            f"""
            Threshold is a cutoff of predicted probabilities and therefore should be <= 1.

系统库中要求使用多类数据集,使用单类数据集初步测试得出提示cleanlab 检查 predictions[0][0] 的形状时期望第二维的大小为 5,但当前的预测数据形状可能不符合要求。
为了解决这个问题,我们在预测框代码中加入了label_map = {‘thyroid_nodule’: 0} # 标签名称到整数标签的映射,去解决要求标签数据至少包含两个类问题。

总结

今天的工作可以总结为以下几个主要步骤:
1、修改官网代码以处理 .pkl 压缩文件:我们调整了官网代码中读取 .pkl 压缩文件的部分,以提取 prediction_paths 和 label_paths 的路径列表。这一步确保我们能够从压缩文件中提取出所需的路径信息。
2、处理标签数据:由于标签数据需要至少包含两个类来满足要求,我们使用了 label_map = {‘thyroid_nodule’: 0} 这个标签映射来解决标签数据的类数问题。这样,我们可以确保处理的数据包含了我们关心的标签。
3、保存错误图像名称:我们直接从 XML 文件中提取了错误的图像名称,并将这些名称保存到一个文本文件中。在保存时,我们去除了文件的后缀,以便于后续的匹配和验证。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值