神机喵算


  • 首页

  • 归档

  • 标签

Ubuntu上安装PyLucene

发表于 2018-09-01

机器环境:Ubuntu 16.04,Java 1.8

  1. 安装ANT:
1
sudo apt install ant
  1. 下载pylucene-4.10.1-1或者pylucene-6.5.0包,并解压。

    注意,不要去pylucene的github仓库https://github.com/svn2github/pylucene下载,其中缺少部分文件。

    当执行make命令时会报错:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    > make
    > mkdir -p lucene-java-7.4.0
    > tar -C ~/apache/lucene.git -cf - lucene | tar -C lucene-java-7.4.0 -xvf -
    > tar: ~/apache/lucene.git: Cannot open: No such file or directory
    > tar: Error is not recoverable: exiting now
    > tar: This does not look like a tar archive
    > tar: Exiting with failure status due to previous errors
    > Makefile:191: recipe for target 'lucene-java-7.4.0' failed
    > make: *** [lucene-java-7.4.0] Error 2
    >
  2. 安装JCC:

    • pylucene-4.10.1-1

      进入jcc,修改setup.py:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      JDK = {
      'darwin': JAVAHOME or JAVAFRAMEWORKS,
      'ipod': '/usr/include/gcc',
      'linux2': '/usr/lib/jvm/java-8-oracle',
      # 'linux2': '/usr/lib/jvm/java-7-openjdk-amd64',
      'sunos5': '/usr/jdk/instances/jdk1.6.0',
      'win32': JAVAHOME,
      'mingw32': JAVAHOME,
      'freebsd7': '/usr/local/diablo-jdk1.6.0'
      }

      执行下面的命令:

      1
      2
      sudo python setup.py build
      sudo python setup.py install

      验证JCC安装成功:

      1
      python -m jcc
    • pylucene-6.5.0

      进入jcc文件夹,

      执行下面的命令:

      1
      2
      sudo python setup.py build
      sudo python setup.py install

      验证JCC安装成功:

      1
      python -m jcc
  3. 安装pylucene:

    根据系统环境修改Makefile文件:

    pylucene-4.10.1-1

    1
    2
    3
    4
    5
    6
    PREFIX_PYTHON=/usr
    #ANT=JAVA_HOME=/usr/lib/jvm/java-7-openjdk-amd64 /usr/bin/ant
    ANT=JAVA_HOME=/usr/lib/jvm/java-8-oracle /usr/bin/ant
    PYTHON=$(PREFIX_PYTHON)/bin/python
    JCC=$(PYTHON) -m jcc --shared
    NUM_FILES=8

    pylucene-6.5.0

    1
    2
    3
    4
    5
    PREFIX_PYTHON=/usr
    ANT=JAVA_HOME=/usr/lib/jvm/java-8-oracle /usr/bin/ant
    PYTHON=$(PREFIX_PYTHON)/bin/python
    JCC=$(PYTHON) -m jcc --shared
    NUM_FILES=8

    执行下面的命令:

    1
    2
    make
    sudo make install

    验证lucene安装成功:

    1
    python -c "import lucene"
  4. 遇到的问题

    执行make命令时报错:

    1
    2
    3
    4
    5
    6
    7
    8
    ...
    BUILD SUCCESSFUL
    Total time: 12 seconds
    ICU not installed
    /opt/apache/pylucene/_install/bin/python -m jcc --shared --jar lucene-java-6.5.0/lucene/build/core/lucene-core-6.5.0.jar --jar lucene-java-6.5.0/lucene/build/analysis/common/lucene-analyzers-common-6.5.0.jar --jar lucene-java-6.5.0/lucene/build/memory/lucene-memory-6.5.0.jar --jar lucene-java-6.5.0/lucene/build/highlighter/lucene-highlighter-6.5.0.jar --jar build/jar/extensions.jar --jar lucene-java-6.5.0/lucene/build/queries/lucene-queries-6.5.0.jar --jar lucene-java-6.5.0/lucene/build/queryparser/lucene-queryparser-6.5.0.jar --jar lucene-java-6.5.0/lucene/build/sandbox/lucene-sandbox-6.5.0.jar --jar lucene-java-6.5.0/lucene/build/analysis/stempel/lucene-analyzers-stempel-6.5.0.jar --jar lucene-java-6.5.0/lucene/build/grouping/lucene-grouping-6.5.0.jar --jar lucene-java-6.5.0/lucene/build/join/lucene-join-6.5.0.jar --jar lucene-java-6.5.0/lucene/build/facet/lucene-facet-6.5.0.jar --jar lucene-java-6.5.0/lucene/build/suggest/lucene-suggest-6.5.0.jar --jar lucene-java-6.5.0/lucene/build/expressions/lucene-expressions-6.5.0.jar --jar lucene-java-6.5.0/lucene/build/analysis/kuromoji/lucene-analyzers-kuromoji-6.5.0.jar --jar lucene-java-6.5.0/lucene/build/misc/lucene-misc-6.5.0.jar --use_full_names --include lucene-java-6.5.0/lucene/expressions/lib/antlr4-runtime-4.5.1-1.jar --include lucene-java-6.5.0/lucene/expressions/lib/asm-5.1.jar --include lucene-java-6.5.0/lucene/expressions/lib/asm-commons-5.1.jar --package java.lang java.lang.System java.lang.Runtime --package java.util java.util.Arrays java.util.Collections java.util.HashMap java.util.HashSet java.util.TreeSet java.lang.IllegalStateException java.lang.IndexOutOfBoundsException java.util.NoSuchElementException java.text.SimpleDateFormat java.text.DecimalFormat java.text.Collator --package java.util.concurrent java.util.concurrent.Executors --package java.util.regex --package java.io java.io.StringReader --package java.nio.file java.nio.file.Path java.nio.file.Files java.nio.file.Paths --exclude org.apache.lucene.sandbox.queries.regex.JakartaRegexpCapabilities --exclude org.apache.regexp.RegexpTunnel --exclude org.apache.lucene.store.WindowsDirectory --exclude org.apache.lucene.store.NativePosixUtil --python lucene --mapping org.apache.lucene.document.Document 'get:(Ljava/lang/String;)Ljava/lang/String;' --mapping java.util.Properties 'getProperty:(Ljava/lang/String;)Ljava/lang/String;' --sequence java.util.AbstractList 'size:()I' 'get:(I)Ljava/lang/Object;' org.apache.lucene.index.IndexWriter:getReader org.apache.lucene.analysis.Tokenizer:input --version 6.5.0 --module python/collections.py --module python/ICUNormalizer2Filter.py --module python/ICUFoldingFilter.py --module python/ICUTransformFilter.py --files 8 --build
    make: /opt/apache/pylucene/_install/bin/python: Command not found
    Makefile:340: recipe for target 'compile' failed
    make: *** [compile] Error 127

    原因是:Makefile文件中的“PREFIX_PYTHON”配置错误,

    1
    PREFIX_PYTHON=/opt/apache/pylucene/_install

    解决办法:按上面的Makefile修改即可。

Enjoy!

侠天,专注于大数据、机器学习和数学相关的内容,并有个人公众号:bigdata_ny分享相关技术文章。

若发现以上文章有任何不妥,请联系我。

image

《Deep Learning with Python》第六章 6.2 理解循环神经网络(RNN)

发表于 2018-09-01

沉下心来,踏实干,会成功的。

6.2 理解循环神经网络(RNN)

前面所有见过的神经网络模型,比如,全联结网络和卷积网络,它们最主要的特征是没有记忆。每个输入被单独处理,也没有保留输入之间的状态。在这种神经网络中,要想处理序列数据或者时序数据,那就需要一次输入整个序列到神经网络模型:把整个序列当作单个数据点。例如,在IMDB的例子中,将一个完整的影评转换成一个向量,并一次性处理。我们把这类神经网络称为前向传播神经网络(feedforward network)。

相比之下,你正在读的句子,是一个词一个词的理解,并记住前一个处理的词;这给了一个句子意思很好的表示。当生物智能处理逐渐增长的信息时,它会保存正在处理信息的中间状态,建立上一个信息到当前信息的更新。

循环神经网络也采用相同的方式,尽管只是一个极其简单的版本。它通过迭代序列数据的每个元素,并保持所见过的相应信息的状态。RNN是一种内循环的神经网络,见图6.9。RNN的状态只存在于一个序列数据中,RNN处理两个不同的、不相关的序列数据时会重置状态。所以你仍然可以把一个序列数据看作单个数据点,并作为神经网络模型的一个输入。不同的是,这个数据点不再是一步处理完,而是对序列元素进行内部迭代。

image

图6.9 循环神经网络(RNN)

下面用Numpy实现一个简单的前向传播的RNN,更好的说明循环(loop)和状态(state)这些术语。该RNN输入一个形状为(时间步长,特征数)[^(timesteps, input_features)]的向量序列,随着时间步长迭代。在t个步长时,它利用当前的状态和输入(形状为(input_features, ))生成输出output。接着把下一步的状态设为前一步的输出。对于第一个时间步长来说,前一步的输出没有定义,即是没有当前状态。所以初始化第一步的状态为零向量,也称为RNN的初始状态(initial state)。

以下是RNN的伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
#Listing 6.19 Pseudocode RNN
'''The state at t
'''
state_t = 0
'''Iterates over sequence elements
'''
for input_t in input_sequence:
output_t = f(input_t, state_t)
'''The previous output becomes the state for the next iteration.
'''
state_t = output_t

你应该能直接写出上面的函数f:用两个矩阵W和U,以及一个偏置向量把输入和状态转换成输出。这类似于前向网络中全联结layer的转换操作。

1
2
3
4
5
6
#Listing 6.20 More detailed pseudocode for the RNN
state_t = 0
for input_t in input_sequence:
output_t = activation(dot(W, input_t) + dot(U, state_t) + b)
state_t = output_t

为了彻底搞清楚上面的术语,这里用原生Numpy写个前向传播的简单RNN。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#Listing 6.21 Numpy implementation of a simple RNN
import numpy as np
'''Number of timesteps in
the input sequence
'''
timesteps = 100
'''Dimensionality of the
input feature space
'''
input_features = 32
'''Dimensionality of the
output feature space
'''
output_features = 64
'''Input data: random
noise for the sake of
the example
'''
inputs = np.random.random((timesteps, input_features))
'''Initial state: an
all-zero vector
'''
state_t = np.zeros((output_features,))
'''Creates random weight matrices
'''
W = np.random.random((output_features, input_features))
U = np.random.random((output_features, output_features))
b = np.random.random((output_features,))
successive_outputs = []
'''input_t is a vector of shape (input_features,).
'''
for input_t in inputs:
'''Combines the input with the current
state (the previous output) to obtain
the current output
'''
output_t = np.tanh(np.dot(W, input_t) + np.dot(U, state_t) + b)
'''Stores this output in a list
'''
successive_outputs.append(output_t)
'''Updates the state of the
network for the next timestep
'''
state_t = output_t
'''The final output is a 2D tensor of
shape (timesteps, output_features).
'''
final_output_sequence = np.concatenate(successive_outputs, axis=0)

看起来很容易,RNN只是一个for循环,重复利用上一个循环的计算结果,仅此而已。当然,你也可以构建许多不同类型的RNN。RNN的特征是阶跃函数(step function),比如下面的函数,见图6.10::

1
output_t = np.tanh(np.dot(W, input_t) + np.dot(U, state_t) + b)

image

图6.10 一个简单的、随时间展开的RNN

注意:上面例子是在时间步长 t 的最终输出:一个形状为(时间步长,特征数)[^(timesteps, input_features)]的2D张量。在处理一个输入序列时,时间步长 t 的输出张量包含从时间步长0到t的信息。因此,在许多情况下,你并不需要所有输出的序列,只要循环(loop)的最后一个输出(output_t)即可。,因为它已包含整个序列的信息。

6.2.1 Keras中的RNN layer

前面用Numpy实现的RNN实际上是Keras的SimpleRNN layer:

1
from keras.layers import SimpleRNN

但是它俩有个小小的区别:与所有其它Keras layer一样,SimpleRNN处理的是批量序列,而不是单个序列。这意味着,SimpleRNN layer的输入形状为(批大小,时间步长,特征)[^(batch_size, timesteps, input_features)],而不是(时间步长,特征)[^(timesteps, input_features)]。

像Keras中所有RNN layer一样,SimpleRNN有两种模式:一,返回时间步长的所有输出的序列,形状为(批大小,时间步长,输出)[^(batch_size, timesteps, out_features)];二,返回每个输入的最后一个输出,(时间步长,输出)[^(timesteps, out_features)]。这两种模式可以用参数return_sequences来控制。下面来看一个简单的SimpleRNN例子,其只返回最后一个时间步长的输出。

1
2
3
4
5
6
from keras.models import Sequential
from keras.layers import Embedding, SimpleRNN
model = Sequential()
model.add(Embedding(10000, 32))
model.add(SimpleRNN(32))
model.summary()
1
2
3
4
5
6
7
8
9
10
________________________________________________________________
Layer (type) Output Shape Param #
================================================================
embedding_22 (Embedding) (None, None, 32) 320000
________________________________________________________________
simplernn_10 (SimpleRNN) (None, 32) 2080
================================================================
Total params: 322,080
Trainable params: 322,080
Non-trainable params: 0

下面是返回所有状态序列的例子。

1
2
3
4
model = Sequential()
model.add(Embedding(10000, 32))
model.add(SimpleRNN(32, return_sequences=True))
model.summary()
1
2
3
4
5
6
7
8
9
10
________________________________________________________________
Layer (type) Output Shape Param #
================================================================
embedding_23 (Embedding) (None, None, 32) 320000
________________________________________________________________
simplernn_11 (SimpleRNN) (None, None, 32) 2080
================================================================
Total params: 322,080
Trainable params: 322,080
Non-trainable params: 0

