文本数据加载与增强

在线运行下载Notebook下载样例代码查看源文件

随着可获得的文本数据逐步增多,对文本数据进行预处理,以便获得可用于网络训练所需干净数据的诉求也更为迫切。文本数据集预处理通常包括文本数据集加载与数据增强两部分。

文本数据加载通常包含以下三种方式:

  1. 通过文本读取的Dataset接口如ClueDatasetTextFileDataset进行读取。

  2. 将数据集转成标准格式(如MindRecord格式),再通过对应接口(如MindDataset)进行读取。

  3. 通过GeneratorDataset接口,接收用户自定义的数据集加载函数,进行数据加载,用法可参考自定义数据集加载章节。

加载文本数据

下面我们以从TXT文件中读取数据为例,介绍TextFileDataset的使用方式,更多文本数据集加载相关信息可参考API文档

  1. 准备文本数据,内容如下:

Welcome to Beijing
北京欢迎您!
我喜欢China!
  1. 创建tokenizer.txt文件并复制文本数据到该文件中,将该文件存放在./datasets路径下。执行如下代码:

[1]:
import os

if not os.path.exists('./datasets'):
    os.mkdir('./datasets')

# 把上面的文本数据写入文件tokenizer.txt
file_handle = open('./datasets/tokenizer.txt', mode='w')
file_handle.write('Welcome to Beijing \n北京欢迎您! \n我喜欢China! \n')
file_handle.close()

上面的代码执行完后,数据集结构为:

./datasets
└── tokenizer.txt
  1. 从TXT文件中加载数据集并打印。代码如下:

[2]:
import mindspore.dataset as ds
import mindspore.dataset.text as text

# 定义文本数据集的加载路径
DATA_FILE = './datasets/tokenizer.txt'

# 从tokenizer.txt中加载数据集
dataset = ds.TextFileDataset(DATA_FILE, shuffle=False)

for data in dataset.create_dict_iterator(output_numpy=True):
    print(text.to_str(data['text']))
Welcome to Beijing
北京欢迎您!
我喜欢China!

文本数据增强

针对文本数据增强,常用操作包含文本分词、词汇表查找等:

  • 文本分词:将原始一长串句子分割成多个基本的词汇。

  • 词汇表查找:查找分割后各词汇对应的id,并将句子中包含的id组成词向量传入网络进行训练。

下面对数据增强过程中用到的分词功能、词汇表查找等功能进行介绍,更多关于文本处理API的使用说明,可以参考API文档

构造与使用词汇表

词汇表提供了单词与id对应的映射关系,通过词汇表,输入单词能找到对应的单词id,反之依据单词id也能获取对应的单词。

MindSpore提供了多种构造词汇表(Vocab)的方法,可以从字典、文件、列表以及Dataset对象中获取原始数据,以便构造词汇表,对应的接口为:from_dictfrom_filefrom_listfrom_dataset

以from_dict为例,构造Vocab的方式如下,传入的dict中包含多组单词和id对。

[3]:
from mindspore.dataset import text

# 构造词汇表
vocab = text.Vocab.from_dict({"home": 3, "behind": 2, "the": 4, "world": 5, "<unk>": 6})

Vocab提供了单词与id之间相互查询的方法,即:tokens_to_idsids_to_tokens方法,用法如下所示:

[4]:
# 根据单词查找id
ids = vocab.tokens_to_ids(["home", "world"])
print("ids: ", ids)

# 根据id查找单词
tokens = vocab.ids_to_tokens([2, 5])
print("tokens: ", tokens)
ids:  [3, 5]
tokens:  ['behind', 'world']

从上面打印的结果可以看出:

  • 单词"home""world"的id分别为35

  • id为2的单词为"behind",id为5的单词为"world"

这一结果也与词汇表一致。此外Vocab也是多种分词器(如WordpieceTokenizer)的必要入参,分词时会将句子中存在于词汇表的单词,前后分割开,变成单独的一个词汇,之后通过查找词汇表能够获取对应的词汇id。

分词器

分词就是将连续的字序列按照一定的规范划分成词序列的过程,合理的分词有助于语义理解。

MindSpore提供了多种不同用途的分词器,如BasicTokenizer、BertTokenizer、JiebaTokenizer等,能够帮助用户高性能地处理文本。用户可以构建自己的字典,使用适当的标记器将句子拆分为不同的标记,并通过查找操作获取字典中标记的索引。此外,用户也可以根据需要实现自定义的分词器。

下面介绍几种常用分词器的使用方法,更多分词器相关信息请参考API文档

BertTokenizer

BertTokenizer操作是通过调用BasicTokenizerWordpieceTokenizer来进行分词的。

下面的样例首先构建了一个文本数据集和字符串列表,然后通过BertTokenizer对数据集进行分词,并展示了分词前后的文本结果。

