本文正在参与「金石方案 . 瓜分6万现金大奖」

前言

本文首要运用 cpu 版别的 tensorflow 2.4 版别完结文本的 word embedding 练习,而且以此为基础完结影评文本分类任务。

本文纲要

  1. 三种文本向量化办法
  2. 获取数据
  3. 处理数据
  4. 建立、练习模型
  5. 导出练习好的词嵌入向量

详细介绍

1. 三种文本向量化办法

通常在深度学习模型中咱们的输入都是以向量形式存在的,所以咱们处理数据进程的重要一项任务就是将文本中的 token (一个 token 可所以英文单词、一个汉字、一个中文词语等,需求自己规则)转化成对应的向量,本文会给出三种常见文本向量化的战略。

(1)One-Hot Encodings 。其实很好了解,假设咱们的数据是“我是人”,由于有 3 个不同的汉字,我会给每个汉字一个对应的索引,然后我会创立一个长度为 3 的向量,假设我给每个汉字赋予的索引为“我->0”“是->1”“人->2”,那么每个字对应的 One-Hot Encodings 为 [1,0,0]、[0,1,0]、[0,0,1] 。那么“我是人”的这个句子的向量表明就能够将这三个向量拼接起来即可。这种办法的优点显着,方便了解和实现,可是缺陷也很显着,功率非常低。One-Hot Encodings 所产生的的向量都是稀疏的。假设词汇表中有 1000 个单词,要对每个单词进行向量化编码,其间简直 99% 的位置都为零。

(2)encode each word with a unique num 。咱们能够运用唯一的数字对每个单词进行编码。还是上面的例子,咱们给每个字分配一个对应的整数,假设分配成果为 “我->1”“是->2”“人->3”,我就能将句子“我是人”这句话就能够编码为一个稠密向量,如 [1,2,3]。此时的向量是一个稠密向量(一切位置都有有意义的整数填充)。可是这种办法有个缺陷,编码的数字是能够人为恣意设置,它不能捕获汉字之间的任何语义联系,也无法从数字上看出对应的近义词之间的联系。

(3)Word Embeddings 。词嵌入是一种将单词编码为有用稠密向量的办法,其间相似的单词具有相似相近的向量编码。词嵌入是浮点类型的稠密向量,向量的长度需求人为指定。咱们不必像上面两种办法手动去设置编码中的向量值,而是将他们都作为可练习的参数,经过给模型喂很多的数据,不断的练习来捕获单词之间的细粒度语义联系,常见的词向量维度能够设置从 8 维到 1024 维规模中的恣意整数。理论上维度越高词嵌入的语义越丰富可是练习本钱越高。如咱们上面的例子,咱们设置词嵌入维度为 4 ,最终经过练习得到的词嵌入可能是 “我->[-3.2, 1.5, -4,6, 3.4]”“是-> [0.2, 0.6, -0.6, 1.5]”“人->[3.4, 5.3, -7.2, 1.5]”。

2. 获取数据

(1)本次咱们要用到的是数据是 Large Movie Review Dataset ,咱们需求运用 tensorflow 的内置函数从网络上下载到本地磁盘,为了简化数据,咱们将练习数据目录中的 unsup 子目录都删除,最终取出 20000 个练习样本作为练习集,取出 5000 个练习样本作为验证集。

import io
import os
import re
import shutil
import string
import tensorflow as tf
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense, Embedding, GlobalAveragePooling1D
from tensorflow.keras.layers import TextVectorization
batch_size = 512
seed = 1
url = "https://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz"
dataset = tf.keras.utils.get_file("aclImdb_v1.tar.gz", url,  untar=True, cache_dir='.', cache_subdir='')
dataset_dir = os.path.join(os.path.dirname(dataset), 'aclImdb')
train_dir = os.path.join(dataset_dir, 'train')
remove_dir = os.path.join(train_dir, 'unsup')
shutil.rmtree(remove_dir)
train_datas = tf.keras.utils.text_dataset_from_directory( 'aclImdb/train', batch_size=batch_size, validation_split=0.2, subset='training', seed=seed)
val_datas = tf.keras.utils.text_dataset_from_directory( 'aclImdb/train', batch_size=batch_size, validation_split=0.2, subset='validation', seed=seed)

(2)这儿展示出 2 条样本,每个样本都有一个标签和一个文本描绘,标签 1 表明评论是 positive , 标签 0 表明评论是: negative 。

1 b'The first time I saw this film, I was in shock for days afterwards. Its painstaking and absorbing treatment of the subject holds the attention, helped by good acting and some really intriguing music. The ending, quite simply, had me gasping. First rate!'
0 b"This is quite possibly the worst movie of all time. It stars Shaquille O'Neil and is about a rapping genie. Apparently someone out there thought that this was a good idea and got suckered into dishing out cash to produce this wonderful masterpiece. The movie gets 1 out of 10."

3. 处理数据

(1)为了确保在加载数据的时分不会呈现 I/O 不会阻塞,咱们在从磁盘加载完数据之后,运用 cache 会将数据保存在内存中,确保在练习模型进程中数据的获取不会成为练习速度的瓶颈。如果说要保存的数据量太大,能够运用 cache 创立磁盘缓存提高数据的读取功率。另外咱们还运用 prefetch 在练习进程中能够并行执行数据的预获取。

