缩写替换
使用维基百科中的缩略词列表,我们遍历句子并用它们的实际单词替换这些缩略词(这需要在标记化之前发生,因为一个标记被分成两部分)。这有助于以后句子结构的改进。该列表可在此处下载。
def normalize_contractions(sentence_list): contraction_list = json.loads(open('english_contractions.json', 'r').read()) norm_sents = [] print("Normalizing contractions") for sentence in tqdm(sentence_list): norm_sents.append(_normalize_contractions_text(sentence, contraction_list)) return norm_sents def _normalize_contractions_text(text, contractions): """ This function normalizes english contractions. """ new_token_list = [] token_list = text.split() for word_pos in range(len(token_list)): word = token_list[word_pos] first_upper = False if word[0].isupper(): first_upper = True if word.lower() in contractions: replacement = contractions[word.lower()] if first_upper: replacement = replacement[0].upper()+replacement[1:] replacement_tokens = replacement.split() if len(replacement_tokens)>1: new_token_list.append(replacement_tokens[0]) new_token_list.append(replacement_tokens[1]) else: new_token_list.append(replacement_tokens[0]) else: new_token_list.append(word) sentence = " ".join(new_token_list).strip(" ") return sentence
拼写矫正
现在,这是一个棘手的问题。它可能会引起一些不需要的更改(大多数可纠正拼写的词典缺少重要的上下文单词,因此他们将它们视为拼写错误)。因此,您必须有意识地使用它。有很多方法可以做到这一点。我选择使用名为symspellpy的模块,该模块的速度非常快(这很重要!),并且可以很好地完成这项工作。做到这一点的另一种方法是,训练一个深度学习模型来基于上下文进行拼写校正,但这完全是另一回事了。
def spell_correction(sentence_list): max_edit_distance_dictionary= 3 prefix_length = 4 spellchecker = SymSpell(max_edit_distance_dictionary, prefix_length) dictionary_path = pkg_resources.resource_filename( "symspellpy", "frequency_dictionary_en_82_765.txt") bigram_path = pkg_resources.resource_filename( "symspellpy", "frequency_bigramdictionary_en_243_342.txt") spellchecker.load_dictionary(dictionary_path, term_index=0, count_index=1) spellchecker.load_bigram_dictionary(dictionary_path, term_index=0, count_index=2) norm_sents = [] print("Spell correcting") for sentence in tqdm(sentence_list): norm_sents.append(_spell_correction_text(sentence, spellchecker)) return norm_sents def _spell_correction_text(text, spellchecker): """ This function does very simple spell correction normalization using pyspellchecker module. It works over a tokenized sentence and only the token representations are changed. """ if len(text) < 1: return "" #Spell checker config max_edit_distance_lookup = 2 suggestion_verbosity = Verbosity.TOP # TOP, CLOSEST, ALL #End of Spell checker config token_list = text.split() for word_pos in range(len(token_list)): word = token_list[word_pos] if word is None: token_list[word_pos] = "" continue if not '\n' in word and word not in string.punctuation and not is_numeric(word) and not (word.lower() in spellchecker.words.keys()): suggestions = spellchecker.lookup(word.lower(), suggestion_verbosity, max_edit_distance_lookup) #Checks first uppercase to conserve the case. upperfirst = word[0].isupper() #Checks for correction suggestions. if len(suggestions) > 0: correction = suggestions[0].term replacement = correction #We call our _reduce_exaggerations function if no suggestion is found. Maybe there are repeated chars. else: replacement = _reduce_exaggerations(word) #Takes the case back to the word. if upperfirst: replacement = replacement[0].upper()+replacement[1:] word = replacement token_list[word_pos] = word return " ".join(token_list).strip() def _reduce_exaggerations(text): """ Auxiliary function to help with exxagerated words. Examples: woooooords -> words yaaaaaaaaaaaaaaay -> yay """ correction = str(text) #TODO work on complexity reduction. return re.sub(r'([\w])\1+', r'\1', correction) def is_numeric(text): for char in text: if not (char in "0123456789" or char in ",%.$"): return False return True
合理化
如果您一直关注我的系列文章,那么您已经知道我已经实现了自己的lemmatizer。但是,为了简单起见,我选择在这里使用传统方法。它快速而直接,但是您可以使用任何其他所需的工具。我还决定删除(替换)所有标签。对于情感分析,我们并不是真的需要它们。
def lemmatize(sentence_list): nlp = spacy.load('en') new_norm=[] print("Lemmatizing Sentences") for sentence in tqdm(sentence_list): new_norm.append(_lemmatize_text(sentence, nlp).strip()) return new_norm def _lemmatize_text(sentence, nlp): sent = "" doc = nlp(sentence) for token in doc: if '@' in token.text: sent+=" @MENTION" elif '#' in token.text: sent+= " #HASHTAG" else: sent+=" "+token.lemma_ return sent
最后,我们将所有步骤加入“pipeline”函数中:
def normalization_pipeline(sentences): print("##############################") print("Starting Normalization Process") sentences = simplify_punctuation_and_whitespace(sentences) sentences = normalize_contractions(sentences) sentences = spell_correction(sentences) sentences = lemmatize(sentences) print("Normalization Process Finished") print("##############################") return sentences
在Google Colab Notebook中运行函数
结果
您可能想知道:应用这些任务的结果是什么?我已经运行了一些计数功能并绘制了一些图表来帮助解释,但我必须清楚一件事:数字表示不是表达文本归一化重要性的最佳方法。
相反,当将文本规范化应用于NLP应用程序时,它可以通过提高效率,准确性和其他相关分数来发挥最佳作用。我将指出一些可以从统计数据中清楚看到的好处。
首先,我们可以清楚地看到不同令牌总数的减少。在这种情况下,我们将令牌数量减少了约32%。
将归一化应用于我们的数据后,我们将令牌数量减少了约32%。
Distinct words in unnormalized: 15233–80% of the text correspond to 4053 distinct words. Distinct words in normalized: 10437–80% of the text correspond to 1251 distinct words.
现在,通用令牌的数量出现了更大的差异。这些令牌包括了所有数据的大约80%。通常,我们通过大约10–20%的令牌范围构成了文本的80%。
通过应用归一化,我们将最常见的令牌数量减少了69%!非常多!这也意味着我们对此数据的任何机器学习技术都将能够更好地推广。
归一化后,最常见的令牌数量减少了69%。
现在,关于文本归一化的一件重要的事是,为了使文本规范化有用,它必须保留默认的自然语言结构。我们可以通过数据本身看到这一点。一个例子是,如果做得好,归一化后的句子将不会变得越来越小。
在下面的直方图中显示了这一点,它表明,尽管归一化后我们的1尺寸句子较少,而2尺寸句子较多,但其余分布遵循未归一化数据的结构(请注意,我们的曲线稍微接近正态分布曲线)。
归一化对整体句子结构影响不大。
另一个有助于我们可视化的工具是Boxplot。它显示了我们的数据如何分布,包括均值,四分位数和离群值。总而言之,我们希望我们的中线与未规范化数据的中线相同(或接近)。我们还希望框(大多数数据的分布)保持在相似的位置。如果我们能够增加数据量的大小,这意味着我们在中位数周围的数据比归一化之前要多(这很好)。此外,我们要减少离群值。
归一化之后,我们能够增加四分位间距(大多数标记所在的位置)。我们还保持相同的中线并减少了异常值。这意味着我们没有破坏我们的文本,但是使它变得不那么复杂)。
结论
我希望在本文中能够解释什么是文本归一化,为什么要这样做以及如何做。
这是几个链接和一个用于进一步研究的文档: