私は C# に比較的慣れていないので、発生する OOM エラーに完全に困惑しています。スパース行列を作成しようとしているため、(行インデックス、列インデックス、値) のトリプレットを収集しています。forループを通過している間、最終的にプロセスによって使用される実際の物理メモリ(リソースマネージャーによると、Windowsが「ワーキングセット」と呼んでいるもの)は約3.5GBに比較的固定されたままです。ただし、コミット (仮想メモリであると思われます) は、コミットの制限に達するまで増加し続け、プログラムが OOM エラーでクラッシュします。
関連するコードは次のとおりです。
public SimMatrix(string sparseMethod, string simMethod, Phrases phrases, DistrFeature features, int topK) {
List<int> rows = new List<int>(phrases.uniquePhraseCount*topK);
List<int> cols = new List<int>(phrases.uniquePhraseCount*topK);
List<double> vals = new List<double>(phrases.uniquePhraseCount*topK);
if (sparseMethod.Equals("invIdx")) {
List<int> nonzeros = new List<int>(features.inverted_idx.Count());
List<int> neighbors = new List<int>(phrases.uniquePhraseCount);
List<double> simVals = new List<double>(phrases.uniquePhraseCount);
List<int> sortedIdx = new List<int>(phrases.uniquePhraseCount);
List<double> sortedSim = new List<double>(phrases.uniquePhraseCount);
for (int i = 0; i < phrases.uniquePhraseCount; i++) { //loop through all phrases
using (SparseDoubleArray row = phrases.feature_values.GetRowSparse(i))
{
if (phrases.feature_values.RowLength(i) > 0) { //i.e., at least one feature fired for phrase
nonzeros = (from pmi in row.Elements select pmi.IndexList[1]).ToList();
neighbors = generateNeighbors(nonzeros, features.inverted_idx);
foreach (int neighbor in neighbors)
simVals.Add(cosineSimilarity(row, phrases.feature_values.GetRowSparse(neighbor)));
var sortedIdxSim = neighbors.Zip(simVals, (a, b) => new { idx = a, sim = b }).OrderByDescending(pair => pair.sim);
sortedIdx = sortedIdxSim.Select(pair => pair.idx).ToList();
sortedSim = sortedIdxSim.Select(pair => pair.sim).ToList();
int topN = (sortedIdxSim.Count() < topK) ? sortedIdxSim.Count() : topK;
rows.AddRange(Enumerable.Repeat(i, topN).ToList());
cols.AddRange(sortedIdx.Take(topN).ToList());
vals.AddRange(sortedSim.Take(topN).ToList());
nonzeros.Clear();
neighbors.Clear();
simVals.Clear();
sortedIdx.Clear();
sortedSim.Clear();
}
else { //just add self similarity
rows.Add(i);
cols.Add(i);
vals.Add(1);
}
Console.WriteLine("{0} phrases done", i + 1);
}
}
}
else { Console.WriteLine("Sorry, no other sparsification method implemented thus far"); }
simMat = new SparseDoubleArray(phrases.uniquePhraseCount, phrases.uniquePhraseCount, rows, cols, vals);
}
static private List<int> generateNeighbors(List<int> idx, Dictionary<int, List<int>> inverted_idx) {
List<int> neighbors = new List<int>();
foreach (int feature in idx) {
neighbors.AddRange(inverted_idx[feature]);
neighbors = neighbors.Distinct().ToList();
}
return neighbors;
}
static public double cosineSimilarity(SparseDoubleArray profile1, SparseDoubleArray profile2) {
double numerator = profile1.Dot(profile2);
double norm1 = profile1.Norm();
double norm2 = profile2.Norm();
double cos_sim = numerator / (norm1 * norm2);
if (cos_sim > 0)
return cos_sim;
else
return 0;
}
コードはいくつかの内部ライブラリ (SparseDoubleArray オブジェクトなど) を使用していることに注意してください。基本的な要点は、すべてのエントリ (i でインデックス付け) をループし、エントリごとにゼロ以外の列インデックスを見つけ、そこから「generateNeighbors」関数を使用して潜在的な近隣のリストを生成することです。候補となる近傍のリストを取得したら、候補となる近傍のそれぞれについて余弦類似度を計算します。次に、インデックスと類似度の値を同時に並べ替え、上位 N 個のインデックス/類似度の値を選択し、それらをインデックス i (行インデックスに対応) と共に、疎行列のインデックスと値を維持するリストに追加します。
for ループの実行中に、コードが非決定論的に壊れているように見えます。i = 25,000 で破綻することもあれば、i = 2000 で破綻することもあります。スパース行列を初期化する段階にさえ到達しません。
洞察や助けをいただければ幸いです。
更新 (2013 年 6 月 10 日)
提供された応答のおかげで、コードのコミットされたメモリを大幅に削減することができました。以下は更新されたコードです。質問への回答とまったく同じではないことに気付くでしょう。変更が必要だった点を詳しく説明します。
public SimMatrix(string sparseMethod, string simMethod, Phrases phrases, DistrFeature features, int topK) {
List<int> rows = new List<int>(phrases.uniquePhraseCount*topK);
List<int> cols = new List<int>(phrases.uniquePhraseCount*topK);
List<double> vals = new List<double>(phrases.uniquePhraseCount*topK);
if (sparseMethod.Equals("invIdx")) {
for (int i = 0; i < phrases.uniquePhraseCount; i++) { //loop through all phrases
using (SparseDoubleArray row = phrases.feature_values.GetRowSparse(i))
{
if (phrases.feature_values.RowLength(i) > 0) { //i.e., at least one feature fired for phrase
IEnumerable<int> nonzeros = from pmi in row.Elements select pmi.IndexList[1];
IEnumerable<int> neighbors = nonzeros.SelectMany(x => features.inverted_idx[x]).Distinct();
IEnumerable<double> simVals = neighbors.Select(x => cosineSimilarity(row, x, phrases));
var sortedIdxSim = neighbors.Zip(simVals, (a, b) => new { idx = a, sim = b }).OrderByDescending(pair => pair.sim).ToList();
//IEnumerable<int> sortedIdx = sortedIdxSim.Select(pair => pair.idx);
//IEnumerable<double> sortedSim = sortedIdxSim.Select(pair => pair.sim);
int sortedIdxSimCount = sortedIdxSim.Count;
int topN = (sortedIdxSimCount < topK) ? sortedIdxSimCount : topK;
rows.AddRange(Enumerable.Repeat(i, topN));
cols.AddRange(sortedIdxSim.Take(topN).Select(pair => pair.idx));
vals.AddRange(sortedIdxSim.Take(topN).Select(pair => pair.sim));
}
else { //just add self similarity
rows.Add(i);
cols.Add(i);
vals.Add(1);
}
if ((i % 1000) == 0)
Console.WriteLine("{0} phrases done;", i + 1);
}
}
}
else { Console.WriteLine("Sorry, no other sparsification method implemented thus far"); }
simMat = new SparseDoubleArray(phrases.uniquePhraseCount, phrases.uniquePhraseCount, rows, cols, vals);
}
static public double cosineSimilarity(SparseDoubleArray profile1, int profile2idx, Phrases phrases) {
using (SparseDoubleArray profile2 = phrases.feature_values.GetRowSparse(profile2idx)) {
double numerator = profile1.Dot(profile2);
double norm1 = profile1.Norm();
double norm2 = profile2.Norm();
double cos_sim = numerator / (norm1 * norm2);
if (cos_sim > 0)
return cos_sim;
else
return 0;
}
}
var sortedIdxSim
まず、 IEnumerable から Listに変換する必要がありました。これは、私が a) このリスト内の要素の数を知る必要があり.Count()
、IEnumerable を呼び出すと IEnumerable に保持されているデータが消去されるように思われるためです。.Take()
また、IEnumerable<int> sortedIdx
(Gjeltema による最初の提案によると) を呼び出すと、 のデータが消去されるようにも見えIEnumerable<double> sortedSim
ます。これは遅延実行によるものですか?私は遅延評価/遅延実行にあまり慣れていないので、ここで何をする必要があるかを誤解しているかもしれません。
しかし、正直なところ、ここでの現在の変更により、プログラムが実際に最後まで実行できるように、コミットされたメモリが大幅に削減されました。誰かが私のために上記の問題を明確にするのを手伝ってくれるなら、それは素晴らしいことです.