English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

ospite
1 / ?
torna alle lezioni

Benvenuto

Benvenuto in una lezione di NLP pratica.

Stai per costruire uno stemmer inglese funzionante da zero: un algoritmo che riduce le parole alla loro forma radicale.

Alla fine, avrai un vero algoritmo testato che trasforma parole come runningrun, happinesshappi, & organizationalorgan.

Scriverai anche test unitari, test di integrazione e test funzionali per il tuo stemmer: perché un algoritmo non testato è solo un'ipotesi.

Che cos'è lo Stemming?

Il problema

I motori di ricerca affrontano un problema fondamentale: un utente cerca "running" ma il documento contiene "run" o "runs" o "runner". Sono tutti lo stesso concetto: ma sono stringhe diverse.


Lo stemming riduce le parole flesse a una forma base comune (lo stem). Non deve essere una parola reale: deve solo essere coerente.


ParolaStem
runningrun
runsrun
runnerrunner
happinesshappi
happilyhappi
happyhappi

Nota che happi non è una parola inglese vera. Non c'è problema. Lo stemming riguarda il raggruppamento, non il significato. Finché happiness, happily, & happy collassano tutti nello stesso stem, la ricerca & il recupero migliorano.

Stemming: Molte Forme Collassano in Un Solo Stem, Migliorando i Risultati di Ricerca

Con le tue parole, spiega perché un motore di ricerca che utilizza lo stemming restituirebbe risultati migliori di uno che corrisponde solo a stringhe esatte. Dai un esempio concreto.

Zellig Harris e l'Analisi Distribuzionale

L'origine dello stemming computazionale

Nel 1955, il linguista Zellig Harris ha pubblicato From Phoneme to Morpheme, descrivendo un metodo per trovare i confini tra unità significative (morfemi) nelle parole.


La sua intuizione era distribuzionale: se guardi un grande corpus di parole inglesi, il confine tra uno stem e un suffisso si presenta come un segnale statistico.


Il metodo della varietà di successore

Per qualsiasi prefisso di una parola, conta quanti caratteri distinti lo seguono nel corpus. Harris ha chiamato questo la varietà di successore.


Considera il prefisso "work" in un corpus contenente: worked, worker, working, works, workshop.


PrefissoChe segueVarietà di successore
wo1
wor1
work1
worke, i, s, sh4
worked, r2

Dopo "work", quattro caratteri diversi possono seguire: un'impennata nella varietà. Quella picco contrassegna un confine di morfema. Lo stem è work e tutto quello che segue è un suffisso.


Questo era radicale nel 1955. Nessuna regola linguistica, nessun dizionario: solo conteggio. Harris ha dimostrato che la struttura del linguaggio si rivela attraverso la distribuzione.

Harris Successor Variety: Spike at 'work' marks the morpheme boundary

Comprendere la Varietà di Successore

Il metodo di Harris funziona in qualsiasi lingua. Non devi conoscere la grammatica: le statistiche rivelano i confini dei morfemi.


In pratica, la pura varietà di successore richiede un corpus ampio e un'attenta rilevazione dei picchi. Ricercatori successivi: Lovins (1968), Porter (1980): hanno semplificato l'approccio nello stripping dei suffissi basato su regole: invece di calcolare la varietà di successore da un corpus, hanno codificato direttamente le regole dei suffissi.


Oggi costruirai uno strumento di stripping dei suffissi basato su regole ispirato dall'intuizione di Harris. Definirai i suffissi esplicitamente, quindi li stripperai dalle parole. È così che funzionano la maggior parte degli stemmer di produzione.

Qual è l'intuizione chiave del metodo della varietà di successore di Harris? In altre parole, quale segnale statistico ti dice dove si trova un confine di morfema?

Il Tuo Primo Strumento di Stripping dei Suffissi

Iniziamo a programmare

Inizia in modo semplice. Scrivi una funzione chiamata stem che accetta una parola & rimuove questi suffissi (in questo ordine):


1. -ing (running → runn)

2. -ed (walked → walk)

3. -ly (quickly → quick)

4. -s (cats → cat)


Regole:

- Converti la parola in minuscole per primo

