Add SVM low level machine learning algorithm, prepare for NLP text classification.

This commit is contained in:
Oceania2018 2018-08-21 09:43:24 -05:00
parent 6bf552b20a
commit a7e0280b1d
36 changed files with 6036 additions and 50 deletions

View file

@ -13,9 +13,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
<PackageReference Include="MSTest.TestAdapter" Version="1.2.1" />
<PackageReference Include="MSTest.TestFramework" Version="1.2.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
<PackageReference Include="MSTest.TestAdapter" Version="1.3.2" />
<PackageReference Include="MSTest.TestFramework" Version="1.3.2" />
</ItemGroup>
<ItemGroup>

View file

@ -0,0 +1,126 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SVM;
using SVM.BotSharp.MachineLearning;
using SVM.BotSharp.MachineLearningTest;
namespace BotSharp.MachineLearning.UnitTest.SVM
{
[TestClass]
public class ClassificationTests
{
[TestMethod]
public void TestTwoClass()
{
SvmType[] svmTypes = new SvmType[]{SvmType.C_SVC, SvmType.NU_SVC};
KernelType[] kernelTypes = new KernelType[]{KernelType.LINEAR, KernelType.POLY, KernelType.RBF, KernelType.SIGMOID};
foreach (SvmType svm in svmTypes)
{
foreach (KernelType kernel in kernelTypes)
{
double score = testTwoClassModel(100, svm, kernel);
Assert.AreEqual(1, score, .01, string.Format("SVM {0} with Kernel {1} did not train correctly", svm, kernel));
}
}
}
[TestMethod]
public void TestTwoClassProbability()
{
SvmType[] svmTypes = new SvmType[] { SvmType.C_SVC, SvmType.NU_SVC };
KernelType[] kernelTypes = new KernelType[] { KernelType.LINEAR, KernelType.POLY, KernelType.RBF, KernelType.SIGMOID };
foreach (SvmType svm in svmTypes)
{
foreach (KernelType kernel in kernelTypes)
{
double score = testTwoClassModel(100, svm, kernel, true);
Assert.AreEqual(1, score, .01, string.Format("SVM {0} with Kernel {1} did not train correctly", svm, kernel));
}
}
}
[TestMethod]
public void TestMulticlass()
{
SvmType[] svmTypes = new SvmType[] { SvmType.C_SVC, SvmType.NU_SVC };
KernelType[] kernelTypes = new KernelType[] { KernelType.LINEAR, KernelType.POLY, KernelType.RBF, KernelType.SIGMOID };
foreach (SvmType svm in svmTypes)
{
foreach (KernelType kernel in kernelTypes)
{
double score = testMulticlassModel(8, 100, svm, kernel);
Assert.AreEqual(1, score, .1, string.Format("SVM {0} with Kernel {1} did not train correctly", svm, kernel));
}
}
}
[TestMethod]
public void TestMulticlassProbability()
{
SvmType[] svmTypes = new SvmType[] { SvmType.C_SVC, SvmType.NU_SVC };
KernelType[] kernelTypes = new KernelType[] { KernelType.LINEAR, KernelType.POLY, KernelType.RBF, KernelType.SIGMOID };
foreach (SvmType svm in svmTypes)
{
foreach (KernelType kernel in kernelTypes)
{
double score = testMulticlassModel(8, 100, svm, kernel, true);
Assert.AreEqual(1, score, .1, string.Format("SVM {0} with Kernel {1} did not train correctly", svm, kernel));
}
}
}
private double testTwoClassModel(int count, SvmType svm, KernelType kernel, bool probability = false, string outputFile = null)
{
Problem train = SVMUtilities.CreateTwoClassProblem(count);
Parameter param = new Parameter();
RangeTransform transform = RangeTransform.Compute(train);
Problem scaled = transform.Scale(train);
param.Gamma = .5;
param.SvmType = svm;
param.KernelType = kernel;
param.Probability = probability;
if (svm == SvmType.C_SVC)
{
param.Weights[-1] = 1;
param.Weights[1] = 1;
}
Model model = Training.Train(scaled, param);
Problem test = SVMUtilities.CreateTwoClassProblem(count, false);
scaled = transform.Scale(test);
return Prediction.Predict(scaled, outputFile, model, false);
}
private double testMulticlassModel(int numberOfClasses, int count, SvmType svm, KernelType kernel, bool probability = false, string outputFile = null)
{
Problem train = SVMUtilities.CreateMulticlassProblem(numberOfClasses, count);
Parameter param = new Parameter();
RangeTransform transform = RangeTransform.Compute(train);
Problem scaled = transform.Scale(train);
param.Gamma = 1.0 / 3;
param.SvmType = svm;
param.KernelType = kernel;
param.Probability = probability;
if (svm == SvmType.C_SVC)
{
for (int i = 0; i < numberOfClasses; i++)
param.Weights[i] = 1;
}
Model model = Training.Train(scaled, param);
Problem test = SVMUtilities.CreateMulticlassProblem(numberOfClasses, count, false);
scaled = transform.Scale(test);
return Prediction.Predict(scaled, outputFile, model, false);
}
}
}

View file

@ -0,0 +1,76 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SVM;
using System.Linq;
using System.IO;
using System.Text;
using SVM.BotSharp.MachineLearning;
using SVM.BotSharp.MachineLearningTest;
namespace BotSharp.MachineLearning.UnitTest.SVM
{
[TestClass]
public class IOTests
{
[TestMethod]
public void ReadProblem()
{
Problem expected = SVMUtilities.CreateTwoClassProblem(100);
Problem actual = Problem.Read("train0.problem");
Assert.AreEqual(expected, actual);
}
[TestMethod]
public void WriteProblem()
{
Problem prob = SVMUtilities.CreateTwoClassProblem(100);
using (MemoryStream stream = new MemoryStream())
using (StreamReader input = new StreamReader("train0.problem"))
{
Problem.Write(stream, prob);
string expected = input.ReadToEnd().Replace("\r\n", "\n");
string actual = Encoding.ASCII.GetString(stream.ToArray());
Assert.AreEqual(expected, actual);
}
}
[TestMethod]
public void ReadModel()
{
Problem train = SVMUtilities.CreateTwoClassProblem(100);
Parameter param = new Parameter();
RangeTransform transform = RangeTransform.Compute(train);
Problem scaled = transform.Scale(train);
param.KernelType = KernelType.LINEAR;
Training.SetRandomSeed(SVMUtilities.TRAINING_SEED);
Model expected = Training.Train(scaled, param);
Model actual = Model.Read("svm0.model");
Assert.AreEqual(expected, actual);
}
[TestMethod]
public void WriteModel()
{
Problem train = SVMUtilities.CreateTwoClassProblem(100);
Parameter param = new Parameter();
RangeTransform transform = RangeTransform.Compute(train);
Problem scaled = transform.Scale(train);
param.KernelType = KernelType.LINEAR;
Training.SetRandomSeed(SVMUtilities.TRAINING_SEED);
Model model = Training.Train(scaled, param);
using (MemoryStream stream = new MemoryStream())
using (StreamReader input = new StreamReader("svm0.model"))
{
Model.Write(stream, model);
string expected = input.ReadToEnd().Replace("\r\n", "\n");
string actual = Encoding.ASCII.GetString(stream.ToArray());
Assert.AreEqual(expected, actual);
}
}
}
}

View file

@ -0,0 +1,48 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SVM;
using SVM.BotSharp.MachineLearning;
using SVM.BotSharp.MachineLearningTest;
namespace BotSharp.MachineLearning.UnitTest.SVM
{
[TestClass]
public class RegressionTests
{
[TestMethod]
public void TestRegression()
{
SvmType[] svmTypes = new SvmType[] { SvmType.NU_SVR, SvmType.EPSILON_SVR };
// LINEAR kernel is pretty horrible for regression
KernelType[] kernelTypes = new KernelType[] { KernelType.LINEAR, KernelType.RBF, KernelType.SIGMOID };
foreach (SvmType svm in svmTypes)
{
foreach (KernelType kernel in kernelTypes)
{
double error = testRegressionModel(100, svm, kernel);
Assert.AreEqual(0, error, 2, string.Format("SVM {0} with Kernel {1} did not train correctly", svm, kernel));
}
}
}
private double testRegressionModel(int count, SvmType svm, KernelType kernel, string outputFile = null)
{
Problem train = SVMUtilities.CreateRegressionProblem(count);
Parameter param = new Parameter();
RangeTransform transform = RangeTransform.Compute(train);
Problem scaled = transform.Scale(train);
param.Gamma = 1.0 / 2;
param.SvmType = svm;
param.KernelType = kernel;
param.Degree = 2;
Model model = Training.Train(scaled, param);
Problem test = SVMUtilities.CreateRegressionProblem(count, false);
scaled = transform.Scale(test);
return Prediction.Predict(scaled, outputFile, model, false);
}
}
}

View file

