指南 3个标注团队操作手册:提升标注速度与质量

使用PyTorch进行图像迁移学习

本示例解释了使用Label Studio和PyTorch进行计算机视觉的基础知识。 该模型采用了流行的ResNet图像分类器的迁移学习技术,并可针对您的数据进行微调。

你可以使用以下示例标注配置:

<View>
  <Image name="image_object" value="$image_url"/>
  <Choices name="image_classes" toName="image_object">
    <Choice value="Cat"/>
    <Choice value="Dog"/>
  </Choices>
</View>

创建模型脚本

如果使用Label Studio的ML SDK创建机器学习后端,请确保您的ML后端脚本执行以下操作:

  • 继承创建的模型类自 label_studio_ml.LabelStudioMLBase
  • Override the 2 methods:
    • predict(),接收输入任务并以Label Studio JSON格式输出预测结果
    • fit(),接收annotations可迭代对象并返回包含已创建链接和资源的字典。该字典随后会通过self.train_output字段用于加载模型。

创建一个名为model.py的文件,其中包含准备好用于训练和推理的PyTorch模型。

首先,创建一个Dataset类,该类接收图像URL列表作为输入,并生成一批经过预处理的图像及其对应标签:

import torch
import torch.nn as nn
import torch.optim as optim
import time

from torch.utils.data import Dataset, DataLoader
from torchvision import datasets, models, transforms

device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')


class ImageClassifierDataset(Dataset):
        
    def __init__(self, image_urls, image_classes):
        self.images = []
        self.labels = []
        
        self.classes = list(set(image_classes))
        self.class_to_label = {c: i for i, c in enumerate(self.classes)}
        
        self.image_size = 224
        self.transforms = transforms.Compose([
            transforms.Resize(self.image_size),
            transforms.CenterCrop(self.image_size),
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ])
        
        
        for image_url, image_class in zip(image_urls, image_classes):
            image = self._get_image_from_url(image_url)
            transformed_image = self.transforms(image)
            self.images.append(transformed_image)
            
            label = self.class_to_label[image_class]
            self.labels.append(label)
            
    def _get_image_from_url(self, url):
        pass
    
    def __getitem__(self, index):
        return self.images[index], self.labels[index]
    
    def __len__(self):
        return len(self.images)

接下来,为预训练的ResNet模型创建一个简单的封装器:

class ImageClassifier(object):
    
    def __init__(self, num_classes):
        self.model = models.resnet18(pretrained=True)
        num_ftrs = self.model.fc.in_features
        self.model.fc = nn.Linear(num_ftrs, num_classes)
        
        self.model = self.model.to(device)
        
        self.criterion = nn.CrossEntropyLoss()
        self.optimizer = optim.SGD(self.model.parameters(), lr=0.001, momentum=0.9)
        
        # Decay LR by a factor of 0.1 every 7 epochs
        self.scheduler = optim.lr_scheduler.StepLR(self.optimizer, step_size=7, gamma=0.1)
    
    def save(self, path):
        torch.save(self.model.state_dict(), path)
    
    def load(self, path):
        self.model.load_state_dict(torch.load(path))
        self.model.eval()
        
    def train(self, dataloader, num_epochs=25):
        since = time.time()

        self.model.train()
        for epoch in range(num_epochs):
            print('Epoch {}/{}'.format(epoch, num_epochs - 1))
            print('-' * 10)
            
            running_loss = 0.0
            running_corrects = 0    
            # Iterate over data.
            for inputs, labels in dataloader:
                inputs = inputs.to(device)
                labels = labels.to(device)

                self.optimizer.zero_grad()
                outputs = self.model(inputs)
                _, preds = torch.max(outputs, 1)
                loss = self.criterion(outputs, labels)
                loss.backward()
                self.optimizer.step()

                # statistics
                running_loss += loss.item() * inputs.size(0)
                running_corrects += torch.sum(preds == labels.data)
                self.scheduler.step()

            epoch_loss = running_loss / len(dataloader.dataset)
            epoch_acc = running_corrects.double() / len(dataloader.dataset)

            print('Train Loss: {:.4f} Acc: {:.4f}'.format(epoch_loss, epoch_acc))

        print()
    
        time_elapsed = time.time() - since
        print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))

        return self.model

最后,重写API方法:

from label_studio_ml.model import LabelStudioMLBase

class ImageClassifierAPI(LabelStudioMLBase):
    
    def __init__(self, **kwargs):
        self.model = ImageClassifier(resources['num_classes'])
        self.model.load(resources['model_path'])
        self.labels = resources['labels']
        
    def predict(self, tasks, **kwargs):
        pass

    def fit(self, completions, **kwargs):
        pass

创建ML后端配置与脚本

Label Studio可以自动创建运行ML后端所需的所有配置和脚本,这些配置和脚本来自您新创建的模型。

调用您的机器学习后端 my_backend,并通过命令行初始化机器学习后端目录 ./my_backend

label-studio-ml init my_backend

最后一条命令会获取您的脚本./model.py并在同级目录下创建./my_backend文件夹,复制启动ML后端所需的各种配置和脚本,适用于开发或生产模式。

备注

你可以为模型脚本指定不同的位置,例如:label-studio-ml init my_backend --script /path/to/my/script.py

启动ML后端服务器

开发模式

在开发模式下,训练和推理在同一个进程中完成,因此服务器在模型训练期间不会响应传入的预测请求。

要在Flask开发模式下启动ML后端服务器,请从命令行运行以下命令:

label-studio-ml start my_backend

服务器已启动在 http://localhost:9090 并在控制台输出日志。

生产模式

生产模式由Redis服务器和RQ作业提供支持,负责后台训练流程。这意味着您可以启动模型训练,同时继续从当前模型状态请求预测。当模型完成训练后,新版本模型会自动更新。

对于生产环境,请确保您的系统已安装Docker和docker-compose。然后在命令行中运行以下命令:

cd my_backend/
docker-compose up

您可以在my_backend/logs/uwsgi.log中查看运行时日志,在my_backend/logs/rq.log中查看RQ训练日志

在Label Studio中使用机器学习后端

初始化并启动一个新的Label Studio项目,连接到正在运行的ML后端:

label-studio start my_project --init --ml-backends http://localhost:9090

获取预测结果

您应该在标注界面中看到模型预测结果。参见使用Label Studio设置机器学习

模型训练

通过点击项目设置中机器学习页面的Start training按钮手动触发模型训练,或使用API调用:

curl -X POST http://localhost:8080/api/models/train