有时堆叠多层RNN layer来增加神经网络模型的表征能力。在这种情况下,必须返回中间层layer输出的所有序列:

1
2
3
4
5
6
7
8
>>> model = Sequential()
>>> model.add(Embedding(10000, 32))
>>> model.add(SimpleRNN(32, return_sequences=True))
>>> model.add(SimpleRNN(32, return_sequences=True))
>>> model.add(SimpleRNN(32, return_sequences=True))
#Last layer only returns the last output
>>> model.add(SimpleRNN(32))
>>> model.summary()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
________________________________________________________________
Layer (type) Output Shape Param #
================================================================
embedding_24 (Embedding) (None, None, 32) 320000
________________________________________________________________
simplernn_12 (SimpleRNN) (None, None, 32) 2080
________________________________________________________________
simplernn_13 (SimpleRNN) (None, None, 32) 2080
________________________________________________________________
simplernn_14 (SimpleRNN) (None, None, 32) 2080
________________________________________________________________
simplernn_15 (SimpleRNN) (None, 32) 2080
================================================================
Total params: 328,320
Trainable params: 328,320
Non-trainable params: 0

让我们将上述模型应用于IMDB影评分类问题。首先,先处理数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#Listing 6.22 Preparing the IMDB data
from keras.datasets import imdb
from keras.preprocessing import sequence
'''Number of words to
consider as features
'''
max_features = 10000
'''Cuts off texts after this many words (among
the max_features most common words)
'''
maxlen = 500
batch_size = 32
print('Loading data...')
(input_train, y_train), (input_test, y_test) = imdb.load_data(
num_words=max_features)
print(len(input_train), 'train sequences')
print(len(input_test), 'test sequences')
print('Pad sequences (samples x time)')
input_train = sequence.pad_sequences(input_train, maxlen=maxlen)
input_test = sequence.pad_sequences(input_test, maxlen=maxlen)
print('input_train shape:', input_train.shape)
print('input_test shape:', input_test.shape)

接着用Embedding layer和SimpleRNN layer训练简单的循环神经网络。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#Listing 6.23 Training the model with Embedding and SimpleRNN layers
from keras.layers import Dense
model = Sequential()
model.add(Embedding(max_features, 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, y_train,
epochs=10,
batch_size=128,
validation_split=0.2)

下面显示训练和验证的损失和准确度,见图6.11和6.12。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#Listing 6.24 Plotting results
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()

image

图6.11 在IMDB影评集上使用SimpleRNN训练和验证的损失曲线

image

图6.12 在IMDB影评集上使用SimpleRNN训练和验证的准确度曲线

在第三章中实现的方法得到的测试准确度为88%。不幸的是,上面简单的RNN的结果竟然没有baseline的好(只有85%的验证准确度)。一部分原因是,输入文本只考虑了前500个词,而不是整个文本序列。因此RNN只获取到少量的信息;另一个问题是,SimpleRNN并不擅长处理长序列,比如文本。

这就要开始介绍高级循环神经网络了。

6.2.2 理解LSTM和GRU layer

SimpleRNN不是Keras中唯一的循环神经网络,其它两个是LSTM和GRU。一般实践中会使用后面两个循环神经网络中的一种。SimpleRNN有一个主要的问题:虽然理论上它在 t 时刻会保持 t 之前所有时刻的输入信息,但是实际是由于依赖太长而学习不到。这是由于梯度爆炸问题导致(vanishing gradient problem),随着层数加深时模型训练失败,具体理论原因由Hochreiter,Schmidhuber和Bengio在1990年代提出,LSTM和GRU layer就是为解决该问题而设计的。

LSTM(Long Short-Term Memory)算法是由Hochreiter和Schmidhuber在1997年开发的,它是SimpleRNN layer的一个变种,增加了跨时间步长的信息记忆。LSTM的本质是为后续时刻保持信息,防止处理过程中老信号的逐渐消失。

为了更好的讲解细节,我们从图6.13的SimpleRNN单元开始。由于权重矩阵较多,这里的output表达式中用字母o作为矩阵W和U的索引(Wo和Uo)。

image

图6.13 LSTM layer的起点:SimpleRNN

接着在上面的图中增加一条携带跨时间步长的信息流,用Ct表示,这里C表示carry。这个信息流的影响:它将整合输入连接和循环连接,影响输入到下一个时间步长的状态。相应的,carry信息流会调整下一个输出和下一个状态,见图6.14,就这么简单。

image

图6.14 从SimpleRNN到LSTM:增加一个carry track

计算下一时刻的carry信息流稍有不同,它涉及到三个不同的变换,类似SimpleRNN单元的表达形式:

1
y = activation(dot(state_t, U) + dot(input_t, W) + b)

但是这三个变换都有自己的权重矩阵,分别用字母i,f和k索引。如下:

1
2
3
4
5
6
7
#Listing 6.25 Pseudocode details of the LSTM architecture (1/2)
output_t = activation(dot(state_t, Uo) + dot(input_t, Wo) + dot(C_t, Vo) + bo)
i_t = activation(dot(state_t, Ui) + dot(input_t, Wi) + bi)
f_t = activation(dot(state_t, Uf) + dot(input_t, Wf) + bf)
k_t = activation(dot(state_t, Uk) + dot(input_t, Wk) + bk)

计算新的carry状态c_t是综合i_t,f_t 和k_t。

1
2
3
#Listing 6.26 Pseudocode details of the LSTM architecture (2/2)
c_t+1 = i_t * k_t + c_t * f_t

将上面的过程添加到图6.15上,这就得到了LSTM,不复杂。

image

图5.15 LSTM的剖析图

LSTM的实际物理意义:c_t和f_t相乘可以认为是carry信息流中遗忘不相关的信息;同时,i_t和k_t提供当前信息,并更新carry track。时至今日,其实这些解释并不太重要,因为这些操作由参数化的权重决定,通过多轮训练学习权重。RNN单元的规格决定模型的假设空间,但这不能决定模型单元做什么,它取决于单元的权重。对于相同的模型单元,不同的权重意味着模型做的事情完全不同。所以组成RNN单元的操作可以解释为一系列的约束,而不是工程意义上的设计。

6.2.3 Keras中LSTM实践

下面使用LSTM layer在IMDB数据集上训练模型,见图6.16和6.17。神经网络结构与前面的SimpleRNN类似,你只需要设置LSTM layer的输出维度,其它参数使用默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#Listing 6.27 Using the LSTM layer in Keras
from keras.layers import LSTM
model = Sequential()
model.add(Embedding(max_features, 32))
model.add(LSTM(32))
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['acc'])
history = model.fit(input_train, y_train,
epochs=10,
batch_size=128,
validation_split=0.2)

image

图6.16 在IMDB影评集上使用LSTM训练和验证的损失曲线

image

图6.17 在IMDB影评集上使用LSTM训练和验证的准确度曲线

从上面的曲线可以看出,LSTM模型达到了89%的验证准确度。不算太差,比SimpleRNN神经网络模型好点(主要是因为LSTM解决了梯度消失的问题),也比第三章的全联结方法要好(即使比第三章用的数据少)。

但是为啥这次结果也没太好?其中的一个原因是,没有进行超参调优,比如词嵌入维度或者LSTM的输出维度。另外一个是,缺乏规则化。但是,说老实话,分析长影评并不能有效的解决情感分析问题。该问题的解决办法是在影评中计算词频,这也是第一个全联结方法所做的。

6.2.4 小结

本小节所学到的知识点:

  • 什么是RNN?以及如何工作?
  • LSTM是什么?它为什么在处理长序列上比原生RNN效果好?
  • 如何使用Keras的RNN layer处理序列数据

未完待续。。。

Enjoy!

翻译本书系列的初衷是,觉得其中把深度学习讲解的通俗易懂。不光有实例,也包含作者多年实践对深度学习概念、原理的深度理解。最后说不重要的一点,François Chollet是Keras作者。
声明本资料仅供个人学习交流、研究,禁止用于其他目的。如果喜欢,请购买英文原版。


侠天,专注于大数据、机器学习和数学相关的内容,并有个人公众号:bigdata_ny分享相关技术文章。

若发现以上文章有任何不妥,请联系我。

image

《Deep Learning with Python》第六章 6.1 深度学习之文本处理

发表于 2018-08-13
6.1 深度学习之文本处理

文本是序列数据传播最广泛的形式之一,它可以理解成一个字母序列或者词序列,但是最常见的形式是词序列。后面章节介绍的深度学习序列处理模型有文档分类、情感分析、作者识别和限制语境问答(QA)。当然了,要记住的是:这些深度学习模型并不是真正意义上以人的思维去理解文字,而只是书面语的统计结构映射而已。基于深度学习的自然语言处理可以看作对字词、句子和段落的模式识别,这有点像计算机视觉中对像素的模式识别。

跟其它所有神经网络一样,深度学习模型并不是以原始文本为输入,而是数值型张量。向量化文本是将文本转换成数值张量的过程。有以下几种方式可以做向量化文本:

  • 将文本分割为词,转换每个词为向量;
  • 将文本分割为字(字母),转换每个字为向量;
  • 抽取词或者字的n-gram,转换每个n-gram转换为向量。n-gram是多个连续词或者字的元组。

将文本分割为字、词或者n-gram的过程称为分词(tokenization),拆分出来的字、词或者n-gram称为token。所有文本向量化的过程都包含分词和token转换为数值型向量。这些向量封装成序列张量“喂入”神经网络模型。有多种方式可以将token转换为数值向量,但是本小节介绍两种方法:one-hot编码和词嵌入。

image

图6.1 文本向量化过程

n-gram和词袋的理解

n-gram是指从句子中抽取的N个连续词的组合。对于字也有相同的概念。

下面是一个简单的例子。句子“the cat sat on the mat”拆分成2-gram的集合如下:

{“The”, “The cat”, “cat”, “cat sat”, “sat”, “sat on”, “on”, “on the”, “the”, “the mat”, “mat”}

拆分成3-gram的集合如下:

{“The”, “The cat”, “cat”, “cat sat”, “The cat sat”, “sat”, “sat on”, “on”, “cat sat on”, “on the”, “the”, “sat on the”, “the mat”, “mat”, “on the mat”}

上面这些集合相应地称为2-gram的词袋,3-gram的词袋。术语词袋(bag)是指token的集合,而不是一个列表或者序列:token是无序的。所有分词方法的结果统称为词袋。

词袋是一个无序的分词方法,其丢失了文本序列的结构信息。词袋模型用于浅语言处理模型中,而不是深度学习模型。抽取n-gram是一种特征工程,但是深度学习是用一种简单粗暴的方法做特征工程,去代替复杂的特征工程。本章后面会讲述一维卷积和RNN,它们能从字、词的组合中学习表征。所以本书不再进一步展开介绍n-gram。但是记住,在轻量级模型或者浅文本处理模型(逻辑回归和随机森林)中,n-gram是一个强有力、不可替代的特征工程工具。

6.1.1 字词的one-hot编码

one-hot编码是最常见、最基本的文本向量化方法。在前面第三章的IMDB和Reuter例子中有使用过。one-hot编码中每个词有唯一的数值索引,然后将对应的索引转成大小为N的二值向量(N为字典的大小):词所对应的索引位置的值为1,其它索引对应的值为0。

当然,字级别也可以做one-hot编码。为了予以区分,列表6.1和6.2分别展示词和字的one-hot编码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#Listing 6.1 Word-level one-hot encoding
import numpy as np
'''
Initial data: one entry per sample (in this example,
a sample is a sentence,
but it could be an entire document)
'''
samples = ['The cat sat on the mat.', 'The dog ate my homework.']
'''
Builds an index of all tokens in the data
'''
token_index = {}
for sample in samples:
'''
Tokenizes the samples via the split method.
In real life, you’d also strip punctuation
and special characters from the samples.
'''
for word in sample.split():
if word not in token_index:
'''
Assigns a unique index to each unique word.
Note that you don’t attribute index 0 to anything.
'''
token_index[word] = len(token_index) + 1
'''
Vectorizes the samples. You’ll only consider
the first max_length words in each sample.
'''
max_length = 10
'''
This is where you store the results.
'''
results = np.zeros(shape=(len(samples),
max_length,
max(token_index.values()) + 1))
for i, sample in enumerate(samples):
for j, word in list(enumerate(sample.split()))[:max_length]:
index = token_index.get(word)
results[i, j, index] = 1.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#Listing 6.2 Character-level one-hot encoding
import string
samples = ['The cat sat on the mat.', 'The dog ate my homework.']
'''
All printable ASCII characters
'''
characters = string.printable
token_index = dict(zip(range(1, len(characters) + 1), characters))
max_length = 50
results = np.zeros((len(samples), max_length, max(token_index.keys()) + 1))
for i, sample in enumerate(samples):
for j, character in enumerate(sample):
index = token_index.get(character)
results[i, j, index] = 1.

Keras有内建工具处理文本的one-hot编码。建议你使用这些工具,因为它们有不少功能,比如,删除指定字符,考虑数据集中最常用的N个字(严格来讲,是避免向量空间过大)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#Listing 6.3 Using Keras for word-level one-hot encoding
from keras.preprocessing.text import Tokenizer
samples = ['The cat sat on the mat.', 'The dog ate my homework.']
'''
Creates a tokenizer, configured to only take into account the 1,000 most common words
'''
tokenizer = Tokenizer(num_words=1000)
'''
Builds the word index
'''
tokenizer.fit_on_texts(samples)
'''
Turns strings into lists of integer indices
'''
sequences = tokenizer.texts_to_sequences(samples)
'''
You could also directly get the one-hot binary representations. Vectorization modes other than one-hot encoding are supported by this tokenizer.
'''
one_hot_results = tokenizer.texts_to_matrix(samples, mode='binary')
'''
How you can recover the word index that was computed
'''
word_index = tokenizer.word_index
print('Found %s unique tokens.' % len(word_index))

