在深度学习中,处理文本数据是一个非常重要的步骤。文本数据通常以字符串的形式存在,而计算机模型只能处理数字。因此,我们需要将文本转换为数字形式,以便模型能够理解和处理。本文将带你一步步了解如何对文本进行预处理,从读取原始文本到将其转换为数字索引。
1. 读取数据集
首先,我们需要从某个地方获取文本数据。这里我们以H.G. Wells的《时间机器》为例。这个数据集虽然不大,但足够我们用来学习和实验。
python
# d2l.py
DATA_HUB['time_machine'] = (
DATA_URL + 'timemachine.txt',
'090b5e7e70c295757f55df93cb0a180b9691891a'
)
def read_time_machine():
"""将时间机器数据集加载到文本行的列表中"""
with open(download('time_machine'), 'r') as f:
lines = f.readlines()
return [re.sub('[^A-Za-z]+', ' ', line).strip().lower() for line in lines]
lines = d2l.read_time_machine()
print(f"# 文本总行数:{len(lines)}")
print(lines[0])
print(lines[10])
在这段代码中,我们首先下载并读取了《时间机器》的文本数据。然后,我们对每一行文本进行了简单的处理:去除了标点符号,并将所有字母转换为小写。
输出:
csharp
# 文本总行数: 3221
the time machine by h g wells
twinkled and his usually pale face was flushed and animated the
2. 词元化
接下来,我们需要将文本拆分为更小的单位,这些单位可以是单词或字符。这个过程称为词元化(Tokenization)。
python
# d2l.py
def tokenize(lines, token='word'):
"""将文本行拆分为单词或字符词元"""
if token == 'word':
return [line.split() for line in lines]
elif token == 'char':
return [list(line) for line in lines]
else:
raise Exception(f'错误:未知词元类型:{token}')
tokens = d2l.tokenize(lines)
for i in range(11):
print(tokens[i])
在这段代码中,我们定义了一个tokenize
函数,它可以将文本行拆分为单词或字符。这里我们选择了按单词进行拆分。
输出:
css
['the', 'time', 'machine', 'by', 'h', 'g', 'wells']
[]
[]
[]
[]
['i']
[]
[]
['the', 'time', 'traveller', 'for', 'so', 'it', 'will', 'be', 'convenient', 'to', 'speak', 'of', 'him']
['was', 'expounding', 'a', 'recondite', 'matter', 'to', 'us', 'his', 'grey', 'eyes', 'shone', 'and']
['twinkled', 'and', 'his', 'usually', 'pale', 'face', 'was', 'flushed', 'and', 'animated', 'the']
3. 构建词表
词元化之后,我们得到了一个个单词或字符。然而,模型无法直接处理这些字符串,因此我们需要将它们转换为数字。为此,我们需要构建一个词表(Vocabulary),将每个词元映射到一个唯一的数字索引。
python
# d2l.py
import collections
class Vocab:
"""文本词表,用于将词元(token)映射为数字索引"""
def __init__(self, tokens=None, min_freq=0, reserved_tokens=None):
"""
初始化词表,构建词元到数字索引的映射
参数:
- tokens (list): 词元的列表,可以是文本数据中提取出的词元。每个词元可以是单词或字符,默认值为 `None`。
- min_freq (int): 最小词频,只有词频大于或等于该值的词元才会被加入词表,默认值为 `0`。
- reserved_tokens (list): 预留词元的列表,这些词元会优先添加到词表中(例如:`<unk>`、`<pad>`等)。默认值为 `None`。
"""
if tokens is None:
tokens = []
if reserved_tokens is None:
reserved_tokens = []
# 统计词元频率,并按频率降序排序
counter = count_corpus(tokens)
self._token_freqs = sorted(counter.items(), key=lambda x: x[1], reverse=True)
# 初始化词表,<unk>代表未知词元
self.idx_to_token = ['<unk>'] + reserved_tokens
self.token_to_idx = {idx: token for idx, token in enumerate(self.idx_to_token)}
# 根据频率添加词元,忽略低频词
for token, freq in self._token_freqs:
if freq < min_freq:
break
if token not in self.token_to_idx:
self.idx_to_token.append(token)
self.token_to_idx[token] = len(self.idx_to_token) - 1
def __len__(self):
"""返回词表大小"""
return len(self.idx_to_token)
def __getitem__(self, tokens):
"""
根据词元返回对应的索引,支持列表输入
参数:
- tokens (str, list, tuple): 输入一个词元(字符串)或者词元列表(字符串列表)。
如果是单个词元,返回该词元的索引;如果是列表或元组,返回每个词元的索引列表。
返回:
- (int, list): 如果输入是单个词元,返回对应的索引;如果是词元列表,返回一个索引列表。
"""
if not isinstance(tokens, (tuple, list)):
return self.token_to_idx.get(tokens, self.unk)
return [self.__getitem__(token) for token in tokens]
@property
def unk(self):
"""返回未知词元的索引(0)"""
return 0
@property
def token_freqs(self):
"""返回词元频率列表"""
return self._token_freqs
def count_corpus(tokens):
"""
统计词元频率,支持1D和2D列表
参数:
- tokens (list, list of lists): 输入词元列表,可以是一个一维的词元列表,或者是多个文本行的词元列表(二维列表)。
返回:
- (dict): 返回一个字典,键为词元,值为该词元在语料库中出现的频率。
"""
if len(tokens) == 0 or isinstance(tokens[0], list):
tokens = [token for line in tokens for token in line]
return collections.Counter(tokens)
# 构建词表
vocad = d2l.Vocab(tokens)
print(list(vocad.token_to_idx.items())[:10])
在这段代码中,我们定义了一个Vocab
类来构建词表。词表将每个词元映射到一个唯一的索引,并且我们还统计了每个词元的出现频率。
输出:
css
[(0, '<unk>'), ('the', 1), ('i', 2), ('and', 3), ('of', 4), ('a', 5), ('to', 6), ('was', 7), ('in', 8), ('that', 9)]
4. 将文本转换为数字索引
有了词表之后,我们就可以将文本中的每个词元转换为对应的数字索引了。
python
for i in [0, 10]:
print("文本:", tokens[i])
print("索引:", vocad[tokens[i]])
在这段代码中,我们将文本行中的每个词元转换为了对应的数字索引。这样,文本数据就变成了模型可以处理的数字序列。
输出:
less
文本: ['the', 'time', 'machine', 'by', 'h', 'g', 'wells']
索引: [1, 19, 50, 40, 2183, 2184, 400]
文本: ['twinkled', 'and', 'his', 'usually', 'pale', 'face', 'was', 'flushed', 'and', 'animated', 'the']
索引: [2186, 3, 25, 1044, 362, 113, 7, 1421, 3, 1045, 1]
5. 整合所有功能
为了方便后续的使用,我们可以将上述所有步骤整合到一个函数中。
python
# d2l.py
def load_corpus_time_machine(max_tokens=-1):
"""
返回时光机器数据集的词元索引列表和词表
参数:
- max_tokens (int): 限制返回的词元数量。如果为负值,表示不限制数量,返回全部数据。默认值为 -1。
返回:
- corpus (list): 词元索引的列表,表示文本中的每个字符(词元)的索引。
- vocab (Vocab): 词表对象,用于映射词元和索引之间的关系。
"""
lines = read_time_machine()
tokens = tokenize(lines, 'char') # 使用字符级别的词元化
vocab = Vocab(tokens) # 构建词表
# 将所有文本行展平为一个词元索引的列表
corpus = [vocab[token] for line in tokens for token in line]
# 如果限制了词元数量,则截取前 max_tokens 个词元
if max_tokens > 0:
corpus = corpus[:max_tokens]
return corpus, vocab
corpus, vocab = d2l.load_corpus_time_machine()
print(len(corpus), len(vocab))
在这段代码中,我们将读取数据 、词元化 、构建词表 以及转换为数字索引的步骤整合到了一个函数中。最终,我们得到了一个包含所有词元索引的列表corpus
,以及对应的词表vocab
。
输出:
170580 28
6. 小结
通过本文,我们学习了如何对文本数据进行预处理。具体来说,我们完成了以下几个步骤:
- 读取数据集:从文件中加载文本数据。
- 词元化:将文本拆分为单词或字符。
- 构建词表:将词元映射到数字索引。
- 转换为数字索引:将文本数据转换为模型可以处理的数字序列。
这些步骤是处理文本数据的基础,后续我们可以将这些数字序列输入到模型中进行训练和预测。