使用 Keras RNN 处理自己准备的 IMDB 数据时,准确率从未超过 0.5

When processing IMDB data prepared by myself with a Keras RNN, accuracy never exceeds 0.5

一件非常难运行的事情正在发生。我从 Kaggle 中获取了 IMDB 语料库,只保留了 50,000 个正面和负面文本,统计词频,根据频率递减对词进行排序,将文本中出现频率最高的 10,000 个词替换为它们的 运行k(加上 3 个单位), 在所有句子开头插入一个 1,并将 10,000 个最常见单词之外的所有单词替换为数字 2。在这方面,我完全按照 Keras imdb class.[=37 文档中给出的说明进行操作=]

然后我 运行 一个带有嵌入层的 RNN,一个 SimpleRNN 层,一个 Dense 层。无论我多么努力,我得到的结果都是准确度始终在 0.5 左右。然后,我用 imdb.load_data(num_words=10000) 替换我的代码,并在第三个时期获得 0.86 的准确度。这怎么可能?为什么会有如此极端的差异?我做错了什么?

这是我使用的代码:

import re, os, time, pickle
word=re.compile(r'^[A-Za-z]+$')
spacyline=re.compile(r'^([0-9]+) ([^ ]+) ([^ ]+) ([^ ]+) ([0-9]+) ([A-Za-z]+)')

DICT=dict()

inp=open("imdb_master_lemma.txt")
for ligne in inp:
    if (ligne[0:9]=="DEBUT DOC"):
        if (ligne[-4:-1]=="neg"):
            classe=-1
        elif (ligne[-4:-1]=="pos"):
            classe=1
    elif (ligne[0:9]=="FIN DOCUM"):
        a=1
    else:
        res=spacyline.findall(ligne)
        if res:
            lemma=str(res[0][3])
            if (word.match(lemma)):
                if (lemma in DICT.keys()):
                    DICT[lemma] += 1
                else:
                    DICT[lemma]=1
inp.close()

SORTED_WORDS=sorted(DICT.keys(), key=lambda x:DICT[x], reverse=True)
THOUSAND=SORTED_WORDS[:9997]
ORDRE=dict()
c=0
for w in THOUSAND:
    ORDRE[w]=c
    c+=1
CORPUS=[]
CLASSES=[]

inp=open("imdb_master_lemma.txt")
for ligne in inp:
    if (ligne[0:9]=="DEBUT DOC"):
        if (ligne[-4:-1]=="neg"):
            classe=0
        elif (ligne[-4:-1]=="pos"):
            classe=1
        a=[]
    if (ligne[0:9]=="DEBUT PHR"):
        a.append(1)
    elif (ligne[0:9]=="FIN DOCUM"):
        CORPUS.append(a)
        CLASSES.append(classe)
    else:
        res=spacyline.findall(ligne)
        if res:
            lemma=str(res[0][3])
            if lemma in ORDRE:
                a.append(ORDRE[lemma]+3)
            elif (word.match(lemma)):
                a.append(2)
inp.close()

from sklearn.utils import shuffle
CORPUS, CLASSES=shuffle(CORPUS, CLASSES)

out=open("keras1000.pickle","wb")
pickle.dump((CORPUS,CLASSES,ORDRE),out)
out.close()

文件imdb_master_lemma.txt包含Spacy处理过的IMDB文本,我只保留引理(已经是小写的,所以这或多或少只是在Keras imdb中使用的它应该工作得更好,因为没有复数并且动词被词形化)。存储 pickle 文件后,我会调用它并按如下方式使用它:

picklefile=open("keras1000.pickle","rb")
(CORPUS,CLASSES,ORDRE)=pickle.load(picklefile)
picklefile.close()

import numpy as np
def vectorize_sequences(sequences, dimension=10000):
    results = np.zeros((len(sequences), dimension))
    for i, sequence in enumerate(sequences):
        results[i, sequence] = 1.
    return results
x_train = np.array(vectorize_sequences(CORPUS[:25000]),dtype=object)
x_test = np.array(vectorize_sequences(CORPUS[25000:]),dtype=object)
train_labels = np.array(CLASSES[:25000])
test_labels = np.array(CLASSES[25000:])

from keras import models
from keras import layers
from keras.models import Sequential
from keras.layers import Flatten, Dense, Embedding, SimpleRNN, LSTM, Bidirectional
from keras.preprocessing import sequence

input_train = sequence.pad_sequences(x_train, maxlen=500)
input_test = sequence.pad_sequences(x_test, maxlen=500)
print('input_train shape:', input_train.shape)
print('input_test shape:', input_test.shape)

model = Sequential()
model.add(Embedding(10000, 32))
model.add(SimpleRNN(32))
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['acc'])

