Pytorch
TensorFlow

#
파이프라인 내부 동작 과정

PyTorch를 사용하는지, TensorFlow를 사용하는지에 따라 내용이 약간 달라지는 첫 번째 섹션입니다. 제목 상단 스위치를 이용해 선호하는 플랫폼을 선택하세요!

완전한 예제를 이용해, 아래의 제1단원 코드를 수행했을 때 뒤에서 어떤 일이 일어나고 있는지 알아봅시다.

    from transformers import pipeline

    classifier = pipeline("sentiment-analysis")
    classifier(
        [
            "I've been waiting for a HuggingFace course my whole life.",
            "I hate this so much!",
        ]
    )

다음과 같은 출력이 나오게 됩니다.

    [{'label': 'POSITIVE', 'score': 0.9598047137260437},
     {'label': 'NEGATIVE', 'score': 0.9994558095932007}]

제1단원에서 확인했듯이, 이 파이프라인 그룹은 세 단계(전처리, 입력을 모델에 넘겨주는 것, 후처리)를 함께 수행합니다.

각 단게에 대해 빠르게 살펴보겠습니다.

##
토크나이저를 이용한 전처리

다른 신경망처럼 Transformer 모델도 원시 텍스트를 바로 처리할 수 없기 때문에 파이프라인의 첫 번째 단계는 텍스트 입력을 모델이 이해할 수 있는 숫자로 변환하는 것입니다. 이 과정을 위해 다음 기능들을 수행하는 토크나이저를 사용합니다.

- 입력을 토큰이라고 부르는 단어나 하위 단어, 또는 심볼(예-구두점)로 분할
- 각 토큰을 하나의 정수에 매핑
- 모델에 유용할 수 있는 부가적인 입력 추가

이 모든 전처리 과정은 모델이 사전학습될 때와 완전히 동일한 방식으로 진행되어야 하기 때문에 Model Hub에서 정보를 다운로드 해야합니다. 전처리를 위해 `AutoTokenizer` 클래스와 AutoTokenizer의 `from_pretrained()` 메서드를 사용합니다. 모델의 체크포인트 이름을 사용하여 모델의 토크나이저와 연관된 데이터를 자동으로 가져와 저장합니다. (따라서 아래 코드를 처음 실행할 때만 다운로드됩니다.)

`sentiment-analysis` 파이프라인의 기본 체크포인트는 `distilbert-base-uncased-finetuned-sst-2-english`(모델 카드 확인은 여기서 가능)이므로 아래 코드를 실행합니다.

    from transformers import AutoTokenizer

    checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
    tokenizer = AutoTokenizer.from_pretrained(checkpoint)

토크나이저가 있다면 문장을 토크나이저에 입력하여 우리의 모델에 전달할 준비가 된 딕셔너리를 출력으로 얻게 됩니다! 남은 것은 입력 ID 목록을 tensor로 변환하는 것입니다.

여러분은 어떤 ML 프레임워크-PyTorch나 TensorFlow, 또는 몇몇 모델을 위한 Flax-가 백엔드로 사용되는지 걱정하지 않고 🤗 Transformers를 사용할 수 있습니다. 하지만 Transformer 모델은 tensor만을 입력으로 받습니다. 만약 tensor에 대해 처음 들어봤다면, NumPy 배열을 생각하면 됩니다. NumPy 배열은 스칼라(0D), 벡터(1D), 행렬(2D) 또는 더 많은 차원을 가질 수 있습니다. 이것은 사실상 텐서입니다. 다른 ML 프레임워크의 텐서도 유사하게 동작하며, NumPy 배열처럼 인스턴스화가 쉽습니다.

얻고자 하는 tensor의 타입(PyTorch, TensorFlow, 일반 NumPy)을 지정하기 위해, `return_tensors` 전달인자를 사용합니다.

    raw_inputs = [
        "I've been waiting for a HuggingFace course my whole life.",
        "I hate this so much!",
    ]
    inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt")
    print(inputs)

