zoukankan      html  css  js  c++  java
  • 【487】深度学习用于文本和序列

    目录:


      将文本分解而成的单元(单词、字符或 n-gram)叫作标记(token),将文本分解成标记的过程叫作分词(tokenization)。所有文本向量化过程都是应用某种分词方案,然后将数值向量与生成的标记相关联。主要有两种方法:对标记作 one-hot 编码(one-hot encoding)与标记嵌入【token embedding,通常只用于单词,叫作词嵌入(word embedding)】。

      词袋(bag-of-words)是一种不保存顺序的分词方法(生成的标记组成一个集合,而不是一个序列,舍弃了句子的总体结构),因此它往往被用于浅层的语言处理模型,而不是深度学习模型。

    一、单词和字符的 one-hot 编码

      one-hot 编码是将标记转换为向量的最常用、最基本的方法。

      Keras 的内置函数可以对原始文本数据进行单词级或字符级的 one-hot 编码。

    import keras
    from keras.preprocessing.text import Tokenizer
    
    samples = ['The cat sat on the mat.', 'The dog ate my homework.']
    
    # 创建一个分词器(tokenizer),设置为只考虑前1000个最常见的单词
    tokenizer = Tokenizer(num_words=1000)
    
    # 构建单词索引,根据samples的内容来创建分词器
    tokenizer.fit_on_texts(samples)
    
    # 将字符串转换为整数索引组成的列表
    # 单词转换为了数字 [[1, 2, 3, 4, 1, 5], [1, 6, 7, 8, 9]]
    sequences = tokenizer.texts_to_sequences(samples)
    
    # 文本转换为了 one-hot编码
    one_hot_results = tokenizer.texts_to_matrix(samples, mode='binary')
    
    # 每个单词对应的索引值
    word_index = tokenizer.word_index
    print('Found %s unique tokens.' % len(word_index))
    
    output:
    Found 9 unique tokens.
    
    sequences
    
    output:
    [[1, 2, 3, 4, 1, 5], [1, 6, 7, 8, 9]]
    
    one_hot_results
    
    output:
    array([[0., 1., 1., ..., 0., 0., 0.],
           [0., 1., 0., ..., 0., 0., 0.]])
    
    word_index
    
    output:
    {'the': 1,
     'cat': 2,
     'sat': 3,
     'on': 4,
     'mat': 5,
     'dog': 6,
     'ate': 7,
     'my': 8,
     'homework': 9}
    

      

    二、使用词嵌入

      将单词与向量相关联还有另一种常用的强大方法,就是使用密集的词向量(word vector),也叫词嵌入(word embedding)。

    • one-hot词向量:稀疏(大部分为 0)、高维(20000 或更高)、硬编码(没有意义)
    • word embedding:密集(0 比较少)、低维(256/512/1024)、从数据中学习得到(具有一定的意义)

      获取词嵌入的两种方法:

    • 在完成主任务的同时学习词嵌入。一开始是随机的词向量,然后对这些词向量进行学习,其学习的方式与学习神经网络的权重相同。
    • 预计算好的词嵌入,然后将其加载到模型中。这些词嵌入叫作预训练词嵌入(pretrained word embedding)。

    2.1 利用 Embedding 层学习词嵌入

      词向量之间的几何关系应该表示这些词之间的语义关系。词嵌入的作用应该是将人类的语言映射到几何空间中。Keras 可以实现学习一个新的嵌入空间。我们要做的就是学习一个层的权重,这个层就是 Embedding 层。

    from keras.layers import Embedding
    
    # Embedding层至少需要两个参数:
    # 标记的个数(这里是1000,即最大单词索引+1)—— 单词数
    # 嵌入的维度(这里是64)—— 词向量维度
    embedding_layer = Embedding(1000, 64)
    

      最好将 Embedding 层理解为一个字典,将整数索引(表示特定单词)映射为密集向量。它接收整数作为输入,并在内部字典中查找这些整数,然后返回相关联的向量。Embedding 层实际上是一种字典查找。

      单词索引 → Embedding 层 → 对应的词向量

    • 单词索引:单词对应的数字
    • Embedding 层:矩阵
    • 对应的词向量:相当于字典,单词与向量一一对应

      Embedding 层的输入是一个二维整数张量,其形状为 (samples, sequence_length),每个元素是一个整数序列。较短的序列应该用 0 填充,较长的序列应该比截断。

      这个 Embedding 层返回一个形状为 (samples, sequence_length, embedding_dimensionality) 的三维浮点数张量。然后可以用 RNN 层或一维卷积层来处理这个三维张量。

      将一个 Embedding 层实例化时,它的权重(即标记向量的内部字典)最开始是随机的,与其他层一样。在训练过程中,利用反向传播来逐渐调节这些词向量。

      加载 IMDB 数据,准备用于 Embedding 层

    from keras.datasets import imdb
    from keras import preprocessing
    # 作为特征的单词个数
    max_features = 10000
    # 在这多单词截断文本(这些单词都属于 max_feature 个最常见的单词)
    # 保留文本的长度
    maxlen = 20
    
    # 将数据加载为整数列表,每个单词对应一个数字
    (x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
    
    # 将整数列表转换成形状为 (samples, maxlen) 的二维整数张量
    # 过长的截断,过短的补0
    x_train = preprocessing.sequence.pad_sequences(x_train, maxlen=maxlen)
    x_test = preprocessing.sequence.pad_sequences(x_test, maxlen=maxlen)
    

      x_train 的每一个元素,都是一个 10 维的向量

      在 IMDB 数据上使用 Embedding 层和分类器

    from keras.models import Sequential
    from keras.layers import Flatten, Dense, Embedding
    
    model = Sequential()
    # input_dim: 10000个最常见的单词
    # output_dim: 每个单词的向量维度为8维
    # input_length: 为文本长度
    # Embedding 层激活的形状为 (samples, maxlen, 8)
    model.add(Embedding(10000, 8, input_length=maxlen))
    
    # 将三维的嵌入张量展平成形状为 (samples, maxlen * 8) 的二维张量
    model.add(Flatten())
    
    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)
    

      结果如下:

    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    embedding_2 (Embedding)      (None, 20, 8)             80000     
    _________________________________________________________________
    flatten_1 (Flatten)          (None, 160)               0         
    _________________________________________________________________
    dense_1 (Dense)              (None, 1)                 161       
    =================================================================
    Total params: 80,161
    Trainable params: 80,161
    Non-trainable params: 0
    _________________________________________________________________
    Train on 20000 samples, validate on 5000 samples
    Epoch 1/10
    20000/20000 [==============================] - 1s 69us/step - loss: 0.6759 - acc: 0.6049 - val_loss: 0.6398 - val_acc: 0.6814
    Epoch 2/10
    20000/20000 [==============================] - 1s 50us/step - loss: 0.5658 - acc: 0.7425 - val_loss: 0.5467 - val_acc: 0.7204
    Epoch 3/10
    20000/20000 [==============================] - 1s 53us/step - loss: 0.4752 - acc: 0.7808 - val_loss: 0.5113 - val_acc: 0.7384
    Epoch 4/10
    20000/20000 [==============================] - 1s 59us/step - loss: 0.4263 - acc: 0.8077 - val_loss: 0.5008 - val_acc: 0.7452
    Epoch 5/10
    20000/20000 [==============================] - 1s 51us/step - loss: 0.3930 - acc: 0.8257 - val_loss: 0.4981 - val_acc: 0.7536
    Epoch 6/10
    20000/20000 [==============================] - 1s 51us/step - loss: 0.3668 - acc: 0.8395 - val_loss: 0.5014 - val_acc: 0.7530
    Epoch 7/10
    20000/20000 [==============================] - 1s 51us/step - loss: 0.3435 - acc: 0.8534 - val_loss: 0.5052 - val_acc: 0.7520
    Epoch 8/10
    20000/20000 [==============================] - 1s 51us/step - loss: 0.3223 - acc: 0.8657 - val_loss: 0.5132 - val_acc: 0.7486
    Epoch 9/10
    20000/20000 [==============================] - 1s 51us/step - loss: 0.3023 - acc: 0.8766 - val_loss: 0.5213 - val_acc: 0.7490
    Epoch 10/10
    20000/20000 [==============================] - 1s 51us/step - loss: 0.2839 - acc: 0.8860 - val_loss: 0.5303 - val_acc: 0.7466
    View Code

      精度为 75%,不过只考虑了评论的前 20 个单词,同时仅仅将嵌入序列展开并在上面训练一个 Dense 层,更好的做法是在嵌入序列上添加循环层或一维卷积层,将每个序列作为整体来学习特征。

    2.2 使用预训练的词嵌入  

    • word2vec
    • Glove(global vectors for word representation,词表示全局向量)

      可以在 Keras 的 Embedding 层直接使用。

    三、理解循环神经网络

      循环神经网络(RNN,recurrent neural network):它处理序列的方式是,遍历所有序列元素,并保存一个状态(state),其中包含与已查看内容相关的信息。Keras 中 SimpleRNN 层实现了最简单的 RNN。

      与 Keras 中的所有循环层一样,SimpleRNN 可以在两种不同的模式下运行:一种是返回每个时间步连续输出的完整序列,即形状为 (batch_size, timesteps, output_features) 的三维张量;另一种是只返回每个输入序列的最终输出,即形状为 (batch_size, output_features) 的二维张量。这两种模式由 return_sequences 这个构造函数参数来控制。我们来看一个使用 SimpleRNN 的例子,它只返回最后一个时间步的输出。

    from keras.models import Sequential
    from keras.layers import Embedding, SimpleRNN
    model = Sequential()
    model.add(Embedding(10000, 32))
    model.add(SimpleRNN(32))
    model.summary()
    
    output:
    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    embedding_1 (Embedding)      (None, None, 32)          320000    
    _________________________________________________________________
    simple_rnn_1 (SimpleRNN)     (None, 32)                2080      
    =================================================================
    Total params: 322,080
    Trainable params: 322,080
    Non-trainable params: 0
    _________________________________________________________________
    

      下面这个例子返回完整的状态序列。

    model = Sequential()
    model.add(Embedding(10000, 32))
    model.add(SimpleRNN(32, return_sequences=True))
    model.summary()
    
    output:
    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    embedding_2 (Embedding)      (None, None, 32)          320000    
    _________________________________________________________________
    simple_rnn_2 (SimpleRNN)     (None, None, 32)          2080      
    =================================================================
    Total params: 322,080
    Trainable params: 322,080
    Non-trainable params: 0
    _________________________________________________________________
    

      为了提高网络的表达能力,将多个循环层逐个堆叠有时也是很有用的。在这种情况下,你需要让所有中间层都返回完整的输出序列。

    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))
    model.add(SimpleRNN(32))
    model.summary()
    
    output:
    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    embedding_3 (Embedding)      (None, None, 32)          320000    
    _________________________________________________________________
    simple_rnn_3 (SimpleRNN)     (None, None, 32)          2080      
    _________________________________________________________________
    simple_rnn_4 (SimpleRNN)     (None, None, 32)          2080      
    _________________________________________________________________
    simple_rnn_5 (SimpleRNN)     (None, None, 32)          2080      
    _________________________________________________________________
    simple_rnn_6 (SimpleRNN)     (None, 32)                2080      
    =================================================================
    Total params: 328,320
    Trainable params: 328,320
    Non-trainable params: 0
    _________________________________________________________________
    

      接下来,我们将这个模型应用于 IMDB 电影评论分类问题。首先,对数据进行预处理。

      准备 IMDB 数据

    from keras.datasets import imdb
    from keras.preprocessing import sequence
    
    max_features = 10000  # number of words to consider as features
    maxlen = 500  # cut texts after this number of words (among top max_features most common words)
    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 层和一个 SimpleRNN 层来训练模型

    from keras.layers import Dense
    
    model = Sequential()
    # 单词数
    # 单词向量维度
    model.add(Embedding(max_features, 32))
    # 输出RNN的单元数
    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)
    

      

    四、理解 LSTM 层和 GRU 层

      SimpleRNN 由于梯度消失问题(vanishing gradient problem),无法学到这种长期依赖信息。

      LSTM,long short-term memory,有 3 个 gate。

      详细参考其他关于 LSTM 的博文

      使用 Keras 中的 LSTM 层

    from keras.layers import LSTM, Dense
    
    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)
    

      验证精度达到 89%,比 SimpleRNN 网络好很多,这主要是因为 LSTM 受梯度消失问题的影响要小得多。

    五、循环神经网络的高级用法

    • 循环 dropout(recurrent dropout):这是一种特殊的内置方法,在循环层中使用 dropout 来降低过拟合。
    • 堆叠循环层(stacking recurrent layers):可以提高网络的表达能力(代价是更高的计算负荷)。
    • 双向循环层(bidirectional recurrent layer)。将相同的信息以不同的方式呈现给循环网络,可以提高精度并缓解遗忘问题。

      GRU,gated recurrent unit,门控循环单元,其工作原理与 LSTM 相同,但它做了一些简化,因此运行的计算代价更低(虽然表示能力可能不如 LSTM)。机器学习中到处可以见到这种计算代价与表示能力之间的折中。

    5.1 循环 dropout(recurrent dropout)

      Keras 的每个循环层都有两个与 dropout 相关的参数:一个是 dropout,它是一个浮点数,指定该层输入单元的 dropout 比率;另一个是 recurrent_dropout,指定循环单元的 dropout 比率。

    ...
    model = Sequential()
    model.add(layers.GRU(32,
                         dropout=0.2,
                         recurrent_dropout=0.2,
                         input_shape=(None, float_data.shape[-1])))
    model.add(layers.Dense(1))
    ...
    

      双向 RNN 层的工作原理

    5.2 堆叠循环层(stacking recurrent layers)

      可以构建更加强大的循环网络。在 Keras 中逐个堆叠循环层,所有中间层都应该返回完整的输出序列(一个 3D 张量),而不是只返回最后一个时间步的输出。这可以通过指定 return_sequences=True 来实现。

    from keras.models import Sequential
    from keras import layers
    from keras.optimizers import RMSprop
    
    model = Sequential()
    model.add(layers.GRU(32,
                         dropout=0.1,
                         recurrent_dropout=0.5,
                         return_sequences=True,
                         input_shape=(None, float_data.shape[-1])))
    model.add(layers.GRU(64, activation='relu',
                         dropout=0.1, 
                         recurrent_dropout=0.5))
    model.add(layers.Dense(1))
    

      

    5.3 双向循环层(bidirectional recurrent layer)

      可以考虑人生如果返老还童,那么会有完全不同的心智模型,这就是双向 RNN 的想法。

      在 Keras 中将一个双向 RNN 实例化,我们需要使用 Bidirectional 层,它的第一个参数是一个循环层实例。Bidirectional 对这个循环层创建了第二个单独实例,然后使用一个实例按正序处理输入序列,另一个实例按照逆序处理输入序列。

      Bidirectional 相当于有 LSTM 正反两层,可以参见下面的对比参数,BiLSTM 是 LSTM 的两倍。

    model = Sequential()
    model.add(layers.Embedding(10000, 32))
    model.add(layers.Bidirectional(layers.LSTM(32)))
    model.add(layers.Dense(1, activation='sigmoid'))
    
    model.summary()
    

      output:

    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    embedding_1 (Embedding)      (None, None, 32)          320000    
    _________________________________________________________________
    bidirectional_1 (Bidirection (None, 64)                16640     
    _________________________________________________________________
    dense_3 (Dense)              (None, 1)                 65        
    =================================================================
    Total params: 336,705
    Trainable params: 336,705
    Non-trainable params: 0
    _________________________________________________________________
    

      LSTM:

    model = Sequential()
    model.add(layers.Embedding(10000, 32))
    model.add(layers.LSTM(32))
    model.add(layers.Dense(1, activation='sigmoid'))
    
    model.summary()
    

      output:

    _________________________________________________________________
    Layer (type)                 Output Shape              Param #   
    =================================================================
    embedding_4 (Embedding)      (None, None, 32)          320000    
    _________________________________________________________________
    lstm_4 (LSTM)                (None, 32)                8320      
    _________________________________________________________________
    dense_6 (Dense)              (None, 1)                 33        
    =================================================================
    Total params: 328,353
    Trainable params: 328,353
    Non-trainable params: 0
    _________________________________________________________________
    

      

  • 相关阅读:
    fstream的使用方法
    Java按键事件KeyEvent
    java之 22天 GUI 图形界面编程(二)
    java之 22天 GUI 图形界面编程(一)
    java使用省略号代替多参数(参数类型... 参数名)
    jsoup
    C++获得本机所有网卡的IP和MAC地址信息
    otl中遇到的一些字符数组长度问题
    otl使用存储过程或是LEFT JOIN时提示输出类型未知的问题
    Android:布局实例之常见用户设置界面
  • 原文地址:https://www.cnblogs.com/alex-bn-lee/p/13758190.html
Copyright © 2011-2022 走看看