one-hot 哈希(hash)编码是one-hot编码的一个变种,它主要用在字典太大难以处理的情况。one-hot 哈希编码是将词通过轻量级的哈希算法打散成固定长度的向量,而不是像one-hot编码将每个词分配给一个索引。one-hot 哈希编码最大的优势是节省内存和数据的在线编码。同时这种方法的一个缺点是碰到哈希碰撞冲突(hash collision),也就是两个不同词的哈希值相同,导致机器学习模型不能分辨这些词。哈希碰撞冲突的 可能性会随着哈希空间的维度越大而减小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#Listing 6.4 Word-level one-hot encoding with hashing trick
samples = ['The cat sat on the mat.', 'The dog ate my homework.']
'''
Stores the words as vectors of size 1,000. If you have close to 1,000 words (or more), you’ll see many hash collisions, which will decrease the accuracy of this encoding method.
'''
dimensionality = 1000
max_length = 10
results = np.zeros((len(samples), max_length, dimensionality))
for i, sample in enumerate(samples):
for j, word in list(enumerate(sample.split()))[:max_length]:
'''
Hashes the word into a random integer index
between 0 and 1,000
'''
index = abs(hash(word)) % dimensionality
results[i, j, index] = 1.
6.1.2 词嵌入

另外一种常用的、高效的文本向量化方法是稠密词向量,也称为词嵌入。one-hot编码得到的向量是二值的、稀疏的(大部分值为0)、高维度的(与字典的大小相同),而词嵌入是低维度的浮点型向量(意即,稠密向量),见图6.2。前面的向量是通过one-hot编码得到的,而词嵌入是由数据学习得到,最常见的词嵌入是256维、512维或者1024维。one-hot编码会导致向量的维度甚至超过20,000维(此处以20,000个词的字典举例)。所以词嵌入能够用更少的维度表示更多的信息。

image

图6.2 one-hot编码和词嵌入得到的向量对比

有两种获得词嵌入的方式:

  • 在解决文档分类或者情感预测的任务中学习词嵌入。一般以随机词向量维开始,然后在训练神经网络模型权重的过程中学习到词向量。
  • 加载预训练的词向量。预训练的词向量一般是从不同于当前要解决的机器学习任务中学习得到的。

下面学习前面的两种方法。

学习词嵌入:Embedding layer

词与稠密向量相关联的最简单方法是随机向量化。但是,这种方法使得嵌入空间变得毫无结构:比如,单词accurate和exact在大部分句子里是可互换的,但得到的嵌入可能完全不同。深度神经网络很难识别出这种噪音和非结构嵌入空间。

更抽象一点的讲,词与词之间的语义相似性在词向量空间中应该以几何关系表现出来。词嵌入可以理解成是人类语言到几何空间的映射过程。例如,你会期望同义词被嵌入为相似的词向量;更一般地说,你期望任意两个词向量的几何距离(比如,L2距离)和相关词的语义距离是有相关性。除了距离之外,词向量在嵌入空间的方向也应该是有意义的。下面举个具体的例子来说明这两点。

image

图6.3 词嵌入空间的实例

在图6.3中,cat、dog、wolf和tiger四个词被嵌入到二维平面空间。在这里选择的词向量表示时,这些词的语义关系能用几何变换来编码表示。比如,从cat到tiger和从dog到wolf有着相同的向量,该向量可以用“从宠物到野生动物”来解释。同样,从dog到cat和从wolf到tiger有相同的向量,该向量表示“从犬科到猫科动物”。

在实际的词嵌入空间中,常见的几何变换例子是“gender”词向量和“plural”词向量。比如,将“female”词向量加到“king”词向量上,可以得到“queen”词向量;将“plural”词向量加到“king”词向量上,可以得到“kings”词向量。

那接下来就要问了,有完美的词向量空间能匹配人类语言吗?能用来解决任意种类的自然语言处理任务吗?答案是可能有,但是现阶段暂时没有。也没有一种词向量可以向人类语言一样有很多种语言,并且是不同形的,因为它们都是在特定文化和特定环境下形成的。但是,怎么才能得到一个优秀的词嵌入空间呢?从程序实现上讲是因任务而异:英文影评情感分析模型对应完美词嵌入空间与英文文档分类模型对应的完美词嵌入空间可能不同,因为不同任务的语义关系重要性是变化的。

因此,对每个新任务来说,最好重新学习的词嵌入空间。幸运的是,反向传播算法和Keras使得学习词嵌入变得容易。下面学习Keras的Embedding layer权重。

1
2
3
4
5
6
7
8
#Listing 6.5 Instantiating an Embedding layer
from keras.layers import Embedding
'''
The Embedding layer takes at least two arguments: the number of possible tokens (here, 1,000: 1 + maximum word index) and the dimensionality of the embeddings (here, 64).
'''
embedding_layer = Embedding(1000, 64)

Embedding layer把词的整数索引映射为稠密向量。它输入整数,在中间字典中查找这些整数对应的向量。Embedding layer是一个高效的字典查表(见图6.4)。

image

图6.4 Embedding layer

Embedding layer的输入是一个形状为(样本,序列长度)[^(sample,sequence_length)]的 2D 整数型张量,该张量的每项都是一个整数序列。Embedding layer能嵌入变长序列:比如,可以“喂入”形状为(32,10)(长度为10的序列数据,32个为一个batch)或者(64,15)(长度为15的序列数据64个为一个batch)。同一个batch中的所有序列数据必须有相同的长度,因为它们会被打包成一个张量。所以比其它序列数据短的序列将用“0”填充,另外,太长的序列会被截断。

Embedding layer返回一个形状为(样本,序列长度,词向量大小)[^(samples,sequence_ length,embedding_dimensionality)]的3D浮点型张量,该张量可以被RNN layer或者1D 卷积layer处理。

当你实例化一个Embedding layer时,它的权重(词向量的中间字典)是随机初始化,和其它layer一样。随着模型的训练,这些词向量通过反向传播算法逐渐调整,传入下游模型使用。一旦模型训练完,嵌入空间会显现出许多结构,不同的模型会训练出不同的特定结构。

下面用熟悉的IMDB影评情感预测任务来说明上面的想法。首先,准备数据集。限制选取词频为top 10,000的常用词,只考虑影评前20个词。神经网络模型将学习8维的词嵌入,把输入的整数序列(2D整数张量)转化为嵌入序列(3D浮点张量)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#Listing 6.6 Loading the IMDB data for use with an Embedding layer
from keras.datasets import imdb
from keras import preprocessing
'''
Number of words to consider as features
'''
max_features = 10000
'''
Cuts off the text after this number of words (among the max_features most common words)
'''
maxlen = 20
'''
Loads the data as lists of integers
'''
(x_train, y_train), (x_test, y_test) = imdb.load_data( num_words=max_features)
'''
Turns the lists of integers into a 2D integer tensor of shape (samples, maxlen)
'''
x_train = preprocessing.sequence.pad_sequences(x_train, maxlen=maxlen)
x_test = preprocessing.sequence.pad_sequences(x_test, maxlen=maxlen)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#Listing 6.7 Using an Embedding layer and classifier on the IMDB data
from keras.models import Sequential
from keras.layers import Flatten, Dense
model = Sequential()
'''
Specifies the maximum input length to the Embedding layer so you can later flatten the embedded inputs. After the Embedding layer, the activations have shape (samples, maxlen, 8).
'''
model.add(Embedding(10000, 8, input_length=maxlen))
'''
Flattens the 3D tensor of embeddings into a 2D tensor of shape (samples, maxlen * 8)
'''
model.add(Flatten())
'''
Adds the classifier on top
'''
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
model.summary()
history = model.fit(x_train, y_train,
epochs=10,
batch_size=32,
validation_split=0.2)

上面的代码得到了约76%的验证准确度,这对于只考虑每个影评的前20个词来说效果已经不错了。注意,仅仅摊平嵌入序列,用单个Dense layer训练模型,会将输入序列的每个词隔离开,并没有考虑词之间的关系和句子结构(例如,该模型可能认为“this movie is a bomb”和“this movie is the bomb” 两句话都是负面影评)。所以在嵌入序列之上加入RNN layer或者1D卷积layer会将句子当做整体来学习特征,后续小节会详细讲解这些。

预训练的词嵌入

有时,你只有很少的训练数据集来学习词嵌入,那怎么办呢?

你可以加载预计算好的词嵌入向量,而不用学习当前待解决任务的词嵌入。这些预计算好的词嵌入是高结构化的,具有有用的特性,其学习到了语言结构的泛化特征。在自然语言处理中使用预训练的词嵌入的基本理论,与图像分类中使用预训练的卷积网络相同:当没有足够的合适数据集来学习当前任务的特征时,你会期望从通用的视觉特征或者语义特征中学到泛化特征。

一些词嵌入是用词共现矩阵统计计算,用各种技术,有些涉及神经网络,有些没有。用非监督的方法计算词的稠密的、低维度的嵌入空间是由Bengio在2000年提出的,但是直到2013年Google的Tomas Mikolov开发出著名的Word2vec算法才开始在学术研究和工业应用上广泛推广。Word2vec可以获取语义信息。

Keras的Embedding layer有各种预训练词嵌入数据可以下载使用,Word2vec是其中之一。另外一个比较流行的词表示是GloVe(Global Vector),它是由斯坦福研究组在2014开发。GloVe是基于词共现矩阵分解的一种词嵌入技术,它的开发者预训练好了成千上万的词嵌入。

下面开始学习如何在Keras模型中使用GloVe词嵌入。其实它的使用方法与Word2vec词嵌入或者其它词嵌入数据相同。

6.1.3 从原始文本到词嵌入

这里的模型网络和上面的类似,只是换作预训练词嵌入。同时,直接从网上下载原始文本数据,而不是使用Keras分词好的IMDB数据。

下载IMDB原始文本

首先,前往http://mng.bz/0tIo下载原IMDB数据集,并解压。

接着,将单个训练影评装载为字符串列表,同时影评label装载为label的列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#Listing 6.8 Processing the labels of the raw IMDB data
import os
imdb_dir = '/Users/fchollet/Downloads/aclImdb'
train_dir = os.path.join(imdb_dir, 'train')
labels = []
texts = []
for label_type in ['neg', 'pos']:
dir_name = os.path.join(train_dir, label_type)
for fname in os.listdir(dir_name):
if fname[-4:] == '.txt':
f = open(os.path.join(dir_name, fname))
texts.append(f.read())
f.close()
if label_type == 'neg':
labels.append(0)
else:
labels.append(1)

分词

开始向量化文本,准备训练集和验证集。因为预训练的词嵌入是对训练集较少时更好,这里加入步骤:取前200个样本数据集。所以你相当于只看了200条影评就开始做影评情感分类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#Listing 6.9 Tokenizing the text of the raw IMDB data
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import numpy as np
'''
Cuts off reviews after 100 words
'''
maxlen = 100
'''
Trains on 200 samples
'''
training_samples = 200
'''
Validates on 10,000 samples
'''
validation_samples = 10000
'''
Considers only the top 10,000 words in the dataset
'''
max_words = 10000
tokenizer = Tokenizer(num_words=max_words)
tokenizer.fit_on_texts(texts)
sequences = tokenizer.texts_to_sequences(texts)
word_index = tokenizer.word_index
print('Found %s unique tokens.' % len(word_index))
data = pad_sequences(sequences, maxlen=maxlen)
labels = np.asarray(labels)
print('Shape of data tensor:', data.shape)
print('Shape of label tensor:', labels.shape)
'''
Splits the data into a training set and a validation set, but first shuffles the data, because you’re starting with data in which samples are ordered (all negative first, then all positive)
'''
indices = np.arange(data.shape[0])
np.random.shuffle(indices)
data = data[indices]
labels = labels[indices]
x_train = data[:training_samples]
y_train = labels[:training_samples]
x_val = data[training_samples: training_samples + validation_samples]
y_val = labels[training_samples: training_samples + validation_samples]

下载GloVe词嵌入

前往https://nlp.stanford.edu/projects/glove下载预训练的2014年英文维基百科的GloVe词嵌入。它是一个822 MB的glove.6B.zip文件,包含400,000个词的100维嵌入向量。

预处理GloVe嵌入

下面解析解压的文件(a.txt)来构建索引,能将词映射为向量表示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#Listing 6.10 Parsing the GloVe word-embeddings file
glove_dir = '/Users/fchollet/Downloads/glove.6B'
embeddings_index = {}
f = open(os.path.join(glove_dir, 'glove.6B.100d.txt'))
for line in f:
values = line.split()
word = values[0]
coefs = np.asarray(values[1:], dtype='float32')
embeddings_index[word] = coefs
f.close()
print('Found %s word vectors.' % len(embeddings_index))

接着,构建能载入Embedding layer的嵌入矩阵。它的矩阵形状为(max_words, embedding_dim),其每项i是在参考词索引中为i的词对应的embedding_dim维向量。注意,索引0不代表任何词,只是个占位符。

1
2
3
4
5
6
7
8
9
10
11
12
13
#Listing 6.11 Preparing the GloVe word-embeddings matrix
embedding_dim = 100
embedding_matrix = np.zeros((max_words, embedding_dim))
for word, i in word_index.items():
if i < max_words:
embedding_vector = embeddings_index.get(word)
if embedding_vector is not None:
'''
Words not found in the embedding index will be all zeros.
'''
embedding_matrix[i] = embedding_vector

定义模型

使用前面相同的模型结构。