history = model.fit(input_train,
                    train_labels,
                    epochs=10,
                    batch_size=128,
                    validation_split=0.2)
results = model.evaluate(input_test, test_labels)

print(results)

结果完全令人失望,准确度约为 0.5。当我用

替换前 14 行时
from keras.datasets import imdb
(x_train, train_labels), (x_test, test_labels) = imdb.load_data(num_words=10000)

然后一切都按照 Chollet 的书中描述的那样工作,我立即获得了非常高的准确性。

谁能告诉我我做错了什么?

PS。这是一个小数据样本,用于说明准备过程: 第一个 IMDB 文档的前两个句子,由 spaCy 处理,是

DEBUT DOCUMENT neg
DEBUT PHRASE
0 Once RB once 1 advmod
1 again RB again 5 advmod
2 Mr. NNP mr. 3 compound
3 Costner NNP costner 5 nsubj
4 has VBZ have 5 aux
5 dragged VBN drag 5 ROOT
6 out RP out 5 prt
7 a DT a 8 det
8 movie NN movie 5 dobj
9 for IN for 5 prep
10 far RB far 11 advmod
11 longer JJR long 9 pcomp
12 than IN than 11 prep
13 necessary JJ necessary 12 amod
14 . . . 5 punct
FIN PHRASE
DEBUT PHRASE
15 Aside RB aside 16 advmod
16 from IN from 33 prep
17 the DT the 21 det
18 terrific JJ terrific 19 amod
19 sea NN sea 21 compound
20 rescue NN rescue 21 compound
21 sequences NNS sequence 16 pobj
22 , , , 21 punct
23 of IN of 26 prep
24 which WDT which 23 pobj
25 there EX there 26 expl
26 are VBP be 21 relcl
27 very RB very 28 advmod
28 few JJ few 26 acomp
29 I PRP -PRON- 33 nsubj
30 just RB just 33 advmod
31 did VBD do 33 aux
32 not RB not 33 neg
33 care VB care 33 ROOT
34 about IN about 33 prep
35 any DT any 34 pobj
36 of IN of 35 prep
37 the DT the 38 det
38 characters NNS character 36 pobj
39 . . . 33 punct
FIN PHRASE

这变成:

[1, 258, 155, 5920, 13, 979, 38, 6, 14, 17, 207, 165, 68, 1526, 1, 1044, 33, 3, 1212, 1380, 1396, 382, 7, 58, 34, 4, 51, 150, 37, 19, 12, 338, 39, 91, 7, 3, 46,

等可以看到,1表示句子开始,258once155again,我错过了mr.因为它包含句点(但这不可能是我的系统出现故障的原因),5920costner(显然 Kevin Costner 的名字出现得如此频繁,以至于它包含在 10,000 个最常用的单词中),13have979drag38out6是文章a14 就是单词 movie,依此类推。我认为这些 运行k 都非常合理,所以我看不出哪里出了问题。

我认为问题在于您在 vectorize_sequences 函数中 one-hot 对输入数据(x_trainx_test)进行了编码。如果您跳过该步骤,您的模型应该与 Keras 示例数据一样工作。

原因是您的输入层 model.add(Embedding(10000, 32)) 期望序列中每个单词的实际索引。因此,正如您在示例中展示的那样:

In [1] : print(x_train[0])
Out[1] : [1, 258, 155, 5920, 13, 979, 38, 6, 14, 17, 207, ...]

然后,嵌入层会将这些索引映射到相应的词向量,并以正确的顺序将它们堆叠在一起,然后再将它们提供给您的 RNN。

当您 one-hot 对序列进行编码时,您不仅会丢失文本中的顺序,还会得到一个维数为 10000 的向量,Keras 可能会在您定义 maxlen 时将其删除用于填充。

input_train = sequence.pad_sequences(x_train, maxlen=500)

这并不是说 one-hot 编码不是一种有效的方法。它只是不适合您的架构,更适合简单的前馈网络。

综上所述,我还没有测试过您的代码,因此还不能说这是否就是所有需要修复的地方。让我知道这是否有帮助,或者您是否需要其他见解。

更新

我刚刚下载了你的数据,运行你的代码只改变了输入向量。如上所述,您只需要为网络提供从预处理步骤中获得的索引。

简单替换

def vectorize_sequences(sequences, dimension=10000):
    results = np.zeros((len(sequences), dimension))
    for i, sequence in enumerate(sequences):
        results[i, sequence] = 1.
    return results
x_train = np.array(vectorize_sequences(CORPUS[:25000]),dtype=object)
x_test = np.array(vectorize_sequences(CORPUS[25000:]),dtype=object)

x_train = CORPUS[:25000]
x_test = CORPUS[25000:]

并且您的代码应该可以很好地工作。我在 5 个 epoch 中很快达到了 95% 的准确率。