[Sentiment Analysis PyTorch] Representación de RNN en el conjunto de datos de IMDB

Directorio de artículos

¿Qué es RNN?

Red neuronal recurrente (RNN), la ventaja de la red neuronal cíclica es que puede manejar entradas de cualquier longitud (o entrada de flujo (stream), intervenir, el blogger odia mucho este nombre pretencioso, parece que el proponente es alto En hecho, solo puede hacer que los que llegan tarde trabajen duro cuando estudian solos). En pocas palabras, una oración **"ir a la cama en lugar de volverse loco"** se puede dividir en: ['go', 'to', 'bed', 'instead', 'of', 'go', 'mad']. Después de eso, podemos usar algunos vectores preentrenados para representar estas palabras. Por ejemplo, podemos usar GloVe para obtener una lista de vectores, y los vectores que contiene son representaciones de palabras correspondientes a posiciones.
Al alimentar el modelo RNN, cada vector se envía y se ejecuta durante un tiempo. En este proceso, algunos RNN especialmente diseñados, como LSTM y GRU, pueden tener algunas cantidades adicionales para calcular, pero eventualmente se obtendrán. Una matriz de salidas, que almacena los vectores de capa oculta calculados para las palabras introducidas durante cada proceso de cálculo. En términos generales, lo que queremos es la capa oculta de la última salida, porque ha pasado por toda la información, por lo que podemos creer que es una representación más apropiada de la oración en cierto sentido.

IMDB

Este conjunto de datos son las reseñas de películas de amigos extranjeros. Puede entenderse como los datos rastreados desde Douban. Después de la lectura manual, se divide en positivo y negativo para el etiquetado. Esto se puede hacer a través de la API de torchtext, pero en realidad es bastante doloroso.
Porque si lo usa de acuerdo con la pantalla al comienzo de su documento oficial, lo que obtiene train_iter = torchtext.datasets.IMDB(split='train')no es un datasetobjeto, sino uno extraño MapperIterDatapipe. Aunque me tomó varias horas estudiar esto, finalmente saqué todos los datos atravesándolos y luego Dataset, DataLoaderhice mi propio iterador de datos en forma de replicación.

Guante

Cuando el bloguero comenzó a hacerlo, para representar palabras, inicializó directamente e ingenuamente torch.nn.Embeddinguna capa de codificación mediante una inicialización aleatoria. Aunque lo pensé cuando lo estaba haciendo, estos códigos inicializados aleatoriamente no podían expresar el significado de estas palabras, pero estaba confundido en ese momento y sentí que el código podría actualizarse continuamente a través del entrenamiento con esta cantidad de datos. Pero los hechos muestran que ser joven es todavía demasiado joven, y se acaba de desperdiciar medio precioso día de vida.
También quiero mencionar aquí un ángulo de observación sobre la pérdida que no cae durante el entrenamiento.Si la pérdida no es estrictamente fija, en otras palabras, su optimizador está funcionando, pero el rendimiento del modelo ha sido pobre.El resultado de la tarea de clasificación suele ser el mismo que el de los ciegos Guess el resultado es el mismo. En este momento, la probabilidad de errores a nivel de código es relativamente pequeña y debe verse desde la estructura general del modelo. Por otro lado, si la pérdida es siempre el mismo número, entonces es necesario verificar si se ha borrado algún gráfico de cálculo.

el código

El código abierto es una gran cosa, espero poder abrir algunas cosas más valiosas en el futuro

import torch
import torch.nn as nn
import torch.optim as optim
import torchtext.vocab.vocab as vocab
import torchtext
from torchtext import datasets
from torch.utils.data import DataLoader,Dataset
from collections import Counter, OrderedDict

BATCH_SIZE = 32
EPOCHS = 10
SHUFFLE = True
DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'

MOST_COMMON_SIZE = 40000
VOCAB_SIZE = MOST_COMMON_SIZE + 2 # unk pad
COMMENT_SIZE = 300

EMBED_SIZE = 50
HIDDEN_SIZE = 32

UNK_TOKEN = '<unk>'
PAD_TOKEN = '<pad>'
imdb_path = 'path_of_dataset'

GloVe = torchtext.vocab.GloVe('6B', 50, cache='/home/haolin_li/code/pretrained_models')

# 这俩就是上面提到的,Datapipe
train_imdb = datasets.IMDB(
    root = imdb_path,
    split = 'train'
)
test_imdb = datasets.IMDB(
    root = imdb_path,
    split = 'test'
)
# 把两个Datapipe里面数据全都拿出来
train_source_targets = []
train_source_comments = []
test_source_targets = []
test_source_comments = []
for target, comment in train_imdb:
    train_source_targets.append(target)
    train_source_comments.append(comment)