1
2
3
4
5
6
7
8
9
10
#Listing 6.12 Model definition
from keras.models import Sequential
from keras.layers import Embedding, Flatten, Dense
model = Sequential()
model.add(Embedding(max_words, embedding_dim, input_length=maxlen)) model.add(Flatten())
model.add(Dense(32, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.summary()

加载GloVe词嵌入

Embedding layer有一个权重矩阵:2D浮点型矩阵,每项i表示索引为i的词对应的词向量。在神经网络模型中加载GloVe词嵌入到Embedding layer

1
2
3
4
#Listing 6.13 Loading pretrained word embeddings into the Embedding layer
model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = False

此外,设置trainable为False,冻结Embedding layer。当一个模型的部分网络是预训练的(像Embedding layer)或者随机初始化(像分类),那该部分网络在模型训练过程中不能更新,避免模型忘记已有的特征。随机初始化layer会触发大的梯度更新,导致已经学习的特征丢失。

训练和评估模型

编译和训练模型。

1
2
3
4
5
6
7
8
9
10
#Listing 6.14 Training and evaluation
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['acc'])
history = model.fit(x_train, y_train,
epochs=10,
batch_size=32,
validation_data=(x_val, y_val))
model.save_weights('pre_trained_glove_model.h5')

现在绘制模型随时间的表现,见图6.5和6.6。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#Listing 6.15 Plotting the results
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()

image

图6.5 使用预训练词嵌入时的训练损失和验证损失曲线

image

图6.6 使用预训练词嵌入时的训练准确度和验证准确度曲线

模型训练在开始不久即出现过拟合,这在训练集较少的情况下很常见。验证准确度有高的variance,不过也到50%了。

可能你的结果不同:因为训练集太少,导致模型效果严重依赖被选择的200个样本(这里选择是随机的)。

你也可以在不加载预训练词嵌入和不冻结embedding layer的情况下训练相同的网络模型。训练集也使用前面相同的200个样本,见图6.7和6.8。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#Listing 6.16 Training the same model without pretrained word embeddings
from keras.models import Sequential
from keras.layers import Embedding, Flatten, Dense
model = Sequential()
model.add(Embedding(max_words, embedding_dim, input_length=maxlen)) model.add(Flatten())
model.add(Dense(32, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.summary()
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['acc'])
history = model.fit(x_train, y_train,
epochs=10,
batch_size=32,
validation_data=(x_val, y_val))

image

图6.7 未使用预训练词嵌入时的训练损失和验证损失曲线

image

图6.8 未使用预训练词嵌入时的训练准确度和验证准确度曲线

这次的结果显示验证准确度不到50%。所以样本量较少的情况下,预训练词嵌入效果更优。

最后,在测试数据集上评估模型。首先,对测试数据进行分词。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#Listing 6.17 Tokenizing the data of the test set
test_dir = os.path.join(imdb_dir, 'test')
labels = []
texts = []
for label_type in ['neg', 'pos']:
dir_name = os.path.join(test_dir, label_type)
for fname in sorted(os.listdir(dir_name)):
if fname[-4:] == '.txt':
f = open(os.path.join(dir_name, fname))
texts.append(f.read())
f.close()
if label_type == 'neg':
labels.append(0)
else:
labels.append(1)
sequences = tokenizer.texts_to_sequences(texts)
x_test = pad_sequences(sequences, maxlen=maxlen)
y_test = np.asarray(labels)

接着,加载并评估第一个模型。

1
2
3
#Listing 6.18 Evaluating the model on the test set
model.load_weights('pre_trained_glove_model.h5') model.evaluate(x_test, y_test)

返回测试准确度56%的结果。

6.1.4 小结

你学到的知识有:

  • 文本分词
  • 使用Keras的Embedding layer学习特定的词嵌入
  • 使用预训练的词嵌入提升自然语言处理问题

未完待续。。。

Enjoy!

翻译本书系列的初衷是,觉得其中把深度学习讲解的通俗易懂。不光有实例,也包含作者多年实践对深度学习概念、原理的深度理解。最后说不重要的一点,François Chollet是Keras作者。
声明本资料仅供个人学习交流、研究,禁止用于其他目的。如果喜欢,请购买英文原版。


侠天,专注于大数据、机器学习和数学相关的内容,并有个人公众号:bigdata_ny分享相关技术文章。

若发现以上文章有任何不妥,请联系我。

image

《Deep Learning with Python》第三章 3.6 走进神经网络之房价预测

发表于 2018-07-04
3.6 房价预测:线性回归

前面两个例子都可以看成是分类问题,它的目标是预测某个输入数据点的单个离散label。常见的另外一类机器学习问题是线性回归,其预测的是一个连续值,而不是离散label。比如,根据气象信息预测明天的气温;根据软件项目计划书预测实现时间。

注意:不要混淆线性回归和逻辑回归算法。逻辑回归不是回归算法,而是分类算法。

3.6.1 波士顿房价数据集

波士顿房价数据集是1970年代中期波士顿郊区的数据样本,包含犯罪率、不动产税税率等。你将用该数据集预测当地房价的中间价。波士顿房价数据集与前面两个例子都不太一样,数据样本点相当少:只有506个,其中404个作为训练样本和102个测试样本。输入数据的每个特征都有不同的scale。例如,一些比例值,取值范围在0和1之间;另一些取值在1到12之间;还有些取值在0到100之间,等等。

1
2
3
#Listing 3.24 Loading the Boston housing dataset
from keras.datasets import boston_housing
(train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()

查看数据样本:

1
2
3
4
>>> train_data.shape
(404, 13)
>>> test_data.shape
(102, 13)

从上面返回的结果可以看出,有404个训练样本和102个测试样本,每个样本有13个数值型特征,比如犯罪率,每个住处的房屋平均数量,高速可达性,等等。

target是自住房屋的中间价,单位为千美元:

1
2
>>> train_targets
[ 15.2, 42.3, 50. ... 19.4, 19.4, 29.1]

房价普遍在\$10000和\$50000之间。价格看起来便宜,但是要记住这是1970年代中期。

3.6.2 准备数据

把不同取值范围的变量值赋给神经网络会出现问题。虽然神经网络会自适应各种各样的数据,但是这会造成模型学习的过程变得困难。一个广泛使用的最佳实践是对此类数据集特征进行归一化:对输入数据的每个特征(输入矩阵的一个列),减去该特征的均值并除以标准方差,这样每个特征的均值为0、均方差为1。

1
2
3
4
5
6
7
#Listing 3.25 Normalizing the data
mean = train_data.mean(axis=0)
train_data -= mean
std = train_data.std(axis=0)
train_data /= std
test_data -= mean
test_data /= std

注意,测试集的归一化使用的是训练集的均值和均方差。一般来说,你不用对测试集数据进行数量计算,即使是简单的数据归一化。

3.6.3 构建神经网络

由于这个例子中的样本量比较少,所以选用两个隐藏单元为64的hidden layer的神经网络。一般来说,训练数据集越小,过拟合的情况越糟糕。这里使用小规模神经网络以减轻过拟合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#Listing 3.26 Model definition
from keras import models
from keras import layers
def build_model():
'''
Because you’ll need to instantiate
the same model multiple times, you
use a function to construct it.
'''
model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(train_data.shape[1],)))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(1))
model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
return model

上面的神经网络以单元大小为1且不带激活函数的Dense layer为结束,意味着这是标量回归(线性回归)。应用激活函数可以限制输出结果的取值范围,比如,如果你在最后一个layer应用sigmoid激活函数,神经网络只会学习0到1之间的预测值。本例中最后一个layer是纯线性的,神经网络学习任意取值范围的预测值。

神经网络模型编译使用的mse损失函数:均方误差(mean squared error)。其损失函数广泛应用在回归问题当中。

你也可以在模型训练中使用平均绝对误差MAE(mean absolute error)。它监测的是预测值和目标值差值的绝对值,比如,MAE为0.5意味着你的预测值偏离均值$500。

3.6.4 使用K-fold验证模型

为了评估神经网络模型的同时调整网络模型超参(比如模型训练的epoch数量),你可以将数据集分割成训练集和验证集。但是由于数据样本点太少,所以验证集数量也非常少(比如,大约100个样本)。这会导致验证集上的分数更多的依赖于你选择哪部分数据作为验证集哪部分数据作为训练集:可能因为分割到验证集的数据点不同而产生较高的variance。这会影响模型评估的真实性。

解决上面问题的最佳方法是使用K-fold交叉验证,见图3.11。它将数据样本点分割为K个partition(一般K=4或者5),实例化K个相同的模型,分别在K-1个partition数据集训练每个模型并在余下的partition数据集上进行评估。然后将得到的K个模型的验证集上的分数求平均。下面的代码看起来更直白。

image

图3.11 3-fold交叉验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#Listing 3.27 K-fold validation
import numpy as np
k = 4
num_val_samples = len(train_data) // k
num_epochs = 100
all_scores = []
for i in range(k):
print('processing fold #', i)
'''
Prepares the validation data: data from partition #k
'''
val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples]
val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]
'''
Prepares the training data: data from all other partitions
'''
partial_train_data = np.concatenate(
[train_data[:i * num_val_samples],
train_data[(i + 1) * num_val_samples:]], axis=0)
partial_train_targets = np.concatenate(
[train_targets[:i * num_val_samples],
train_targets[(i + 1) * num_val_samples:]], axis=0)
'''
Builds the Keras model (already compiled)
'''
model = build_model()
'''
Trains the model (in silent mode, verbose = 0)
'''
model.fit(partial_train_data, partial_train_targets, epochs=num_epochs,
batch_size=1, verbose=0)
'''
Evaluates the model on the validation data
'''
val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=0)
all_scores.append(val_mae)

设置num_epochs = 100,运行得到如下的结果:

1
2
3
4
>>> all_scores
[2.588258957792037, 3.1289568449719116, 3.1856116051248984, 3.0763342615401386]
>>> np.mean(all_scores)
2.9947904173572462

每次运行的确得到不同的验证分数,从2.6到3.2。平均值为3.0,这比任意单个分数都要稳定,这也是K-fold交叉验证的价值所在。在本例中,偏离平均的大小为\$3000,这明显对于\$10000到\$50000范围的价格还是可以的。

下面将神经网络模型训练epoch设为500。修改迭代训练的代码保存每个epoch的验证分数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#Listing 3.28 Saving the validation logs at each fold
num_epochs = 500
all_mae_histories = []
for i in range(k):
print('processing fold #', i)
'''
Prepares the validation data: data from partition #k
'''
val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples]
val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]
'''
Prepares the training data: data from all other partitions
'''
partial_train_data = np.concatenate(
[train_data[:i * num_val_samples],
train_data[(i + 1) * num_val_samples:]], axis=0)
partial_train_targets = np.concatenate(
[train_targets[:i * num_val_samples],
train_targets[(i + 1) * num_val_samples:]], axis=0)
'''
Builds the Keras model (already compiled)
'''
model = build_model()
'''
Trains the model (in silent mode, verbose = 0)
'''
history = model.fit(partial_train_data, partial_train_targets,
epochs=num_epochs,
validation_data=(val_data, val_targets),
batch_size=1, verbose=0)
mae_history = history.history['val_mean_absolute_error']
all_mae_histories.append(mae_history)

接下来计算每个epoch的K个fold所对应的MAE的平均值:

1
2
3
#Listing 3.29 Building the history of successive mean K-fold validation scores
average_mae_history = [
np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)]

下面绘制验证分数图表,见3.12。

