• 이전 버전의 컨버터는 pyTorch를 변환하기 위해서는 ONNX를 거쳐야 했다.

    • 근데 ONNX는 표준 모델이기 때문에 발전이 느렸고, PyTorch 모델을 ONNX로 내보내는 게 실패하는 경우가 많았다.
  • PyTorch 변환

    • PyTorch → TorchScript → Core ML
    import coremltools as ct
    
    model = ct.convert(
    	source_model,// TorchScript model, 혹은 TorchScript model이 있는 경로
    	inputs=[ct.ImageType(name="input", shape(3, 224, 224))]	
    )
    
    • 기본적으로 TorchScript와 Core ML의 모델은 1대 1대응이지만, 1대 다일수도, 다 대 1일 수도 있다.
  • 커스텀 오퍼레이션이 필요하다면

    • swift로 구현 가능
    • 빌더로 넣거나
  • 그래서 TorchScript를 어떻게 얻나?

    • JIT trace(torch.jit.trace)
    • JIT script(torch.jit.script)
  • trace 로 얻기

    • 예제 입력을 넣어서 실행하면서, 이를 추척해서 스크립트 생성

    • validation 데이터, 혹은 랜덤 데이터를 예제 입력으로 쓰면 된다.

      import torch
      
      traced_model = torch.jit.trace(model, example_input)
      
    • 실제

    # # Converting a Segmentation Model via CoreML
    
    # ### Imports
    
    import urllib
    import torch
    import torch.nn as nn
    import torchvision
    import json
    
    from torchvision import transforms
    import coremltools as ct
    from PIL import Image
    
    # ### Load Sample Model and Image
    
    # Load model
    model = torch.hub.load('pytorch/vision:v0.6.0', 'deeplabv3_resnet101', pretrained=True).eval()
    # Load sample image
    input_image = Image.open("dog_and_cat.jpg")
    display(input_image)
    
    # ### Image Preprocessing
    
    to_tensor = transforms.ToTensor()
    input_tensor = to_tensor(input_image)
    input_batch = input_tensor.unsqueeze(0)
    
    # ### Trace the Model with PyTorch
    
    # First attempt at tracing
    trace = torch.jit.trace(model, input_batch)
    
    # ### Wrap the Model to Allow Tracing
    # only tensor and tuple can be output. dictionaries are not allowed
    class WrappedDeeplabv3Resnet101(nn.Module):
        
        def __init__(self):
            super(WrappedDeeplabv3Resnet101, self).__init__()
            self.model = torch.hub.load('pytorch/vision:v0.6.0', 'deeplabv3_resnet101', pretrained=True).eval()
        
        def forward(self, x):
            res = self.model(x)
            x = res["out"]
            return x
    
    # ### Trace the Wrapped Model
    
    traceable_model = WrappedDeeplabv3Resnet101().eval()
    trace = torch.jit.trace(traceable_model, input_batch)
    
    # ### Convert to Core ML 
    
    # Define input
    _input = ct.ImageType(
        name="input_1", 
        shape=input_batch.shape, 
        bias=[-0.485/0.229,-0.456/0.224,-0.406/0.225], 
        scale= 1./(255*0.226)
    )
    
    # Convert model
    mlmodel = ct.convert(
        trace,
        inputs=[_input],
    )
    
    # ### Set the Model Metadata
    
    labels_json = {"labels": ["background", "aeroplane", "bicycle", "bird", "board", "bottle", "bus", "car", "cat", "chair", "cow", "diningTable", "dog", "horse", "motorbike", "person", "pottedPlant", "sheep", "sofa", "train", "tvOrMonitor"]}
    
    mlmodel.type = 'imageSegmenter'
    mlmodel.user_defined_metadata['com.apple.coreml.model.preview.params'] = json.dumps(labels_json)
    
    # ### Save the Model for Visualization
    
    mlmodel.save("SegmentationModel.mlmodel")
    
  • scripting

    • 트레이싱이 안되는 경우에 사용
    • 직접 모델을 코드로 정의
    class MyModel(nn.Module):
      def forward(self, x):
        x = self.conv(x)
        x = self.relu(x)
        x = self.avg_pool(x)
        x = self.dropout(x)
        x = self.linear(x)
        return x
    
    import torch
    
    model = MyModel()
    scripted_model = torch.jit.script(model)
    
  • tracing vs scripting

    • tracing: 데이터를 통해서 쉽게 기존 모델을 꺼내올 수 있다. 또한 모델 표현이 간결해질 확률이 높다. 대신 입력 데이터가 사용하지 않은 레이어는 누락된다. (애플 권장)
    • scripting: 모델을 직접 선언하기 때문에, 누락이 없다. 다만 직접 정의해야 되므로 조금은 번거롭다.
  • Language Model

    • 문자열 자동 완성: 현재 문자열을 토큰화해서, 다음에 나올 토큰을 예상하는 것
    # # Converting a Language Model via Core ML
    
    # ### Imports
    
    import torch
    import numpy as np
    from transformers import GPT2LMHeadModel, GPT2Tokenizer
    import coremltools as ct
    
    # ### Model
    
    class FinishMySentence(torch.nn.Module):
        def __init__(self, model=None, eos=198):
            super(FinishMySentence, self).__init__()
            self.eos = torch.tensor([eos])
            self.next_token_predictor = model
            self.default_token = torch.tensor([0])
        
        def forward(self, x):
            sentence = x
            token = self.default_token
            while token != self.eos:
                predictions, _ = self.next_token_predictor(sentence)
                token = torch.argmax(predictions[-1, :], dim=0, keepdim=True)
                sentence = torch.cat((sentence, token), 0)
            
            return sentence
    
    # ### Initialize the Token Predictor
    
    token_predictor = GPT2LMHeadModel.from_pretrained("gpt2", torchscript=True).eval()
    
    # ### Trace the Token Predictor
    # 
    
    random_tokens = torch.randint(10000, (5,))
    
    traced_token_predictor = torch.jit.trace(token_predictor, random_tokens)
    
    # ### Script the Outer Loop
    # 
    
    model = FinishMySentence(model=traced_token_predictor)
    scripted_model = torch.jit.script(model)
    
    # ### Convert to Core ML
    # 
    
    mlmodel = ct.convert(
        scripted_model,
        # Range for the sequence dimension to be between [1, 64]
        inputs=[ct.TensorType(name="context", shape=(ct.RangeDim(1, 64),), dtype=np.int32)],
    )
    
    # ### Encode the Sentence Fragment
    # 
    
    sentence_fragment = "The Manhattan bridge is"
    
    tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
    context = torch.tensor(tokenizer.encode(sentence_fragment))
    
    # ### Run the Model
    
    coreml_inputs = {"context": context.to(torch.int32).numpy()}
    prediction_dict = mlmodel.predict(coreml_inputs)
    generated_tensor = prediction_dict["sentence:2"]
    generated_text = tokenizer.decode(generated_tensor)
    
    print("Fragment: {}".format(sentence_fragment))
    print("Completed: {}".format(generated_text))
    
    • TroubleShooting
      • output 형식 문제: output은 dictionary가 될 수 없다.

        • 해결법: 작은 Wrapper를 만들어서, dictionary를 tensor나 tuple로 해체한다.
      • Trace warning: Python value에 대해서는 trace를 할 수 없다.

        • 이는 tracer가 공격적으로 오류를 내는 것이다.
        • 파이썬 기본 값에 대한 기본 연산이면 괜찮다. 이것만 써라.
        • 외부 라이브러리는 어떤 것도 지원하지 않는다.
      • JIT script의 타입 문제: JIT scripter는 타입을 필요로 한다.

        • 의미 있는 생성자나, 타입 어노테이션을 활용한다.
        class TestNet(nn.Module):
        	def forward(self, x):
        		_list = []
        		
        		for i in range(10):
        			_list.append(i)
        		return list
        
        model = torch.jit.script(TestNet())
        
        RuntimeError: Argument for call are not valid
        ...
        Expected a value of type 'Tensor' ... but instead found type 'int'
        ...
        
        class TestNet(nn.Module):
        	def forward(self, x):
        		_list: List[int] = [0]
        		
            for i in range(10):
        			_list.append(i)
        		return _list[1:]
        model = torch.jit.script(TestNet())
        
      • evaluation 모드로의 변경: 특정 레이어는 evaluation모드에서 비활성화되고, 컨버터는 이를 무시한다.

      • 더 자세한 정보는 coremltool과 pytorch사이트 참조

        • coremltools
        • pyTorch