[웹 문서 뼈대를 만드는 HTML]

- 웹 브라우저 창에 웹 문서의 내용을 보여주는데 필요한 약속

- 제목, 본문 등의 웹 요소를 알려주는 역할

- HyperText Markup Language

 

 

 

[웹 문서를 꾸미는 CSS]

- HTML로 만든 내용을 사용자가 알아보기 쉽게 꾸미거나 배치

- CSS를 이용해 웹요소를 적절하게 배치, 강조 가능

- Cascading Style Sheet

 

 

[사용자 동작에 반응하는 JS]

- 사용자의 동작(스크롤, 클릭)에 반응(동적인 효과)

- React같은 JS Framework를 위해 사용.

- JavaScript

 

 

 

 

 

[Frontend 개발을 위한 tech]

Javascript의 framework

 

[Backend 개발을 위한 tech]

 

 

 

 

 

 

 

 

 

 

 

Text  Classification

- Text Classification은 NLP에서 매우 일반적인 작업

- 말 그대로 text를 분류하기 위한 작업

- Sentiment Analysis 또한 text classificaton에 포함된다.

 

 

[Text Classification with DistilBERT and 🤗]

DistilBERT의 경우, BERT에 비견되는 성능을 내지만 훨씬 작고 효율적이라는 강점이 존재한다.

https://github.com/V2LLAIN/NLP/blob/main/Text_Classification/Sentiment_Analysis.ipynb

 

GitHub - V2LLAIN/NLP

Contribute to V2LLAIN/NLP development by creating an account on GitHub.

github.com

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

NLP 의 여러 task

1. Text Classification

- sentiment analysis

 

2. Named Entity Recognition (NER)

 

 

3. Question Answering

- extractive question answering

 

4. Text Summarization

 

 

5. Text Translation

 

 

6. Text Generation

 

 

 

 

 

 

def scaled_dot_product_attention(Q, K, V):
  dim_k = Q.size(-1)
  scores = torch.bmm(Q, K.transpose(1, 2)) / sqrt(dim_k)
  weights = F.softmax(scores, dim=-1)
  return torch.bmm(weights, V)

 

from transformers import AutoConfig
from transformers import AutoTokenizer
import torch.nn.functional as F

model = "distilbert-base-uncased"
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
tokenizer = AutoTokenizer.from_pretrained(model).to(device)

inputs = tokenizer(text, return_tensors='pt', add_special_tokens=False)
config = AutoConfig.from_pretrained(model)

 

class Positional_Embedding(nn.Module):
  def __init__(self, config):
    super().__init__()
    self.token_embeddings = nn.Embedding(config.vocab_size, config.hidden_size)
    self.position_embeddings = nn.Embedding(config.max_position_embeddings, config.hidden_size)
    self.layer_norm = nn.LayerNprm(config.hidden_size, eps=1e-12)
    self.dropout = nn.Dropout()

  def forward(self, input_ids):
    seq_len = input_ids.size(1)
    position_ids = torch.arange(seq_len, dtype=torch.long).unsqueeze(0)
    token_embeddings = self.token_embeddings(input_ids)
    position_embeddings = self.position_embeddings(position_ids)

    embeddings = token_embeddings + position_embeddings
    embeddings = self.layer_norm(embeddings)
    embeddings = self.dropout(embeddings)
    return embeddings​

 

class Attention_Head(nn.Module):
  def __init__(self, embed_dim, head_dim):
    super().__init__()
    self.q = nn.Linear(embed_dim, head_dim)
    self.k = nn.Linear(embed_dim, head_dim)
    self.v = nn.Linear(embed_dim, head_dim)

  def forward(self, hidden_state):
    attention_outputs = scaled_dot_product_attention(
        self.q(hidden_state), self.k(hidden_state), self.v(hidden_state))
    return attention_outputs



class Multi_Head_Attention(nn.Module):
  def __init__(self, config):
    super().__init__()
    embed_dim = config.hidden_size
    head_num = config.num_attention_heads
    head_dim = embed_dim // head_num

    self.heads = nn.ModuleList(
        [Attention_Head(embed_dim, head_dim) for _ in range(head_num)]
    )
    self.output_linear = nn.Linear(embed_dim, dembed_dim)

  def forward(self, hidden_state):
    x = torch.cat([h(hidden_state) for h in self.heads], dim=-1)
    x = self.output_linear(x)
    return x

 

class FeedForward(nn.Module):
  def __init__(self, config):
    super().__init__()
    self.linear_1 = nn.Linear(config.hidden_size, config.intermediate_size)
    self.linear_2 = nn.Linear(config.intermediate_size, config.hidden_size)
    self.gelu = nn.GELU()
    self.dropout = nn.Dropout(config.hidden_dropout_prob)

  def forward(self, x):
    x = self.linear_1(x)
    x = self.gelu(x)
    x = self.linear_2(x)
    x = self.dropout(x)
    return x

 

class Transformer_Encoder(nn.Module):
  def __init__(self, config):
    super().__init__()
    self.embeddings = Embeddings(config)
    self.layers = nn.ModuleList([TransformerEncoder(config)
                                for _ in range(config.num_hidden_layers)])
    self.layer_norm_1 = nn.LayerNorm(config.hidden_size)
    self.layer_norm_2 = nn.LayerNorm(config.hidden_size)
    self.attetion = Multi_Head_Attention(config)
    self.feed_forward = FeedForward(config)

  def forward(self, x):
    x = self.embeddings(x)
    for layer in self.Layers:
      x = Layer(x)
    hidden_state = self.layer_norm_1(x)
    x = x + self.attention(hidden_state)
    x = x + self.feed_forward(self.layer_norm_2(x))
    return x

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 What is different between Pytorch and Tensorflow?