1
2
3
4
5
6
7
#Listing 3.30 Plotting validation scores
import matplotlib.pyplot as plt
plt.plot(range(1, len(average_mae_history) + 1), average_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()

image

图3.12 每个epoch验证集MAE的趋势图

看上面的图表可能有点困难,因为数量级的问题和相当高的variance。下面对其进行处理:

  • 忽略前十个数据点,因为它们与曲线上剩余的值不在同一个量级上;
  • 用每个数据点的指数滑动平均值代替原数据点,来平滑曲线

对应的结果见图3.13。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#Listing 3.31 Plotting validation scores, excluding the first 10 data points
def smooth_curve(points, factor=0.9):
smoothed_points = []
for point in points:
if smoothed_points:
previous = smoothed_points[-1]
smoothed_points.append(previous * factor + point * (1 - factor))
else:
smoothed_points.append(point)
return smoothed_points
smooth_mae_history = smooth_curve(average_mae_history[10:])
plt.plot(range(1, len(smooth_mae_history) + 1), smooth_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()

image

图3.13 每个epoch验证集MAE的趋势图(排除前十个数据点)

根据上面的图表可以看出,验证集的MAE在80个epoch之后没有什么显著的提升。意味着该点之后模型开始过拟合。

一旦你完成模型其它参数的调优(除了epoch数量,你可以调整hidden layer的隐藏单元数量),你可以用最佳的参数在所有训练集上训练最终的生产上使用的模型,然后看下该模型在测试集上的效果。

1
2
3
4
5
6
7
8
9
#Listing 3.32 Training the final model
#Gets a fresh, compiled model
model = build_model()
#Trains it on the entirety of the data
model.fit(train_data, train_targets,
epochs=80, batch_size=16, verbose=0)
test_mse_score, test_mae_score = model.evaluate(test_data, test_targets)

最终结果如下:

1
2
>>> test_mae_score
2.5532484335057877

最后得到的结果偏离平均为$2550。

3.6.5 小结

从本例你应该学到以下知识点:

  • 回归模型的损失函数与分类问题的不同,常用均方差损失函数(MSE)
  • 相应的,常用的回归模型指标是平均绝对误差(MAE),确切地说,回归模型没有准确度的概念
  • 输入数据的取值范围不同时,应该在数据预处理阶段将每个特征进行归一化
  • 当数据样本太少时,可以使用K-fold交叉验证稳定的评估一个模型
  • 当训练集数据比较少时,倾向于使用小规模神经网络(一般是一到两个隐藏层),避免过拟合

未完待续。。。

Enjoy!

翻译本书系列的初衷是,觉得其中把深度学习讲解的通俗易懂。不光有实例,也包含作者多年实践对深度学习概念、原理的深度理解。最后说不重要的一点,François Chollet是Keras作者。
声明本资料仅供个人学习交流、研究,禁止用于其他目的。如果喜欢,请购买英文原版。


侠天,专注于大数据、机器学习和数学相关的内容,并有个人公众号:bigdata_ny分享相关技术文章。

若发现以上文章有任何不妥,请联系我。

image

《Deep Learning with Python》第三章 3.5 走进神经网络之新闻多分类

发表于 2018-06-09
3.5 新闻分类:多分类

在上一小节,学习了如何使用全联接神经网络将向量输入分为二类。但是,当需要多分类时该咋办呢?

在本小节,你将学习构建神经网络,把路透社新闻分为互不相交的46类主题。很明显,这个问题是多分类问题,并且每个数据点都只归为一类,那么该问题属于单标签、多分类;如果每个数据点可以属于多个分类,那么你面对的将是多标签、多分类问题。

3.5.1 路透社新闻数据集

路透社新闻数据集是由路透社1986年发布的短新闻和对应主题的集合,它常被用作文本分类的练手数据集。该数据集有46个不同的新闻主题,在训练集中每个主题包含至少10个新闻。

和IMDB和MNIST数据集一样,路透社新闻数据集也打包作为Keras的一部分,下面简单看下:

1
2
3
4
#Listing 3.12 Loading the Reuters dataset
from keras.datasets import reuters
(train_data, train_labels), (test_data, test_labels) = reuters.load_data(
num_words=10000)

设置参数num_words=10000,保留训练集中词频为top 10000的单词。

你有8982条训练样本数据和2246条测试样本数据:

1
2
3
4
>>> len(train_data)
8982
>>> len(test_data)
2246

从上述返回的结果看,每个样本都是整数列表(词索引):

1
2
3
>>> train_data[10]
[1, 245, 273, 207, 156, 53, 74, 160, 26, 14, 46, 296, 26, 39, 74, 2979,
3554, 14, 46, 4689, 4329, 86, 61, 3499, 4795, 14, 61, 451, 4329, 17, 12]

下面代码可以把词索引解码成词:

1
2
3
4
5
#Listing 3.13 Decoding newswires back to text
word_index = reuters.get_word_index()
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
#Note that the indices are offset by 3 because 0, 1, and 2 are reserved indices for “padding,” “start of sequence,” and “unknown.”
decoded_newswire = ' '.join([reverse_word_index.get(i - 3, '?') for i in train_data[0]])

样本的label是0到45的整数(主题索引):

1
2
>>> train_labels[10]
3
3.5.2 准备数据

使用和上一小节同样的代码进行数据向量化。

1
2
3
4
5
6
7
8
9
10
11
12
13
#Listing 3.14 Encoding the data
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
#Vectorized training data
x_train = vectorize_sequences(train_data)
#Vectorized test data
x_test = vectorize_sequences(test_data)

向量化label有两种方法:一种是,将label列表转成整数张量,另一种是,使用one-hot编码。one-hot编码广泛适用于分类数据,也称为分类编码。它的更详细介绍在6.1小节。在本例中,label的one-hot编码是将每个label映射到label索引位置为1的值。

1
2
3
4
5
6
7
8
9
def to_one_hot(labels, dimension=46):
results = np.zeros((len(labels), dimension))
for i, label in enumerate(labels):
results[i, label] = 1.
return results
#Vectorized training labels
one_hot_train_labels = to_one_hot(train_labels)
#Vectorized test labels
one_hot_test_labels = to_one_hot(test_labels)

上述label向量化的方式在Keras中有内建的函数实现,这在MNIST的例子中已经使用过。

1
2
from keras.utils.np_utils import to_categorical
one_hot_train_labels = to_categorical(train_labels) one_hot_test_labels = to_categorical(test_labels)
3.5.3 构建神经网络

这个主题分类问题和前一个影评分类类似:两类问题都是将短文本分类。但是这里有个新的限制:输出分类的数量由过去的2个变为46个。所以输出空间的维度更大。

使用一系列的Dense layer时,每个layer只能访问上一个layer的输出信息。如果某一个layer丢失一些与分类相关的信息时,接下来的layer不可能再恢复这些信息,所以每个layer都可能成为潜在的信息瓶颈。在前面的例子中,选用的16维中间layer,但是16维空间并不能学习到46个不同的分类。

考虑到上面的情况,这里使用更大的layer,隐藏单元设为64。

1
2
3
4
5
6
7
#Listing 3.15 Model definition
from keras import models
from keras import layers
model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(64, activation='relu')) model.add(layers.Dense(46, activation='softmax'))

上述代码中的神经网络架构需要注意两个事情:

  • 最后一个Dense layer大小为46。这意味着每个输入样本,神经网络模型输出一个46维向量。其中每个项代表不同的分类;
  • 最后一个layer使用softmax激活函数。这意味着神经网络模型输出一个46维的概率分布。对于每个输入样本,模型将输出一个46维的输出向量,每个output[i]是样本属于类别 i 的概率,且46个分数之和为1。

对于本例最适合的损失函数是categorical_crossentropy。该函数度量两个概率分布的距离,意即,模型输出的概率分布与label真实分布之间的距离。为了最小化两个分布的距离,训练模型使得其输出更接近真实label。

1
2
3
4
#Listing 3.16 Compiling the model
model.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
3.5.4 验证模型

下面从训练数据中分出1000个样本作为验证集。

1
2
3
4
5
6
#Listing 3.17 Setting aside a validation set
x_val = x_train[:1000]
partial_x_train = x_train[1000:]
y_val = one_hot_train_labels[:1000]
partial_y_train = one_hot_train_labels[1000:]

接着训练神经网络模型20个epoch。

1
2
3
4
5
6
#Listing 3.18 Training the model
history = model.fit(partial_x_train,
partial_y_train,
epochs=20,
batch_size=512,
validation_data=(x_val, y_val))

最后,显示损失函数和准确度的曲线,见图3.9和3.10。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#Listing 3.19 Plotting the training and validation loss
import matplotlib.pyplot as plt
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(loss) + 1)
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#Listing 3.20 Plotting the training and validation accuracy
#Clears the figure
plt.clf()
acc = history.history['acc']
val_acc = history.history['val_acc']
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()

image

图3.9 训练集和验证集的损失曲线

image

图3.10 训练集和验证集的准确度曲线

从上面的图可以看出,神经网络模型训练在第9个epoch开始过拟合。接着从头开始训练9个epoch,然后在测试集赏进行评估。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#Listing 3.21 Retraining a model from scratch
model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(64, activation='relu')) model.add(layers.Dense(46, activation='softmax'))
model.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
model.fit(partial_x_train,
partial_y_train,
epochs=9,
batch_size=512,
validation_data=(x_val, y_val))
results = model.evaluate(x_test, one_hot_test_labels)

下面是最终训练结果:

1
2
>>> results
[0.9565213431445807, 0.79697239536954589]

上面的方法达到约80%的准确度。在二分类问题中,纯随机分类的准确度是50%。而在本例中,纯随机分类的准确度将近19%,所以本例的模型结果还是不错的,至少超过随机基准线:

1
2
3
4
5
6
>>> import copy
>>> test_labels_copy = copy.copy(test_labels)
>>> np.random.shuffle(test_labels_copy)
>>> hits_array = np.array(test_labels) == np.array(test_labels_copy)
>>> float(np.sum(hits_array)) / len(test_labels)
0.18655387355298308
3.5.5 模型预测

你可以用模型实例的predict方法验证返回的46个主题分类的概率分布。下面对所有的测试集生成主题预测。

1
2
#Listing 3.22 Generating predictions for new data
predictions = model.predict(x_test)

predictions的每项是一个长度为64的向量:

1
2
>>> predictions[0].shape
(46,)

这些向量的系数之和为1:

1
2
>>> np.sum(predictions[0])
1.0

下面从预测分类中找出概率最大的项:

1
2
>>> np.argmax(predictions[0])
4
3.5.6 处理label和loss的不同方法

前面提到过label编码的两外一种方法,将其转化为整数张量,比如:

1
2
y_train = np.array(train_labels)
y_test = np.array(test_labels)

上述处理label的方法唯一需要改变的是损失函数。在listing 3.21代码中使用的损失函数,categorical_crossentropy,期望label是一个分类编码。对于整数label,你应该选用sparse_categorical_crossentropy损失函数:

1
2
3
model.compile(optimizer='rmsprop',
loss='sparse_categorical_crossentropy',
metrics=['acc'])

上面这个新的损失函数在数学上是和categorical_crossentropy相同的,不同之处在于接口不同。

3.5.7 中间layer的重要性

前面提过,因为最后的输出是46维,你应该避免中间layer小于46个hidden unit。下面为你展示中间layer小于46维导致的信息瓶颈问题,以4个hidden unit为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
#Listing 3.23 A model with an information bottleneck
model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(4, activation='relu')) model.add(layers.Dense(46, activation='softmax'))
model.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
model.fit(partial_x_train,
partial_y_train,
epochs=20,
batch_size=128,
validation_data=(x_val, y_val))

现在新的模型达到最大约71%的验证准确度,丢失了8%。这种情况主要是压缩许多信息到一个低维度的空间,导致没有足够多的信息可以恢复。

3.5.8 延伸实验
  • 尝试使用更大或者更小的layer:32个unit、128个unit等等;
  • 本例使用两个隐藏层。可以尝试一个或者三个隐藏层。
3.5.9 总结

从本例应该学习到的知识点:

  • 如果你想将数据分为N类,那神经网络模型最后一个Dense layer大小为N;
  • 在单标签、多分类的问题中,模型输出应该用softmax激活函数,输出N个分类的概率分布;
  • 分类交叉熵是分类问题合适的损失函数。它最小化模型输出的概率分布和真实label的概率分布之间的距离;
  • 处理多分类中label的两种方法:
    • 通过one-hot编码编码label,并使用categorical_crossentropy作为损失函数;
    • 通过整数张量编码label,并使用sparse_categorical_crossentropy损失函数
  • 对于数据分类的类别较多的情况,应该避免创建较小的中间layer,导致信息瓶颈。

未完待续。。。

Enjoy!

翻译本书系列的初衷是,觉得其中把深度学习讲解的通俗易懂。不光有实例,也包含作者多年实践对深度学习概念、原理的深度理解。最后说不重要的一点,François Chollet是Keras作者。
声明本资料仅供个人学习交流、研究,禁止用于其他目的。如果喜欢,请购买英文原版。


侠天,专注于大数据、机器学习和数学相关的内容,并有个人公众号:bigdata_ny分享相关技术文章。

若发现以上文章有任何不妥,请联系我。

image

《Deep Learning with Python》第三章 3.4 走进神经网络之电影影评分类

发表于 2018-03-17
3.4 电影影评分类

二元分类,或者称为二值分类,可能是应用最广泛的机器学习问题。通过学习本例,你将掌握如何基于文本内容将影评分为正、负二类。

3.4.1 IMDB数据集

本文将从互联网电影数据库(IMDB)获取50,000个流行电影影评作为数据集。这里将其分割为25,000个影评的训练集和25,000个影评的测试集。其中每个数据集都包含50%的好评和50%的差评。

为什么要将数据集分割成训练集和测试集呢?因为测试机器学习模型所使用的数据集不能和训练该模型的数据集是同一个。在训练集上表现良好的模型,并不意味着一定会在“未曾见过”的测试集上也有相同的表现。也就是说,你更关注的是训练的模型在新数据集上的性能(因为训练集数据的标签是已知的,很显然这些是不需要去预测的)。例如,可能你的模型可以将训练样本和对应的目标在内存中进行一一映射,但是这个模型对从“未见过的”数据无法进行预测。下一章会更详细的讨论该观点。

Keras已经包括IMDB数据集,并进行了数据预处理:影评(单词序列)转换成整数序列,这里每个整数代表对应单词在字典的索引值。

下面的代码将加载IMDB数据集,当你首次运行该代码,将会在服务器上下载大约80M的数据。

1
2
3
4
#Listing 3.1 Loading the IMDB dataset
from keras.datasets import imdv
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(
num_words=10000)

设置参数num_words=10000,保留训练集中词频为top 10000的单词,低频单词丢弃。变量train_data和test_data是影评列表(list),每条影评看成是单词序列,用单词索引进行编码。train_labels和test_labels是0和1的列表,其中0代表差评(negative),1代表好评(positive)。

1
2
3
4
>>> train_data[0]
[1, 14, 22, 16, ... 178, 32]
>>> train_labels[0]
1

前面限制影评中的单词词频为top 10000,所以单词的索引不会超过10000:

1
2
>>> max([max(sequence) for sequence in train_data])
9999

下面来个好玩的,如何将编码后的影评进行解码得到单词呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
'''
word_index is a dictionary mapping
words to an integer index.
'''
word_index = imdb.get_word_index()
reverse_word_index = dict(
'''
Reverses it, mapping integer indices to words
'''
[(value, key) for (key, value) in word_index.items()])
decoded_review = ' '.join(
'''
Decodes the review. Note that the indices
are offset by 3 because 0, 1, and 2 are
reserved indices for “padding,” “start of
sequence,” and “unknown.”
'''
[reverse_word_index.get(i - 3, '?') for i in train_data[0]])
3.4.2 准备数据

神经网络不能输入整数列表,所以需要将整数列表转换成张量。有两种方式可以实现:

  • 填充列表:先将列表填充成相同长度的,再转成形状为(样本数,单词索引长度)的整数张量。接着用神经网络的第一层layer(Embedding layer)处理整数张量。
  • one-hot编码:one-hot编码是将单词索引转成0、1的向量。比如,将序列[3, 5]转成10,000维向量,其中索引3和5的值为1,其它索引对应的值为0。然后使用神经网络的Dense layer作为第一层layer处理浮点型向量数据。

