文本预处理步骤
1. 标准化文本
对输入的文本进行统一的标准化,比如去掉文本中的HTML标签,或者去掉标点符号,或者大写转小写等,执行统一的标准化有助于后续的处理,比如分词。
下面对一段文本进行处理,去掉HTML标签,并将大写转为小写如下
标准化前:<p>I'm a boy, I like ML.</p>
标准化后:i'm a boy, i like ml.
2. 将文本切分成更小的单元(分词)
我们已经有了标准化后的句子了,但在将数据输入进神经网络模型之前,还需要做一些事情,即将句子进行分词;这样便于统计整个文本的词汇量,从而建立词袋模型。
分词前:i'm a boy, i like ml
分词后:['i', "'", 'm', 'a', 'boy', ',', 'i', 'like', 'ml', '.']
分词后的单元不一定要具有词义,这取决于分词的作用是什么。
3. 按某种方式重组单元形成Token(多元文法)
如果需要的话,我们还应该按照某种形式将拆分后的单元进行重组,比如n-grams(n个词组成的序列)
// 三元文法序列,重组后的每个元素被称为Token
["i ' m", "' m a", 'm a boy', 'a boy ,', 'boy , i', ', i like', 'i like ml', 'like ml .']
4. 将每个Token与一个唯一的数字关联起来
当我们对整个数据集进行分词后,就能够统计出当前数据集的词汇量了,假设总词汇是1000词,就能够为每一个Token分配一个1000以内的数字,并保证每个Token拿到的数字是唯一的
5. 向量化文本
当我们知道每个词对应的是什么数字时,就可以将每个句子转换成数字序列了,即向量化。当然,词向量的数字类型可以是int,也可以是float。
Token列表:['i', 'm', 'a', 'boy', 'i', 'like', 'ml']
词向量:[32, 103, 54, 672, 32, 78, 23]
有了词向量,就能够将其作为神经网络模型的输入了。
注意:当我们利用模型进行预测时,输入的内容也需要经过同样的预处理流程。
基于TensorFlow实现文本预处理
首先确保安装了TensorFlow和TensorFlow-text,TensorFlow-text模块提供了一些基础分词器
pip install -q -U tensorflow
pip install -q -U tensorflow-text
导入相关的包
import tensorflow as tf
import tensorflow_text as text
标准化
转化字符集编码
我们需要对文本使用同一的字符集编码,常用的字符集是UTF-8,通过TF可以轻松的进行编码转换
docs = tf.constant([u'Everything not saved will be lost.'.encode('UTF-16-BE'), u'Sad☹'.encode('UTF-16-BE')])
utf8_docs = tf.strings.unicode_transcode(docs, input_encoding='UTF-16-BE', output_encoding='UTF-8')
分词
TensorFlow-text组件提供了一些最简单的分词器,列举其中两个如下
WhitespaceTokenizer
该分词器提供一种基于空格的基本分词方式,通常这种分词器可以对英文分词,但对中文就不适用了(中文分词有许多开源实现,比如hanlp)
import tensorflow_text as text
tokenizer = text.WhitespaceTokenizer()
en_tokens = tokenizer.tokenize(['I\'m a boy.'])
cn_tokens = tokenizer.tokenize(['植物大战僵尸'])
print('英文分词:', en_tokens.to_list())
print('中文分词:', cn_tokens.to_list())
Output:
英文分词: [[b"I'm", b'a', b'boy.']]
中文分词: [[b'\xe6\xa4\x8d\xe7\x89\xa9\xe5\xa4\xa7\xe6\x88\x98\xe5\x83\xb5\xe5\xb0\xb8']]
UnicodeScriptTokenizer
从这个名称应该能大致知道这种分词器是基于Unicode编码的,它的功能非常像WhitespaceTokenizer,不同之处在于它能够分隔标点符号,效果如下
import tensorflow_text as text
tokenizer = text.UnicodeScriptTokenizer()
en_tokens = tokenizer.tokenize(['I\'m a boy.'])
print(en_tokens.to_list())
Output:
[[b'I', b"'", b'm', b'a', b'boy', b'.']]
这两种分词器是无法对中文文本分词的,有一种最简单的分词方式就是将文本按照单个Unicode字符分隔,tf.strings模块提供了这样的api,我们来看看
import tensorflow as tf
tokens = tf.strings.unicode_split(input=['你好。'],input_encoding='UTF-8')
print(tokens.to_list())
Output:
[[ b'\xe4\xbd\xa0', // 你
b'\xe5\xa5\xbd', // 好
b'\xe3\x80\x82']] // 。
N-grams
另外,Tensorflow-text还提供了N元文法(n-grams)的实现,如下是一个实现二元文法的例子
tokenizer = text.WhitespaceTokenizer()
tokens = tokenizer.tokenize(['Everything not saved will be lost.'])
two_grams = text.ngrams(tokens, width=2, reduction_type=text.Reduction.STRING_JOIN)
print(two_grams.to_list())
Output:
[[b'Everything not', b'not saved', b'saved will', b'will be', b'be lost.']]
词汇表
当我们有了Token列表后,便可以统计并创建词汇表了,以下代码简单做了几件事:
- 使用分词器将文本切分成Token列表
- 利用字典表vocab_dict,统计每个Token的频次
- 将vocab_dict的元素按照词频倒序排列
- 只取vocab_dict的前10000个元素作为词汇表vocab
import collections
VOCAB_SIZE = 10000
tokens = tokenizer.tokenize(dataset)
vocab_dict = collections.defaultdict(lambda: 0)
for toks in tokens.as_numpy_iterator():
for tok in toks:
vocab_dict[tok] += 1
vocab = sorted(vocab_dict.items(), key=lambda x: x[1], reverse=True)
vocab = [token for token, count in vocab]
vocab = vocab[:VOCAB_SIZE]
print("Vocab size: ", len(vocab))
print("First five vocab entries:", vocab[:5])
TextVectorization
TensorFlow提供了文本预处理层preprocessing.TextVectorization,经过该层的处理,能够将一批文本转换成Token索引列表。也就是说,TextVectorization能够为我们执行预处理所需的一系列步骤,我们只需要定义相应的处理函数即可。
tf.keras.layers.experimental.preprocessing.TextVectorization(
max_tokens=None,
standardize=LOWER_AND_STRIP_PUNCTUATION,
split=SPLIT_ON_WHITESPACE,
ngrams=None,
output_mode=INT,
output_sequence_length=None,
pad_to_max_tokens=True,
vocabulary=None)
| 参数 | 说明 |
|---|---|
| max_tokens | 词汇表的最大值,若设置为None则表示不限制其大小;若词汇数大于了最大值,则会将出现次数最少的词丢掉;注意,该最大值是包含了oov令牌数量的,所以最大词汇量是max_tokens-size(oov)的大小。 |
| standardize | 标准化方式,默认是LOWER_AND_STRIP_PUNCTUATION,即转小写并去掉标点符号;可以自定义Callable函数。 |
| split | 分词器,默认是按空格分词;可以自定义分词方式。 |
| ngrams | 是否创建n-grams,参数可以是None或者任意整数,None代表不创建。 |
| output_mode | 参数可以是"int",“binary”,“count"和"tf-idf”;当设置为int时,输出为token索引列表;当设置为binary时,输出为one-hot编码;当设置为count时,类似binary,不同在于数组的每一位上表示词的统计个数;当设置为tf-idf时,类似binary,不同在于数组每一位上的个数表示该词在其他文本中出现的频次。 |
| output_sequence_length | 只有output_mode设置为int时才有效;参数表示输出向量的维度,其维度会根据设置的长度进行填充或删减 |
| pad_to_max_tokens | 只有output_mode设置为binary、count和tf-idf时才有效;如果设置为True,即使词汇表的token数少于max_tokens,输入的特征向量也会被填充至max_tokens的大小 |
| vocabulary | 设置词汇表;参数可以是词汇list也可以是词汇表所在的文件路径;注意词汇表中的token不能重复,否则会报错 |
使用示例
# 测试数据
text_dataset = tf.data.Dataset.from_tensor_slices(["foo", "bar", "baz"])
max_features = 5000 # 最大词汇表大小
max_len = 4 # 词向量维度
embedding_dims = 2
# 创建TextVectorization
vectorize_layer = TextVectorization(
max_tokens=max_features,
output_mode='int',
output_sequence_length=max_len)
# 创建词汇表,对于大数据集,使用批量输入是最好的方式;
# 若已有词汇表,则可以调用vectorize_layer.set_vocabulary(vocab)直接设置
vectorize_layer.adapt(text_dataset.batch(64))
# 创建模型
model = tf.keras.models.Sequential()
# 小批量输入,每批次包含32条string类型的数组
model.add(tf.keras.Input(shape=(32,), dtype=tf.string))
# 将TextVectorization作为第一层
# 通过这一层后,会输出形如(batch_size, max_len)的张量
model.add(vectorize_layer)
# 现在模型具有了将字符串转为词向量的能力
# 随后便可以将词向量输入进词嵌入层进行下一层的学习了
input_data = [["foo qux bar"], ["qux baz"]]
model.predict(input_data)
Output:
array([[2,1,4,0],
[1,3,0,0]])
参考文档:
Load and preprocess data – Text
tf.keras.layers.experimental.preprocessing.TextVectorization