@ -1,4 +1,22 @@
using BotSharp.MachineLearning.CRFLite.Decoder;
/*
* BotSharp.NLP Library
* Copyright (C) 2018 Haiping Chen
*
* 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 BotSharp.MachineLearning.CRFLite.Decoder;
using System;
using System.Collections.Generic;
using System.IO;
@ -65,7 +83,7 @@ namespace BotSharp.MachineLearning.CRFLite
if (inbuf.Count == 0)
{
//Empty input string
return BaseUtils.ERROR_SUCCESS;
return BaseUtils.RETURN_SUCCESS;
}
ret = tagger.reset();
@ -94,7 +112,7 @@ namespace BotSharp.MachineLearning.CRFLite
return ret;
}
return BaseUtils.ERROR_SUCCESS;
return BaseUtils.RETURN_SUCCESS;
}
@ -109,7 +127,7 @@ namespace BotSharp.MachineLearning.CRFLite
if (inbuf.Count == 0)
{
//Empty input string
return BaseUtils.ERROR_SUCCESS;
return BaseUtils.RETURN_SUCCESS;
}
ret = tagger.reset();
@ -138,7 +156,7 @@ namespace BotSharp.MachineLearning.CRFLite
return ret;
}
return BaseUtils.ERROR_SUCCESS;
return BaseUtils.RETURN_SUCCESS;
}
}
}

View file

@ -1,4 +1,22 @@
using BotSharp.MachineLearning.CRFLite.Encoder;
/*
* BotSharp.NLP Library
* Copyright (C) 2018 Haiping Chen
*
* 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 BotSharp.MachineLearning.CRFLite.Encoder;
using System;
using System.Collections.Generic;
using System.Linq;

View file

@ -149,7 +149,7 @@ namespace BotSharp.MachineLearning.CRFLite.Decoder
}
}
return BaseUtils.ERROR_SUCCESS;
return BaseUtils.RETURN_SUCCESS;
}
public int initNbest()
@ -164,10 +164,10 @@ namespace BotSharp.MachineLearning.CRFLite.Decoder
eos.next = null;
if (BaseUtils.heap_insert(eos, heap_queue) < 0)
{
return BaseUtils.ERROR_INSERT_HEAP_FAILED;
return BaseUtils.RETURN_INSERT_HEAP_FAILED;
}
}
return BaseUtils.ERROR_SUCCESS;
return BaseUtils.RETURN_SUCCESS;
}
public int next()
@ -199,7 +199,7 @@ namespace BotSharp.MachineLearning.CRFLite.Decoder
n.next = top;
if (BaseUtils.heap_insert(n, heap_queue) < 0)
{
return BaseUtils.ERROR_INSERT_HEAP_FAILED;
return BaseUtils.RETURN_INSERT_HEAP_FAILED;
}
}
}
@ -212,7 +212,7 @@ namespace BotSharp.MachineLearning.CRFLite.Decoder
Z_ = cost_ = 0.0;
BaseUtils.heap_reset(heap_queue);
return BaseUtils.ERROR_SUCCESS;
return BaseUtils.RETURN_SUCCESS;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -235,7 +235,7 @@ namespace BotSharp.MachineLearning.CRFLite.Decoder
}
}
return BaseUtils.ERROR_SUCCESS;
return BaseUtils.RETURN_SUCCESS;
}
public int add(List<List<string>> row_p)
@ -243,7 +243,7 @@ namespace BotSharp.MachineLearning.CRFLite.Decoder
x_ = row_p;
word_num = (short)x_.Count;
return BaseUtils.ERROR_SUCCESS;
return BaseUtils.RETURN_SUCCESS;
}
@ -270,7 +270,7 @@ namespace BotSharp.MachineLearning.CRFLite.Decoder
break;
}
}
return BaseUtils.ERROR_SUCCESS;
return BaseUtils.RETURN_SUCCESS;
}
//Label input string. The result is saved as result []
@ -282,7 +282,7 @@ namespace BotSharp.MachineLearning.CRFLite.Decoder
//no word need to be labeled
if (word_num == 0)
{
return BaseUtils.ERROR_SUCCESS;
return BaseUtils.RETURN_SUCCESS;
}
//building feature set
@ -326,7 +326,7 @@ namespace BotSharp.MachineLearning.CRFLite.Decoder
}
return BaseUtils.ERROR_SUCCESS;
return BaseUtils.RETURN_SUCCESS;
}
@ -334,7 +334,7 @@ namespace BotSharp.MachineLearning.CRFLite.Decoder
{
if (word_num <= 0)
{
return BaseUtils.ERROR_INVALIDATED_PARAMETER;
return BaseUtils.RETURN_INVALIDATED_PARAMETER;
}
using (var v = _buildersPool.GetOrCreate())
{
@ -351,7 +351,7 @@ namespace BotSharp.MachineLearning.CRFLite.Decoder
var res = featureIndex.apply_rule(templ, cur, builder, this);
if (res == null)
{
return BaseUtils.ERROR_EMPTY_FEATURE;
return BaseUtils.RETURN_EMPTY_FEATURE;
}
id = featureIndex.get_id(res.ToString());
if (id != -1)
@ -373,7 +373,7 @@ namespace BotSharp.MachineLearning.CRFLite.Decoder
var strFeature = featureIndex.apply_rule(templ, cur, builder, this);
if (strFeature == null)
{
return BaseUtils.ERROR_EMPTY_FEATURE;
return BaseUtils.RETURN_EMPTY_FEATURE;
}
id = featureIndex.get_id(strFeature.ToString());
@ -387,7 +387,7 @@ namespace BotSharp.MachineLearning.CRFLite.Decoder
feature_cache_size++;
}
return BaseUtils.ERROR_SUCCESS;
return BaseUtils.RETURN_SUCCESS;
}
}
@ -465,7 +465,7 @@ namespace BotSharp.MachineLearning.CRFLite.Decoder
}
}
return BaseUtils.ERROR_SUCCESS;
return BaseUtils.RETURN_SUCCESS;
}
}
}

View file

@ -92,7 +92,7 @@ namespace BotSharp.MachineLearning.CRFLite.Encoder
return pair.Key;
}
return BaseUtils.ERROR_INVALIDATED_FEATURE;
return BaseUtils.RETURN_INVALIDATED_FEATURE;
}
public long GetOrAddId(string key)

View file

@ -19,7 +19,7 @@ namespace BotSharp.MachineLearning.CRFLite
//build raw result at first
var iRet = termbuf_build(term_buf);
if (iRet != BaseUtils.ERROR_SUCCESS)
if (iRet != BaseUtils.RETURN_SUCCESS)
{
return iRet;
}
@ -82,7 +82,7 @@ namespace BotSharp.MachineLearning.CRFLite
}
return BaseUtils.ERROR_SUCCESS;
return BaseUtils.RETURN_SUCCESS;
}
@ -125,7 +125,7 @@ namespace BotSharp.MachineLearning.CRFLite
}
}
return BaseUtils.ERROR_SUCCESS;
return BaseUtils.RETURN_SUCCESS;
}
}
}

View file

@ -70,7 +70,7 @@ namespace BotSharp.MachineLearning.CRFLite
cost_ = -node_[s, result_[s]].bestCost;
return BaseUtils.ERROR_SUCCESS;
return BaseUtils.RETURN_SUCCESS;
}
private void calcAlpha(int m, int n)

View file

@ -32,16 +32,15 @@ namespace BotSharp.MachineLearning.CRFLite
public const int MODEL_TYPE_NORM = 100;
public const int ERROR_INVALIDATED_FEATURE = -8;
public const int ERROR_HEAP_SIZE_TOO_BIG = -7;
public const int ERROR_INSERT_HEAP_FAILED = -6;
public const int ERROR_EMPTY_FEATURE = -5;
public const int ERROR_INVALIDATED_PARAMETER = -4;
public const int ERROR_WRONG_STATUS = -3;
public const int ERROR_TOO_LONG_WORD = -2;
public const int ERROR_UNKNOWN = -1;
public const int ERROR_SUCCESS = 0;
public const int RETURN_INVALIDATED_FEATURE = -8;
public const int RETURN_HEAP_SIZE_TOO_BIG = -7;
public const int RETURN_INSERT_HEAP_FAILED = -6;
public const int RETURN_EMPTY_FEATURE = -5;
public const int RETURN_INVALIDATED_PARAMETER = -4;
public const int RETURN_WRONG_STATUS = -3;
public const int RETURN_TOO_LONG_WORD = -2;
public const int RETURN_UNKNOWN = -1;
public const int RETURN_SUCCESS = 0;
public static Heap heap_init(int max_size)
{
@ -82,7 +81,7 @@ namespace BotSharp.MachineLearning.CRFLite
{
if (H.size >= H.capacity)
{
return BaseUtils.ERROR_HEAP_SIZE_TOO_BIG;
return BaseUtils.RETURN_HEAP_SIZE_TOO_BIG;
}
var i = ++H.size;
while (i != 1 && H.elem_ptr_list[i / 2].fx > qe.fx)

View file

@ -0,0 +1,161 @@
/*
* SVM.NET Library
* Copyright (C) 2008 Matthew Johnson
*
* 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;
namespace SVM.BotSharp.MachineLearning
{
internal class Cache
{
private int _count;
private long _size;
private sealed class head_t
{
public head_t(Cache enclosingInstance)
{
_enclosingInstance = enclosingInstance;
}
private Cache _enclosingInstance;
public Cache EnclosingInstance
{
get
{
return _enclosingInstance;
}
}
internal head_t prev, next; // a cicular list
internal float[] data;
internal int len; // data[0,len) is cached in this entry
}
private head_t[] head;
private head_t lru_head;
public Cache(int count, long size)
{
_count = count;
_size = size;
head = new head_t[_count];
for (int i = 0; i < _count; i++)
head[i] = new head_t(this);
_size /= 4;
_size -= _count * (16 / 4); // sizeof(head_t) == 16
lru_head = new head_t(this);
lru_head.next = lru_head.prev = lru_head;
}
private void lru_delete(head_t h)
{
// delete from current location
h.prev.next = h.next;
h.next.prev = h.prev;
}
private void lru_insert(head_t h)
{
// insert to last position
h.next = lru_head;
h.prev = lru_head.prev;
h.prev.next = h;
h.next.prev = h;
}
private static void swap<T>(ref T lhs, ref T rhs)
{
T tmp = lhs;
lhs = rhs;
rhs = tmp;
}
// request data [0,len)
// return some position p where [p,len) need to be filled
// (p >= len if nothing needs to be filled)
// java: simulate pointer using single-element array
public int GetData(int index, out float[] data, int len)
{
head_t h = head[index];
if (h.len > 0)
lru_delete(h);
int more = len - h.len;
if (more > 0)
{
// free old space
while (_size < more)
{
head_t old = lru_head.next;
lru_delete(old);
_size += old.len;
old.data = null;
old.len = 0;
}
// allocate new space
float[] new_data = new float[len];
if (h.data != null)
Array.Copy(h.data, 0, new_data, 0, h.len);
h.data = new_data;
_size -= more;
swap(ref h.len, ref len);
}
lru_insert(h);
data = h.data;
return len;
}
public void SwapIndex(int i, int j)
{
if (i == j)
return;
if (head[i].len > 0)
lru_delete(head[i]);
if (head[j].len > 0)
lru_delete(head[j]);
swap(ref head[i].data, ref head[j].data);
swap(ref head[i].len, ref head[j].len);
if (head[i].len > 0)
lru_insert(head[i]);
if (head[j].len > 0)
lru_insert(head[j]);
if (i > j)
swap(ref i, ref j);
for (head_t h = lru_head.next; h != lru_head; h = h.next)
{
if (h.len > i)
{
if (h.len > j)
swap(ref h.data[i], ref h.data[j]);
else
{
// give up
lru_delete(h);
_size += h.len;
h.data = null;
h.len = 0;
}
}
}
}
}
}

View file

@ -0,0 +1,209 @@
/*
* SVM.NET Library
* Copyright (C) 2008 Matthew Johnson
*
* 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.Globalization;
using System.Threading;
namespace SVM.BotSharp.MachineLearning
{
/// <summary>
/// A transform which learns the mean and variance of a sample set and uses these to transform new data
/// so that it has zero mean and unit variance.
/// </summary>
public class GaussianTransform : IRangeTransform
{
private double[] _means;
private double[] _stddevs;
/// <summary>
/// Determines the Gaussian transform for the provided problem.
/// </summary>
/// <param name="prob">The Problem to analyze</param>
/// <returns>The Gaussian transform for the problem</returns>
public static GaussianTransform Compute(Problem prob)
{
int[] counts = new int[prob.MaxIndex];
double[] means = new double[prob.MaxIndex];
foreach (Node[] sample in prob.X)
{
for (int i = 0; i < sample.Length; i++)
{
means[sample[i].Index-1] += sample[i].Value;
counts[sample[i].Index-1]++;
}
}
for (int i = 0; i < prob.MaxIndex; i++)
{
if (counts[i] == 0)
counts[i] = 2;
means[i] /= counts[i];
}
double[] stddevs = new double[prob.MaxIndex];
foreach (Node[] sample in prob.X)
{
for (int i = 0; i < sample.Length; i++)
{
double diff = sample[i].Value - means[sample[i].Index - 1];
stddevs[sample[i].Index - 1] += diff * diff;
}
}
for (int i = 0; i < prob.MaxIndex; i++)
{
if (stddevs[i] == 0)
continue;
stddevs[i] /= (counts[i] - 1);
stddevs[i] = Math.Sqrt(stddevs[i]);
}
return new GaussianTransform(means, stddevs);
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="means">Means in each dimension</param>
/// <param name="stddevs">Standard deviation in each dimension</param>
public GaussianTransform(double[] means, double[] stddevs)
{
_means = means;
_stddevs = stddevs;
}
/// <summary>
/// Saves the transform to the disk. The samples are not stored, only the
/// statistics.
/// </summary>
/// <param name="stream">The destination stream</param>
/// <param name="transform">The transform</param>
public static void Write(Stream stream, GaussianTransform transform)
{
TemporaryCulture.Start();
StreamWriter output = new StreamWriter(stream);
output.WriteLine(transform._means.Length);
for (int i = 0; i < transform._means.Length; i++)
output.WriteLine("{0} {1}", transform._means[i], transform._stddevs[i]);
output.Flush();
TemporaryCulture.Stop();
}
/// <summary>
/// Reads a GaussianTransform from the provided stream.
/// </summary>
/// <param name="stream">The source stream</param>
/// <returns>The transform</returns>
public static GaussianTransform Read(Stream stream)
{
TemporaryCulture.Start();
StreamReader input = new StreamReader(stream);
int length = int.Parse(input.ReadLine(), CultureInfo.InvariantCulture);
double[] means = new double[length];
double[] stddevs = new double[length];
for (int i = 0; i < length; i++)
{
string[] parts = input.ReadLine().Split();
means[i] = double.Parse(parts[0], CultureInfo.InvariantCulture);
stddevs[i] = double.Parse(parts[1], CultureInfo.InvariantCulture);
}
TemporaryCulture.Stop();
return new GaussianTransform(means, stddevs);
}
/// <summary>
/// Saves the transform to the disk. The samples are not stored, only the
/// statistics.
/// </summary>
/// <param name="filename">The destination filename</param>
/// <param name="transform">The transform</param>
public static void Write(string filename, GaussianTransform transform)
{
FileStream output = File.Open(filename, FileMode.Create);
try
{
Write(output, transform);
}
finally
{
output.Close();
}
}
/// <summary>
/// Reads a GaussianTransform from the provided stream.
/// </summary>
/// <param name="filename">The source filename</param>
/// <returns>The transform</returns>
public static GaussianTransform Read(string filename)
{
FileStream input = File.Open(filename, FileMode.Open);
try
{
return Read(input);
}
finally
{
input.Close();
}
}
#region IRangeTransform Members
/// <summary>
/// Transform the input value using the transform stored for the provided index.
/// </summary>
/// <param name="input">Input value</param>
/// <param name="index">Index of the transform to use</param>
/// <returns>The transformed value</returns>
public double Transform(double input, int index)
{
index--;
if (_stddevs[index] == 0)
return 0;
double diff = input - _means[index];
diff /= _stddevs[index];
return diff;
}
/// <summary>
/// Transforms the input array.
/// </summary>
/// <param name="input">The array to transform</param>
/// <returns>The transformed array</returns>
public Node[] Transform(Node[] input)
{
Node[] output = new Node[input.Length];
for (int i = 0; i < output.Length; i++)
{
int index = input[i].Index;
double value = input[i].Value;
output[i] = new Node(index, Transform(value, index));
}
return output;
}
#endregion
}
}

View file

@ -0,0 +1,41 @@
/*
* SVM.NET Library
* Copyright (C) 2008 Matthew Johnson
*
* 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/>.
*/
namespace SVM.BotSharp.MachineLearning
{
/// <summary>
/// Interface implemented by range transforms.
/// </summary>
public interface IRangeTransform
{
/// <summary>
/// Transform the input value using the transform stored for the provided index.
/// </summary>
/// <param name="input">Input value</param>
/// <param name="index">Index of the transform to use</param>
/// <returns>The transformed value</returns>
double Transform(double input, int index);
/// <summary>
/// Transforms the input array.
/// </summary>
/// <param name="input">The array to transform</param>
/// <returns>The transformed array</returns>
Node[] Transform(Node[] input);
}
}

