下载IMDB数据集
IMDB是内置于keras库的电影数据库,里面包含了50 000条严重两极分化的评论。已经过预处理:评论的单词序列已经被转化为整数序列,即每个整数代表字典中的某个单词。25 000条用于训练,25 000条用于测试。下面对数据集通过代码进行分析:
- 导入keras数据包
from keras.datasets import imdb
- 通过imdb.load_data方法导入数据,其中num_words=10000表示仅保留数据中前10000个常见出现的单词,低频出现的单词被舍弃。方法返回类型为(train_data, train_labels), (test_data, test_labels),其中train_data是训练用的评论数据,train_labels是对应训练用评论的分类(0表示负面,1表示正面),test_data是测试的评论数据,test_labels是测试的评论数据的分类。
(train_data, train_labels), (test_data,
test_labels) = imdb.load_data(num_words=10000)
我们用print函数将train_data和train_labels打印出来。结果如下:
[list([1, 14, 22, 16, 43, 530, 973, 1622, 1385, 65, 458, 4468, 66, 3941, 4, 173, 36, 256, 5, 25, 100, 43, 838, 112, 50, 670, 2, 9, 35, 480, 284, 5, 150, 4, 172, 112, 167, 2, 336, 385, 39, 4, 172, 4536, 1111, 17, 546, 38, 13, 447, 4, 192, 50, 16, 6, 147, 2025, 19, 14, 22, 4, 1920, 4613, 469, 4, 22, 71, 87, 12, 16, 43, 530, 38, 76, 15, 13, 1247, 4, 22, 17, 515, 17, 12, 16, 626, 18, 2, 5, 62, 386, 12, 8, 316, 8, 106, 5, 4, 2223, 5244, 16, 480, 66, 3785, 33, 4, 130, 12, 16, 38, 619, 5, 25, 124, 51, 36, 135, 48, 25, 1415, 33, 6, 22, 12, 215, 28, 77, 52, 5, 14, 407, 16, 82, 2, 8, 4, 107, 117, 5952, 15, 256, 4, 2, 7, 3766, 5, 723, 36, 71, 43, 530, 476, 26, 400, 317, 46, 7, 4, 2, 1029, 13, 104, 88, 4, 381, 15, 297, 98, 32, 2071, 56, 26, 141, 6, 194, 7486, 18, 4, 226, 22, 21, 134, 476, 26, 480, 5, 144, 30, 5535, 18, 51, 36, 28, 224, 92, 25, 104, 4, 226, 65, 16, 38, 1334, 88, 12, 16, 283, 5, 16, 4472, 113, 103, 32, 15, 16, 5345, 19, 178, 32])...
[1 0 0 ... 0 1 0]
因为数据已经通过预处理,我们可以看到,打印出来的数据是一组整数序列。
下面我们将数字映射到字符,看看每个整数分别代表什么单词。
word_index = imdb.get_word_index()
reversed_word_index = dict([value, key] for (key, value) in word_index.items())
decoded_review = ' '.join(
[reversed_word_index.get(i - 3, '?') for i in train_data[0]])
imdb.get_word_index方法返回一个从单词映射到整数索引的字典。将word_index打印出来后的数据:
{...., 'ev': 88575, 'chicatillo': 88576, 'transacting': 88577, "'la": 27630, 'percent': 8925, 'oprah': 7996, 'sics': 88578, 'illinois': 11925, 'dogtown': 40828, 'roars': 20595, 'branch': 9456, 'kerouac': 52002, 'wheelers': 88579, 'sica': 20596, 'lance': 6435, "pipe's": 88580, 'discretionary': 64179, 'contends': 40829, 'copywrite': 88581, 'geysers': 52003, 'artbox': 88582, 'cronyn': 52004, 'hardboiled': 52005, "voorhees'": 88583, '35mm': 16815, "'l'": 88584, 'paget': 18509, 'expands': 20597,...}
reversed_word_index 是一个字典,将word_index当中{字符:数字}的数据格式,转化为{数字:字符}的数据格式。之所以做这样的转化,是因为,我们读取的数据是处理好的(整数的形式),所以,需要以数字作为检索项,来检索字符。
reversed_word_index .get(i - 3, '?'),i-3是键值,‘?’是键值不存在的时候返回的值,也就是默认标点符号是问号(?)。这里取了train_data[0],即训练数据里面的第一组数据。’ ‘.join([char])是在字符串后面加空格并将char插入到字符串后面。我们把decoded_review打印出来:
? this film was just brilliant casting location scenery story direction everyone's really suited the part they played and you could just imagine being there robert ? is an amazing actor and now the same being director ? father came from the same scottish island as myself so i loved the fact there was a real connection with this film the witty remarks throughout the film were great it was just brilliant so much that i bought the film as soon as it was released for ? and would recommend it to everyone to watch and the fly fishing was amazing really cried at the end it was so sad and you know what they say if you cry at a film it must have been good and this definitely was also ? to the two little boy's that played the ? of norman and paul they were just brilliant children are often left out of the ? list i think because the stars that play them all grown up are such a big profile for the whole film but these children are amazing and should be praised for what they have done don't you think the whole story was so lovely because it was true and was someone's life after all that was shared with us all
这样我们就看到了从整数反向转化成评论的效果了。当然,读者需要注意的是,做反向的转化,对进行二分类而言并没有意义,我只是想通过这个过程告诉大家,整数和字符之间是一一对应的。神经网络只能对数进行操作,而无法对字符进行运算,所以我们需要将字符处理成数字的形式。当然,字符编码成数字的方法有很多种,后面的章节里我们还会有介绍。
将整数序列编码为二进制矩阵
上面一节,我们把数据下载并读取出来,现在我们要对数据进行处理。处理的目的是:将整数序列编码为二进制矩阵。
- 实现编码函数
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
numpy.zeros(shape,dtype=float,order = 'C'),其中shape是矩阵形状,即矩阵是几行几列,dtype默认值为float,order是数据存储方式,‘C’表示以行的形式存储,’F‘是以列的形式存储。所以np.zeros((len(sequences), dimension))方法的意思是,以sequences的长度为矩阵行数,以10000为矩阵列数,生成一个0矩阵。
enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标。举例说明
seasons = ['Spring', 'Summer', 'Fall', 'Winter']
list(enumerate(seasons))
输出的结果为:
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
那么我们举例来说明
for i, sequence in enumerate(sequences):
results[i, sequence] = 1.
这两行代码的含义是:
假如sequences = [1,3,2,3],results是4行4列的0矩阵。那么enumerate之后的数据为[(0,1), (1,3), (2,2), (3,3)],那么:
results[0,1] = 1.
results[1,3] = 1.
results[2,2] = 1.
resluts[3,3] = 1.
那么矩阵results可表示为
0 1 0 0
0 0 0 1
0 0 1 0
0 0 0 1
也就是说矩阵当中的每一行都能够以二进制的形式唯一的表示sequences 当中的一个数据,并且,每一行只有一位被置位,我们把这种方法叫做ONE-HOT编码。
因为我们在上面一节中获取数据的时候,只获取的前10000个常用词,所以矩阵的列数不会超过10000,所以我们定义的vectorize_sequences方法,dimension=10000。
- 对train_data和test_data进行向量化编码
x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)
- 对train_labels和test_labels进行向量化
y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(test_labels).astype('float32')
asarray方法可以将结构数据转化为ndarray(矩阵向量)。举例说明:
data1=[[1,1,1],[1,1,1],[1,1,1]]
arr3=np.asarray(data1)
arr3的结果为:
arr3:
[[1 1 1]
[1 1 1]
[1 1 1]]
建立keras二分类模型
通过上面的处理,我们已经把数据处理成矩阵向量的形式,接下来,要进入我们核心的内容了:利用keras建立二分类模型:
from keras import models
from keras import layers
model = models.Sequential() #采用Sequential模型的方式
model.add(layers.Dense(16, activation='relu', input_shape=(10000, )))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
keras有两种建立模型的方式,一种是采用keras内置的Sequential模型的方式,另外一种是采用函数式API的方式,两种模式各自有其优缺点,从入门和易用性的角度上讲,Sequential模型使用更简单;但是函数式API的方式,使用更灵活,可以根据自身需求实现不同的网络模型,达到网络最优化的目的。一般而言,如果不是自己研究一种网络模型,Sequential模型的方式足够满足日常需求。这里我们采用的就是Sequential模型的方式。
model.add方法的作用是增加一层,layers.Dense方法的作用是增加全连接层,所谓全连接的概念指的是,前一层的每一个输出,都是该层任意神经元的输入。如下图所示
layers.Dense(16, activation='relu', input_shape=(10000, ))
其中,16表示神经单元个数,relu是激活函数,input_shape是输入矩阵的形状。神经单元个数,也就是该层网络的输出个数。relu是激活函数,keras提供了很多的内置激活函数,详情可参考keras官方手册了解。也就是说,通过本行代码,增加一层单元个数为16,输入为10000,激活函数为relu的全连接层。
后面两层的增加类似,只不过,除了第一层外,其他网络层不涉及输入形状的问题,因为其输入数据的个数由前一层决定。
编译网络模型
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['accuracy'])
编译网络模型方法中,指定了模型的优化函数、损失函数以及评估函数。简单的讲,损失函数用来评估网络计算值和实际值之间的误差,优化函数通过方向传播误差的方法优化网络的权重,评估函数同损失函数类似,只不过它是用来评估网络性能的方法。一般而言,二分类问题我们选用binary_crossentropy作为损失函数。
训练网络
model.fit(x_train, y_train, epochs=20, batch_size=512)
在训练网络的方法中,epochs表示训练的次数,batch_size表示小批量随机梯度下降方法当中的批量值,也就是每512组数据计算一次平均的随机梯度下降值。
到此为止,关于二分类问题的基础代码讲解完毕,下面我们通过执行model.predict(x_test)函数来看一下,测试数据的输出结果:
[[0.01034644]
[0.9999963 ]
[0.9942436 ]
...
[0.03826523]
[0.00732365]
[0.97382474]]
我们可以发现,网络的输出结果实际上概率值,比如第一个值0.01034644,我们可以认为这条评论大概率是负面的,第二个值0.9999963,则大概率的是正面的评论。