[5]:
import mindspore.dataset as ds
import mindspore.dataset.text as text

# 构造待分词数据
input_list = ["床前明月光", "疑是地上霜", "举头望明月", "低头思故乡", "I am making small mistakes during working hours",
              "😀嘿嘿😃哈哈😄大笑😁嘻嘻", "繁體字"]

# 加载文本数据集
dataset = ds.NumpySlicesDataset(input_list, column_names=["text"], shuffle=False)

print("------------------------before tokenization----------------------------")
for data in dataset.create_dict_iterator(output_numpy=True):
    print(text.to_str(data['text']))
------------------------before tokenization----------------------------
床前明月光
疑是地上霜
举头望明月
低头思故乡
I am making small mistakes during working hours
😀嘿嘿😃哈哈😄大笑😁嘻嘻
繁體字

上面为数据集未被分词前的数据打印情况,下面使用BertTokenizer分词器对数据集进行分词。

[6]:
# 构建词汇表
vocab_list = [
    "床", "前", "明", "月", "光", "疑", "是", "地", "上", "霜", "举", "头", "望", "低", "思", "故", "乡",
    "繁", "體", "字", "嘿", "哈", "大", "笑", "嘻", "i", "am", "mak", "make", "small", "mistake",
    "##s", "during", "work", "##ing", "hour", "😀", "😃", "😄", "😁", "+", "/", "-", "=", "12",
    "28", "40", "16", " ", "I", "[CLS]", "[SEP]", "[UNK]", "[PAD]", "[MASK]", "[unused1]", "[unused10]"]

# 加载词汇表
vocab = text.Vocab.from_list(vocab_list)

# 使用BertTokenizer分词器对文本数据集进行分词操作
tokenizer_op = text.BertTokenizer(vocab=vocab)
dataset = dataset.map(operations=tokenizer_op)

print("------------------------after tokenization-----------------------------")
for i in dataset.create_dict_iterator(num_epochs=1, output_numpy=True):
    print(text.to_str(i['text']))
------------------------after tokenization-----------------------------
['床' '前' '明' '月' '光']
['疑' '是' '地' '上' '霜']
['举' '头' '望' '明' '月']
['低' '头' '思' '故' '乡']
['I' 'am' 'mak' '##ing' 'small' 'mistake' '##s' 'during' 'work' '##ing'
 'hour' '##s']
['😀' '嘿' '嘿' '😃' '哈' '哈' '😄' '大' '笑' '😁' '嘻' '嘻']
['繁' '體' '字']

从上面两次的打印结果可以看出,数据集中的句子、词语和表情符号等都被BertTokenizer分词器以词汇表中的词汇为最小单元进行了分割,“故乡”被分割成了‘故’和‘乡’,“明月”被分割成了‘明’和‘月’。值得注意的是,“mistakes”被分割成了‘mistake’和‘##s’。

JiebaTokenizer

JiebaTokenizer操作是基于jieba的中文分词。

以下示例代码完成下载字典文件hmm_model.utf8jieba.dict.utf8,并将其放到指定位置。

[7]:
from mindvision.dataset import DownLoad

# 字典文件存放路径
dl_path = "./dictionary"

# 获取字典文件源
dl_url_hmm = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/hmm_model.utf8"
dl_url_jieba = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/jieba.dict.utf8"

# 下载字典文件
dl = DownLoad()
dl.download_url(url=dl_url_hmm, path=dl_path)
dl.download_url(url=dl_url_jieba, path=dl_path)

下载的文件放置的目录结构如下:

./dictionary/
├── hmm_model.utf8
└── jieba.dict.utf8

下面的样例首先构建了一个文本数据集,然后使用HMM与MP字典文件创建JiebaTokenizer对象,并对数据集进行分词,最后展示了分词前后的文本结果。

[8]:
import mindspore.dataset as ds
import mindspore.dataset.text as text

# 构造待分词数据
input_list = ["明天天气太好了我们一起去外面玩吧"]

# 加载数据集
dataset = ds.NumpySlicesDataset(input_list, column_names=["text"], shuffle=False)

print("------------------------before tokenization----------------------------")
for data in dataset.create_dict_iterator(output_numpy=True):
    print(text.to_str(data['text']))
------------------------before tokenization----------------------------
明天天气太好了我们一起去外面玩吧

上面为数据集未被分词前的数据打印情况,下面使用JiebaTokenizer分词器对数据集进行分词。

[9]:
HMM_FILE = "./dictionary/hmm_model.utf8"
MP_FILE = "./dictionary/jieba.dict.utf8"

# 使用JiebaTokenizer分词器对数据集进行分词
jieba_op = text.JiebaTokenizer(HMM_FILE, MP_FILE)
dataset = dataset.map(operations=jieba_op, input_columns=["text"], num_parallel_workers=1)