Pytorch Tensorflow(keras) 생성 / 구현
nn.Linear keras.layers.Dense Dense
nn.Module keras.layers.Layer 모델구성 기반클래스
nn.Dropout keras.layers.Dropout Dropout
nn.LayerNorm keras.layers.LayerNormalization Layer Normalization
nn.Embedding keras.layers.Embedding Embedding
nn.GELU keras.activations.gelu GELU 활성화 함수
nn.Bmm tf.matmul Batch 행렬곱셈
model.forward model.call 모델 정방향 패스

 

 

모델 정의 방법

tensorflow.keras

tf.debugging.set_log_device_placement(True)

class MyModel(tf.keras.Model):
    def __init__(self):
        super(MyModel,self).__init__()
        
        self.flatten = tf.keras.layers.Flatten(input_shape=(28,28))
        self.fc1 = tf.keras.layers.Dense(512,activation='relu')
        self.dropout = tf.keras.layers.Dropout(0.2)
        self.fc2 = tf.keras.layers.Dense(10, activation='softmax')
        
    def call(self,inputs):
        x = self.flatten(inputs)
        x = self.fc1(x)
        x = self.dropout(x)
        x = self.fc2(x)
        return x
        
model = MyModel()

cf) tensorflow는 tf.debugging.set_log_device_placement(True) 코드를 통해서 어디에 할당되어있는지를 확인할 수 있다.


PyTorch

# Get cpu or gpu device for training.
device = "cuda" if torch.cuda.is_available() else "cpu"

# Define model
class NeuralNetwork(nn.Module):
    def __init__(self):
        super(NeuralNetwork, self).__init__()
        
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10)
        )

    def forward(self, inputs):
        x = self.flatten(inputs)
        logits = self.linear_relu_stack(x)
        return logits

model = NeuralNetwork().to(device)


모델 정의 시 차이점을 비교해 보자.

  GPU 할당 상속 클래스 fully connected layer 순방향 계산 함수명 모델 인스턴스 할당
PyTorch cuda.is_available() nn.Module nn.Linear call() model.to(device)
Tensorflow 자동 keras.Model keras.layers.Dense forward() model()



PyTorch는 GPU 탑재 여부를 담은 변수 device를 모델 객체에 할당할 때 포함시킵니다.
model = NeuralNetwork().to(device)


모델 컴파일 방법

tensorflow.keras

  • tensorflow는 정의한 모델 객체의 complile 메서드를 통해서 optimizer , loss function , metrics를 정의.
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])


PyTorch

  • PyTorch는 optimizer , loss function 변수에 저장
  • 저장된 변수를 학습 함수에서 직접 사용.
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)





모델 학습 방법

차이점은 모델 학습 부분에서 가장 큰 차이를 보인다.

tensorflow.keras

  • tensorflow 는 fit() 함수로 간단하게 학습을 사용할 수 있다.
model.fit(train_images, train_labels, epochs=5)


PyTorch

  • PyTorch는 함수 속 for 문을 정의하여 모델 학습(train)과 검증(test) 코드를 구현해야 합니다.
    PyTorch가 구현하기 어렵지만 더욱더 직관적이고 학습하기 용이하다.
  • optimizer.zero_grad() : Gradient를 초기화합니다.
    loss.backward() : Loss를 기준으로 Gradient를 저장합니다.
    optimizer.step() : 파라미터(weight) 업데이트.
def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)
    model.train()
    for batch, (X, y) in enumerate(dataloader):
        X, y = X.to(device), y.to(device)

        # Compute prediction error
        pred = model(X)
        loss = loss_fn(pred, y)

        # Backpropagation
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        if batch % 100 == 0:
            loss, current = loss.item(), batch * len(X)
            print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")
         
def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    model.eval()
    test_loss, correct = 0, 0
    with torch.no_grad():
        for X, y in dataloader:
            X, y = X.to(device), y.to(device)
            pred = model(X)
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
    test_loss /= num_batches
    correct /= size
    print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")
    
epochs = 5
for t in range(epochs):
    print(f"Epoch {t+1}\n-------------------------------")
    train(train_dataloader, model, loss_fn, optimizer)
    test(test_dataloader, model, loss_fn)
print("Done!")




 

 

Ex) CIFAR100 with Pytorch

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms

# Device 설정
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# CIFAR-100 데이터셋 불러오기
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

trainset = torchvision.datasets.CIFAR100(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=128, shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR100(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=100, shuffle=False, num_workers=2)

# 신경망 모델 정의
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Conv2d(3, 32, 3, padding=1)
        self.relu1 = nn.ReLU()
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(32, 64, 3, padding=1)
        self.relu2 = nn.ReLU()
        self.fc1 = nn.Linear(64 * 8 * 8, 512)
        self.relu3 = nn.ReLU()
        self.fc2 = nn.Linear(512, 100)
    
    def forward(self, x):
        x = self.pool(self.relu1(self.conv1(x)))
        x = self.pool(self.relu2(self.conv2(x)))
        x = x.view(-1, 64 * 8 * 8)
        x = self.relu3(self.fc1(x))
        x = self.fc2(x)
        return x

model = CNN().to(device)

# 손실 함수 및 최적화 알고리즘 정의
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)

# 모델 학습
num_epochs = 100

for epoch in range(num_epochs):
    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        inputs, labels = data[0].to(device), data[1].to(device)
        optimizer.zero_grad()
        
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        
        running_loss += loss.item()
        if i % 100 == 99:
            print(f'Epoch [{epoch+1}/{num_epochs}], Step [{i+1}/{len(trainloader)}], Loss: {running_loss/100:.4f}')
            running_loss = 0.0

