自我训练算法

本文关键字:算法 自我 | 更新日期: 2023-09-27 18:26:53

我想为一个特定的问题开发一个自训练算法。为了使事情简单,我将把它归结为一个简单的例子。

更新:我添加了一个有效的解决方案作为下面这个问题的答案

假设我有一个来自数据库的实体的巨大列表。每个实体都具有相同的类型,并且具有字节类型的4个属性。

public class Entity
{
    public byte Prop1 { get; set; }
    public byte Prop2 { get; set; }
    public byte Prop3 { get; set; }
    public byte Prop4 { get; set; }
}

现在,我想针对一个简单的条件动态测试每个实体的一个或多个属性。这基本上意味着我想针对这个条件测试所有属性的所有可能组合。

为了完成这项工作,我为属性创建了一个位掩码。

[Flags]
public enum EEntityValues
{
    Undefined = 0,
    Prop1 = 1,
    Prop2 = 2,
    Prop3 = 4,
    Prop4 = 8,
}

并添加了一个获取位掩码最大值的方法。对于本例,返回15(1+2+4+8)。

public static int GetMaxValue<T>() where T : struct
{
    return Enum.GetValues( typeof(T) ).Cast<int>().Sum();
}

在这个阶段,我可以用一个简单的循环迭代所有的属性组合。例如,在第一次迭代中,测试属性Prop1,在第二次迭代中测试Prop2,在第三次迭代中检测Prop1和Prop2,依此类推

for(int i = 1; i <= GetMaxValue<EEntityValues>(); i++)
{
     EEntityValues flags = (EEntityValues)i;
     if(flags.HasSet(EEntityValues.Prop1))
     {
         ....
     }
}

现在让我们将实体带入游戏。

List<Entity> entities = GetEntitiesFromDb();
for(int i = 1; i <= GetMaxValue<EEntityValues>(); i++)
{
     EEntityValues flags = (EEntityValues)i;
     byte minProp1Value = 10, minProp2Value = 20, minProp3Value = 30, minProp4Value = 40;
     foreach(Entitiy entity in entities)
     {
         if(flags.HasSet(EEntityValues.Prop1) && entitiy.Prop1 >= minProp1Value)
         {
              ....
         } else { continue; }
         if(flags.HasSet(EEntityValues.Prop2) && entitiy.Prop2 >= minProp2Value)
         {
              ....
         } else { continue; }
     }
}

好吧,如果我的最小值是静态的,这很好。

现在让我们变得更复杂。正如我们所记得的,在第一次迭代中,我们只测试属性Prop1,因为位掩码是1。Prop1的值范围为0..255。我还为这个属性定义了一个最小值,它的有效范围是1..255。这个最小值只是一个过滤器,用于跳过foreach循环中的实体。

现在我想在提升最低级别的同时测试属性Prop1。这些测试不是问题的一部分,所以我不把它们包括在我的样本中。

     byte minProp1Value = 1;
     while(minProp1Value <= 255)
     {
         foreach(Entitiy entity in entities)
         {
              if(flags.HasSet(EEntityValues.Prop1) && entitiy.Prop1 >= minProp1Value)
              {
                  .... // Testing the matching entity and storing the result
              } else { continue; }
         }
         minProp1Value++;
     }

这对于单个属性来说很简单。在第三次迭代中,我必须处理两个属性,Prop1和Prop2,因为位掩码是3。

     byte minProp1Value = 1, minProp2Value = 1;
     while(minProp1Value <= 255)
     {
         while(minProp2Value <= 255)
         {
              foreach(Entitiy entity in entities)
              {
                   ....
              }
              minProp2Value++;
         }
         minProp1Value++;
         minProp2Value = 1;
     }

正如你所看到的,在这个阶段,我正在针对不断上升的最低水平测试每个实体的Prop1和Prop2。

由于我要处理动态生成的多个属性集,所以我不能将while循环硬编码到代码中。这就是为什么我正在寻找一个更智能的解决方案来测试给定属性集(位掩码)的最小值的所有可能组合。

自我训练算法

休息后,我想出了一个似乎符合我要求的解决方案。限制是,所有测试的属性都应该是具有相同值范围的相同类型,这对我来说很好,因为所有属性都是抽象的百分比值。

顺便说一句,我不确定"自我训练算法"这个话题在这里是否有点误导。有几种方法可以实现这样的解决方案,但如果您不知道数据的行为以及值的影响,最简单的解决方案是强行使用所有可能的组合来确定最佳拟合结果。这就是我在这里展示的。

