378 lines
12 KiB
C#
378 lines
12 KiB
C#
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Linq;
|
|||
|
|
using System.Text;
|
|||
|
|
using System.Text.RegularExpressions;
|
|||
|
|
using System.IO;
|
|||
|
|
|
|||
|
|
namespace BotSharp.Models.CRFLite.Utils
|
|||
|
|
{
|
|||
|
|
public class VQCluster : IComparer<VQCluster>
|
|||
|
|
{
|
|||
|
|
public int iStart, iEnd;
|
|||
|
|
public double variance, mean;
|
|||
|
|
|
|||
|
|
public VQCluster(double m, double v, int i, int j)
|
|||
|
|
{
|
|||
|
|
mean = m;
|
|||
|
|
variance = v;
|
|||
|
|
iStart = i;
|
|||
|
|
iEnd = j;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public VQCluster() { }
|
|||
|
|
|
|||
|
|
public int Compare(VQCluster X, VQCluster Y)
|
|||
|
|
{
|
|||
|
|
if (X.mean > Y.mean) return 1;
|
|||
|
|
if (X.mean < Y.mean) return -1;
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
|
|||
|
|
public class VectorQuantization
|
|||
|
|
{
|
|||
|
|
protected List<VQCluster> vqClusters;
|
|||
|
|
protected double[] codebook;
|
|||
|
|
protected VarBigArray<double> dataSet;
|
|||
|
|
protected int dataSetSize;
|
|||
|
|
|
|||
|
|
public double[] CodeBook { get { return codebook; } }
|
|||
|
|
|
|||
|
|
public VectorQuantization()
|
|||
|
|
{
|
|||
|
|
dataSet = new VarBigArray<double>(1024 * 1024);
|
|||
|
|
dataSetSize = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Add a set of data into data set
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="values"></param>
|
|||
|
|
public void Add(double[] values)
|
|||
|
|
{
|
|||
|
|
foreach (double value in values)
|
|||
|
|
{
|
|||
|
|
Add(value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Add a single data into data set
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="value"></param>
|
|||
|
|
public void Add(double value)
|
|||
|
|
{
|
|||
|
|
dataSet[dataSetSize] = value;
|
|||
|
|
dataSetSize++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public int ComputeVQ(double value)
|
|||
|
|
{
|
|||
|
|
return BinarySearch(value);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Build codebook according given data set
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="vqSize"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
public double BuildCodebook(int vqSize)
|
|||
|
|
{
|
|||
|
|
if (vqSize > dataSetSize)
|
|||
|
|
{
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
dataSet.Sort(0, dataSetSize);
|
|||
|
|
|
|||
|
|
//Set entire data as a single cluster, and then split it
|
|||
|
|
double mean, var;
|
|||
|
|
ComputeVariables(0, dataSetSize - 1, out mean, out var);
|
|||
|
|
VQCluster c = new VQCluster(mean, var, 0, dataSetSize - 1);
|
|||
|
|
vqClusters = new List<VQCluster>();
|
|||
|
|
vqClusters.Add(c);
|
|||
|
|
|
|||
|
|
//Split clusters according its variance values
|
|||
|
|
while (vqClusters.Count < vqSize)
|
|||
|
|
{
|
|||
|
|
int maxVarClusterId = MaxVarianceClusterId();
|
|||
|
|
if (maxVarClusterId < 0) break; // no more to split
|
|||
|
|
|
|||
|
|
//Split the cluster into two and remove the orginal one
|
|||
|
|
SplitCluster(vqClusters[maxVarClusterId].iStart, vqClusters[maxVarClusterId].iEnd, 0, 1);
|
|||
|
|
vqClusters.RemoveAt(maxVarClusterId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//Adjust clusters according their mean values
|
|||
|
|
AdjustCluster();
|
|||
|
|
|
|||
|
|
//Final codebook
|
|||
|
|
vqSize = vqClusters.Count;
|
|||
|
|
codebook = new double[vqSize];
|
|||
|
|
double distortion = 0;
|
|||
|
|
for (int i = 0; i < vqSize; i++)
|
|||
|
|
{
|
|||
|
|
codebook[i] = vqClusters[i].mean;
|
|||
|
|
for (int j = vqClusters[i].iStart; j <= vqClusters[i].iEnd; j++)
|
|||
|
|
{
|
|||
|
|
double diff = dataSet[j] - codebook[i];
|
|||
|
|
distortion += diff * diff;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
distortion = Math.Sqrt(distortion / dataSetSize);
|
|||
|
|
return distortion;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool WriteCodebook(string filename)
|
|||
|
|
{
|
|||
|
|
using (StreamWriter sw = new StreamWriter(filename))
|
|||
|
|
{
|
|||
|
|
sw.WriteLine("Codeword\tMean\tCount");
|
|||
|
|
for (int i = 0; i < codebook.Length; i++)
|
|||
|
|
{
|
|||
|
|
int count = (int)(vqClusters[i].iEnd - vqClusters[i].iStart + 1);
|
|||
|
|
sw.WriteLine("{0,8} {1}\t{2}", i, codebook[i], count);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void ReadCodebook(string filename)
|
|||
|
|
{
|
|||
|
|
using (StreamReader sr = new StreamReader(filename))
|
|||
|
|
{
|
|||
|
|
//Skip column title
|
|||
|
|
sr.ReadLine();
|
|||
|
|
|
|||
|
|
//Read each line
|
|||
|
|
string line = null;
|
|||
|
|
List<double> cb = new List<double>();
|
|||
|
|
while ((line = sr.ReadLine()) != null)
|
|||
|
|
{
|
|||
|
|
string[] words = line.Split();
|
|||
|
|
double mean = 0;
|
|||
|
|
|
|||
|
|
int n = int.Parse(words[0]);
|
|||
|
|
mean = double.Parse(words[1]);
|
|||
|
|
int count = int.Parse(words[2]);
|
|||
|
|
cb.Add(mean);
|
|||
|
|
}
|
|||
|
|
codebook = cb.ToArray<double>();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Adjust cluster boundary according mean values
|
|||
|
|
/// </summary>
|
|||
|
|
void AdjustCluster()
|
|||
|
|
{
|
|||
|
|
int vqsize = vqClusters.Count;
|
|||
|
|
bool updateCluster = true;
|
|||
|
|
double mean, var;
|
|||
|
|
|
|||
|
|
vqClusters.Sort(new VQCluster());
|
|||
|
|
|
|||
|
|
for (int iter = 0; iter < 20 && updateCluster; iter++)
|
|||
|
|
{
|
|||
|
|
updateCluster = false;
|
|||
|
|
for (int i = 1; i < vqsize; i++)
|
|||
|
|
{
|
|||
|
|
int j = (int)vqClusters[i - 1].iEnd;
|
|||
|
|
while (true)
|
|||
|
|
{
|
|||
|
|
double d1 = dataSet[j] - vqClusters[i - 1].mean;
|
|||
|
|
if (d1 <= 0) break;
|
|||
|
|
|
|||
|
|
double d2 = vqClusters[i].mean - dataSet[j];
|
|||
|
|
|
|||
|
|
if (d1 <= d2) break;
|
|||
|
|
j--;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (j < vqClusters[i - 1].iEnd)
|
|||
|
|
{
|
|||
|
|
ComputeVariables((int)vqClusters[i - 1].iStart, j, out mean, out var);
|
|||
|
|
UpdateCluster(i - 1, (int)vqClusters[i - 1].iStart, j, mean, var);
|
|||
|
|
|
|||
|
|
ComputeVariables(j + 1, (int)vqClusters[i].iEnd, out mean, out var);
|
|||
|
|
UpdateCluster(i, j + 1, (int)vqClusters[i].iEnd, mean, var);
|
|||
|
|
|
|||
|
|
updateCluster = true;
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
j = (int)vqClusters[i].iStart;
|
|||
|
|
while (true)
|
|||
|
|
{
|
|||
|
|
double d1 = vqClusters[i].mean - dataSet[j];
|
|||
|
|
if (d1 <= 0) break;
|
|||
|
|
|
|||
|
|
double d2 = dataSet[j] - vqClusters[i - 1].mean;
|
|||
|
|
|
|||
|
|
if (d1 <= d2) break;
|
|||
|
|
j++;
|
|||
|
|
}
|
|||
|
|
if (j > vqClusters[i].iStart)
|
|||
|
|
{
|
|||
|
|
ComputeVariables((int)vqClusters[i - 1].iStart, j - 1, out mean, out var);
|
|||
|
|
UpdateCluster(i - 1, (int)vqClusters[i - 1].iStart, j - 1, mean, var);
|
|||
|
|
|
|||
|
|
ComputeVariables(j, (int)vqClusters[i].iEnd, out mean, out var);
|
|||
|
|
UpdateCluster(i, j, (int)vqClusters[i].iEnd, mean, var);
|
|||
|
|
|
|||
|
|
updateCluster = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Search codebook and get the index which value is the nearest to given value
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="value"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private int BinarySearch(double value)
|
|||
|
|
{
|
|||
|
|
int low = 0, high = codebook.Length, mid = 0;
|
|||
|
|
while (low < high)
|
|||
|
|
{
|
|||
|
|
mid = (int)((high - low) / 2) + low;
|
|||
|
|
if (value > codebook[mid])
|
|||
|
|
low = mid + 1;
|
|||
|
|
else if (value < codebook[mid])
|
|||
|
|
high = mid;
|
|||
|
|
else
|
|||
|
|
return mid;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int cw = mid;
|
|||
|
|
double delta = Math.Abs(value - codebook[cw]);
|
|||
|
|
if (mid + 1 < codebook.Length)
|
|||
|
|
{
|
|||
|
|
double d2 = Math.Abs(value - codebook[mid + 1]);
|
|||
|
|
if (d2 < delta)
|
|||
|
|
{
|
|||
|
|
cw = mid + 1;
|
|||
|
|
delta = d2;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (mid - 1 >= 0)
|
|||
|
|
{
|
|||
|
|
double d2 = Math.Abs(value - codebook[mid - 1]);
|
|||
|
|
if (d2 < delta)
|
|||
|
|
{
|
|||
|
|
cw = mid - 1;
|
|||
|
|
delta = d2;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return cw;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Return the cluster id which has the biggest variance value
|
|||
|
|
/// </summary>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private int MaxVarianceClusterId()
|
|||
|
|
{
|
|||
|
|
double maxVar = -1;
|
|||
|
|
int c = -1;
|
|||
|
|
for (int i = 0; i < vqClusters.Count; i++)
|
|||
|
|
{
|
|||
|
|
if (vqClusters[i].variance > maxVar)
|
|||
|
|
{
|
|||
|
|
maxVar = vqClusters[i].variance;
|
|||
|
|
c = i;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return c;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Computing the mean and variance of given data set
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="iStart"></param>
|
|||
|
|
/// <param name="iEnd"></param>
|
|||
|
|
/// <param name="mean"></param>
|
|||
|
|
/// <param name="variance"></param>
|
|||
|
|
private void ComputeVariables(int iStart, int iEnd, out double mean, out double variance)
|
|||
|
|
{
|
|||
|
|
double sum = 0;
|
|||
|
|
|
|||
|
|
mean = 0;
|
|||
|
|
variance = 0;
|
|||
|
|
for (int i = iStart; i <= iEnd; i++)
|
|||
|
|
sum += dataSet[i];
|
|||
|
|
mean = sum / (iEnd - iStart + 1);
|
|||
|
|
|
|||
|
|
sum = 0;
|
|||
|
|
if (dataSet[iStart] < mean && dataSet[iEnd] > mean)
|
|||
|
|
{
|
|||
|
|
for (int i = iStart; i <= iEnd; i++)
|
|||
|
|
{
|
|||
|
|
double diff = dataSet[i] - mean;
|
|||
|
|
sum += diff * diff;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
variance = sum;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Update given cluster's values
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="index"></param>
|
|||
|
|
/// <param name="iStart"></param>
|
|||
|
|
/// <param name="iEnd"></param>
|
|||
|
|
/// <param name="mean"></param>
|
|||
|
|
/// <param name="variance"></param>
|
|||
|
|
private void UpdateCluster(int index, int iStart, int iEnd, double mean, double variance)
|
|||
|
|
{
|
|||
|
|
vqClusters[index].iStart = iStart;
|
|||
|
|
vqClusters[index].iEnd = iEnd;
|
|||
|
|
vqClusters[index].mean = mean;
|
|||
|
|
vqClusters[index].variance = variance;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Split one cluster into two clusters according its mean
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="iStart"></param>
|
|||
|
|
/// <param name="iEnd"></param>
|
|||
|
|
/// <param name="depth"></param>
|
|||
|
|
/// <param name="maxDepth"></param>
|
|||
|
|
private void SplitCluster(int iStart, int iEnd, int depth, int maxDepth)
|
|||
|
|
{
|
|||
|
|
if (iStart > iEnd)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
double mean, variance;
|
|||
|
|
ComputeVariables(iStart, iEnd, out mean, out variance);
|
|||
|
|
if (depth == maxDepth)
|
|||
|
|
{
|
|||
|
|
VQCluster c = new VQCluster(mean, variance, iStart, iEnd);
|
|||
|
|
vqClusters.Add(c);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
//Split the cluster into two clusters according mean value
|
|||
|
|
int i;
|
|||
|
|
for (i = iStart; i <= iEnd; i++)
|
|||
|
|
{
|
|||
|
|
//The following data will be greater than mean value, so we split it here
|
|||
|
|
if (dataSet[i] > mean)
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
SplitCluster(iStart, i - 1, depth + 1, maxDepth);
|
|||
|
|
SplitCluster(i, iEnd, depth + 1, maxDepth);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|