print("Training finished!")

# 테스트 데이터셋을 사용하여 모델 평가
correct = 0
total = 0

with torch.no_grad():
    for data in testloader:
        images, labels = data[0].to(device), data[1].to(device)
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

accuracy = 100 * correct / total
print(f'Accuracy on test set: {accuracy:.2f}%')

 

 

 

 

[Transformer] : with new attention "Self-Attention"

Attention is all you need[Vaswani2017; https://arxiv.org/abs/1706.03762]

 

Attention Is All You Need

The dominant sequence transduction models are based on complex recurrent or convolutional neural networks in an encoder-decoder configuration. The best performing models also connect the encoder and decoder through an attention mechanism. We propose a new

arxiv.org

위의 논문에서는 Convolution, Recurrent 등의 신경망의 구성요소를 모두 제거하고

"오로지 Attention만으로 신경망을 구현한다."

 

cf. 문맥 고려 임베딩(Contextualized Embedding)은 transformer가 개발되기 전, ELMo같은 언어모델에서 모든 토큰 임베딩을 비율을 달리해 통합하여 문맥을 내포하도록 하는 방법이다.

 

transformer가 한번에 처리하기에 이로 인해 순환신경망이 갖는 bottleneck문제를 해결할 수 있다.

🧐  기본적인 Architecture

<Encoder>
단어는 embedding을 통해 d차원의 embedding벡터로 표현된다. (논문에서는d= 512를 사용)
Inputs
라는 부분에 입력이 되며, 이때 Sequential 구조가 아님을 알 수 있다.
즉, transformer는 모든 단어를 "한번에 입력"한다.
이렇게 "한번에 입력"받는 방식은 "Self-Attention"을 가능하게 한다.

{Positional Encoding}
단어의 위치정보를 보완하기 위해 positional encoding을 통해 "embedding벡터에 위치정보를 더한다".

{MHA : Multi-head Attention}
Encoder에 MHA(Multi-Head Attention)층 FF(Feed Foward)층이 존재.
이때, MHA층 "Self-Attention"을 수행한다!
- multi-head라는 말처럼 h개 head가 독립적으로 self-attention을 수행, 결과를 결합한다.
- MHA층의 출력은 FF층의 입력이 된다. (MHA->FF)
- 또한, FF(Feed Forward)층은 FC(Fully-Connected) layer로 이루어진다.

MHA층과 FF층 모두 Add&Norm을 적용한다.
- AddShortcut Connection
- Normlayer Normalization을 의미한다.
- Nx는 Encoder block이 N개 연결되었다는 것을 의미한다. (논문에서는 N=6을 사용)



<Decoder>
Encoder처럼 단어 embedding과 Positional Encoding을 통해 변환된 단어들이 "한번에 입력"된다.
예를들어, 한국어문장은 Inputs에, 영어문장은 Outputs에 입력된다.

- Decoder에는 ~에 표시된 2개의 MHA층 FF층이 존재하며 출력에 Add&Norm을 적용한다.
- 이후 출력층은 "softmax"를 통해 확률벡터를 출력하며, 이런 Decoder block이 N개 연결되어 있다.

~은 3개의 MHA층으로 Attention을 담당하는 핵심요소이다.
-  Encoder에서 입력문장을 구성하는 단어간의 Attention을 처리한다.
같은 문장을 구성하는 단어간의 Attention이기에 이를 "Self-Attention"이라 부른다.

- ② Masked MHA의 의미는 다음과 같다.
Decoder는 i일 때, 1, 2, ..., i-1까지만 관찰해야한다.
그렇기에 Mask를 사용해 i+1, i+2, ... 등을 감추어야(masked) 한다.


-  Encoder가 처리한 입력과 Decoder가 처리하고있는 출력사이의 Attention을 다룬다.
따라서  "Self-Attention이 아니다!!"
이때, Encoder의 state벡터는 key와 value이고
Decoder의 state는 query로 작용하며 Encoder와 Decoder는 의 MHA를 통해 정보를 교환한다.


 로 표시한 MHA(Multi-Head Attention)층의 "Self-Attention"은 transformer의 main idea이다.

이제 전반적인 Transformer의 Architecture를 보았으니 좀 더 세부적으로 Encoder와 Decoder의 동작에 대해 설명하겠다.

 

 

 

 

 

 

 

[Encoder의 동작과정]

🧐  Input Embedding. &. Positional Encoding
encoder는 입력문장의 단어를 임베딩 후 d_model차원의 벡터로 변환한다. (논문에서는 d=512로 설정)
또한, 모든 단어를 "한번에 입력"하기 위해 문장을 T×d_model 크기의 S 행렬로 표현한다. (이때, T는 단어의 개수이다.)

행렬 S의 모든 행이 동시에 처리되서 순서정보를 신경망이 얻지 못할 가능성을 배제하기 위해 위치정보를 표현한 행렬 P S행렬에 더해 X행렬을 만들고 X를 신경망에 입력한다. 이때, 3가지 행렬 모두 같은 size를 갖는다.
이 과정을 "Positional Encoding"이라 한다.
위치행렬 P를 생성하는 여러 방법 중, 아래 식과 같은 sin, cos함수를 사용한다.




🧐  Self-Attention
X행렬에 대해 의 MHA로 "Self-Attention"을 수행한다.


Self-Attention을 구현하는 여러 방법 중 transformer architecture를 소개한 "Attention is All You Need"논문에서 다룬 "Scaled dot-product Attention"이 가장 일반적이며 이 메커니즘은 4단계로 구현된다.

 - 각 token embedding을 Q, K, V 3개의 벡터로 투영

 - Attention Score 계산: similarity function를 사용해 Q와 K가 얼마나 관련되는지 계산
   이때, QK의 dot곱 값이 커지면 비슷하다는 것을 의미한다.
   또한, n개의 input token이 있는 sequence일때, 크기가 n×n인 attention matrix가 생성된다.

 - Attention weight를 계산: 일반적으로 dot곱은 임의의 큰 수를 생성→ 훈련과정이 불안정해진다.
  이를 처리하기 위해 Attention score에 scaling factor(인자)를 곱해 분산을 정규화하고 softmax를 적용해 모든 열 합이 1이 되게 한다.
  이를 통해 만들어진 n×n행렬에는 attention weight wij가 넣어진다.

  - token embedding update, attention weight가 계산되면 벡터 v를 곱한다. 




"Self-Attention의 본질"
이때, query는 확장이 필요한데, 하나의 벡터 q로 표현하는 Bahdanau attention과 달리 Self-Attention에서는 query가 T개의 행을 갖는 행렬 Q가 되기에 확장된 query행렬은 다음과 같이 나타내며, 이때 Q, K, V는 모두 T×d_model크기의 행렬이고 이 식의 결과인 C도 T×d_model행렬이다.
"Self-Attention 구현의 가장 단순한 방법"은 바로 Q, K, V를 모두 X로 설정하고 위의 식을 적용하는 것이다.
하지만 transformer는 이런 단순한 방법이 아닌 query, key, value를 가중치 행렬 WQ, WK, WV를 이용해 X를 변환해 사용하며, 이때, QKT는 √d_key로 나누어 정규화를 거친다. 
이를 식으로 나타내면 아래와 같다.
X:  T × d_model 행렬
WQ:  d_model × d_key 행렬
WV:  d_model × d_value 행렬
Q, K: T × d_key 행렬
V:  T × d_value 행렬
(보통 d_model > d_value, d_key 로 설정한다.)
🧐  Multi-Head Attention

Architecture에서 로 표시한 MHA에는 여러 head가 있으며 각 head는 고유한 transform matrix WQ, WK, WV를 갖고 softmax식을 이용해 "Self-Attention"을 독립적으로 수행해 상호보완한다.
즉, 여러 head로 self-attention을 진행, 결과를 결합해 성능향상을 진행시킨다.

따라서 MHA의 동작을 아래와 같은 식으로 정의한다.


예를 들어보자.


이후 MHA를 통과한 C행렬은 Add&Norm층을 통과하는데, Skip connection과 Layer Normalization을 진행한다.
- Skip-connection: MHA층의 출력특징맵 C와 입력특징맵 X를 더하는 연산
- Layer Normalization: mini-batch를 구성하는 sample별로 특징맵의 분포가 N(0,1)이 되게하는 방법이다.
🧐 position-wise FeedForward layer
MHA에서 사용된 아래 식에 따라 출력한 X'을 입력으로 받아 X''을 출력한다.
아래 X''을 구하는 식을 보면 MLP를 구하는 방법처럼 linear transformation을 사용하는 것을 알 수 있다.
X X''은 모두 encoder의 최초 입력 X와 같이 Txd의 행렬이다.
이는 transformer가 신경망을 흐르는 tensor의 모양이 유지시켜 행렬연산이 원활히 이루어짐을 알 수 있다.


position-wise FF라 부르는 이유??

 

 

 

[Decoder의 동작과정]

encoder와 거의 비슷하게 동작하기에 다른 부분에 주안점을 두어 설명을 진행한다.



🧐  Masked Multi-Head Attention 층

Decoder에 로 표시된 Masked MHA층은 행렬일부를 마스크로 가린다는 점을 제외하면 의 MHA층과 같다. 
i = 1일 때, <SOS>를 입력하면 모델은 'That'을 예측하고
i = 2일 때, 'That'을 보고 'can't'를 예측하는 이런 방식을 재귀적으로 수행하는 자귀회귀방식을 적용해 언어번역을 진행한다.

다만, 학습단계는 추론단계와 상황이 다른데,
- 예측단계는 출력문장을 모른채 동작하지만
- 학습단계는 입출력문장이 모두 주어지고 자귀회귀방식을 사용가능하다.
(다만 교사강요; teacher forcing방식을 주로 사용한다.)
이는 잘못 예측했을 경우, 이후 단어는 계속 틀릴 가능성이 높기 때문이다.
교사강요에서는 '<SOS> That can't turn red by itself <EOS>'를 decoder에 한꺼번에 입력한다.


transformer도 교사강요로 학습을 수행하는데, 예측단계의 자기회귀를 모사한 "Masking"을 사용한다.


Masked MHA층은 이 행렬의 대각윗행렬의 모든 원소를 무한대에 가까운 음수로 설정하면, 이전에 나온 단어들에만 주목할 수 있게 되는데, 예를 들어보면 아래와 같다.





🧐 Encoder와 연결된 Multi-Head Attention 층 (Not Self-Attention)
위의 그림에서 으로 표시된 MHA층은 encoder와 decoder가 상호작용한다.
①: 이 MHA층은 입력문장내의 단어간의 Attention을 처리하는 "Self-Attention"이다.
②: 이 MHA층은 출력문장내의 단어간의 Attention을 처리하는 "Self-Attention"이다.
③: 이 MHA층은 Decoder로 입력된 문장의 단어가 Encoder로 입력된 문장의 단어에 주목할 정보를 처리한다.
"Self-Attention"이라는 점만 제외하고 모든 연산은  의 MHA층과 동일하다.

에서의 식은 "Self-Attention"을 처리하기에 같은 문장에서 구한 행렬 X만 존재한다.
∴ query와 key, value 계산 시 모두 X를 사용한다.

하지만 의 MHA층은 encoder의 X와 decoder의 X가 존재한다.
이를 구별하기 위해 X_enc, X_dec라 할 때,  key와 value는 X_enc를 사용하고 query는 X_dec를 사용한다.
이를 통해 query가 key와 value에 주목하는 정도를 계산할 수 있으며 아래와 같이 나타낼 수 있다.
여기서 MHA층의 출력 행렬은 Architecture에서 볼 수 있듯 Add&Norm층, FC층, Add&Norm층을 거친 후 Linear와 softmax로 구성된 출력층으로 전달된다.

 

 

 

[Transformer의 주요 도전과제]

언어
 - pretrain이 대부분 영어로 되어 data가 소량이거나 거의 없을 때


Data 가용성
- labeling된 data가 없거나 소량인 경우


긴 문서 처리
- Self-Attention의 경우, text길이가 문단정도될 때, 잘 동작
- 문서와 같이 긴 text는 cost가 많이 든다.


불투명성
- 다른 Deep Learning모델과 마찬가지로 Black-Box모델이다.
- 즉, 모델의 예측이유를 설명하기 어렵거나 불가하다.


편향
- 인터넷의 text data로 pretrain을 진행하기에 data에 있는 편향이 모델에 그대로 전이될 수 있다.

 

 

[구현을 통한 예시 증명과정]

import scipy.special as sp

# Self-Attention(single_head)
def self_attention(X, Wq, Wk, Wv, d_key):
    Q = np.matmul(X, Wq)
    K = np.matmul(X, Wk)
    V = np.matmul(X, Wv)
    
    QK = np.matmul(Q, np.transpose(K))
    a = sp.softmax(QK / np.sqrt(d_key), axis=1) # attention vector
    c = np.matmul(a, V) # context vector
    
    return Q, K, V, a, c
    
    
    X = [[0.0, 0.6, 0.3, 0.0],
     [0.1, 0.9, 0.0, 0.0],
     [0.0, 0.1, 0.8, 0.1],
     [0.3, 0.0, 0.6, 0.0],
     [0.0, 0.1, 0.0, 0.9]]
     

d_key = 2

Wq_1 = [[1,0],
      [1,0],
      [0,1],
      [0,3]]

Wk_1 = [[0,1],
      [1,0],
      [1,0],
      [0,2]]

Wv_1 = [[1,2],
      [0,1],
      [1,0],
      [0,0]]
      
Wq_2 = [[0,1],
      [0,3],
      [1,0],
      [1,0]]

Wk_2 = [[1,0],
      [1,2],
      [1,0],
      [0,1]]

Wv_2 = [[1,1],
      [0,0],
      [1,1],
      [1,0]]
Q1,K1,V1,A1,C1 = self_attention(X, Wq_1, Wk_1, Wv_1, d_key)
Q2,K2,V2,A2,C2 = self_attention(X, Wq_2, Wk_2, Wv_2, d_key)
C_concat = np.concatenate([C1, C2], axis=1)
Wo = [[0.1, 0.3, 0.5, 0.2],
      [0.1, 0.1, 0.0, 0.2],
      [0.2, 0.1, 0.6, 0.3],
      [0.5, 0.3, 0.1, 0.0]]
      
      
C = np.round(np.matmul(C_concat, Wo), 4)
C
array([[0.2738, 0.2756, 0.452 , 0.2934],
       [0.2362, 0.2629, 0.4152, 0.2847],
       [0.3769, 0.3007, 0.5142, 0.295 ],
       [0.3909, 0.3305, 0.5584, 0.3299],
       [0.3374, 0.2203, 0.412 , 0.216 ]])

 

 

 

 

 

 

가장 유명한 transformer architecture 모델로 다음 3가지를 꼽을 수 있다.

[GPT  &  BERT  &  BART] 

[GPT] - Decoder유형_(autoregressive attention)

- Thanks for lunch, I had a ... 같은 "시작 text가 주어지면 가장 가능성 있는 다음단어를 예측"하는 식으로 시퀀스를 자동완성

- transformer architecture의 decoder부분만 사용하고 ULMFiT같은 언어 모델링 방법을 사용

- GPT가 pretrain한 BookCorpus dataset은 도서 약 7000권으로 구성

 

[BERT] - Encoder유형_(bidirectional attention)

- text classification, 개체명 인식과 같은 "text sequence input을 풍부한 수치적 표현으로 변환"하는 task에 잘 맞는다.

- transformer architecture의 encoder부분을 사용하고 masked language modeling이라는 특별한 언어 모델링 방법을 사용하며 GPT와 달리 BookCorpus는 물론, Wikipedia로도 pretrain을 진행하였다.

ex) I looked at my [MASK] and saw that [MASK] was late

