0

準備された単語ベースの 3 グラムのファイルが 14 個あり、txt ファイルの合計サイズは 75 GB です。ngram は「;」で区切られます。単語列に続く単語は「|」で区切られます。ここで、単語が 3 つの単語シーケンスに続く頻度を数えたいと思います。データ量が多いため、できるだけ早く処理する必要があります。

私のアプローチは次のとおりです。

  1. ngram ごと、セパレーターごとに行を分割する;
  2. ngram をセパレーターで分割|
  3. ngram を 2 つのテーブルsequencesに格納し、単語がテーブルwords内のそのシーケンスに出現する頻度をカウントしますwords

SQL Server 2014 Express があり、テーブルの構造は次のとおりです。

  • [dbo].[sequences]:Id | Sequence
  • [dbo].[words]:Id | sid | word | count

シーケンス テーブルは明確である必要があります。words テーブルでsidは、the は関連するシーケンス ID、word は単語文字列、count は int 数であり、そのシーケンスの後に単語が出現する頻度をカウントします。

私の次のソリューションでは、最初は 1 行あたり約 1 秒必要ですが、これは非常に遅くなります。Parallel を使用しようとしましたが、別のプロセスが何かを挿入しているときにテーブルがロックされているため、SQL エラーが発生します。

私のプログラム:

    static void Main(string[] args)
    {
        DateTime begin = DateTime.Now;
        SqlConnection myConnection = new SqlConnection(@"Data Source=(localdb)\Projects;Database=ngrams;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False");
        myConnection.Open();
        for (int i = 0; i < 14; i++)
        {
            using (FileStream fs = File.Open(@"F:\Documents\ngrams\prepared_" + i + ".txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            using (BufferedStream bs = new BufferedStream(fs))
            using (StreamReader sr = new StreamReader(bs))
            {
                string line;
                int a = 0;
                while ((line = sr.ReadLine()) != null)
                {
                    string[] ngrams = line.Split(new char[] { ';' });
                    foreach (string ngram in ngrams)
                    {
                        string[] gram = ngram.Split(new Char[] { '|' });
                        if (gram.Length > 1)
                        {
                            string sequence = gram[0];
                            string word = gram[1];
                            storeNgrams(myConnection, sequence, word);
                        }
                    }
                    Console.WriteLine(DateTime.Now.Subtract(begin).TotalMinutes);
                    a++;
                }
            }
        }

        Console.WriteLine("Processed 75 Gigabyte in hours: " + DateTime.Now.Subtract(begin).TotalHours);
    }

    private static void storeNgrams(SqlConnection myConnection, string sequence, string word)
    {
        SqlCommand insSeq = new SqlCommand("INSERT INTO sequences (sequence) VALUES (@sequence); SELECT SCOPE_IDENTITY()", myConnection);
        SqlCommand insWord = new SqlCommand("INSERT INTO words (sid, word, count) VALUES (@sid, @word, @count)", myConnection);
        SqlCommand updateWordCount = new SqlCommand("UPDATE words SET count = @count WHERE sid = @sid AND word = @word", myConnection);
        SqlCommand searchSeq = new SqlCommand("SELECT Id from sequences WHERE sequence = @sequence", myConnection);
        SqlCommand getWordCount = new SqlCommand("Select count from words WHERE sid = @sid AND word = @word", myConnection);
        searchSeq.Parameters.AddWithValue("@sequence", sequence);
        object searchSeq_obj = searchSeq.ExecuteScalar();
        if (searchSeq_obj != null)
        {
            insNgram(insWord, updateWordCount, getWordCount, searchSeq_obj, word).ExecuteNonQuery();
        }
        else
        {
            insSeq.Parameters.AddWithValue("@sequence", sequence);
            object sid_obj = insSeq.ExecuteScalar();
            if (sid_obj != null)
            {
                insNgram(insWord, updateWordCount, getWordCount, sid_obj, word).ExecuteNonQuery();
            }
        }
    }

    private static SqlCommand insNgram(SqlCommand insWord, SqlCommand updateWordCount, SqlCommand getWordCount, object sid_obj, string word)
    {
        int sid = Convert.ToInt32(sid_obj);
        getWordCount.Parameters.AddWithValue("@sid", sid);
        getWordCount.Parameters.AddWithValue("@word", word);
        object wordCount_obj = getWordCount.ExecuteScalar();
        if (wordCount_obj != null)
        {
            int wordCount = Convert.ToInt32(wordCount_obj) + 1;
            return storeWord(updateWordCount, sid, word, wordCount);
        }
        else
        {
            int wordCount = 1;
            return storeWord(insWord, sid, word, wordCount);
        }
    }

    private static SqlCommand storeWord(SqlCommand updateWord, int sid, string word, int wordCount)
    {
        updateWord.Parameters.AddWithValue("@sid", sid);
        updateWord.Parameters.AddWithValue("@word", word);
        updateWord.Parameters.AddWithValue("@count", wordCount);
        return updateWord;
    }

法外な時間を必要としないように、ngram をより速く処理するにはどうすればよいですか?

PS: 私は C# と自然言語処理がまったく初めてです。

編集 1 :サンプル ngram の要求に応じて、各行に約 4 または 5 あります (ただし、もちろん単語の組み合わせは異なります):ほぼ同じ | お気に入り;

編集 2: コードを次のように変更すると、エラーSystem.AggregateException: At least one failure occured ---> System.InvalidOperationException: There is already an open DataReader associated with this Command that must be closed first. 、ここのように。

 Parallel.For(0, 14, i => sqlaction(myConnection, i, begin));

編集 3:接続文字列にMultipleActiveResultSets=trueを 追加すると、Parallel を使用してもエラーは発生しません。関連するすべてのループを同等の Parallel に置き換え、行番号 (169521628 行) を数えるだけですべてのファイルを実行し、1 行に必要な平均時間を計算しました。これは 0.051502946 秒です。それでも101日くらいはかかります!

4

0 に答える 0