print("------------------------after tokenization-----------------------------")
for data in dataset.create_dict_iterator(num_epochs=1, output_numpy=True):
    print(text.to_str(data['text']))
------------------------after tokenization-----------------------------
['明天' '天气' '太好了' '我们' '一起' '去' '外面' '玩吧']

从上面两次打印结果来看,数据集中的句子被JiebaTokenizer分词器以词语为最小单元进行了划分。

SentencePieceTokenizer

SentencePieceTokenizer操作是基于开源自然语言处理工具包SentencePiece封装的分词器。

以下示例代码将下载文本数据集文件botchan.txt,并将其放置到指定位置。

[10]:
# 数据集存放位置
dl_path = "./datasets"

# 获取语料数据源
dl_url_botchan = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/botchan.txt"

# 下载语料数据
dl.download_url(url=dl_url_botchan, path=dl_path)

下载的文件放置的目录结构如下:

./datasets/
└── botchan.txt

下面的样例首先构建了一个文本数据集,然后从vocab_file文件中构建一个vocab对象,再通过SentencePieceTokenizer对数据集进行分词,并展示了分词前后的文本结果。

[11]:
import mindspore.dataset as ds
import mindspore.dataset.text as text
from mindspore.dataset.text import SentencePieceModel, SPieceTokenizerOutType

# 构造待分词数据
input_list = ["Nothing in the world is difficult for one who sets his mind on it."]

# 加载数据集
dataset = ds.NumpySlicesDataset(input_list, column_names=["text"], shuffle=False)

print("------------------------before tokenization----------------------------")
for data in dataset.create_dict_iterator(output_numpy=True):
    print(text.to_str(data['text']))
------------------------before tokenization----------------------------
Nothing in the world is difficult for one who sets his mind on it.

上面为数据集未被分词前的数据打印情况,下面使用SentencePieceTokenizer分词器对数据集进行分词。

[12]:
# 语料数据文件存放路径
vocab_file = "./datasets/botchan.txt"

# 从语料数据中学习构建词汇表
vocab = text.SentencePieceVocab.from_file([vocab_file], 5000, 0.9995, SentencePieceModel.WORD, {})

# 使用SentencePieceTokenizer分词器对数据集进行分词
tokenizer_op = text.SentencePieceTokenizer(vocab, out_type=SPieceTokenizerOutType.STRING)
dataset = dataset.map(operations=tokenizer_op)

print("------------------------after tokenization-----------------------------")
for i in dataset.create_dict_iterator(num_epochs=1, output_numpy=True):
    print(text.to_str(i['text']))
------------------------after tokenization-----------------------------
['▁Nothing' '▁in' '▁the' '▁world' '▁is' '▁difficult' '▁for' '▁one' '▁who'
 '▁sets' '▁his' '▁mind' '▁on' '▁it.']

从上面两次打印结果来看,数据集中的句子被SentencePieceTokenizer分词器以词语为最小单元进行了划分。在SentencePieceTokenizer分词器的处理过程中,空格作为普通符号处理,并使用下划线标记空格。

UnicodeCharTokenizer

UnicodeCharTokenizer操作是根据Unicode字符集来分词的。

下面的样例首先构建了一个文本数据集,然后通过UnicodeCharTokenizer对数据集进行分词,并展示了分词前后的文本结果。

[13]:
import mindspore.dataset as ds
import mindspore.dataset.text as text

# 构造待分词数据
input_list = ["Welcome to Beijing!", "北京欢迎您!", "我喜欢China!"]

# 加载数据集
dataset = ds.NumpySlicesDataset(input_list, column_names=["text"], shuffle=False)

print("------------------------before tokenization----------------------------")
for data in dataset.create_dict_iterator(output_numpy=True):
    print(text.to_str(data['text']))
------------------------before tokenization----------------------------
Welcome to Beijing!
北京欢迎您!
我喜欢China!

上面为数据集未被分词前的数据打印情况,下面使用UnicodeCharTokenizer分词器对数据集进行分词。

[14]:
# 使用UnicodeCharTokenizer分词器对数据集进行分词
tokenizer_op = text.UnicodeCharTokenizer()
dataset = dataset.map(operations=tokenizer_op)

print("------------------------after tokenization-----------------------------")
for data in dataset.create_dict_iterator(num_epochs=1, output_numpy=True):
    print(text.to_str(data['text']).tolist())
------------------------after tokenization-----------------------------
['W', 'e', 'l', 'c', 'o', 'm', 'e', ' ', 't', 'o', ' ', 'B', 'e', 'i', 'j', 'i', 'n', 'g', '!']
['北', '京', '欢', '迎', '您', '!']
['我', '喜', '欢', 'C', 'h', 'i', 'n', 'a', '!']