- 변종: RoBERTa, DistilBERT, ...

 

 

[BART,  T5] - Encoder-Decoder유형

- 한 text sequence를 다른 sequence로 mapping하는 복잡한 모델링에 사용하는 유형으로 기계번역요약작업에 적합하다.

 

 

 

 

 

 

 

 

[Hugging Face Transformers] 

서로 호환되지 않는 framework(e.g Tensorflow, Pytorch)를 사용해 model을 배포하면 자신의 application에 포킹하기 쉽지 않았고, Hugging Face(🤗)의 트랜스포머로 단일화된 API를 구축하였다.

 허깅페이스 라이브러리는 다음 3가지 Deep Learning framework를 지원한다. 추가적으로 프레임워크 전환이 용이하다.

 - Pytorch

 - Tensorflow

 - JAX

자연어의 경우, 단어가 나타나는 순서가 중요해서 시간개념이 중요하다.

[RNN LSTM] _ NLP

🧐  RNN (Recurrent Neural Network)
일반적으로 i의 단어는 그 이전에 발생한 i-1개 단어와 상호작용한다.
RNN



🧐  Long Term Dependency과  LSTM(Long Short-Term-Memory)

Long Range Dependency : RNN은 시간을 처리할 수 있는 능력을 갖추지만 길이가 긴 sample에는 한계가 있다.
hi는 1, 2, ..., i 순간에 발생하는 단어의 정보가 혼합되기에 오래된 단어의 정보는 희미해진다.
즉, 앞쪽단어와 멀리있는 단어가 밀접하게 상호작용하는 long-range dependency를 제대로 처리하지 못하는 문제가 발생한다.

