ABONAMENTE VIDEO REDACȚIA
RO
EN
NOU
Numărul 150
Numărul 149 Numărul 148 Numărul 147 Numărul 146 Numărul 145 Numărul 144 Numărul 143 Numărul 142 Numărul 141 Numărul 140 Numărul 139 Numărul 138 Numărul 137 Numărul 136 Numărul 135 Numărul 134 Numărul 133 Numărul 132 Numărul 131 Numărul 130 Numărul 129 Numărul 128 Numărul 127 Numărul 126 Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ LISTĂ EDIȚII ▼
Numărul 59
Abonament PDF

Artemis - o platformă extensibilă de căutare și manipulare de date în limbaj natural

Tămaș Ionuț
Software Developer @ TORA Trading Services



PROGRAMARE


Un domeniu de model bine structurat conține multe informații ce sunt expresive și ușor de înțeles pentru utilizatorul final. De exemplu, o clasă Comandă are o proprietate Client cu semantica: "O comandă este făcută de un client", iar clasa Client conține proprietăți simple precum Nume, Vârstă, Email cu o semantică ușor de înțeles: Un client numit John în vârstă de 30 de ani cu adresa de email john\@email.com. Astfel, observăm că un domeniu de model bine abstractizat aduce după sine în mod gratuit informații de care ne putem folosi atunci când construim experiența finală pentru utilizator (UX).

Majoritatea operațiunilor noastre pe aplicații web pot fi clasificate în mod generic în două categorii: căutare și procesare de anumite entități. În special în sisteme de administrare web, căutarea este implementată în mod tradițional, navigând la o pagină de căutare pentru anumite modele și aplicând filtre (via căsuțe text, combo lists, radio-buttons, etc.) pentru a filtra pe baza unui subset din proprietăți un grid de entități.

Ar fi ideal dacă tot mecanismul de căutare ar consta într-o singură căsuță text ce poate să răspundă, în limbaj natural unor interogări pentru comenzi precum "Comenzi făcute de John cu prețul > 200RON și orașul de livrare Seattle" și să genereze dinamic un grid pe baza acestor căutări cu rezultatele obținute; de asemenea, aceste interogări să poată să fie aplicate pe orice tip de entitate din domeniul nostru.

În acest fel, am putea expune o experiență de căutare mai rapidă pentru utilizatorii noștri și scapa programatorii sistemului de povara de a construi mecanisme de căutare adaptate pentru domeniul sistemului.

Prolog: Definirea problemei și pipeline-ul de procesare

Construirea unei platforme NLP (Natural Language Processing) comparabilă cu înțelegerea unui om este deocamdată o problemă AI-completă (deocamdată imposibilă), însă scopul nostru este de a:

Bazându-ne pe acestea, schițăm următoarea diagramă a pipeline-ului de procesare în Figura 1:

Figura 1 - Pipeline-ul de procesare NLP

Capitolul 1: Etapa de configurare

Etapa de configurare este pasul în care transformăm domeniul de model al sistemului într-un graf de căutare și vom folosi acest graf atunci când procesăm inputul utilizatorului. Am optat pentru o configurare bazată pe adnotări:

[Searchable]
public partial class Order
{
    [NonSearchable]
    public int Id { get; set; }
    public string ShipCity { get; set; }
    public string ShipCountry { get; set; }
    public string ShipAddress { get; set; }
    public string Code { get; set; }
    public decimal TotalPrice { get; set; }

    [NonSearchable]
    public Nullable<int> ShipperId { get; set; }
    [NonSearchable]
    public Nullable<int> EmployeeId { get; set; }
    [NonSearchable]
    public Nullable<int> CustomerId { get; set; }

    [Searchable("managed by", "handled by", 
    "assigned to")]
    public virtual Employee Employee { get; set; }
    [Searchable("shipped by")]
    public virtual Shipper Shipper { get; set; }
    [Searchable("made by")]
    public virtual Customer Customer { get; set; }
}

Am definit trei tipuri de atribute pentru configurarea domeniului:

Prin alias definim o expresie ce reprezintă semantica tip-proprietate. Implicit, toate proprietățile fac parte din mecanismul de căutare cu semantica implicită "Entity with property name" pentru toate proprietățile non-booleene. Dupa adnotarea domeniului, construim un graf ce conține nodurile ca tipuri "interogabile" legate între ele prin muchii ce conțin semantica relațională definită în aliasurile adnotărilor.
Proprietățile booleene merită atenție specială deoarece ele vin în diverse nuanțe naturale. În faza de construcție, extragem un alias implicit pentru fiecare proprietate booleană în funcție de numele ei. Câteva exemple sunt definite în Figura 3 și este prezentată o scurtă clasificare a acestora: Figura 3 - Proprietățile de tip boolean și regulile de transformare