View file

@ -0,0 +1,226 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SVM.BotSharp.MachineLearning
{
internal interface IQMatrix
{
float[] GetQ(int column, int len);
double[] GetQD();
void SwapIndex(int i, int j);
}
internal abstract class Kernel : IQMatrix
{
private Node[][] _x;
private double[] _xSquare;
private KernelType _kernelType;
private int _degree;
private double _gamma;
private double _coef0;
public abstract float[] GetQ(int column, int len);
public abstract double[] GetQD();
public virtual void SwapIndex(int i, int j)
{
_x.SwapIndex(i, j);
if (_xSquare != null)
{
_xSquare.SwapIndex(i, j);
}
}
private static double powi(double value, int times)
{
double tmp = value, ret = 1.0;
for (int t = times; t > 0; t /= 2)
{
if (t % 2 == 1) ret *= tmp;
tmp = tmp * tmp;
}
return ret;
}
public double KernelFunction(int i, int j)
{
switch (_kernelType)
{
case KernelType.LINEAR:
return dot(_x[i], _x[j]);
case KernelType.POLY:
return powi(_gamma * dot(_x[i], _x[j]) + _coef0, _degree);
case KernelType.RBF:
return Math.Exp(-_gamma * (_xSquare[i] + _xSquare[j] - 2 * dot(_x[i], _x[j])));
case KernelType.SIGMOID:
return Math.Tanh(_gamma * dot(_x[i], _x[j]) + _coef0);
case KernelType.PRECOMPUTED:
return _x[i][(int)(_x[j][0].Value)].Value;
default:
return 0;
}
}
public Kernel(int l, Node[][] x_, Parameter param)
{
_kernelType = param.KernelType;
_degree = param.Degree;
_gamma = param.Gamma;
_coef0 = param.Coefficient0;
_x = (Node[][])x_.Clone();
if (_kernelType == KernelType.RBF)
{
_xSquare = new double[l];
for (int i = 0; i < l; i++)
_xSquare[i] = dot(_x[i], _x[i]);
}
else _xSquare = null;
}
private static double dot(Node[] xNodes, Node[] yNodes)
{
double sum = 0;
int xlen = xNodes.Length;
int ylen = yNodes.Length;
int i = 0;
int j = 0;
Node x = xNodes[0];
Node y = yNodes[0];
while (true)
{
if (x._index == y._index)
{
sum += x._value * y._value;
i++;
j++;
if (i < xlen && j < ylen)
{
x = xNodes[i];
y = yNodes[j];
}
else if (i < xlen)
{
x = xNodes[i];
break;
}
else if (j < ylen)
{
y = yNodes[j];
break;
}
else break;
}
else
{
if (x._index > y._index)
{
++j;
if (j < ylen)
y = yNodes[j];
else break;
}
else
{
++i;
if (i < xlen)
x = xNodes[i];
else break;
}
}
}
return sum;
}
private static double computeSquaredDistance(Node[] xNodes, Node[] yNodes)
{
Node x = xNodes[0];
Node y = yNodes[0];
int xLength = xNodes.Length;
int yLength = yNodes.Length;
int xIndex = 0;
int yIndex = 0;
double sum = 0;
while (true)
{
if (x._index == y._index)
{
double d = x._value - y._value;
sum += d * d;
xIndex++;
yIndex++;
if (xIndex < xLength && yIndex < yLength)
{
x = xNodes[xIndex];
y = yNodes[yIndex];
}
else if(xIndex < xLength){
x = xNodes[xIndex];
break;
}
else if(yIndex < yLength){
y = yNodes[yIndex];
break;
}else break;
}
else if (x._index > y._index)
{
sum += y._value * y._value;
if (++yIndex < yLength)
y = yNodes[yIndex];
else break;
}
else
{
sum += x._value * x._value;
if (++xIndex < xLength)
x = xNodes[xIndex];
else break;
}
}
for (; xIndex < xLength; xIndex++)
{
double d = xNodes[xIndex]._value;
sum += d * d;
}
for (; yIndex < yLength; yIndex++)
{
double d = yNodes[yIndex]._value;
sum += d * d;
}
return sum;
}
public static double KernelFunction(Node[] x, Node[] y, Parameter param)
{
switch (param.KernelType)
{
case KernelType.LINEAR:
return dot(x, y);
case KernelType.POLY:
return powi(param.Degree * dot(x, y) + param.Coefficient0, param.Degree);
case KernelType.RBF:
{
double sum = computeSquaredDistance(x, y);
return Math.Exp(-param.Gamma * sum);
}
case KernelType.SIGMOID:
return Math.Tanh(param.Gamma * dot(x, y) + param.Coefficient0);
case KernelType.PRECOMPUTED:
return x[(int)(y[0].Value)].Value;
default:
return 0;
}
}
}
}

View file