LSTM : RNN을 개조해 long-term dependency를 처리하는 능력을 강화한다.
input과 output을 열거나 막는 gate를 두어 선별적으로 기억하는 기능으로 여닫는 정도를 조절한다.
이때, 여닫는 정도는 학습으로 알아낸 가중치로 결정된다.

 

 

[seq2seq ] _ NLP

🧐  seq2seq

seq2seq : Sequence to Sequence[Sutskever2014; https://arxiv.org/abs/1409.3215]의 혁신성은 "가변길이의 문장을 가변길이의 문장으로 변환"할 수 있다는 것이다.

ex) 한국어→영어로 번역 시, 둘의 문장길이가 달라 seq2seq model이 필요하다.
- 학습 시 Decoder의 input부분과 output부분이 모두 동작한다. 
즉, 정답에 해당하는 출력을 알려주는 교사강요(teacher forcing)방법을 사용한다.

- 예측 시 정답을 모르기 때문에  위의 회색표시한 input 부분을 제외하고 자기회귀(auto-regressive) 방식으로 동작한다.
자기회귀에서 <SOS>가 입력되면 첫 단어 'That'을 출력하고 'That'을 보고 그 다음 둘째 단어 'can't'를 출력한다.
즉, 이전에 출력된 단어를 보고 현재단어를 출력하는 일을 반복하며, 문장끝을 나타내는 <EOS>가 발생하면 멈춘다.