下面采用后一种方法向量化数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
def vectorize_sequences(sequences, dimension=10000):
#Creates an all-zero matrix of shape (len(sequences), dimension)
results = np.zeros((len(sequences), dimension))
for i, sequence in enumerate(sequences):
'''
Sets specific indices of results[i] to 1s
'''
results[i, sequence] = 1.
return results
'''
Vectorized training data and test data
'''
x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)

下面看下向量化后的结果:

1
2
>>> x_train[0]
array([ 0., 1., 1., ..., 0., 0., 0.]

同理,向量化对应的label:

1
2
y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(test_labels).astype('float32')

数据准备好了,就等着传入神经网络模型。

3.4.3 构建神经网络模型

输入数据为向量,label为标量(1和0),相当简单。一系列带有relu激活函数的全联接层(Dense layer)的神经网络就可以很好的解决影评分类:Dense(16, activation=’relu’)。

每个Dense layer设置隐藏单元(hidden unit)数为16。hidden unit是layer的一维表征空间。由第二章得知,每个带有relu激励函数的Dense layer可以实现下面链式的张量操作:

1
output = relu(dot(W, input) + b)

16个hidden unit意味着权重矩阵的形状为(输入维度,16):输入数据与权重矩阵W点积的结果是投影到16维表征空间(,接着加上偏置向量b,然后应用relu激活操作)。给人的直觉是表征空间的维数即是中间学习表示的自由度。hidden unit越多(高维表征空间)允许神经网络学习更复杂的表示,但同时也让神经网络计算成本增加,可能导致不可预期的模式(模式会提高训练集上的性能,降低测试集上的表现,也就是常说的“过拟合”现象)。

逐层排列的Dense layer架构有两个关键点:

  • 选择多少层Dense layer
  • 每个Dense layer选择多少个hidden unit

在第四章中的常规性原则将会指导你对上述问题做出选择。此时,你就暂时相信下面的架构选择哦:

  • 两个具有16个hidden unit的中间层
  • 第三层layer将输出当前影评的情感预测值(标量)

中间层使用relu作为激活函数,最后一层layer使用sigmoid激活函数,输出0到1之间的概率值。激活函数relu(rectified linear unit(修正线性单元),ReLU),对于所有负值都置为0,而正值不变,见图3.4;而激活函数sigmoid将变量值映射为[0, 1]区间,可以看作是概率值,见图3.5。

image

图3.4 Relu激活函数

image

图3.5 Sigmoid激活函数

image

图3.6 三层layer神经网络

图3.6显示了神经网络的大体架构。下面是Keras的实现,和前面MNIST数字识别的例子类似:

1
2
3
4
5
6
#Listing 3.3 The model definition
from keras import models
from keras import layers
model = models.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'))

什么是激活函数呢?为什么要使用激活函数?

没有像relu这样的激活函数(俗称非线性单元)的话,那Dense layer只剩下两个线性操作:点积和加法。

1
2
> output = dot(W, input) + b
>

>

这样layer只能学习到输入数据的线性变换(仿射变换):layer的假设空间就成了输入数据的所有可能的线性变换到16维表征空间的集合。这样的假设空间并不能学习到多层layer的表征,因为一系列的线性layer等效于一个线性操作:layer数的增加并不会扩展假设空间。

为了从深度学习中得到更丰富的假设空间,你需要加入非线性部分,或者激活函数。relu是深度学习中最常用的激活函数之一,但是也有其它可选:prelu激活函数、elu激活函数等等。

接着,选择损失函数和优化器。因为本例是二值分类问题,神经网络模型输出是概率值(网络的最后一层layer带有sigmoid激活函数,输出一维数据),所以最好的损失函数是binary_crossentropy损失函数。但这不是唯一的选择,你也可以使用mean_squared_error损失函数。一般输出为概率值的模型优先选择交叉熵损失函数(crossentropy)。交叉熵是信息论中的指标,用来度量概率分布之间的距离。本例是用来度量实际分布与预测值的差距。

这里为模型选择binary_crossentropy损失函数和rmsprop优化器。注意监控模型训练过程中的准确度。

1
2
3
4
#Listing 3.4 Compiling the model
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['accuracy'])

上面传入的优化器optimizer、损失函数loss和指标metrics三个参数都是字符串型,这是因为’rmsprop’、’binary_crossentropy’和’accuracy’都是Keras内置实现的。如果想配置自定义的优化器或者损失函数或者指标函数,你可以用参数optimizer传入优化器类,见代码3.5;用参数loss和metrics传入函数对象,见代码3.6:

1
2
3
4
5
6
7
8
9
10
11
12
#Listing 3.5 Configuring the optimiser
from keras import optimisers
model.compile(optimizer=optimizers.RMSprop(lr=0.001),
loss='binary_crossentropy',
metrics=['accuracy'])
#Listing 3.6 Using custom losses and metrics
from keras import losses
from keras import metrics
model.compile(optimizer=optimizers.RMSprop(lr=0.001),
loss=losses.binary_crossentropy,
metrics=[metrics.binary_accuracy])
3.4.4 验证模型

为了监控模型训练过程中模型在新数据上的准确度,需要从原始的训练数据集中分出10,000个样本作为验证集。

1
2
3
4
5
6
#Listing 3.7 Setting aside a validation set
x_val = x_train[:10000]
partial_x_train = x_train[10000:]
y_val = y_train[:10000]
partial_y_train = y_train[10000:]

现在开始模型训练,迭代训练的epoch(在所有训练集数据上跑完一次称为一个epoch)为20个,mini-batch大小为512。训练过程中监控验证集数据上的损失函数和准确度,设置参数validation_data。

1
2
3
4
5
6
7
8
9
#Listing 3.8 Training your model
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['acc'])
history = model.fit(partial_x_train,
partial_y_train,
epochs=20,
batch_size=512,
validation_data=(x_val, y_val))

在CPU上训练模型时,每个epoch耗时不到2秒,整个训练过程大概持续20秒。每个epoch结束时,会有个短暂的停顿,这时模型会计算验证集数据上的损失值和准确度。

注意,调用model.fit()会返回一个History对象,该对象有个history成员,它是一个包含训练过程的每个数据的字典。下面来看下:

1
2
3
>>> history_dict = history.history
>>> history_dict.keys()
[u'acc', u'loss', u'val_acc', u'val_loss']

history字典有四项:模型训练和验证中每个指标一项。接下来的两段代码,使用Matplotlib在同一幅图中绘制训练集损失和验证集损失,见图3.7;同时将训练集准确度和验证集准确度绘制在同一幅图中,见图3.8。注意,因为神经网络的初始化是随机的,可能会导致你的结果与本例稍有差别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#Listing 3.9 Plotting the training and validation loss
import matplotlib.pyplot as pet
history_dict = history.history
loss_values = history_dict['loss']
val_loss_values = history_dict['val_loss']
epochs = range(1, len(acc) + 1)
'''
“bo” is for “blue dot.”
“b” is for “solid blue line.”
'''
plt.plot(epochs, loss_values, 'bo', label='Training loss') plt.plot(epochs, val_loss_values, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()

image

图3.7 迭代训练中训练集和验证集的损失趋势

1
2
3
4
5
6
7
8
9
10
11
12
13
#Listing 3.10 Plotting the training and validation accuracy
#Clears the figure
plt.clf()
acc_values = history_dict['acc']
val_acc_values = history_dict['val_acc']
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()

image

图3.8 迭代训练中的训练集和验证集的准确度趋势

正如你所见,随着迭代训练的epoch,训练损失值不断减小,而准确度不断提升。这也是执行梯度下降优化器期待的结果:不断迭代训练减小损失。但是验证集数据上的损失值和准确度表现的并不是如此:验证集在第四个epoch后效果达到最好。这也是前面提醒过的:模型在训练集上表现良好并不代表在新数据集上也有同样的表现。准确地来讲,这是过拟合(overfiting):在迭代训练第2个epoch后,模型在训练集上出现了过度优化,最终的学习表征像是为训练集特制的,对新数据丧失了泛化能力。

本例中,为了防止过拟合出现,需要在迭代训练3个epoch后停止训练。一般来讲,我们可以使用多种技术解决过拟合,这些会在第四章中详细介绍。

下面从头迭代训练4个epoch生成新的神经网络,并在测试集上评估效果:

1
2
3
4
5
6
7
8
#Listing 3.11 Retraining a model from scratch
model = models.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'))
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['accuracy'])
model.fit(x_train, y_train, epochs=4, batch_size=512)
results = model.evaluate(x_test, y_test)

最好的评估结果如下:

1
2
>>> results
[0.2929924130630493, 0.88327999999999995]

这个相当直白的方法获得了88%的准确度。使用最新的方法,你将会得到接近95%的准确度。

3.4.5 模型预测

训练完神经网络模型,使用predict方法进行影评情感预测:

1
2
3
4
5
6
7
8
>>> model.predict(x_test)
array([[ 0.98006207]
[ 0.99758697]
[ 0.99975556]
...,
[ 0.82167041]
[ 0.02885115]
[ 0.65371346]], dtype=float32)

正如你所看到的结果,神经网络模型对一些样本数据的预测结果自信(概率为0.99或者更高,或者0.01或者更小),但是对另外一些的预测结果不是太自信(概率为0.6,0.4的情况)。

3.4.6 延伸实验

下面的一些实验使得神经网络的架构选择更合理些(虽然还是有待提升的空间):

  • 本例使用的两个隐藏层。可以尝试选择一个或者三个隐藏层,看下会怎样影响验证集和测试集的准确度;
  • 选择更多的hidden unit或者更少的hidden unit:32个unit,64个unit等等;
  • 使用mse损失函数代替binary_crossentropy损失函数;
  • 使用tanh激活函数(在神经网络早期常用的激活函数)代替relu激活函数。
3.4.7 总结

从本实例学到的知识点:

  • 原始数据集预处理为张量传入神经网络。单词序列编码为二值向量或者其它形式;
  • 一系列带有relu激活函数的Dense layer能解决广泛的问题,包括情感分类,后续会常用到的;
  • 二值分类问题(输出两个类别)中,最后的一个Dense layer带有一个sigmoid激活函数和一个单元:网络输出是0到1之间的标量,代表概率值;
  • 二分类问题中有sigmoid标量输出的,损失函数选择binary_crossentropy损失函数;
  • rmsprop优化器对于大部分深度学习模型来说是足够好的选择;
  • 随着在训练集上表现越来越好,神经网络模型开始过拟合,在新数据上表现越来越差。关注验证集上的监控指标

未完待续。。。

Enjoy!

翻译本书系列的初衷是,觉得其中把深度学习讲解的通俗易懂。不光有实例,也包含作者多年实践对深度学习概念、原理的深度理解。最后说不重要的一点,François Chollet是Keras作者。
声明本资料仅供个人学习交流、研究,禁止用于其他目的。如果喜欢,请购买英文原版。


侠天,专注于大数据、机器学习和数学相关的内容,并有个人公众号:bigdata_ny分享相关技术文章。

若发现以上文章有任何不妥,请联系我。

image

《Deep Learning with Python》第三章 3.2 走进神经网络之Keras简单入门

发表于 2018-03-17
3.2 Keras简单入门

