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

Training Word2Vec Model from sourced data - Issue Tokenizing data

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

数据集如下所示:

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

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

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

我面临以下问题:

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

reddit_subset = reddit_data[:50]

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

这会产生以下结果:

这实际上适用于 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', index=None, header=None)
    end_time = time.time()
    print("Processing has been completed in: ", (end_time - start_time), " seconds.")

虽然这段代码允许我实际分块处理这个巨大的数据集并产生结果,否则我会因内存故障而崩溃,但我得到的结果不符合我的 word2vec 要求,让我离开对它的原因感到非常困惑。

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

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

有没有人更聪明地帮助我理解为什么相同的标记化函数会根据我对数据帧的迭代方式产生完全不同的结果?

如果您通读整期并坚持到底,我将不胜感激!

首先也是最重要的是,超过一定大小的数据,尤其是在处理原始文本或标记化文本时,您可能不会 想为每个中间结果使用 Pandas 数据帧。

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

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

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

特别是对于大型文本语料库,更典型的做法是放弃 CSV,而是使用大型文本文件,每行一个文本,以换行符分隔,每一行都经过预标记,以便可以完全信任空格作为标记分隔。

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

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

有关这种处理大量文本的有效方法的更多详细信息,请参阅此文章: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()