无论如何,出于测试目的,我在实体类中添加了一个随机数生成器。

public class Entity
{
    public byte Prop1 { get; set; }
    public byte Prop2 { get; set; }
    public byte Prop3 { get; set; }
    public byte Prop4 { get; set; }
    public Entity()
    {
        Random random = new Random( Guid.NewGuid().GetHashCode() );
        byte[] bytes = new byte[ 4 ];
        random.NextBytes( bytes );
        this.Prop1 = bytes[0];
        this.Prop2 = bytes[1];
        this.Prop3 = bytes[2];
        this.Prop4 = bytes[3];
    }
}

我的比特掩码保持不变。

[Flags]
public enum EProperty
{
    Undefined = 0,
    Prop1 = 1,
    Prop2 = 1 << 1,
    Prop3 = 1 << 2,
    Prop4 = 1 << 3
}

然后我添加了一些新的扩展方法来处理我的比特掩码。

public static class BitMask
{
    public static int GetMaxValue<T>() where T : struct
    {
        return Enum.GetValues(typeof (T)).Cast<int>().Sum();
    }
    public static int GetTotalCount<T>() where T : struct
    {
        return Enum.GetValues(typeof (T)).Cast<int>().Count(e => e > 0);
    }
    public static int GetFlagCount<T>(this T mask) where T : struct
    {
        int result = 0, value = (int) (object) mask;
        while (value != 0)
        {
            value = value & (value - 1);
            result++;
        }
        return result;
    }
    public static IEnumerable<T> Split<T>(this T mask)
    {
        int maskValue = (int) (object) mask;
        foreach (T flag in Enum.GetValues(typeof (T)))
        {
            int flagValue = (int) (object) flag;
            if (0 != (flagValue & maskValue))
            {
                yield return flag;
            }
        }
    }
}

Than我写了一个查询生成器

public static class QueryBuilder
{
    public static IEnumerable<Entity> Where(this IEnumerable<Entity> entities, EProperty[] properties, int[] values)
    {
        IEnumerable<Entity> result = entities.Select(e => e);
        for (int index = 0; index <= properties.Length - 1; index++)
        {
            EProperty property = properties[index];
            int value = values[index];
            switch (property)
            {
                case EProperty.Prop1:
                    result = result.Where(e => Math.Abs(e.Prop1) >= value);
                    break;
                case EProperty.Prop2:
                    result = result.Where(e => Math.Abs(e.Prop2) >= value);
                    break;
                case EProperty.Prop3:
                    result = result.Where(e => Math.Abs(e.Prop3) >= value);
                    break;              
                case EProperty.Prop4:
                    result = result.Where(e => Math.Abs(e.Prop4) >= value);
                    break;   
            }
        }
        return result;
    }
}

最后,我已经准备好进行训练了。

    private const int maxThreads = 10;
    private const int minValue = 0;
    private const int maxValue = 100;
    private static IEnumerable<Entity> entities;
    public static void Main(string[] args)
    {
        Console.WriteLine(DateTime.Now.ToLongTimeString());
        entities = Enumerable.Repeat(new Entity(), 10).ToList();
        Action<EProperty[], int[]> testCase = RunTestCase;
        RunSelfTraining( testCase );
        Console.WriteLine(DateTime.Now.ToLongTimeString());
        Console.WriteLine("Done.");
        Console.Read();
    }
    private static void RunTestCase( EProperty[] properties, int[] values ) 
    {         
        foreach( Entity entity in entities.Where( properties, values ) )
        {
        }
    }
    private static void RunSelfTraining<T>( Action<T[], int[]> testCase ) where T : struct
    {
        ParallelOptions parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = maxThreads };
        for (int maskValue = 1; maskValue <= BitMask.GetMaxValue<T>(); maskValue++)
        {
            T mask = ( T ) (object)maskValue;
            T[] properties = mask.Split().ToArray();         
            int variations = (int) Math.Pow(maxValue - minValue + 1, properties.Length);
            Parallel.For(1, variations, parallelOptions, variation =>
            {
                int[] values = GetVariation(variation, minValue, maxValue, properties.Length).ToArray();   
                testCase.Invoke(properties, values);        
            } );
        }
    }
    public static IEnumerable<int> GetVariation( int index, int minValue, int maxValue, int count )
    {
        index = index - 1; 
        int range = maxValue - minValue + 1;
        for( int j = 0; j < count; j++ )
        {
            yield return index % range + minValue;
            index = index / range;
        }
    }
}