TF2.0 文本分类的基本方法

本文的示例为使用 IMDB 的评论数据来做情感分类(sentiment analysis):
数据源地址:https://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz

1. 加载数据集

使用 tf.keras.preprocessing.text_dataset_from_directory() 函数从目录中加载文本数据集,需要目录保持以下结构:

main_directory/
...class_a/
......a_text_1.txt
......a_text_2.txt
...class_b/
......b_text_1.txt
......b_text_2.txt

其中 class_a / class_b 是分类标签,示例中数据集为 IMDB的评论数据,目录结构为:

➜  aclImdb ll
total 3432
-rw-r--r--  1 hongbin.dhb  staff    4037  6 26  2011 README
-rw-r--r--  1 hongbin.dhb  staff  845980  4 13  2011 imdb.vocab
-rw-r--r--  1 hongbin.dhb  staff  903029  6 12  2011 imdbEr.txt
drwxr-xr-x  7 hongbin.dhb  staff     224  4 13  2011 test
drwxr-xr-x  9 hongbin.dhb  staff     288 10 10 19:51 train

加载数据集的代码如下:

# 每批大小
batch_size = 32
seed = 42

# 从train目录中抽取80%作为训练集
raw_train_ds = tf.keras.preprocessing.text_dataset_from_directory(
    'aclImdb/train', 
    batch_size=batch_size, 
    validation_split=0.2, 
    subset='training', 
    seed=seed)

# 从目录train中抽取20%作为验证集
raw_val_ds = tf.keras.preprocessing.text_dataset_from_directory(
    'aclImdb/train', 
    batch_size=batch_size, 
    validation_split=0.2, 
    subset='validation', 
    seed=seed)

# 从目录test中加载测试集
raw_test_ds = tf.keras.preprocessing.text_dataset_from_directory(
    'aclImdb/test', 
    batch_size=batch_size)

返回值 raw_train_ds 是带标注的数据集,类型为 tf.data.Dataset,
需要注意的是,从训练集中摘取验证集需要使用 validation_split 和 subset 参数,同时需要指定 seed 参数,或者使用 shuffle=False 来保证训练集和验证集数据没有交叉。

下面看一下加载的数据集的标注 lable 和 class_name:

for text_batch, label_batch in raw_train_ds.take(1):
  for i in range(3):
    print("Review", text_batch.numpy()[i])
    print("Label", label_batch.numpy()[i])

标注label的值为 0 / 1,标注的类别名用 raw_train_ds.class_name[0/1] 获取:

print("Label 0 corresponds to", raw_train_ds.class_names[0])
print("Label 1 corresponds to", raw_train_ds.class_names[1])

输出

Label 0 corresponds to neg
Label 1 corresponds to pos

2. 数据预处理

数据预处理需要对文本进行 标准化(Standardize)、分词(Tokenize)、向量化(Vectorize) 处理

  • 标准化通常指去掉文本中的标点符号或 HTML 标签
  • 分词指将文本切分为一个一个的单词
  • 向量化指将一个一个单词转化为数字,方便喂入神经网络

这里是一个自定义的标准化处理方法

def custom_standardization(input_data):
	# 文本转换为小写字母 
	lowercase = tf.strings.lower(input_data)
	# 替换 <br /> 标签为空格(单词分隔符)  
	stripped_html = tf.strings.regex_replace(lowercase, '<br />', ' ')
	# 删除标点符号,一般标点符号后面都有空格
	return tf.strings.regex_replace(stripped_html,
                                  '[%s]' % re.escape(string.punctuation),
                                  '')

使用 TextVectorization 方法 做文本标准化,分词,及向量化处理

from tensorflow.keras.layers.experimental.preprocessing import TextVectorization

# 最大的特征数量,也就是分词后最大的单词数量
max_features = 10000
# 最大的向量长度
sequence_length = 250

# 构建向量化层(用于做文本向量化处理)
# 用自定义标准化方法做标准化处理
# 最大分词数量 10000
# 输出的向量长度 250
vectorize_layer = TextVectorization(
    standardize=custom_standardization,
    max_tokens=max_features,
    output_mode='int',
    output_sequence_length=sequence_length)

接下来需要调用 adapt 方法来将文本转换为我们需要的向量数据

# 取原始训练集中的文本部分(不取标签字段)
train_text = raw_train_ds.map(lambda x, y: x)
vectorize_layer.adapt(train_text)

定义一个函数处理原始数据集

def vectorize_text(text, label):
	# 将文本变成一维数组
	text = tf.expand_dims(text, -1)
	# 输出文本向量以及标签
	return vectorize_layer(text), label

处理训练集,验证集和测试集数据

# 数据集的每一行都是 文本 和 标注
train_ds = raw_train_ds.map(vectorize_text)
val_ds = raw_val_ds.map(vectorize_text)
test_ds = raw_test_ds.map(vectorize_text)

