Transformer 기반 텍스트 감정 분석(Keras 버전)

I. 소개

2017년부터 RNN 시리즈 네트워크는 점차 Transformer라는 네트워크로 대체되었으며, 이제 Transformer는 자연어 처리 분야의 주류 모델이 되었으며 Transformer는 언어 모델 분야에서 큰 붐을 일으켰습니다. Bert에서 GPT3로, 그리고 이제 ChatGPT로. 트랜스포머는 인간이 상상할 수 없는 기능을 달성했으며 지금도 계속 발전하고 있습니다.

이 기사에서는 Transformer의 인코더 부분을 기반으로 텍스트 감정 분석 작업을 구현합니다.

2. 데이터 처리

데이터 처리는 기존 LSTM(Keras 버전) 기반의 텍스트 감성 분석 코드를 참고할 수 있으며 , 본 글에서는 또 다른 간단한 방법을 사용하여 구현해 본다.

2.1 데이터 미리보기

먼저 해당 데이터( ai.stanford.edu/~amaas/data… )를 다운로드해야 합니다 . 아래 사진의 위치를 ​​클릭하세요.


데이터의 압축이 풀린 후 다음과 같은 디렉터리 구조가 얻어집니다.
데이터의 압축이 풀린 후 다음과 같은 디렉터리 구조가 얻어집니다.

- aclImdb
    - test
        - neg
        - pos
        - labeledBow.feat
        - urls_neg.txt
        - urls_pos.txt
    - train
        - neg
        - pos

영화 리뷰 데이터 세트인데, neg에 포함된 리뷰는 낮은 평점을 받은 리뷰이고, pos에 포함된 리뷰는 높은 평점을 받은 리뷰입니다. 우리에게 필요한 데이터는 테스트에서는 neg와 pos이고, train에서는 neg와 pos입니다(neg는 음수를 의미하고 pos는 양수를 의미함). 아래에서 처리를 시작하겠습니다.

2.2 가져오기 모듈

코드 작성을 시작하기 전에 관련 모듈을 가져와야 합니다.

import os
import keras
import tensorflow as tf
from keras import layers

제 환경은 tensorflow2.7입니다. tensorflow의 일부 버전은 가져오기 방법이 다르기 때문에 자신의 환경에 맞게 교체할 수 있습니다.

2.3 데이터 읽기

주석 파일을 읽으려면 여기에 함수를 정의하십시오.

def load_data(data_dir=r'/home/zack/Files/datasets/aclImdb/train'):
    """
    data_dir:train的目录或test的目录
    输出:
        X:评论的字符串列表
        y:标签列表(0,1)
    """
    classes = ['pos', 'neg']
    X, y = [], []
    for idx, cls in enumerate(classes):
        # 拼接某个类别的目录
        cls_path = os.path.join(data_dir, cls)
        for file in os.listdir(cls_path):
            # 拼接单个文件的目录
            file_path = os.path.join(cls_path, file)
            with open(file_path, encoding='utf-8') as f:
                X.append(f.read().strip())
                y.append(idx)
    return X, np.array(y)

위 함수는 나중에 처리할 수 있도록 두 개의 목록을 가져옵니다.

2.4 어휘 구축 및 토큰화

이전 부분의 처리는 이전 기사와 동일하며 어휘 구축 및 토큰화 작업은 keras API를 사용하여 구현됩니다. 코드는 아래와 같이 표시됩니다.

X, y = load_data()
vectorization = TextVectorization(max_tokens=vocab_size, output_sequence_length=seq_len)
# 构建词表
vectorization.adapt(X)
# tokenize
X = vectorization(X)

Adapt 메소드는 문장 목록을 수신하고, Adapt 메소드를 호출한 후 keras는 단어 목록을 작성하고 벡터화(X)를 사용하여 문장 목록을 단어 ID 목록으로 변환하는 데 도움을 줍니다.

3. 모델 구축

여기서는 Transformer의 인코더 부분이 네트워크의 백본으로 사용됩니다. PositionalEmbedding과 TransformerEncoder라는 두 부분을 구현하고 두 부분을 감정 분류 모델로 구성해야 합니다.

3.1 트랜스포머인코더

