/// <summary> /// Standardowy konstruktor /// </summary> /// <param name="workingDirPath">Ścieżka do katalogu z plikiem zapisanej macierzy</param> public IxHalMatrixReader(string workingDirPath) { this.workingDirPath = Misc.provideEndBackslash(workingDirPath); reader = new BinaryReader(File.Open(this.workingDirPath + "halMatrix.dat", FileMode.Open, FileAccess.Read, FileShare.Read)); progress = new ProgressReport(reader.BaseStream.Length, 1); }
/// <summary> /// Resetuje obiekt, zaczynając odczyt pliku macierzy od początku /// </summary> public void reset() { reader.BaseStream.Position = 0; progress = new ProgressReport(reader.BaseStream.Length, 1); }
/// <summary> /// Standardowy konstruktor /// /// Odczytuje pierwszy wiersz. /// </summary> /// <param name="reader">Strumień binarny do odczytu macierzy z dysku</param> public MatrixMergerDisk(BinaryReader reader) { this.reader = reader; progressReport = new ProgressReport(reader.BaseStream.Length, 1); moveNext(); }
public IxSearchResult search(string searched) { /* Ztokenizuj fraz� */ tokenListing.reset(); tokenListing.addFromText(searched, false); uint[] tokensIds = tokenListing.getTokensIds(); Dictionary<uint, float> resultIds = new Dictionary<uint, float>(); foreach (uint tokenId in tokensIds) { /* Dla ka�dego tokenu frazy pobierz list� wyst�pie� */ Posting[] postings = invIndex.getPostingsByTokenId(tokenId); if (tokensIds.Length > 1) { /* Sta�y wsp�czynnik IDF dzielony przez wag� pierwszego dokumentu w zbiorze podwynik�w dla tego tokenu */ float IDFperMaxRank = (float)(Math.Log(documentsCount / postings.Length) + 1) / postings[0].rank; /* Scal wyniki dla tego tokenu z poprzednimi wynikami */ foreach (Posting posting in postings) if (!resultIds.ContainsKey(posting.documentId)) { resultIds[posting.documentId] = posting.rank * IDFperMaxRank; } else { resultIds[posting.documentId] += posting.rank * IDFperMaxRank; } } else { /* Nie ma poprzednich wynik�w - przepisz */ foreach (Posting posting in postings) resultIds[posting.documentId] = posting.rank; } } /* Zwracany wynik wyszukiwania */ IxSearchResult result = new IxSearchResult(); result.documents = new IxDocument[resultIds.Count]; /* Przepisz wynik ze s�ownika do tablicy, pierwsze sortowanie wg ID dokumentu: wi�ksze szanse na szybszy odczyt tytu��w */ IEnumerator<KeyValuePair<uint, float>> resultEnumerator = resultIds.OrderByDescending<KeyValuePair<uint, float>, float>(key => key.Key).GetEnumerator(); ProgressReport progress; if (IxSettings.consoleDebug) { Console.WriteLine("Rewriting result documents' ids and reading titles..."); progress = new ProgressReport(resultIds.Count, 5); } int index = 0; /* Przepisz wyniki, pobieraj�c nazwy dokument�w */ while (resultEnumerator.MoveNext()) { result.documents[index] = documentsIndex.getDocumentById(resultEnumerator.Current.Key); result.documents[index].Id = resultEnumerator.Current.Key; result.documents[index++].rank = resultEnumerator.Current.Value; if (IxSettings.consoleDebug) progress.progressOne(); } progress.done(); /* Teraz posortuj wg malej�cych ocen */ Array.Sort<IxDocument>( result.documents, delegate(IxDocument firstDocument, IxDocument nextDocument) { return nextDocument.rank.CompareTo(firstDocument.rank); } ); return result; }
/// <summary> /// Podaje kontekst dla wskazanego tokenu /// /// Jeśli kontekst znajduje się w pamięci podręcznej, zwraca go. W przeciwnym wypadku tworzona jest tymczasowa macierz HAL, /// na której będzie liczony kontekst tokenu. Kontekst jest obcinany do długości IxSettings.halNearestCutoff. /// </summary> /// <param name="tokenId">ID tokenu do podania kontekstu</param> /// <returns>Kontekst tokenu</returns> public KeyValuePair<uint, int>[] getNearestForTokenId(uint tokenId, int[] halWindow) { ProgressReport progress; string tokenWindowId = tokenId.ToString() + halWindow.Aggregate(halWindow[0].ToString(), (s, i) => s + " " + i.ToString()); // http://stackoverflow.com/questions/1822811/int-array-to-string/1823413#1823413 if (nearest.ContainsKey(tokenWindowId)) return nearest[tokenWindowId]; // TODO uwaga na tokeny zawierające liczby oraz na stopsłowa (nie powinny się znajdować, bo to co trafia do indeksu jest filtrowane) Matrix<uint> matrix = new Matrix<uint>(); /* Zaczynamy od analizy tokenów powiązanych z tym, czyli zawartych w dokumentach, gdzie * występuje token, dla którego liczymy */ Posting[] documents = invIndex.getPostingsByTokenId(tokenId); if (IxSettings.consoleDebug) { progress = new ProgressReport(documents.Length, 5); Console.WriteLine("Analiza HAL dokumentów zawierających token..."); } /* Nie analizować dwukrotnie tych samych dokumentów */ HashSet<uint> alreadyAnalyzedDocuments = new HashSet<uint>(); Semaphore semaphore = new Semaphore(IxSettings.halAnalyzerThreadsNum, IxSettings.halAnalyzerThreadsNum); /* Przeanalizuj wszystkie dokumenty, w których wystąpił token, dla którego liczymy */ foreach (Posting document in documents) { uint[] documentTokens; /* Pobierz tokeny dokumentu */ try { documentTokens = fwdIndex.getDocumentTokenList(document.documentId, IxSettings.halOnDemandMainDocumentsMinLength); } catch (ArgumentOutOfRangeException) { continue; } if (documentTokens.Length == 0) continue; semaphore.WaitOne(); /* Zleć analizę dokumentu */ ThreadPool.QueueUserWorkItem(new WaitCallback(analyzeDocument), new AnalyzerThreadData( documentTokens, semaphore, new Analyzer<uint>(matrix, halWindow), null )); alreadyAnalyzedDocuments.Add(document.documentId); if (IxSettings.consoleDebug) progress.progressOne(); } for (int i = 0; i < IxSettings.halAnalyzerThreadsNum; i++) semaphore.WaitOne(); semaphore.Release(IxSettings.halAnalyzerThreadsNum); if (IxSettings.consoleDebug) progress.done(); if (IxSettings.halOnDemandExtendedAnalysis) { /* Konieczne jeszcze będzie przeanalizowanie pozostałych dokumentów, w których wystąpiły * wszystkie tokeny, które wystąpiły w dokumentach, w których wystąpił ten, dla którego * liczymy. Zdanie złożone opisujące podobnie złożony proces... */ uint[] rowsIds = matrix.rows.Keys.ToArray<uint>(); uint[] cellsIds = matrix.getRow(tokenId).cells.Keys.ToArray<uint>(); if (IxSettings.consoleDebug) { progress = new ProgressReport(rowsIds.Length + cellsIds.Length, 20); Console.WriteLine("Obliczanie zbioru tokenów powiązanych do uwzględnienia w analizie..."); } /* Tokeny, których zawierające je dokumentów analiza także jest konieczna. Przy okazji stanowi to zbiór tokenów, * których analiza (uwzględnienie w analizie HAL) ma wpływ na wynik kontekstu tokenu, dla którego liczymy (ważna * jest jedynie zwiększona odległość, którą wprowadza, ale wiersz tokenu głównego nie zawiera komórki dla tego tokenu) */ HashSet<uint> tokensIdsToAnalyze = new HashSet<uint>(); /* Wylistuj tokeny z wierszy macierzy HAL */ foreach (uint rowsId in rowsIds) { tokensIdsToAnalyze.Add(rowsId); if (IxSettings.consoleDebug) progress.progressOne(); } Array.Resize<uint>(ref rowsIds, 0); // Zwolnij pamięć /* Wylistuj tokeny z komórek wiersza tokenu, dla którego liczymy kontekst */ foreach (uint cellsId in cellsIds) { if (!tokensIdsToAnalyze.Contains<uint>(cellsId)) tokensIdsToAnalyze.Add(cellsId); if (IxSettings.consoleDebug) progress.progressOne(); } Array.Resize<uint>(ref cellsIds, 0); // Zwolnij pamięć if (IxSettings.consoleDebug) { progress.done(); progress = new ProgressReport(tokensIdsToAnalyze.Count, 1); Console.WriteLine("Analiza dokumentów zawierających tokeny powiązane..."); } // analyzeDocument - performance, analyzeDocumentRelated - saves memory WaitCallback analysisCallback = IxSettings.halFastExtendedAnalysis ? new WaitCallback(analyzeDocument) : new WaitCallback(analyzeDocumentRelated); /* Pobieraj listę dokumentów dla każdego z tokenów i analizuj te, których jeszcze nie przeliczano */ foreach (uint tokenIdToAnalyze in tokensIdsToAnalyze) { if (tokenIdToAnalyze == tokenId) continue; /* Dokumenty, w których wystąpił token */ documents = invIndex.getPostingsByTokenId(tokenIdToAnalyze); foreach (Posting document in documents) { if (alreadyAnalyzedDocuments.Contains<uint>(document.documentId)) continue; // Ten dokument już analizowano uint[] documentTokens; /* Pobierz tokenu dokumentu */ try { documentTokens = fwdIndex.getDocumentTokenList(document.documentId, IxSettings.halOnDemandExtendedDocumentsMinLength); } catch (ArgumentOutOfRangeException) { continue; } if (documentTokens.Length == 0) continue; semaphore.WaitOne(); /* Zakolejkuj analizę dokumentu */ ThreadPool.QueueUserWorkItem( analysisCallback, new AnalyzerThreadData( documentTokens, semaphore, new Analyzer<uint>(matrix, halWindow), tokensIdsToAnalyze ) ); alreadyAnalyzedDocuments.Add(document.documentId); } if (IxSettings.consoleDebug) progress.progressOne(); } for (int i = 0; i < IxSettings.halAnalyzerThreadsNum; i++) semaphore.WaitOne(); if (IxSettings.consoleDebug) progress.done(); } /* Użyj kalkulatora kontekstu na macierzy HAL */ ContextComputer<uint> nearestComputer = new ContextComputer<uint>(matrix); KeyValuePair<uint, int>[] result = nearestComputer.getNearest(tokenId); /* Skróć wynik - podany pełen kontekst */ if (result.Length > IxSettings.halNearestCutoff) Array.Resize<KeyValuePair<uint, int>>(ref result, IxSettings.halNearestCutoff); nearest[tokenWindowId] = result; nearestChanged = true; return result; }
/// <summary> /// Tworzy kontekst dla wszystkich tokenów w macierzy i zapisuje go do pliku /// /// Kontekst jest zarządzalny przez IxHalContext. /// </summary> public void calculateContext() { ProgressReport progressReport; /* Posłuży do zapisywania wyników */ IxHalContext contextWriter = new IxHalContext(workingDirPath, IxHalContext.Mode.CREATE); /* Obiekt czytający dla tokenów, dla których będzie liczony kontekst */ IxHalMatrixReader matrixReader = new IxHalMatrixReader(workingDirPath); matrixReader.setProgressReportPrefix("!"); /* Obiekt czytający dla tokenów, od których liczone będą odległości do kontekstów */ IxHalMatrixReader matrixReaderSub = new IxHalMatrixReader(workingDirPath); /* Semafor maksymalnej liczby jednocześnie zakolejkowanych wątków ThreadPool */ Semaphore semaphore = new Semaphore(IxSettings.halAnalyzerThreadsNum, IxSettings.halAnalyzerThreadsNum); /* Seria wierszy tokenów, dla których aktualnie liczony jest kontekst */ KeyValuePair<uint, ArrayRow<uint>>[] calculatedForRows = matrixReader.readArrayRowsChunk(1000); while (calculatedForRows.Length != 0) { /* Kalkulatory kontekstów */ IxHalTokenContextCalculator[] calculators = IxHalTokenContextCalculator.create(semaphore, calculatedForRows); int calculatorsCount = calculators.Length; /* Porcja wierszy tokenów, od których liczone będą odległości do kontekstów */ KeyValuePair<uint, ArrayRow<uint>>[] calculatedAgainstRows = matrixReaderSub.readArrayRowsChunk(1000); while (calculatedAgainstRows.Length != 0) { progressReport = new ProgressReport(calculatorsCount, 10, "c"); /* Zleć uzupełnianie kontekstów kalkulatorom */ for (int i = 0; i < calculatorsCount; i++) { calculators[i].calculate(calculatedAgainstRows); progressReport.progressOne(); } /* Pobierz następną porcję tokenów, od których liczone są odległości do kontekstów */ calculatedAgainstRows = matrixReaderSub.readArrayRowsChunk(1000); progressReport.done(); } /* Obliczono już cały kontekst dla tej porcji calculatedForRows - zresetuj obiekt czytający */ matrixReaderSub.reset(); Misc.waitFullSemaphore(semaphore, IxSettings.halAnalyzerThreadsNum); progressReport = new ProgressReport(calculatorsCount, 10, "f"); /* Finalizuj obliczanie kontekstów w kalkulatorach */ for (int i = 0; i < calculatorsCount; i++) { calculators[i].computeFinalResult(); progressReport.progressOne(); } Misc.waitFullSemaphore(semaphore, IxSettings.halAnalyzerThreadsNum); progressReport.done(); progressReport = new ProgressReport(calculatorsCount, 10, "w"); /* Przekaż w kolejności wyniki do contextWriter */ for (int i = 0; i < calculatorsCount; i++) { calculators[i].writeDownResult(contextWriter); progressReport.progressOne(); } /* Pobierz kolejną porcję wierszy tokenów, dla których będzie liczony kontekst */ calculatedForRows = matrixReader.readArrayRowsChunk(100); progressReport.done(); } /* Zakończ pracę */ contextWriter.finalize(); }
/// <summary> /// Wyszukuje dokumenty wg zadanej frazy z użyciem funkcjonalności HAL, która może być sparametryzowana /// /// Można podać frazę prostą, wtedy wyszukiwanie następuje zgodnie z domyślnymi ustawieniami z IxSettings. /// Jeśli natomiast podana fraza będzie zawierała 11 parametrów, oddzielonych od siebie i szukanych tokenów /// znakami "/" (slash), zostaną użyte sparsowane parametry. Sparametryzowana fraza wyszukiwania musi mieć /// postać: <b>wyszukiwane tokeny/f/f/i/i/f/f/i[]/i/i/b/i</b>, gdzie: f - float, i - integer, i[] - kilka liczb /// integer oddzielonych spacją, b - boolean. Parametry to po kolei: straightMultiplier, relatedMultiplier, /// relatedSearchCount, relatedFoundMinCount, relFoundRateIfContainsStraight, relFoundRateIfNotContainsStraight, /// halWindow, skipFirstNRelated, maxUsedRelatedDistanceSum, relatedDistanceRelativeToFirst, speechPartFiltering /// (patrz dokumentacja funkcji getDocumentsIdsByTokenIdWithHAL()). /// </summary> /// <param name="searched"></param> /// <returns>Wynik wyszukiwania IxHALSearchResult</returns> public IxSearchResult search(string searched) { string[] parts = searched.Split(new char[] { '/' }); #region HAL search parameters float straightMultiplier = IxSettings.halSearchTokensRate; float relatedMultiplier = IxSettings.halSearchRelTokensRate; int relatedSearchCount = IxSettings.halSearchRelTokensCount; int relatedFoundMinCount = IxSettings.halSearchRelTokensReqCount; float relFoundRateIfContainsStraight = IxSettings.halSearchRelFoundRateIfContainsStraight; float relFoundRateIfNotContainsStraight = IxSettings.halSearchRelFoundRateIfNotContainsStraight; int[] halWindow = IxSettings.defaultHalWindow; int skipFirstNRelated = IxSettings.halSkipFirstNRelated; int maxUsedRelatedDistanceSum = IxSettings.halMaxRelatedDistanceSum; bool relatedDistanceRelativeToFirst = IxSettings.halRelatedDistanceRelativeFirst; HalSpeechPartFiltering speechPartFiltering = HalSpeechPartFiltering.NONE; if (parts.Length == 12) { try { straightMultiplier = float.Parse(parts[1].Trim()); relatedMultiplier = float.Parse(parts[2].Trim()); relatedSearchCount = int.Parse(parts[3].Trim()); relatedFoundMinCount = int.Parse(parts[4].Trim()); relFoundRateIfContainsStraight = float.Parse(parts[5].Trim()); relFoundRateIfNotContainsStraight = float.Parse(parts[6].Trim()); string[] halWindowParts = parts[7].Split(new char[] { ' ' }); halWindow = new int[halWindowParts.Length]; int halWindowIndex = 0; for (int i = 0; i < halWindowParts.Length; i++) { if (halWindowParts[i].Equals("")) continue; halWindow[halWindowIndex++] = int.Parse(halWindowParts[i]); } Array.Resize<int>(ref halWindow, halWindowIndex); skipFirstNRelated = int.Parse(parts[8].Trim()); maxUsedRelatedDistanceSum = int.Parse(parts[9].Trim()); relatedDistanceRelativeToFirst = bool.Parse(parts[10].Trim()); speechPartFiltering = (HalSpeechPartFiltering)int.Parse(parts[11]); } catch (FormatException) { return new IxHALSearchResult("Search error: improper parameters (phrase/f/f/i/i/f/f/HAL window/i/i/b/i)"); } } #endregion /* Wyłuskaj z wyszukiwanej frazy tokeny */ tokenListing.reset(); tokenListing.addFromText(parts[0], false); uint[] tokensIds = tokenListing.getTokensIds(); Dictionary<uint, float> resultIds = new Dictionary<uint, float>(); KeyValuePair<string, KeyValuePair<string, int>[]>[] halContexts = new KeyValuePair<string, KeyValuePair<string, int>[]>[tokensIds.Length]; KeyValuePair<string, int>[] halContextsSoughtCounts = new KeyValuePair<string, int>[tokensIds.Length]; uint halContextIndex = 0; /* Wyszukuj po kolei tokeny */ foreach (uint tokenId in tokensIds) { Dictionary<uint, float> subResultIds; int relatedSoughtCount; /* Pobierz wyniki dla pojedynczego tokenu z frazy, kontekst jest zapisywany bezpośrednio w halContexts[halContextIndex], * dokumenty i liczba wyszukanych tokenów kontekstu trzeba będzie zsumować/przepisać */ getDocumentsIdsByTokenIdWithHAL(tokenId, straightMultiplier, relatedMultiplier, relatedSearchCount, relatedFoundMinCount, relFoundRateIfContainsStraight, relFoundRateIfNotContainsStraight, halWindow, skipFirstNRelated, maxUsedRelatedDistanceSum, relatedDistanceRelativeToFirst, speechPartFiltering, out subResultIds, out halContexts[halContextIndex], out relatedSoughtCount); /* Zapisz liczbę wyszukwianych tokenów z kontekstu */ halContextsSoughtCounts[halContextIndex] = new KeyValuePair<string, int>(halContexts[halContextIndex].Key, relatedSoughtCount); /* Zsumuj wynik dokumentów z dotychczasowymi wynikami */ foreach (KeyValuePair<uint, float> oneResult in subResultIds) if (resultIds.ContainsKey(oneResult.Key)) resultIds[oneResult.Key] += oneResult.Value; else resultIds.Add(oneResult.Key, oneResult.Value); halContextIndex++; } /* Zwracany wynik wyszukiwania */ IxHALSearchResult result = new IxHALSearchResult(); result.documents = new IxDocument[resultIds.Count]; result.context = halContexts; result.contextSoughtCounts = halContextsSoughtCounts; /* Przepisz wynik ze słownika do tablicy, pierwsze sortowanie wg ID dokumentu: większe szanse na szybszy odczyt tytułów */ IEnumerator<KeyValuePair<uint, float>> resultEnumerator = resultIds.OrderByDescending<KeyValuePair<uint, float>, float>(key => key.Key).GetEnumerator(); ProgressReport progress; if (IxSettings.consoleDebug) { Console.WriteLine("Rewriting result documents' ids and reading titles..."); progress = new ProgressReport(resultIds.Count, 5); } int index = 0; /* Przepisz wyniki, pobierając nazwy dokumentów */ while (resultEnumerator.MoveNext()) { result.documents[index] = documentsIndex.getDocumentById(resultEnumerator.Current.Key); result.documents[index++].rank = resultEnumerator.Current.Value; if (IxSettings.consoleDebug) progress.progressOne(); } progress.done(); /* Teraz posortuj wg malejących ocen */ Array.Sort<IxDocument>( result.documents, delegate(IxDocument firstDocument, IxDocument nextDocument) { return nextDocument.rank.CompareTo(firstDocument.rank); } ); return result; }