全书使用的代码示例采用Keras(https://keras.io)实现。Keras是一个Python编写的深度学习框架,提供方便的方式定义和训练几乎所有的深度学习模型。Keras原本是为研究者开发的,旨在加速试验。Keras具有以下特色:

  • 同一段代码可以在CPU或者GPU上无缝切换
  • 用户友好的API使得创建深度学习模型的原型更简单
  • 內建支持卷积神经网络(计算机视觉)、循环神经网络(序列处理),以及两者的任意组合
  • 支持任意网络结构:多输入或者多输出模型,layer共享,模型共享,等等。这意味着Keras能构建任意深度学习模型,从对抗生成网络到图灵机。

Keras采用MIT license分发,意味着可以在商业项目中使用。截止到2017年中旬,Keras同时兼容Python 2.7到3.6版本。

Keras已有超过200,000个使用者,涵盖从学术研究到工业企业(包括创业公司和大型公司)。Google、Netflix、Uber、Yelp和成百上千的创业公司使用Keras解决各种问题。Keras也是机器学习竞赛网站Kaggle最流行的使用框架。

image

图 3.2 Google搜索中深度学习框架的关注度趋势

3.2.1 Keras、TensorFlow、Theano和CNTK

Keras提供high-level的开发深度学习模型的构建模块,其是model-level的库。Keras不能解决low-level的操作,比如张量操作和差分。Keras可以依赖指定的、经过优化的张量操作库作为其后端引擎(backend engine),而不是选择一个张量库去试图作为Keras的实现。Keras是以模块化的方式解决问题,见图3.3,不同的后端引擎能够为Keras进行无缝地扩展。当前已经存在三种后端引擎实现:TensorFlow后端引擎,Theano后端引擎,和CNTK后端引擎。将来可能会为Keras扩展更多的深度学习执行引擎。

image

图3.3 深度学习软件和硬件栈

TensorFlow、CNTK和Theano是当前主流的深度学习平台,其中Theano(http://deeplearning.net/software/theano)是蒙特利尔大学大学MILA实验室开发的,TensorFlow(www.tensorflow.org)由谷歌开发,CNTK(https://github.com/Microsoft/CNTK)由微软开发。用Keras编写的代码不用做任何改变即可在这些执行引擎上运行。在深度学习模型开发阶段,使用Keras可以在两种不同的执行引擎之间切换,比如,解决某种任务时,其中某个执行引擎执行会快。我们推荐使用TensorFlow作为深度学习模型的默认执行引擎,因为TensorFlow已经被广泛采纳、扩展,以及产品化。

通过TensorFlow(CNTK或者Theano),Keras能在CPU和GPU上无缝运行。TensorFlow封装low-level的Eigen(http://eigen.tuxfamily.org)张量操作库来运行CPU;封装经过优化的深度学习操作的库,即NVIDIA CUDA神经网络库(cuDNN),来运行GPU。

3.2.2 使用Keras开发:快速预览

你已经看过一个Keras模型的例子:MNIST数字识别的实例。典型的Keras工作流程就像如下的例子:

  1. 定义训练数据集:输入张量和目标张量
  2. 定义layer的网络(或者model):将输入张量映射为目标张量
  3. 配置学习过程:选定损失函数、优化器和监控的一些指标
  4. 迭代训练数据:调用模型的fit()函数在训练数据集上迭代训练

有两种方式定义神经网络模型:使用Sequential类(专为layer的线性逐层连接的模型,截止目前大部分都是此类模型)或者使用函数式API(为有向无环图式layer连接的模型,可以构建任意形式的模型)。

对于新手来讲,下面的例子使用Sequential类定义两层layer的模型(注意,为第一个layer传入指定形状的输入数据):

1
2
3
4
5
from keras import models
from keras import layers
model = models.Sequential()
model.add(layers.Dense(32, activation='relu', input_shape=(784,)))
model.add(layers.Dense(10, activation='softmax'))

接着用函数式API定义上述相同的模型:

1
2
3
4
input_tensor = layers.Input(shape=(784,))
x = layers.Dense(32, activation='relu')(input_tensor)
output_tensor = layers.Dense(10, activation='softmax')(x)
model = models.Model(inputs=input_tensor, outputs=output_tensor)

使用函数式API操作模型处理的数据张量,将layer应用于这些张量,就好像它们是函数一样使用。

备注:函数式API的详细使用教程将在第七章介绍。在第七章之前,代码例子会使用Sequential类创建模型。

一旦定义好了模型架构,使用Sequential模型还是函数式API模型都不重要。下面的步骤是相同的。

在编译步骤配置学习过程,指定使用模型的优化器和损失函数,选择训练的监控指标。下面的例子使用单个损失函数:

1
2
3
4
from keras import optimisers
model.compile(optimizer=optimizers.RMSprop(lr=0.001),
loss='use',
metrics=['accuracy'])

最后,为模型传入输入数据和相应的目标数据的Numpy数组,通过fit()函数进行学习过程:

1
model.fit(input_tensor, target_tensor, batch_size=128, epochs=10)

后面几个章节将会帮你建立坚实的直觉:哪类的问题适用哪种网络架构?如何选择正确的学习配置?如何调整模型直到得到想要的结果?下面将会在3.4,3.5和3.6小节介绍三个基本的例子:二分类,多分类和回归。

未完待续。。。

Enjoy!

翻译本书系列的初衷是,觉得其中把深度学习讲解的通俗易懂。不光有实例,也包含作者多年实践对深度学习概念、原理的深度理解。最后说不重要的一点,François Chollet是Keras作者。
声明本资料仅供个人学习交流、研究,禁止用于其他目的。如果喜欢,请购买英文原版。


侠天,专注于大数据、机器学习和数学相关的内容,并有个人公众号:bigdata_ny分享相关技术文章。

若发现以上文章有任何不妥,请联系我。

image

《Deep Learning with Python》第三章 3.1 走进神经网络之神经网络剖析

发表于 2018-02-11

第三章 走进神经网络

本章涉及的知识点:

  • 神经网络核心组件

  • 开始Keras之旅

  • 组建深度学习工作站

  • 使用神经网络解决基本的分类和回归问题

    本章开始使用神经网络解决实际问题。巩固第二章中的示例,并应用学到的知识解决三类问题,其可以覆盖最常见三类神经网络场景:二值分类,多分类和线性回归。

    本章将仔细讲解第二章涉及的神经网络核心组件:layer,网络,观察函数和优化器。接着快速的过一下Keras,其是本章通篇使用的Python深度学习库。组建支持TensorFlow、Keras和GPU的深度学习工作站。然后将着重深挖使用深度学习解决三个实际问题:

    • 影评分类(好评或者差评,二分类)
    • 路途社新闻主题(多分类)
    • 预估房价(回归)

    本章之后,你会使用神经网络解决简单的机器学习问题,比如,分类问题和回归问题。你将在第四章开始建立更有原则性、理论驱动的理解机器学习。

    3.1 神经网络剖析

    训练神经网络模型绕不开下面的对象:

    • Layer:layer可以组成一个网络(network)或者模型(model)
    • 输入数据和相应的目标
    • 损失函数:损失函数定义深度学习中的反馈信号
    • 优化器:优化器决定深度学习的行进

    如图3.1所示,可视化上述对象间的交互:网络是由多个layer联结组成的,将输入数据映射到输出的预测数据。损失函数则比较预测值和目标的差值,意即损失值。优化器最小化损失函数来调整网络权重。

    image

    图3.1 网络、layer、损失函数和优化器之间的关系

    下面详细介绍网络、layer、损失函数和优化器。

    3.1.1 Layer:深度学习的基础组件

    神经网络的基础数据结构是layer,其在第二章中有提到。layer是将一个或者多个输入张量转换成一个或者多个输出张量的数据处理模块。有些layer是无状态的,但是更多的layer是带状态的:layer的权重,该权重是由随机梯度下降学习的一个或者几个张量,它们包含网络的知识(knowledge)。

    不同的张量格式和数据类型需要不同的layer进行数据处理,比如,简单的向量数据存储成形状为(样本,特征)的2D张量,它经常用致密的全联接层(也称为全联接层或者致密层,Keras中的Dense类)处理。序列数据存储成形状为(样本,时间戳,特征)的3D张量,使用循环神经网络层(recurrent layer,比如,LSTM layer)处理。图片数据存储成4D张量,常用2D卷积层(Conv2D)处理。

    Layer犹如深度学习的“乐高积木”,Keras通过剪辑拼接兼容的layer来构建深度学习模型,从而形成数据转换的管道。这里layer的兼容性特指:每类layer只接受一定形状的输入张量,返回一定形状的输出张量。下面看个具体的例子:

    1
    2
    from keras import layers
    layer = layers.Dense(32, input_shape=(784,))

    上面的例子中,创建一个只接受2D张量的layer,该2D张量的第一个维度是784(轴0,batch维度并没有指定,因此它可以接受任意值)。该layer返回第一个维度是32的张量。

    因此,这个layer联结的下游layer是以32维的向量为输入。使用Keras时无需担心layer的兼容性,因为加入到模型的layer会自动适配上一layer的形状。例如,下面的代码:

    1
    2
    3
    4
    5
    6
    from keras import models
    from keras import layers
    model = models.Sequential()
    model.add(layers.Dense(32, input_shape=(784,)))
    model.add(layers.Dense(32))

    其中第二个layer没有输入形状的参数,但是它可以自动推导其输入张量的形状是前一个layer的输出张量的形状。

    3.1.2 模型:层的网络(networks of layers)

    深度学习模型是一个有向无环图。最常见的例子是layer的逐层排列、线性连接,将单个输入映射到单个输出。

    继续深究,你会发现更多种网络拓扑的变种。下面是一些常用的网络拓扑:

    • Two-branch网络
    • Multihead网络
    • 感知机

    网络组成的拓扑定义了假设空间(hypothesis space)。你可能还记得第一章中机器学习的定义:在预定的可能空间中,基于反馈信号寻找输入数据集的有效的表示。选定了一个网络拓扑,意味着限制了一系列张量操作的可行性空间(假设空间)。为权重张量搜索一组合适的值,将输入数据映射到输出数据。

    挑选正确的网络结构更像是一个艺术活,而不是一种科学。虽然有一些最佳实践和基本原则貌似可以依赖,但是实际上只有不断地实践才能帮你成为合适的神经网络架构师。后面几个章节将会教你明确的原则构建神经网络,帮你塑造直面问题的直觉。

    3.1.3 损失函数和优化器:掌控学习的进度

    一旦网络定义好,接着迫在眉睫的是以下两件事:

    • 损失函数(观察函数):模型训练中损失函数值最小化,意味着任务的成功;
    • 优化器:决定基于损失函数的神经网络如何迭代。它一般是随机梯度下降(stochastic gradient descent,SGD)的某种变体。

    有多路输出的神经网络可能有多个损失函数(每路输出一个损失函数)。而梯度下降的过程必须基于单个标量损失值;所以,对于多损失函数的网络,所有损失函数被组合(一般通过平均)成单个标量值。

    选择好正确的观察函数对于解决问题来说是至关重要的:你的网络尽可能找到最小化损失的捷径。所以,如果观察函数不能很好的与任务的成功相关联,那网络迭代结束并不会得到预想的结果。记住所有的神经网络都是最小化损失函数。

    幸运地是,对于常见问题(比如分类、回归、序列预测),有些简单的指导原则来选择正确的损失函数。例如,二分类问题采用二值交叉熵损失,多分类问题采用分类交叉熵,回归问题选取均方误差,序列化学习问题选择联结时序分类(connectionist temporal classification,CTC)损失,等等。只有当你遇到完全新的问题,才需要开发自定义的观察函数。后面几章将会详细的阐述哪类问题选择哪种损失函数。

未完待续。。。

Enjoy!

翻译本书系列的初衷是,觉得其中把深度学习讲解的通俗易懂。不光有实例,也包含作者多年实践对深度学习概念、原理的深度理解。最后说不重要的一点,François Chollet是Keras作者。
声明本资料仅供个人学习交流、研究,禁止用于其他目的。如果喜欢,请购买英文原版。


侠天,专注于大数据、机器学习和数学相关的内容,并有个人公众号:bigdata_ny分享相关技术文章。

若发现以上文章有任何不妥,请联系我。

image

《Deep Learning with Python》第二章 2.2 神经网络的数据表示

发表于 2018-01-27
2.2 神经网络的数据表示

在上面的例子中,数据存储为多维Numpy数组,也称为张量(tensor)。当前流行的机器学习系统都以张量作为基本数据结构。所以Google的TensorFlow也拿张量命名。那张量是什么呢?

张量是数据的容器(container)。这里的数据一般是数值型数据,所以是数字的容器。大家所熟悉的矩阵是二维(2D)张量。张量是广义的矩阵,它的某一维也称为轴(axis)。

  • 标量(Scalar,0D 张量)

只包含一个数字的张量称为标量(或者数量张量,零维张量,0D张量)。在Numpy中,一个float32或者float64位的数值称为数量张量。Numpy张量可用其ndim属性显示轴的序数,数量张量有0个轴(ndim == 0)。张量的轴的序数也称为阶(rank)。下面是Numpy标量:

1
2
3
4
5
>>> import numpy as np
>>> x = np.array(12)
>>> x
array(12)
>>> x.ndim 0
  • 向量(1D张量)

数字的数组也称为向量,或者一维张量(1D张量)。一维张量只有一个轴。下面来看一个Numpy向量:

1
2
3
4
5
>>> x = np.array([12, 3, 6, 14])
>>> x
array([12, 3, 6, 14])
>>> x.ndim
1

该向量有5项,也称为5维的向量。但是不要混淆5D向量和5D张量!一个5D向量只有一个轴,以及沿该轴有5个维数(元素);然而一个5D张量有5个轴,并且沿每个轴可以有任意个的维数。维度既能表示沿某个轴的项的数量(比如,上面的5D向量),又能表示一个张量中轴的数量(比如,上面的5D张量),时常容易混淆。对于后者,用更准确地技术术语来讲,应该称为5阶张量(张量的阶即是轴的数量),但人们更常用的表示方式是5D张量。

  • 矩阵(2D张量)

向量的数组称为矩阵,或者二维张量(2D张量)。矩阵有两个轴,也常称为行和列。你可以将数字排成的矩形网格看成矩阵,下面是一个Numpy矩阵:

1
2
3
4
5
>>> x = np.array([[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]])
>>> x.ndim
2

沿着第一个轴的项称为行,沿着第二个轴的项称为列。上面的例子中,[5, 78, 2, 34, 0]是矩阵 x 第一行,[5, 6, 7]是第一列。

  • 三维张量(3D张量)和更高维张量

矩阵的数组称为三维张量(3D张量),你可以将其看成是数字排列成的立方体,下面是一个Numpy三维张量:

1
2
3
4
5
6
7
8
9
10
11
>>> x = np.array([[[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]],
[[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]],
[[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]]])
>>> x.ndim
3

同理,将三维张量放进数组可以创建四维张量,其它更高维的张量亦是如此。深度学习中常用的张量是 0D 到 4D。如果处理视频数据,你会用到5D。

  • 关键属性

    张量具有如下三个关键属性:

    • 轴的数量(阶数,rank):一个三维张量有3个轴,矩阵有2个轴。Python Numpy中的张量维度为ndim。
    • 形状(shape):它是一个整数元组,描述张量沿每个轴有多少维。例如,前面的例子中,矩阵的形状为(3,5),三维张量的形状为(3,3,5)。向量的形状只有三个元素,比如(3,),标量有空形状,()。
    • 数据类型:张量中包含的数据类型有float32,unit8,float64等等,调用Python的dtype属性获取。字符型张量是极少见的。注意,Numpy中不存在字符串张量,其它大部分库也不存在。因为张量存在于预先申请的、连续的内存分段;而字符是变长的。

下面来几个具体的例子,回看MNIST数据集。首先加载MNIST数据集:

1
2
from keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

接着,用ndim属性显示张量train_images的轴数量:

1
2
>>> print(train_images.ndim)
3

打印形状:

1
2
>>> print(train_images.shape)
(60000, 28, 28)

使用dtype属性打印数据类型:

1
2
>>> print(train_images.dtype)
uint8

所以train_images是一个8-bit 整数的三维张量。更确切地说,它是一个包含60,000个矩阵的数组,其中每个矩阵是28 x 28 的整数。每个矩阵是一个灰度图,其值为0到255。

下面使用Python Matplotlib库显示三维张量中的第四幅数字图,见图2.2:

1
2
3
4
5
#Listing 2.6 Displaying the fourth digit
digit = train_images[4]
import matplotlib.pyplot as plt
plt.imshow(digit, cmap=plt.cm.binary)
plt.show()

image

图2.2 数字图样例

  • Numpy中的张量操作

上面的例子中,使用了train_images[i]沿第一个轴选择指定的数字图。选择张量的指定元素称为张量分片(tensor slicing),下面看Numpy数组中的张量切片操作:

选择#10到#100(不包括#100)的数字图,对应的张量形状为(90,28,28):

1
2
3
>>> my_slice = train_images[10:100]
>>> print(my_slice.shape)
(90, 28, 28)

其等效的表示方法有,沿每个轴为张量分片指定起始索引和终止索引。注意,“:”等效于选择整个轴的数据:

1
2
3
4
5
6
>>> my_slice = train_images[10:100, :, :]
>>> my_slice.shape
(90, 28, 28)
>>> my_slice = train_images[10:100, 0:28, 0:28]
>>> my_slice.shape
(90, 28, 28)

一般,你可以沿着张量每个轴任意选择两个索引之间的元素。例如,选择所有图片的右下角的14 x 14的像素:

1
my_slice = train_images[:, 14:, 14:]

你也可以用负索引。就像Python list中的负索引一样,它表示相对于当前轴末端的位置。剪切图片中间14 x 14像素,使用如下的方法:

1
my_slice = train_images[:, 7:-7, 7:-7]
  • 批量数据(batch)的表示

    在深度学习中,张量数据的第一个轴(axis 0,轴的序数从0开始)一般是样本轴(sample axis),有时也称为样本维度(sample dimension )。在MNIST手写数字识别的例子中,样本是数字图片。

    另外,深度学习模型不会一次处理整个数据集,而是将其拆分成小批量的数据集。下面是一个MNIST手写数字的batch,其中batch大小为128:

    1
    batch = train_images[:128]

    接着下一个batch:

    1
    batch = train_images[128:256]

    第n个batch:

    1
    batch = train_images[128 * n:128 * (n + 1)]

    对于张量batch来说,第一个轴(axis 0)称为batch轴或者batch维度。在使用Keras和其它深度学习库时会遇到这个术语。

  • 真实世界中的张量数据

    下面来一些具体的张量例子,后续也会用到。大部分张量都可以归为以下几类:

    • 向量数据:形状为(样本,特征)[^ (samples, features) ]的二维张量
    • 时序数据(timeseries data)或者序列数据(sequence data):形状为(样本,时间戳,特征)[^(samples, timesteps, features)]的三维张量
    • 图片数据:形状为(样本,高度,宽度,管道)[^(samples, height, width, channels)]或者(样本,管道,高度,宽度)[^(samples, channels, height, width)]的四维张量
    • 视频数据:形状为(样本,帧,高度,宽度,管道)[^(samples, frames, height, width, channels)]或者(样本,帧,管道,高度,宽度)[^(samples, frames, channels, height, width)]的五维张量
  • 向量数据

    向量数据是最常见的例子。在数据集中,单个数据点可以编码成一个向量,然后一批向量数据可以编码成二维张量(即,向量的数组),其中第一个轴为样本轴(samples axis),第二个轴为特征轴(features axis)。

    下面来看两个实例:

    • 人口数据:这里考虑人的年龄,邮政编码和收入。每个人的特征是一个包含3个值的向量,因此100,000个人的数据集存储为形状为(100000,3)的二维张量
    • 文本数据:这里每个文档用词汇表(考虑20,000个常用词的字典)中每个词出现的次数来表示。那么每个文档编码成一个包含20,000个值(词汇表中每个词一个值)的向量。因此,500个文档的数据集存储为形状为(500,20000)的张量
  • 时序数据或者序列数据

    当样本数据集中时间或者序列的排序较为重要,你应该将数据集存储为带显式的时间轴(time axis)的三维张量。每个样本编码成一个向量的序列(二维张量),因此,一批二维张量数据可以编码成三维张量,见图2.3:

    image

    图2.3 三维时序张量数据

    习惯上,时间轴是第二个轴(轴序数为1)。下面看几个例子:

    • 股票价格数据:每分钟保存股票的当前价格,上一分钟的最高价格,上一分钟的最低价格。每分钟的股票价格编码成一个三维向量,一整天的股票交易编码成形状为(390,3)的二维张量(股票交易每天有390分钟)。250天的股票数据存储为(250,390,3)的三维张量。这里每个样本为一天的股票交易数据。
    • 推特消息数据:这里用128个不重复的字符表将每条推文编码成280字符序列。每个字符编码成大小为128的二进制向量(该字符所在的索引位置的项为1,其它值都为0)。每条推文编码成形状为(280,128)的二维张量,那么1亿条推文存储为(1000000,280,128)的张量。
  • 图片数据

    图片典型有三个维度:高度、宽度和颜色深度。灰度图片(比如MNIST手写数字图片)仅有一个颜色通道,因此可以存储为二维张量,但是习惯上图片张量都是三维的,因此灰度图片只用一维颜色管道表示。128张大小为256 x 256的灰度图片存储成形状为(128,256,256,1)的张量,128张彩色图片存储成形状为(128,256,256,1)的张量,见图2.4。

    image

    图片张量有两种写法:颜色管道在后(TensorFlow的写法),颜色管道在前(Theano的写法)。谷歌的TensorFlow机器学习框架将颜色深度轴放在末尾:(样本,高度,宽度,管道)[^(samples, height, width, channels)]。同时,Theano将颜色深度轴放在batch轴右边。按Theano的写法,前面的例子写成(128,1,256,256)和(128,3,256,256)。Keras深度学习框架对两种表示方法都支持。

  • 视频数据

    视频数据是现实世界中少有的几种需用五维张量表示的数据。视频可以理解成帧的序列,每帧是一副彩色图片。因为每帧是三维张量(高度,宽度,管道),所以帧的序列存储成四维张量(帧,高度,宽度,管道)。那不同的视频就要存储为五维张量了(样本,帧,高度,宽度,管道)[^(samples, frames, height, width, channels)]。

    例如,一个60秒,144 x 256的油管视频按每秒采样4帧将会有240帧。那么4个不同的视频采样存储为形状为(4,240,144,256,3),总共有106,168,320个值。如果数据类型dtype为float32,那每个值保存为32位,所以该张量表示占405MB。而在真实生活中,你看到的视频都不用float32保存,一般都用大块数据存储格式(比如MPEG格式)压缩。

未完待续。。。

Enjoy!

翻译本书系列的初衷是,觉得其中把深度学习讲解的通俗易懂。不光有实例,也包含作者多年实践对深度学习概念、原理的深度理解。最后说不重要的一点,François Chollet是Keras作者。
声明本资料仅供个人学习交流、研究,禁止用于其他目的。如果喜欢,请购买英文原版。


侠天,专注于大数据、机器学习和数学相关的内容,并有个人公众号:bigdata_ny分享相关技术文章。

若发现以上文章有任何不妥,请联系我。

image

《Deep Learning with Python》第二章 2.1 神经网络的MNIST手写数字识别

发表于 2018-01-06

第二章 神经网络的数学基础

本章涉及的知识点:

  • 神经网络的MNIST手写数字识别(概览)
  • 张量(Tensor)和张量操作
  • 如何通过后向传播和梯度下降进行神经网络学习

理解深度学习要求熟悉许多简单的数学概念:张量,张量操作,微分,梯度下降等等。本章目的是让读者对这些概念有个大体的认识。

这里以一个实际的神经网络例子为开头,增加一些张量和梯度下降背景的了解。然后一点点的介绍新概念。记住,这些概念对理解后续章节的例子有帮助。

读完本章后,你将对神经网络的工作原理有个直观的认识。神经网络的应用实践将在第三章详细讲解。

2.1 神经网络的MNIST手写数字识别

第一个具体的神经网络例子是,使用Keras(Python库)学习手写数字分类。如果对Keras或者类似的库没有什么经验,那么你可能无法立即搞懂该例子的所有步骤。可能你压根还没安装Keras,那也没关系咯。下一章会详细讲解该例子的每一步。所以,不用担心啦,准备开始了。

解决的问题:识别灰度的手写数字图片(28 x 28)的数字(0到9)。本例使用机器学习经典的MNIST手写数字数据集,包含60000张训练图片饿10000张测试图片。它是NIST(National Institute of Standards and Technology)数据库的子集,建立于1980年代。你可以把“MNIST手写数字识别”看成是深度学习的“Hello World”,其可以验证算法的正确性。如果成为机器学习工作者,你将会在科学论文、博客等上不断地看到它。图2.1 是MNIST数据集的图片。

image

图2.1 MNIST手写数字图片

类(class)和标签(label)的区别:

在机器学习中,分类问题中的一个类别称之为类(class);数据点称为样本;类关联的具体样本称为标签(label)。

你不需要立即重现本例,下一章3.3小结将会使用Keras。

Keras会预加载MNIST数据集,得到的是四个Numpy数组:

1
2
3
#Listing 2.1. Loading the MNIST dataset in Keras
from keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

train_images 和train_labels形成训练数据集。test_images和test_labels形成测试数据集。图片由NUmpy数组组成,标签是一个数字数组,范围是0到9。图片和标签是一一对应的。

下面看下训练数据:

1
2
3
4
5
6
>>> train_images.shape
(60000, 28, 28)
>>> len(train_labels)
60000
>>> train_labels
array([5, 0, 4, ..., 5, 6, 8], dtype=uint8)

接着是测试数据:

1
2
3
4
5
6
>>> test_images.shape
(10000, 28, 28)
>>> len(test_labels)
10000
>>> test_labels
array([7, 2, 1, ..., 4, 5, 6], dtype=uint8)

接下来的流程是:首先,将训练数据(train_images 和train_labels)赋值给神经网络模型;然后神经网络学习图片及相应的标签;最后训练好的模型输入测试图片test_images进行预测,并用test_labels验证预测值。

下面构建神经网络:

1
2
3
4
5
6
7
#Listing 2.2. The network architecture
from keras import models
from keras import layers
network = models.Sequential()
network.add(layers.Dense(512, activation='relu', input_shape=(28 * 28,)))
network.add(layers.Dense(10, activation='softmax'))

神经网络的核心基础组件是层(layer),它是数据处理模块,可以认为是数据的过滤器(filter)。训练数据进入layer输出有用的形式,具体地说,layer抽取训练数据的学习特征。希望的是学习表征可以有效地解决问题。大部分深度学习包含多个layer形成链式,不断地进行数据提取。深度学习模型由一系列连续的数据过滤器(layer)提炼数据。

本例中的神经网络由两个稠密层(Dense layer)序列组成,其中稠密层也称为全联接层(*fully connected layer)。第二层是个softmax layer,它将输出一个包含10个概率的数组(概率之和为1)。每个概率值表示当前的数字图片属于10个数字分类中某一个的概率。

准备训练的步骤,下面是模型编译步骤(compilation step)中的三步:

  • 损失函数:度量神经网络模型在训练数据集上的性能,使得其向正确的方向迭代。
  • 优化器:基于训练数据和损失函数调整模型的机制。
  • 模型训练和测试的监控指标:本例只关注准确度(正确分类的图片比例)。

后续两章会清晰的讲解损失函数和优化器的功能:

1
2
3
4
#Listing 2.3. The compilation step
network.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])

在模型训练之前进行数据预处理,将数据集按神经网络模型的要求重塑(即,改变大小形状),并归一化到[0, 1]区间。例如,前面训练图片存储为(60000, 28, 28)维德数组,数值类型为uint8,其大小为[0, 255]。这里将其转换成值为0到1的float32类型的(60000, 28 * 28) 维数据。

1
2
3
4
5
6
#Listing 2.4. Preparing the image data
train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype('float32') / 255
test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype('float32') / 255

下面对标签进行编码,具体解释将在第三章给出:

1
2
3
4
5
#Listing 2.5. Preparing the labels
from keras.utils import to_categorical
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)