padding과 truncation에 대해 벌써 걱정하지 마세요. 나중에 설명하도록 하겠습니다. 여기서 기억해야 할 중요한 점은 하나의 문장 또는 여러 개의 문장 리스트를 토크나이저 함수로 전달할 수 있을 뿐만 아니라 얻고 싶은 텐서 유형까지 지정할 수 있다는 것입니다. (텐서 유형이 지정되지 않으면 이중 리스트를 결과로 얻게 됩니다)

PyTorch tensor로 지정했을 때의 결과입니다.

    {
        'input_ids': tensor([
            [  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172, 2607,  2026,  2878,  2166,  1012,   102],
            [  101,  1045,  5223,  2023,  2061,  2172,   999,   102,     0,     0,     0,     0,     0,     0,     0,     0]
        ]),
        'attention_mask': tensor([
            [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
            [1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
        ])
    }

출력은 `input_ids`와 `attention_mask` 두 개의 키를 갖는 딕셔너리입니다. `input_ids`는 각 문장 내 토큰의 고유 식별자인 정수로 이루어진 2개의 행(한 행이 하나의 문장)을 가지고 있습니다. `attention_mask`는 이 챕터의 뒤쪽에서 설명할 것입니다.

##
모델 살펴보기

토크나이저를 다운받은 방식과 동일한 방식으로 사전학습된 모델을 다운받을 수 있습니다. 🤗 Transformers는 `from_pretrained` 메서드를 가진 `AutoModel` 클래스를 제공합니다.

    from transformers import AutoModel

    checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
    model = AutoModel.from_pretrained(checkpoint)

이 코드 스니펫에서 이전 파이프라인에서 사용된 것과 동일한 체크포인트를 다운로드(실제로는 이미 저장되어 있어야 합니다.)하고 그것으로 모델을 인스턴스화했습니다.

해당 아키텍처는 기본 Transformer 모듈만 포함하고 있습니다. 입력이 주어지면 features라고도 불리는 hidden states를 출력합니다. 각 모델의 입력에 대해 Transformer 모델에 의해 수행된 입력의 문맥적 이해로 표현할 수 있는 고차원 벡터를 가져옵니다.

이 내용이 이해되지 않더라도 걱정하지 마세요. 뒤에서 설명할 것입니다.

이러한 hidden states는 그 자체로 유용할 수 있지만 일반적으로 head라고 알려진 모델의 다른 부분에 입력으로 들어갑니다. 제1단원에서, 동일한 구조로 다른 태스크를 수행할 수 있었는데 이 태스크들은 서로 다른 헤드와 연관되어 있습니다.

###
고차원 벡터란?

Transformer 모듈에 의한 출력 벡터는 일반적으로 크며 보통 3개의 차원을 가집니다.

- Batch size: 한 번에 처리되는 시퀀스의 수 (예제에서는 2)
- Sequence length: 시퀀스의 숫자 표현 길이 (예제에서는 16)
- Hidden size: 각 모델 입력 벡터 차원

위에서 마지막 값으로 인해 “고차원”이라고 불립니다. hidden size는 매우 클 수 있습니다 (작은 모델은 768이 일반적이며 큰 모델은 3072나 그 이상의 값이 될 수 있습니다)

전처리 과정을 거친 입력을 모델로 넘기면 아래 결과를 확인할 수 있습니다.

    outputs = model(**inputs)
    print(outputs.last_hidden_state.shape)

    torch.Size([2, 16, 768])

🤗 Transformers 모델의 출력은 `namedtuple` 또는 딕셔너리 형태입니다. 속성이나 키(`outputs["last_hidden_state"]`)를 이용해 요소에 접근할 수 있고 찾고자 하는 것의 정확한 위치를 안다면 인덱스(`outputs[0]`)도 사용할 수 있습니다.

###
모델 헤드: 숫자로 이해하기

모델 헤드는 hidden state의 고차원 벡터를 입력으로 받아 다른 차원으로 투영합니다. 모델 헤드는 보통 하나 이상의 선형 레이어로 이루어져 있습니다.

Transformer 모델의 출력은 처리할 모델 헤드로 바로 전달됩니다.

이 다이어그램에서, 모델은 모델의 임베딩 레이어와 후속 레이어로 표현됩니다. 임베딩 레이어는 토큰화된 각각의 입력 ID를 연관된 토큰을 나타내는 벡터로 변환합니다. 후속 레이어는 문장의 최종 표현을 만들기 위해 어텐션 메커니즘을 이용해 이 벡터들을 처리합니다.

🤗 Transformer에는 다양한 아키텍처가 있으며, 각각의 아키텍처는 특정 작업을 처리하도록 설계되었습니다. 아래는 일부 아키텍처입니다.

- `*Model` (retrieve the hidden states)
- `*ForCausalLM`
- `*ForMaskedLM`
- `*ForMultipleChoice`
- `*ForQuestionAnswering`
- `*ForSequenceClassification`
- `*ForTokenClassification`
- 그 외 🤗

이 예제를 위해서는 문장을 긍정 또는 부정으로 분류할 수 있게 하는 시퀀스 분류 헤드를 가진 모델이 필요합니다. 따라서 `AutoModel` 클래스가 아닌 `AutoModelForSequenceClassification`을 사용할 것입니다.

    from transformers import AutoModelForSequenceClassification

    checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
    model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
    outputs = model(**inputs)

출력 형태를 보면 차원이 훨씬 적은 것을 알 수 있습니다. 모델 헤드는 이전에 봤던 고차원 벡터를 입력으로 받아 2개의 값(레이블 당 하나)으로 이루어진 벡터를 출력합니다.

    print(outputs.logits.shape)

    torch.Size([2, 2])

우리는 2개의 문장과 2개의 레이블만 있기 때문에 모델로부터 얻은 출력 형태는 2 x 2입니다.

##
출력 후처리

모델의 출력 값이 그 자체로 의미있는 것은 아닙니다. 한 번 보도록 합시다.

    print(outputs.logits)

    tensor([[-1.5607,  1.6123],
            [ 4.1692, -3.3464]], grad_fn=<AddmmBackward>)

모델은 첫 번째 문장에 대해 `[-1.5607, 1.6123]`으로 예측했고 두 번째 문장에 대해 `[ 4.1692, -3.3464]`으로 예측했습니다. 이 값들은 확률이 아니라 모델의 마지막 층에 의해 출력된 정규화되지 않은 점수인 logits입니다. 확률로 변환되기 위해 logits은 SoftMax 층을 거쳐야 합니다. 학습을 위한 손실 함수가 일반적으로 SoftMax와 같은 마지막 활성함수와 교차 엔트로피와 같은 실제 손실 함수를 모두 사용하기 때문에 모든 🤗 Transformers 모델의 출력은 logit입니다.

    import torch

    predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
    print(predictions)

    tensor([[4.0195e-02, 9.5980e-01],
            [9.9946e-01, 5.4418e-04]], grad_fn=<SoftmaxBackward>)

모델은 첫 번째 문장에 대해 `[0.0402, 0.9598]`로 예측했고 두 번째 모델에 대해 `[0.9995, 0.0005]`로 예측했습니다. 이 값들은 확실하게 확률값입니다.

각 위치에 해당하는 레이블을 얻기 위해, 모델 config의 `id2label` 속성을 살펴봅시다. Config에 대한 더 많은 내용은 다음 섹션에서 진행됩니다.

    model.config.id2label

    {0: 'NEGATIVE', 1: 'POSITIVE'}

모델의 예측 결과를 아래처럼 결론 지을 수 있습니다.

- 첫 번째 문장: NEGATIVE: 0.0402, POSITIVE: 0.9598
- 두 번째 문장: NEGATIVE: 0.9995, POSITIVE: 0.0005

파이프라인 세 단계-토크나이저를 이용한 전처리, 모델에 입력 넣어주기, 후처리-를 성공적으로 재현했습니다! 이제 각 단계별로 좀 더 깊게 알아보는 시간을 가져봅시다.

✏️ 직접 해보세요! 2개 이상의 문장을 골라 `sentiment-analysis` 파이프라인을 적용해보세요. 이 챕터에서 본 내용을 그대로 수행해보고 같은 결과가 나오는지 확인해보세요!