@ -0,0 +1,420 @@
/*
* SVM.NET Library
* Copyright (C) 2008 Matthew Johnson
*
* 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.IO;
using System.Threading;
using System.Globalization;
namespace SVM.BotSharp.MachineLearning
{
/// <summary>
/// Encapsulates an SVM Model.
/// </summary>
[Serializable]
public class Model
{
internal Model()
{
}
/// <summary>
/// Parameter object.
/// </summary>
public Parameter Parameter{get;set;}
/// <summary>
/// Number of classes in the model.
/// </summary>
public int NumberOfClasses{get;set;}
/// <summary>
/// Total number of support vectors.
/// </summary>
public int SupportVectorCount { get; set; }
/// <summary>
/// The support vectors.
/// </summary>
public Node[][] SupportVectors{get;set;}
/// <summary>
/// The coefficients for the support vectors.
/// </summary>
public double[][] SupportVectorCoefficients{get;set;}
/// <summary>
/// Values in [1,...,num_training_data] to indicate SVs in the training set
/// </summary>
public int[] SupportVectorIndices { get; set; }
/// <summary>
/// Constants in decision functions
/// </summary>
public double[] Rho{get;set;}
/// <summary>
/// First pairwise probability.
/// </summary>
public double[] PairwiseProbabilityA{get;set;}
/// <summary>
/// Second pairwise probability.
/// </summary>
public double[] PairwiseProbabilityB{get;set;}
// for classification only
/// <summary>
/// Class labels.
/// </summary>
public int[] ClassLabels{get;set;}
/// <summary>
/// Number of support vectors per class.
/// </summary>
public int[] NumberOfSVPerClass{get;set;}
public override bool Equals(object obj)
{
Model test = obj as Model;
if (test == null)
return false;
bool same = ClassLabels.IsEqual(test.ClassLabels);
same = same && NumberOfClasses == test.NumberOfClasses;
same = same && NumberOfSVPerClass.IsEqual(test.NumberOfSVPerClass);
if(PairwiseProbabilityA != null)
same = same && PairwiseProbabilityA.IsEqual(test.PairwiseProbabilityA);
if(PairwiseProbabilityB != null)
same = same && PairwiseProbabilityB.IsEqual(test.PairwiseProbabilityB);
same = same && Parameter.Equals(test.Parameter);
same = same && Rho.IsEqual(test.Rho);
same = same && SupportVectorCoefficients.IsEqual(test.SupportVectorCoefficients);
same = same && SupportVectorCount == test.SupportVectorCount;
same = same && SupportVectors.IsEqual(test.SupportVectors);
return same;
}
public override int GetHashCode()
{
return ClassLabels.ComputeHashcode() +
NumberOfClasses.GetHashCode() +
NumberOfSVPerClass.ComputeHashcode() +
PairwiseProbabilityA.ComputeHashcode() +
PairwiseProbabilityB.ComputeHashcode() +
Parameter.GetHashCode() +
Rho.ComputeHashcode() +
SupportVectorCoefficients.ComputeHashcode() +
SupportVectorCount.GetHashCode() +
SupportVectors.ComputeHashcode();
}
/// <summary>
/// Reads a Model from the provided file.
/// </summary>
/// <param name="filename">The name of the file containing the Model</param>
/// <returns>the Model</returns>
public static Model Read(string filename)
{
FileStream input = File.OpenRead(filename);
try
{
return Read(input);
}
finally
{
input.Close();
}
}
/// <summary>
/// Reads a Model from the provided stream.
/// </summary>
/// <param name="stream">The stream from which to read the Model.</param>
/// <returns>the Model</returns>
public static Model Read(Stream stream)
{
TemporaryCulture.Start();
StreamReader input = new StreamReader(stream);
// read parameters
Model model = new Model();
Parameter param = new Parameter();
model.Parameter = param;
model.Rho = null;
model.PairwiseProbabilityA = null;
model.PairwiseProbabilityB = null;
model.ClassLabels = null;
model.NumberOfSVPerClass = null;
bool headerFinished = false;
while (!headerFinished)
{
string line = input.ReadLine();
string cmd, arg;
int splitIndex = line.IndexOf(' ');
if (splitIndex >= 0)
{
cmd = line.Substring(0, splitIndex);
arg = line.Substring(splitIndex + 1);
}
else
{
cmd = line;
arg = "";
}
arg = arg.ToLower();
int i,n;
switch(cmd){
case "svm_type":
param.SvmType = (SvmType)Enum.Parse(typeof(SvmType), arg.ToUpper());
break;
case "kernel_type":
if (arg == "polynomial")
arg = "poly";
param.KernelType = (KernelType)Enum.Parse(typeof(KernelType), arg.ToUpper());
break;
case "degree":
param.Degree = int.Parse(arg);
break;
case "gamma":
param.Gamma = double.Parse(arg);
break;
case "coef0":
param.Coefficient0 = double.Parse(arg);
break;
case "nr_class":
model.NumberOfClasses = int.Parse(arg);
break;
case "total_sv":
model.SupportVectorCount = int.Parse(arg);
break;
case "rho":
n = model.NumberOfClasses * (model.NumberOfClasses - 1) / 2;
model.Rho = new double[n];
string[] rhoParts = arg.Split();
for(i=0; i<n; i++)
model.Rho[i] = double.Parse(rhoParts[i]);
break;
case "label":
n = model.NumberOfClasses;
model.ClassLabels = new int[n];
string[] labelParts = arg.Split();
for (i = 0; i < n; i++)
model.ClassLabels[i] = int.Parse(labelParts[i]);
break;
case "probA":
n = model.NumberOfClasses * (model.NumberOfClasses - 1) / 2;
model.PairwiseProbabilityA = new double[n];
string[] probAParts = arg.Split();
for (i = 0; i < n; i++)
model.PairwiseProbabilityA[i] = double.Parse(probAParts[i]);
break;
case "probB":
n = model.NumberOfClasses * (model.NumberOfClasses - 1) / 2;
model.PairwiseProbabilityB = new double[n];
string[] probBParts = arg.Split();
for (i = 0; i < n; i++)
model.PairwiseProbabilityB[i] = double.Parse(probBParts[i]);
break;
case "nr_sv":
n = model.NumberOfClasses;
model.NumberOfSVPerClass = new int[n];
string[] nrsvParts = arg.Split();
for (i = 0; i < n; i++)
model.NumberOfSVPerClass[i] = int.Parse(nrsvParts[i]);
break;
case "SV":
headerFinished = true;
break;
default:
throw new Exception("Unknown text in model file");
}
}
// read sv_coef and SV
int m = model.NumberOfClasses - 1;
int l = model.SupportVectorCount;
model.SupportVectorCoefficients = new double[m][];
for (int i = 0; i < m; i++)
{
model.SupportVectorCoefficients[i] = new double[l];
}
model.SupportVectors = new Node[l][];
for (int i = 0; i < l; i++)
{
string[] parts = input.ReadLine().Trim().Split();
for (int k = 0; k < m; k++)
model.SupportVectorCoefficients[k][i] = double.Parse(parts[k]);
int n = parts.Length-m;
model.SupportVectors[i] = new Node[n];
for (int j = 0; j < n; j++)
{
string[] nodeParts = parts[m + j].Split(':');
model.SupportVectors[i][j] = new Node();
model.SupportVectors[i][j].Index = int.Parse(nodeParts[0]);
model.SupportVectors[i][j].Value = double.Parse(nodeParts[1]);
}
}
TemporaryCulture.Stop();
return model;
}
/// <summary>
/// Writes a model to the provided filename. This will overwrite any previous data in the file.
/// </summary>
/// <param name="filename">The desired file</param>
/// <param name="model">The Model to write</param>
public static void Write(string filename, Model model)
{
FileStream stream = File.Open(filename, FileMode.Create);
try
{
Write(stream, model);
}
finally
{
stream.Close();
}
}
/// <summary>
/// Writes a model to the provided stream.
/// </summary>
/// <param name="stream">The output stream</param>
/// <param name="model">The model to write</param>
public static void Write(Stream stream, Model model)
{
TemporaryCulture.Start();
StreamWriter output = new StreamWriter(stream);
Parameter param = model.Parameter;
output.Write("svm_type {0}\n", param.SvmType);
output.Write("kernel_type {0}\n", param.KernelType);
if (param.KernelType == KernelType.POLY)
output.Write("degree {0}\n", param.Degree);
if (param.KernelType == KernelType.POLY || param.KernelType == KernelType.RBF || param.KernelType == KernelType.SIGMOID)
output.Write("gamma {0:0.000000}\n", param.Gamma);
if (param.KernelType == KernelType.POLY || param.KernelType == KernelType.SIGMOID)
output.Write("coef0 {0:0.000000}\n", param.Coefficient0);
int nr_class = model.NumberOfClasses;
int l = model.SupportVectorCount;
output.Write("nr_class {0}\n", nr_class);
output.Write("total_sv {0}\n", l);
{
output.Write("rho");
for (int i = 0; i < nr_class * (nr_class - 1) / 2; i++)
output.Write(" {0:0.000000}", model.Rho[i]);
output.Write("\n");
}
if (model.ClassLabels != null)
{
output.Write("label");
for (int i = 0; i < nr_class; i++)
output.Write(" {0}", model.ClassLabels[i]);
output.Write("\n");
}
if (model.PairwiseProbabilityA != null)
// regression has probA only
{
output.Write("probA");
for (int i = 0; i < nr_class * (nr_class - 1) / 2; i++)
output.Write(" {0:0.000000}", model.PairwiseProbabilityA[i]);
output.Write("\n");
}
if (model.PairwiseProbabilityB != null)
{
output.Write("probB");
for (int i = 0; i < nr_class * (nr_class - 1) / 2; i++)
output.Write(" {0:0.000000}", model.PairwiseProbabilityB[i]);
output.Write("\n");
}
if (model.NumberOfSVPerClass != null)
{
output.Write("nr_sv");
for (int i = 0; i < nr_class; i++)
output.Write(" {0}", model.NumberOfSVPerClass[i]);
output.Write("\n");
}
output.Write("SV\n");
double[][] sv_coef = model.SupportVectorCoefficients;
Node[][] SV = model.SupportVectors;
for (int i = 0; i < l; i++)
{
for (int j = 0; j < nr_class - 1; j++)
output.Write("{0:0.000000} ", sv_coef[j][i]);
Node[] p = SV[i];
if (p.Length == 0)
{
output.Write("\n");
continue;
}
if (param.KernelType == KernelType.PRECOMPUTED)
output.Write("0:{0:0.000000}", (int)p[0].Value);
else
{
output.Write("{0}:{1:0.000000}", p[0].Index, p[0].Value);
for (int j = 1; j < p.Length; j++)
output.Write(" {0}:{1:0.000000}", p[j].Index, p[j].Value);
}
output.Write("\n");
}
output.Flush();
TemporaryCulture.Stop();
}
}
}

View file

@ -0,0 +1,117 @@
/*
* SVM.NET Library
* Copyright (C) 2008 Matthew Johnson
*
* 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;
namespace SVM.BotSharp.MachineLearning
{
/// <summary>
/// Encapsulates a node in a Problem vector, with an index and a value (for more efficient representation
/// of sparse data.
/// </summary>
[Serializable]
public class Node : IComparable<Node>
{
internal int _index;
internal double _value;
/// <summary>
/// Default Constructor.
/// </summary>
public Node()
{
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="index">The index of the value.</param>
/// <param name="value">The value to store.</param>
public Node(int index, double value)
{
_index = index;
_value = value;
}
/// <summary>
/// Index of this Node.
/// </summary>
public int Index
{
get
{
return _index;
}
set
{
_index = value;
}
}
/// <summary>
/// Value at Index.
/// </summary>
public double Value
{
get
{
return _value;
}
set
{
_value = value;
}
}
/// <summary>
/// String representation of this Node as {index}:{value}.
/// </summary>
/// <returns>{index}:{value}</returns>
public override string ToString()
{
return string.Format("{0}:{1}", _index, _value.Truncate());
}
public override bool Equals(object obj)
{
Node other = obj as Node;
if(other == null)
return false;
return _index == other._index && _value.Truncate() == other._value.Truncate();
}
public override int GetHashCode()
{
return _index.GetHashCode() + _value.GetHashCode();
}
#region IComparable<Node> Members
/// <summary>
/// Compares this node with another.
/// </summary>
/// <param name="other">The node to compare to</param>
/// <returns>A positive number if this node is greater, a negative number if it is less than, or 0 if equal</returns>
public int CompareTo(Node other)
{
return _index.CompareTo(other._index);
}
#endregion
}
}

View file

@ -0,0 +1,221 @@
/*
* SVM.NET Library
* Copyright (C) 2008 Matthew Johnson
*
* 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.Linq;
using System.Collections.Generic;
namespace SVM.BotSharp.MachineLearning
{
/// <summary>
/// Contains all of the types of SVM this library can model.
/// </summary>
public enum SvmType {
/// <summary>
/// C-SVC.
/// </summary>
C_SVC,
/// <summary>
/// nu-SVC.
/// </summary>
NU_SVC,
/// <summary>
/// one-class SVM
/// </summary>
ONE_CLASS,
/// <summary>
/// epsilon-SVR
/// </summary>
EPSILON_SVR,
/// <summary>
/// nu-SVR
/// </summary>
NU_SVR
};
/// <summary>
/// Contains the various kernel types this library can use.
/// </summary>
public enum KernelType {
/// <summary>
/// Linear: u'*v
/// </summary>
LINEAR,
/// <summary>
/// Polynomial: (gamma*u'*v + coef0)^degree
/// </summary>
POLY,
/// <summary>
/// Radial basis function: exp(-gamma*|u-v|^2)
/// </summary>
RBF,
/// <summary>
/// Sigmoid: tanh(gamma*u'*v + coef0)
/// </summary>
SIGMOID,
/// <summary>
/// Precomputed kernel
/// </summary>
PRECOMPUTED,
};
/// <summary>
/// This class contains the various parameters which can affect the way in which an SVM
/// is learned. Unless you know what you are doing, chances are you are best off using
/// the default values.
/// </summary>
[Serializable]
public class Parameter : ICloneable
{
/// <summary>
/// Default Constructor. Gives good default values to all parameters.
/// </summary>
public Parameter()
{
SvmType = SvmType.C_SVC;
KernelType = KernelType.RBF;
Degree = 3;
Gamma = 0; // 1/k
Coefficient0 = 0;
Nu = 0.5;
CacheSize = 40;
C = 1;
EPS = 1e-3;
P = 0.1;
Shrinking = true;
Probability = false;
Weights = new Dictionary<int, double>();
}
/// <summary>
/// Type of SVM (default C-SVC)
/// </summary>
public SvmType SvmType{get;set;}
/// <summary>
/// Type of kernel function (default Polynomial)
/// </summary>
public KernelType KernelType{get;set;}
/// <summary>
/// Degree in kernel function (default 3).
/// </summary>
public int Degree{get;set;}
/// <summary>
/// Gamma in kernel function (default 1/k)
/// </summary>
public double Gamma{get;set;}
/// <summary>
/// Zeroeth coefficient in kernel function (default 0)
/// </summary>
public double Coefficient0{get;set;}
/// <summary>
/// Cache memory size in MB (default 100)
/// </summary>
public double CacheSize{get;set;}
/// <summary>
/// Tolerance of termination criterion (default 0.001)
/// </summary>
public double EPS{get;set;}
/// <summary>
/// The parameter C of C-SVC, epsilon-SVR, and nu-SVR (default 1)
/// </summary>
public double C{get;set;}
/// <summary>
/// Contains custom weights for class labels. Default weight value is 1.
/// </summary>
public Dictionary<int,double> Weights{get; private set;}
/// <summary>
/// The parameter nu of nu-SVC, one-class SVM, and nu-SVR (default 0.5)
/// </summary>
public double Nu{get;set;}
/// <summary>
/// The epsilon in loss function of epsilon-SVR (default 0.1)
/// </summary>
public double P{get;set;}
/// <summary>
/// Whether to use the shrinking heuristics, (default True)
/// </summary>
public bool Shrinking{get;set;}
/// <summary>
/// Whether to train an SVC or SVR model for probability estimates, (default False)
/// </summary>
public bool Probability{get;set;}
public override bool Equals(object obj)
{
Parameter other = obj as Parameter;
if (other == null)
return false;
return other.C == C &&
other.CacheSize == CacheSize &&
other.Coefficient0 == Coefficient0 &&
other.Degree == Degree &&
other.EPS == EPS &&
other.Gamma == Gamma &&
other.KernelType == KernelType &&
other.Nu == Nu &&
other.P == P &&
other.Probability == Probability &&
other.Shrinking == Shrinking &&
other.SvmType == SvmType &&
other.Weights.ToArray().IsEqual(Weights.ToArray());
}
public override int GetHashCode()
{
return C.GetHashCode() +
CacheSize.GetHashCode() +
Coefficient0.GetHashCode() +
Degree.GetHashCode() +
EPS.GetHashCode() +
Gamma.GetHashCode() +
KernelType.GetHashCode() +
Nu.GetHashCode() +
P.GetHashCode() +
Probability.GetHashCode() +
Shrinking.GetHashCode() +
SvmType.GetHashCode() +
Weights.ToArray().ComputeHashcode();
}
#region ICloneable Members
/// <summary>
/// Creates a memberwise clone of this parameters object.
/// </summary>
/// <returns>The clone (as type Parameter)</returns>
public object Clone()
{
return base.MemberwiseClone();
}
#endregion
}
}

View file

@ -0,0 +1,271 @@
/*
* SVM.NET Library
* Copyright (C) 2008 Matthew Johnson
*
* 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.Threading;
using System.Threading.Tasks;
namespace SVM.BotSharp.MachineLearning
{
/// <summary>
/// Class representing a grid square result.
/// </summary>
public class GridSquare
{
/// <summary>
/// The C value
/// </summary>
public double C;
/// <summary>
/// The Gamma value
/// </summary>
public double Gamma;
/// <summary>
/// The cross validation score
/// </summary>
public double Score;
public override string ToString()
{
return string.Format("{0} {1} {2}", C, Gamma, Score);
}
}
/// <summary>
/// This class contains routines which perform parameter selection for a model which uses C-SVC and
/// an RBF kernel.
/// </summary>
public static class ParameterSelection
{
/// <summary>
/// Default number of times to divide the data.
/// </summary>
public const int NFOLD = 5;
/// <summary>
/// Default minimum power of 2 for the C value (-5)
/// </summary>
public const int MIN_C = -5;
/// <summary>
/// Default maximum power of 2 for the C value (15)
/// </summary>
public const int MAX_C = 15;
/// <summary>
/// Default power iteration step for the C value (2)
/// </summary>
public const int C_STEP = 2;
/// <summary>
/// Default minimum power of 2 for the Gamma value (-15)
/// </summary>
public const int MIN_G = -15;
/// <summary>
/// Default maximum power of 2 for the Gamma Value (3)
/// </summary>
public const int MAX_G = 3;
/// <summary>
/// Default power iteration step for the Gamma value (2)
/// </summary>
public const int G_STEP = 2;
/// <summary>
/// Used to control the degree of parallelism used in grid exploration. Default value is the number of processors.
/// </summary>
public static int Threads = Environment.ProcessorCount;
/// <summary>
/// Returns a logarithmic list of values from minimum power of 2 to the maximum power of 2 using the provided iteration size.
/// </summary>
/// <param name="minPower">The minimum power of 2</param>
/// <param name="maxPower">The maximum power of 2</param>
/// <param name="iteration">The iteration size to use in powers</param>
/// <returns></returns>
public static List<double> GetList(double minPower, double maxPower, double iteration)
{
List<double> list = new List<double>();
for (double d = minPower; d <= maxPower; d += iteration)
list.Add(Math.Pow(2, d));
return list;
}
/// <summary>
/// Performs a Grid parameter selection, trying all possible combinations of the two lists and returning the
/// combination which performed best. The default ranges of C and Gamma values are used. Use this method if there is no validation data available, and it will
/// divide it 5 times to allow 5-fold validation (training on 4/5 and validating on 1/5, 5 times).
/// </summary>
/// <param name="problem">The training data</param>
/// <param name="createParams">The parameters to use when optimizing</param>
/// <param name="report">Function used to report results</param>
/// <param name="C">The optimal C value will be put into this variable</param>
/// <param name="Gamma">The optimal Gamma value will be put into this variable</param>
/// <returns>A list of grid squares and their results</returns>
public static List<GridSquare> Grid(
Problem problem,
Func<Parameter> createParams,
Action<GridSquare> report,
out double C,
out double Gamma)
{
return Grid(problem, createParams, GetList(MIN_C, MAX_C, C_STEP), GetList(MIN_G, MAX_G, G_STEP), report, NFOLD, out C, out Gamma);
}
/// <summary>
/// Performs a Grid parameter selection, trying all possible combinations of the two lists and returning the
/// combination which performed best. Use this method if there is no validation data available, and it will
/// divide it 5 times to allow 5-fold validation (training on 4/5 and validating on 1/5, 5 times).
/// </summary>
/// <param name="problem">The training data</param>
/// <param name="createParams">The parameters to use when optimizing</param>
/// <param name="CValues">The set of C values to use</param>
/// <param name="GammaValues">The set of Gamma values to use</param>
/// <param name="report">Function used to report results</param>
/// <param name="C">The optimal C value will be put into this variable</param>
/// <param name="Gamma">The optimal Gamma value will be put into this variable</param>
/// <returns>A list of grid squares and their results</returns>
public static List<GridSquare> Grid(
Problem problem,
Func<Parameter> createParams,
List<double> CValues,
List<double> GammaValues,
Action<GridSquare> report,
out double C,
out double Gamma)
{
return Grid(problem, createParams, CValues, GammaValues, report, NFOLD, out C, out Gamma);
}
/// <summary>
/// Performs a Grid parameter selection, trying all possible combinations of the two lists and returning the
/// combination which performed best. Use this method if validation data isn't available, as it will
/// divide the training data and train on a portion of it and test on the rest.
/// </summary>
/// <param name="problem">The training data</param>
/// <param name="createParams">The parameters to use when optimizing</param>
/// <param name="CValues">The set of C values to use</param>
/// <param name="GammaValues">The set of Gamma values to use</param>
/// <param name="report">Function used to report results</param>
/// <param name="nrfold">The number of times the data should be divided for validation</param>
/// <param name="C">The optimal C value will be placed in this variable</param>
/// <param name="Gamma">The optimal Gamma value will be placed in this variable</param>
/// <returns>A list of grid squares and their results</returns>
public static List<GridSquare> Grid(
Problem problem,
Func<Parameter> createParams,
List<double> CValues,
List<double> GammaValues,
Action<GridSquare> report,
int nrfold,
out double C,
out double Gamma)
{
C = 0;
Gamma = 0;
List<GridSquare> squares = new List<GridSquare>();
foreach (double testC in CValues)
foreach (double testGamma in GammaValues)
squares.Add(new GridSquare { C = testC, Gamma = testGamma });
ThreadLocal<Parameter> parameters = new ThreadLocal<Parameter>(() => createParams());
Parallel.ForEach(squares, new ParallelOptions{MaxDegreeOfParallelism=Threads}, square =>
{
parameters.Value.C = square.C;
parameters.Value.Gamma = square.Gamma;
square.Score = Training.PerformCrossValidation(problem, parameters.Value, nrfold);
if(report != null)
report(square);
});
GridSquare best = squares.OrderByDescending(o => o.Score).First();
C = best.C;
Gamma = best.Gamma;
return squares.OrderBy(o => o.C).ThenBy(o => o.Gamma).ToList();
}
/// <summary>
/// Performs a Grid parameter selection, trying all possible combinations of the two lists and returning the
/// combination which performed best. Uses the default values of C and Gamma.
/// </summary>
/// <param name="problem">The training data</param>
/// <param name="validation">The validation data</param>
/// <param name="createParams">The parameters to use when optimizing</param>
/// <param name="report">Function used to report results</param>
/// <param name="C">The optimal C value will be placed in this variable</param>
/// <param name="Gamma">The optimal Gamma value will be placed in this variable</param>
/// <returns>A list of grid squares and their results</returns>
public static List<GridSquare> Grid(
Problem problem,
Problem validation,
Func<Parameter> createParams,
Action<GridSquare> report,
out double C,
out double Gamma)
{
return Grid(problem, validation, createParams, GetList(MIN_C, MAX_C, C_STEP), GetList(MIN_G, MAX_G, G_STEP), report, out C, out Gamma);
}
/// <summary>
/// Performs a Grid parameter selection, trying all possible combinations of the two lists and returning the
/// combination which performed best.
/// </summary>
/// <param name="problem">The training data</param>
/// <param name="validation">The validation data</param>
/// <param name="createParams">The parameters to use when optimizing</param>
/// <param name="CValues">The C values to use</param>
/// <param name="GammaValues">The Gamma values to use</param>
/// <param name="report">Function used to report results</param>
/// <param name="C">The optimal C value will be placed in this variable</param>
/// <param name="Gamma">The optimal Gamma value will be placed in this variable</param>
/// <returns>A list of grid squares and their results</returns>
public static List<GridSquare> Grid(
Problem problem,
Problem validation,
Func<Parameter> createParams,
List<double> CValues,
List<double> GammaValues,
Action<GridSquare> report,
out double C,
out double Gamma)
{
C = 0;
Gamma = 0;
List<GridSquare> squares = new List<GridSquare>();
foreach (double testC in CValues)
foreach (double testGamma in GammaValues)
squares.Add(new GridSquare { C = testC, Gamma = testGamma });
ThreadLocal<Parameter> parameters = new ThreadLocal<Parameter>(() => createParams());
Parallel.ForEach(squares, new ParallelOptions { MaxDegreeOfParallelism = Threads }, square =>
{
parameters.Value.C = square.C;
parameters.Value.Gamma = square.Gamma;
Model model = Training.Train(problem, parameters.Value);
square.Score = Prediction.Predict(validation, null, model, false);
if (report != null)
report(square);
});
GridSquare best = squares.OrderByDescending(o => o.Score).First();
C = best.C;
Gamma = best.Gamma;
return squares.OrderBy(o=>o.C).ThenBy(o=>o.Gamma).ToList();
}
}
}

View file

@ -0,0 +1,415 @@
/*
* SVM.NET Library
* Copyright (C) 2008 Matthew Johnson
*
* 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.Globalization;
namespace SVM.BotSharp.MachineLearning
{
/// <summary>
/// Class encoding a member of a ranked set of labels.
/// </summary>
public class RankPair : IComparable<RankPair>
{
private double _score, _label;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="score">Score for this pair</param>
/// <param name="label">Label associated with the given score</param>
public RankPair(double score, double label)
{
_score = score;
_label = label;
}
/// <summary>
/// The score for this pair.
/// </summary>
public double Score
{
get
{
return _score;
}
}
/// <summary>
/// The Label for this pair.
/// </summary>
public double Label
{
get
{
return _label;
}
}
#region IComparable<RankPair> Members
/// <summary>
/// Compares this pair to another. It will end up in a sorted list in decending score order.
/// </summary>
/// <param name="other">The pair to compare to</param>
/// <returns>Whether this should come before or after the argument</returns>
public int CompareTo(RankPair other)
{
return other.Score.CompareTo(Score);
}
#endregion
/// <summary>
/// Returns a string representation of this pair.
/// </summary>
/// <returns>A string in the for Score:Label</returns>
public override string ToString()
{
return string.Format("{0}:{1}", Score, Label);
}
}
/// <summary>
/// Class encoding the point on a 2D curve.
/// </summary>
public class CurvePoint
{
private float _x, _y;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="x">X coordinate</param>
/// <param name="y">Y coordinate</param>
public CurvePoint(float x, float y)
{
_x = x;
_y = y;
}
/// <summary>
/// X coordinate
/// </summary>
public float X
{
get
{
return _x;
}
}
/// <summary>
/// Y coordinate
/// </summary>
public float Y
{
get
{
return _y;
}
}
/// <summary>
/// Creates a string representation of this point.
/// </summary>
/// <returns>string in the form (x, y)</returns>
public override string ToString()
{
return string.Format("({0}, {1})", _x, _y);
}
}
/// <summary>
/// Class which evaluates an SVM model using several standard techniques.
/// </summary>
public class PerformanceEvaluator
{
private class ChangePoint
{
public ChangePoint(int tp, int fp, int tn, int fn)
{
TP = tp;
FP = fp;
TN = tn;
FN = fn;
}
public int TP, FP, TN, FN;
public override string ToString()
{
return string.Format("{0}:{1}:{2}:{3}", TP, FP, TN, FN);
}
}
private List<CurvePoint> _prCurve;
private double _ap;
private List<CurvePoint> _rocCurve;
private double _auc;
private List<RankPair> _data;
private List<ChangePoint> _changes;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="set">A pre-computed ranked pair set</param>
public PerformanceEvaluator(List<RankPair> set)
{
_data = set;
computeStatistics();
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="model">Model to evaluate</param>
/// <param name="problem">Problem to evaluate</param>
/// <param name="category">Label to be evaluate for</param>
public PerformanceEvaluator(Model model, Problem problem, double category) : this(model, problem, category, "tmp.results") { }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="model">Model to evaluate</param>
/// <param name="problem">Problem to evaluate</param>
/// <param name="resultsFile">Results file for output</param>
/// <param name="category">Category to evaluate for</param>
public PerformanceEvaluator(Model model, Problem problem, double category, string resultsFile)
{
Prediction.Predict(problem, resultsFile, model, true);
parseResultsFile(resultsFile, problem.Y, category);
computeStatistics();
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="resultsFile">Results file</param>
/// <param name="correctLabels">The correct labels of each data item</param>
/// <param name="category">The category to evaluate for</param>
public PerformanceEvaluator(string resultsFile, double[] correctLabels, double category)
{
parseResultsFile(resultsFile, correctLabels, category);
computeStatistics();
}
private void parseResultsFile(string resultsFile, double[] labels, double category)
{
StreamReader input = new StreamReader(resultsFile);
string[] parts = input.ReadLine().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
int confidenceIndex = -1;
for (int i = 1; i < parts.Length; i++)
if (double.Parse(parts[i], CultureInfo.InvariantCulture) == category)
{
confidenceIndex = i;
break;
}
_data = new List<RankPair>();
for (int i = 0; i < labels.Length; i++)
{
parts = input.ReadLine().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
double confidence = double.Parse(parts[confidenceIndex], CultureInfo.InvariantCulture);
_data.Add(new RankPair(confidence, labels[i] == category ? 1 : 0));
}
input.Close();
}
private void computeStatistics()
{
_data.Sort();
findChanges();
computePR();
computeRoC();
}
private void findChanges()
{
int tp, fp, tn, fn;
tp = fp = tn = fn = 0;
for (int i = 0; i < _data.Count; i++)
{
if (_data[i].Label == 1)
fn++;
else tn++;
}
_changes = new List<ChangePoint>();
for (int i = 0; i < _data.Count; i++)
{
if (_data[i].Label == 1)
{
tp++;
fn--;
}
else
{
fp++;
tn--;
}
_changes.Add(new ChangePoint(tp, fp, tn, fn));
}
}
private float computePrecision(ChangePoint p)
{
return (float)p.TP / (p.TP + p.FP);
}
private float computeRecall(ChangePoint p)
{
return (float)p.TP / (p.TP + p.FN);
}
private void computePR()
{
_prCurve = new List<CurvePoint>();
_prCurve.Add(new CurvePoint(0, 1));
float precision = computePrecision(_changes[0]);
float recall = computeRecall(_changes[0]);
float precisionSum = 0;
if (_changes[0].TP > 0)
{
precisionSum += precision;
_prCurve.Add(new CurvePoint(recall, precision));
}
for (int i = 1; i < _changes.Count; i++)
{
precision = computePrecision(_changes[i]);
recall = computeRecall(_changes[i]);
if (_changes[i].TP > _changes[i - 1].TP)
{
precisionSum += precision;
_prCurve.Add(new CurvePoint(recall, precision));
}
}
_prCurve.Add(new CurvePoint(1, (float)(_changes[0].TP + _changes[0].FN) / (_changes[0].FP + _changes[0].TN)));
_ap = precisionSum / (_changes[0].FN + _changes[0].TP);
}
/// <summary>
/// Writes the Precision-Recall curve to a tab-delimited file.
/// </summary>
/// <param name="filename">Filename for output</param>
public void WritePRCurve(string filename)
{
StreamWriter output = new StreamWriter(filename);
output.WriteLine(_ap);
for (int i = 0; i < _prCurve.Count; i++)
output.WriteLine("{0}\t{1}", _prCurve[i].X, _prCurve[i].Y);
output.Close();
}
/// <summary>
/// Writes the Receiver Operating Characteristic curve to a tab-delimited file.
/// </summary>
/// <param name="filename">Filename for output</param>
public void WriteROCCurve(string filename)
{
StreamWriter output = new StreamWriter(filename);
output.WriteLine(_auc);
for (int i = 0; i < _rocCurve.Count; i++)
output.WriteLine("{0}\t{1}", _rocCurve[i].X, _rocCurve[i].Y);
output.Close();
}
/// <summary>
/// Receiver Operating Characteristic curve
/// </summary>
public List<CurvePoint> ROCCurve
{
get
{
return _rocCurve;
}
}
/// <summary>
/// Returns the area under the ROC Curve
/// </summary>
public double AuC
{
get
{
return _auc;
}
}
/// <summary>
/// Precision-Recall curve
/// </summary>
public List<CurvePoint> PRCurve
{
get
{
return _prCurve;
}
}
/// <summary>
/// The average precision
/// </summary>
public double AP
{
get
{
return _ap;
}
}
private float computeTPR(ChangePoint cp)
{
return computeRecall(cp);
}
private float computeFPR(ChangePoint cp)
{
return (float)cp.FP / (cp.FP + cp.TN);
}
private void computeRoC()
{
_rocCurve = new List<CurvePoint>();
_rocCurve.Add(new CurvePoint(0, 0));
float tpr = computeTPR(_changes[0]);
float fpr = computeFPR(_changes[0]);
_rocCurve.Add(new CurvePoint(fpr, tpr));
_auc = 0;
for (int i = 1; i < _changes.Count; i++)
{
float newTPR = computeTPR(_changes[i]);
float newFPR = computeFPR(_changes[i]);
if (_changes[i].TP > _changes[i - 1].TP)
{
_auc += tpr * (newFPR - fpr) + .5 * (newTPR - tpr) * (newFPR - fpr);
tpr = newTPR;
fpr = newFPR;
_rocCurve.Add(new CurvePoint(fpr, tpr));
}
}
_rocCurve.Add(new CurvePoint(1, 1));
_auc += tpr * (1 - fpr) + .5 * (1 - tpr) * (1 - fpr);
}
}
}

View file

@ -0,0 +1,117 @@
/*
* SVM.NET Library
* Copyright (C) 2008 Matthew Johnson
*
* 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;
namespace SVM.BotSharp.MachineLearning
{
/// <summary>
/// Class encapsulating a precomputed kernel, where each position indicates the similarity score for two items in the training data.
/// </summary>
[Serializable]
public class PrecomputedKernel
{
private float[,] _similarities;
private int _rows;
private int _columns;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="similarities">The similarity scores between all items in the training data</param>
public PrecomputedKernel(float[,] similarities)
{
_similarities = similarities;
_rows = _similarities.GetLength(0);
_columns = _similarities.GetLength(1);
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="nodes">Nodes for self-similarity analysis</param>
/// <param name="param">Parameters to use when computing similarities</param>
public PrecomputedKernel(List<Node[]> nodes, Parameter param)
{
_rows = nodes.Count;
_columns = _rows;
_similarities = new float[_rows, _columns];
for (int r = 0; r < _rows; r++)
{
for (int c = 0; c < r; c++)
_similarities[r, c] = _similarities[c, r];
_similarities[r, r] = 1;
for (int c = r + 1; c < _columns; c++)
_similarities[r, c] = (float)Kernel.KernelFunction(nodes[r], nodes[c], param);
}
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="rows">Nodes to use as the rows of the matrix</param>
/// <param name="columns">Nodes to use as the columns of the matrix</param>
/// <param name="param">Parameters to use when compute similarities</param>
public PrecomputedKernel(List<Node[]> rows, List<Node[]> columns, Parameter param)
{
_rows = rows.Count;
_columns = columns.Count;
_similarities = new float[_rows, _columns];
for (int r = 0; r < _rows; r++)
for (int c = 0; c < _columns; c++)
_similarities[r, c] = (float)Kernel.KernelFunction(rows[r], columns[c], param);
}
/// <summary>
/// Constructs a <see cref="Problem"/> object using the labels provided. If a label is set to "0" that item is ignored.
/// </summary>
/// <param name="rowLabels">The labels for the row items</param>
/// <param name="columnLabels">The labels for the column items</param>
/// <returns>A <see cref="Problem"/> object</returns>
public Problem Compute(double[] rowLabels, double[] columnLabels)
{
List<Node[]> X = new List<Node[]>();
List<double> Y = new List<double>();
int maxIndex = 0;
for (int i = 0; i < columnLabels.Length; i++)
if (columnLabels[i] != 0)
maxIndex++;
maxIndex++;
for (int r = 0; r < _rows; r++)
{
if (rowLabels[r] == 0)
continue;
List<Node> nodes = new List<Node>();
nodes.Add(new Node(0, X.Count + 1));
for (int c = 0; c < _columns; c++)
{
if (columnLabels[c] == 0)
continue;
double value = _similarities[r, c];
nodes.Add(new Node(nodes.Count, value));
}
X.Add(nodes.ToArray());
Y.Add(rowLabels[r]);
}
return new Problem(X.Count, Y.ToArray(), X.ToArray(), maxIndex);
}
}
}

View file

@ -0,0 +1,214 @@
/*
* SVM.NET Library
* Copyright (C) 2008 Matthew Johnson
*
* 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.Linq;
using System.IO;
using System.Diagnostics;
namespace SVM.BotSharp.MachineLearning
{
/// <summary>
/// Class containing the routines to perform class membership prediction using a trained SVM.
/// </summary>
public static class Prediction
{
/// <summary>
/// Predicts the class memberships of all the vectors in the problem.
/// </summary>
/// <param name="problem">The SVM Problem to solve</param>
/// <param name="outputFile">File for result output</param>
/// <param name="model">The Model to use</param>
/// <param name="predict_probability">Whether to output a distribution over the classes</param>
/// <returns>Percentage correctly labelled</returns>
public static double Predict(
Problem problem,
string outputFile,
Model model,
bool predict_probability)
{
int correct = 0;
int total = 0;
double error = 0;
double sumv = 0, sumy = 0, sumvv = 0, sumyy = 0, sumvy = 0;
StreamWriter output = outputFile != null ? new StreamWriter(outputFile) : null;
SvmType svm_type = Procedures.svm_get_svm_type(model);
int nr_class = Procedures.svm_get_nr_class(model);
int[] labels = new int[nr_class];
double[] prob_estimates = null;
if (predict_probability)
{
if (svm_type == SvmType.EPSILON_SVR || svm_type == SvmType.NU_SVR)
{
Console.WriteLine("Prob. model for test data: target value = predicted value + z,\nz: Laplace distribution e^(-|z|/sigma)/(2sigma),sigma=" + Procedures.svm_get_svr_probability(model));
}
else
{
Procedures.svm_get_labels(model, labels);
prob_estimates = new double[nr_class];
if (output != null)
{
output.Write("labels");
for (int j = 0; j < nr_class; j++)
{
output.Write(" " + labels[j]);
}
output.Write("\n");
}
}
}
for (int i = 0; i < problem.Count; i++)
{
double target = problem.Y[i];
Node[] x = problem.X[i];
double v;
if (predict_probability && (svm_type == SvmType.C_SVC || svm_type == SvmType.NU_SVC))
{
v = Procedures.svm_predict_probability(model, x, prob_estimates);
if (output != null)
{
output.Write(v + " ");
for (int j = 0; j < nr_class; j++)
{
output.Write(prob_estimates[j] + " ");
}
output.Write("\n");
}
}
else
{
v = Procedures.svm_predict(model, x);
if(output != null)
output.Write(v + "\n");
}
if (v == target)
++correct;
error += (v - target) * (v - target);
sumv += v;
sumy += target;
sumvv += v * v;
sumyy += target * target;
sumvy += v * target;
++total;
}
if(output != null)
output.Close();
if (model.Parameter.SvmType == SvmType.EPSILON_SVR || model.Parameter.SvmType == SvmType.NU_SVR)
return error / total;
else return (double)correct / total;
}
/// <summary>
/// Predict the labels for all the data points in a problem.
/// </summary>
/// <param name="model">The model to use</param>
/// <param name="problem">The problem to solve</param>
/// <returns>The predicted labels</returns>
public static double[] PredictLabels(this Model model, Problem problem)
{
return problem.X.Select(o => model.Predict(o)).ToArray();
}
/// <summary>
/// Predict the probability distributions over all labels for each data point in a problem.
/// </summary>
/// <param name="model">The model to use</param>
/// <param name="problem">The problem to solve</param>
/// <returns>A distribution over labels for each data point</returns>
public static double[][] PredictLabelsProbability(this Model model, Problem problem)
{
return problem.X.Select(o => model.PredictProbability(o)).ToArray();
}
/// <summary>
/// Predict the class for a single input vector.
/// </summary>
/// <param name="model">The Model to use for prediction</param>
/// <param name="x">The vector for which to predict class</param>
/// <returns>The result</returns>
public static double Predict(this Model model, Node[] x)
{
return Procedures.svm_predict(model, x);
}
/// <summary>
/// Predicts a class distribution for the single input vector.
/// </summary>
/// <param name="model">Model to use for prediction</param>
/// <param name="x">The vector for which to predict the class distribution</param>
/// <returns>A probability distribtion over classes</returns>
public static double[] PredictProbability(this Model model, Node[] x)
{
SvmType svm_type = Procedures.svm_get_svm_type(model);
if (svm_type != SvmType.C_SVC && svm_type != SvmType.NU_SVC)
throw new Exception("Model type " + svm_type + " unable to predict probabilities.");
int nr_class = Procedures.svm_get_nr_class(model);
double[] probEstimates = new double[nr_class];
Procedures.svm_predict_probability(model, x, probEstimates);
return probEstimates;
}
private static void exit_with_help()
{
Debug.Write("usage: svm_predict [options] test_file model_file output_file\n" + "options:\n" + "-b probability_estimates: whether to predict probability estimates, 0 or 1 (default 0); one-class SVM not supported yet\n");
Environment.Exit(1);
}
/// <summary>
/// Legacy method, provided to allow usage as though this were the command line version of libsvm.
/// </summary>
/// <param name="args">Standard arguments passed to the svm_predict exectutable. See libsvm documentation for details.</param>
[Obsolete("Use the other version of Predict() instead")]
public static void Predict(params string[] args)
{
int i = 0;
bool predictProbability = false;
// parse options
for (i = 0; i < args.Length; i++)
{
if (args[i][0] != '-')
break;
++i;
switch (args[i - 1][1])
{
case 'b':
predictProbability = int.Parse(args[i]) == 1;
break;
default:
throw new ArgumentException("Unknown option");
}
}
if (i >= args.Length)
throw new ArgumentException("No input, model and output files provided");
Problem problem = Problem.Read(args[i]);
Model model = Model.Read(args[i + 1]);
Predict(problem, args[i + 2], model, predictProbability);
}
}
}

View file

@ -0,0 +1,190 @@
/*
* SVM.NET Library
* Copyright (C) 2008 Matthew Johnson
*
* 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.IO;
using System.Collections.Generic;
using System.Threading;
using System.Globalization;
namespace SVM.BotSharp.MachineLearning
{
/// <summary>
/// Encapsulates a problem, or set of vectors which must be classified.
/// </summary>
[Serializable]
public class Problem
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="count">Number of vectors</param>
/// <param name="y">The class labels</param>
/// <param name="x">Vector data.</param>
/// <param name="maxIndex">Maximum index for a vector</param>
public Problem(int count, double[] y, Node[][] x, int maxIndex)
{
Count = count;
Y = y;
X = x;
MaxIndex = maxIndex;
}
/// <summary>
/// Empty Constructor. Nothing is initialized.
/// </summary>
public Problem()
{
}
/// <summary>
/// Number of vectors.
/// </summary>
public int Count { get; set; }
/// <summary>
/// Class labels.
/// </summary>
public double[] Y { get; set; }
/// <summary>
/// Vector data.
/// </summary>
public Node[][] X { get; set; }
/// <summary>
/// Maximum index for a vector.
/// </summary>
public int MaxIndex { get; set; }
public override bool Equals(object obj)
{
Problem other = obj as Problem;
if (other == null)
return false;
return other.Count == Count &&
other.MaxIndex == MaxIndex &&
other.X.IsEqual(X) &&
other.Y.IsEqual(Y);
}
public override int GetHashCode()
{
return Count.GetHashCode() +
MaxIndex.GetHashCode() +
X.ComputeHashcode() +
Y.ComputeHashcode();
}
/// <summary>
/// Reads a problem from a stream.
/// </summary>
/// <param name="stream">Stream to read from</param>
/// <returns>The problem</returns>
public static Problem Read(Stream stream)
{
TemporaryCulture.Start();
StreamReader input = new StreamReader(stream);
List<double> vy = new List<double>();
List<Node[]> vx = new List<Node[]>();
int max_index = 0;
while (input.Peek() > -1)
{
string[] parts = input.ReadLine().Trim().Split();
vy.Add(double.Parse(parts[0]));
int m = parts.Length - 1;
Node[] x = new Node[m];
for (int j = 0; j < m; j++)
{
x[j] = new Node();
string[] nodeParts = parts[j + 1].Split(':');
x[j].Index = int.Parse(nodeParts[0]);
x[j].Value = double.Parse(nodeParts[1]);
}
if (m > 0)
max_index = Math.Max(max_index, x[m - 1].Index);
vx.Add(x);
}
TemporaryCulture.Stop();
return new Problem(vy.Count, vy.ToArray(), vx.ToArray(), max_index);
}
/// <summary>
/// Writes a problem to a stream.
/// </summary>
/// <param name="stream">The stream to write the problem to.</param>
/// <param name="problem">The problem to write.</param>
public static void Write(Stream stream, Problem problem)
{
TemporaryCulture.Start();
StreamWriter output = new StreamWriter(stream);
for (int i = 0; i < problem.Count; i++)
{
output.Write(problem.Y[i]);
for (int j = 0; j < problem.X[i].Length; j++)
output.Write(" {0}:{1:0.000000}", problem.X[i][j].Index, problem.X[i][j].Value);
output.Write("\n");
}
output.Flush();
TemporaryCulture.Stop();
}
/// <summary>
/// Reads a Problem from a file.
/// </summary>
/// <param name="filename">The file to read from.</param>
/// <returns>the Probem</returns>
public static Problem Read(string filename)
{
FileStream input = File.OpenRead(filename);
try
{
return Read(input);
}
finally
{
input.Close();
}
}
/// <summary>
/// Writes a problem to a file. This will overwrite any previous data in the file.
/// </summary>
/// <param name="filename">The file to write to</param>
/// <param name="problem">The problem to write</param>
public static void Write(string filename, Problem problem)
{
FileStream output = File.Open(filename, FileMode.Create);
try
{
Write(output, problem);
}
finally
{
output.Close();
}
}
}
}

View file

@ -0,0 +1,245 @@
/*
* SVM.NET Library
* Copyright (C) 2008 Matthew Johnson
*
* 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.IO;
using System.Threading;
using System.Globalization;
namespace SVM.BotSharp.MachineLearning
{
/// <summary>
/// Class which encapsulates a range transformation.
/// </summary>
public class RangeTransform : IRangeTransform
{
/// <summary>
/// Default lower bound for scaling (-1).
/// </summary>
public const int DEFAULT_LOWER_BOUND = -1;
/// <summary>
/// Default upper bound for scaling (1).
/// </summary>
public const int DEFAULT_UPPER_BOUND = 1;
/// <summary>
/// Determines the Range transform for the provided problem. Uses the default lower and upper bounds.
/// </summary>
/// <param name="prob">The Problem to analyze</param>
/// <returns>The Range transform for the problem</returns>
public static RangeTransform Compute(Problem prob)
{
return Compute(prob, DEFAULT_LOWER_BOUND, DEFAULT_UPPER_BOUND);
}
/// <summary>
/// Determines the Range transform for the provided problem.
/// </summary>
/// <param name="prob">The Problem to analyze</param>
/// <param name="lowerBound">The lower bound for scaling</param>
/// <param name="upperBound">The upper bound for scaling</param>
/// <returns>The Range transform for the problem</returns>
public static RangeTransform Compute(Problem prob, double lowerBound, double upperBound)
{
double[] minVals = new double[prob.MaxIndex];
double[] maxVals = new double[prob.MaxIndex];
for (int i = 0; i < prob.MaxIndex; i++)
{
minVals[i] = double.MaxValue;
maxVals[i] = double.MinValue;
}
for (int i = 0; i < prob.Count; i++)
{
for (int j = 0; j < prob.X[i].Length; j++)
{
int index = prob.X[i][j].Index - 1;
double value = prob.X[i][j].Value;
minVals[index] = Math.Min(minVals[index], value);
maxVals[index] = Math.Max(maxVals[index], value);
}
}
for (int i = 0; i < prob.MaxIndex; i++)
{
if (minVals[i] == double.MaxValue || maxVals[i] == double.MinValue)
{
minVals[i] = 0;
maxVals[i] = 0;
}
}
return new RangeTransform(minVals, maxVals, lowerBound, upperBound);
}
private double[] _inputStart;
private double[] _inputScale;
private double _outputStart;
private double _outputScale;
private int _length;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="minValues">The minimum values in each dimension.</param>
/// <param name="maxValues">The maximum values in each dimension.</param>
/// <param name="lowerBound">The desired lower bound for all dimensions.</param>
/// <param name="upperBound">The desired upper bound for all dimensions.</param>
public RangeTransform(double[] minValues, double[] maxValues, double lowerBound, double upperBound)
{
_length = minValues.Length;
if(maxValues.Length != _length)
throw new Exception("Number of max and min values must be equal.");
_inputStart = new double[_length];
_inputScale = new double[_length];
for (int i = 0; i < _length; i++)
{
_inputStart[i] = minValues[i];
_inputScale[i] = maxValues[i] - minValues[i];
}
_outputStart = lowerBound;
_outputScale = upperBound - lowerBound;
}
private RangeTransform(double[] inputStart, double[] inputScale, double outputStart, double outputScale, int length)
{
_inputStart = inputStart;
_inputScale = inputScale;
_outputStart = outputStart;
_outputScale = outputScale;
_length = length;
}
/// <summary>
/// Transforms the input array based upon the values provided.
/// </summary>
/// <param name="input">The input array</param>
/// <returns>A scaled array</returns>
public Node[] Transform(Node[] input)
{
Node[] output = new Node[input.Length];
for (int i = 0; i < output.Length; i++)
{
int index = input[i].Index;
double value = input[i].Value;
output[i] = new Node(index, Transform(value, index));
}
return output;
}
/// <summary>
/// Transforms this an input value using the scaling transform for the provided dimension.
/// </summary>
/// <param name="input">The input value to transform</param>
/// <param name="index">The dimension whose scaling transform should be used</param>
/// <returns>The scaled value</returns>
public double Transform(double input, int index)
{
index--;
double tmp = input - _inputStart[index];
if (_inputScale[index] == 0)
return 0;
tmp /= _inputScale[index];
tmp *= _outputScale;
return tmp + _outputStart;
}
/// <summary>
/// Writes this Range transform to a stream.
/// </summary>
/// <param name="stream">The stream to write to</param>
/// <param name="r">The range to write</param>
public static void Write(Stream stream, RangeTransform r)
{
TemporaryCulture.Start();
StreamWriter output = new StreamWriter(stream);
output.WriteLine(r._length);
output.Write(r._inputStart[0]);
for(int i=1; i<r._inputStart.Length; i++)
output.Write(" " + r._inputStart[i]);
output.WriteLine();
output.Write(r._inputScale[0]);
for (int i = 1; i < r._inputScale.Length; i++)
output.Write(" " + r._inputScale[i]);
output.WriteLine();
output.WriteLine("{0} {1}", r._outputStart, r._outputScale);
output.Flush();
TemporaryCulture.Stop();
}
/// <summary>
/// Writes this Range transform to a file. This will overwrite any previous data in the file.
/// </summary>
/// <param name="outputFile">The file to write to</param>
/// <param name="r">The Range to write</param>
public static void Write(string outputFile, RangeTransform r)
{
FileStream s = File.Open(outputFile, FileMode.Create);
try
{
Write(s, r);
}
finally
{
s.Close();
}
}
/// <summary>
/// Reads a Range transform from a file.
/// </summary>
/// <param name="inputFile">The file to read from</param>
/// <returns>The Range transform</returns>
public static RangeTransform Read(string inputFile)
{
FileStream s = File.OpenRead(inputFile);
try
{
return Read(s);
}
finally
{
s.Close();
}
}
/// <summary>
/// Reads a Range transform from a stream.
/// </summary>
/// <param name="stream">The stream to read from</param>
/// <returns>The Range transform</returns>
public static RangeTransform Read(Stream stream)
{
TemporaryCulture.Start();
StreamReader input = new StreamReader(stream);
int length = int.Parse(input.ReadLine());
double[] inputStart = new double[length];
double[] inputScale = new double[length];
string[] parts = input.ReadLine().Split();
for (int i = 0; i < length; i++)
inputStart[i] = double.Parse(parts[i]);
parts = input.ReadLine().Split();
for (int i = 0; i < length; i++)
inputScale[i] = double.Parse(parts[i]);
parts = input.ReadLine().Split();
double outputStart = double.Parse(parts[0]);
double outputScale = double.Parse(parts[1]);
TemporaryCulture.Stop();
return new RangeTransform(inputStart, inputScale, outputStart, outputScale, length);
}
}
}

View file

@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SVM.BotSharp.MachineLearning
{
internal static class SVMExtensions
{
private const double PRECISION = 1000000.0;
public static double Truncate(this double x)
{
return Math.Round(x * PRECISION) / PRECISION;
}
public static void SwapIndex<T>(this T[] list, int i, int j)
{
T tmp = list[i];
list[i] = list[j];
list[j] = tmp;
}
public static bool IsEqual<T>(this T[][] lhs, T[][] rhs)
{
if (lhs.Length != rhs.Length)
return false;
for (int i = 0; i < lhs.Length; i++)
{
if (!lhs[i].IsEqual(rhs[i]))
return false;
}
return true;
}
public static bool IsEqual<T>(this T[] lhs, T[] rhs)
{
if (lhs.Length != rhs.Length)
return false;
for (int i = 0; i < lhs.Length; i++)
if (!lhs[i].Equals(rhs[i]))
return false;
return true;
}
public static bool IsEqual(this double[] lhs, double[] rhs)
{
if (lhs.Length != rhs.Length)
return false;
for (int i = 0; i < lhs.Length; i++)
{
double x = lhs[i].Truncate();
double y = rhs[i].Truncate();
if (x != y)
return false;
}
return true;
}
public static bool IsEqual(this double[][] lhs, double[][] rhs)
{
if (lhs.Length != rhs.Length)
return false;
for (int i = 0; i < lhs.Length; i++)
if (!lhs[i].IsEqual(rhs[i]))
return false;
return true;
}
public static int ComputeHashcode<T>(this T[] array)
{
return array.Sum(o => o.GetHashCode());
}
public static int ComputeHashcode<T>(this T[][] array)
{
return array.Sum(o => o.ComputeHashcode());
}
}
}

View file

@ -0,0 +1,116 @@
using SVM;
using SVM.BotSharp.MachineLearning;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SVM.BotSharp.MachineLearningTest
{
public static class SVMUtilities
{
private const double SCALE = 100;
public const int TRAINING_SEED = 20080524;
public const int TESTING_SEED = 20140407;
public static Problem CreateTwoClassProblem(int count, bool isTraining = true)
{
Problem prob = new Problem();
prob.Count = count;
prob.MaxIndex = 2;
Random rand = new Random(isTraining ? TRAINING_SEED : TESTING_SEED);
// create points on either side of the vertical axis
int positive = count / 2;
List<double> labels = new List<double>();
List<Node[]> data = new List<Node[]>();
for (int i = 0; i < count; i++)
{
double x = rand.NextDouble() * SCALE + 10;
double y = rand.NextDouble() * SCALE - (SCALE * .5);
x = i < positive ? x : -x;
data.Add(new Node[] { new Node(1, x), new Node(2, y) });
labels.Add(i < positive ? 1 : -1);
}
prob.X = data.ToArray();
prob.Y = labels.ToArray();
return prob;
}
public static Problem CreateMulticlassProblem(int numberOfClasses, int count, bool isTraining = true)
{
if (numberOfClasses > 8)
throw new ArgumentException("Number of classes must be < 8");
Problem prob = new Problem();
prob.Count = count;
prob.MaxIndex = 3;
int[] samplesPerClass = new int[numberOfClasses];
double countPerClass = (double)count / numberOfClasses;
double current = countPerClass;
for (int i = 1; i < samplesPerClass.Length; i++)
{
samplesPerClass[i] = (int)current;
current += countPerClass;
samplesPerClass[i - 1] = samplesPerClass[i] - samplesPerClass[i - 1];
}
samplesPerClass[samplesPerClass.Length - 1] = count - samplesPerClass.Last();
int[] xSigns = new int[8] { -1, 1, 1, -1, -1, 1, 1, -1 };
int[] ySigns = new int[8] { 1, 1, -1, -1, 1, 1, -1, -1 };
int[] zSigns = new int[8] { 1, 1, 1, 1, -1, -1, -1, -1 };
Random rand = new Random(isTraining ? TRAINING_SEED : TESTING_SEED);
List<double> labels = new List<double>();
List<Node[]> data = new List<Node[]>();
for (int i = 0; i < numberOfClasses; i++)
{
for (int j = 0; j < samplesPerClass[i]; j++)
{
double x = rand.NextDouble() * SCALE + 10;
double y = rand.NextDouble() * SCALE + 10;
double z = rand.NextDouble() * SCALE + 10;
x *= xSigns[i];
y *= ySigns[i];
z *= zSigns[i];
data.Add(new Node[] { new Node(1, x), new Node(2, y), new Node(3, z) });
labels.Add(i);
}
}
prob.X = data.ToArray();
prob.Y = labels.ToArray();
return prob;
}
public static Problem CreateRegressionProblem(int count, bool isTraining = true)
{
Problem prob = new Problem();
prob.Count = count;
prob.MaxIndex = 2;
Random rand = new Random(isTraining ? TRAINING_SEED : TESTING_SEED);
List<double> labels = new List<double>();
List<Node[]> data = new List<Node[]>();
for (int i = 0; i < count; i++)
{
double y = rand.NextDouble() * 10 - 5;
double z = rand.NextDouble() * 10 - 5;
double x = 2 * y + z;
data.Add(new Node[] { new Node(1, y), new Node(2, z) });
labels.Add(x);
}
prob.X = data.ToArray();
prob.Y = labels.ToArray();
return prob;
}
}
}

View file

@ -0,0 +1,49 @@
/*
* SVM.NET Library
* Copyright (C) 2008 Matthew Johnson
*
* 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;
namespace SVM.BotSharp.MachineLearning
{
/// <summary>
/// Deals with the scaling of Problems so they have uniform ranges across all dimensions in order to
/// result in better SVM performance.
/// </summary>
public static class Scaling
{
/// <summary>
/// Scales a problem using the provided range. This will not affect the parameter.
/// </summary>
/// <param name="prob">The problem to scale</param>
/// <param name="range">The Range transform to use in scaling</param>
/// <returns>The Scaled problem</returns>
public static Problem Scale(this IRangeTransform range, Problem prob)
{
Problem scaledProblem = new Problem(prob.Count, new double[prob.Count], new Node[prob.Count][], prob.MaxIndex);
for (int i = 0; i < scaledProblem.Count; i++)
{
scaledProblem.X[i] = new Node[prob.X[i].Length];
for (int j = 0; j < scaledProblem.X[i].Length; j++)
scaledProblem.X[i][j] = new Node(prob.X[i][j].Index, range.Transform(prob.X[i][j].Value, prob.X[i][j].Index));
scaledProblem.Y[i] = prob.Y[i];
}
return scaledProblem;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Threading;
namespace SVM.BotSharp.MachineLearning
{
internal static class TemporaryCulture
{
private static CultureInfo _culture;
public static void Start()
{
_culture = Thread.CurrentThread.CurrentCulture;
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
}
public static void Stop()
{
Thread.CurrentThread.CurrentCulture = _culture;
}
}
}

View file

@ -0,0 +1,232 @@
/*
* SVM.NET Library
* Copyright (C) 2008 Matthew Johnson
*
* 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;
namespace SVM.BotSharp.MachineLearning
{
/// <summary>
/// Class containing the routines to train SVM models.
/// </summary>
public static class Training
{
/// <summary>
/// Whether the system will output information to the console during the training process.
/// </summary>
public static bool IsVerbose
{
get
{
return Procedures.IsVerbose;
}
set
{
Procedures.IsVerbose = value;
}
}
private static double doCrossValidation(Problem problem, Parameter parameters, int nr_fold)
{
int i;
double[] target = new double[problem.Count];
Procedures.svm_cross_validation(problem, parameters, nr_fold, target);
int total_correct = 0;
double total_error = 0;
double sumv = 0, sumy = 0, sumvv = 0, sumyy = 0, sumvy = 0;
if (parameters.SvmType == SvmType.EPSILON_SVR || parameters.SvmType == SvmType.NU_SVR)
{
for (i = 0; i < problem.Count; i++)
{
double y = problem.Y[i];
double v = target[i];
total_error += (v - y) * (v - y);
sumv += v;
sumy += y;
sumvv += v * v;
sumyy += y * y;
sumvy += v * y;
}
return(problem.Count * sumvy - sumv * sumy) / (Math.Sqrt(problem.Count * sumvv - sumv * sumv) * Math.Sqrt(problem.Count * sumyy - sumy * sumy));
}
else
for (i = 0; i < problem.Count; i++)
if (target[i] == problem.Y[i])
++total_correct;
return (double)total_correct / problem.Count;
}
public static void SetRandomSeed(int seed)
{
Procedures.setRandomSeed(seed);
}
/// <summary>
/// Legacy. Allows use as if this was svm_train. See libsvm documentation for details on which arguments to pass.
/// </summary>
/// <param name="args"></param>
[Obsolete("Provided only for legacy compatibility, use the other Train() methods")]
public static void Train(params string[] args)
{
Parameter parameters;
Problem problem;
bool crossValidation;
int nrfold;
string modelFilename;
parseCommandLine(args, out parameters, out problem, out crossValidation, out nrfold, out modelFilename);
if (crossValidation)
PerformCrossValidation(problem, parameters, nrfold);
else Model.Write(modelFilename, Train(problem, parameters));
}
/// <summary>
/// Performs cross validation.
/// </summary>
/// <param name="problem">The training data</param>
/// <param name="parameters">The parameters to test</param>
/// <param name="nrfold">The number of cross validations to use</param>
/// <returns>The cross validation score</returns>
public static double PerformCrossValidation(Problem problem, Parameter parameters, int nrfold)
{
string error = Procedures.svm_check_parameter(problem, parameters);
if (error == null)
return doCrossValidation(problem, parameters, nrfold);
else throw new Exception(error);
}
/// <summary>
/// Trains a model using the provided training data and parameters.
/// </summary>
/// <param name="problem">The training data</param>
/// <param name="parameters">The parameters to use</param>
/// <returns>A trained SVM Model</returns>
public static Model Train(Problem problem, Parameter parameters)
{
string error = Procedures.svm_check_parameter(problem, parameters);
if (error == null)
return Procedures.svm_train(problem, parameters);
else throw new Exception(error);
}
private static void parseCommandLine(string[] args, out Parameter parameters, out Problem problem, out bool crossValidation, out int nrfold, out string modelFilename)
{
int i;
parameters = new Parameter();
// default values
crossValidation = false;
nrfold = 0;
// parse options
for (i = 0; i < args.Length; i++)
{
if (args[i][0] != '-')
break;
++i;
switch (args[i - 1][1])
{
case 's':
parameters.SvmType = (SvmType)int.Parse(args[i]);
break;
case 't':
parameters.KernelType = (KernelType)int.Parse(args[i]);
break;
case 'd':
parameters.Degree = int.Parse(args[i]);
break;
case 'g':
parameters.Gamma = double.Parse(args[i]);
break;
case 'r':
parameters.Coefficient0 = double.Parse(args[i]);
break;
case 'n':
parameters.Nu = double.Parse(args[i]);
break;
case 'm':
parameters.CacheSize = double.Parse(args[i]);
break;
case 'c':
parameters.C = double.Parse(args[i]);
break;
case 'e':
parameters.EPS = double.Parse(args[i]);
break;
case 'p':
parameters.P = double.Parse(args[i]);
break;
case 'h':
parameters.Shrinking = int.Parse(args[i]) == 1;
break;
case 'b':
parameters.Probability = int.Parse(args[i]) == 1;
break;
case 'v':
crossValidation = true;
nrfold = int.Parse(args[i]);
if (nrfold < 2)
{
throw new ArgumentException("n-fold cross validation: n must >= 2");
}
break;
case 'w':
parameters.Weights[int.Parse(args[i - 1].Substring(2))] = double.Parse(args[1]);
break;
default:
throw new ArgumentException("Unknown Parameter");
}
}
// determine filenames
if (i >= args.Length)
throw new ArgumentException("No input file specified");
problem = Problem.Read(args[i]);
if (parameters.Gamma == 0)
parameters.Gamma = 1.0 / problem.MaxIndex;
if (i < args.Length - 1)
modelFilename = args[i + 1];
else
{
int p = args[i].LastIndexOf('/') + 1;
modelFilename = args[i].Substring(p) + ".model";
}
}
}
}

View file

@ -18,7 +18,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DevExpress.Xpo" Version="18.1.4" />
<ProjectReference Include="..\BotSharp.MachineLearning\BotSharp.MachineLearning.csproj" />
</ItemGroup>
<ItemGroup>

View file

@ -1,4 +1,22 @@
using System;
/*
* BotSharp.NLP Library
* Copyright (C) 2018 Haiping Chen
*
* 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.Linq;
using System.Text;

View file

@ -1,4 +1,22 @@
using System;
/*
* BotSharp.NLP Library
* Copyright (C) 2018 Haiping Chen
*
* 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.Linq;
using System.Text;

View file

@ -1,4 +1,22 @@
using System;
/*
* BotSharp.NLP Library
* Copyright (C) 2018 Haiping Chen
*
* 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.Linq;
using System.Text;

View file

@ -1,7 +1,4 @@
.. image:: https://raw.githubusercontent.com/Oceania2018/BotSharp/master/BotSharp.WebHost/wwwroot/images/BotSharp.png
:height: 30px
The Open Source AI Bot Platform Builder for Enterprise
The Open Source AI Bot Platform Builder for Enterprise
======================================================
*"Conversation as a platform (CaaP) is the future, so it's perfect that we're already offering the whole toolkits to our enterprise developers using the BotSharp Bot Platform Builder to build a CaaP. It opens up as much learning power as possible for your enterprise robots and precisely control every step of the AI processing pipeline."*

View file

@ -3,8 +3,9 @@
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to BotSharp's documentation!
====================================
.. image:: https://raw.githubusercontent.com/Oceania2018/BotSharp/master/BotSharp.WebHost/wwwroot/images/BotSharp.png
:height: 30px
.. include:: ../README.rst
.. toctree::