using BotSharp.NLP.Tokenize; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.Serialization.Formatters.Binary; using System.Text; using System.Text.RegularExpressions; namespace BotSharp.NLP.Models.TF_IDF { /// /// Copyright (c) 2018 Bo Peng /// /// Permission is hereby granted, free of charge, to any person obtaining /// a copy of this software and associated documentation files (the /// "Software"), to deal in the Software without restriction, including /// without limitation the rights to use, copy, modify, merge, publish, /// distribute, sublicense, and/or sell copies of the Software, and to /// permit persons to whom the Software is furnished to do so, subject to /// the following conditions: /// /// The above copyright notice and this permission notice shall be /// included in all copies or substantial portions of the Software. /// public class TFIDF { List vocabulary { get; set; } public TFIDF() { } /// /// Document vocabulary, containing each word's IDF value. /// private static Dictionary _vocabularyIDF = new Dictionary(); public static List> GetTFIDFWeightsVectors(string[] documents, int vocabularyThreshold = 1) { List> stemmedDocs; List vocabulary; // Get the vocabulary and stem the documents at the same time. vocabulary = GetVocabulary(documents, out stemmedDocs, vocabularyThreshold); if (_vocabularyIDF.Count == 0) { // Calculate the IDF for each vocabulary term. foreach (var term in vocabulary) { double numberOfDocsContainingTerm = stemmedDocs.Where(d => d.Contains(term)).Count(); _vocabularyIDF[term] = Math.Log((double)stemmedDocs.Count / ((double)1 + numberOfDocsContainingTerm)); } } // Transform each document into a vector of tfidf values. List> vectors = new List>(); foreach (var doc in stemmedDocs) { List vector = new List(); foreach (string word in doc) { double tf = doc.Where(d => d == word).Count(); double tfidf = tf * _vocabularyIDF[word]; vector.Add(tfidf); } vectors.Add(vector); } return vectors; } /// /// Normalizes a TF*IDF array of vectors using L2-Norm. /// Xi = Xi / Sqrt(X0^2 + X1^2 + .. + Xn^2) /// /// List> /// List> public static List> Normalize(List> vectors) { // Normalize the vectors using L2-Norm. List> normalizedVectors = new List>(); foreach (var vector in vectors) { var normalized = Normalize(vector); normalizedVectors.Add(normalized); } return normalizedVectors; } /// /// Normalizes a TF*IDF vector using L2-Norm. /// Xi = Xi / Sqrt(X0^2 + X1^2 + .. + Xn^2) /// /// List /// List public static List Normalize(List vector) { List result = new List(); double sumSquared = 0; foreach (var value in vector) { sumSquared += value * value; } double SqrtSumSquared = Math.Sqrt(sumSquared); foreach (var value in vector) { // L2-norm: Xi = Xi / Sqrt(X0^2 + X1^2 + .. + Xn^2) result.Add(value / SqrtSumSquared); } return result; } /// /// Saves the TFIDF vocabulary to disk. /// /// File path public static void Save(string filePath = "vocabulary.dat") { // Save result to disk. using (FileStream fs = new FileStream(filePath, FileMode.Create)) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(fs, _vocabularyIDF); } } /// /// Loads the TFIDF vocabulary from disk. /// /// File path public static void Load(string filePath = "vocabulary.dat") { // Load from disk. using (FileStream fs = new FileStream(filePath, FileMode.Open)) { BinaryFormatter formatter = new BinaryFormatter(); _vocabularyIDF = (Dictionary)formatter.Deserialize(fs); } } /// /// Parses and tokenizes a list of documents, returning a vocabulary of words. /// /// string[] /// List of List of string /// Vocabulary (list of strings) private static List GetVocabulary(string[] docs, out List> stemmedDocs, int vocabularyThreshold) { List vocabulary = new List(); Dictionary wordCountList = new Dictionary(); stemmedDocs = new List>(); int docIndex = 0; var tokenizer = new TokenizerFactory(new TokenizationOptions { Pattern = RegexTokenizer.WHITE_SPACE }, SupportedLanguage.English); foreach (var doc in docs) { List stemmedDoc = new List(); docIndex++; if (docIndex % 100 == 0) { Console.WriteLine("Processing " + docIndex + "/" + docs.Length); } List tokens = tokenizer.Tokenize(doc); List list = new List(); tokenizer.Tokenize(doc).ForEach( token => { list.Add(token.Text.ToLower()); }); string[] parts2 = list.ToArray(); //string[] parts2 = Tokenize(doc); List words = new List(); foreach (string part in parts2) { // Strip non-alphanumeric characters. string stripped = Regex.Replace(part, "[^a-zA-Z0-9]", ""); try { var english = new EnglishWord(stripped); string stem = english.Stem; words.Add(stem); if (stem.Length > 0) { // Build the word count list. if (wordCountList.ContainsKey(stem)) { wordCountList[stem]++; } else { wordCountList.Add(stem, 0); } stemmedDoc.Add(stem); } } catch { } } stemmedDocs.Add(stemmedDoc); } // Get the top words. var vocabList = wordCountList.Where(w => w.Value >= vocabularyThreshold); foreach (var item in vocabList) { vocabulary.Add(item.Key); } return vocabulary; } } public class EnglishWord { public EnglishWord(string input) { this.Original = input; this.Stem = input; this.Length = input.Length; } public string Stem { get; set; } public string Original { get; } public int Length { get; } } }