for target, comment in test_imdb:
    test_source_targets.append(target)
    test_source_comments.append(comment)
# 计算词频
word_counter = Counter()
tokenizer = torchtext.data.utils.get_tokenizer('basic_english')
for comment in train_source_comments:
    for word in tokenizer(comment):
        word_counter[word] += 1
# 前十个都是些没啥语义的 of  with that 这样的,所以直接去掉
most_common_words = word_counter.most_common(MOST_COMMON_SIZE+10)[10:]
word_ordered_dict = OrderedDict(most_common_words)
# 在torchtext这个版本中可以通过添加specials的方式直接把unk 和 pad加进去,还是挺方便的,默认放在最前面
my_vocab = vocab(word_ordered_dict, specials=[UNK_TOKEN, PAD_TOKEN])
# 当键值的不在keys中时,默认返回是unk
my_vocab.set_default_index(my_vocab[UNK_TOKEN])
#前两个分别代表unk 和 pad的向量表示,后面的按照顺序连上
embedding_weight_matrix = [[0 for i in range(EMBED_SIZE)] for j in range(2)]
# 注意这里最好转成numpy()否则后面整体转tensor的时候也有点烦人
embedding_weight_matrix.extend([GloVe.get_vecs_by_tokens(word[0]).detach().numpy() for word in most_common_words])

class imdb_dataset(Dataset):
    def __init__(self, source_targets, source_comments, tokenizer, my_vocab):
        self.source_targets = source_targets
        # 见下面的函数
        self.conver_targets()
        self.source_comments = []
        # 把str的comment先分词,再转成idx的形式
        for comment in source_comments:
            split = tokenizer(comment)
            if len(split) > COMMENT_SIZE:
                self.source_comments.append([my_vocab[word] for word in split[:COMMENT_SIZE]])
            else:
                split.extend([PAD_TOKEN for i in range(COMMENT_SIZE - len(split))])
                self.source_comments.append([my_vocab[word] for word in split[:COMMENT_SIZE]])
        self.length = len(source_targets)

    def __len__(self):
        return self.length

    def __getitem__(self,idx):
        return self.source_targets[idx], self.source_comments[idx]

    def conver_targets(self):
        for i, target in enumerate(self.source_targets):
            self.source_targets[i] = 0 if target == 'neg' else 1

train_dataset = imdb_dataset(train_source_targets, train_source_comments, tokenizer, my_vocab)
test_dataset = imdb_dataset(test_source_targets, test_source_comments, tokenizer, my_vocab)
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=SHUFFLE)
test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=SHUFFLE)
# 无attention的模型
class sentimental_rnn(nn.Module):
    def __init__(self, vocab_size:int, embed_size:int, hidden_size:int, dropout:float, pad_idx:int, embedding_weight_matrix):
        super(sentimental_rnn, self).__init__()
        
        self.embedding = nn.Embedding.from_pretrained(torch.Tensor(embedding_weight_matrix), freeze=True)
        self.dropout = nn.Dropout(dropout)

        self.rnn = nn.GRU(embed_size, hidden_size, batch_first=False) # 比较奇怪的一点,从Loader里面出来就是Seq_first的状态
        self.classifier = nn.Sequential(
            nn.Linear(hidden_size, 128),
            nn.ReLU(),
            nn.Linear(128, 2),
            # nn.Softmax()
        )
    
    def forward(self, x):
        # x: seq, batch
        x = self.embedding(x) 
        # x = self.dropout(x)
        # x: seq, batch, embed_size
        output, h_n = self.rnn(x)
        # output: seq, batch, hidden
        output = output[-1, :, :]
        # batch, hidden
        pred = self.classifier(output)
        # batch, 2
        return pred
