Riconosci una grammatica in una sequenza di token fuzzy


13

Ho documenti di testo che contengono principalmente elenchi di articoli.

Ogni elemento è un gruppo di più token di diversi tipi: Nome, Cognome, Data di nascita, Numero di telefono, Città, Occupazione, ecc. Un token è un gruppo di parole.

Gli articoli possono trovarsi su più righe.

Gli elementi di un documento hanno circa la stessa sintassi di token, ma non devono necessariamente essere esattamente gli stessi.

Potrebbero esserci dei token più / meno tra gli oggetti e all'interno degli oggetti.

FirstName LastName BirthDate PhoneNumber
Occupation City
FirstName LastName BirthDate PhoneNumber PhoneNumber
Occupation City
FirstName LastName BirthDate PhoneNumber
Occupation UnrecognizedToken
FirstName LastName PhoneNumber
Occupation City
FirstName LastName BirthDate PhoneNumber
City Occupation

L'obiettivo è identificare la grammatica utilizzata, ad es

Occupation City

e alla fine identifica tutti gli oggetti, anche se non coincidono esattamente.

Per rimanere brevi e leggibili, utilizziamo invece alcuni alias A, B, C, D, ... per designare quei tipi di token.

per esempio

A B C
D F
A B C
D E F
F
A B C
D E E F
A C B
D E F
A B D C
D E F
A B C
D E F G

Qui possiamo vedere che la sintassi dell'articolo è

A B C
D E F

perché è quello che corrisponde meglio alla sequenza.

La sintassi (tipi e ordini di token) può variare molto da un documento all'altro. ad esempio un altro documento potrebbe avere quell'elenco

D A
D A
D
D A
B
D A

L'obiettivo è capire quella sintassi senza previa conoscenza di essa .

Da adesso, anche una nuova linea viene considerata come token. Un documento può quindi essere rappresentato come una sequenza di token a 1 dimensione:


Qui la sequenza ripetuta sarebbe A B C Bperché è il token che crea il minor numero di conflitti.

Complessiamo un po '. Da adesso ogni token non ha un tipo determinato. Nel mondo reale, non siamo sempre sicuri al 100% del tipo di token. Invece, gli diamo una probabilità di avere un certo tipo.

  A 0.2    A 0.0    A 0.1
  B 0.5    B 0.5    B 0.9     etc.
  C 0.0    C 0.0    C 0.0
  D 0.3    D 0.5    D 0.0

Ecco un grafico astratto di ciò che voglio ottenere:

Soluzione considerata A: Convoluzione di una patch di token

Questa soluzione consiste nell'applicare una convoluzione con diverse patch di token e prendere quella che crea il minor numero di conflitti.

La parte difficile qui è trovare potenziali patch da rotolare lungo la sequenza di osservazione. Poche idee per questo, ma niente di molto soddisfacente:

Costruisci un modello Markov della transizione tra i token

Svantaggio: poiché un modello Markov non ha memoria, perderemo gli ordini di transizione. Ad esempio, se la sequenza ripetuta è A B C B D, perdiamo il fatto che A-> B si verifica prima di C-> B.

Costruisci un albero di suffissi

Questo sembra essere ampiamente utilizzato in biologia al fine di analizzare le nucleobasi (GTAC) nel DNA / RNA. Svantaggio: gli alberi dei suffissi sono utili per la corrispondenza esatta di token esatti (ad es. Caratteri). Non abbiamo sequenze esatte né token esatti.

Forza bruta

Prova ogni combinazione di ogni dimensione. Potrebbe effettivamente funzionare, ma richiederebbe un po 'di tempo (lungo).

Soluzione considerata B: costruire una tabella delle distanze dei suffissi di Levenshtein

L'intuizione è che potrebbero esistere dei minimi locali di distanza quando si calcola la distanza da ogni suffisso a ogni suffisso.

La funzione di distanza è la distanza di Levenshtein, ma saremo in grado di personalizzarla in futuro al fine di tenere conto della probabilità di essere di un certo tipo, invece di avere un tipo fisso per ogni token.