Transformer에 대해 간단히 소개하자면, Transformer의 다양한 구성요소를 대략적으로 살펴보겠습니다. Transformer 구조는 다음과 같습니다.

변압기.jpg

왼쪽이 Encoder, 오른쪽이 Decoder인데, 우리가 구현하고자 하는 것은 Encoder 부분입니다. 디코더(Decoder) 부분을 아래에서 위로 살펴보겠습니다.

(1) 입력 임베딩과 위치 인코딩

Transformer의 입력은 의 모양을 가진 ID 목록입니다 batch_size × sequence_len. 입력은 먼저 간단한 임베딩 레이어(입력 임베딩)를 거쳐 이라는 모양을 얻 batch_size × sequence_len × embed_dim습니다 te. te여기에는 sequence_len단어 임베딩이 포함되어 있으며 te첫 번째 임베딩은 pe[0]벡터에 추가되고 te두 ​​번째 임베딩은 t[1]벡터에 추가되는 식입니다.

따라서 pe모양은 sequence_len × embed_dim위치 pe정보를 담고 있는 이어야 한다. 원 논문에서는 pe구하는 고정된 공식이 있고, 이를 고정한 pe후 본 논문에서는 구현 시 이를 Positional Embedding이라는 방법으로 대체하며, 구현 코드는 다음과 같다.

class PositionalEmbedding(layers.Layer):
    def __init__(self, input_size, seq_len, embed_dim):
        super(PositionalEmbedding, self).__init__()
        self.seq_len = seq_len
        # 词嵌入
        self.tokens_embedding = layers.Embedding(input_size, embed_dim)
        # 位置嵌入
        self.positions_embedding = layers.Embedding(seq_len, embed_dim)

    def call(self, inputs, *args, **kwargs):
        # 生成位置id
        positions = tf.range(0, self.seq_len, dtype='int32')
        te = self.tokens_embedding(inputs)
        pe = self.positions_embedding(positions)
        return te + pe

여기에는 단어 임베딩과 유사한 아이디어가 사용되어 네트워크가 스스로 위치 정보를 학습할 수 있습니다.

(2) 멀티 헤드 어텐션

Multi-Head Attention은 시퀀스에서 여러 Self-Attention을 수행한 다음 각 Self-Attention의 구조를 함께 연결하는 것으로 생각할 수 있습니다. Keras와 Pytorch에는 이에 상응하는 구현이 있는데, 여기서는 이를 사용하는 방법을 살펴보겠습니다.

MultiHeadAttention 레이어를 생성할 때 헤드 수와 키의 차원을 지정해야 하며, 순방향 전파 중에 두 개의 동일한 시퀀스가 ​​전달되면 Self-Attention이 수행됩니다. 코드는 다음과 같습니다.

from keras import layers
import tensorflow as tf

# 形状为batch_size × sequence_len × embed_dim
X = tf.random.uniform((3, 10, 5))
mta = layers.MultiHeadAttention(4, 10)
out = mta(X, X)
# 输出:(3, 10, 5)
print(out.shape)

MultiHeadAttention의 입력과 출력 형태가 일관적인 것을 코드에서 볼 수 있습니다.

(3) 추가 및 정규화

Attention 이후에는 Attention 입력과 Attention 출력을 Add & Norm이라는 모듈에 넣습니다. 여기서 실제로 두 개를 추가한 다음 LayerNormalization을 수행합니다. 구조는 다음과 같습니다.

이미지.png

x1과 x2를 Attention에 임베딩하는 워드를 입력하여 z1과 z2를 얻은 다음 x1과 x2를 결합하여 행렬 X를 형성하고, z1과 z2는 행렬 Z를 형성하고 LayerNorm(X+Z)을 계산하고 다음 레이어로 진입합니다. 코드는 다음과 같이 구현됩니다. 다음과 같습니다:

# 定义层
mta = layers.MultiHeadAttention(4, 10)
ln = layers.LayerNormalization()
# 正向传播
X = tf.random.uniform((3, 10, 5))
Z = mta(X, X)
out = ln(X+Z)
# 输出 (3, 10, 5)
print(out.shape)

(4) 피드포워드

