This function takes in a string and a fixed list of vocab:
- splits a string into words by spaces
- for each word, check in the dictionary/vocab and find the longest matching substrings, if none matching use the
[UNK]
word - if a word is longer than a predefined length, it'll also become
[UNK]
Code:
def tokenize(text, vocab, max_input_chars_per_word=10, unk_token="[UNK]"):
output_tokens = []
for token in text.split():
chars = list(token)
if len(chars) > max_input_chars_per_word:
output_tokens.append(unk_token)
continue
is_bad = False
start = 0
sub_tokens = []
while start < len(chars):
end = len(chars)
cur_substr = None
while start < end:
substr = "".join(chars[start:end])
if start > 0:
substr = "##" + substr
if substr in vocab:
cur_substr = substr
break
end -= 1
if cur_substr is None:
is_bad = True
break
sub_tokens.append(cur_substr)
start = end
if is_bad:
output_tokens.append(unk_token)
else:
output_tokens.extend(sub_tokens)
return output_tokens
And example input and expected output:
vocab = ["the", "go", "##es", "to", "eat", "pum", "##pkins", "of", "##gos", "##stein"
"#400", "1", "boredom", "##folk", "man", "##go", "out", "folks", "##0",
"un", "##aff", "##able"]
s = "the unaffable folks goes to eat 1400 folkspkinsgosgo pumpkins and 10 mangos out of boredom"
tokenize(s, vocab)
[out]:
['the',
'un',
'##aff',
'##able',
'folks',
'go',
'##es',
'to',
'eat',
'[UNK]',
'[UNK]',
'pum',
'##pkins',
'[UNK]',
'1',
'##0',
'man',
'##gos',
'out',
'of',
'boredom']
The nested while loop looks a complicated and checking each token isn't parallelized since it looks like it's independent of other tokens. How can this function be improved?
Simpler loops, or checking in parallel or maybe an easier to find longest matching substring from a list when iterating through the tokens? Or regexes?