# attention版模型
class sentimental_rnn_attention(nn.Module):
    def __init__(self, embed_size:int, hidden_size:int, dropout:float, n_layers:int, embedding_weight_matrix):
        super(sentimental_rnn_attention, self).__init__()
        
        self.embedding = nn.Embedding.from_pretrained(torch.Tensor(embedding_weight_matrix), freeze=True)
        self.dropout = nn.Dropout(dropout)

        self.rnn = nn.GRU(embed_size, hidden_size, num_layers=n_layers, batch_first=False) # 比较奇怪的一点,从Loader里面出来就是Seq_first的状态
        # 这个attention只综合了output
        self.W = nn.Parameter(torch.Tensor(hidden_size, hidden_size))
        self.K = nn.Parameter(torch.Tensor(hidden_size, 1))
        self.softmax = nn.Softmax(dim=1)

        self.fc = nn.Linear(hidden_size, 2)

        nn.init.uniform_(self.W, -0.1, 0.1)
        nn.init.uniform_(self.K, -0.1, 0.1)
    
    def forward(self, x):
        # x: seq, batch
        x = self.embedding(x) 
        x = self.dropout(x)
        # x: seq, batch, embed_size
        output, h_n = self.rnn(x)
        # output: seq, batch, hidden
        # h_n: n_layers, batch, hidden
        output = output.permute(1,0,2) 
        # batch, seq, hidden
        weights = torch.tanh(output.matmul(self.W))
        # batch, seq, hidden
        weights = weights.matmul(self.K)
        # print(weights.shape)
        # batch, seq, 1
        weights = self.softmax(weights)
        # print(weights.shape)
        # batch, seq, 1
        output = output * weights
        # batch, seq, hidden
        output = torch.sum(output, dim=1)
        pred = self.fc(output)
        # batch, 2
        return pred


def evaluate(model, test_loader, loss_fn):
    total_count = 0
    correct_count = 0
    total_loss = 0
    with torch.no_grad():
        for i, (targets, comments) in enumerate(test_loader):
            targets = torch.LongTensor(targets).to(DEVICE)
            comments = torch.LongTensor([ts.cpu().detach().numpy() for ts in comments]).to(DEVICE)

            predictions = model(comments)
            loss = loss_fn(predictions, targets)
            total_loss += loss.detach().cpu().numpy() * BATCH_SIZE
            predictions = predictions.argmax(dim=1)

            total_count += BATCH_SIZE
            correct_count += torch.sum(targets == predictions)
    return  correct_count / total_count, total_loss / total_count

def train(model, train_loader, test_loader, loss_fn):
    for epoch in range(EPOCHS):
        for i, (targets, comments) in enumerate(train_loader):
            optimizer.zero_grad()
            targets = torch.LongTensor(targets).to(DEVICE)
            comments = torch.LongTensor([ts.cpu().detach().numpy() for ts in comments]).to(DEVICE)

            predictions = model(comments)
            loss = loss_fn(predictions, targets)
            loss.backward()
            optimizer.step()

            if i % 50 == 0 and i != 0:
                print("epoch:{}  crnt_train_loss:{}".format(epoch, loss.cpu().detach().numpy()))
        acc, avg_loss = evaluate(model, test_loader, loss_fn)
        print("test performance     accuracy:{}  avg_loss:{}".format(acc, avg_loss))
            
model = sentimental_rnn(VOCAB_SIZE, EMBED_SIZE, HIDDEN_SIZE, 0.2, my_vocab[PAD_TOKEN], embedding_weight_matrix).to(DEVICE)
optimizer = optim.Adam(model.parameters())
loss_fn = nn.CrossEntropyLoss()

train(model, train_loader, test_loader, loss_fn)

No lo pensé cuando lo escribí, pero después de copiarlo así, siento que tensorflow es fácil de escribir y PyTorch es muy elocuente. Pero al igual que jugar Lego, aunque los bloques pequeños consumen energía, hay más tipos de cosas para armar, por lo que es más fácil innovar.

A veces, este tipo de cosas realmente están impulsadas por el interés y es muy divertido después del entrenamiento.
Solo dices "Esta película es horrible", y él te dice que es una maldición,
inserte la descripción de la imagen aquí
y luego cambia una palabra, y sabe cómo elogiarlo.
inserte la descripción de la imagen aquí
Finalmente, hay otro punto interesante. Por supuesto, también puede ser el nivel de mi formación No es suficiente, cuando una oración tiene un punto de inflexión y se mezcla la longitud de las oraciones anteriores y posteriores, es difícil de reconocer, en otras palabras, no es sensible a las palabras de marcador estructural.
inserte la descripción de la imagen aquí
Se debe agregar una oración más para fortalecer el peso semántico a fin de obtener el resultado correcto
inserte la descripción de la imagen aquí
PD: Realmente me gusta Jackie Chan, pero ahora es demasiado tarde, no puedo pensar en otras personas después de trabajar en el código durante un día, así que de escribir un nicho cremoso Encuentra algo para ti, es mejor dejar este trabajo a la gran multitud de fanáticos de Jackie Chan en quienes puedo confiar. \dux

Supongo que te gusta

Origin blog.csdn.net/Petersburg/article/details/123765403
Recomendado
Clasificación