03wk-1: 감성분석 파고들기 (1)

Author

최규빈

Published

September 19, 2024

1. 강의영상

2. Imports

import datasets
import transformers
import evaluate
import numpy as np
import torch # 파이토치
/home/cgb3/anaconda3/envs/hf/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm

3. 이전코드

- Step1~4를 위한 준비

# ## Step1 
# 데이터불러오기 = datasets.load_dataset
# 데이터전처리하기1 = 토크나이저 = transformers.AutoTokenizer.from_pretrained("distilbert/distilbert-base-uncased") 
# def 데이터전처리하기2(examples):
#     return 데이터전처리하기1(examples["text"], truncation=True)
# ## Step2 
# 인공지능생성하기 = transformers.AutoModelForSequenceClassification.from_pretrained
# ## Step3 
# 데이터콜렉터 = transformers.DataCollatorWithPadding(tokenizer=토크나이저)
# def 평가하기(eval_pred):
#     predictions, labels = eval_pred
#     predictions = np.argmax(predictions, axis=1)
#     accuracy = evaluate.load("accuracy")
#     return accuracy.compute(predictions=predictions, references=labels)
# 트레이너세부지침생성기 = transformers.TrainingArguments
# 트레이너생성기 = transformers.Trainer
# ## Step4 
# 강인공지능생성하기 = transformers.pipeline

- Step 1~4

# ## Step1 
# 데이터 = 데이터불러오기('imdb')
# 전처리된데이터 = 데이터.map(데이터전처리하기2,batched=True)
# 전처리된훈련자료, 전처리된검증자료 = 전처리된데이터['train'], 전처리된데이터['test']
# ## Step2 
# 인공지능 = 인공지능생성하기("distilbert/distilbert-base-uncased", num_labels=2)
# ## Step3 
# 트레이너세부지침 = 트레이너세부지침생성기(
#     output_dir="my_awesome_model",
#     learning_rate=2e-5,
#     per_device_train_batch_size=16,
#     per_device_eval_batch_size=16,
#     num_train_epochs=2, # 전체문제세트를 2번 공부하라..
#     weight_decay=0.01,
#     eval_strategy="epoch",
#     save_strategy="epoch",
#     load_best_model_at_end=True,
#     push_to_hub=False,
# )
# 트레이너 = 트레이너생성기(
#     model=인공지능,
#     args=트레이너세부지침,
#     train_dataset=전처리된훈련자료,
#     eval_dataset=전처리된검증자료,
#     tokenizer=토크나이저,
#     data_collator=데이터콜렉터,
#     compute_metrics=평가하기,
# )
# 트레이너.train()
# ## Step4 
# 강인공지능 = 강인공지능생성하기("sentiment-analysis", model="my_awesome_model/checkpoint-1563")
# print(강인공지능("This movie was a huge disappointment."))
# print(강인공지능("This was a masterpiece."))

4. DataSet

- 가장 중요한 원초적 질문: 데이터 셋을 바꿔치기 하려면?

- 내가 원하는 데이터를 아래와 같은 양식(=형태)으로 정리해야함.

데이터 = datasets.load_dataset('imdb')
데이터
DatasetDict({
    train: Dataset({
        features: ['text', 'label'],
        num_rows: 25000
    })
    test: Dataset({
        features: ['text', 'label'],
        num_rows: 25000
    })
    unsupervised: Dataset({
        features: ['text', 'label'],
        num_rows: 50000
    })
})
데이터, type(데이터)
(DatasetDict({
     train: Dataset({
         features: ['text', 'label'],
         num_rows: 25000
     })
     test: Dataset({
         features: ['text', 'label'],
         num_rows: 25000
     })
     unsupervised: Dataset({
         features: ['text', 'label'],
         num_rows: 50000
     })
 }),
 datasets.dataset_dict.DatasetDict)
데이터['train'], type(데이터['train'])
(Dataset({
     features: ['text', 'label'],
     num_rows: 25000
 }),
 datasets.arrow_dataset.Dataset)
데이터['train'][0]
{'text': 'I rented I AM CURIOUS-YELLOW from my video store because of all the controversy that surrounded it when it was first released in 1967. I also heard that at first it was seized by U.S. customs if it ever tried to enter this country, therefore being a fan of films considered "controversial" I really had to see this for myself.<br /><br />The plot is centered around a young Swedish drama student named Lena who wants to learn everything she can about life. In particular she wants to focus her attentions to making some sort of documentary on what the average Swede thought about certain political issues such as the Vietnam War and race issues in the United States. In between asking politicians and ordinary denizens of Stockholm about their opinions on politics, she has sex with her drama teacher, classmates, and married men.<br /><br />What kills me about I AM CURIOUS-YELLOW is that 40 years ago, this was considered pornographic. Really, the sex and nudity scenes are few and far between, even then it\'s not shot like some cheaply made porno. While my countrymen mind find it shocking, in reality sex and nudity are a major staple in Swedish cinema. Even Ingmar Bergman, arguably their answer to good old boy John Ford, had sex scenes in his films.<br /><br />I do commend the filmmakers for the fact that any sex shown in the film is shown for artistic purposes rather than just to shock people and make money to be shown in pornographic theaters in America. I AM CURIOUS-YELLOW is a good film for anyone wanting to study the meat and potatoes (no pun intended) of Swedish cinema. But really, this film doesn\'t have much of a plot.',
 'label': 0}