- Rimuovi solo un suffisso (il primo match nell'ordine sopra)

- Rimuovi solo se lo stem rimanente è lungo almeno 3 caratteri

- Restituisci lo stem


Esempio:

def stem(word):
    word = word.lower()
    # your suffix stripping logic here
    return word
Scrivi la funzione `stem`. Dovrebbe rimuovere -ing, -ed, -ly, o -s (primo match, in ordine) dalla parola, ma solo se lo stem rimanente ha 3 o più caratteri. Testalo stampando stem('running'), stem('walked'), stem('quickly'), & stem('cats').

Gestione dei Casi Limite

Rendere lo stemmer più intelligente

Il tuo strumento di base ha un problema: runningrunn & hopinghop. Abbiamo bisogno di due raffinamenti:


1. Pulizia consonante doppia: se la rimozione di -ing o -ed lascia una consonante doppia alla fine (come runn), rimuovi l'ultima lettera → run

2. Ripristino di e silenziosa: se la rimozione di -ing lascia uno stem che termina con una consonante (non una vocale), & l'originale potrebbe aver avuto una e silenziosa (come hop da hoping), aggiungi e indietro → hope


Per la regola di e silenziosa, mantienila semplice: se dopo la rimozione di -ing, lo stem è 3+ caratteri, termina con una consonante, & il penultimo carattere è una vocale (un pattern come hop, mak, tak), aggiungi e indietro.


Aggiungi anche questi nuovi suffissi (controllali prima di -ing, -ed, -ly, -s):

5. -tion (organization → organiza)

6. -ness (happiness → happi)

7. -ment (movement → move)

8. -able (readable → read)

9. -ible (sensible → sens)


Priorità dei suffissi aggiornata: -tion, -ness, -ment, -able, -ible, -ing, -ed, -ly, -s


Mantieni la regola della lunghezza minima dello stem: rimuovi solo se lo stem rimanente è 3+ caratteri.

Aggiorna la tua funzione `stem` con i nuovi suffissi, la pulizia consonante doppia, & il ripristino di e silenziosa. Stampa i risultati per: stem('running'), stem('hoping'), stem('happiness'), stem('organization'), stem('readable').

Regole -ies & -ier

Più morfologia

L'inglese ha un altro pattern comune: le parole che terminano in -y cambiano in -ies, -ied, o -ier quando sono flesse.


ParolaDovrebbe stem a
babiesbabi
carriedcarri
earlierearli
fliesfli
studiedstudi

Aggiungi queste regole prima dei controlli -s & -ed:

- -ies → rimuovi & aggiungi i (babies → babi)

- -ied → rimuovi & aggiungi i (carried → carri)

- -ier → rimuovi & aggiungi i (earlier → earli)


Stessa regola della lunghezza minima dello stem: trasforma solo se il risultato è 3+ caratteri.

Aggiungi le regole -ies, -ied, & -ier alla tua funzione stem. Stampa i risultati per: stem('babies'), stem('carried'), stem('earlier'), stem('happiness'), stem('running').

Perché Testare?

I test non sono opzionali

Hai uno stemmer che funziona. Come sai che in realtà funziona? Proprio ora, stai eseguendo alcuni esempi a mano. Questo non scala.


Il software professionale utilizza tre livelli di test:


Test unitari: testano una funzione in isolamento con input noti e output attesi. Veloci, numerosi, specifici.


Test di integrazione: testano che più componenti funzionino insieme. Per uno stemmer, questo significa testarlo su un lotto di parole e verificare che i risultati siano coerenti.


Test funzionali: testano il sistema dall'esterno, come farebbe un utente. Per uno stemmer, questo significa alimentarlo con testo reale e verificare che l'output abbia senso per un caso d'uso reale come la ricerca.


Scriverai tutti e tre.

Three Levels of Testing: Unit, Integration, and Functional Test Pyramid

Scrivi i Test Unitari

Test unitari

Scrivi una funzione chiamata run_unit_tests che testa la tua funzione stem con almeno 15 casi di test coprendo:


1. Stripping dei suffissi di base: parole che terminano in -ing, -ed, -ly, -s

2. Suffissi complessi: -tion, -ness, -ment, -able, -ible

3. Flessione Y: -ies, -ied, -ier

4. Casi limite: parole corte che non dovrebbero essere rimosse, parole senza suffisso, parole già stemmate

5. Pulizia consonante doppia: running → run, sitting → sit

6. Ripristino di e silenziosa: hoping → hope

7. Insensibilità alle maiuscole: l'input maiuscolo dovrebbe essere minuscolo


Struttura i tuoi test così:

def run_unit_tests():
    tests = [
        ('running', 'run'),
        ('cats', 'cat'),
        # ... almeno 15 casi di test
    ]
    passed = 0
    failed = 0
    for word, expected in tests:
        result = stem(word)
        if result == expected:
            passed += 1
        else:
            failed += 1
            print(f'FAIL: stem({word}) = {result}, expected {expected}')
    print(f'{passed}/{passed + failed} unit tests passed')
    return failed == 0
Includi la tua funzione `stem` completa E scrivi `run_unit_tests` con almeno 15 casi di test coprendo tutte le 7 categorie sopra. Chiama `run_unit_tests()` alla fine.

Scrivi i Test di Integrazione

Test di integrazione

I test unitari verificano input individuali. I test di integrazione verificano che i componenti funzionino insieme correttamente.


Per uno stemmer, una proprietà di integrazione chiave è coerenza: se stemmi la stessa parola due volte, ottieni lo stesso risultato. E le parole che dovrebbero raggrupparsi dovrebbero produrre lo stesso stem.


Scrivi una funzione chiamata run_integration_tests che testa:


1. Idempotenza: fare uno stemming di una parola già stemmate dovrebbe restituire lo stesso stem. stem(stem(word)) == stem(word) per tutte le parole.

2. Raggruppamento: le parole che dovrebbero condividere uno stem effettivamente lo fanno. Testa almeno 3 famiglie di parole (ad es., run/runs/running/runner dovrebbero tutti condividere uno stem).

3. Elaborazione batch: elabora un elenco di 20+ parole e verifica nessun arresto anomalo, nessuna stringa vuota, nessun valore None.


def run_integration_tests():
    # Test 1: idempotency
    # Test 2: word family grouping
    # Test 3: batch stability
    ...
Includi la tua funzione `stem` & scrivi `run_integration_tests` con tutte e tre le categorie di test. Chiamala alla fine.

Scrivi i Test Funzionali

Test funzionali

I test funzionali verificano che il sistema funzioni per il suo caso d'uso previsto. Il tuo stemmer esiste per migliorare la ricerca: quindi testalo.


Scrivi una funzione chiamata run_functional_tests che:


1. Simulazione di ricerca: dato un elenco di stringhe di documenti e una parola di query, stemma sia i documenti che la query, quindi controlla se i termini della query stemmate appaiono nei documenti stemmate. Testa che cercare 'running' trovi un documento contenente 'run' e 'runner'.

2. Controllo della precisione: verifica che lo stemming NON raggruppi erroneamente le parole non correlate. 'university' e 'universe' potrebbero condividere uno stem: controlla come il tuo stemmer gestisce questo (va bene se li raggruppa; documenta il comportamento).

3. Elaborazione di testo reale: stemma ogni parola in un paragrafo di testo inglese reale. Verifica che l'output sia ragionevole: nessuna stringa vuota, nessun arresto anomalo, l'output ha lo stesso numero di parole dell'input.


def run_functional_tests():
    # Test 1: search finds related documents
    # Test 2: precision: check over-stemming
    # Test 3: real paragraph processing
    ...
Includi la tua funzione `stem` & scrivi `run_functional_tests` con tutte e tre le categorie di test. Chiamala alla fine.

Quello che Hai Costruito

Quello che hai costruito

Hai implementato uno stemmer inglese funzionante con:

- 12 regole di suffisso (-tion, -ness, -ment, -able, -ible, -ies, -ied, -ier, -ing, -ed, -ly, -s)

- Pulizia consonante doppia

- Ripristino di e silenziosa

- Test unitari, test di integrazione, & test funzionali


La discendenza

Il tuo stemmer discende da una linea di lavoro iniziata con Zellig Harris nel 1955:


- Harris (1955): Ha scoperto che i confini dei morfemi mostrano come segnali statistici (varietà di successore)

- Lovins (1968): Primo algoritmo di stemming pubblicato, 294 regole di suffisso

- Porter (1980): Semplificato a ~60 regole in 5 passaggi, divenne lo standard per decenni

- Snowball (2001): Quadro di Porter generalizzato a più lingue

- Il tuo stemmer (oggi): 12 regole, lo stesso principio fondamentale


Cosa potresti fare dopo

- Implementare l'algoritmo Porter completo (è ben documentato & un grande esercizio)

- Portare il tuo stemmer in C per un miglioramento della velocità di 100x

- Costruire un semplice motore di ricerca che utilizza il tuo stemmer per indicizzare & interrogare i file di testo

- Confrontare l'output del tuo stemmer con PorterStemmer di NLTK per misurare l'accuratezza


Il codice che hai scritto oggi è la stessa operazione fondamentale che viene eseguita all'interno di ogni motore di ricerca del pianeta. Non male per un giorno di lavoro.

Stemmer Lineage: Harris 1955 through Snowball 2001

Rifletti su quello che hai costruito. Quale è stata la cosa più sorprendente che hai imparato? Se dovessi migliorare il tuo stemmer, cosa aggiungeresti o cambieresti?