从源数据训练 Word2Vec 模型 - 问题标记化数据

问题描述

我最近从 Google Bigquery 中获取并整理了大量 reddit 数据。

数据集如下所示:

Data Preview

在将此数据传递给 word2vec 以创建词汇表并进行训练之前,我需要正确标记“body_cleaned”列。

我已经尝试使用手动创建的函数和 NLTK 的 word_tokenize 进行标记化,但现在我将专注于使用 word_tokenize。

因为我的数据集比较大,接近 1200 万行,我不可能一次性打开和执行数据集的功能。 Pandas 尝试将所有内容加载到 RAM 中,正如您所理解的,它会崩溃,即使在具有 24GB 内存的系统上也是如此。

我面临以下问题:

  • 当我对数据集进行分词时(使用 NTLK word_tokenize),如果我对整个数据集执行该功能,它会正确分词并且 word2vec 接受该输入并在其词汇表中正确学习/输出单词。
  • 当我通过首先对数据帧进行批处理并对其进行迭代来标记数据集时,生成标记列不是 word2vec 喜欢的;尽管 word2vec 用 4 个多小时收集的数据训练其模型,但它学习的最终词汇由多种编码的单个字符以及表情符号组成,而不是单词。

为了解决这个问题,我创建了一个很小的数据子集,并尝试以两种不同的方式对该数据执行标记化:

  • 知道我的计算机可以处理对数据集执行的操作,我只是做了:
reddit_subset = reddit_data[:50]

reddit_subset['tokens'] = reddit_subset['body_cleaned'].apply(lambda x: word_tokenize(x))

这会产生以下结果:

Tokenized Data Preview

这实际上适用于 word2vec 并生成可以使用的模型。到目前为止很棒。

由于我无法一次性对如此大的数据集进行操作,因此我必须在如何处理此数据集方面发挥创意。我的解决方案是使用 Panda 自己的 batchsize 参数对数据集进行批处理并在小迭代中对其进行处理。

我编写了以下函数来实现:

def reddit_data_cleaning(filepath,batchsize=20000):
    if batchsize:
        df = pd.read_csv(filepath,encoding='utf-8',error_bad_lines=False,chunksize=batchsize,iterator=True,lineterminator='\n')
    print("Beginning the data cleaning process!")
    start_time = time.time()
    flag = 1
    chunk_num = 1
    for chunk in df:
        chunk[u'tokens'] = chunk[u'body_cleaned'].apply(lambda x: word_tokenize(x))
        chunk_num += 1
    if flag == 1:
            chunk.dropna(how='any')
            chunk = chunk[chunk['body_cleaned'] != 'deleted']
            chunk = chunk[chunk['body_cleaned'] != 'removed']
            print("Beginning writing a new file")
            chunk.to_csv(str(filepath[:-4] + '_tokenized.csv'),mode='w+',index=None,header=True)
            flag = 0
        else:
            chunk.dropna(how='any')
            chunk = chunk[chunk['body_cleaned'] != 'deleted']
            chunk = chunk[chunk['body_cleaned'] != 'removed']
            print("Adding a chunk into an already existing file")
            chunk.to_csv(str(filepath[:-4] + '_tokenized.csv'),mode='a',header=None)
    end_time = time.time()
    print("Processing has been completed in: ",(end_time - start_time)," seconds.")

尽管这段代码让我能够真正地分块处理这个庞大的数据集并产生结果,否则我会因内存故障而崩溃,但我得到的结果不符合我的 word2vec 要求,让我感到非常困惑究其原因。

我用上面的函数对Data子集进行了同样的操作,比较了两个函数的结果差异,得到如下:

Comparison of Methods

所需的结果在 new_tokens 列上,对数据帧进行分块的函数生成“tokens”列结果。

有没有人能更明智地帮助我理解为什么相同的标记函数会产生完全不同的结果,具体取决于我对数据帧的迭代方式?

如果您通读整个问题并坚持完成,我很感激您!

解决方法