- datasets.arrow_dataset.Dataset 의 인스턴스 만들기

아래와 같은 함수가 있음.

#datasets.Dataset.from_dict?
#클래스메소드
\(\star\) datasets.Dataset.from_dict 사용법 (ref: ChatGPT)

datasets.Dataset.from_dict는 Python의 딕셔너리(dict)를 Dataset 객체로 변환하는 함수입니다. 주로 딕셔너리 형태의 데이터를 빠르게 데이터셋으로 변환할 때 사용됩니다.

간단한 사용법

from datasets import Dataset

# 딕셔너리를 Dataset으로 변환
data_dict = {
    'text': ["Hello world", "How are you?", "Fine, thanks!"],
    'label': [0, 1, 1]
}

# Dataset 생성
dataset = Dataset.from_dict(data_dict)

# 출력
print(dataset)

주요 매개변수:

  • mapping: 필수, 문자열을 키로 하고 리스트 또는 배열을 값으로 하는 딕셔너리.
  • features: 선택, 데이터셋의 각 필드 타입을 정의.
  • info: 선택, 데이터셋에 대한 추가 정보(설명, 인용 등).
  • split: 선택, 데이터셋의 나누기(‘train’, ‘test’ 등).

반환값:

  • Dataset: PyArrow 기반의 데이터셋 객체.
train_dict = {
    'text': [
        "I prefer making decisions based on logic and objective facts.",
        "I always consider how others might feel when making a decision.",
        "Data and analysis drive most of my decisions.",
        "I rely on my empathy and personal values to guide my choices."
    ],
    'label': [0, 1, 0, 1]  # 0은 T(사고형), 1은 F(감정형)
}

test_dict = {
    'text': [
        "I find it important to weigh all the pros and cons logically.",
        "When making decisions, I prioritize harmony and people's emotions."
    ],
    'label': [0, 1]  # 0은 T(사고형), 1은 F(감정형)
}
train_data = datasets.Dataset.from_dict(train_dict)
test_data = datasets.Dataset.from_dict(test_dict)

- datasets.dataset_dict.DatasetDict의 인스턴스 만들기

나의데이터 = datasets.dataset_dict.DatasetDict({'train':train_data, 'test':test_data})
나의데이터['train'][0]
{'text': 'I prefer making decisions based on logic and objective facts.',
 'label': 0}

- 일단 아래와 같은 형태로 분석할 데이터를 저장할 수 있다면, 나머지 분석은 코드를 복/붙하여 진행할 수 있음.

train_dict = {
    'text': [
        "I prefer making decisions based on logic and objective facts.",
        "I always consider how others might feel when making a decision.",
        "Data and analysis drive most of my decisions.",
        "I rely on my empathy and personal values to guide my choices."
    ],
    'label': [0, 1, 0, 1]  # 0은 T(사고형), 1은 F(감정형)
}

test_dict = {
    'text': [
        "I find it important to weigh all the pros and cons logically.",
        "When making decisions, I prioritize harmony and people's emotions."
    ],
    'label': [0, 1]  # 0은 T(사고형), 1은 F(감정형)
}

5. 토크나이저

- 토크나이저를 불러오는 코드

