2015-12-15 22 views
8

Mi piacerebbe sviluppare un algoritmo di autoapprendimento per un problema specifico. Per semplificare le cose, lo renderò semplice nell'esempio.Algoritmo di auto-addestramento

Aggiornamento: Ho aggiunto una soluzione funzionante come risposta a questa domanda di seguito.

Diciamo che ho un enorme elenco di entità provenienti da un database. Ogni entità è dello stesso tipo e ha 4 proprietà di tipo byte.

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

Ora mi piacerebbe testare dinamicamente una o più proprietà di ogni entità contro una semplice condizione. Il che significa fondamentalmente che voglio testare tutte le possibili combinazioni di tutte le proprietà contro questa condizione.

Per fare questo ho creato una maschera di bit per le proprietà.

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

E aggiunto un metodo per ottenere il valore massimo del bit mask. Che restituisce 15 (1 + 2 + 4 + 8) per questo esempio.

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

A questo punto sono in grado di scorrere tutte le combinazioni di proprietà con un semplice ciclo. Nell'esempio della prima iterazione viene testata la proprietà Prop1, nella seconda iterazione viene testata la Prop2, nella terza iterazione Prop1 e Prop2 vengono testati e così via.

for(int i = 1; i <= GetMaxValue<EEntityValues>(); i++) 
{ 
    EEntityValues flags = (EEntityValues)i; 

    if(flags.HasSet(EEntityValues.Prop1)) 
    { 
     .... 
    } 
} 

Ora mettiamo le entità nel gioco.

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; } 
    } 
} 

Bene, questo funziona perfettamente se i miei valori minimi sono statici.

Ora diventiamo più complicati. Come ricordiamo, nella prima iterazione testiamo solo la proprietà Prop1, poiché la maschera bit è 1. L'intervallo di valori per Prop1 è 0..255. Ho anche definito un valore minimo per questa proprietà che ha un intervallo valido di 1,25. Questo valore minimo è solo un filtro per saltare le entità nel ciclo foreach.

Ora mi piacerebbe testare la proprietà Prop1 mentre sto aumentando il livello minimo. Questi test non fanno parte del problema quindi non li includo nei miei campioni.

 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++; 
    } 

Questo è semplice per una singola proprietà. Sulla terza iterazione ho a che fare con 2 immobili, Prop1 e prop2, perché la maschera di bit è 3.

 byte minProp1Value = 1, minProp2Value = 1; 

    while(minProp1Value <= 255) 
    { 
     while(minProp2Value <= 255) 
     { 
       foreach(Entitiy entity in entities) 
       { 
        .... 
       } 

       minProp2Value++; 
     } 

     minProp1Value++; 
     minProp2Value = 1; 
    } 

Come si può vedere, in questa fase sto testando Prop1 e prop2 di ciascuna entità contro un aumento del livello minimo.

Per il motivo che ho a che fare con set di proprietà multiple generate dinamicamente, non posso eseguire l'hardcode dei loop while nel mio codice. Ecco perché sto cercando una soluzione più intelligente per testare tutte le possibili combinazioni di valori minimi per il set di proprietà dato (bit mask).

+0

Non sono sicuro seguo .. Stai dicendo che vuole un modo per testare le combinazioni di (ad esempio) '' prop1' e prop2' invece di tutti e 4 in una volta? – Rob

+1

Inoltre ti rendi conto che ci sono combinazioni di "4.288.250.625" per queste quattro proprietà? – Rob

+0

Quindi vuoi dire che vuoi essere in grado di controllare le combinazioni 'prop1 & prop2', ma anche' prop1 & prop3', (etc, etc) così come tutto in una volta? – Rob

risposta

1

Dopo una pausa ho trovato una soluzione che mi sembra adatta alle mie esigenze. La limitazione è che tutte le proprietà testate dovrebbero essere dello stesso tipo con lo stesso intervallo di valori, che nel mio caso va bene per me perché tutte le proprietà sono valori percentuali astratti.

A proposito, non sono sicuro che l'argomento "algoritmo di autoapprendimento" sia un po 'fuorviante qui. Ci sono un paio di modi per implementare una soluzione del genere, ma se non hai idea di come si comportano i tuoi dati e quali effetti hanno i valori, la soluzione più semplice è quella di forzare tutte le possibili combinazioni per identificare il risultato migliore. Questo è quello che sto mostrando qui.

In ogni caso, a scopo di test ho aggiunto un generatore di numeri casuali alla mia classe di entità.

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]; 
    } 
} 

La mia maschera di bit rimane intatta.

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

Than ho aggiunto alcune nuove Méthodes estensione a che fare con la mia maschera di bit.

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 ho scritto un generatore di query

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; 
    } 
} 

E finalmente sono pronto per eseguire il training.

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; 
     } 
    } 
}