2018-09-04 02:05:57 +00:00
|
|
|
/*
|
|
|
|
|
* BotSharp.NLP Library
|
|
|
|
|
* Copyright (C) 2018 Bo Peng
|
|
|
|
|
*
|
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
2018-09-29 04:21:44 +00:00
|
|
|
using Bigtree.Algorithm.Features;
|
|
|
|
|
using Bigtree.Algorithm.SVM;
|
2018-09-26 11:45:35 +00:00
|
|
|
using BotSharp.NLP.Featuring;
|
|
|
|
|
using BotSharp.NLP.Txt2Vec;
|
2018-09-26 22:09:39 +00:00
|
|
|
using Newtonsoft.Json;
|
2018-09-04 22:34:41 +00:00
|
|
|
using Txt2Vec;
|
2018-09-04 02:05:57 +00:00
|
|
|
|
|
|
|
|
namespace BotSharp.NLP.Classify
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// This is a simple (naive) classification method based on Support Vector Machine (SVM)
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class SVMClassifier : IClassifier
|
|
|
|
|
{
|
2018-09-26 22:09:39 +00:00
|
|
|
private List<string> features;
|
|
|
|
|
private List<Tuple<string, int>> dictionary;
|
|
|
|
|
private List<string> categories;
|
|
|
|
|
private RangeTransform transform;
|
2018-09-29 04:21:44 +00:00
|
|
|
private Bigtree.Algorithm.SVM.Model model;
|
2018-10-03 21:49:40 +00:00
|
|
|
private List<string> featuresInTfIdf;
|
2018-09-04 02:05:57 +00:00
|
|
|
|
2018-09-12 20:31:20 +00:00
|
|
|
public void Train(List<Sentence> sentences, ClassifyOptions options)
|
2018-09-11 22:29:36 +00:00
|
|
|
{
|
2018-09-26 22:09:39 +00:00
|
|
|
SVMClassifierTrain(sentences, options);
|
2018-09-11 22:29:36 +00:00
|
|
|
}
|
|
|
|
|
|
2018-09-26 22:09:39 +00:00
|
|
|
public void SVMClassifierTrain(List<Sentence> sentences, ClassifyOptions options, SvmType svm = SvmType.C_SVC, KernelType kernel = KernelType.RBF, bool probability = true, string outputFile = null)
|
2018-09-04 02:05:57 +00:00
|
|
|
{
|
2018-10-03 21:49:40 +00:00
|
|
|
var tfidf = new TfIdfFeatureExtractor();
|
|
|
|
|
tfidf.Dimension = options.Dimension;
|
|
|
|
|
tfidf.Sentences = sentences;
|
|
|
|
|
tfidf.CalBasedOnCategory();
|
|
|
|
|
featuresInTfIdf = tfidf.Keywords();
|
|
|
|
|
|
2018-09-04 02:05:57 +00:00
|
|
|
// copy test multiclass Model
|
|
|
|
|
Problem train = new Problem();
|
2018-10-03 22:28:47 +00:00
|
|
|
train.X = GetData(sentences, options).ToArray();
|
2018-09-26 22:09:39 +00:00
|
|
|
train.Y = GetLabels(sentences).ToArray();
|
2018-09-04 22:34:41 +00:00
|
|
|
train.Count = train.X.Count();
|
2018-09-26 22:09:39 +00:00
|
|
|
train.MaxIndex = train.X[0].Count();//int.MaxValue;
|
2018-09-04 02:05:57 +00:00
|
|
|
|
|
|
|
|
Parameter param = new Parameter();
|
2018-09-26 22:09:39 +00:00
|
|
|
transform = RangeTransform.Compute(train);
|
2018-09-04 02:05:57 +00:00
|
|
|
Problem scaled = transform.Scale(train);
|
|
|
|
|
param.Gamma = 1.0 / 3;
|
|
|
|
|
param.SvmType = svm;
|
|
|
|
|
param.KernelType = kernel;
|
|
|
|
|
param.Probability = probability;
|
|
|
|
|
|
2018-09-26 22:09:39 +00:00
|
|
|
int numberOfClasses = train.Y.OrderBy(x => x).Distinct().Count();
|
2018-09-04 02:05:57 +00:00
|
|
|
if (numberOfClasses == 1)
|
|
|
|
|
{
|
2018-10-01 21:28:40 +00:00
|
|
|
Console.Write("Number of classes must greater than one!");
|
2018-09-04 02:05:57 +00:00
|
|
|
}
|
2018-10-01 21:28:40 +00:00
|
|
|
|
2018-09-04 02:05:57 +00:00
|
|
|
if (svm == SvmType.C_SVC)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < numberOfClasses; i++)
|
|
|
|
|
param.Weights[i] = 1;
|
|
|
|
|
}
|
2018-09-26 22:09:39 +00:00
|
|
|
|
|
|
|
|
model = Training.Train(scaled, param);
|
|
|
|
|
|
2018-09-04 22:34:41 +00:00
|
|
|
Console.Write("Training finished!");
|
2018-09-04 02:05:57 +00:00
|
|
|
}
|
|
|
|
|
|
2018-09-26 22:09:39 +00:00
|
|
|
public List<Tuple<string, double>> Classify(Sentence sentence, ClassifyOptions options)
|
2018-09-04 02:05:57 +00:00
|
|
|
{
|
2018-09-26 22:09:39 +00:00
|
|
|
var categoryList = new List<Tuple<string, double>>();
|
|
|
|
|
|
|
|
|
|
var result = Predict(sentence, options).FirstOrDefault();
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < result.Length; i++)
|
2018-09-04 02:05:57 +00:00
|
|
|
{
|
2018-09-26 22:09:39 +00:00
|
|
|
categoryList.Add(new Tuple<string, double>(categories[i], result[i]));
|
2018-09-04 02:05:57 +00:00
|
|
|
}
|
|
|
|
|
|
2018-09-26 22:09:39 +00:00
|
|
|
return categoryList;
|
2018-09-04 02:05:57 +00:00
|
|
|
}
|
|
|
|
|
|
2018-09-26 22:09:39 +00:00
|
|
|
public double[][] Predict(Sentence sentence, ClassifyOptions options)
|
2018-09-04 02:05:57 +00:00
|
|
|
{
|
2018-09-26 22:09:39 +00:00
|
|
|
Problem predict = new Problem();
|
2018-10-03 22:28:47 +00:00
|
|
|
predict.X = GetData(new List<Sentence> { sentence }, options).ToArray();
|
2018-09-26 22:09:39 +00:00
|
|
|
predict.Y = new double[1];
|
|
|
|
|
predict.Count = predict.X.Count();
|
|
|
|
|
predict.MaxIndex = features.Count;
|
2018-09-04 02:05:57 +00:00
|
|
|
|
2018-09-26 22:09:39 +00:00
|
|
|
transform = options.Transform;
|
|
|
|
|
Problem scaled = transform.Scale(predict);
|
|
|
|
|
|
|
|
|
|
return Prediction.PredictLabelsProbability(model, scaled);
|
2018-09-04 02:05:57 +00:00
|
|
|
}
|
2018-09-04 22:34:41 +00:00
|
|
|
|
2018-09-26 22:09:39 +00:00
|
|
|
public List<double> GetLabels(List<Sentence> sentences)
|
2018-09-04 22:34:41 +00:00
|
|
|
{
|
2018-09-26 22:09:39 +00:00
|
|
|
categories = sentences.Select(x => x.Label).Distinct().OrderBy(x => x).ToList();
|
|
|
|
|
List<double> labels = new List<double>();
|
|
|
|
|
|
|
|
|
|
foreach (var sentence in sentences)
|
2018-09-04 22:34:41 +00:00
|
|
|
{
|
2018-09-26 22:09:39 +00:00
|
|
|
var labelId = categories.IndexOf(sentence.Label).ToString();
|
|
|
|
|
labels.Add(double.Parse(labelId));
|
2018-09-04 22:34:41 +00:00
|
|
|
}
|
|
|
|
|
|
2018-09-26 22:09:39 +00:00
|
|
|
return labels;
|
2018-09-04 22:34:41 +00:00
|
|
|
}
|
|
|
|
|
|
2018-10-03 22:28:47 +00:00
|
|
|
public List<Node[]> GetData(List<Sentence> sentences, ClassifyOptions options)
|
2018-09-04 22:34:41 +00:00
|
|
|
{
|
2018-10-03 21:49:40 +00:00
|
|
|
//var extractor = new CountFeatureExtractor();
|
2018-10-03 17:15:46 +00:00
|
|
|
var extractor = new Word2VecFeatureExtractor();
|
2018-10-03 22:28:47 +00:00
|
|
|
extractor.ModelFile = options.Word2VecFilePath;
|
2018-09-26 22:09:39 +00:00
|
|
|
extractor.Sentences = sentences;
|
|
|
|
|
if(features != null)
|
|
|
|
|
{
|
|
|
|
|
extractor.Features = features;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(dictionary != null)
|
|
|
|
|
{
|
|
|
|
|
extractor.Dictionary = dictionary;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-03 21:49:40 +00:00
|
|
|
extractor.Vectorize(featuresInTfIdf);
|
2018-09-26 22:09:39 +00:00
|
|
|
|
|
|
|
|
if(features == null)
|
|
|
|
|
{
|
|
|
|
|
features = extractor.Features;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(dictionary == null)
|
2018-09-04 22:34:41 +00:00
|
|
|
{
|
2018-09-26 22:09:39 +00:00
|
|
|
dictionary = extractor.Dictionary;
|
2018-09-04 22:34:41 +00:00
|
|
|
}
|
|
|
|
|
|
2018-09-26 22:09:39 +00:00
|
|
|
List<Node[]> datas = new List<Node[]>();
|
|
|
|
|
|
|
|
|
|
foreach (var sentence in sentences)
|
|
|
|
|
{
|
|
|
|
|
List<Node> curNodes = new List<Node>();
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < extractor.Features.Count; i++)
|
|
|
|
|
{
|
2018-10-03 21:49:40 +00:00
|
|
|
|
2018-09-26 22:09:39 +00:00
|
|
|
int name = i;
|
2018-10-03 21:49:40 +00:00
|
|
|
/*var xx = sentence.Words.Find(x => x.Lemma == extractor.Features[i]);
|
2018-09-26 22:09:39 +00:00
|
|
|
|
|
|
|
|
if (xx == null)
|
|
|
|
|
{
|
|
|
|
|
curNodes.Add(new Node(name, 0));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
curNodes.Add(new Node(name, xx.Vector));
|
2018-10-03 21:49:40 +00:00
|
|
|
}*/
|
|
|
|
|
|
|
|
|
|
curNodes.Add(new Node(i, sentence.Vector[i]));
|
2018-09-26 22:09:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
datas.Add(curNodes.ToArray());
|
|
|
|
|
}
|
|
|
|
|
return datas;
|
2018-09-04 22:34:41 +00:00
|
|
|
}
|
2018-09-12 20:31:20 +00:00
|
|
|
|
|
|
|
|
public string SaveModel(ClassifyOptions options)
|
|
|
|
|
{
|
2018-09-26 22:09:39 +00:00
|
|
|
options.TransformFilePath = Path.Combine(options.ModelDir, "transform");
|
|
|
|
|
options.FeaturesFileName = Path.Combine(options.ModelDir, "features");
|
|
|
|
|
options.DictionaryFileName = Path.Combine(options.ModelDir, "dictionary");
|
|
|
|
|
options.CategoriesFileName = Path.Combine(options.ModelDir, "categories");
|
2018-10-03 21:49:40 +00:00
|
|
|
options.FeaturesInTfIdfFileName = Path.Combine(options.ModelDir, "featuresInTfIdf");
|
2018-09-26 22:09:39 +00:00
|
|
|
|
|
|
|
|
File.WriteAllText(options.FeaturesFileName, JsonConvert.SerializeObject(features));
|
|
|
|
|
|
2018-10-03 21:49:40 +00:00
|
|
|
File.WriteAllText(options.FeaturesInTfIdfFileName, JsonConvert.SerializeObject(featuresInTfIdf));
|
|
|
|
|
|
2018-09-26 22:09:39 +00:00
|
|
|
File.WriteAllText(options.DictionaryFileName, JsonConvert.SerializeObject(dictionary));
|
|
|
|
|
|
|
|
|
|
File.WriteAllText(options.CategoriesFileName, JsonConvert.SerializeObject(categories));
|
|
|
|
|
|
|
|
|
|
RangeTransform.Write(options.TransformFilePath, transform);
|
2018-09-29 04:21:44 +00:00
|
|
|
Bigtree.Algorithm.SVM.Model.Write(options.ModelFilePath, model);
|
2018-09-26 22:09:39 +00:00
|
|
|
|
|
|
|
|
return options.ModelFilePath;
|
2018-09-12 20:31:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
object IClassifier.LoadModel(ClassifyOptions options)
|
|
|
|
|
{
|
2018-09-26 22:09:39 +00:00
|
|
|
options.FeaturesFileName = Path.Combine(options.ModelDir, "features");
|
|
|
|
|
options.DictionaryFileName = Path.Combine(options.ModelDir, "dictionary");
|
|
|
|
|
options.ModelFilePath = Path.Combine(options.ModelDir, options.ModelName);
|
|
|
|
|
options.TransformFilePath = Path.Combine(options.ModelDir, "transform");
|
|
|
|
|
options.CategoriesFileName = Path.Combine(options.ModelDir, "categories");
|
2018-10-03 21:49:40 +00:00
|
|
|
options.FeaturesInTfIdfFileName = Path.Combine(options.ModelDir, "featuresInTfIdf");
|
2018-09-26 22:09:39 +00:00
|
|
|
|
|
|
|
|
features = JsonConvert.DeserializeObject<List<String>>(File.ReadAllText(options.FeaturesFileName));
|
|
|
|
|
|
2018-10-03 21:49:40 +00:00
|
|
|
featuresInTfIdf = JsonConvert.DeserializeObject<List<String>>(File.ReadAllText(options.FeaturesInTfIdfFileName));
|
|
|
|
|
|
2018-09-26 22:09:39 +00:00
|
|
|
dictionary = JsonConvert.DeserializeObject<List<Tuple<string, int>>>(File.ReadAllText(options.DictionaryFileName));
|
|
|
|
|
|
|
|
|
|
categories = JsonConvert.DeserializeObject<List<String>>(File.ReadAllText(options.CategoriesFileName));
|
|
|
|
|
|
2018-09-29 04:21:44 +00:00
|
|
|
model = Bigtree.Algorithm.SVM.Model.Read(options.ModelFilePath);
|
2018-09-26 22:09:39 +00:00
|
|
|
|
|
|
|
|
options.Transform = RangeTransform.Read(options.TransformFilePath);
|
|
|
|
|
|
|
|
|
|
return model;
|
2018-09-12 20:31:20 +00:00
|
|
|
}
|
2018-09-04 02:05:57 +00:00
|
|
|
}
|
|
|
|
|
}
|