토크나이저 = 데이터전처리하기1 = transformers.AutoTokenizer.from_pretrained("distilbert/distilbert-base-uncased")
/home/cgb3/anaconda3/envs/hf/lib/python3.12/site-packages/transformers/tokenization_utils_base.py:1601: FutureWarning: `clean_up_tokenization_spaces` was not set. It will be set to `True` by default. This behavior will be depracted in transformers v4.45, and will be then set to `False` by default. For more details check this issue: https://github.com/huggingface/transformers/issues/31884
  warnings.warn(
#토크나이저?
\(\star\) 토크나이저 사용법 (ref: ChatGPT)

주요 파라미터:

  1. text:
    • Union[str, List[str], List[List[str]]]
    • 주어진 텍스트를 토큰화합니다. 이 텍스트는 문자열일 수도 있고, 문자열의 리스트 또는 리스트 안의 리스트일 수도 있습니다.
  2. text_pair:
    • Union[str, List[str], List[List[str]], NoneType]
    • 두 개의 텍스트를 함께 모델에 입력할 때 사용됩니다. 예를 들어, 질문-답변 쌍 같은 경우 이 두 번째 텍스트를 넣습니다.
  3. text_target:
    • Union[str, List[str], List[List[str]]]
    • 토큰화를 할 때 목표(target) 텍스트에 해당하는 부분입니다. 주로 시퀀스 생성 모델에서 활용됩니다.
  4. text_pair_target:
    • Union[str, List[str], List[List[str]], NoneType]
    • 위의 text_pair와 유사하게 목표(target) 텍스트의 두 번째 텍스트를 나타냅니다.
  5. add_special_tokens:
    • bool
    • 문장의 시작, 끝, 구분자 같은 특별한 토큰을 추가할지 여부를 결정합니다. 기본값은 True입니다.
  6. padding:
    • Union[bool, str, transformers.utils.generic.PaddingStrategy]
    • 문장 길이가 다를 때 패딩을 넣어 문장의 길이를 동일하게 맞춥니다. 패딩 전략에는 True, False, 'longest', 'max_length' 등이 있습니다.
  7. truncation:
    • Union[bool, str, transformers.tokenization_utils_base.TruncationStrategy]
    • 문장이 너무 길 경우 지정된 최대 길이에 맞춰 잘라내는 옵션입니다. 전략에는 True, False, 'longest_first', 'only_first', 'only_second' 등이 있습니다.
  8. max_length:
    • Optional[int]
    • 문장의 최대 길이를 설정합니다. None일 경우 기본 설정을 따릅니다.
  9. stride:
    • int
    • 텍스트를 자를 때 중첩을 만들기 위한 옵션입니다. 즉, 자른 부분과 다음 부분 사이의 겹치는 범위를 설정합니다.
  10. is_split_into_words:
    • bool
    • 텍스트가 이미 단어 단위로 분리되어 있는지 여부를 나타냅니다. 기본적으로는 False로, 텍스트가 단어 단위로 분리되지 않았다고 가정합니다.
  11. return_tensors:
    • Union[str, transformers.utils.generic.TensorType, NoneType]
    • 출력 형식으로 텐서를 반환할지 여부를 설정합니다. 'pt'(PyTorch), 'tf'(TensorFlow), 'np'(NumPy) 등을 지정할 수 있습니다.
  12. return_token_type_ids:
    • Optional[bool]
    • 토큰 타입 ID를 반환할지 여부를 설정합니다. 주로 두 개의 문장을 함께 처리할 때 문장을 구분하기 위해 사용됩니다.
  13. return_attention_mask:
    • Optional[bool]
    • attention_mask를 반환할지 여부를 설정합니다. 패딩된 토큰이 모델의 어텐션에 영향을 주지 않도록 마스크를 설정합니다.
  14. return_overflowing_tokens:
    • bool
    • 텍스트가 최대 길이를 초과하는 경우, 잘린 토큰을 반환할지 여부를 결정합니다.
  15. return_special_tokens_mask:
    • bool
    • 특별한 토큰에 대한 마스크를 반환할지 여부를 설정합니다.
  16. return_offsets_mapping:
    • bool
    • 텍스트의 각 토큰이 원본 텍스트에서 어느 위치에 있는지 나타내는 오프셋 맵핑을 반환할지 여부를 설정합니다.
  17. return_length:
    • bool
    • 토큰화된 문장의 길이를 반환할지 여부를 설정합니다.
  18. verbose:
    • bool
    • 디버깅 메시지를 출력할지 여부를 설정합니다. 기본값은 True로 설정되어 있습니다.

사용 예시:

from transformers import AutoTokenizer

# 토크나이저 불러오기
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

# 텍스트 토큰화
encoding = tokenizer(
    text="Hello, how are you?",
    padding=True,
    truncation=True,
    max_length=10,
    return_tensors='pt'
)

print(encoding)

이 코드에서는 “Hello, how are you?”라는 텍스트를 bert-base-uncased 토크나이저로 토큰화하고, 패딩과 트렁케이션을 적용하며, PyTorch 텐서 형식으로 반환하도록 설정했습니다.

이러한 파라미터는 주로 자연어 처리(NLP) 모델을 훈련하거나 추론할 때 데이터 전처리 과정에서 많이 사용됩니다.

- 기본사용1: 토크나이저의 기능 – (1) 단어별로 다른숫자를 맵핑 (2) 처음과 끝은 각각 101, 102라는 숫자로 맵핑

토크나이저('hi hello')
{'input_ids': [101, 7632, 7592, 102], 'attention_mask': [1, 1, 1, 1]}
토크나이저('hi hi hello')
{'input_ids': [101, 7632, 7632, 7592, 102], 'attention_mask': [1, 1, 1, 1, 1]}
토크나이저('hi hi hello hello hello hi')
{'input_ids': [101, 7632, 7632, 7592, 7592, 7592, 7632, 102], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1]}

- 기본사용2: 여러개의 텍스트도 [텍스트1, 텍스트2, ... ] 꼴로 전달하면 토크나이저가 알아서 잘 처리해준다.

토크나이저(['hi hello', 'hello hello hello', 'hi hi'])
{'input_ids': [[101, 7632, 7592, 102], [101, 7592, 7592, 7592, 102], [101, 7632, 7632, 102]], 'attention_mask': [[1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1]]}

- truncation=True 의 역할 – 너무 문장이 길면 잘라내는 역할을 한다.

토크나이저('hi hello '*2)
{'input_ids': [101, 7632, 7592, 7632, 7592, 102], 'attention_mask': [1, 1, 1, 1, 1, 1]}
len(토크나이저('hi hello '*2)['input_ids']), len(토크나이저('hi hello '*2)['attention_mask'])
(6, 6)
len(토크나이저('hi hello '*300,truncation=True)['input_ids']) # 원래는 602가 나와야하는데 짤려서 512만 나옴
512

- 너무 문장이 짧아서 뭔가를 채우기도 할까? – max_length, padding, attention_mask

토크나이저('hi hello', max_length = 10, padding="max_length" )
{'input_ids': [101, 7632, 7592, 102, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 0, 0, 0, 0, 0, 0]}

6. 인공지능 (\(\star\))

A. 1단계

인공지능에 대한 이해

- 인공지능 불러오기

torch.manual_seed(43052)
인공지능 = model = transformers.AutoModelForSequenceClassification.from_pretrained(
    "distilbert/distilbert-base-uncased", num_labels=2
)
Some weights of DistilBertForSequenceClassification were not initialized from the model checkpoint at distilbert/distilbert-base-uncased and are newly initialized: ['classifier.bias', 'classifier.weight', 'pre_classifier.bias', 'pre_classifier.weight']
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.