Keras调用fit方法开始训练神经网络模型:

1
2
3
4
5
>>> network.fit(train_images, train_labels, epochs=5, batch_size=128)
Epoch 1/5
60000/60000 [==============================] - 9s - loss: 0.2524 - acc: 0.9273
Epoch 2/5
51328/60000 [========================>.....] - ETA: 1s - loss: 0.1035 - acc: 0.9692

上面显示的两个数字是训练神经网络模型时的损失值和准确度。

可以看到,很快在训练数据上达到0.989 (98.9%) 的准确度。下面在测试数据集上验证模型的性能:

1
2
3
>>> test_loss, test_acc = network.evaluate(test_images, test_labels)
>>> print('test_acc:', test_acc)
test_acc: 0.9785

测试数据集上的准确度为97.8%,比训练数据集上的小一点。训练数据集上的准确度和测试数据集上的差距意味着过拟合,机器学习模型在新数据集上的效果比训练数据集的差。过拟合将在第三章详细讲解。

从上面可以看到:只用不到20行Python代码实现构建和训练一个神经网络模型来进行手写数字图片识别。下一小结,你将学习张量(,神经网络的数据存储对象),张量操作,梯度下降。

未完待续。。。

Enjoy!

翻译本书系列的初衷是,觉得其中把深度学习讲解的通俗易懂。不光有实例,也包含作者多年实践对深度学习概念、原理的深度理解。最后说不重要的一点,François Chollet是Keras作者。
声明本资料仅供个人学习交流、研究,禁止用于其他目的。如果喜欢,请购买英文原版。


侠天,专注于大数据、机器学习和数学相关的内容,并有个人公众号:bigdata_ny分享相关技术文章。

若发现以上文章有任何不妥,请联系我。

image

12…4
侠天

侠天

侠天,专注于大数据、机器学习和数学相关的内容,并有个人公众号:bigdata_ny分享相关技术文章。

37 日志
微博 InfoQ
© 2018 侠天
由 Hexo 强力驱动
主题 - NexT.Mist