AUTOTUNE = tf.data.AUTOTUNE
train_datas = train_datas.cache().prefetch(buffer_size=AUTOTUNE)
val_datas = val_datas.cache().prefetch(buffer_size=AUTOTUNE)

(2)将练习数据中的标签去掉,只保留文本描绘,然后运用 TextVectorization 对数据进行预处理,先转化层小写英文,然后再将无用的字符剔除,而且咱们规则了每个文本的最大长度为 100 个单词,超过的文本部分会被丢掉。最终将练习数据中的词都放入一个最大为 10000 的词汇表中,其间有一个特殊的表明 OOV 的 [UNK] ,也就说来自练习数据中的词只要 9999 个,运用 vectorize_layer 为每个单词进行 int 向量化,其实就是在文章开头提到的第二种向量化战略。

def handle(input_data):
    lowercase = tf.strings.lower(input_data)
    stripped_html = tf.strings.regex_replace(lowercase, '<br />', ' ')
    return tf.strings.regex_replace(stripped_html, '[%s]' % re.escape(string.punctuation), '')
vocab_size = 10000
sequence_length = 100
vectorize_layer = TextVectorization(standardize=handle,
                                    max_tokens=vocab_size,
                                    output_mode='int',
                                    output_sequence_length=sequence_length)
text_datas = train_datas.map(lambda x, y: x)
vectorize_layer.adapt(text_datas)

4. 建立、练习模型

咱们此次建立的模型是一个“Continuous bag of words” 风格的模型。

(1)第一层是现已上面初始化好的 vectorize_layer ,它能够将文本经过预处理,然后将分割出来的单词都赋予对应的整数。

(2)第二层是一个嵌入层,咱们定义了词嵌入维度为 32,也就是为每一个词对应的整数都转化为一个 32 维的向量来进行表明,这些向量的值是能够在模型练习时进行学习的权重参数。经过此层输出的维度为:(batch_size, sequence_length, embedding_dim)。

(3)第三层是一个 GlobalAveragePooling1D 操作,由于每个样本的维度为 (sequence_length, embedding_dim) ,该操作能够依照对 sequence_length 维度求平均值来为每个样本返回一个固定长度的输出向量,最终输出的维度为:(batch_size, embedding_dim)。

(4)第四层是一个输出 32 维向量的全衔接层操作,而且运用 relu 激活函数进行非线性变化。

(5)最终一层是一个输出 1 维向量的全衔接层操作,表明该样本的归于 positive 的概率。

(6)优化器挑选 Adam ,丢失函数为 BinaryCrossentropy ,评估指标为 accuracy

embedding_dim=32
model = Sequential([
  vectorize_layer,
  Embedding(vocab_size, embedding_dim, name="embedding"),
  GlobalAveragePooling1D(),
  Dense(32, activation='relu'),
  Dense(1)
])
model.compile(optimizer='adam', loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),  metrics=['accuracy'])
model.fit(train_datas, validation_data=val_datas, epochs=20, callbacks=[tensorboard_callback])

练习进程打印:

Epoch 1/20
40/40 [==============================] - 3s 52ms/step - loss: 0.6898 - accuracy: 0.4985 - val_loss: 0.6835 - val_accuracy: 0.5060
Epoch 2/20
40/40 [==============================] - 2s 50ms/step - loss: 0.6654 - accuracy: 0.4992 - val_loss: 0.6435 - val_accuracy: 0.5228
...
Epoch 19/20
40/40 [==============================] - 2s 49ms/step - loss: 0.1409 - accuracy: 0.9482 - val_loss: 0.4532 - val_accuracy: 0.8210
Epoch 20/20
40/40 [==============================] - 2s 48ms/step - loss: 0.1327 - accuracy: 0.9528 - val_loss: 0.4681 - val_accuracy: 0.8216	

5. 导出练习好的词嵌入向量

这儿咱们取出现已练习好的词嵌入,然后打印出前三个单词以及词向量,由于索引 0 的词是空字符,所以直接跳过了,只显示了两个单词的内容。咱们能够将一切练习好的词嵌入向量都写入本地磁盘的文件,供以后运用。

weights = model.get_layer('embedding').get_weights()[0]
vocab = vectorize_layer.get_vocabulary()
for i, word in enumerate(vocab[:3]):
    if i == 0:
        continue   
    vecoter = weights[i]
    print(word,"||", ','.join([str(x) for x in vecoter]))

单词和对应词嵌入向量:

[UNK] || 0.020502748,-0.038312573,-0.036612183,-0.050346173,-0.07899615,-0.03143682,-0.06429587,0.07334388,-0.01887771,-0.08744612,-0.021639654,0.04726765,0.042426057,0.2240213,0.022607388,-0.08052631,0.023943739,0.05245169,-0.017815227,0.053340062,-0.033523336,0.057832733,-0.007486237,-0.16336738,0.022891225,0.12611994,-0.11084395,-0.0076115266,-0.03733231,-0.010371257,-0.045643456,-0.05392711
the || -0.029460065,-0.0021714368,-0.010394105,-0.03353872,-0.097529344,-0.05249973,-0.03901586,0.009200298,-0.085409686,-0.09302798,-0.07607663,0.046305165,-0.010357974,0.28357282,0.009442638,-0.036655612,0.063269086,0.06721396,0.063007854,0.03185595,-0.014642656,0.089468665,-0.014918188,-0.15671577,0.043026615,0.17086154,-0.0461816,0.021180542,-0.045269016,-0.101499856,-0.03948177,0.028299723