- 인공지능의 정체? 엄청나게 많은 숫자들이 포함된 어떠한 물체 (엄청나게 많은 파라메터들이 포함된 네트워크)

인공지능
DistilBertForSequenceClassification(
  (distilbert): DistilBertModel(
    (embeddings): Embeddings(
      (word_embeddings): Embedding(30522, 768, padding_idx=0)
      (position_embeddings): Embedding(512, 768)
      (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
      (dropout): Dropout(p=0.1, inplace=False)
    )
    (transformer): Transformer(
      (layer): ModuleList(
        (0-5): 6 x TransformerBlock(
          (attention): MultiHeadSelfAttention(
            (dropout): Dropout(p=0.1, inplace=False)
            (q_lin): Linear(in_features=768, out_features=768, bias=True)
            (k_lin): Linear(in_features=768, out_features=768, bias=True)
            (v_lin): Linear(in_features=768, out_features=768, bias=True)
            (out_lin): Linear(in_features=768, out_features=768, bias=True)
          )
          (sa_layer_norm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
          (ffn): FFN(
            (dropout): Dropout(p=0.1, inplace=False)
            (lin1): Linear(in_features=768, out_features=3072, bias=True)
            (lin2): Linear(in_features=3072, out_features=768, bias=True)
            (activation): GELUActivation()
          )
          (output_layer_norm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
        )
      )
    )
  )
  (pre_classifier): Linear(in_features=768, out_features=768, bias=True)
  (classifier): Linear(in_features=768, out_features=2, bias=True)
  (dropout): Dropout(p=0.2, inplace=False)
)
인공지능.classifier.weight
Parameter containing:
tensor([[-0.0234,  0.0279,  0.0242,  ...,  0.0091, -0.0063, -0.0133],
        [ 0.0087,  0.0007, -0.0099,  ...,  0.0183, -0.0007,  0.0295]],
       requires_grad=True)
인공지능.pre_classifier.weight
Parameter containing:
tensor([[-0.0122,  0.0030,  0.0301,  ...,  0.0003,  0.0327, -0.0123],
        [-0.0041,  0.0008,  0.0169,  ..., -0.0163, -0.0117, -0.0135],
        [ 0.0020, -0.0215, -0.0021,  ...,  0.0016,  0.0102, -0.0483],
        ...,
        [-0.0026, -0.0327,  0.0099,  ...,  0.0341, -0.0184, -0.0109],
        [ 0.0088, -0.0345, -0.0011,  ...,  0.0018,  0.0172, -0.0122],
        [-0.0148,  0.0147, -0.0184,  ..., -0.0166,  0.0241,  0.0201]],
       requires_grad=True)

- 인공지능? “입력정보 -> 정리된숫자 -> 계산 -> 계산된숫자 -> 출력정보” 의 과정에서 “계산”을 담당.

  • 인공지능이 가지고 있는 숫자들은 “계산”에 사용된다.

- 입력정보에 영화에 대한 부정적 평가에 해당하는 텍스트를 넣는다면?

입력정보_원시텍스트 = "This movie was a huge disappointment."
정리된숫자_토큰화된자료 = 토크나이저(입력정보_원시텍스트,return_tensors='pt')
정리된숫자_토큰화된자료
{'input_ids': tensor([[  101,  2023,  3185,  2001,  1037,  4121, 10520,  1012,   102]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1]])}
인공지능(**정리된숫자_토큰화된자료)
SequenceClassifierOutput(loss=None, logits=tensor([[-0.1173,  0.0261]], grad_fn=<AddmmBackward0>), hidden_states=None, attentions=None)
계산된숫자_로짓 = 인공지능(**정리된숫자_토큰화된자료).logits.detach().numpy()
계산된숫자_로짓
array([[-0.11731046,  0.02610319]], dtype=float32)
출력정보_확률 = np.exp(계산된숫자_로짓)/np.exp(계산된숫자_로짓).sum() # 0일확률(=부정평가일확률), 1일확률(=긍정평가일확률)
출력정보_확률
array([[0.46420792, 0.53579205]], dtype=float32)
출력정보_확률.argmax() # 부정적 영화평가에 대한 인공지능의 예측 
1
계산된숫자_로짓.argmax() # 부정적 영화평가에 대한 인공지능의 예측 <-- 이렇게 구해도 됩니다.. 왜??
1

- 입력정보에 영화에 대한 긍정적 평가에 해당하는 텍스트를 넣는다면?

입력정보_원시텍스트 = "This was a masterpiece."
정리된숫자_토큰화된자료 = 토크나이저(입력정보_원시텍스트,return_tensors='pt')
정리된숫자_토큰화된자료
{'input_ids': tensor([[  101,  2023,  2001,  1037, 17743,  1012,   102]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1]])}
인공지능(**정리된숫자_토큰화된자료)
SequenceClassifierOutput(loss=None, logits=tensor([[-0.1080,  0.0264]], grad_fn=<AddmmBackward0>), hidden_states=None, attentions=None)
계산된숫자_로짓 = 인공지능(**정리된숫자_토큰화된자료).logits.detach().numpy()
계산된숫자_로짓
array([[-0.10802954,  0.02638793]], dtype=float32)
출력정보_확률 = np.exp(계산된숫자_로짓)/np.exp(계산된숫자_로짓).sum() # 0일확률(=부정평가일확률), 1일확률(=긍정평가일확률)
출력정보_확률
array([[0.46644613, 0.53355384]], dtype=float32)
출력정보_확률.argmax() # 긍정적 영화평가에 대한 인공지능의 예측 
1
계산된숫자_로짓.argmax() # 긍정적 영화평가에 대한 인공지능의 예측
1

- 아무숫자나 뱉어내는 듯 \(\to\) 멍청한 인공지능 (옹호: 멍청한건 당연함. 아직 학습전이니까)

- 인공지능에 대한 이해1: 인공지능은 “정리된숫자”를 입력으로 하고 일련의 계산을 거쳐서 “계산된숫자”를 출력해주는 함수라 생각할 수 있음.

- 인공지능에 대한 이해2: 인공지능은 (1) 많은숫자들과 (2) 고유의 계산방식을 가지고 있음.

  • 인공지능이 내부에 자체적으로 저장하고 있는 숫자들 “파라메터”라고 부름.
  • 인공지능은 나름의 법칙에 따라 “데이터”와 “파라메터”의 숫자들을 연산함. 즉 인공지능은 자체적으로 데이터와 파라메터를 어떻게 계산할지 알고있는데, 이러한 고유의 계산방식을 “아키텍처”라고 말함.

- 인공지능에 대한 이해3: 두개의 인공지능이 서로 다른 고유의 계산방식을 가지고 있다면 두 인공지능은 “다른 모델” 임.

- 인공지능에 대한 이해3’: 동일한 생성방식으로 만들어진 인공지능들은 모두 같은 모델임. 예를들면 아래의 인공지능1,2는 같은 모델임

인공지능1 = transformers.AutoModelForSequenceClassification.from_pretrained(
    "distilbert/distilbert-base-uncased", num_labels=2
)
인공지능2 = transformers.AutoModelForSequenceClassification.from_pretrained(
    "distilbert/distilbert-base-uncased", num_labels=2
)

- 인공지능에 대한 이해4: 두 인공지능이 같은모델이라고 해도, 항상 같은 결과를 주는건 아님. 파라메터에 따라 다른 결과를 줄 수도 있음. (예를들면 위의 인공지능1,2는 같은 모델이지만 다른 파라메터를 가지므로 다른 결과를 줌)

B. 2단계

미니배치의 이해

- 예비학습

arr = np.array([[1,2],[2,3],[3,4]])
arr
array([[1, 2],
       [2, 3],
       [3, 4]])
arr / arr.sum(axis=1).reshape(-1,1)
array([[0.33333333, 0.66666667],
       [0.4       , 0.6       ],
       [0.42857143, 0.57142857]])

- 예비개념1: 인공지능은 사실 영화평을 하나씩 하나씩 처리하지 않는다. 덩어리로 처리한다.

- 예비개념2: 그렇다고 해서 인공지능이 25000개를 모두 덩어리로 처리하는건 아니다 \(\to\) 16개씩 혹은 32개씩 묶어서 작은덩어리를 만든 후 처리한다.

  • 16, 32와 같은 숫자를 batch_size 라고 한다.
  • 16개, 32개로 모인 작은덩어리를 미니배치라고 한다.

- 16개의 입력정보를 한번에 처리

입력정보들_원시텍스트 = 데이터['train'][:16]['text']
정리된숫자들_토큰화된자료 = 토크나이저(입력정보들_원시텍스트,truncation=True,return_tensors='pt',padding=True)
계산된숫자들_로짓 = 인공지능(**정리된숫자들_토큰화된자료).logits.detach().numpy()
출력정보들_확률 = np.exp(계산된숫자들_로짓) / np.exp(계산된숫자들_로짓).sum(axis=1).reshape(-1,1)
출력정보들_확률
array([[0.47823825, 0.5217617 ],
       [0.47215328, 0.5278467 ],
       [0.4804948 , 0.5195052 ],
       [0.46609667, 0.5339033 ],
       [0.48814487, 0.5118551 ],
       [0.48004225, 0.5199578 ],
       [0.49288097, 0.50711906],
       [0.49470255, 0.5052974 ],
       [0.4941453 , 0.50585467],
       [0.49016201, 0.50983804],
       [0.495789  , 0.504211  ],
       [0.48260576, 0.51739424],
       [0.49386355, 0.5061364 ],
       [0.49013382, 0.5098662 ],
       [0.48240176, 0.5175982 ],
       [0.49301967, 0.5069803 ]], dtype=float32)
계산된숫자들_로짓.argmax(axis=1) # 인공지능의 예측
array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])