- 한계 : 가장 큰 문제는 encoder의 마지막 hidden state만 decoder에 전달한다는 점이다.
그림에서 보면 h5만 decoder로 전달된다.
따라서 encoder는 마지막 hidden state에 모든 정보를 압축해야하는 부담이 존재한다.

 

 

 

[query-key-value로 계산하는 attention] _ NLP

🧐  query-key-value

QKV : attention을 계산하는 방법을 여러가지인데, 최근에는 query가 key와 유사한 정도를 측정하고 유사성 정보를 가중치로 사용해 value를 가중합하는 방법을 주로 사용한다.

Non-local Neural Networkex) f(xi, xj)의 xi와 xj가 query와 key에 해당하고, g(xj)가 value에 해당한다.


query-key-value로 attention 계산하기
query와 key의 유사도 벡터 s는 벡터의 내적으로 계산한다 가정하며
attention 벡터 a는 유사도벡터 s에 softmax를 적용한 벡터이다.
이때, s = (s1, s2, s3, s4)이고 a = (0.1895, 0.1843, 0.4419, 0.1843)이 된다.

이제 a를 가중치로 사용해 value의 가중합을 구하면 context 벡터 c
c = 0.1895*(1 2 5) + 0.1843*(1 1 5) + 0.4419*(3 2 4) + 0.1843*(6 1 2)
   = (2.8053  1.6314  4.0052).

벡터의 차원을 d라 하면, q는 1xd행렬이고 K V nxd행렬이다.
이때, n은 key와 value가 갖는 벡터의 개수이다.

 

 

 

[seq2seq  with Attention] _ NLP