首先,超过一定大小的数据,尤其是在处理原始文本或标记化文本时,您可能不想使用 Pandas 数据框对于每个中期结果。

它们增加了不完全“Pythonic”的额外开销和复杂性。对于以下情况尤其如此:

  • Python list 对象,其中每个单词都是一个单独的字符串:一旦您将原始字符串标记为这种格式,例如将此类文本提供给 Gensim 的 Word2Vec 模型,尝试将它们放入Pandas 只会导致令人困惑的列表表示问题(就像您的列一样,其中相同的文本可能显示为 ['yessir','shit','is','real'] – 这是一个真正的 Python 列表文字 – 或 [yessir,shit,is,real] – 这可能是其他一些混乱如果任何令牌具有挑战性字符,则打破)。
  • 原始词向量(或更高版本的文本向量):与数据帧相比,这些在原始 Numpy 数组中更紧凑、更自然/更高效

所以,无论如何,如果 Pandas 有助于加载或其他非文本字段,请在那里使用它。但是然后对标记化文本和向量使用更基础的 Python 或 Numpy 数据类型 - 也许在您的 Dataframe 中使用某些字段(如唯一 ID)来关联两者。

特别是对于大型文本语料库,更典型的是摆脱 CSV 而是使用大型文本文件,每个换行分隔行一个文本,并且每一行都预先标记化,以便空格可以完全信任为标记- 分隔。

也就是说:即使您的初始文本数据具有更复杂的标点敏感标记化,或其他组合/更改/拆分其他标记的预处理,也请尝试只做一次(特别是如果它涉及昂贵的正则表达式),将结果写入一个简单的文本文件,然后符合简单的规则:每行读取一个文本,每行只用空格分开。

许多算法,例如 Gensim 的 Word2VecFastText,可以直接流式传输此类文件,也可以通过非常低开销的可迭代包装器流式传输此类文件 - 因此文本从不完全在内存中,仅根据需要重复读取多次训练迭代。

有关这种处理大量文本的有效方式的更多详细信息,请参阅此文章:https://rare-technologies.com/data-streaming-in-python-generators-iterators-iterables/

,

在听取了 gojomo 的建议后,我简化了读取 csv 文件和写入文本文件的方法。

我最初使用 Pandas 的方法对一个大约有 1200 万行的文件产生了一些非常糟糕的处理时间,并且由于 Pandas 在将数据写入文件之前如何将数据全部读入内存而导致内存问题。

我还意识到我之前的代码存在重大缺陷。 我正在打印一些输出(作为健全性检查),由于我经常打印输出,我溢出 Jupyter 并使笔记本崩溃,无法完成底层和最重要的任务。

我摆脱了这一点,使用 csv 模块简化读取并写入 txt 文件,并在不到 10 秒的时间内处理了约 1200 万行的 reddit 数据库。

也许不是最好的一段代码,但我正在争先恐后地解决一个问题,这个问题对我来说是几天的障碍(并没有意识到我的问题的一部分是我的健全性检查崩溃了 Jupyter 是一个更大的问题)沮丧)。

def generate_corpus_txt(csv_filepath,output_filepath):
    import csv
    import time
    start_time = time.time()
    with open(csv_filepath,encoding = 'utf-8') as csvfile:
        datareader = csv.reader(csvfile)
        count = 0
        header = next(csvfile)
        print(time.asctime(time.localtime())," ---- Beginning Processing")
        with open(output_filepath,'w+') as output:
            # Check file as empty
            if header != None:
                for row in datareader:
                        # Iterate over each row after the header in the csv
                        # row variable is a list that represents a row in csv
                    processed_row = str(' '.join(row)) + '\n'
                    output.write(processed_row)
                    count += 1
                    if count == 1000000:
                        print(time.asctime(time.localtime())," ---- Processed 1,000,000 Rows of data.")
                        count = 0
    print('Processing took:',int((time.time()-start_time)/60),' minutes')
    output.close()
    csvfile.close()