Capitolul 2: Etapa de parsare

Faza de parsare este componenta centrală a frameworkului nostru: aici luăm inputul utilizatorului şi îl procesăm pe baza grafului de căutare pentru a construi o structură de date pe care o vom folosi în următorii paşi ai procesării.

Să vedem nişte exemple de interogări pe care dorim să le parsăm pentru a descoperi regulile nostru:

Pe baza acestor exemple, definim regulile de parsare din Figura 4: Figura 4 - Exemple și regulile de parsare

Figura 5 - State machine-ul parser-ului

Parserul este implementat pe baza diagramei Finite-State Machine, împărţind inputul utilizatorului după spaţii. Structura de date a parserului menţine o stare internă, o stivă a tipurilor identificate şi un arbore al tipurilor de date împreună cu interogări asupra proprietăţilor lor, precum exemplu reprezentat în Figura 6, unde putem vedea cum structura de date îşi schimbă starea în funcţie de cuvintele procesate.

Expresiile booleene sunt mai dificil de procesat deoarece operatorul şi valoarea de comparat sunt absente şi trebuie inferate din semantică relaţiei. Această procesare o facem generând două tipuri de aliasuri pentru fiecare proprietate booleană: versiunea afirmativă şi cea negată, iar în etapa de parsare identificăm versiunea semantică şi tranziționăm direct în starea comparand.

Capitolul 3: Etapa de filtrare de date

După etapa de parsare, construim o expresie generică pentru entitatea de bază urmărind arborele de parsare generat anterior. Pentru construcţia acestei expresii am optat pentru o abordare fluentă, precum în exemplul de mai jos pentru o interogare a unei proprietăţi numerice "mai mare ca", filtre similar fiind implementate asemănător:

public ExpressionBuilder AndGreaterThan(
  string property, int value)
{
  Expression source = GetExpressionBody(property);
  ConstantExpression targetValue = 
    Expression.Constant(value, source.Type); 

  BinaryExpression comparisonExpression = 
    Expression.GreaterThan(source, targetValue);

  _accumulator = Expression.AndAlso(
    _accumulator, comparisonExpression);

    return this;
} 

Pentru interogarea Orders with price > 100 managed by "John", vom folosi construcția:

Expression expression = ExpressionBuilder
               .Empty
               .WithType(typeof(Order))
               .AndGreaterThan("Prie", 100)
               .AndContains("Employee.Name", "John")
               .GetExpression();

unde metoda GetExpression returnează o expresie lambda non-generică pentru tipul specificat. După construcţia expresiei de filtrare, am construit câteva metode ajutătoare (extension methods) pentru a aplica filtrul pe instanţe IEnumerable sau IQueryable:

static IEnumerable<T> WhereBy<T>
 (this IEnumerable<T> collection, Expression filter)

static IEnumerable<object> Where
 (this IQueryable queryable, Expression filter)

Putem folosi aceste metode ajutătoare precum urmează:

IEnumerable<Order> orders = GetOrders();                    
// IEnumerable collection instance

IEnumerable<object> memoryResult = 
  orders.WhereBy(filter);     
// "WhereBy" extension

DbContext context = new ArtemisContext();                      
// EF data context

DbSet queryable = context.Set(typeof(Order));                  
// IQueryable instance

IEnumerable<object> queryableResult = 
  queryable.Where(filter); 
// "Where" extension

Capitolul 4: Etapa de predicție

Pe baza arborelui de parsare, putem arată sugestii utilizatorului pentru a produce o interogare validă. Ne folosim de starea curentă a parsorului şi de cuvintele neidentificate pentru a genera sugestii. De interes aici sunt sugestiile de proprietăţi, unde folosim stiva tipurilor parsate pentru a da o prioritate mai mare a proprietăţilor tipurilor de pe vârful stivei decât celor de jos, deoarece utilizatorii vor aplică filtre pe ultimul tip parsat. Dăm precedenţă proprietăţilor ce nu au fost folosite deja, căci cel mai probabil utilizatorul va aplica un filtru pe proprietate. Figura 6 arată evoluţia entităţilor parsate pe stivă şi arborele de interogare construit pentru interogarea: Orders with price > 100 managed by "John", iar in Figura 1 putem vedea câteva exemple de sugestii.

Epilog: Artemis NLP

Domeniul "Natural language processing" este o zonă foarte complexă în știința calculatoarelor. În acest articol, ne-am concentrat asupra paşilor construirii unui aparat NLP robust, restricţionat oricărui domeniu de model împreună cu o suită de facilităţi de augmentare a semanticii extensibile.

NUMĂRUL 149 - Development with AI

Sponsori

  • Accenture
  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • MHP
  • BoatyardX
  • .msg systems
  • P3 group
  • Ing Hubs
  • Cognizant Softvision
  • Colors in projects