피드 포워드는 단순한 완전 연결 레이어이지만 여기서는 단일 벡터가 완전 연결입니다. 즉, 각 벡터 z1-zn이 선형 레이어를 독립적으로 통과합니다. 또한 Feed Forward 레이어에는 두 개의 완전 연결 레이어가 있는데, 먼저 확대한 다음 축소합니다. 코드는 다음과 같습니다.

import keras
from keras import layers
import tensorflow as tf

mta = layers.MultiHeadAttention(4, 10)
ln = layers.LayerNormalization()
# Feed Forward层
ff = keras.Sequential([
    layers.Dense(10, activation='relu'),
    layers.Dense(5)
])
X = tf.random.uniform((3, 10, 5))
Z = mta(X, X)
Z = ln(X+Z)
out = ff(Z)
# 输出 (3, 10, 5)
print(out.shape)

지금까지 인코더의 다양한 구성요소를 설명했습니다. 다음으로 TransformerEncoder 레이어를 구현합니다.

3.2 인코더 구현

이제 위의 부분을 PositionalEmbedding을 포함하지 않는 TransformerEncoder 클래스에 작성합니다. 코드는 다음과 같습니다.

class TransformerEncoder(layers.Layer):
    def __init__(self, embed_dim, hidden_dim, num_heads, **kwargs):
        super(TransformerEncoder, self).__init__(**kwargs)
        # Multi-Head Attention层
        self.attention = layers.MultiHeadAttention(
            num_heads=num_heads, key_dim=embed_dim
        )
        # Feed Forward层
        self.feed_forward = keras.Sequential([
            layers.Dense(hidden_dim, activation='relu'),
            layers.Dense(embed_dim)
        ])
        # layernorm层
        self.layernorm1 = layers.LayerNormalization()
        self.layernorm2 = layers.LayerNormalization()

    def call(self, inputs, *args, **kwargs):
        # 计算Self-Attention
        attention_output = self.attention(inputs, inputs)
        # 进行第一个Layer & Norm
        ff_input = self.layernorm1(inputs + attention_output)
        # Feed Forward
        ff_output = self.feed_forward(ff_input)
        # 进行第二个Layer & Norm
        outputs = self.layernorm2(ff_input + ff_output)
        return outputs

batch_size × sequence_len × embed_dim이제 텐서를 수신하고 동일한 모양의 텐서를 출력하는 TransformerEncoder를 구현합니다 . 감정 분석에 사용하려면 출력 뒤에 글로벌 평균 풀링과 완전히 연결된 레이어를 연결할 수 있습니다.

3.3 분류 모델

아래에서는 이전 PositionalEmbedding 및 TransformerEncoder를 사용하여 텍스트 분류 네트워크를 구현합니다. 코드는 다음과 같습니다.

# 超参数
vocab_size = 20000
seq_len = 180
batch_size = 64
hidden_size = 1024
embed_dim = 256
num_heads = 8
# 加载数据
X_train, y_train = load_data()
X_test, y_test = load_data(r'/home/zack/Files/datasets/aclImdb/test')

vectorization = layers.TextVectorization(
    max_tokens=vocab_size, 
    output_sequence_length=seq_len,
    pad_to_max_tokens=True
)
vectorization.adapt(X_train)
X_train = vectorization(X_train)
X_test = vectorization(X_test)
# 构建模型
inputs = layers.Input(shape=(seq_len,))
x = PositionalEmbedding(vocab_size, seq_len, embed_dim)(inputs)
x = TransformerEncoder(embed_dim, hidden_size, num_heads)(x)
x = layers.GlobalAveragePooling1D()(x)
x = layers.Dropout(0.5)(x)
outputs = layers.Dense(1, activation='sigmoid')(x)
model = keras.Model(inputs, outputs)
# 训练
model.compile(loss='binary_crossentropy', metrics=['accuracy'])
model.fit(
    X_train,
    y_train,
    epochs=20,
    batch_size=batch_size,
    validation_data=[X_test, y_test]
)

최종 훈련 후 테스트 세트의 정확도는 약 85%입니다.

추천

출처blog.csdn.net/ZackSock/article/details/129759543