最后还有重要的一步,为了数据处理性能,需要将向量化后的数据放入缓存

AUTOTUNE = tf.data.experimental.AUTOTUNE

train_ds = train_ds.cache().prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)
test_ds = test_ds.cache().prefetch(buffer_size=AUTOTUNE)

到这里,数据预处理基本完成了,train_ds, val_ds, test_ds 可以用于输入我们的模型。

3. 构建网络模型

embedding_dim = 16

model = tf.keras.Sequential([
	layers.Embedding(max_features + 1, embedding_dim),
	layers.Dropout(0.2),
	layers.GlobalAveragePooling1D(),
	layers.Dropout(0.2),
	layers.Dense(1)])

model.summary()

输出

Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (None, None, 16)          160016    
_________________________________________________________________
dropout (Dropout)            (None, None, 16)          0         
_________________________________________________________________
global_average_pooling1d (Gl (None, 16)                0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 16)                0         
_________________________________________________________________
dense (Dense)                (None, 1)                 17        
=================================================================
Total params: 160,033
Trainable params: 160,033
Non-trainable params: 0
_________________________________________________________________

模型说明:

  • 第一层是嵌入层,嵌入层的输入以单词索引下标作为整数编码后的数据,然后查每个单词索引下标对应的向量,这些向量是模型训练的时候学出来的,具体的原理和过程我们以后说
  • 接着GlobalAveragePooling1D层
  • 最后是一个只有1个输出节点的全连接层(激活函数为 sigmod)

4. 损失函数和优化器

因为是二分类问题,并且模型输出了分类的概率
所以使用 losses.BinaryCrossentropy 损失函数

model.compile(
	loss=losses.BinaryCrossentropy(from_logits=True), 
	optimizer='adam', 
	metrics=tf.metrics.BinaryAccuracy(threshold=0.0)
)

5. 模型训练

仅需要传入 dataset 给模型的 fit 方法

epochs = 10
history = model.fit(
    train_ds,
    validation_data=val_ds,
    epochs=epochs)

6. 评估模型

loss, accuracy = model.evaluate(test_ds)

print("Loss: ", loss)
print("Accuracy: ", accuracy)

输出

782/782 [==============================] - 3s 3ms/step - loss: 0.3104 - binary_accuracy: 0.8734
Loss:  0.31037017703056335
Accuracy:  0.8733999729156494

7. 绘制模型损失和准确率曲线

模型训练方法 model.fit 返回了 history 对象,包含了训练过程中的精确率和损失的数据,使用history来绘制相关曲线

绘制损失曲线

history_dict = history.history

# 训练集上的准确率
acc = history_dict['binary_accuracy']
# 验证集上的准确率
val_acc = history_dict['val_binary_accuracy']
# 训练集上的损失
loss = history_dict['loss']
# 验证集上的损失
val_loss = history_dict['val_loss']

epochs = range(1, len(acc) + 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()

绘制准确率曲线

# 绘制训练准确率
plt.plot(epochs, acc, 'ro', label='Training acc')
# 绘制验证准确率
plt.plot(epochs, val_acc, 'r', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend(loc='lower right')

plt.show()

8. 模型导出

上门我们使用TextVectorization 方法处理了文本数据,再喂入模型训练,如果想直接使用原始的文本数据来训练模型并做预测,可以构建一个新模型,在第一层使用 vectory_layer 层,第二层为上面我们训练好的模型,最后一层接一个 sigmod 目标函数,这个新模型不需要训练了,可直接编译使用

# 构建新模型,使用之前训练好的模型
export_model = tf.keras.Sequential([
  vectorize_layer,
  model,
  layers.Activation('sigmoid')
])

# 模型编译,指定损失函数及优化器
export_model.compile(
    loss=losses.BinaryCrossentropy(from_logits=False), 
    optimizer="adam", 
    metrics=['accuracy']
)

# 使用原始的测试集数据评估模型准确性
loss, accuracy = export_model.evaluate(raw_test_ds)
print(accuracy)

输出

782/782 [==============================] - 4s 5ms/step - loss: 0.3104 - accuracy: 0.8734
0.8733999729156494

使用新的模型来预测新数据的情感分类:

examples = [
  "The movie was great!",
  "The movie was okay.",
  "The movie was terrible..."
]

export_model.predict(examples)

输出

array([[0.634246  ],
       [0.45762002],
       [0.37179616]], dtype=float32)

将文本处理层(标准化,分词,向量化)放入模型中,导出训练好的模型,可以简单地处理原始文本,并且能减少训练集和测试的数据倾斜问题

但是将文本处理层放到模型外边,能更好的利用CPU的并行处理能力,以及在GPU上训练是数据能够得到缓存,所以通常在模型开发的时候,我们将文本处理层放到外面,在模型部署的时候再将文本处理层放到模型里面。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值