- 기억할 것: 정리된숫자들_토큰화된자료 는 모두 길이가 512임. (그렇게 되도록 패딩함)

정리된숫자들_토큰화된자료['input_ids'].shape, 정리된숫자들_토큰화된자료['attention_mask'].shape
(torch.Size([16, 512]), torch.Size([16, 512]))

- 실제 단어수

정리된숫자들_토큰화된자료['attention_mask'].sum(axis=1)
tensor([363, 304, 133, 185, 495, 154, 143, 388, 512, 297, 365, 171, 192, 173,
        470, 263])

- 패딩된단어수

512-정리된숫자들_토큰화된자료['attention_mask'].sum(axis=1)
tensor([149, 208, 379, 327,  17, 358, 369, 124,   0, 215, 147, 341, 320, 339,
         42, 249])

- 이러한 변환이 필요한 이유? 인공지능은 항상 (n,m) 차원으로 정리된 숫자들만 입력으로 받을 수 있음.

  • 왜? 사실 인공지능은 행렬계산을 하도록 설계되어있음.
  • 그래서 할수없이 padding을 하거나 truncation을 하는 것임. (실제로는 행렬이 아니지만 억지로 행렬을 만들기 위해서)

C. 3단계

동적패딩을 이해하자.

- 만약에 batch_size=4로 설정하여 처리한다면?