seq2seq는 입력문장의 모든 정보를 encoder의 마지막 hidden state h5에 압축해 넣어야하는 부담이 있다.
Bahdanau는 아래 식의 attention을 이용해 decoder가 encoder의 모든 state(
h1, h2, ... , h5)에 접근할 수 있게 허용하여 성능을 향상한다.[Bahdanau2014; https://arxiv.org/abs/1409.0473]


🧐 Bahdanau Attention
위의 seq2seq의 경우, decoder는 i=6일 때, encoder가 i=2일 때, 단어 '저절로'에 주목해야 'itself'라는 단어를 제대로 생성할 수 있다.
decoder가 i=6일 때, (.01  .9  .02  .03  .04)처럼 2번째 원소값이 큰 attention vector a를 생성하면 encoder의 두번째 단어 '저절로'에 더 주목할 수 있어 성능이 향상될 수 있다.

- 이에 대해 query, key, value가 무엇이 되어야 위와 같이 될 수 있는지 살펴보자.

<Attention Map>

 

 

'A.I > 자연어 처리' 카테고리의 다른 글

self.NLP(3-2). Transformer with pytorch  (0) 2023.06.30
self.NLP(3-1). Pytorch 기본설명.  (0) 2023.06.29
self.NLP(3). Transformer & etc.  (0) 2023.06.29
self.NLP(1). tokenization  (2) 2022.12.29
self.NLP(0). 자연어 처리란?  (0) 2022.12.28

1.4 소거법과 A = LU

핵심: rank 1인 행렬의 관점에서 소거법을 살펴보는 것.
즉, 기존 행렬인 A는 rank = 1인 행렬들의 합으로 볼 수 있다.

ex.

 

 

1.4 .1 소거법을 이용한 Ax = b의 풀이

R3공간에서 세 평면이 만나는 것을 시각화하는 것은 쉽지 않다.
Rn공간에서 초평면(hyperplane) n개가 한 점에서 만남을 시각화 하는 것도 쉽지 않다.

But! 열벡터의 일차결합은 훨씬 쉽다!!

 - 행렬 A에서는 반드시 3 or n개의 일차독립인 벡터가 있어야 한다.
 - 이 열들은 반드시 R3의 동일한 평면 or Rn의 동일한 초평면에 있지 않아야 한다.
이를 아래와 같은 문장으로 해석가능하다.
일차독립인 열  Ax = 0의 유일해는 영벡터 x = 0이다.

즉, 일차독립모든 열에 0을 곱해야하는 경우열들의 결합이 0벡터가 됨을 의미한다.

if, 해가 유일: 열벡터의 일차결합이 b가 되는 유일한 결합을 소거법으로 Ax = b의 해를 구할 수 있다.




이때, 소거법은 열 하나하나에 순서대로 적용상삼각행렬 U를 찾을 때까지 계속 진행한다.

1행은 변하지 않는다.
이후 1행에 수를 곱해 A의 2, 3, 4행에서 곱한 결과를 뺀다.

다만, 곱셈과 덧셈을 분리하는 이런류의 계산은 높은 계산량을 요구한다.
따라서 이를 위한 해결책이 바로 A = LU이다.

 

 

 

1.4 .2  A = LU 분해

소거법은 행렬 A를 A = LU라는 L(하삼각행렬)과 U(상삼각행렬)의 곱으로 분해하는 것이다.

 

 

 

1.4 .3  Ax = b의 해법

Ax = b의 해를 구하려면 이 방정식 양변에 동일한 변환을 가해야 한다.

직접적인 방법: b를 열에 추가한 행렬 [A  b]를 다루는 것
A에 수행하는 소거법(AL⁻¹ = U)의 단계를 b에도 똑같이 적용한다.
 - [A  b] = [LU  b]에서 시작해 소거법을 적용하면
 - [U  L¹b] = [U  c] 즉, Ux = c를 얻는다.

 Ax = b는 Lc = b와 Ux = c로 나누어 진다.
소거법으로 c를 얻은 후 대입법으로 x를 구한다.
즉, x = U¹c = U¹L¹b = A¹b

 

 

 

 

1.4 .4 행 교환(치환)

모든 n×n 가역행렬(invertible matrix) A는 PA = LU를 만족한다.
이때, P는 치환행렬이다.


Ex.

 

 

cf. Cholesky's Decomposition  (for. symmetric matrix)

  1. A가 대칭행렬 이다.
  2. 행렬식의 값이 Positive 이다

    위의 조건에 대해 LU분해의 결과, 아래와 같다.

 

 

1.3 The Four Fundamental Subspaces

모든 m×n 행렬 A에서 4개의 부분공간은 다음과 같다.
 - 2개의 Rm 부분공간
 - 2개의 Rn 부분공간

Ex) 


[Counting Law]
r개의 독립인 일차방정식으로 이뤄진 Ax = 0에는
일차독립인 해가 n - r개 존재한다.
(일차독립은 Ax = 0의 해가 단 하나만 존재함을 의미)

 

😶Graph

위 그래프는 5개의 edge와 4개의 vertex를 갖는다.

영공간 N(A) : 영공간을 찾기위해 5개의 방정식에서 b=0으로 두고 계산했을 때, 4개의 미지수 x1, x2, x3, x4는 모두 같은 값 c를 갖는다. 따라서 모든 벡터 x = (c,c,c,c)는 Ax = 0의 해가 된다.
이 영공간은 R4에서 직선으로 
 - 특수해 x = (1,1,1,1)은 영공간 N(A)의 기저(basis)이고 rank = 1(∵직선)이다.



열공간 C(A) : r  = 4 - 1 = 3개의 일차독립인 열이 존재한다.

아래 3개의 열은 A의 열공간의 기저(basis of column space)이다.

A의 1, 2, 3열은 일차독립(linearly independent)이며 
4열은 다른 3열의 일차결합(linear combination)이다.




행공간 C(AT) : column과 마찬가지로 rank = 3이지만, 처음 3개의 행은 일차독립이 아니다.(∵3행 = 2행 - 1행)
처음으로 3개의 일차독립이 되는 행은 1, 2, 4행이다.
 - 1, 2, 4행들은 행공간의 기저이다.



좌영공간 N(AT) : ATy = 0의 해를 구해보자.
행의 일차결합은 0이며 이미 3행이 2행에서 1행을 뺀 것을 알고 있기에 하나의 해는 y = (1, -1, 1, 0, 0)이다.
또 하나의 y는 y = (0, 0, -1, 1, -1)로 이 해는 ATy = 0의 일차독립인 해이다.
따라서 좌영공간 N(AT)의 차원은 m - r = 5 - 3 = 2이다.
따라서 이 2개의 y는 좌영공간의 기저(basis of left nullspace)이다.



▶ A의 행공간 C(A)의 차원: r = 3
▶ A의 열공간 C(AT)의 차원: r = 3
▶ A의 영공간 N(A)의 차원: n - r = 1
▶ AT의 영공간 N(AT)의 차원: m - r = 2


 

 

 

1.3.1  AB와 A + B의 rank

행렬을 곱할 때, rank가 증가할 수 있는데 이는 column space와 row space에서 확인할 수 있다.
또한, rank가 감소하지 않는 특별한 상황도 존재하며 이를 통해 AB의 rank를 알 수 있다.