从上面两次打印结果可以看出,数据集中的句子被UnicodeCharTokenizer分词器进行分割,中文以单个汉字为最小单元,英文以单个字母为最小单元。

WhitespaceTokenizer

WhitespaceTokenizer操作是根据空格来进行分词的。

下面的样例首先构建了一个文本数据集,然后通过WhitespaceTokenizer对数据集进行分词,并展示了分词前后的文本结果。

[15]:
import mindspore.dataset as ds
import mindspore.dataset.text as text

# 构造待分词数据
input_list = ["Welcome to Beijing!", "北京欢迎您!", "我喜欢China!", "床前明月光,疑是地上霜。"]

# 加载数据集
dataset = ds.NumpySlicesDataset(input_list, column_names=["text"], shuffle=False)

print("------------------------before tokenization----------------------------")
for data in dataset.create_dict_iterator(output_numpy=True):
    print(text.to_str(data['text']))
------------------------before tokenization----------------------------
Welcome to Beijing!
北京欢迎您!
我喜欢China!
床前明月光,疑是地上霜。

上面为数据集未被分词前的数据打印情况,下面使用WhitespaceTokenizer分词器对数据集进行分词。

[16]:
# 使用WhitespaceTokenizer分词器对数据集进行分词
tokenizer_op = text.WhitespaceTokenizer()
dataset = dataset.map(operations=tokenizer_op)

print("------------------------after tokenization-----------------------------")
for i in dataset.create_dict_iterator(num_epochs=1, output_numpy=True):
    print(text.to_str(i['text']).tolist())
------------------------after tokenization-----------------------------
['Welcome', 'to', 'Beijing!']
['北京欢迎您!']
['我喜欢China!']
['床前明月光,疑是地上霜。']

从上面两次打印结果可以看出,数据集中的句子被WhitespaceTokenizer分词器以空格为分隔符进行分割。

WordpieceTokenizer

WordpieceTokenizer操作是基于单词集来进行划分的,划分依据可以是单词集中的单个单词,或者多个单词的组合形式。

下面的样例首先构建了一个文本数据集,然后从单词列表中构建vocab对象,通过WordpieceTokenizer对数据集进行分词,并展示了分词前后的文本结果。

[17]:
import mindspore.dataset as ds
import mindspore.dataset.text as text

# 构造待分词数据
input_list = ["My", "favorite", "book", "is", "love", "during", "the", "cholera", "era", ".", "what",
              "我", "最", "喜", "欢", "的", "书", "是", "霍", "乱", "时", "期", "的", "爱", "情", "。", "好"]

# 构造英文词汇表
vocab_english = ["book", "cholera", "era", "favor", "##ite", "My", "is", "love", "dur", "##ing", "the", "."]

# 构造中文词汇表
vocab_chinese = ['我', '最', '喜', '欢', '的', '书', '是', '霍', '乱', '时', '期', '爱', '情', '。']

# 加载数据集
dataset = ds.NumpySlicesDataset(input_list, column_names=["text"], shuffle=False)

print("------------------------before tokenization----------------------------")
for data in dataset.create_dict_iterator(output_numpy=True):
    print(text.to_str(data['text']))
------------------------before tokenization----------------------------
My
favorite
book
is
love
during
the
cholera
era
.
what
我
最
喜
欢
的
书
是
霍
乱
时
期
的
爱
情
。
好

上面为数据集未被分词前的数据打印情况,此处特意构造了词汇表中没有的单词“what”和“好”,下面使用WordpieceTokenizer分词器对数据集进行分词。

[18]:
# 使用WordpieceTokenizer分词器对数据集进行分词
vocab = text.Vocab.from_list(vocab_english+vocab_chinese)
tokenizer_op = text.WordpieceTokenizer(vocab=vocab)
dataset = dataset.map(operations=tokenizer_op)

print("------------------------after tokenization-----------------------------")
for i in dataset.create_dict_iterator(num_epochs=1, output_numpy=True):
    print(text.to_str(i['text']))
------------------------after tokenization-----------------------------
['My']
['favor' '##ite']
['book']
['is']
['love']
['dur' '##ing']
['the']
['cholera']
['era']
['.']
['[UNK]']
['我']
['最']
['喜']
['欢']
['的']
['书']
['是']
['霍']
['乱']
['时']
['期']
['的']
['爱']
['情']
['。']
['[UNK]']

从上面两次打印结果可以看出,数据集中的词语被WordpieceTokenizer分词器以构造的词汇表进行分词,“My”仍然被分为“My”,“love”仍然被分为“love”。值得注意的是,“favorite”被分为了“favor”和“##ite”,由于“word”和“好”在词汇表中未找到,所以使用[UNK]表示。