입력정보들_원시텍스트 = 데이터['train'][:4]['text']
정리된숫자들_토큰화된자료 = 토크나이저(입력정보들_원시텍스트,truncation=True,return_tensors='pt',padding=True)
계산된숫자들_로짓 = 인공지능(**정리된숫자들_토큰화된자료).logits.detach().numpy()
출력정보들_확률 = np.exp(계산된숫자들_로짓) / np.exp(계산된숫자들_로짓).sum(axis=1).reshape(-1,1)
출력정보들_확률
array([[0.47823825, 0.5217618 ],
       [0.47215328, 0.5278467 ],
       [0.4804948 , 0.5195052 ],
       [0.46609667, 0.5339033 ]], dtype=float32)
계산된숫자들_로짓.argmax(axis=1) # 인공지능의 예측 
array([1, 1, 1, 1])

- 정리된숫자들_토큰화된자료['input_ids'] 의 차원은 어떠할까? (4,512)

정리된숫자들_토큰화된자료['input_ids'].shape, 정리된숫자들_토큰화된자료['attention_mask'].shape
(torch.Size([4, 363]), torch.Size([4, 363]))
  • 끝의 차원이 512가 아니라 363이다.. 왜??

- 덩어리의 상태에 따라서 유동적으로 패딩 \(\to\) 이렇게 해도 잘 돌아감

입력정보들_원시텍스트 = 데이터['train'][:4]['text']
정리된숫자들_토큰화된자료 = 토크나이저(입력정보들_원시텍스트,truncation=True,return_tensors='pt',padding=True)
인공지능(**정리된숫자들_토큰화된자료)
SequenceClassifierOutput(loss=None, logits=tensor([[-0.0847,  0.0024],
        [-0.0830,  0.0285],
        [-0.0600,  0.0180],
        [-0.0919,  0.0440]], grad_fn=<AddmmBackward0>), hidden_states=None, attentions=None)
입력정보들_원시텍스트 = 데이터['train'][4:8]['text']
정리된숫자들_토큰화된자료 = 토크나이저(입력정보들_원시텍스트,truncation=True,return_tensors='pt',padding=True)
인공지능(**정리된숫자들_토큰화된자료)
SequenceClassifierOutput(loss=None, logits=tensor([[-0.0422,  0.0053],
        [-0.0646,  0.0152],
        [-0.0172,  0.0112],
        [-0.0283, -0.0072]], grad_fn=<AddmmBackward0>), hidden_states=None, attentions=None)
입력정보들_원시텍스트 = 데이터['train'][8:12]['text']
정리된숫자들_토큰화된자료 = 토크나이저(입력정보들_원시텍스트,truncation=True,return_tensors='pt',padding=True)
인공지능(**정리된숫자들_토큰화된자료)
SequenceClassifierOutput(loss=None, logits=tensor([[-0.0246, -0.0012],
        [-0.0594, -0.0200],
        [-0.0240, -0.0071],
        [-0.0836, -0.0140]], grad_fn=<AddmmBackward0>), hidden_states=None, attentions=None)
입력정보들_원시텍스트 = 데이터['train'][12:16]['text']
정리된숫자들_토큰화된자료 = 토크나이저(입력정보들_원시텍스트,truncation=True,return_tensors='pt',padding=True)
인공지능(**정리된숫자들_토큰화된자료)
SequenceClassifierOutput(loss=None, logits=tensor([[-0.0440, -0.0195],
        [-0.0469, -0.0074],
        [-0.0542,  0.0162],
        [-0.0316, -0.0037]], grad_fn=<AddmmBackward0>), hidden_states=None, attentions=None)

- 싹다 maxlen=512로 가정하고 패딩해서 돌린결과와 비교 \(\to\) 같음 \(\to\) 동적패딩이 효율적

입력정보들_원시텍스트 = 데이터['train'][:16]['text']
정리된숫자들_토큰화된자료 = 토크나이저(입력정보들_원시텍스트,truncation=True,return_tensors='pt',padding=True)
인공지능(**정리된숫자들_토큰화된자료)
SequenceClassifierOutput(loss=None, logits=tensor([[-0.0847,  0.0024],
        [-0.0830,  0.0285],
        [-0.0600,  0.0180],
        [-0.0919,  0.0440],
        [-0.0422,  0.0053],
        [-0.0646,  0.0152],
        [-0.0172,  0.0112],
        [-0.0283, -0.0072],
        [-0.0246, -0.0012],
        [-0.0594, -0.0200],
        [-0.0240, -0.0071],
        [-0.0836, -0.0140],
        [-0.0440, -0.0195],
        [-0.0469, -0.0074],
        [-0.0542,  0.0162],
        [-0.0316, -0.0037]], grad_fn=<AddmmBackward0>), hidden_states=None, attentions=None)