rank의 주요명제
4. mxr행렬 A, rxn행렬 B의 rank가 모두 r이라면, AB의 rank도 r이라는 의미.


명제 1. : AB의 열공간과 행공간에 관한 내용
C(AB)는 C(A)에 포함
된다.
C((AB)T)는 C(BT)에 포함된다.
 - 1.1절에서 언급했듯, rank(행) = rank(열)이다.
 - 즉, 행 또는 열을 이용한다면 행렬곱셈인 AB는 rank를 증가시킬 수 없다!



명제 2. : A + B의 각 열은 A의 열과 B의 열의 합이다.
rank(A + B) ≤ rank(A) + rank(B)
는 항상 참이다.
  - 이는 C(A)와 C(B)의 기저의 결합을 의미한다.

rank(A + B) = rank(A) + rank(B) 는 항상 참이 아니다.
  - 이 명제는 A = B = I 일 때, 거짓이다.



명제 3. : A와 ATA에 모두 n개의 열이 있다.
두 행렬은 모두 영공간이 같다. 따라서 두 행렬에서 n - r의 값은 같고 rank는 모두 r이 된다.
 - 또한 rank(AT) ≤ rank(ATA) = rank(A)이다.

 

Ex). ATA와 A의 영공간이 같음을 보여라.

 

 

 

1.2 행렬 곱셈 AB

내적(행과 열의 곱셈)은 AB = C의 각 성분 계산을 위해 필요

ex) A의 2행 , B의 3열간의 곱셈의 합은 C의 c₂₃의 값이다.
※ 선형대수학 제 1정리
· row rank = column rank
· r개의 일차독립 열(column)  ↔  r개의 일차독립 행(row)

 

 

1.2.1  AB = (rank 1인 행렬의 합)

AB = A열과 B행의 곱셈이라 하자.

cf) AB = (m×n)(n×p) , 총 mnp의 곱셈 연산수
cf-1) 행×열: mp번의 내적, 매번 n번의 곱셈
cf-2) 열×행: n번의 외적, 매번 mp번의 곱셈

 

 

1.2.2  열과 행의 곱셈에 대한 이해

Data Science에서 외적을 이용한 행렬곱셈이 필수인 이유는?
 - 간단히 말해, 특정 행렬에서 "어떤 중요부분을 찾으려"하기 위해.

행렬 A, B에 대해 
 - B의 행의 일차결합을 얻고 싶을 때: AB
 - B의 열의 일차결합을 얻고 싶을 때: BA

즉, AB의 열은 A의 열의 일차결합
이고 행은 B의 행의 일차결합이다.
- 따라서 AB의 열공간은 A의 열공간에 포함된다.


응용선형대수학에서 가장 중요한 주제는 A를 CR로 분해하는 것이다.
그리고 A = CR에서 cₖrₖ를 살펴보고자 할 때, 중요.

 

5개의 중요한 행렬 분해
1. A = LU
 - L은 하삼각행렬, U는 상삼각행렬이다.

2. A = QR
 - 그람-슈미트(Gram-Schmidt)를 통해 열 a1, ..., an을 직교화(orthogonalizing)하여 얻는다.
 - R은 상삼각 행렬이며 이때, Q에 정규직교인 열이 있다.(QTQ = I)

3. S = QΛQT
 - 대칭행렬(Symmetric matrix) S = ST의 고윳값 λ1, ... , λn에서 얻는다.
 - 이때, Λ는대각성분의 고윳값(eigen value) 
 - Q의 열은 정규직교인 고유벡터(eigen vector) , QT = Q⁻¹

4. A =
XΛ
X⁻¹
 - 대각화(diagonalization)는 행렬에 일차독립인 고유벡터가 n개일 때, 가능
 - Λ의 대각성분은 고윳값이며, X의 열은 A의 고유벡터이다.

5. A = U∑VT
 - 임의의 A행렬의 특잇값분해(Singular Vector Decomposition; SVD)이다.
 - 의 성분에는 특잇값 σ1, ... ,  σr이 있다.
 - U와 V에는 정규직교인 특이벡터(singular vector)가 존재.

 

Ex. S = QΛQT

Q⁻¹ = QT이므로 SQ = QΛ의 양변에 QT를 곱하면 S = QΛQT = 대칭행렬을 얻는다.

각 고윳값 λ와 고유벡터 q는 rank=1인 행렬인 λ q qT를 만든다.

 

- rank = 1인 행렬 : S = (QΛ)QT = (λ₁ q₁) qT + ... + (λn qn) qnT
- 모두 대칭 :  qiqiT의 전치행렬은 qiqiT이다.

 

 

 

스펙트럼 정리 (spectrum theorem)  S = (QΛ)QT

모든 대칭행렬 S는 n개의 실수인 고윳값과 n개의 정규직교인 고유벡터를 갖는다.

S = ST일 때, S의 고윳값은 실수이다.

 

cf. 증명에서 조심해야할 부분: 고윳값 λi가 반복될 때

  - 다항식이 중근을 갖거나 (λ - λj)M 형태로 인수분해된다.

  - 이 경우, M개의 일차독립인 고유벡터를 찾아야 한다.

  - 이때 행렬 S - λjI의 rank = n - M이다. (단, S = ST일 때 가능)

 

cf-2. 마찬가지로 A = U∑VT또한 대각행렬 에서 특잇값 σ가 M번 반복될 때, 주의

 - 즉, Av = σu를 만족하는 Singular Vector v와 u쌍이 M개 존재해야함을 의미.

+ Recent posts