1

ID(整数)|タイトル(テキスト)|テキスト(テキスト)|タグ(テキスト)の4つのフィールドを含む6M行のデータベース(sqlite)があります。

ここで、たとえばタイトルに表示される各単語の出現回数をカウントし、word|count や tag|word|count などの他のテーブルにインポートする必要があります。

私のコードは Python 2.7 では次のようになります。

from nltk.tokenize import wordpunct_tokenize
from collections import Counter
import sqlite3

word_count = Counter()
pair_count = Counter()

conn = sqlite3.connect('database')
c = conn.cursor()

for query in c.execute('SELECT Tags, Title FROM data'):
    tags = query[0].strip().split()
        title = wordpunct_tokenize(query[1])
        for word in title:
            word_count[word] += 1
            for tag in tags:
                pair_count[(tag, word)] += 1
...

問題は、カウンターが大きくなりすぎて、1M 行でメモリ エラーが発生したことです。100K 行ごとにカウンターを再初期化し、カウントを db ファイルに追加しようとしましたが、タグと単語のペアの数が膨大なため、このアプローチは非常に遅いようです。

...
for query in c.execute('SELECT Tags, Title FROM data'):
    i += 1
    if i % 100000 == 0:
        conn1 = sqlite3.connect('counts.db')
        c1 = conn1.cursor()

        # update word count
        for word in word_count:
            c1.execute('SELECT Count FROM word_count WHERE Word=?', (word,))
            count = c1.fetchone()
            # add to existing count and update
            if count:
                count = word_count[word] + count[0]
                c1.execute('UPDATE word_count SET Count=? WHERE Word=?', (count, word))
            # insert new row
            else:
                c1.execute('INSERT INTO title_word_count VALUES (?,?)', (word, word_count[word]))

        # update pair count                
        for pair in pair_count:
            c1.execute('SELECT Count FROM pair_count WHERE Tag=? AND Word=?', pair)
            count = c1.fetchone()
            if count:
                count = pair_count[pair] + count[0]
                c1.execute('UPDATE pair_count SET Count=? WHERE Tag=? AND Word=?', (count, pair[0], pair[1]))
            else:
                c1.execute('INSERT INTO pair_count VALUES (?,?,?)', (pair[0], pair[1], pair_count[pair]))
        conn1.commit()
        conn1.close()

        # reinitiate counters
        word_count = Counter()
        pair_count = Counter()
...

複数のマシンにアクセスせずにこの問題を解決する方法はありますか? また、コードに関する提案をいただければ幸いです。


編集:

インデックスを作成してcounts.db各バッチを更新しようとしましたが、それでも遅すぎます。それぞれ 200000 行の 7 つのバッチを処理するのに 10 時間かかりました。

私は最初のアイデアに従うことで終わりました。ただし、100K 行ごとにカウントを更新する代わりに、ペアsubcountsが重複している可能性があるにもかかわらず、それらをテーブルに挿入するだけです。Tag, Word

その後INSERT INTO pair_count SELECT Tag, Word, SUM(Count) FROM subcounts GROUP BY Tag, Word;、最終結果を教えてくれました。合計で約3時間かかりました。

@abernert の提案に従って取得した一時テーブルを誤って削除しましたが、実行可能だと思います。

アドバイスをくれた @Steve と @abernert に感謝します!

4

1 に答える 1

3

行が (タグ、単語) 順に並べられている場合、1 つのペア カウントのすべての更新を取得してから、次のペアのすべての更新を取得する、というように続きます。

残念ながら、データを適切に正規化していないため、それを取得できません。

最後の文の意味がわからない場合は、データベースの正規化について読む必要があります。ウィキペディアの第 3 正規形は、開始するのに適した場所のようです。

データ モデルを修正できない場合は、一時テーブルを作成して修正できます。

c.execute('DROP TABLE IF EXISTS _data')
c.execute('CREATE TABLE _data (Tag, Word)')
for query in c.execute('SELECT Tags, Title FROM data'):
    tags = query[0].strip().split()
    words = wordpunct_tokenize(query[1])
    c.executemany('INSERT INTO _data (Tag, Word) VALUES(?, ?)',
                  itertools.product(tags, words))
c.commit()

実際には両方の列を分割する必要はありません。どちらか大きい方だけです。しかし、本当にディスク容量を節約する必要がない限り、これははるかにクリーンです。

とにかく、どちらが大きいかに応じて、またはのORDER BYいずれかを実行できるようになりました。現在取り組んでいる値だけを保持する必要はありません。1 つの値のすべての行を取得し、次の値のすべての行を取得する、というように続きます。Tag, WordWord, Tagtag_count

つまり、 を使用すると、GROUP BYsqlite3 にカウントを任せることができます。

これは、そもそも Python で反復処理を行う必要がないことも意味します。sqlite3 にもそれをさせることができます:

c.execute('''INSERT INTO pair_count 
             SELECT Tag, Word, COUNT(*) FROM _data GROUP BY Tag, Word''')
于 2013-10-01T23:12:26.973 に答える