D. 4단계

손실(=loss)의 개념을 이해하자

- 정리된숫자들_토큰화된자료에서 labels를 추가 전달하면, 인공지능(**정리된숫자들_토큰화된자료)의 결과로 loss가 추가계산됨.

입력정보들_원시텍스트 = 데이터['train'][:4]['text']
정리된숫자들_토큰화된자료 = 토크나이저(입력정보들_원시텍스트,truncation=True,return_tensors='pt',padding=True)
#데이터['train'][:4]['label'] # 정답확인
정리된숫자들_토큰화된자료['labels'] = torch.tensor([0,0,0,0]) # 정답입력
인공지능(**정리된숫자들_토큰화된자료)
SequenceClassifierOutput(loss=tensor(0.7461, grad_fn=<NllLossBackward0>), logits=tensor([[-0.0847,  0.0024],
        [-0.0830,  0.0285],
        [-0.0600,  0.0180],
        [-0.0919,  0.0440]], grad_fn=<AddmmBackward0>), hidden_states=None, attentions=None)

- 정리를 해보자.

입력정보들_원시텍스트 = 데이터['train'][:4]['text']
정리된숫자들_토큰화된자료 = 토크나이저(입력정보들_원시텍스트,truncation=True,return_tensors='pt',padding=True)
print(f'실제정답: {데이터['train'][:4]['label']}') # 정답확인
정리된숫자들_토큰화된자료['labels'] = torch.tensor([0,0,0,0]) # 정답입력
계산된숫자들_로짓 = 인공지능(**정리된숫자들_토큰화된자료).logits.detach().numpy()
출력정보들_확률 = np.exp(계산된숫자들_로짓)/np.exp(계산된숫자들_로짓).sum(axis=1).reshape(-1,1)
print(f'인공지능의예측: {계산된숫자들_로짓.argmax(axis=1)}')
print(f'인공지능의확신정도: {출력정보들_확률.max(axis=1)}')
print(f'손실(loss): {인공지능(**정리된숫자들_토큰화된자료).loss.item():.6f}')
실제정답: [0, 0, 0, 0]
인공지능의예측: [1 1 1 1]
인공지능의확신정도: [0.5217618 0.5278467 0.5195052 0.5339033]
손실(loss): 0.746100

- loss는 작을수록 주어진 데이터에 대한 정답을 잘 맞추고 있다고 볼 수 있음. (그렇지만 학습이 잘 되었다는걸 보장하지는 못함)

텍스트0-텍스트3

print("텍스트0 -- 텍스트3")
입력정보들_원시텍스트 = 데이터['train'][:4]['text']
정리된숫자들_토큰화된자료 = 토크나이저(입력정보들_원시텍스트,truncation=True,return_tensors='pt',padding=True)
print(f'실제정답: {데이터['train'][:4]['label']}') # 정답확인
정리된숫자들_토큰화된자료['labels'] = torch.tensor(데이터['train'][:4]['label']) # 정답입력
계산된숫자들_로짓 = 인공지능(**정리된숫자들_토큰화된자료).logits.detach().numpy()
출력정보들_확률 = np.exp(계산된숫자들_로짓)/np.exp(계산된숫자들_로짓).sum(axis=1).reshape(-1,1)
print(f'인공지능의예측: {계산된숫자들_로짓.argmax(axis=1)}')
print(f'인공지능의확신정도: {출력정보들_확률.max(axis=1)}')
print(f'손실(loss): {인공지능(**정리된숫자들_토큰화된자료).loss.item():.6f}')
텍스트0 -- 텍스트3
실제정답: [0, 0, 0, 0]
인공지능의예측: [1 1 1 1]
인공지능의확신정도: [0.5217618 0.5278467 0.5195052 0.5339033]
손실(loss): 0.746100

텍스트12498-텍스트12501

print("텍스트12498 -- 텍스트12501")
입력정보들_원시텍스트 = 데이터['train'][12498:12502]['text']
정리된숫자들_토큰화된자료 = 토크나이저(입력정보들_원시텍스트,truncation=True,return_tensors='pt',padding=True)
print(f'실제정답: {데이터['train'][12498:12502]['label']}') # 정답확인
정리된숫자들_토큰화된자료['labels'] = torch.tensor(데이터['train'][12498:12502]['label']) # 정답입력
계산된숫자들_로짓 = 인공지능(**정리된숫자들_토큰화된자료).logits.detach().numpy()
출력정보들_확률 = np.exp(계산된숫자들_로짓)/np.exp(계산된숫자들_로짓).sum(axis=1).reshape(-1,1)
print(f'인공지능의예측: {계산된숫자들_로짓.argmax(axis=1)}')
print(f'인공지능의확신정도: {출력정보들_확률.max(axis=1)}')
print(f'손실(loss): {인공지능(**정리된숫자들_토큰화된자료).loss.item():.6f}')
텍스트12498 -- 텍스트12501
실제정답: [0, 0, 1, 1]
인공지능의예측: [1 1 1 1]
인공지능의확신정도: [0.52731967 0.5243837  0.51578873 0.5351162 ]
손실(loss): 0.694952

텍스트12502-텍스트12506