Per rimanere semplici in quella dimostrazione, useremo i token di tipo fisso e useremo il classico Levenshtein per calcolare la distanza tra i token.

ad es. Diamo la sequenza di input ABCGDEFGH ABCDEFGH ABCDNEFGH.

Calcoliamo la distanza di ogni suffisso con ogni suffisso (ritagliato per essere della stessa dimensione):

for i = 0 to sequence.lengh
  for j = i to sequence.lengh
    # Create the suffixes
    suffixA = sequence.substr(i)
    suffixB = sequence.substr(j)
    # Make the suffixes the same size
    chunkLen = Math.min(suffixA.length, suffixB.length)
    suffixA = suffixA.substr(0, chunkLen)
    suffixB = suffixB.substr(0, chunkLen)
    # Compute the distance
    distance[i][j] = LevenshteinDistance(suffixA, suffixB)

Otteniamo ad esempio il seguente risultato (il bianco è una piccola distanza, il nero è grande):

Ora, è ovvio che qualsiasi suffisso rispetto a se stesso avrà una distanza nulla. Ma non siamo interessati al suffisso (esattamente o parzialmente) che si abbina da solo, quindi ritagliamo quella parte.

Poiché i suffissi sono ritagliati delle stesse dimensioni, il confronto di una stringa lunga produce sempre una distanza maggiore rispetto al confronto di stringhe più piccole.

Dobbiamo compensarlo con una penalità regolare a partire da destra (+ P), sfumando linearmente a sinistra.

Non sono ancora sicuro di come scegliere una buona funzione di penalità adatta a tutti i casi.

Qui applichiamo una penalità (+ P = 6) all'estrema destra, sfumando a 0 a sinistra.

Ora possiamo vedere chiaramente emergere 2 chiare linee diagonali. Ci sono 3 oggetti (Item1, Item2, Item3) in quella sequenza. La linea più lunga rappresenta la corrispondenza tra Item1 vs Item2 e Item2 vs Item3. Il secondo più lungo rappresenta la corrispondenza tra Item1 e Item3.

Ora non sono sicuro del modo migliore di sfruttare quei dati. È semplice come prendere le linee diagonali più alte?

Supponiamo che lo sia.

Calcoliamo il valore medio della linea diagonale che inizia da ciascun token. Possiamo vedere il risultato nella seguente immagine (il vettore sotto la matrice):

Esistono chiaramente 3 minimi locali, che corrispondono all'inizio di ogni articolo. Sembra fantastico!

Ora aggiungiamo alcune altre imperfezioni nella sequenza: ABCGDEFGH ABCDEFGH TROLL ABCDEFGH

Chiaramente ora, il nostro vettore di medie diagonali è incasinato e non possiamo più sfruttarlo ...

La mia ipotesi è che ciò potrebbe essere risolto da una funzione di distanza personalizzata (anziché da Levenshtein), in cui l'inserimento di un intero blocco potrebbe non essere così penalizzato. Questo è ciò di cui non sono sicuro.

Conclusione

Nessuna delle soluzioni basate sulla convoluzione esplorate sembra adattarsi al nostro problema.

La soluzione basata sulla distanza Levenshtein sembra promettente, soprattutto perché è compatibile con i token di tipo basato sulla probabilità. Ma non sono ancora sicuro di come sfruttarne i risultati.

Le sarei molto grato se hai esperienza in un campo correlato e un paio di buoni suggerimenti da darci o altre tecniche da esplorare. Grazie mille in anticipo.


Hai considerato di utilizzare un modello autoregressivo di qualche tipo? en.wikipedia.org/wiki/Autoregressive_model
jcrudy

Non capisco davvero cosa vuoi e perché. Ma forse gli algoritmi di compressione possono aiutare in qualche modo.
Gerenuk,

1
Ho aggiunto una sperimentazione che ho fatto oggi, basandomi sulla distanza di Levenshtein. Sembra promettente. Inoltre, ho modificato un po 'l'introduzione, quindi spero sia più chiara. Grazie per i tuoi suggerimenti, darò un'occhiata.
OoDeLally,

@Gerenuk Un commento così meraviglioso!
uhbif19

Risposte:


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.