print("텍스트12502 -- 텍스트12506")
입력정보들_원시텍스트 = 데이터['train'][12502:12506]['text']
정리된숫자들_토큰화된자료 = 토크나이저(입력정보들_원시텍스트,truncation=True,return_tensors='pt',padding=True)
print(f'실제정답: {데이터['train'][12502:12506]['label']}') # 정답확인
정리된숫자들_토큰화된자료['labels'] = torch.tensor(데이터['train'][12502:12506]['label']) # 정답입력
계산된숫자들_로짓 = 인공지능(**정리된숫자들_토큰화된자료).logits.detach().numpy()
출력정보들_확률 = np.exp(계산된숫자들_로짓)/np.exp(계산된숫자들_로짓).sum(axis=1).reshape(-1,1)
print(f'인공지능의예측: {계산된숫자들_로짓.argmax(axis=1)}')
print(f'인공지능의확신정도: {출력정보들_확률.max(axis=1)}')
print(f'손실(loss): {인공지능(**정리된숫자들_토큰화된자료).loss.item():.6f}')
텍스트12502 -- 텍스트12506
실제정답: [1, 1, 1, 1]
인공지능의예측: [1 1 1 1]
인공지능의확신정도: [0.52180934 0.51908445 0.521884   0.5197189 ]
손실(loss): 0.652730

- 똑같이 틀려도 오답에 대한 확신이 강할수록 loss가 크다.

print("텍스트0 -- 텍스트1")
입력정보들_원시텍스트 = 데이터['train'][:2]['text']
정리된숫자들_토큰화된자료 = 토크나이저(입력정보들_원시텍스트,truncation=True,return_tensors='pt',padding=True)
print(f'실제정답: {데이터['train'][:2]['label']}') # 정답확인
정리된숫자들_토큰화된자료['labels'] = torch.tensor(데이터['train'][:2]['label']) # 정답입력
계산된숫자들_로짓 = 인공지능(**정리된숫자들_토큰화된자료).logits.detach().numpy()
출력정보들_확률 = np.exp(계산된숫자들_로짓)/np.exp(계산된숫자들_로짓).sum(axis=1).reshape(-1,1)
print(f'인공지능의예측: {계산된숫자들_로짓.argmax(axis=1)}')
print(f'인공지능의확신정도: {출력정보들_확률.max(axis=1)}')
print(f'손실(loss): {인공지능(**정리된숫자들_토큰화된자료).loss.item():.6f}')
텍스트0 -- 텍스트1
실제정답: [0, 0]
인공지능의예측: [1 1]
인공지능의확신정도: [0.5217617 0.5278467]
손실(loss): 0.744049
print("텍스트1 -- 텍스트2")
입력정보들_원시텍스트 = 데이터['train'][1:3]['text']
정리된숫자들_토큰화된자료 = 토크나이저(입력정보들_원시텍스트,truncation=True,return_tensors='pt',padding=True)
print(f'실제정답: {데이터['train'][1:3]['label']}') # 정답확인
정리된숫자들_토큰화된자료['labels'] = torch.tensor(데이터['train'][1:3]['label']) # 정답입력
계산된숫자들_로짓 = 인공지능(**정리된숫자들_토큰화된자료).logits.detach().numpy()
출력정보들_확률 = np.exp(계산된숫자들_로짓)/np.exp(계산된숫자들_로짓).sum(axis=1).reshape(-1,1)
print(f'인공지능의예측: {계산된숫자들_로짓.argmax(axis=1)}')
print(f'인공지능의확신정도: {출력정보들_확률.max(axis=1)}')
print(f'손실(loss): {인공지능(**정리된숫자들_토큰화된자료).loss.item():.6f}')
텍스트1 -- 텍스트2
실제정답: [0, 0]
인공지능의예측: [1 1]
인공지능의확신정도: [0.5278467 0.5195052]
손실(loss): 0.741695
\(\star\star\star\) 학습이 가능한 이유 (대충 아이디어만)

1. 랜덤으로 하나의 인공지능을 생성한다. (아래의 코드로 가능)

인공지능 = 인공지능생성기()

2. 하나의 미니배치를 선택한다.

3. 인공지능의 파라메터중 하나의 숫자를 선택한다. 예를들면 아래와 같은 상황이 있다고 하자.

인공지능.classifier.weight
Parameter containing:
tensor([[-0.0234,  0.0279,  0.0242,  ...,  0.0091, -0.0063, -0.0133],
        [ 0.0087,  0.0007, -0.0099,  ...,  0.0183, -0.0007,  0.0295]],
       requires_grad=True)

하나의 숫자 -0.0234를 선택한다.

4. -0.0234의 값을 아주 조금 변화시킨다. 예를들면 -0.0233, -0.0235 와 같은 숫자로 바꾼다. 2에서 고정된 미니배치에 대하여 -0.0234, -0.0233, -0.0235 에 대한 loss를 계산해보고 비교한다.

  • -0.0234이 최저 loss라면? 값을 안바꾸는게 좋겠음.
  • -0.0233이 최저 loss라면?? 값을 -0.0233으로 바꿈.
  • -0.0235이 최저 loss라면?? 값을 -0.0235으로 바꿈.

5. 다음은 다른 모든 파라메터에 대하여 3-4을 반복한다. (과정을 반복할수록 loss는 작아지겠죠, 즉 인공지능은 정답을 잘 맞추겠죠)

6. 다른 미니배치에 대하여 2-5를 반복한다.

- 인공지능의 학습은 마법같은 신비한 현상이 아니고, 극한의 노가다를 통해 얻어지는 산물일 뿐이다.